elftools 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +82 -0
- data/lib/elftools/constants.rb +44 -0
- data/lib/elftools/dynamic/hash_table.rb +16 -0
- data/lib/elftools/dynamic/symbols.rb +45 -17
- data/lib/elftools/dynamic/versions.rb +87 -0
- data/lib/elftools/dynamic.rb +21 -4
- data/lib/elftools/elf_file.rb +1 -0
- data/lib/elftools/relative_relocations.rb +88 -0
- data/lib/elftools/relocation.rb +50 -0
- data/lib/elftools/sections/relative_relocation_section.rb +46 -0
- data/lib/elftools/sections/section.rb +31 -0
- data/lib/elftools/sections/sections.rb +8 -0
- data/lib/elftools/sections/sym_tab_section.rb +39 -7
- data/lib/elftools/sections/symbol.rb +91 -1
- data/lib/elftools/sections/version_definition_section.rb +40 -0
- data/lib/elftools/sections/version_need_section.rb +40 -0
- data/lib/elftools/sections/version_section.rb +44 -0
- data/lib/elftools/structs.rb +109 -16
- data/lib/elftools/util.rb +68 -11
- data/lib/elftools/version.rb +1 -1
- data/lib/elftools/version_tables.rb +155 -0
- metadata +9 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0213b757f8eab7a9a4806fdca95e68e8ac3f64fc226c69cf272d071def187172
|
|
4
|
+
data.tar.gz: 891db5a15bc0b44c09d0135a0c5b8f95633fa49b07d18e8e2a563f8384842f90
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7a10ccb7a4d7b4b7c9bdc30aca01a330848ea0c9aa9a34b310e315acd5823312f413b84b9ddc72c479d3363494960abfd41b10857a34c6402fec2958ee039e64
|
|
7
|
+
data.tar.gz: 57902a64f1450d229bff599d7a3fac08f3f8e1f80490924a2d1b959404e90aa68880d92437a5d1a55e2d19c88ea8028c954fa209c51f6037117b4f1057e1eabb
|
data/README.md
CHANGED
|
@@ -97,6 +97,15 @@ symbols.map(&:name).reject(&:empty?).first(5).join(' ')
|
|
|
97
97
|
#=> "crtstuff.c __JCR_LIST__ deregister_tm_clones register_tm_clones __do_global_dtors_aux"
|
|
98
98
|
```
|
|
99
99
|
|
|
100
|
+
Where a symbol is and how large it is:
|
|
101
|
+
```ruby
|
|
102
|
+
main = symtab_section.symbol_by_name('main')
|
|
103
|
+
'%#x' % main.value
|
|
104
|
+
#=> "0x4006dd"
|
|
105
|
+
main.size
|
|
106
|
+
#=> 142
|
|
107
|
+
```
|
|
108
|
+
|
|
100
109
|
What a symbol refers to and how it is linked are recorded in `st_info` and `st_other`,
|
|
101
110
|
values are defined in `ELFTools::Constants::STT`, `STB`, and `STV` respectively.
|
|
102
111
|
```ruby
|
|
@@ -139,6 +148,33 @@ dynamic.relocations.map { |rel| dynamic.symbol_at(rel.symbol_index).name }.first
|
|
|
139
148
|
#=> ["__gmon_start__", "stdin", "puts"]
|
|
140
149
|
```
|
|
141
150
|
|
|
151
|
+
A symbol of a file that is loaded binds to a version of the name, which is how one file
|
|
152
|
+
offers `memcpy` twice and each caller keeps the one it was built against. The name is left
|
|
153
|
+
as the file records it.
|
|
154
|
+
```ruby
|
|
155
|
+
dynamic.symbol_by_name('printf').version
|
|
156
|
+
#=> "GLIBC_2.2.5"
|
|
157
|
+
dynamic.symbol_by_name('__stack_chk_fail').version
|
|
158
|
+
#=> "GLIBC_2.4"
|
|
159
|
+
|
|
160
|
+
# What the file needs, without walking a symbol at all.
|
|
161
|
+
dynamic.version_requirements.map { |need| [need.file, need.versions.map(&:name)] }
|
|
162
|
+
#=> [["libc.so.6", ["GLIBC_2.4", "GLIBC_2.2.5"]]]
|
|
163
|
+
|
|
164
|
+
# The sections record the same, for a file that still has them.
|
|
165
|
+
elf.sections_by_type(:gnu_verneed).first.requirements.first.file
|
|
166
|
+
#=> "libc.so.6"
|
|
167
|
+
elf.section_by_name('.dynsym').symbol_by_name('printf').version
|
|
168
|
+
#=> "GLIBC_2.2.5"
|
|
169
|
+
|
|
170
|
+
# What a library defines, the first naming the library rather than a version of it.
|
|
171
|
+
libc = ELFTools::ELFFile.new(File.open('spec/files/libc.so.6'))
|
|
172
|
+
libc.dynamic.version_definitions.map(&:name).first(3)
|
|
173
|
+
#=> ["libc.so.6", "GLIBC_2.2.5", "GLIBC_2.2.6"]
|
|
174
|
+
libc.dynamic.version_definitions[2].parents
|
|
175
|
+
#=> ["GLIBC_2.2.5"]
|
|
176
|
+
```
|
|
177
|
+
|
|
142
178
|
Nothing a file is loaded by records how large its symbol table is, because the loader
|
|
143
179
|
looks a name up through a hash table and jumps straight to an index rather than ever
|
|
144
180
|
enumerating it. `num_symbols` is therefore how far the hash table and the relocations
|
|
@@ -159,6 +195,19 @@ libc.dynamic.symbol_by_name('malloc').type_name
|
|
|
159
195
|
#=> "STT_FUNC"
|
|
160
196
|
```
|
|
161
197
|
|
|
198
|
+
What a section is for is recorded in `sh_flags`, as a segment records it in `p_flags`.
|
|
199
|
+
```ruby
|
|
200
|
+
[elf.section_by_name('.text').executable?, elf.section_by_name('.text').writable?]
|
|
201
|
+
#=> [true, false]
|
|
202
|
+
|
|
203
|
+
# Only some of the sections take memory while the file runs, the rest being what is
|
|
204
|
+
# recorded about it.
|
|
205
|
+
elf.sections.select(&:allocated?).map(&:name).first(5).join(' ')
|
|
206
|
+
#=> ".interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym"
|
|
207
|
+
elf.sections.reject(&:allocated?).map(&:name).reject(&:empty?).join(' ')
|
|
208
|
+
#=> ".comment .shstrtab .symtab .strtab"
|
|
209
|
+
```
|
|
210
|
+
|
|
162
211
|
## Segments
|
|
163
212
|
|
|
164
213
|
```ruby
|
|
@@ -228,6 +277,19 @@ elf.dynamic.relocations.map(&:type_name).uniq
|
|
|
228
277
|
#=> ["R_X86_64_GLOB_DAT", "R_X86_64_COPY", "R_X86_64_JUMP_SLOT"]
|
|
229
278
|
```
|
|
230
279
|
|
|
280
|
+
Nearly every relocation of a file that is loaded only adds the load bias to a word, so a
|
|
281
|
+
file may pack them into a bitmap instead of spending an entry on each. They are read with
|
|
282
|
+
the rest, from the tags or from the section holding them, and are named after the machine
|
|
283
|
+
because the bitmap records no type of its own.
|
|
284
|
+
```ruby
|
|
285
|
+
packed = ELFTools::ELFFile.new(File.open('spec/files/aarch64.relr.elf'))
|
|
286
|
+
packed.dynamic.relocations.count { |rel| rel.type_name == 'R_AARCH64_RELATIVE' }
|
|
287
|
+
#=> 132
|
|
288
|
+
section = packed.sections_by_type(:relr).first
|
|
289
|
+
[section.name, section.header.sh_size, section.num_relocations]
|
|
290
|
+
#=> [".relr.dyn", 48, 132]
|
|
291
|
+
```
|
|
292
|
+
|
|
231
293
|
## Patch
|
|
232
294
|
|
|
233
295
|
Patch ELF is so easy!
|
|
@@ -256,6 +318,26 @@ interp_segment.interp_name
|
|
|
256
318
|
# save the patched ELF
|
|
257
319
|
elf.save('elf.patched')
|
|
258
320
|
|
|
321
|
+
Values that share a byte with others, which a symbol and a relocation both record, are
|
|
322
|
+
assigned as what they mean rather than as the bits holding them. A value too large for
|
|
323
|
+
its bits is reported instead of being written over its neighbours.
|
|
324
|
+
```ruby
|
|
325
|
+
elf = ELFTools::ELFFile.new(File.open('spec/files/amd64.elf'))
|
|
326
|
+
symbol = elf.section_by_name('.symtab').symbol_by_name('main')
|
|
327
|
+
symbol.type = ELFTools::Constants::STT_OBJECT
|
|
328
|
+
symbol.bind = ELFTools::Constants::STB_WEAK
|
|
329
|
+
[symbol.type_name, symbol.bind_name]
|
|
330
|
+
#=> ["STT_OBJECT", "STB_WEAK"]
|
|
331
|
+
symbol.bind = 16
|
|
332
|
+
#=> ArgumentError: Symbol binding must be in 0..15, got 16
|
|
333
|
+
|
|
334
|
+
relocation = elf.dynamic.relocations.first
|
|
335
|
+
relocation.symbol_index = 3
|
|
336
|
+
relocation.type = ELFTools::Constants::R::X86_64::R_X86_64_JUMP_SLOT
|
|
337
|
+
[relocation.symbol_index, relocation.type_name]
|
|
338
|
+
#=> [3, "R_X86_64_JUMP_SLOT"]
|
|
339
|
+
```
|
|
340
|
+
|
|
259
341
|
# in bash
|
|
260
342
|
# $ file elf.patched
|
|
261
343
|
# elf.patched: ELF 64-bit LSB executable, ARM, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86, for GNU...
|
data/lib/elftools/constants.rb
CHANGED
|
@@ -288,6 +288,23 @@ module ELFTools
|
|
|
288
288
|
end
|
|
289
289
|
include EM
|
|
290
290
|
|
|
291
|
+
# Flags of a version, recorded in the +vd_flags+ of a definition and the
|
|
292
|
+
# +vna_flags+ of a requirement.
|
|
293
|
+
module VER_FLG
|
|
294
|
+
VER_FLG_BASE = 0x1 # The version the file itself is, rather than one of the versions it defines
|
|
295
|
+
VER_FLG_WEAK = 0x2 # A version no symbol is bound to
|
|
296
|
+
VER_FLG_INFO = 0x4 # A version recorded for information rather than to be matched
|
|
297
|
+
end
|
|
298
|
+
include VER_FLG
|
|
299
|
+
|
|
300
|
+
# The indices a symbol names a version with that name no version.
|
|
301
|
+
module VER_NDX
|
|
302
|
+
VER_NDX_LOCAL = 0 # A symbol of the file itself, which nothing outside it binds to
|
|
303
|
+
VER_NDX_GLOBAL = 1 # A symbol of no version at all
|
|
304
|
+
VER_NDX_HIDDEN = 0x8000 # Not an index but a bit of one, marking a version that is not the default
|
|
305
|
+
end
|
|
306
|
+
include VER_NDX
|
|
307
|
+
|
|
291
308
|
# Relocation types, see +elftools/constants/relocation+ for the constants.
|
|
292
309
|
module R
|
|
293
310
|
# Return the name of a relocation type.
|
|
@@ -310,6 +327,33 @@ module ELFTools
|
|
|
310
327
|
names&.fetch(type, nil) || format('<unknown>: 0x%x', type)
|
|
311
328
|
end
|
|
312
329
|
|
|
330
|
+
# The type a machine calls a relocation that only adds the load bias,
|
|
331
|
+
# which every architecture defining one spells +R_<arch>_RELATIVE+.
|
|
332
|
+
#
|
|
333
|
+
# A table of them recorded as +DT_RELR+ names no type, because the
|
|
334
|
+
# format holds nothing but addresses and every one of them relocates
|
|
335
|
+
# this way, so the type is asked of the machine instead.
|
|
336
|
+
# @param [Integer?] machine Value of +e_machine+.
|
|
337
|
+
# @return [Integer, nil]
|
|
338
|
+
# The type, +nil+ if the machine names no such relocation.
|
|
339
|
+
# @example
|
|
340
|
+
# relative(Constants::EM_X86_64)
|
|
341
|
+
# #=> 8 # R_X86_64_RELATIVE
|
|
342
|
+
# relative(Constants::EM_AARCH64)
|
|
343
|
+
# #=> 1027 # R_AARCH64_RELATIVE, not the R_AARCH64_P32_RELATIVE of ILP32
|
|
344
|
+
def self.relative(machine)
|
|
345
|
+
architecture = MACHINES[machine]
|
|
346
|
+
return if architecture.nil?
|
|
347
|
+
|
|
348
|
+
@relative ||= {}
|
|
349
|
+
@relative.fetch(architecture) do
|
|
350
|
+
names = const_get(architecture).constants.grep(/_RELATIVE\z/)
|
|
351
|
+
# An architecture naming more than one names the other for a second
|
|
352
|
+
# data model, which spells it out in the name and so is longer.
|
|
353
|
+
@relative[architecture] = names.min_by(&:length)&.then { |name| const_get(architecture).const_get(name) }
|
|
354
|
+
end
|
|
355
|
+
end
|
|
356
|
+
|
|
313
357
|
# Names of every relocation type an architecture defines.
|
|
314
358
|
# @return [Hash{Integer => String}]
|
|
315
359
|
def self.names_of(architecture)
|
|
@@ -24,6 +24,17 @@ module ELFTools
|
|
|
24
24
|
@endian = endian
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
+
# Whether the table is built over every symbol rather than over a subset
|
|
28
|
+
# of them.
|
|
29
|
+
#
|
|
30
|
+
# Two things follow where it is. How many symbols it is built over is how
|
|
31
|
+
# many there are, rather than how far it reaches. And a name it does not
|
|
32
|
+
# lead to is not one the file records, so nothing is left to search.
|
|
33
|
+
# @return [Boolean] The answer.
|
|
34
|
+
def covers_every_symbol?
|
|
35
|
+
false
|
|
36
|
+
end
|
|
37
|
+
|
|
27
38
|
private
|
|
28
39
|
|
|
29
40
|
# The header the table starts with.
|
|
@@ -66,6 +77,11 @@ module ELFTools
|
|
|
66
77
|
header.nchain.to_i
|
|
67
78
|
end
|
|
68
79
|
|
|
80
|
+
# (see ELFTools::Dynamic::HashTable#covers_every_symbol?)
|
|
81
|
+
def covers_every_symbol?
|
|
82
|
+
true
|
|
83
|
+
end
|
|
84
|
+
|
|
69
85
|
# The index a name sits at.
|
|
70
86
|
#
|
|
71
87
|
# A bucket leads to a chain of the indices whose names hash alike, so
|
|
@@ -30,25 +30,26 @@ module ELFTools
|
|
|
30
30
|
|
|
31
31
|
@symbol_at_map ||= {}
|
|
32
32
|
@symbol_at_map[n] ||= begin
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
sym = read_struct(klass, sym_offset + (n * struct(klass).num_bytes))
|
|
37
|
-
Sections::Symbol.new(sym, stream, symstr: method(:string_table), machine: @machine)
|
|
33
|
+
sym = read_struct(Structs::ELF_sym[header.elf_class], sym_offset + (n * sym_entsize))
|
|
34
|
+
Sections::Symbol.new(sym, stream, symstr: method(:string_table), machine: @machine,
|
|
35
|
+
version: -> { version_at(n) })
|
|
38
36
|
end
|
|
39
37
|
end
|
|
40
38
|
|
|
41
39
|
# How many symbols the tags reach.
|
|
42
40
|
#
|
|
43
|
-
#
|
|
44
|
-
#
|
|
45
|
-
# jumps straight to an index, so where the table ends is none of its
|
|
46
|
-
# business. Two things bound it instead, the hash table that indexes the
|
|
47
|
-
# names a file exports and the relocations that name a symbol by index,
|
|
48
|
-
# and the answer is how far the further of the two reaches. Only +DT_HASH+
|
|
49
|
-
# records the number outright.
|
|
41
|
+
# A hash table that records the number outright is answered with, because
|
|
42
|
+
# nothing a file records can reach further than the table it counts.
|
|
50
43
|
#
|
|
51
|
-
#
|
|
44
|
+
# Where the file records no such table, nothing records how large its
|
|
45
|
+
# symbol table is. The loader never enumerates it: it looks a name up
|
|
46
|
+
# through a hash table and jumps straight to an index, so where the table
|
|
47
|
+
# ends is none of its business. Two things bound it instead, the hash
|
|
48
|
+
# table that indexes the names a file exports and the relocations that
|
|
49
|
+
# name a symbol by index, and the answer is how far the further of the two
|
|
50
|
+
# reaches.
|
|
51
|
+
#
|
|
52
|
+
# That answer is a lower bound. A symbol that is neither indexed by the
|
|
52
53
|
# hash table nor named by a relocation is invisible to both, and is
|
|
53
54
|
# missing from the count. {#symbol_at} is exact for any index.
|
|
54
55
|
# @return [Integer] The number.
|
|
@@ -56,7 +57,7 @@ module ELFTools
|
|
|
56
57
|
# elf.dynamic.num_symbols
|
|
57
58
|
# #=> 9
|
|
58
59
|
def num_symbols
|
|
59
|
-
@num_symbols ||=
|
|
60
|
+
@num_symbols ||= counted_num_symbols || bounded_num_symbols
|
|
60
61
|
end
|
|
61
62
|
|
|
62
63
|
# Iterate all symbols.
|
|
@@ -92,9 +93,11 @@ module ELFTools
|
|
|
92
93
|
# Get symbol by its name.
|
|
93
94
|
#
|
|
94
95
|
# The hash tables answer first, which is the lookup the loader itself
|
|
95
|
-
# performs and takes no scanning.
|
|
96
|
-
#
|
|
97
|
-
#
|
|
96
|
+
# performs and takes no scanning. Where one of them is built over every
|
|
97
|
+
# symbol its answer is the whole answer, and a name it does not lead to
|
|
98
|
+
# is not one the file records. Otherwise the name is searched for among
|
|
99
|
+
# the symbols {#symbols} reaches, because a table need only index the
|
|
100
|
+
# names a file exports and a file need not record one at all.
|
|
98
101
|
# @param [String] name The name of symbol.
|
|
99
102
|
# @return [ELFTools::Sections::Symbol, nil] The desired symbol.
|
|
100
103
|
# @example
|
|
@@ -105,6 +108,9 @@ module ELFTools
|
|
|
105
108
|
# not led anywhere.
|
|
106
109
|
index = hash_tables.lazy.filter_map { |table| table.index_of(name) { |i| symbol_at(i).name == name } }.first
|
|
107
110
|
return symbol_at(index) if index
|
|
111
|
+
# A symbol with no name is the one thing such a table leaves out,
|
|
112
|
+
# having nothing to be indexed by, so it is still searched for.
|
|
113
|
+
return if !name.empty? && hash_tables.any?(&:covers_every_symbol?)
|
|
108
114
|
|
|
109
115
|
each_symbol.find { |symbol| symbol.name == name }
|
|
110
116
|
end
|
|
@@ -121,6 +127,20 @@ module ELFTools
|
|
|
121
127
|
end
|
|
122
128
|
end
|
|
123
129
|
|
|
130
|
+
# How many symbols a table that counts them says there are.
|
|
131
|
+
# @return [Integer, nil] The number, +nil+ if the file records no such table.
|
|
132
|
+
def counted_num_symbols
|
|
133
|
+
hash_tables.find(&:covers_every_symbol?)&.num_symbols
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# How far what the file records reaches, for a file that counts its
|
|
137
|
+
# symbols nowhere. Reading the relocations is what costs, so it is only
|
|
138
|
+
# done for such a file.
|
|
139
|
+
# @return [Integer] The number, zero if nothing reaches a symbol.
|
|
140
|
+
def bounded_num_symbols
|
|
141
|
+
(hash_tables.map(&:num_symbols) + [count_from_relocations]).compact.max || 0
|
|
142
|
+
end
|
|
143
|
+
|
|
124
144
|
# How far the relocations reach, i.e. the highest index they name plus one.
|
|
125
145
|
# They only ever name the symbols something in the file refers to.
|
|
126
146
|
# @return [Integer, nil] The number, +nil+ if none names a symbol.
|
|
@@ -129,6 +149,14 @@ module ELFTools
|
|
|
129
149
|
highest && highest + 1
|
|
130
150
|
end
|
|
131
151
|
|
|
152
|
+
# How many bytes an entry of the symbol table takes, which is what its
|
|
153
|
+
# structure takes, which is also what +DT_SYMENT+ records and what a file
|
|
154
|
+
# has no way of disagreeing with.
|
|
155
|
+
# @return [Integer] The number.
|
|
156
|
+
def sym_entsize
|
|
157
|
+
@sym_entsize ||= struct(Structs::ELF_sym[header.elf_class]).num_bytes
|
|
158
|
+
end
|
|
159
|
+
|
|
132
160
|
# Get the +DT_SYMTAB+'s +d_val+ offset related to file.
|
|
133
161
|
# @return [Integer] The file offset.
|
|
134
162
|
# @raise [ELFTools::ELFError]
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'elftools/constants'
|
|
4
|
+
require 'elftools/version_tables'
|
|
5
|
+
|
|
6
|
+
module ELFTools
|
|
7
|
+
module Dynamic
|
|
8
|
+
# The versions a file binds its symbols to.
|
|
9
|
+
#
|
|
10
|
+
# Two tables record them, the versions the file needs of the files it is
|
|
11
|
+
# loaded with and the versions it defines for what it exports, and a symbol
|
|
12
|
+
# names one of either by the same index.
|
|
13
|
+
#
|
|
14
|
+
# @note
|
|
15
|
+
# This module is included by {ELFTools::Dynamic} and reads through the
|
|
16
|
+
# methods there, so it cannot be included on its own.
|
|
17
|
+
module Versions
|
|
18
|
+
# The versions this file needs of the files it is loaded with.
|
|
19
|
+
# @return [Array<ELFTools::VersionTables::Requirement>]
|
|
20
|
+
# The requirements, in the order the table records them.
|
|
21
|
+
# @example
|
|
22
|
+
# elf.dynamic.version_requirements.map { |need| [need.file, need.versions.map(&:name)] }
|
|
23
|
+
# #=> [['libc.so.6', ['GLIBC_2.4', 'GLIBC_2.2.5']]]
|
|
24
|
+
def version_requirements
|
|
25
|
+
@version_requirements ||= read_table(:verneed, :verneednum) { |at, count| tables.requirements(at, count) }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# The versions this file defines for what it exports.
|
|
29
|
+
#
|
|
30
|
+
# The first of them is the file itself rather than a version of it, which
|
|
31
|
+
# {ELFTools::VersionTables::Definition#base?} tells apart.
|
|
32
|
+
# @return [Array<ELFTools::VersionTables::Definition>]
|
|
33
|
+
# The definitions, in the order the table records them.
|
|
34
|
+
# @example
|
|
35
|
+
# elf.dynamic.version_definitions.map(&:name).first(3)
|
|
36
|
+
# #=> ['libc.so.6', 'GLIBC_2.2.5', 'GLIBC_2.2.6']
|
|
37
|
+
def version_definitions
|
|
38
|
+
@version_definitions ||= read_table(:verdef, :verdefnum) { |at, count| tables.definitions(at, count) }
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
# The version the +n+-th symbol binds to.
|
|
44
|
+
# @param [Integer] n The symbol index.
|
|
45
|
+
# @return [ELFTools::VersionTables::Version, nil]
|
|
46
|
+
# The version, +nil+ if the file records none, or if the symbol is one
|
|
47
|
+
# of the file's own or of no version at all.
|
|
48
|
+
def version_at(n)
|
|
49
|
+
VersionTables.version(versym_at(n), versions_by_index)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# What the +n+-th symbol records as its version.
|
|
53
|
+
# @return [Integer, nil] The index, +nil+ if the file records none.
|
|
54
|
+
def versym_at(n)
|
|
55
|
+
@versym_offset ||= begin
|
|
56
|
+
tag = tag_by_type(:versym)
|
|
57
|
+
tag && offset_of(tag)
|
|
58
|
+
end
|
|
59
|
+
return if @versym_offset.nil?
|
|
60
|
+
|
|
61
|
+
stream.pos = @versym_offset + (n * 2)
|
|
62
|
+
stream.read(2).to_s.unpack1(endian == :big ? 'S>' : 'S<')
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# The tables the tags point at.
|
|
66
|
+
# @return [ELFTools::VersionTables] The tables.
|
|
67
|
+
def tables
|
|
68
|
+
@tables ||= VersionTables.new(stream, string_table, endian:)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Reads a table the tags point at, as many entries as a tag counts.
|
|
72
|
+
# @return [Array] What the block makes of it, empty without the tags.
|
|
73
|
+
def read_table(address, count)
|
|
74
|
+
tag = tag_by_type(address)
|
|
75
|
+
return [] if tag.nil?
|
|
76
|
+
|
|
77
|
+
yield(offset_of(tag), tag_by_type(count).header.d_val.to_i)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# The name each index names, of either table.
|
|
81
|
+
# @return [Hash{Integer => String}] The names.
|
|
82
|
+
def versions_by_index
|
|
83
|
+
@versions_by_index ||= VersionTables.names(version_requirements, version_definitions)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
data/lib/elftools/dynamic.rb
CHANGED
|
@@ -3,8 +3,10 @@
|
|
|
3
3
|
require 'elftools/constants'
|
|
4
4
|
require 'elftools/dynamic/string_table'
|
|
5
5
|
require 'elftools/dynamic/symbols'
|
|
6
|
+
require 'elftools/dynamic/versions'
|
|
6
7
|
require 'elftools/dynamic/tag'
|
|
7
8
|
require 'elftools/exceptions'
|
|
9
|
+
require 'elftools/relative_relocations'
|
|
8
10
|
require 'elftools/relocation'
|
|
9
11
|
require 'elftools/structs'
|
|
10
12
|
|
|
@@ -17,6 +19,7 @@ module ELFTools
|
|
|
17
19
|
# attributes exist.
|
|
18
20
|
module Dynamic
|
|
19
21
|
include Symbols
|
|
22
|
+
include Versions
|
|
20
23
|
|
|
21
24
|
# Iterate all tags.
|
|
22
25
|
#
|
|
@@ -118,17 +121,20 @@ module ELFTools
|
|
|
118
121
|
|
|
119
122
|
# The relocations the tags point at.
|
|
120
123
|
#
|
|
121
|
-
#
|
|
122
|
-
# +DT_JMPREL+ names, whose entries are of the kind +DT_PLTREL+ names
|
|
124
|
+
# Three tables record them: the one +DT_REL+ or +DT_RELA+ names, the one
|
|
125
|
+
# +DT_JMPREL+ names, whose entries are of the kind +DT_PLTREL+ names, and
|
|
126
|
+
# the one +DT_RELR+ names, which packs the relocations that only add the
|
|
127
|
+
# load bias into a bitmap and so records no type of its own.
|
|
123
128
|
# @return [Array<ELFTools::Relocation>] The relocations, in the order the
|
|
124
|
-
# tags record them.
|
|
129
|
+
# tags record them, the packed ones last.
|
|
125
130
|
# @raise [ELFTools::ELFError]
|
|
126
131
|
# If a table is not in any loadable segment.
|
|
127
132
|
# @example
|
|
128
133
|
# elf.dynamic.relocations.map(&:type_name).uniq
|
|
129
134
|
# #=> ['R_X86_64_GLOB_DAT', 'R_X86_64_JUMP_SLOT']
|
|
130
135
|
def relocations
|
|
131
|
-
@relocations ||= relocation_tables.flat_map { |start, size, rela| read_relocations(start, size, rela) }
|
|
136
|
+
@relocations ||= relocation_tables.flat_map { |start, size, rela| read_relocations(start, size, rela) } +
|
|
137
|
+
packed_relocations
|
|
132
138
|
end
|
|
133
139
|
|
|
134
140
|
private
|
|
@@ -151,6 +157,17 @@ module ELFTools
|
|
|
151
157
|
tables << [jmprel, tag_by_type(:pltrelsz), tag_by_type(:pltrel).header.d_val.to_i == Constants::DT_RELA]
|
|
152
158
|
end
|
|
153
159
|
|
|
160
|
+
# Reads the table +DT_RELR+ names, which is absent from most files.
|
|
161
|
+
# @return [Array<ELFTools::Relocation>] The relocations, empty without it.
|
|
162
|
+
def packed_relocations
|
|
163
|
+
start = tag_by_type(:relr)
|
|
164
|
+
return [] if start.nil?
|
|
165
|
+
|
|
166
|
+
offset = offset_of(start)
|
|
167
|
+
RelativeRelocations.new(stream, offset...(offset + tag_by_type(:relrsz).header.d_val.to_i),
|
|
168
|
+
elf_class: header.elf_class, endian:, machine: @machine).to_a
|
|
169
|
+
end
|
|
170
|
+
|
|
154
171
|
# Reads one table of relocations.
|
|
155
172
|
# @return [Array<ELFTools::Relocation>] The relocations.
|
|
156
173
|
def read_relocations(start, size, rela)
|
data/lib/elftools/elf_file.rb
CHANGED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'elftools/constants'
|
|
4
|
+
require 'elftools/relocation'
|
|
5
|
+
require 'elftools/structs'
|
|
6
|
+
|
|
7
|
+
module ELFTools
|
|
8
|
+
# The relocations a file packs into a bitmap instead of recording one by one.
|
|
9
|
+
#
|
|
10
|
+
# Almost every relocation of a file that is loaded anywhere only adds the
|
|
11
|
+
# load bias to a word, which takes an entry recording an address, a type that
|
|
12
|
+
# is the same every time, and an addend that repeats what the word already
|
|
13
|
+
# holds. A file may pack them instead, as a run of addresses in ascending
|
|
14
|
+
# order, and spend a bit rather than an entry on each.
|
|
15
|
+
#
|
|
16
|
+
# An even entry is an address, and relocates the word there. An odd entry is
|
|
17
|
+
# a bitmap of the words following the last address, a set bit relocating one
|
|
18
|
+
# of them. Nothing records a type, because every relocation here is the one
|
|
19
|
+
# {ELFTools::Constants::R.relative} names.
|
|
20
|
+
class RelativeRelocations
|
|
21
|
+
# Instantiate a {ELFTools::RelativeRelocations} object.
|
|
22
|
+
# @param [#pos=, #read] stream Streaming object.
|
|
23
|
+
# @param [Range<Integer>] bytes The file offsets the table occupies.
|
|
24
|
+
# @param [Integer] elf_class 32 or 64, the width of an entry.
|
|
25
|
+
# @param [Symbol] endian +:little+ or +:big+.
|
|
26
|
+
# @param [Integer] machine
|
|
27
|
+
# The machine of the file, which decides what these relocations are of.
|
|
28
|
+
def initialize(stream, bytes, elf_class:, endian:, machine:)
|
|
29
|
+
@stream = stream
|
|
30
|
+
@bytes = bytes
|
|
31
|
+
@elf_class = elf_class
|
|
32
|
+
@endian = endian
|
|
33
|
+
@machine = machine
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# The relocations the table packs.
|
|
37
|
+
# @return [Array<ELFTools::Relocation>]
|
|
38
|
+
# The relocations, in the ascending order the table records them.
|
|
39
|
+
def to_a
|
|
40
|
+
type = Constants::R.relative(@machine)
|
|
41
|
+
addresses.map do |address, from|
|
|
42
|
+
rel = Structs::ELF_Rel.new(endian: @endian, offset: from)
|
|
43
|
+
rel.elf_class = @elf_class
|
|
44
|
+
rel.r_offset = address
|
|
45
|
+
relocation = Relocation.new(rel, @stream, machine: @machine)
|
|
46
|
+
# Through the relocation, so that the type is laid out in +r_info+ the
|
|
47
|
+
# way the machine lays it out.
|
|
48
|
+
relocation.type = type if type
|
|
49
|
+
relocation
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
# How many bytes an entry takes, which is the width of an address.
|
|
56
|
+
# @return [Integer] The number.
|
|
57
|
+
def width
|
|
58
|
+
@elf_class / 8
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Every address the table relocates, and the entry it was read from.
|
|
62
|
+
# @return [Array<Array(Integer, Integer)>] The addresses.
|
|
63
|
+
def addresses
|
|
64
|
+
found = []
|
|
65
|
+
# Where a bitmap counts from, which an address moves to just past itself.
|
|
66
|
+
here = 0
|
|
67
|
+
entries.each do |entry, from|
|
|
68
|
+
if entry.even?
|
|
69
|
+
found << [entry, from]
|
|
70
|
+
here = entry + width
|
|
71
|
+
else
|
|
72
|
+
# The lowest bit says the entry is a bitmap rather than an address.
|
|
73
|
+
(1...(width * 8)).each { |bit| found << [here + ((bit - 1) * width), from] if entry[bit] == 1 }
|
|
74
|
+
here += ((width * 8) - 1) * width
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
found
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# What the table records, and where each was read from.
|
|
81
|
+
# @return [Array<Array(Integer, Integer)>] The entries.
|
|
82
|
+
def entries
|
|
83
|
+
@stream.pos = @bytes.begin
|
|
84
|
+
format = "#{width == 8 ? 'Q' : 'L'}#{@endian == :big ? '>' : '<'}"
|
|
85
|
+
@stream.read(@bytes.size).to_s.unpack("#{format}*").each_with_index.map { |e, i| [e, @bytes.begin + (i * width)] }
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
data/lib/elftools/relocation.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'elftools/constants'
|
|
4
|
+
require 'elftools/util'
|
|
4
5
|
|
|
5
6
|
module ELFTools
|
|
6
7
|
# A relocation entry.
|
|
@@ -37,6 +38,24 @@ module ELFTools
|
|
|
37
38
|
sym_and_type.last
|
|
38
39
|
end
|
|
39
40
|
|
|
41
|
+
# Sets which symbol this relocation is against.
|
|
42
|
+
# @param [Integer] index The symbol index.
|
|
43
|
+
# @raise [ArgumentError] If the bits recording it cannot hold it.
|
|
44
|
+
# @example
|
|
45
|
+
# relocation.symbol_index = 3
|
|
46
|
+
def symbol_index=(index)
|
|
47
|
+
header.r_info = info_of(Util.fits!(index, index_bits, 'Symbol index'), type)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Sets what this relocation does.
|
|
51
|
+
# @param [Integer] type The relocation type.
|
|
52
|
+
# @raise [ArgumentError] If the bits recording it cannot hold it.
|
|
53
|
+
# @example
|
|
54
|
+
# relocation.type = ELFTools::Constants::R::X86_64::R_X86_64_JUMP_SLOT
|
|
55
|
+
def type=(type)
|
|
56
|
+
header.r_info = info_of(symbol_index, Util.fits!(type, type_bits, 'Relocation type'))
|
|
57
|
+
end
|
|
58
|
+
|
|
40
59
|
# The name of {#type}.
|
|
41
60
|
#
|
|
42
61
|
# Every architecture numbers relocation types on its own, so the name is
|
|
@@ -51,6 +70,37 @@ module ELFTools
|
|
|
51
70
|
|
|
52
71
|
private
|
|
53
72
|
|
|
73
|
+
# The +r_info+ recording a symbol index and a type, laid out the way this
|
|
74
|
+
# file lays it out. The inverse of {#sym_and_type}.
|
|
75
|
+
# @return [Integer] The +r_info+.
|
|
76
|
+
def info_of(index, type)
|
|
77
|
+
return mips64_info_of(index, type) if mips64?
|
|
78
|
+
|
|
79
|
+
(index << mask_bit) | type
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# The +r_info+ as the 64-bit MIPS ABI records it, leaving the bytes the
|
|
83
|
+
# ABI keeps for itself as they were.
|
|
84
|
+
# @return [Integer] The +r_info+.
|
|
85
|
+
def mips64_info_of(index, type)
|
|
86
|
+
info = header.r_info.to_i
|
|
87
|
+
return (index << 32) | (info & 0xffff_ff00) | type if header.class.self_endian == :big
|
|
88
|
+
|
|
89
|
+
(info & 0x00ff_ffff_0000_0000) | (type << 56) | index
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# How many bits record a symbol index.
|
|
93
|
+
# @return [Integer] The number.
|
|
94
|
+
def index_bits
|
|
95
|
+
mips64? ? 32 : (header.elf_class - mask_bit)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# How many bits record a relocation type.
|
|
99
|
+
# @return [Integer] The number.
|
|
100
|
+
def type_bits
|
|
101
|
+
mips64? ? 8 : mask_bit
|
|
102
|
+
end
|
|
103
|
+
|
|
54
104
|
# What +r_info+ records, i.e. a symbol index and a relocation type. Most
|
|
55
105
|
# machines split the field in half between the two, one lays it out its
|
|
56
106
|
# own way.
|