elftools 1.3.0 → 2.0.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.
Files changed (104) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +111 -7
  3. data/lib/elftools/constants/machine.rb +248 -0
  4. data/lib/elftools/constants/machine_names.rb +246 -0
  5. data/lib/elftools/constants/relocation/aarch64.rb +213 -0
  6. data/lib/elftools/constants/relocation/alpha.rb +47 -0
  7. data/lib/elftools/constants/relocation/amdgpu.rb +28 -0
  8. data/lib/elftools/constants/relocation/arm.rb +150 -0
  9. data/lib/elftools/constants/relocation/avr.rb +51 -0
  10. data/lib/elftools/constants/relocation/bfin.rb +72 -0
  11. data/lib/elftools/constants/relocation/bpf.rb +21 -0
  12. data/lib/elftools/constants/relocation/cr16.rb +46 -0
  13. data/lib/elftools/constants/relocation/cris.rb +46 -0
  14. data/lib/elftools/constants/relocation/crx.rb +35 -0
  15. data/lib/elftools/constants/relocation/csky.rb +79 -0
  16. data/lib/elftools/constants/relocation/d10v.rb +23 -0
  17. data/lib/elftools/constants/relocation/d30v.rb +27 -0
  18. data/lib/elftools/constants/relocation/dlx.rb +24 -0
  19. data/lib/elftools/constants/relocation/epiphany.rb +28 -0
  20. data/lib/elftools/constants/relocation/fr30.rb +27 -0
  21. data/lib/elftools/constants/relocation/frv.rb +57 -0
  22. data/lib/elftools/constants/relocation/ft32.rb +27 -0
  23. data/lib/elftools/constants/relocation/h8.rb +69 -0
  24. data/lib/elftools/constants/relocation/hppa.rb +135 -0
  25. data/lib/elftools/constants/relocation/i370.rb +26 -0
  26. data/lib/elftools/constants/relocation/i386.rb +59 -0
  27. data/lib/elftools/constants/relocation/i860.rb +48 -0
  28. data/lib/elftools/constants/relocation/i960.rb +22 -0
  29. data/lib/elftools/constants/relocation/ia64.rb +117 -0
  30. data/lib/elftools/constants/relocation/ip2k.rb +29 -0
  31. data/lib/elftools/constants/relocation/iq2000.rb +27 -0
  32. data/lib/elftools/constants/relocation/kvx.rb +97 -0
  33. data/lib/elftools/constants/relocation/lm32.rb +32 -0
  34. data/lib/elftools/constants/relocation/loongarch.rb +144 -0
  35. data/lib/elftools/constants/relocation/m32c.rb +27 -0
  36. data/lib/elftools/constants/relocation/m32r.rb +57 -0
  37. data/lib/elftools/constants/relocation/m68hc11.rb +35 -0
  38. data/lib/elftools/constants/relocation/m68k.rb +57 -0
  39. data/lib/elftools/constants/relocation/mcore.rb +27 -0
  40. data/lib/elftools/constants/relocation/mep.rb +36 -0
  41. data/lib/elftools/constants/relocation/metag.rb +63 -0
  42. data/lib/elftools/constants/relocation/microblaze.rb +48 -0
  43. data/lib/elftools/constants/relocation/mips.rb +125 -0
  44. data/lib/elftools/constants/relocation/mmix.rb +51 -0
  45. data/lib/elftools/constants/relocation/mn10200.rb +22 -0
  46. data/lib/elftools/constants/relocation/mn10300.rb +49 -0
  47. data/lib/elftools/constants/relocation/moxie.rb +17 -0
  48. data/lib/elftools/constants/relocation/msp430.rb +27 -0
  49. data/lib/elftools/constants/relocation/msp430x.rb +37 -0
  50. data/lib/elftools/constants/relocation/mt.rb +21 -0
  51. data/lib/elftools/constants/relocation/nds32.rb +167 -0
  52. data/lib/elftools/constants/relocation/nfp.rb +41 -0
  53. data/lib/elftools/constants/relocation/nfp3200.rb +46 -0
  54. data/lib/elftools/constants/relocation/nios2.rb +74 -0
  55. data/lib/elftools/constants/relocation/or1k.rb +69 -0
  56. data/lib/elftools/constants/relocation/pj.rb +24 -0
  57. data/lib/elftools/constants/relocation/ppc.rb +131 -0
  58. data/lib/elftools/constants/relocation/ppc64.rb +177 -0
  59. data/lib/elftools/constants/relocation/pru.rb +31 -0
  60. data/lib/elftools/constants/relocation/riscv.rb +74 -0
  61. data/lib/elftools/constants/relocation/rl78.rb +70 -0
  62. data/lib/elftools/constants/relocation/rx.rb +81 -0
  63. data/lib/elftools/constants/relocation/s12z.rb +22 -0
  64. data/lib/elftools/constants/relocation/s390.rb +82 -0
  65. data/lib/elftools/constants/relocation/score.rb +36 -0
  66. data/lib/elftools/constants/relocation/sh.rb +124 -0
  67. data/lib/elftools/constants/relocation/sparc.rb +108 -0
  68. data/lib/elftools/constants/relocation/spu.rb +32 -0
  69. data/lib/elftools/constants/relocation/tic6x.rb +48 -0
  70. data/lib/elftools/constants/relocation/tilegx.rb +134 -0
  71. data/lib/elftools/constants/relocation/tilepro.rb +105 -0
  72. data/lib/elftools/constants/relocation/v800.rb +101 -0
  73. data/lib/elftools/constants/relocation/v850.rb +66 -0
  74. data/lib/elftools/constants/relocation/vax.rb +29 -0
  75. data/lib/elftools/constants/relocation/visium.rb +30 -0
  76. data/lib/elftools/constants/relocation/wasm32.rb +16 -0
  77. data/lib/elftools/constants/relocation/x86_64.rb +68 -0
  78. data/lib/elftools/constants/relocation/xgate.rb +34 -0
  79. data/lib/elftools/constants/relocation/xstormy16.rb +29 -0
  80. data/lib/elftools/constants/relocation/xtensa.rb +75 -0
  81. data/lib/elftools/constants/relocation/z80.rb +28 -0
  82. data/lib/elftools/constants/relocation.rb +181 -0
  83. data/lib/elftools/constants.rb +124 -231
  84. data/lib/elftools/dynamic/hash_table.rb +216 -0
  85. data/lib/elftools/dynamic/string_table.rb +30 -0
  86. data/lib/elftools/dynamic/symbols.rb +146 -0
  87. data/lib/elftools/dynamic/tag.rb +66 -0
  88. data/lib/elftools/dynamic.rb +102 -62
  89. data/lib/elftools/elf_file.rb +74 -23
  90. data/lib/elftools/lazy_array.rb +42 -4
  91. data/lib/elftools/note.rb +6 -3
  92. data/lib/elftools/relocation.rb +94 -0
  93. data/lib/elftools/sections/dynamic_section.rb +13 -0
  94. data/lib/elftools/sections/relocation_section.rb +24 -43
  95. data/lib/elftools/sections/section.rb +4 -4
  96. data/lib/elftools/sections/sym_tab_section.rb +14 -34
  97. data/lib/elftools/sections/symbol.rb +112 -0
  98. data/lib/elftools/segments/dynamic_segment.rb +1 -0
  99. data/lib/elftools/segments/load_segment.rb +33 -5
  100. data/lib/elftools/segments/segment.rb +8 -4
  101. data/lib/elftools/structs.rb +24 -3
  102. data/lib/elftools/util.rb +1 -1
  103. data/lib/elftools/version.rb +1 -1
  104. metadata +101 -11
