elftools 2.1.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: 0213b757f8eab7a9a4806fdca95e68e8ac3f64fc226c69cf272d071def187172
4
- data.tar.gz: 891db5a15bc0b44c09d0135a0c5b8f95633fa49b07d18e8e2a563f8384842f90
3
+ metadata.gz: cbbd11ec3b95d867ab34551c14f1a0a53c3c5e2ebb539d226b16378873a0f74a
4
+ data.tar.gz: 05301ad23c8d366858d57f58cccf4a3274608a036de07e629e85075b4e1babd5
5
5
  SHA512:
6
- metadata.gz: 7a10ccb7a4d7b4b7c9bdc30aca01a330848ea0c9aa9a34b310e315acd5823312f413b84b9ddc72c479d3363494960abfd41b10857a34c6402fec2958ee039e64
7
- data.tar.gz: 57902a64f1450d229bff599d7a3fac08f3f8e1f80490924a2d1b959404e90aa68880d92437a5d1a55e2d19c88ea8028c954fa209c51f6037117b4f1057e1eabb
6
+ metadata.gz: 2e96723b2a63f7f6692cc6777bd64bd3b5e3aae7321a09da3a9098e26a4a95162bc892a2933088bc6fef89f14cb72269434e71c32bc2551f5b252105b09d6aa8
7
+ data.tar.gz: b177302e11ea50108f582dc4f70651136dcc5a15e2363856f2cfc453fbd2979eb202520a40468b4349af12e0dc1c84960b4972548c360bf902729e3acb98f456
@@ -466,6 +466,15 @@ module ELFTools
466
466
  end
467
467
  include SHN
468
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
+
469
478
  # Section flag mask types, records in +sh_flag+.
470
479
  module SHF
471
480
  SHF_WRITE = (1 << 0) # Writable
@@ -29,11 +29,11 @@ module ELFTools
29
29
  return if n.negative?
30
30
 
31
31
  @symbol_at_map ||= {}
32
- @symbol_at_map[n] ||= begin
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) })
36
- 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
+ )
37
37
  end
38
38
 
39
39
  # How many symbols the tags reach.
@@ -154,7 +154,14 @@ module ELFTools
154
154
  # has no way of disagreeing with.
155
155
  # @return [Integer] The number.
156
156
  def sym_entsize
157
- @sym_entsize ||= struct(Structs::ELF_sym[header.elf_class]).num_bytes
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)
158
165
  end
159
166
 
160
167
  # Get the +DT_SYMTAB+'s +d_val+ offset related to file.
@@ -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
@@ -38,7 +38,7 @@ module ELFTools
38
38
  0.step do |i|
39
39
  tag = tag_at(i).tap(&block)
40
40
  arr << tag
41
- break if tag.header.d_tag == ELFTools::Constants::DT_NULL
41
+ break if tag.type == ELFTools::Constants::DT_NULL
42
42
  end
43
43
  arr
44
44
  end
@@ -79,7 +79,7 @@ module ELFTools
79
79
  # #=> #<ELFTools::Dynamic::Tag:0x0055d3d2d91b28 @header={:d_tag=>3, :d_val=>6295552}>
80
80
  def tag_by_type(type)
81
81
  type = Util.to_constant(Constants::DT, type)
82
- each_tag.find { |tag| tag.header.d_tag == type }
82
+ each_tag.find { |tag| tag.type == type }
83
83
  end
84
84
 
85
85
  # Get tags of specific type.
@@ -91,7 +91,7 @@ module ELFTools
91
91
  # @see #tag_by_type
92
92
  def tags_by_type(type)
93
93
  type = Util.to_constant(Constants::DT, type)
94
- each_tag.select { |tag| tag.header.d_tag == type }
94
+ each_tag.select { |tag| tag.type == type }
95
95
  end
96
96
 
97
97
  # Get the +n+-th tag.
@@ -112,11 +112,10 @@ module ELFTools
112
112
  @tag_at_map ||= {}
113
113
  return @tag_at_map[n] if @tag_at_map[n]
114
114
 
115
- dyn = Structs::ELF_Dyn.new(endian:)
116
- dyn.elf_class = header.elf_class
117
- stream.pos = tag_start + n * dyn.num_bytes
118
- dyn.offset = stream.pos
119
- @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)
120
119
  end
121
120
 
122
121
  # The relocations the tags point at.
@@ -176,32 +175,14 @@ module ELFTools
176
175
  # An entry takes what its structure takes. DT_RELAENT and DT_RELENT
177
176
  # record the same number, which a file has no way of disagreeing with
178
177
  # and every file here agrees with.
179
- entsize = struct(klass).num_bytes
178
+ elf_class = header.elf_class
179
+ entsize = klass.num_bytes(elf_class: elf_class, endian: endian)
180
180
  Array.new(size.header.d_val.to_i / entsize) do |i|
181
- 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)
182
183
  end
