elftools 1.3.1 → 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.
Files changed (112) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +193 -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 +168 -231
  84. data/lib/elftools/dynamic/hash_table.rb +232 -0
  85. data/lib/elftools/dynamic/string_table.rb +30 -0
  86. data/lib/elftools/dynamic/symbols.rb +174 -0
  87. data/lib/elftools/dynamic/tag.rb +66 -0
  88. data/lib/elftools/dynamic/versions.rb +87 -0
  89. data/lib/elftools/dynamic.rb +119 -62
  90. data/lib/elftools/elf_file.rb +74 -22
  91. data/lib/elftools/lazy_array.rb +42 -4
  92. data/lib/elftools/note.rb +6 -3
  93. data/lib/elftools/relative_relocations.rb +88 -0
  94. data/lib/elftools/relocation.rb +144 -0
  95. data/lib/elftools/sections/dynamic_section.rb +13 -0
  96. data/lib/elftools/sections/relative_relocation_section.rb +46 -0
  97. data/lib/elftools/sections/relocation_section.rb +24 -43
  98. data/lib/elftools/sections/section.rb +35 -4
  99. data/lib/elftools/sections/sections.rb +8 -0
  100. data/lib/elftools/sections/sym_tab_section.rb +47 -35
  101. data/lib/elftools/sections/symbol.rb +202 -0
  102. data/lib/elftools/sections/version_definition_section.rb +40 -0
  103. data/lib/elftools/sections/version_need_section.rb +40 -0
  104. data/lib/elftools/sections/version_section.rb +44 -0
  105. data/lib/elftools/segments/dynamic_segment.rb +1 -0
  106. data/lib/elftools/segments/load_segment.rb +33 -5
  107. data/lib/elftools/segments/segment.rb +8 -4
  108. data/lib/elftools/structs.rb +132 -18
  109. data/lib/elftools/util.rb +69 -12
  110. data/lib/elftools/version.rb +1 -1
  111. data/lib/elftools/version_tables.rb +155 -0
  112. metadata +108 -11