@@ -0,0 +1,216 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'elftools/structs'
4
+
5
+ module ELFTools
6
+ module Dynamic
7
+ # A table of names the loader looks a symbol up in, instead of searching
8
+ # the symbol table for it.
9
+ #
10
+ # A file records one of two kinds, or both of them. Both answer the same
11
+ # two questions, how far the symbol table they index reaches and which
12
+ # index a name sits at, and differ in how they are laid out, in how they
13
+ # hash a name, and in which symbols they index at all.
14
+ class HashTable
15
+ # Instantiate a {ELFTools::Dynamic::HashTable} object.
16
+ # @param [#pos=, #read] stream Streaming object.
17
+ # @param [Integer] offset The file offset the table starts at.
18
+ # @param [Integer] elf_class 32 or 64, the width of a mask of the table.
19
+ # @param [Symbol] endian +:little+ or +:big+.
20
+ def initialize(stream, offset, elf_class:, endian:)
21
+ @stream = stream
22
+ @offset = offset
23
+ @elf_class = elf_class
24
+ @endian = endian
25
+ end
26
+
27
+ private
28
+
29
+ # The header the table starts with.
30
+ # @return [ELFTools::Structs::ELFStruct] The header.
31
+ def header
32
+ @header ||= begin
33
+ @stream.pos = @offset
34
+ self.class::HEADER.new(endian: @endian).read(@stream)
35
+ end
36
+ end
37
+
38
+ # Reads the four-byte word at an offset into the table, the unit buckets
39
+ # and chains are laid out in.
40
+ # @param [Integer] offset The offset into the table.
41
+ # @return [Integer] The word.
42
+ def word_at(offset)
43
+ read_at(offset, 4)
44
+ end
45
+
46
+ # Reads an unsigned integer at an offset into the table.
47
+ # @param [Integer] offset The offset into the table.
48
+ # @param [Integer] bytes How many bytes it takes, 4 or 8.
49
+ # @return [Integer] The integer.
50
+ def read_at(offset, bytes)
51
+ @stream.pos = @offset + offset
52
+ @stream.read(bytes).unpack1("#{bytes == 4 ? 'L' : 'Q'}#{@endian == :big ? '>' : '<'}")
53
+ end
54
+
55
+ # The table +DT_HASH+ points at, which the System V ABI defines.
56
+ #
57
+ # A chain of it belongs to every symbol, so it is the one table that
58
+ # records how many there are.
59
+ class SysV < HashTable
60
+ # The header the table starts with.
61
+ HEADER = Structs::ELF_Hash
62
+
63
+ # How many symbols the table is built over, which it records outright.
64
+ # @return [Integer] The number.
65
+ def num_symbols
66
+ header.nchain.to_i
67
+ end
68
+
69
+ # The index a name sits at.
70
+ #
71
+ # A bucket leads to a chain of the indices whose names hash alike, so
72
+ # the block is what tells them apart.
73
+ # @param [String] name The name.
74
+ # @yieldparam [Integer] index An index whose name hashes like +name+.
75
+ # @yieldreturn [Boolean] Whether the symbol there is the one wanted.
76
+ # @return [Integer, nil]
77
+ # The index, +nil+ if the table does not lead to the name.
78
+ def index_of(name)
79
+ return if header.nbucket.to_i.zero?
80
+
81
+ n = word_at(buckets + ((hash_of(name) % header.nbucket.to_i) * 4))
82
+ # Index zero is the undefined symbol, so it ends a chain instead of
83
+ # belonging to one.
84
+ while n.positive? && n < num_symbols
85
+ return n if yield(n)
86
+
87
+ n = word_at(chain + (n * 4))
88
+ end
89
+ end
90
+
91
+ private
92
+
93
+ # Where the buckets start, as an offset into the table.
94
+ # @return [Integer] The offset.
95
+ def buckets
96
+ header.num_bytes
97
+ end
98
+
99
+ # Where the chains start, as an offset into the table.
100
+ # @return [Integer] The offset.
101
+ def chain
102
+ buckets + (header.nbucket.to_i * 4)
103
+ end
104
+
105
+ # The hash the System V ABI defines, which keeps a name in 28 bits.
106
+ # @param [String] name The name.
107
+ # @return [Integer] The hash.
108
+ def hash_of(name)
109
+ name.each_byte.reduce(0) do |h, c|
110
+ h = (h << 4) + c
111
+ top = h & 0xf000_0000
112
+ (h ^ (top >> 24)) & ~top
113
+ end
114
+ end
115
+ end
116
+
117
+ # The table +DT_GNU_HASH+ points at.
118
+ #
119
+ # It only indexes the defined symbols a file exports under a name, and
120
+ # the symbols before {#symndx} are by construction not among them, so a
121
+ # name it does not lead to may still be in the symbol table.
122
+ class Gnu < HashTable
123
+ # The header the table starts with.
124
+ HEADER = Structs::ELF_GnuHash
125
+
126
+ # How far the table reaches, i.e. the highest index it indexes plus one.
127
+ # @return [Integer] The number.
128
+ def num_symbols
129
+ last = Array.new(header.nbuckets.to_i) { |i| word_at(buckets + (i * 4)) }.max || 0
130
+ return symndx if last < symndx
131
+
132
+ n = last - symndx
133
+ n += 1 while word_at(chain + (n * 4)).even?
134
+ symndx + n + 1
135
+ end
136
+
137
+ # The index a name sits at.
138
+ #
139
+ # A bucket leads to a chain of the indices whose names hash alike, so
140
+ # the block is what tells them apart.
141
+ # @param [String] name The name.
142
+ # @yieldparam [Integer] index An index whose name hashes like +name+.
143
+ # @yieldreturn [Boolean] Whether the symbol there is the one wanted.
144
+ # @return [Integer, nil]
145
+ # The index, +nil+ if the table does not lead to the name.
146
+ def index_of(name, &block)
147
+ return if header.nbuckets.to_i.zero? || header.maskwords.to_i.zero?
148
+
149
+ h = hash_of(name)
150
+ return unless may_index?(h)
151
+
152
+ n = word_at(buckets + ((h % header.nbuckets.to_i) * 4))
153
+ return if n.zero? || n < symndx
154
+
155
+ walk(n, h, &block)
156
+ end
157
+
158
+ private
159
+
160
+ # The first symbol index the table indexes.
161
+ # @return [Integer] The index.
162
+ def symndx
163
+ header.symndx.to_i
164
+ end
165
+
166
+ # Where the buckets start, as an offset into the table.
167
+ # @return [Integer] The offset.
168
+ def buckets
169
+ header.num_bytes + (header.maskwords.to_i * @elf_class / 8)
170
+ end
171
+
172
+ # Where the chains start, as an offset into the table.
173
+ # @return [Integer] The offset.
174
+ def chain
175
+ buckets + (header.nbuckets.to_i * 4)
176
+ end
177
+
178
+ # Walks the chain a bucket leads to, whose entries are the hashes of
179
+ # the names it holds, with the lowest bit marking the last of them.
180
+ # @param [Integer] index The index the bucket leads to.
181
+ # @param [Integer] hash The hash of the name wanted.
182
+ # @return [Integer, nil] The index, +nil+ if the chain ends without it.
183
+ def walk(index, hash)
184
+ loop do
185
+ # The lowest bit belongs to the chain rather than to the hash.
186
+ value = word_at(chain + ((index - symndx) * 4))
187
+ return index if (value | 1) == (hash | 1) && yield(index)
188
+ break if value.odd?
189
+
190
+ index += 1
191
+ end
192
+ end
193
+
194
+ # Whether the filter in front of the table rules a hash out, which it
195
+ # answers for a name the file does not export without the table being
196
+ # read at all. Looking a name up across a chain of files is what it is
197
+ # there for.
198
+ # @param [Integer] hash The hash.
199
+ # @return [Boolean] Whether the name may be indexed.
200
+ def may_index?(hash)
201
+ width = @elf_class / 8
202
+ word = read_at(header.num_bytes + (((hash / @elf_class) % header.maskwords.to_i) * width), width)
203
+ mask = (1 << (hash % @elf_class)) | (1 << ((hash >> header.shift2.to_i) % @elf_class))
204
+ word & mask == mask
205
+ end
206
+
207
+ # The hash GNU defines, which is djb2.
208
+ # @param [String] name The name.
209
+ # @return [Integer] The hash.
210
+ def hash_of(name)
211
+ name.each_byte.reduce(5381) { |h, c| ((h * 33) + c) & 0xffff_ffff }
212
+ end
213
+ end
214
+ end
215
+ end
216
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'elftools/util'
4
+
5
+ module ELFTools
6
+ module Dynamic
7
+ # The names a file records, which the tags point at rather than a section.
8
+ #
9
+ # Answers what {ELFTools::Sections::StrTabSection} answers, so that what is
10
+ # read from the tags names itself the way what is read from the sections
11
+ # does.
12
+ class StringTable
13
+ # Instantiate a {ELFTools::Dynamic::StringTable} object.
14
+ # @param [#pos=, #read] stream Streaming object.
15
+ # @param [Method] offset
16
+ # Call this method to get the file offset the table starts at.
17
+ def initialize(stream, offset)
18
+ @stream = stream
19
+ @offset = offset
20
+ end
21
+
22
+ # Return the name recorded at an offset into the table.
23
+ # @param [Integer] offset Usually from +tag.d_val+ or +sym.st_name+.
24
+ # @return [String] The name without null bytes.
25
+ def name_at(offset)
26
+ Util.cstring(@stream, @offset.call + offset)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,146 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'elftools/dynamic/hash_table'
4
+ require 'elftools/exceptions'
5
+ require 'elftools/sections/symbol'
6
+ require 'elftools/structs'
7
+
8
+ module ELFTools
9
+ module Dynamic
10
+ # The symbols a file is loaded by, which the tags point at.
11
+ #
12
+ # @note
13
+ # This module is included by {ELFTools::Dynamic} and reads through the
14
+ # methods there, so it cannot be included on its own.
15
+ module Symbols
16
+ # Get the +n+-th symbol.
17
+ #
18
+ # Symbols are lazy loaded.
19
+ # @note
20
+ # We cannot do bound checking of +n+ here, because nothing records how
21
+ # many symbols there are. {#num_symbols} is a lower bound rather than a
22
+ # bound, so checking against it would hide symbols this method reads
23
+ # correctly.
24
+ # @param [Integer] n The index.
25
+ # @return [ELFTools::Sections::Symbol] The desired symbol.
26
+ # @raise [ELFTools::ELFError]
27
+ # If +DT_SYMTAB+ is absent, or its address is not in any loadable segment.
28
+ def symbol_at(n)
29
+ return if n.negative?
30
+
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
39
+ end
40
+
41
+ # How many symbols the tags reach.
42
+ #
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.
50
+ #
51
+ # This is therefore a lower bound. A symbol that is neither indexed by the
52
+ # hash table nor named by a relocation is invisible to both, and is
53
+ # missing from the count. {#symbol_at} is exact for any index.
54
+ # @return [Integer] The number.
55
+ # @example
56
+ # elf.dynamic.num_symbols
57
+ # #=> 9
58
+ def num_symbols
59
+ @num_symbols ||= (hash_tables.map(&:num_symbols) + [count_from_relocations]).compact.max || 0
60
+ end
61
+
62
+ # Iterate all symbols.
63
+ #
64
+ # Symbols are lazy loaded, so {#symbol_by_name} only creates the symbols
65
+ # it has to look at.
66
+ # @yieldparam [ELFTools::Sections::Symbol] symbol A symbol object.
67
+ # @yieldreturn [void]
68
+ # @return [Enumerator<ELFTools::Sections::Symbol>, Array<ELFTools::Sections::Symbol>]
69
+ # If block is not given, an enumerator will be returned.
70
+ # Otherwise, return array of symbols.
71
+ def each_symbol(&block)
72
+ return enum_for(:each_symbol) unless block_given?
73
+
74
+ Array.new(num_symbols) { |i| symbol_at(i).tap(&block) }
75
+ end
76
+
77
+ # The name this used to go by, kept so that it keeps working.
78
+ alias each_symbols each_symbol
79
+
80
+ # The symbols the tags point at, which is where a file that has been
81
+ # stripped of its sections still records them.
82
+ #
83
+ # As many of them as {#num_symbols} reaches.
84
+ # @return [Array<ELFTools::Sections::Symbol>] The symbols.
85
+ # @example
86
+ # elf.dynamic.symbols.map(&:name)
87
+ # #=> ['', 'puts', '__stack_chk_fail', 'printf', '__libc_start_main']
88
+ def symbols
89
+ each_symbol.to_a
90
+ end
91
+
92
+ # Get symbol by its name.
93
+ #
94
+ # 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.
98
+ # @param [String] name The name of symbol.
99
+ # @return [ELFTools::Sections::Symbol, nil] The desired symbol.
100
+ # @example
101
+ # elf.dynamic.symbol_by_name('__libc_start_main').type_name
102
+ # #=> 'STT_FUNC'
103
+ def symbol_by_name(name)
104
+ # Lazily, so that a table is only read when the ones before it have
105
+ # not led anywhere.
106
+ index = hash_tables.lazy.filter_map { |table| table.index_of(name) { |i| symbol_at(i).name == name } }.first
107
+ return symbol_at(index) if index
108
+
109
+ each_symbol.find { |symbol| symbol.name == name }
110
+ end
111
+
112
+ private
113
+
114
+ # The tables the file records the names it exports in, of whichever kinds
115
+ # it records.
116
+ # @return [Array<ELFTools::Dynamic::HashTable>] The tables.
117
+ def hash_tables
118
+ @hash_tables ||= { hash: HashTable::SysV, gnu_hash: HashTable::Gnu }.filter_map do |type, klass|
119
+ tag = tag_by_type(type)
120
+ klass.new(stream, offset_of(tag), elf_class: header.elf_class, endian:) if tag
121
+ end
122
+ end
123
+
124
+ # How far the relocations reach, i.e. the highest index they name plus one.
125
+ # They only ever name the symbols something in the file refers to.
126
+ # @return [Integer, nil] The number, +nil+ if none names a symbol.
127
+ def count_from_relocations
128
+ highest = relocations.map(&:symbol_index).max
129
+ highest && highest + 1
130
+ end
131
+
132
+ # Get the +DT_SYMTAB+'s +d_val+ offset related to file.
133
+ # @return [Integer] The file offset.
134
+ # @raise [ELFTools::ELFError]
135
+ # If +DT_SYMTAB+ is absent, or its address is not in any loadable segment.
136
+ def sym_offset
137
+ @sym_offset ||= begin
138
+ symtab = tag_by_type(:symtab)
139
+ raise ELFError, 'DT_SYMTAB not found' if symtab.nil?
140
+
141
+ offset_of(symtab)
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'elftools/constants'
4
+
5
+ module ELFTools
6
+ module Dynamic
7
+ # A tag class.
8
+ class Tag
9
+ attr_reader :header # @return [ELFTools::Structs::ELF_Dyn] The dynamic tag header.
10
+ attr_reader :stream # @return [#pos=, #read] Streaming object.
11
+
12
+ # Instantiate a {ELFTools::Dynamic::Tag} object.
13
+ # @param [ELF_Dyn] header The dynamic tag header.
14
+ # @param [#pos=, #read] stream Streaming object.
15
+ # @param [ELFTools::Dynamic::StringTable] strtab
16
+ # The string table the names of tags are recorded in.
17
+ def initialize(header, stream, strtab)
18
+ @header = header
19
+ @stream = stream
20
+ @strtab = strtab
21
+ end
22
+
23
+ # Some dynamic have name.
24
+ TYPE_WITH_NAME = [Constants::DT_NEEDED,
25
+ Constants::DT_SONAME,
26
+ Constants::DT_RPATH,
27
+ Constants::DT_RUNPATH].freeze
28
+ # Return the content of this tag records.
29
+ #
30
+ # For normal tags, this method just return
31
+ # +header.d_val+. For tags with +header.d_val+
32
+ # in meaning of string offset (e.g. DT_NEEDED), this method would
33
+ # return the string it specified.
34
+ # Tags with type in {TYPE_WITH_NAME} are those tags with name.
35
+ # @return [Integer, String] The content this tag records.
36
+ # @example
37
+ # dynamic = elf.segment_by_type(:dynamic)
38
+ # dynamic.tag_by_type(:init).value
39
+ # #=> 4195600 # 0x400510
40
+ # dynamic.tag_by_type(:needed).value
41
+ # #=> 'libc.so.6'
42
+ def value
43
+ name || header.d_val.to_i
44
+ end
45
+
46
+ # Is this tag has a name?
47
+ #
48
+ # The criteria here is if this tag's type is in {TYPE_WITH_NAME}.
49
+ # @return [Boolean] Is this tag has a name.
50
+ def name?
51
+ TYPE_WITH_NAME.include?(header.d_tag)
52
+ end
53
+
54
+ # Return the name of this tag.
55
+ #
56
+ # Only tags with name would return a name.
57
+ # Others would return +nil+.
58
+ # @return [String, nil] The name.
59
+ def name
60
+ return nil unless name?
61
+
62
+ @strtab.name_at(header.d_val.to_i)
63
+ end
64
+ end
65
+ end
66
+ end
@@ -1,5 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'elftools/constants'
4
+ require 'elftools/dynamic/string_table'
5
+ require 'elftools/dynamic/symbols'
6
+ require 'elftools/dynamic/tag'
7
+ require 'elftools/exceptions'
8
+ require 'elftools/relocation'
9
+ require 'elftools/structs'
10
+
3
11
  module ELFTools
