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.
- checksums.yaml +4 -4
- data/README.md +111 -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 +124 -231
- data/lib/elftools/dynamic/hash_table.rb +216 -0
- data/lib/elftools/dynamic/string_table.rb +30 -0
- data/lib/elftools/dynamic/symbols.rb +146 -0
- data/lib/elftools/dynamic/tag.rb +66 -0
- data/lib/elftools/dynamic.rb +102 -62
- data/lib/elftools/elf_file.rb +74 -23
- data/lib/elftools/lazy_array.rb +42 -4
- data/lib/elftools/note.rb +6 -3
- data/lib/elftools/relocation.rb +94 -0
- data/lib/elftools/sections/dynamic_section.rb +13 -0
- data/lib/elftools/sections/relocation_section.rb +24 -43
- data/lib/elftools/sections/section.rb +4 -4
- data/lib/elftools/sections/sym_tab_section.rb +14 -34
- data/lib/elftools/sections/symbol.rb +112 -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 +24 -3
- data/lib/elftools/util.rb +1 -1
- data/lib/elftools/version.rb +1 -1
- 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
|
data/lib/elftools/dynamic.rb
CHANGED
|
@@ -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
|
|
22
|
-
return enum_for(:
|
|
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 ||=
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
#
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
#
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
#
|
|
124
|
-
#
|
|
125
|
-
#
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
-
|
|
214
|
+
offset_of(strtab)
|
|
175
215
|
end
|
|
176
216
|
end
|
|
177
217
|
end
|