@@ -0,0 +1,232 @@
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
+ # 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
+
38
+ private
39
+
40
+ # The header the table starts with.
41
+ # @return [ELFTools::Structs::ELFStruct] The header.
42
+ def header
43
+ @header ||= begin
44
+ @stream.pos = @offset
45
+ self.class::HEADER.new(endian: @endian).read(@stream)
46
+ end
47
+ end
48
+
49
+ # Reads the four-byte word at an offset into the table, the unit buckets
50
+ # and chains are laid out in.
51
+ # @param [Integer] offset The offset into the table.
52
+ # @return [Integer] The word.
53
+ def word_at(offset)
54
+ read_at(offset, 4)
55
+ end
56
+
57
+ # Reads an unsigned integer at an offset into the table.
58
+ # @param [Integer] offset The offset into the table.
59
+ # @param [Integer] bytes How many bytes it takes, 4 or 8.
60
+ # @return [Integer] The integer.
61
+ def read_at(offset, bytes)
62
+ @stream.pos = @offset + offset
63
+ @stream.read(bytes).unpack1("#{bytes == 4 ? 'L' : 'Q'}#{@endian == :big ? '>' : '<'}")
64
+ end
65
+
66
+ # The table +DT_HASH+ points at, which the System V ABI defines.
67
+ #
68
+ # A chain of it belongs to every symbol, so it is the one table that
69
+ # records how many there are.
70
+ class SysV < HashTable
71
+ # The header the table starts with.
72
+ HEADER = Structs::ELF_Hash
73
+
74
+ # How many symbols the table is built over, which it records outright.
75
+ # @return [Integer] The number.
76
+ def num_symbols
77
+ header.nchain.to_i
78
+ end
79
+
80
+ # (see ELFTools::Dynamic::HashTable#covers_every_symbol?)
81
+ def covers_every_symbol?
82
+ true
83
+ end
84
+
85
+ # The index a name sits at.
86
+ #
87
+ # A bucket leads to a chain of the indices whose names hash alike, so
88
+ # the block is what tells them apart.
89
+ # @param [String] name The name.
90
+ # @yieldparam [Integer] index An index whose name hashes like +name+.
91
+ # @yieldreturn [Boolean] Whether the symbol there is the one wanted.
92
+ # @return [Integer, nil]
93
+ # The index, +nil+ if the table does not lead to the name.
94
+ def index_of(name)
95
+ return if header.nbucket.to_i.zero?
96
+
97
+ n = word_at(buckets + ((hash_of(name) % header.nbucket.to_i) * 4))
98
+ # Index zero is the undefined symbol, so it ends a chain instead of
99
+ # belonging to one.
100
+ while n.positive? && n < num_symbols
101
+ return n if yield(n)
102
+
103
+ n = word_at(chain + (n * 4))
104
+ end
105
+ end
106
+
107
+ private
108
+
109
+ # Where the buckets start, as an offset into the table.
110
+ # @return [Integer] The offset.
111
+ def buckets
112
+ header.num_bytes
113
+ end
114
+
115
+ # Where the chains start, as an offset into the table.
116
+ # @return [Integer] The offset.
117
+ def chain
118
+ buckets + (header.nbucket.to_i * 4)
119
+ end
120
+
121
+ # The hash the System V ABI defines, which keeps a name in 28 bits.
122
+ # @param [String] name The name.
123
+ # @return [Integer] The hash.
124
+ def hash_of(name)
125
+ name.each_byte.reduce(0) do |h, c|
126
+ h = (h << 4) + c
127
+ top = h & 0xf000_0000
128
+ (h ^ (top >> 24)) & ~top
129
+ end
130
+ end
131
+ end
132
+
133
+ # The table +DT_GNU_HASH+ points at.
134
+ #
135
+ # It only indexes the defined symbols a file exports under a name, and
136
+ # the symbols before {#symndx} are by construction not among them, so a
137
+ # name it does not lead to may still be in the symbol table.
138
+ class Gnu < HashTable
139
+ # The header the table starts with.
140
+ HEADER = Structs::ELF_GnuHash
141
+
142
+ # How far the table reaches, i.e. the highest index it indexes plus one.
143
+ # @return [Integer] The number.
144
+ def num_symbols
145
+ last = Array.new(header.nbuckets.to_i) { |i| word_at(buckets + (i * 4)) }.max || 0
146
+ return symndx if last < symndx
147
+
148
+ n = last - symndx
149
+ n += 1 while word_at(chain + (n * 4)).even?
150
+ symndx + n + 1
151
+ end
152
+
153
+ # The index a name sits at.
154
+ #
155
+ # A bucket leads to a chain of the indices whose names hash alike, so
156
+ # the block is what tells them apart.
157
+ # @param [String] name The name.
158
+ # @yieldparam [Integer] index An index whose name hashes like +name+.
159
+ # @yieldreturn [Boolean] Whether the symbol there is the one wanted.
160
+ # @return [Integer, nil]
161
+ # The index, +nil+ if the table does not lead to the name.
162
+ def index_of(name, &block)
163
+ return if header.nbuckets.to_i.zero? || header.maskwords.to_i.zero?
164
+
165
+ h = hash_of(name)
166
+ return unless may_index?(h)
167
+
168
+ n = word_at(buckets + ((h % header.nbuckets.to_i) * 4))
169
+ return if n.zero? || n < symndx
170
+
171
+ walk(n, h, &block)
172
+ end
173
+
174
+ private
175
+
176
+ # The first symbol index the table indexes.
177
+ # @return [Integer] The index.
178
+ def symndx
179
+ header.symndx.to_i
180
+ end
181
+
182
+ # Where the buckets start, as an offset into the table.
183
+ # @return [Integer] The offset.
184
+ def buckets
185
+ header.num_bytes + (header.maskwords.to_i * @elf_class / 8)
186
+ end
187
+
188
+ # Where the chains start, as an offset into the table.
189
+ # @return [Integer] The offset.
190
+ def chain
191
+ buckets + (header.nbuckets.to_i * 4)
192
+ end
193
+
194
+ # Walks the chain a bucket leads to, whose entries are the hashes of
195
+ # the names it holds, with the lowest bit marking the last of them.
196
+ # @param [Integer] index The index the bucket leads to.
197
+ # @param [Integer] hash The hash of the name wanted.
198
+ # @return [Integer, nil] The index, +nil+ if the chain ends without it.
199
+ def walk(index, hash)
200
+ loop do
201
+ # The lowest bit belongs to the chain rather than to the hash.
202
+ value = word_at(chain + ((index - symndx) * 4))
203
+ return index if (value | 1) == (hash | 1) && yield(index)
204
+ break if value.odd?
205
+
206
+ index += 1
207
+ end
208
+ end
209
+
210
+ # Whether the filter in front of the table rules a hash out, which it
211
+ # answers for a name the file does not export without the table being
212
+ # read at all. Looking a name up across a chain of files is what it is
213
+ # there for.
214
+ # @param [Integer] hash The hash.
215
+ # @return [Boolean] Whether the name may be indexed.
216
+ def may_index?(hash)
217
+ width = @elf_class / 8
218
+ word = read_at(header.num_bytes + (((hash / @elf_class) % header.maskwords.to_i) * width), width)
219
+ mask = (1 << (hash % @elf_class)) | (1 << ((hash >> header.shift2.to_i) % @elf_class))
220
+ word & mask == mask
221
+ end
222
+
223
+ # The hash GNU defines, which is djb2.
224
+ # @param [String] name The name.
225
+ # @return [Integer] The hash.
226
+ def hash_of(name)
227
+ name.each_byte.reduce(5381) { |h, c| ((h * 33) + c) & 0xffff_ffff }
228
+ end
229
+ end
230
+ end
231
+ end
232
+ 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,174 @@
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
+ 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
37
+ end
38
+
39
+ # How many symbols the tags reach.
40
+ #
41
+ # A hash table that records the number outright is answered with, because
42
+ # nothing a file records can reach further than the table it counts.
43
+ #
44
+ # Where the file records no such table, nothing records how large its
45
+ # symbol table is. The loader never enumerates it: it looks a name up
46
+ # through a hash table and jumps straight to an index, so where the table
47
+ # ends is none of its business. Two things bound it instead, the hash
48
+ # table that indexes the names a file exports and the relocations that
49
+ # name a symbol by index, and the answer is how far the further of the two
50
+ # reaches.
51
+ #
52
+ # That answer is a lower bound. A symbol that is neither indexed by the
53
+ # hash table nor named by a relocation is invisible to both, and is
54
+ # missing from the count. {#symbol_at} is exact for any index.
55
+ # @return [Integer] The number.
56
+ # @example
57
+ # elf.dynamic.num_symbols
58
+ # #=> 9
59
+ def num_symbols
60
+ @num_symbols ||= counted_num_symbols || bounded_num_symbols
61
+ end
62
+
63
+ # Iterate all symbols.
64
+ #
65
+ # Symbols are lazy loaded, so {#symbol_by_name} only creates the symbols
66
+ # it has to look at.
67
+ # @yieldparam [ELFTools::Sections::Symbol] symbol A symbol object.
68
+ # @yieldreturn [void]
69
+ # @return [Enumerator<ELFTools::Sections::Symbol>, Array<ELFTools::Sections::Symbol>]
70
+ # If block is not given, an enumerator will be returned.
71
+ # Otherwise, return array of symbols.
72
+ def each_symbol(&block)
73
+ return enum_for(:each_symbol) unless block_given?
74
+
75
+ Array.new(num_symbols) { |i| symbol_at(i).tap(&block) }
76
+ end
77
+
78
+ # The name this used to go by, kept so that it keeps working.
79
+ alias each_symbols each_symbol
80
+
81
+ # The symbols the tags point at, which is where a file that has been
82
+ # stripped of its sections still records them.
83
+ #
84
+ # As many of them as {#num_symbols} reaches.
85
+ # @return [Array<ELFTools::Sections::Symbol>] The symbols.
86
+ # @example
87
+ # elf.dynamic.symbols.map(&:name)
88
+ # #=> ['', 'puts', '__stack_chk_fail', 'printf', '__libc_start_main']
89
+ def symbols
90
+ each_symbol.to_a
91
+ end
92
+
93
+ # Get symbol by its name.
94
+ #
95
+ # The hash tables answer first, which is the lookup the loader itself
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.
101
+ # @param [String] name The name of symbol.
102
+ # @return [ELFTools::Sections::Symbol, nil] The desired symbol.
103
+ # @example
104
+ # elf.dynamic.symbol_by_name('__libc_start_main').type_name
105
+ # #=> 'STT_FUNC'
106
+ def symbol_by_name(name)
107
+ # Lazily, so that a table is only read when the ones before it have
108
+ # not led anywhere.
109
+ index = hash_tables.lazy.filter_map { |table| table.index_of(name) { |i| symbol_at(i).name == name } }.first
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?)
114
+
115
+ each_symbol.find { |symbol| symbol.name == name }
116
+ end
117
+
118
+ private
119
+
120
+ # The tables the file records the names it exports in, of whichever kinds
121
+ # it records.
122
+ # @return [Array<ELFTools::Dynamic::HashTable>] The tables.
123
+ def hash_tables
124
+ @hash_tables ||= { hash: HashTable::SysV, gnu_hash: HashTable::Gnu }.filter_map do |type, klass|
125
+ tag = tag_by_type(type)
126
+ klass.new(stream, offset_of(tag), elf_class: header.elf_class, endian:) if tag
127
+ end
128
+ end
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
+
144
+ # How far the relocations reach, i.e. the highest index they name plus one.
145
+ # They only ever name the symbols something in the file refers to.
146
+ # @return [Integer, nil] The number, +nil+ if none names a symbol.
147
+ def count_from_relocations
148
+ highest = relocations.map(&:symbol_index).max
149
+ highest && highest + 1
150
+ end
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
+
160
+ # Get the +DT_SYMTAB+'s +d_val+ offset related to file.
161
+ # @return [Integer] The file offset.
162
+ # @raise [ELFTools::ELFError]
163
+ # If +DT_SYMTAB+ is absent, or its address is not in any loadable segment.
164
+ def sym_offset
165
+ @sym_offset ||= begin
166
+ symtab = tag_by_type(:symtab)
167
+ raise ELFError, 'DT_SYMTAB not found' if symtab.nil?
168
+
169
+ offset_of(symtab)
170
+ end
171
+ end
172
+ end
173
+ end
174
+ 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
@@ -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