4
12
  # Define common methods for dynamic sections and dynamic segments.
5
13
  #
@@ -8,6 +16,8 @@ module ELFTools
8
16
  # and {ELFTools::Segments::DynamicSegment} because methods here assume some
9
17
  # attributes exist.
10
18
  module Dynamic
19
+ include Symbols
20
+
11
21
  # Iterate all tags.
12
22
  #
13
23
  # @note
@@ -18,8 +28,8 @@ module ELFTools
18
28
  # @return [Enumerator<ELFTools::Dynamic::Tag>, Array<ELFTools::Dynamic::Tag>]
19
29
  # If block is not given, an enumerator will be returned.
20
30
  # Otherwise, return array of tags.
21
- def each_tags(&block)
22
- return enum_for(:each_tags) unless block_given?
31
+ def each_tag(&block)
32
+ return enum_for(:each_tag) unless block_given?
23
33
 
24
34
  arr = []
25
35
  0.step do |i|
@@ -30,11 +40,14 @@ module ELFTools
30
40
  arr
31
41
  end
32
42
 
43
+ # The name this used to go by, kept so that it keeps working.
44
+ alias each_tags each_tag
45
+
33
46
  # Use {#tags} to get all tags.
34
47
  # @return [Array<ELFTools::Dynamic::Tag>]
