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.
- checksums.yaml +4 -4
- data/README.md +193 -7
- data/lib/elftools/constants/machine.rb +248 -0
- data/lib/elftools/constants/machine_names.rb +246 -0
- data/lib/elftools/constants/relocation/aarch64.rb +213 -0
- data/lib/elftools/constants/relocation/alpha.rb +47 -0
- data/lib/elftools/constants/relocation/amdgpu.rb +28 -0
- data/lib/elftools/constants/relocation/arm.rb +150 -0
- data/lib/elftools/constants/relocation/avr.rb +51 -0
- data/lib/elftools/constants/relocation/bfin.rb +72 -0
- data/lib/elftools/constants/relocation/bpf.rb +21 -0
- data/lib/elftools/constants/relocation/cr16.rb +46 -0
- data/lib/elftools/constants/relocation/cris.rb +46 -0
- data/lib/elftools/constants/relocation/crx.rb +35 -0
- data/lib/elftools/constants/relocation/csky.rb +79 -0
- data/lib/elftools/constants/relocation/d10v.rb +23 -0
- data/lib/elftools/constants/relocation/d30v.rb +27 -0
- data/lib/elftools/constants/relocation/dlx.rb +24 -0
- data/lib/elftools/constants/relocation/epiphany.rb +28 -0
- data/lib/elftools/constants/relocation/fr30.rb +27 -0
- data/lib/elftools/constants/relocation/frv.rb +57 -0
- data/lib/elftools/constants/relocation/ft32.rb +27 -0
- data/lib/elftools/constants/relocation/h8.rb +69 -0
- data/lib/elftools/constants/relocation/hppa.rb +135 -0
- data/lib/elftools/constants/relocation/i370.rb +26 -0
- data/lib/elftools/constants/relocation/i386.rb +59 -0
- data/lib/elftools/constants/relocation/i860.rb +48 -0
- data/lib/elftools/constants/relocation/i960.rb +22 -0
- data/lib/elftools/constants/relocation/ia64.rb +117 -0
- data/lib/elftools/constants/relocation/ip2k.rb +29 -0
- data/lib/elftools/constants/relocation/iq2000.rb +27 -0
- data/lib/elftools/constants/relocation/kvx.rb +97 -0
- data/lib/elftools/constants/relocation/lm32.rb +32 -0
- data/lib/elftools/constants/relocation/loongarch.rb +144 -0
- data/lib/elftools/constants/relocation/m32c.rb +27 -0
- data/lib/elftools/constants/relocation/m32r.rb +57 -0
- data/lib/elftools/constants/relocation/m68hc11.rb +35 -0
- data/lib/elftools/constants/relocation/m68k.rb +57 -0
- data/lib/elftools/constants/relocation/mcore.rb +27 -0
- data/lib/elftools/constants/relocation/mep.rb +36 -0
- data/lib/elftools/constants/relocation/metag.rb +63 -0
- data/lib/elftools/constants/relocation/microblaze.rb +48 -0
- data/lib/elftools/constants/relocation/mips.rb +125 -0
- data/lib/elftools/constants/relocation/mmix.rb +51 -0
- data/lib/elftools/constants/relocation/mn10200.rb +22 -0
- data/lib/elftools/constants/relocation/mn10300.rb +49 -0
- data/lib/elftools/constants/relocation/moxie.rb +17 -0
- data/lib/elftools/constants/relocation/msp430.rb +27 -0
- data/lib/elftools/constants/relocation/msp430x.rb +37 -0
- data/lib/elftools/constants/relocation/mt.rb +21 -0
- data/lib/elftools/constants/relocation/nds32.rb +167 -0
- data/lib/elftools/constants/relocation/nfp.rb +41 -0
- data/lib/elftools/constants/relocation/nfp3200.rb +46 -0
- data/lib/elftools/constants/relocation/nios2.rb +74 -0
- data/lib/elftools/constants/relocation/or1k.rb +69 -0
- data/lib/elftools/constants/relocation/pj.rb +24 -0
- data/lib/elftools/constants/relocation/ppc.rb +131 -0
- data/lib/elftools/constants/relocation/ppc64.rb +177 -0
- data/lib/elftools/constants/relocation/pru.rb +31 -0
- data/lib/elftools/constants/relocation/riscv.rb +74 -0
- data/lib/elftools/constants/relocation/rl78.rb +70 -0
- data/lib/elftools/constants/relocation/rx.rb +81 -0
- data/lib/elftools/constants/relocation/s12z.rb +22 -0
- data/lib/elftools/constants/relocation/s390.rb +82 -0
- data/lib/elftools/constants/relocation/score.rb +36 -0
- data/lib/elftools/constants/relocation/sh.rb +124 -0
- data/lib/elftools/constants/relocation/sparc.rb +108 -0
- data/lib/elftools/constants/relocation/spu.rb +32 -0
- data/lib/elftools/constants/relocation/tic6x.rb +48 -0
- data/lib/elftools/constants/relocation/tilegx.rb +134 -0
- data/lib/elftools/constants/relocation/tilepro.rb +105 -0
- data/lib/elftools/constants/relocation/v800.rb +101 -0
- data/lib/elftools/constants/relocation/v850.rb +66 -0
- data/lib/elftools/constants/relocation/vax.rb +29 -0
- data/lib/elftools/constants/relocation/visium.rb +30 -0
- data/lib/elftools/constants/relocation/wasm32.rb +16 -0
- data/lib/elftools/constants/relocation/x86_64.rb +68 -0
- data/lib/elftools/constants/relocation/xgate.rb +34 -0
- data/lib/elftools/constants/relocation/xstormy16.rb +29 -0
- data/lib/elftools/constants/relocation/xtensa.rb +75 -0
- data/lib/elftools/constants/relocation/z80.rb +28 -0
- data/lib/elftools/constants/relocation.rb +181 -0
- data/lib/elftools/constants.rb +168 -231
- data/lib/elftools/dynamic/hash_table.rb +232 -0
- data/lib/elftools/dynamic/string_table.rb +30 -0
- data/lib/elftools/dynamic/symbols.rb +174 -0
- data/lib/elftools/dynamic/tag.rb +66 -0
- data/lib/elftools/dynamic/versions.rb +87 -0
- data/lib/elftools/dynamic.rb +119 -62
- data/lib/elftools/elf_file.rb +74 -22
- data/lib/elftools/lazy_array.rb +42 -4
- data/lib/elftools/note.rb +6 -3
- data/lib/elftools/relative_relocations.rb +88 -0
- data/lib/elftools/relocation.rb +144 -0
- data/lib/elftools/sections/dynamic_section.rb +13 -0
- data/lib/elftools/sections/relative_relocation_section.rb +46 -0
- data/lib/elftools/sections/relocation_section.rb +24 -43
- data/lib/elftools/sections/section.rb +35 -4
- data/lib/elftools/sections/sections.rb +8 -0
- data/lib/elftools/sections/sym_tab_section.rb +47 -35
- data/lib/elftools/sections/symbol.rb +202 -0
- data/lib/elftools/sections/version_definition_section.rb +40 -0
- data/lib/elftools/sections/version_need_section.rb +40 -0
- data/lib/elftools/sections/version_section.rb +44 -0
- data/lib/elftools/segments/dynamic_segment.rb +1 -0
- data/lib/elftools/segments/load_segment.rb +33 -5
- data/lib/elftools/segments/segment.rb +8 -4
- data/lib/elftools/structs.rb +132 -18
- data/lib/elftools/util.rb +69 -12
- data/lib/elftools/version.rb +1 -1
- data/lib/elftools/version_tables.rb +155 -0
- 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
|