183
184
  end
184
185
 
185
- # A structure of the endianness and the class the file records it in.
186
- # @param [Class] klass The structure class.
187
- # @return [ELFTools::Structs::ELFStruct] The structure, before it is read.
188
- def struct(klass)
189
- struct = klass.new(endian:)
190
- struct.elf_class = header.elf_class
191
- struct
192
- end
193
-
194
- # Reads a structure the file records at a file offset.
195
- # @param [Class] klass The structure class.
196
- # @param [Integer] offset The file offset.
197
- # @return [ELFTools::Structs::ELFStruct] The structure.
198
- def read_struct(klass, offset)
199
- struct = struct(klass)
200
- struct.offset = offset
201
- stream.pos = offset
202
- struct.read(stream)
203
- end
204
-
205
186
  # The file offset the address a tag records points at.
206
187
  # @param [ELFTools::Dynamic::Tag] tag The tag.
207
188
  # @return [Integer] The file offset.
@@ -210,7 +191,7 @@ module ELFTools
210
191
  def offset_of(tag)
211
192
  vma = tag.header.d_val.to_i
212
193
  @offset_from_vma.call(vma) ||
213
- 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))
214
195
  end
215
196
 
216
197
  # The names the tags and the symbols point at.
@@ -105,12 +105,19 @@ module ELFTools
105
105
  #========= method about sections
106
106
 
107
107
  # Number of sections in this file.
108
+ #
109
+ # A file with more sections than the ELF header can count records a zero
110
+ # there and states the number in the first section header instead, which a
111
+ # file with no section headers at all records as well.
108
112
  # @return [Integer] The desired number.
109
113
  # @example
110
114
  # elf.num_sections
111
115
  # #=> 29
112
116
  def num_sections
113
- header.e_shnum
117
+ count = header.e_shnum.to_i
118
+ return count unless count.zero? && !header.e_shoff.to_i.zero?
119
+
120
+ first_section_header.sh_size.to_i
114
121
  end
115
122
 
116
123
  # Acquire the section named as +name+.
@@ -197,15 +204,25 @@ module ELFTools
197
204
  # elf.section_name_table.name
198
205
  # #=> '.shstrtab'
199
206
  def section_name_table
200
- section_at(header.e_shstrndx)
207
+ index = header.e_shstrndx.to_i
208
+ # An index too large for the ELF header is stated in the first section
209
+ # header instead.
210
+ index = first_section_header.sh_link.to_i if index == Constants::SHN_XINDEX
211
+ section_at(index)
201
212
  end
202
213
 
203
214
  #========= method about segments
204
215
 
205
216
  # Number of segments in this file.
217
+ #
218
+ # A file with more segments than the ELF header can count states the number
219
+ # in the first section header instead, as it does for the sections.
206
220
  # @return [Integer] The desited number.
207
221
  def num_segments
208
- header.e_phnum
222
+ count = header.e_phnum.to_i
223
+ return count unless count == Constants::PN_XNUM
224
+
225
+ first_section_header.sh_info.to_i
209
226
  end
210
227
 
211
228
  # Iterate all segments.
@@ -373,15 +390,35 @@ module ELFTools
373
390
 
374
391
  # bad idea..
375
392
  def loaded_headers
376
- explore = lambda do |obj|
377
- return obj if obj.is_a?(::ELFTools::Structs::ELFStruct)
378
- return obj.map(&explore) if obj.is_a?(Array)
393
+ # By identity, so that a graph that reaches the same thing by many paths,
394
+ # or leads round in circles, is walked once rather than over and over.
395
+ headers_in(self, {}.compare_by_identity).flatten
396
+ end
379
397
 
380
- obj.instance_variables.map do |s|
381
- explore.call(obj.instance_variable_get(s))
382
- end
383
- end
384
- explore.call(self).flatten
398
+ # The structures reachable from an object, whatever holds them.
399
+ # @param [Object] obj The object.
400
+ # @param [Hash] seen What has been reached already.
401
+ # @return [Array] The structures, nested.
402
+ def headers_in(obj, seen)
403
+ # A class is not one of the things a file records.
404
+ return [] if seen[obj] || obj.is_a?(Module)
405
+
406
+ seen[obj] = true
407
+ return obj if obj.is_a?(::ELFTools::Structs::ELFStruct)
408
+
409
+ held_by(obj).map { |held| headers_in(held, seen) }
410
+ end
411
+
412
+ # What an object holds, which a walk of it goes on to.
413
+ # @param [Object] obj The object.
414
+ # @return [Enumerable] What it holds.
415
+ def held_by(obj)
416
+ return obj if obj.is_a?(Array)
417
+ # A hash is held by its values, which is where the tags and the symbols
418
+ # read through the tags are remembered.
419
+ return obj.each_value if obj.is_a?(Hash)
420
+
421
+ obj.instance_variables.map { |name| obj.instance_variable_get(name) }
385
422
  end