35
48
  # Array of tags.
36
49
  def tags
37
- @tags ||= each_tags.to_a
50
+ @tags ||= each_tag.to_a
38
51
  end
39
52
 
40
53
  # Get a tag of specific type.
@@ -63,7 +76,7 @@ module ELFTools
63
76
  # #=> #<ELFTools::Dynamic::Tag:0x0055d3d2d91b28 @header={:d_tag=>3, :d_val=>6295552}>
64
77
  def tag_by_type(type)
65
78
  type = Util.to_constant(Constants::DT, type)
66
- each_tags.find { |tag| tag.header.d_tag == type }
79
+ each_tag.find { |tag| tag.header.d_tag == type }
67
80
  end
68
81
 
69
82
  # Get tags of specific type.
@@ -75,7 +88,7 @@ module ELFTools
75
88
  # @see #tag_by_type
76
89
  def tags_by_type(type)
77
90
  type = Util.to_constant(Constants::DT, type)
78
- each_tags.select { |tag| tag.header.d_tag == type }
91
+ each_tag.select { |tag| tag.header.d_tag == type }
79
92
  end
80
93
 
81
94
  # Get the +n+-th tag.
@@ -100,7 +113,22 @@ module ELFTools
100
113
  dyn.elf_class = header.elf_class
101
114
  stream.pos = tag_start + n * dyn.num_bytes
