elftools 2.0.0 → 2.2.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 +53 -0
- data/lib/elftools/dynamic/hash_table.rb +16 -0
- data/lib/elftools/dynamic/symbols.rb +54 -19
- data/lib/elftools/dynamic/tag.rb +28 -6
- data/lib/elftools/dynamic/versions.rb +87 -0
- data/lib/elftools/dynamic.rb +33 -35
- data/lib/elftools/elf_file.rb +61 -11
- data/lib/elftools/relative_relocations.rb +87 -0
- data/lib/elftools/relocation.rb +72 -9
- data/lib/elftools/sections/relative_relocation_section.rb +46 -0
- data/lib/elftools/sections/relocation_section.rb +16 -5
- data/lib/elftools/sections/section.rb +31 -0
- data/lib/elftools/sections/sections.rb +8 -0
- data/lib/elftools/sections/sym_tab_section.rb +64 -10
- data/lib/elftools/sections/symbol.rb +112 -10
- 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 +373 -17
- 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: cbbd11ec3b95d867ab34551c14f1a0a53c3c5e2ebb539d226b16378873a0f74a
|
|
4
|
+
data.tar.gz: 05301ad23c8d366858d57f58cccf4a3274608a036de07e629e85075b4e1babd5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2e96723b2a63f7f6692cc6777bd64bd3b5e3aae7321a09da3a9098e26a4a95162bc892a2933088bc6fef89f14cb72269434e71c32bc2551f5b252105b09d6aa8
|
|
7
|
+
data.tar.gz: b177302e11ea50108f582dc4f70651136dcc5a15e2363856f2cfc453fbd2979eb202520a40468b4349af12e0dc1c84960b4972548c360bf902729e3acb98f456
|
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)
|
|
@@ -422,6 +466,15 @@ module ELFTools
|
|
|
422
466
|
end
|
|
423
467
|
include SHN
|
|
424
468
|
|
|
469
|
+
# The escape value the ELF header records where it cannot hold a count of
|
|
470
|
+
# the program headers, which is then recorded in the first section header.
|
|
471
|
+
module PN
|
|
472
|
+
extend Naming
|
|
473
|
+
|
|
474
|
+
PN_XNUM = 0xffff # the number of program headers is too large for the header to hold
|
|
475
|
+
end
|
|
476
|
+
include PN
|
|
477
|
+
|
|
425
478
|
# Section flag mask types, records in +sh_flag+.
|
|
426
479
|
module SHF
|
|
427
480
|
SHF_WRITE = (1 << 0) # Writable
|
|
@@ -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
|
|
@@ -29,26 +29,27 @@ module ELFTools
|
|
|
29
29
|
return if n.negative?
|
|
30
30
|
|
|
31
31
|
@symbol_at_map ||= {}
|
|
32
|
-
@symbol_at_map[n] ||=
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
Sections::Symbol.new(sym, stream, symstr: method(:string_table), machine: @machine)
|
|
38
|
-
end
|
|
32
|
+
@symbol_at_map[n] ||= Sections::Symbol.new(
|
|
33
|
+
Structs::Fields.new(Structs::ELF_sym[header.elf_class], stream, sym_offset + (n * sym_entsize),
|
|
34
|
+
elf_class: header.elf_class, endian: endian),
|
|
35
|
+
stream, symstr: string_table_reader, machine: @machine, version: -> { version_at(n) }
|
|
36
|
+
)
|
|
39
37
|
end
|
|
40
38
|
|
|
41
39
|
# How many symbols the tags reach.
|
|
42
40
|
#
|
|
43
|
-
#
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
#
|
|
47
|
-
#
|
|
48
|
-
#
|
|
49
|
-
#
|
|
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.
|
|
43
|
+
#
|
|
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.
|
|
50
51
|
#
|
|
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,21 @@ 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 ||= Structs::ELF_sym[header.elf_class].num_bytes(elf_class: header.elf_class, endian: endian)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# What reads the table these symbols are named in, kept so that reading
|
|
161
|
+
# a table of them does not make one for every symbol.
|
|
162
|
+
# @return [Method] The method.
|
|
163
|
+
def string_table_reader
|
|
164
|
+
@string_table_reader ||= method(:string_table)
|
|
165
|
+
end
|
|
166
|
+
|
|
132
167
|
# Get the +DT_SYMTAB+'s +d_val+ offset related to file.
|
|
133
168
|
# @return [Integer] The file offset.
|
|
134
169
|
# @raise [ELFTools::ELFError]
|
data/lib/elftools/dynamic/tag.rb
CHANGED
|
@@ -1,25 +1,47 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'elftools/constants'
|
|
4
|
+
require 'elftools/structs'
|
|
4
5
|
|
|
5
6
|
module ELFTools
|
|
6
7
|
module Dynamic
|
|
7
8
|
# A tag class.
|
|
8
9
|
class Tag
|
|
9
|
-
attr_reader :header # @return [ELFTools::Structs::ELF_Dyn] The dynamic tag header.
|
|
10
10
|
attr_reader :stream # @return [#pos=, #read] Streaming object.
|
|
11
11
|
|
|
12
12
|
# Instantiate a {ELFTools::Dynamic::Tag} object.
|
|
13
|
-
# @param [ELF_Dyn] header
|
|
13
|
+
# @param [ELFTools::Structs::ELF_Dyn, ELFTools::Structs::Fields] header
|
|
14
|
+
# The dynamic tag header, or what the file records in one.
|
|
15
|
+
# {ELFTools::Structs::Fields} answers what the tag records until
|
|
16
|
+
# something asks for {#header} itself, which builds the structure then.
|
|
14
17
|
# @param [#pos=, #read] stream Streaming object.
|
|
15
18
|
# @param [ELFTools::Dynamic::StringTable] strtab
|
|
16
19
|
# The string table the names of tags are recorded in.
|
|
17
20
|
def initialize(header, stream, strtab)
|
|
18
|
-
@
|
|
21
|
+
@fields = header.is_a?(Structs::Fields) ? header : Structs::Fields.of(header)
|
|
19
22
|
@stream = stream
|
|
20
23
|
@strtab = strtab
|
|
21
24
|
end
|
|
22
25
|
|
|
26
|
+
# The structure the file records this tag in.
|
|
27
|
+
#
|
|
28
|
+
# One is built here for a tag read without it, which is what assigning to
|
|
29
|
+
# a field of one needs and what {ELFTools::ELFFile#patches} reports the
|
|
30
|
+
# changes of.
|
|
31
|
+
# @return [ELFTools::Structs::ELF_Dyn] The structure.
|
|
32
|
+
def header
|
|
33
|
+
@fields.struct
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# What kind of tag this is, which {ELFTools::Constants::DT} names.
|
|
37
|
+
# @return [Integer] The type.
|
|
38
|
+
# @example
|
|
39
|
+
# dynamic.tag_by_type(:needed).type == ELFTools::Constants::DT_NEEDED
|
|
40
|
+
# #=> true
|
|
41
|
+
def type
|
|
42
|
+
@fields[:d_tag]
|
|
43
|
+
end
|
|
44
|
+
|
|
23
45
|
# Some dynamic have name.
|
|
24
46
|
TYPE_WITH_NAME = [Constants::DT_NEEDED,
|
|
25
47
|
Constants::DT_SONAME,
|
|
@@ -40,7 +62,7 @@ module ELFTools
|
|
|
40
62
|
# dynamic.tag_by_type(:needed).value
|
|
41
63
|
# #=> 'libc.so.6'
|
|
42
64
|
def value
|
|
43
|
-
name ||
|
|
65
|
+
name || @fields[:d_val]
|
|
44
66
|
end
|
|
45
67
|
|
|
46
68
|
# Is this tag has a name?
|
|
@@ -48,7 +70,7 @@ module ELFTools
|
|
|
48
70
|
# The criteria here is if this tag's type is in {TYPE_WITH_NAME}.
|
|
49
71
|
# @return [Boolean] Is this tag has a name.
|
|
50
72
|
def name?
|
|
51
|
-
TYPE_WITH_NAME.include?(
|
|
73
|
+
TYPE_WITH_NAME.include?(type)
|
|
52
74
|
end
|
|
53
75
|
|
|
54
76
|
# Return the name of this tag.
|
|
@@ -59,7 +81,7 @@ module ELFTools
|
|
|
59
81
|
def name
|
|
60
82
|
return nil unless name?
|
|
61
83
|
|
|
62
|
-
@strtab.name_at(
|
|
84
|
+
@strtab.name_at(@fields[:d_val])
|
|
63
85
|
end
|
|
64
86
|
end
|
|
65
87
|
end
|
|
@@ -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
|
#
|
|
@@ -35,7 +38,7 @@ module ELFTools
|
|
|
35
38
|
0.step do |i|
|
|
36
39
|
tag = tag_at(i).tap(&block)
|
|
37
40
|
arr << tag
|
|
38
|
-
break if tag.
|
|
41
|
+
break if tag.type == ELFTools::Constants::DT_NULL
|
|
39
42
|
end
|
|
40
43
|
arr
|
|
41
44
|
end
|
|
@@ -76,7 +79,7 @@ module ELFTools
|
|
|
76
79
|
# #=> #<ELFTools::Dynamic::Tag:0x0055d3d2d91b28 @header={:d_tag=>3, :d_val=>6295552}>
|
|
77
80
|
def tag_by_type(type)
|
|
78
81
|
type = Util.to_constant(Constants::DT, type)
|
|
79
|
-
each_tag.find { |tag| tag.
|
|
82
|
+
each_tag.find { |tag| tag.type == type }
|
|
80
83
|
end
|
|
81
84
|
|
|
82
85
|
# Get tags of specific type.
|
|
@@ -88,7 +91,7 @@ module ELFTools
|
|
|
88
91
|
# @see #tag_by_type
|
|
89
92
|
def tags_by_type(type)
|
|
90
93
|
type = Util.to_constant(Constants::DT, type)
|
|
91
|
-
each_tag.select { |tag| tag.
|
|
94
|
+
each_tag.select { |tag| tag.type == type }
|
|
92
95
|
end
|
|
93
96
|
|
|
94
97
|
# Get the +n+-th tag.
|
|
@@ -109,26 +112,28 @@ module ELFTools
|
|
|
109
112
|
@tag_at_map ||= {}
|
|
110
113
|
return @tag_at_map[n] if @tag_at_map[n]
|
|
111
114
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
@tag_at_map[n] = Tag.new(dyn.read(stream), stream, string_table)
|
|
115
|
+
elf_class = header.elf_class
|
|
116
|
+
offset = tag_start + (n * Structs::ELF_Dyn.num_bytes(elf_class: elf_class, endian: endian))
|
|
117
|
+
fields = Structs::Fields.new(Structs::ELF_Dyn, stream, offset, elf_class: elf_class, endian: endian)
|
|
118
|
+
@tag_at_map[n] = Tag.new(fields, stream, string_table)
|
|
117
119
|
end
|
|
118
120
|
|
|
119
121
|
# The relocations the tags point at.
|
|
120
122
|
#
|
|
121
|
-
#
|
|
122
|
-
# +DT_JMPREL+ names, whose entries are of the kind +DT_PLTREL+ names
|
|
123
|
+
# Three tables record them: the one +DT_REL+ or +DT_RELA+ names, the one
|
|
124
|
+
# +DT_JMPREL+ names, whose entries are of the kind +DT_PLTREL+ names, and
|
|
125
|
+
# the one +DT_RELR+ names, which packs the relocations that only add the
|
|
126
|
+
# load bias into a bitmap and so records no type of its own.
|
|
123
127
|
# @return [Array<ELFTools::Relocation>] The relocations, in the order the
|
|
124
|
-
# tags record them.
|
|
128
|
+
# tags record them, the packed ones last.
|
|
125
129
|
# @raise [ELFTools::ELFError]
|
|
126
130
|
# If a table is not in any loadable segment.
|
|
127
131
|
# @example
|
|
128
132
|
# elf.dynamic.relocations.map(&:type_name).uniq
|
|
129
133
|
# #=> ['R_X86_64_GLOB_DAT', 'R_X86_64_JUMP_SLOT']
|
|
130
134
|
def relocations
|
|
131
|
-
@relocations ||= relocation_tables.flat_map { |start, size, rela| read_relocations(start, size, rela) }
|
|
135
|
+
@relocations ||= relocation_tables.flat_map { |start, size, rela| read_relocations(start, size, rela) } +
|
|
136
|
+
packed_relocations
|
|
132
137
|
end
|
|
133
138
|
|
|
134
139
|
private
|
|
@@ -151,6 +156,17 @@ module ELFTools
|
|
|
151
156
|
tables << [jmprel, tag_by_type(:pltrelsz), tag_by_type(:pltrel).header.d_val.to_i == Constants::DT_RELA]
|
|
152
157
|
end
|
|
153
158
|
|
|
159
|
+
# Reads the table +DT_RELR+ names, which is absent from most files.
|
|
160
|
+
# @return [Array<ELFTools::Relocation>] The relocations, empty without it.
|
|
161
|
+
def packed_relocations
|
|
162
|
+
start = tag_by_type(:relr)
|
|
163
|
+
return [] if start.nil?
|
|
164
|
+
|
|
165
|
+
offset = offset_of(start)
|
|
166
|
+
RelativeRelocations.new(stream, offset...(offset + tag_by_type(:relrsz).header.d_val.to_i),
|
|
167
|
+
elf_class: header.elf_class, endian:, machine: @machine).to_a
|
|
168
|
+
end
|
|
169
|
+
|
|
154
170
|
# Reads one table of relocations.
|
|
155
171
|
# @return [Array<ELFTools::Relocation>] The relocations.
|
|
156
172
|
def read_relocations(start, size, rela)
|
|
@@ -159,32 +175,14 @@ module ELFTools
|
|
|
159
175
|
# An entry takes what its structure takes. DT_RELAENT and DT_RELENT
|
|
160
176
|
# record the same number, which a file has no way of disagreeing with
|
|
161
177
|
# and every file here agrees with.
|
|
162
|
-
|
|
178
|
+
elf_class = header.elf_class
|
|
179
|
+
entsize = klass.num_bytes(elf_class: elf_class, endian: endian)
|
|
163
180
|
Array.new(size.header.d_val.to_i / entsize) do |i|
|
|
164
|
-
|
|
181
|
+
fields = Structs::Fields.new(klass, stream, offset + (i * entsize), elf_class: elf_class, endian: endian)
|
|
182
|
+
Relocation.new(fields, stream, machine: @machine)
|
|
165
183
|
end
|
|
166
184
|
end
|
|
167
185
|
|
|
168
|
-
# A structure of the endianness and the class the file records it in.
|
|
169
|
-
# @param [Class] klass The structure class.
|
|
170
|
-
# @return [ELFTools::Structs::ELFStruct] The structure, before it is read.
|
|
171
|
-
def struct(klass)
|
|
172
|
-
struct = klass.new(endian:)
|
|
173
|
-
struct.elf_class = header.elf_class
|
|
174
|
-
struct
|
|
175
|
-
end
|
|
176
|
-
|
|
177
|
-
# Reads a structure the file records at a file offset.
|
|
178
|
-
# @param [Class] klass The structure class.
|
|
179
|
-
# @param [Integer] offset The file offset.
|
|
180
|
-
# @return [ELFTools::Structs::ELFStruct] The structure.
|
|
181
|
-
def read_struct(klass, offset)
|
|
182
|
-
struct = struct(klass)
|
|
183
|
-
struct.offset = offset
|
|
184
|
-
stream.pos = offset
|
|
185
|
-
struct.read(stream)
|
|
186
|
-
end
|
|
187
|
-
|
|
188
186
|
# The file offset the address a tag records points at.
|
|
189
187
|
# @param [ELFTools::Dynamic::Tag] tag The tag.
|
|
190
188
|
# @return [Integer] The file offset.
|
|
@@ -193,7 +191,7 @@ module ELFTools
|
|
|
193
191
|
def offset_of(tag)
|
|
194
192
|
vma = tag.header.d_val.to_i
|
|
195
193
|
@offset_from_vma.call(vma) ||
|
|
196
|
-
raise(ELFError, format('Invalid %s address 0x%x', Constants::DT.mapping(@machine, tag.
|
|
194
|
+
raise(ELFError, format('Invalid %s address 0x%x', Constants::DT.mapping(@machine, tag.type), vma))
|
|
197
195
|
end
|
|
198
196
|
|
|
199
197
|
# The names the tags and the symbols point at.
|