386
423
 
387
424
  def identify
@@ -404,6 +441,18 @@ module ELFTools
404
441
  raise ELFDataError, format('Invalid EI_DATA "\x%02x"', ei_data) if endian.nil?
405
442
  end
406
443
 
444
+ # The first section header, which is where a file too large for the counts
445
+ # the ELF header records states them.
446
+ # @return [ELFTools::Structs::ELF_Shdr] The header.
447
+ def first_section_header
448
+ @first_section_header ||= begin
449
+ stream.pos = header.e_shoff.to_i
450
+ shdr = Structs::ELF_Shdr.new(endian:, offset: stream.pos)
451
+ shdr.elf_class = elf_class
452
+ shdr.read(stream)
453
+ end
454
+ end
455
+
407
456
  def create_section(n)
408
457
  stream.pos = header.e_shoff + n * header.e_shentsize
409
458
  shdr = Structs::ELF_Shdr.new(endian:, offset: stream.pos)
@@ -39,10 +39,9 @@ module ELFTools
39
39
  def to_a
40
40
  type = Constants::R.relative(@machine)
41
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)
42
+ fields = Structs::Fields.from(Structs::ELF_Rel, { r_offset: address, r_info: 0 },
43
+ elf_class: @elf_class, endian: @endian, offset: from)
44
+ relocation = Relocation.new(fields, @stream, machine: @machine)
46
45
  # Through the relocation, so that the type is laid out in +r_info+ the
47
46
  # way the machine lays it out.
48
47
  relocation.type = type if type
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'elftools/constants'
4
+ require 'elftools/structs'
4
5
  require 'elftools/util'
5
6
 
6
7
  module ELFTools
@@ -8,22 +9,33 @@ module ELFTools
8
9
  #
9
10
  # Can be either a REL or RELA relocation.
10
11
  class Relocation
11
- attr_reader :header # @return [ELFTools::Structs::ELF_Rel, ELFTools::Structs::ELF_Rela] Rel(a) header.
12
12
  attr_reader :stream # @return [#pos=, #read] Streaming object.
13
13
 
14
14
  # Instantiate a {Relocation} object.
15
- # @param [ELFTools::Structs::ELF_Rel, ELFTools::Structs::ELF_Rela] header
16
- # The relocation header.
15
+ # @param [ELFTools::Structs::ELF_Rel, ELFTools::Structs::ELF_Rela, ELFTools::Structs::Fields] header
16
+ # The relocation header, or what the file records in one.
17
+ # {ELFTools::Structs::Fields} answers what the relocation records until
18
+ # something asks for {#header} itself, which builds the structure then.
17
19
  # @param [#pos=, #read] stream The streaming object.
18
20
  # @param [Integer] machine
19
21
  # The machine of the ELF file, which decides what {#type} means and how
20
22
  # {#header} records it.
21
23
  def initialize(header, stream, machine: nil)
22
- @header = header
24
+ @fields = header.is_a?(Structs::Fields) ? header : Structs::Fields.of(header)
23
25
  @stream = stream
24
26
  @machine = machine
25
27
  end
26
28
 
29
+ # The structure the file records this relocation in.
30
+ #
31
+ # One is built here for a relocation read without it, which is what
32
+ # assigning to a field of one needs and what {ELFTools::ELFFile#patches}
33
+ # reports the changes of.
34
+ # @return [ELFTools::Structs::ELF_Rel, ELFTools::Structs::ELF_Rela] The structure.
35
+ def header
36
+ @fields.struct
37
+ end
38
+
27
39
  # Which symbol this relocation is against, as an index into the symbol
28
40
  # table.
29
41
  # @return [Integer] The symbol index.
@@ -44,7 +56,7 @@ module ELFTools
44
56
  # @example
45
57
  # relocation.symbol_index = 3
46
58
  def symbol_index=(index)
47
- header.r_info = info_of(Util.fits!(index, index_bits, 'Symbol index'), type)
59
+ @fields[:r_info] = info_of(Util.fits!(index, index_bits, 'Symbol index'), type)
48
60
  end
49
61
 
50
62
  # Sets what this relocation does.
@@ -53,7 +65,7 @@ module ELFTools
53
65
  # @example
54
66
  # relocation.type = ELFTools::Constants::R::X86_64::R_X86_64_JUMP_SLOT
55
67
  def type=(type)
56
- header.r_info = info_of(symbol_index, Util.fits!(type, type_bits, 'Relocation type'))
68
+ @fields[:r_info] = info_of(symbol_index, Util.fits!(type, type_bits, 'Relocation type'))
57
69
  end
58
70
 
59
71
  # The name of {#type}.
@@ -83,8 +95,8 @@ module ELFTools
83
95
  # ABI keeps for itself as they were.
84
96
  # @return [Integer] The +r_info+.
85
97
  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
