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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4ec85f62d56b2fe312d1e3aa73093f7f2ddee77da2fc3479ce6656d2df2d06c6
4
- data.tar.gz: 6e8d33b1c9d0b7e9f2de9684b764a33fc90164333bd9a0d2b07a647bb79895a6
3
+ metadata.gz: cbbd11ec3b95d867ab34551c14f1a0a53c3c5e2ebb539d226b16378873a0f74a
4
+ data.tar.gz: 05301ad23c8d366858d57f58cccf4a3274608a036de07e629e85075b4e1babd5
5
5
  SHA512:
6
- metadata.gz: 37fff29d3b9cd9ca69a6a6b50289636dd8e20e54e051c42ecee1a889aca94b7cbd8b16b40a1ca8a64279c395f8d992c8cb7e501d81d10cd66c7658ecd6649727
7
- data.tar.gz: 279fb63197a5e4a5100739734e6cba39f294eebb09fb643461fe65f55e00bcb51e6fac5d11d383fd54018f4da3e8a8e663b59614d6f6dc44f0741c64f4693855
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...
@@ -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] ||= begin
33
- klass = Structs::ELF_sym[header.elf_class]
34
- # An entry takes what its structure takes, which is also what
35
- # DT_SYMENT records and what a file has no way of disagreeing with.
36
- sym = read_struct(klass, sym_offset + (n * struct(klass).num_bytes))
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
- # Nothing a file is loaded by records how large its symbol table is. The
44
- # loader never enumerates it: it looks a name up through a hash table and
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.
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
- # This is therefore a lower bound. A symbol that is neither indexed by the
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 ||= (hash_tables.map(&:num_symbols) + [count_from_relocations]).compact.max || 0
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. They do not index every symbol, and a
96
- # file need not record one at all, so a name they do not lead to is
97
- # searched for among the symbols {#symbols} reaches.
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]
@@ -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 The dynamic tag 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
- @header = header
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 || header.d_val.to_i
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?(header.d_tag)
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(header.d_val.to_i)
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
@@ -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.header.d_tag == ELFTools::Constants::DT_NULL
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.header.d_tag == type }
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.header.d_tag == type }
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
- dyn = Structs::ELF_Dyn.new(endian:)
113
- dyn.elf_class = header.elf_class
114
- stream.pos = tag_start + n * dyn.num_bytes
115
- dyn.offset = stream.pos
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
- # Two tables record them: the one +DT_REL+ or +DT_RELA+ names, and the one
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
- entsize = struct(klass).num_bytes
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
- Relocation.new(read_struct(klass, offset + (i * entsize)), stream, machine: @machine)
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.header.d_tag.to_i), vma))
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.