102
115
  dyn.offset = stream.pos
103
- @tag_at_map[n] = Tag.new(dyn.read(stream), stream, method(:str_offset))
116
+ @tag_at_map[n] = Tag.new(dyn.read(stream), stream, string_table)
117
+ end
118
+
119
+ # The relocations the tags point at.
120
+ #
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
+ # @return [Array<ELFTools::Relocation>] The relocations, in the order the
124
+ # tags record them.
125
+ # @raise [ELFTools::ELFError]
126
+ # If a table is not in any loadable segment.
127
+ # @example
128
+ # elf.dynamic.relocations.map(&:type_name).uniq
129
+ # #=> ['R_X86_64_GLOB_DAT', 'R_X86_64_JUMP_SLOT']
130
+ def relocations
131
+ @relocations ||= relocation_tables.flat_map { |start, size, rela| read_relocations(start, size, rela) }
104
132
  end
105
133
 
106
134
  private
@@ -109,69 +137,81 @@ module ELFTools
109
137
  header.class.self_endian
110
138
  end
111
139
 
112
- # Get the DT_STRTAB's +d_val+ offset related to file.
113
- def str_offset
114
- # TODO: handle DT_STRTAB not exitsts.
115
- @str_offset ||= @offset_from_vma.call(tag_by_type(:strtab).header.d_val.to_i)
140
+ # Where each table of relocations starts, the tag recording how many bytes
141
+ # it takes, and whether its entries record an addend.
142
+ # @return [Array<Array(ELFTools::Dynamic::Tag, ELFTools::Dynamic::Tag, Boolean)>] The tables.
143
+ def relocation_tables
144
+ tables = %i[rel rela].filter_map do |type|
145
+ tag = tag_by_type(type)
146
+ [tag, tag_by_type(:"#{type}sz"), type == :rela] if tag
147
+ end
148
+ jmprel = tag_by_type(:jmprel)
149
+ return tables if jmprel.nil?
150
+
151
+ tables << [jmprel, tag_by_type(:pltrelsz), tag_by_type(:pltrel).header.d_val.to_i == Constants::DT_RELA]
116
152
  end