98
+ info = @fields[:r_info]
99
+ return (index << 32) | (info & 0xffff_ff00) | type if @fields.endian == :big
88
100
 
89
101
  (info & 0x00ff_ffff_0000_0000) | (type << 56) | index
90
102
  end
@@ -92,7 +104,7 @@ module ELFTools
92
104
  # How many bits record a symbol index.
93
105
  # @return [Integer] The number.
94
106
  def index_bits
95
- mips64? ? 32 : (header.elf_class - mask_bit)
107
+ mips64? ? 32 : (@fields.elf_class - mask_bit)
96
108
  end
97
109
 
98
110
  # How many bits record a relocation type.
@@ -108,14 +120,15 @@ module ELFTools
108
120
  def sym_and_type
109
121
  return mips64_sym_and_type if mips64?
110
122
 
111
- [header.r_info >> mask_bit, header.r_info & ((1 << mask_bit) - 1)]
123
+ info = @fields[:r_info]
124
+ [info >> mask_bit, info & ((1 << mask_bit) - 1)]
112
125
  end
113
126
 
114
127
  # Whether the file records relocations the way the 64-bit MIPS ABI does,
115
128
  # which is the one layout that departs from halving +r_info+.
116
129
  # @return [Boolean] The answer.
117
130
  def mips64?
118
- @machine == Constants::EM_MIPS && header.elf_class == 64
131
+ @machine == Constants::EM_MIPS && @fields.elf_class == 64
119
132
  end
120
133
 
121
134
  # Reads +r_info+ as the 64-bit MIPS ABI records it, i.e. a symbol index of
@@ -131,14 +144,14 @@ module ELFTools
131
144
  # 0x0718050000000008 #=> [8, 7]
132
145
  # @return [Array(Integer, Integer)] The symbol index and the type.
133
146
  def mips64_sym_and_type
134
- info = header.r_info.to_i
135
- return [info >> 32, info & 0xff] if header.class.self_endian == :big
147
+ info = @fields[:r_info]
148
+ return [info >> 32, info & 0xff] if @fields.endian == :big
136
149
 
137
150
  [info & 0xffff_ffff, info >> 56]
138
151
  end
139
152
 
140
153
  def mask_bit
141
- header.elf_class == 32 ? 8 : 32
154
+ @fields.elf_class == 32 ? 8 : 32
142
155
  end
143
156
  end
144
157
  end
@@ -78,12 +78,23 @@ module ELFTools
78
78
  private
79
79
 
80
80
  def create_relocation(n)
81
- stream.pos = header.sh_offset + n * header.sh_entsize
82
81
  klass = rela? ? Structs::ELF_Rela : Structs::ELF_Rel
83
- rel = klass.new(endian: header.class.self_endian, offset: stream.pos)
84
- rel.elf_class = header.elf_class
85
- rel.read(stream)
86
- Relocation.new(rel, stream, machine: @machine)
82
+ fields = Structs::Fields.new(klass, stream, table_offset + (n * entsize),
83
+ elf_class: header.elf_class, endian: header.class.self_endian)
84
+ Relocation.new(fields, stream, machine: @machine)
85
+ end
86
+
87
+ # Where this table starts in the file, asked of the header once rather
88
+ # than once for every relocation read.
89
+ # @return [Integer] The file offset.
90
+ def table_offset
91
+ @table_offset ||= header.sh_offset.to_i
92
+ end
93
+
94
+ # How many bytes this table spaces its entries by.
95
+ # @return [Integer] The number.
96
+ def entsize
97
+ @entsize ||= header.sh_entsize.to_i
87
98
  end
88
99
  end
89
100
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'elftools/sections/section'
4
4
  require 'elftools/sections/symbol'
5
+ require 'elftools/structs'
5
6
  require 'elftools/version_tables'
6
7
 
7
8
  module ELFTools
@@ -99,10 +100,31 @@ module ELFTools
99
100
  private
100
101
 
101
102
  def create_symbol(n)
102
- stream.pos = header.sh_offset + n * header.sh_entsize
103
- sym = Structs::ELF_sym[header.elf_class].new(endian: header.class.self_endian, offset: stream.pos)
104
- sym.read(stream)
105
- Symbol.new(sym, stream, symstr: method(:symstr), machine: @machine, version: -> { version_at(n) })
103
+ Symbol.new(
104
+ Structs::Fields.new(Structs::ELF_sym[header.elf_class], stream, table_offset + (n * entsize),
105
+ elf_class: header.elf_class, endian: header.class.self_endian),
106
+ stream, symstr: symstr_reader, machine: @machine, version: -> { version_at(n) }
107
+ )
108
+ end
109
+
110
+ # Where this table starts in the file, asked of the header once rather
111
+ # than once for every symbol read.
112
+ # @return [Integer] The file offset.
113
+ def table_offset
114
+ @table_offset ||= header.sh_offset.to_i
115
+ end
116
+
117
+ # How many bytes this table spaces its entries by.
118
+ # @return [Integer] The number.
119
+ def entsize
120
+ @entsize ||= header.sh_entsize.to_i
121
+ end
122
+
123
+ # What reads the section these symbols are named in, kept so that
124
+ # reading the table does not make one for every symbol.
125
+ # @return [Method] The method.
126
+ def symstr_reader
127
+ @symstr_reader ||= method(:symstr)
106
128
  end
