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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4ec85f62d56b2fe312d1e3aa73093f7f2ddee77da2fc3479ce6656d2df2d06c6
4
- data.tar.gz: 6e8d33b1c9d0b7e9f2de9684b764a33fc90164333bd9a0d2b07a647bb79895a6
3
+ metadata.gz: 0213b757f8eab7a9a4806fdca95e68e8ac3f64fc226c69cf272d071def187172
4
+ data.tar.gz: 891db5a15bc0b44c09d0135a0c5b8f95633fa49b07d18e8e2a563f8384842f90
5
5
  SHA512:
6
- metadata.gz: 37fff29d3b9cd9ca69a6a6b50289636dd8e20e54e051c42ecee1a889aca94b7cbd8b16b40a1ca8a64279c395f8d992c8cb7e501d81d10cd66c7658ecd6649727
7
- data.tar.gz: 279fb63197a5e4a5100739734e6cba39f294eebb09fb643461fe65f55e00bcb51e6fac5d11d383fd54018f4da3e8a8e663b59614d6f6dc44f0741c64f4693855
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...
@@ -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
- 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)
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
- # 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.
50
43
  #
51
- # This is therefore a lower bound. A symbol that is neither indexed by the
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 ||= (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,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
@@ -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
- # 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.
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)
@@ -413,6 +413,7 @@ module ELFTools
413
413
  offset_from_vma: method(:offset_from_vma),
414
414
  section_name_table: method(:section_name_table),
415
415
  section_at: method(:section_at),
416
+ sections: method(:sections),
416
417
  machine: header.e_machine.to_i)
417
418
  end
418
419
 
@@ -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
@@ -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.