117
153
 
118
- # A tag class.
119
- class Tag
120
- attr_reader :header # @return [ELFTools::Structs::ELF_Dyn] The dynamic tag header.
121
- attr_reader :stream # @return [#pos=, #read] Streaming object.
122
-
123
- # Instantiate a {ELFTools::Dynamic::Tag} object.
124
- # @param [ELF_Dyn] header The dynamic tag header.
125
- # @param [#pos=, #read] stream Streaming object.
126
- # @param [Method] str_offset
127
- # Call this method to get the string offset related
128
- # to file.
129
- def initialize(header, stream, str_offset)
130
- @header = header
131
- @stream = stream
132
- @str_offset = str_offset
154
+ # Reads one table of relocations.
155
+ # @return [Array<ELFTools::Relocation>] The relocations.
156
+ def read_relocations(start, size, rela)
157
+ klass = rela ? Structs::ELF_Rela : Structs::ELF_Rel
158
+ offset = offset_of(start)
159
+ # An entry takes what its structure takes. DT_RELAENT and DT_RELENT
160
+ # record the same number, which a file has no way of disagreeing with
161
+ # and every file here agrees with.
162
+ entsize = struct(klass).num_bytes
163
+ Array.new(size.header.d_val.to_i / entsize) do |i|
164
+ Relocation.new(read_struct(klass, offset + (i * entsize)), stream, machine: @machine)
133
165
  end