107
129
 
108
130
  # The version the +n+-th symbol binds to.
@@ -1,18 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'elftools/constants'
4
+ require 'elftools/structs'
4
5
  require 'elftools/util'
5
6
 
6
7
  module ELFTools
7
8
  module Sections
8
9
  # Class of symbol.
9
10
  class Symbol
10
- attr_reader :header # @return [ELFTools::Structs::ELF32_sym, ELFTools::Structs::ELF64_sym] Section header.
11
11
  attr_reader :stream # @return [#pos=, #read] Streaming object.
12
12
 
13
13
  # Instantiate a {ELFTools::Sections::Symbol} object.
14
- # @param [ELFTools::Structs::ELF32_sym, ELFTools::Structs::ELF64_sym] header
15
- # The symbol header.
14
+ # @param [ELFTools::Structs::ELF32_sym, ELFTools::Structs::ELF64_sym, ELFTools::Structs::Fields] header
15
+ # The symbol header, or what the file records in one. {ELFTools::Structs::Fields}
16
+ # answers what the symbol records until something asks for {#header}
17
+ # itself, which builds the structure then.
16
18
  # @param [#pos=, #read] stream The streaming object.
17
19
  # @param [ELFTools::Sections::StrTabSection, Proc] symstr
18
20
  # The symbol string section.
@@ -24,17 +26,27 @@ module ELFTools
24
26
  # Call this to get the version this symbol binds to, which only the
25
27
  # symbols a file is loaded by have.
26
28
  def initialize(header, stream, symstr: nil, machine: nil, version: nil)
27
- @header = header
29
+ @fields = header.is_a?(Structs::Fields) ? header : Structs::Fields.of(header)
28
30
  @stream = stream
29
31
  @symstr = symstr
30
32
  @machine = machine
31
33
  @version = version
32
34
  end
33
35
 
36
+ # The structure the file records this symbol in.
37
+ #
38
+ # One is built here for a symbol read without it, which is what assigning
39
+ # to a field of a symbol needs and what {ELFTools::ELFFile#patches}
40
+ # reports the changes of.
41
+ # @return [ELFTools::Structs::ELF32_sym, ELFTools::Structs::ELF64_sym] The structure.
42
+ def header
43
+ @fields.struct
44
+ end
45
+
34
46
  # Return the symbol name.
35
47
  # @return [String] The name.
36
48
  def name
37
- @name ||= @symstr.call.name_at(header.st_name)
49
+ @name ||= @symstr.call.name_at(@fields[:st_name])
38
50
  end
39
51
 
40
52
  # The version this symbol binds to.
@@ -70,7 +82,7 @@ module ELFTools
70
82
  # elf.section_by_name('.symtab').symbol_by_name('main').value
71
83
  # #=> 4196061 # 0x4006dd
72
84
  def value
73
- header.st_value.to_i
85
+ @fields[:st_value]
74
86
  end
75
87
 
76
88
  # How many bytes what this symbol names takes.
@@ -79,7 +91,7 @@ module ELFTools
79
91
  # elf.section_by_name('.symtab').symbol_by_name('main').size
80
92
  # #=> 142
81
93
  def size
82
- header.st_size.to_i
94
+ @fields[:st_size]
83
95
  end
84
96
 
85
97
  # What kind of entity this symbol refers to.
@@ -90,7 +102,7 @@ module ELFTools
90
102
  # symbol.type == ELFTools::Constants::STT_FUNC
91
103
  # #=> true
92
104
  def type
93
- header.st_info & 0xf
105
+ @fields[:st_info] & 0xf
94
106
  end
95
107
 
96
108
  # Sets what kind of entity this symbol refers to.
@@ -122,7 +134,7 @@ module ELFTools
122
134
  # symbol.bind == ELFTools::Constants::STB_GLOBAL
123
135
  # #=> true
124
136
  def bind
125
- header.st_info >> 4
137
+ @fields[:st_info] >> 4
126
138
  end
127
139
 
128
140
  # Sets how this symbol is linked against others with the same name.
@@ -152,7 +164,7 @@ module ELFTools
152
164
  # symbol.visibility == ELFTools::Constants::STV_HIDDEN
153
165
  # #=> true
154
166
  def visibility
155
- header.st_other & 0x3
167
+ @fields[:st_other] & 0x3
156
168
  end
157
169
 
158
170
  # Sets how this symbol is accessed once it becomes part of an executable