166
+ end
134
167
 
135
- # Some dynamic have name.
136
- TYPE_WITH_NAME = [Constants::DT_NEEDED,
137
- Constants::DT_SONAME,
138
- Constants::DT_RPATH,
139
- Constants::DT_RUNPATH].freeze
140
- # Return the content of this tag records.
141
- #
142
- # For normal tags, this method just return
143
- # +header.d_val+. For tags with +header.d_val+
144
- # in meaning of string offset (e.g. DT_NEEDED), this method would
145
- # return the string it specified.
146
- # Tags with type in {TYPE_WITH_NAME} are those tags with name.
147
- # @return [Integer, String] The content this tag records.
148
- # @example
149
- # dynamic = elf.segment_by_type(:dynamic)
150
- # dynamic.tag_by_type(:init).value
151
- # #=> 4195600 # 0x400510
152
- # dynamic.tag_by_type(:needed).value
153
- # #=> 'libc.so.6'
154
- def value
155
- name || header.d_val.to_i
156
- end
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
157
176
 
158
- # Is this tag has a name?
159
- #
160
- # The criteria here is if this tag's type is in {TYPE_WITH_NAME}.
161
- # @return [Boolean] Is this tag has a name.
162
- def name?
163
- TYPE_WITH_NAME.include?(header.d_tag)
164
- end
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
165
187
 
166
- # Return the name of this tag.
167
- #
168
- # Only tags with name would return a name.
169
- # Others would return +nil+.
170
- # @return [String, nil] The name.
171
- def name
172
- return nil unless name?
188
+ # The file offset the address a tag records points at.
189
+ # @param [ELFTools::Dynamic::Tag] tag The tag.
190
+ # @return [Integer] The file offset.
191
+ # @raise [ELFTools::ELFError]
192
+ # If the address is not in any loadable segment.
193
+ def offset_of(tag)
194
+ vma = tag.header.d_val.to_i
195
+ @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))
197
+ end
198
+
199
+ # The names the tags and the symbols point at.
200
+ # @return [ELFTools::Dynamic::StringTable] The string table.
201
+ def string_table
202
+ @string_table ||= StringTable.new(stream, method(:str_offset))
203
+ end
204
+
205
+ # Get the DT_STRTAB's +d_val+ offset related to file.
206
+ # @return [Integer] The file offset.
207
+ # @raise [ELFTools::ELFError]
208
+ # If DT_STRTAB is absent, or its address is not in any loadable segment.
209
+ def str_offset
210
+ @str_offset ||= begin
211
+ strtab = tag_by_type(:strtab)
212
+ raise ELFError, 'DT_STRTAB not found' if strtab.nil?
173
213
 
174
- Util.cstring(stream, @str_offset.call + header.d_val.to_i)
214
+ offset_of(strtab)
175
215
  end
176
216
  end
177
217
  end