@@ -195,7 +207,7 @@ module ELFTools
195
207
  # symbol.section_index == ELFTools::Constants::SHN_UNDEF
196
208
  # #=> true # the symbol is undefined and to be resolved at runtime
197
209
  def section_index
198
- header.st_shndx.to_i
210
+ @fields[:st_shndx]
199
211
  end
200
212
  end
201
213
  end
@@ -2,6 +2,8 @@
2
2
 
3
3
  require 'bindata'
4
4
 
5
+ require 'elftools/exceptions'
6
+
5
7
  module ELFTools
6
8
  # Define ELF related structures in this module.
7
9
  #
@@ -15,6 +17,23 @@ module ELFTools
15
17
  { selection: :elf_class, choices: { 32 => :"#{t}32", 64 => :"#{t}64" }, copy_on_change: true }
16
18
  end
17
19
 
20
+ # How an integer of each width is packed, for +String#unpack+, whether it
21
+ # records a sign or not.
22
+ UNPACK_TEMPLATES = {
23
+ little: {
24
+ false => { 1 => 'C', 2 => 'v', 4 => 'V', 8 => 'Q<' },
25
+ true => { 1 => 'c', 2 => 's<', 4 => 'l<', 8 => 'q<' }
26
+ },
27
+ big: {
28
+ false => { 1 => 'C', 2 => 'n', 4 => 'N', 8 => 'Q>' },
29
+ true => { 1 => 'c', 2 => 's>', 4 => 'l>', 8 => 'q>' }
30
+ }
31
+ }.freeze
32
+
33
+ # Bytes of set bits to read a prototype from, longer than any structure
34
+ # here, so that reading one never runs short of them.
35
+ SET_BITS = ("\xff" * 256).b.freeze
36
+
18
37
  attr_accessor :elf_class # @return [Integer] 32 or 64.
19
38
  attr_accessor :offset # @return [Integer] The file offset of this header.
20
39
 
@@ -103,9 +122,49 @@ module ELFTools
103
122
  end
104
123
 
105
124
  # Gets the endianness of current class.
125
+ #
126
+ # A class is of one endianness for as long as it exists, and asking
127
+ # bindata what it is named costs more than remembering the answer.
106
128
  # @return [:little, :big] The endianness.
107
129
  def self_endian
108
- bindata_name[-2..] == 'be' ? :big : :little
130
+ @self_endian ||= bindata_name[-2..] == 'be' ? :big : :little
131
+ end
132
+
133
+ # What the fields of a structure record, read straight from its bytes.
134
+ #
135
+ # Reading a table of structures costs a structure for every entry of
136
+ # it otherwise, which is most of what reading the table costs. Nothing
137
+ # is remembered of the bytes, so a caller that means to assign to a
138
+ # field wants a structure instead.
139
+ # @param [String] bytes The bytes a structure is recorded in.
140
+ # @param [Integer] elf_class 32 or 64, which decides how wide the fields recording an address are.
141
+ # @param [:little, :big] endian The endianness the file records it in.
142
+ # @return [Hash{Symbol => Integer}] Each field, and what it records.
143
+ # @raise [ELFTools::ELFError] If this is not a structure of integers.
144
+ # @example
145
+ # ELF64_sym.unpack_fields(bytes, elf_class: 64, endian: :little)
146
+ # #=> { st_name: 1, st_info: 18, st_other: 0, st_shndx: 15, st_value: 4198864, st_size: 101 }
147
+ def unpack_fields(bytes, elf_class:, endian:)
148
+ values = bytes.unpack(unpack_template(elf_class, endian))
149
+ fields = {}
150
+ # Paired by hand rather than zipped, which would make an array for
151
+ # every field of every structure read.
152
+ field_names(elf_class, endian).each_with_index { |name, i| fields[name] = values[i] }
153
+ fields
154
+ end
155
+
156
+ # How many bytes a structure of this kind takes.
157
+ # @param [Integer] elf_class 32 or 64, which decides how wide the fields recording an address are.
158
+ # @param [:little, :big] endian The endianness the file records it in.
159
+ # @return [Integer] The number.
160
+ # @example
161
+ # ELF64_sym.num_bytes(elf_class: 64, endian: :little)
162
+ # #=> 24
163
+ def num_bytes(elf_class:, endian:)
164
+ @num_bytes ||= {}
165
+ # Nested rather than keyed by the pair, which would make an array of
166
+ # it for every structure read.
167
+ (@num_bytes[elf_class] ||= {})[endian] ||= prototype(elf_class, endian).num_bytes
109
168
  end
110
169
 
111
170
  # Packs an integer to string.
@@ -128,6 +187,210 @@ module ELFTools
128
187
  out = out.pack('C*')
129
188
  self_endian == :little ? out : out.reverse
130
189
  end
190
+
191
+ private
192
+
193
+ # A structure of this kind with every bit of it set, which the layout is
194
+ # read off: how wide each field is, and whether it records a sign.
195
+ #
196
+ # Every structure of a kind and a class is laid out alike, so one is
197
+ # built for the kind rather than for each question asked about it.
198
+ # @param [Integer] elf_class 32 or 64, which decides how wide the fields recording an address are.
199
+ # @param [:little, :big] endian The endianness the file records it in.
200
+ # @return [ELFTools::Structs::ELFStruct] The structure.
201
+ def prototype(elf_class, endian)
202
+ @prototypes ||= {}
203
+ (@prototypes[elf_class] ||= {})[endian] ||= begin
204
+ struct = new(endian: endian)
205
+ struct.elf_class = elf_class
206
+ struct.read(SET_BITS)
207
+ end
208
+ end
209
+
210
+ # What the fields of this structure are named, in the order they are
211
+ # recorded.
212
+ # @param [Integer] elf_class 32 or 64, which decides how wide the fields recording an address are.
213
+ # @param [:little, :big] endian The endianness the file records it in.
214
+ # @return [Array<Symbol>] The names.
215
+ def field_names(elf_class, endian)
216
+ @field_names ||= {}
217
+ (@field_names[elf_class] ||= {})[endian] ||= prototype(elf_class, endian).field_names
218
+ end
219
+
220
+ # How the fields of this structure are packed, as a template for
221
+ # +String#unpack+.
222
+ # @param [Integer] elf_class 32 or 64, which decides how wide the fields recording an address are.
223
+ # @param [:little, :big] endian The endianness the file records it in.
224
+ # @return [String] The template.
225
+ def unpack_template(elf_class, endian)
226
+ @unpack_templates ||= {}
227
+ (@unpack_templates[elf_class] ||= {})[endian] ||=
228
+ prototype(elf_class, endian).each_pair.map { |_, field| field_template(field, endian) }.join
229
+ end
230
+
231
+ # How one field is packed, which is its width and whether what it read
232
+ # from a field of set bits came back negative.
233
+ # @param [BinData::Base] field The field, read from bits that are all set.
234
+ # @param [:little, :big] endian The endianness the file records it in.
235
+ # @return [String] The template.
236
+ # @raise [ELFTools::ELFError] If the field is not an integer of 1, 2, 4, or 8 bytes.
237
+ def field_template(field, endian)
238
+ width = field.num_bytes
239
+ template = UNPACK_TEMPLATES.fetch(endian)
240
+ raise ELFError, format('%s is not a structure of integers', name) unless template[false].key?(width)
241
+
242
+ template.fetch(field.to_i.negative?)[width]
243
+ end
244
+ end
245
+ end
246
+
247
+ # What a structure the file records at an offset holds, read without
248
+ # building the structure.
249
+ #
250
+ # Reading a table of structures costs a structure for every entry of it
251
+ # otherwise, and setting up the fields of one is most of what it costs.
252
+ # {#struct} builds one for whatever wants the structure itself, which
253
+ # is what assigning to a field takes, and what is assigned is answered
254
+ # with from then on.
255
+ class Fields
256
+ attr_reader :elf_class # @return [Integer] 32 or 64.
257
+ attr_reader :endian # @return [:little, :big] The endianness the file records it in.
258
+
259
+ # What a structure already built holds, for a caller that has one.
260
+ # @param [ELFTools::Structs::ELFStruct] struct The structure.
261
+ # @return [ELFTools::Structs::Fields] The fields.
262
+ def self.of(struct)
263
+ allocate.tap { |fields| fields.send(:built_from, struct) }
264
+ end
265
+
266
+ # Fields nothing in the file records, for what a file states some other
267
+ # way than by recording a structure of it.
268
+ #
269
+ # There is nothing to read and nothing to patch, so a structure is only
270
+ # built if something asks for one, and is built from these.
271
+ # @param [Class] klass The structure class.
272
+ # @param [Hash{Symbol => Integer}] fields What each field is to record.
273
+ # @param [Integer] elf_class 32 or 64.
274
+ # @param [:little, :big] endian The endianness the file records it in.
275
+ # @param [Integer] offset Where in the file this came from.
276
+ # @return [ELFTools::Structs::Fields] The fields.
277
+ # @example
278
+ # Fields.from(ELF_Rel, { r_offset: 0x1000 }, elf_class: 64, endian: :little, offset: 0x40)
279
+ def self.from(klass, fields, elf_class:, endian:, offset:)
280
+ allocate.tap { |made| made.send(:made_of, klass, fields, elf_class, endian, offset) }
281
+ end
282
+
283
+ # @param [Class] klass The structure class.
284
+ # @param [#pos=, #read] stream The streaming object.
285
+ # @param [Integer] offset The file offset the structure is recorded at.
286
+ # @param [Integer] elf_class 32 or 64.
287
+ # @param [:little, :big] endian The endianness the file records it in.
288
+ # @raise [EOFError] If the file does not reach that far.
289
+ def initialize(klass, stream, offset, elf_class:, endian:)
290
+ @klass = klass
291
+ @stream = stream
292
+ @offset = offset
293
+ @elf_class = elf_class
294
+ @endian = endian
295
+ @fields = unpack
296
+ end
297
+
298
+ # What one field of the structure records.
299
+ #
300
+ # The structure answers once there is one, so that a field assigned to
301
+ # reads back as it was assigned.
302
+ # @param [Symbol] name The name of the field.
303
+ # @return [Integer] The value.
304
+ # @example
305
+ # fields[:st_value]
306
+ # #=> 4198864
307
+ def [](name)
308
+ return @struct[name].to_i if @struct
309
+
310
+ @fields[name]
311
+ end
312
+
313
+ # Assigns to one field.
314
+ #
315
+ # The structure takes the assignment wherever the file records one, so
316
+ # that {ELFTools::ELFFile#patches} reports it. Fields the file records
317
+ # no structure of take it themselves, there being nothing to patch.
318
+ # @param [Symbol] name The name of the field.
319
+ # @param [Integer] value What it is to record.
320
+ # @return [void]
321
+ def []=(name, value)
322
+ if @struct.nil? && @stream.nil?
323
+ @fields[name] = value
324
+ else
325
+ struct[name] = value
326
+ end
327
+ end
328
+
329
+ # The structure itself, read from where the file records it, or built
330
+ # from these fields where it records none.
331
+ #
332
+ # The same one however often it is asked for, so that assigning to a
333
+ # field of it is not forgotten.
334
+ # @return [ELFTools::Structs::ELFStruct] The structure.
335
+ def struct
336
+ @struct ||= @stream.nil? ? build : read
337
+ end
338
+
339
+ private
340
+
341
+ # Takes a structure that is already built as what these fields hold.
342
+ # @param [ELFTools::Structs::ELFStruct] struct The structure.
343
+ # @return [void]
344
+ def built_from(struct)
345
+ @struct = struct
346
+ @elf_class = struct.elf_class
347
+ @endian = struct.class.self_endian
348
+ end
349
+
350
+ # Takes fields nothing in the file records a structure of.
351
+ # @return [void]
352
+ def made_of(klass, fields, elf_class, endian, offset)
353
+ @klass = klass
354
+ @fields = fields
355
+ @elf_class = elf_class
356
+ @endian = endian
357
+ @offset = offset
358
+ end
359
+
360
+ # The structure the file records, read from where it records it.
361
+ # @return [ELFTools::Structs::ELFStruct] The structure.
362
+ def read
363
+ struct = new_struct
364
+ @stream.pos = @offset
365
+ struct.read(@stream)
366
+ end
367
+
368
+ # A structure holding what these fields hold, for fields the file
369
+ # records no structure of.
370
+ # @return [ELFTools::Structs::ELFStruct] The structure.
371
+ def build
372
+ new_struct.tap { |struct| @fields.each { |name, value| struct[name] = value } }
373
+ end
374
+
375
+ # A structure of this kind, of the class and the order of the file, and
376
+ # of where in it this came from.
377
+ # @return [ELFTools::Structs::ELFStruct] The structure.
378
+ def new_struct
379
+ struct = @klass.new(endian: @endian, offset: @offset)
380
+ struct.elf_class = @elf_class
381
+ struct
382
+ end
383
+
384
+ # What the fields record, read from the bytes recording them.
385
+ # @return [Hash{Symbol => Integer}] Each field, and what it records.
386
+ # @raise [EOFError] If the file does not reach that far, as reading the structure itself does.
387
+ def unpack
388
+ num_bytes = @klass.num_bytes(elf_class: @elf_class, endian: @endian)
389
+ @stream.pos = @offset
390
+ bytes = @stream.read(num_bytes)
391
+ raise EOFError, 'End of file reached' if bytes.nil? || bytes.bytesize < num_bytes
392
+
393
+ @klass.unpack_fields(bytes, elf_class: @elf_class, endian: @endian)
131
394
  end
132
395
  end
133
396
 
@@ -2,5 +2,5 @@
2
2
 
3
3
  module ELFTools
4
4
  # Current gem version
5
- VERSION = '2.1.0'
5
+ VERSION = '2.2.0'
6
6
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: elftools
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.0
4
+ version: 2.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - david942j
@@ -259,7 +259,7 @@ licenses:
259
259
  - MIT
260
260
  metadata:
261
261
  rubygems_mfa_required: 'true'
262
- changelog_uri: https://github.com/david942j/rbelftools/blob/v2.1.0/CHANGELOG.md
262
+ changelog_uri: https://github.com/david942j/rbelftools/blob/v2.2.0/CHANGELOG.md
263
263
  rdoc_options: []
264
264
  require_paths:
265
265
  - lib