ruby-elf 1.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.
- data/COPYING +339 -0
- data/DONATING +42 -0
- data/bin/cowstats +264 -0
- data/bin/elfgrep +185 -0
- data/bin/missingstatic +112 -0
- data/bin/rbelf-size +123 -0
- data/bin/verify-lfs +120 -0
- data/extras/README.extras +5 -0
- data/extras/bindings-parsers.rb +157 -0
- data/lib/bytestream-reader.rb +271 -0
- data/lib/elf.rb +248 -0
- data/lib/elf/dynamic.rb +392 -0
- data/lib/elf/file.rb +366 -0
- data/lib/elf/gnu.rb +174 -0
- data/lib/elf/section.rb +321 -0
- data/lib/elf/stringtable.rb +49 -0
- data/lib/elf/sunw.rb +158 -0
- data/lib/elf/symbol.rb +368 -0
- data/lib/elf/symbol/demangler_gcc3.rb +1952 -0
- data/lib/elf/symboltable.rb +90 -0
- data/lib/elf/tools.rb +228 -0
- data/lib/elf/utils/loader.rb +112 -0
- data/lib/elf/utils/pool.rb +37 -0
- data/lib/elf/value.rb +128 -0
- data/manpages/cowstats.1 +180 -0
- data/manpages/elfgrep.1 +188 -0
- data/manpages/missingstatic.1 +176 -0
- data/manpages/rbelf-size.1 +186 -0
- data/manpages/verify-lfs.1 +95 -0
- data/tools/assess_duplicate_save.rb +105 -0
- data/tools/link-collisions/analyse.rb +57 -0
- data/tools/link-collisions/harvest.rb +367 -0
- data/tools/link-collisions/known-broken +165 -0
- data/tools/link-collisions/multimplementations +125 -0
- data/tools/link-collisions/suppress.rb +84 -0
- data/tools/link-collisions/suppressions +279 -0
- data/tools/nm.rb +78 -0
- data/tools/rbelf-lddtree.rb +49 -0
- data/tools/readelf-d.rb +76 -0
- metadata +114 -0
data/lib/elf/dynamic.rb
ADDED
@@ -0,0 +1,392 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
# Simple ELF parser for Ruby
|
3
|
+
#
|
4
|
+
# Copyright © 2007-2010 Diego E. "Flameeyes" Pettenò <flameeyes@gmail.com>
|
5
|
+
# Portions inspired by elf.py
|
6
|
+
# Copyright © 2002 Netgraft Corporation
|
7
|
+
# Portions inspired by elf.h
|
8
|
+
# Copyright © 1995-2006 Free Software Foundation, Inc.
|
9
|
+
#
|
10
|
+
# This program is free software; you can redistribute it and/or modify
|
11
|
+
# it under the terms of the GNU General Public License as published by
|
12
|
+
# the Free Software Foundation; either version 2 of the License, or
|
13
|
+
# (at your option) any later version.
|
14
|
+
#
|
15
|
+
# This program is distributed in the hope that it will be useful,
|
16
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
17
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
18
|
+
# GNU General Public License for more details.
|
19
|
+
#
|
20
|
+
# You should have received a copy of the GNU General Public License
|
21
|
+
# along with this generator; if not, write to the Free Software
|
22
|
+
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
23
|
+
|
24
|
+
require 'elf/utils/loader'
|
25
|
+
|
26
|
+
module Elf
|
27
|
+
class Dynamic < Section
|
28
|
+
class Type < Value
|
29
|
+
class Unknown < Elf::Value::Unknown
|
30
|
+
def initialize(val, desc)
|
31
|
+
super(val, desc)
|
32
|
+
end
|
33
|
+
|
34
|
+
# For unknown types, guess Value is the key.
|
35
|
+
def attribute
|
36
|
+
:Value
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
attr_reader :attribute
|
41
|
+
|
42
|
+
def initialize(val, params)
|
43
|
+
super(val, params)
|
44
|
+
|
45
|
+
@attribute = params[2]
|
46
|
+
end
|
47
|
+
|
48
|
+
fill(
|
49
|
+
0 => [ :Null, "NULL", :Ignore ],
|
50
|
+
1 => [ :Needed, "NEEDED", :Value ],
|
51
|
+
2 => [ :PltRelSz, "PLTRELSZ", :Value ],
|
52
|
+
3 => [ :PltGot, "PLTGOT", :Ignore ],
|
53
|
+
4 => [ :Hash, "HASH", :Address ],
|
54
|
+
5 => [ :StrTab, "STRTAB", :Address ],
|
55
|
+
6 => [ :SymTab, "SYMTAB", :Address ],
|
56
|
+
7 => [ :RelA, "RELA", :Address ],
|
57
|
+
8 => [ :RelASz, "RELASZ", :Value ],
|
58
|
+
9 => [ :RelAEnt, "RELAENT", :Value ],
|
59
|
+
10 => [ :StrSz, "STRSZ", :Value ],
|
60
|
+
11 => [ :SymEnt, "SYMENT", :Value ],
|
61
|
+
12 => [ :Init, "INIT", :Address ],
|
62
|
+
13 => [ :Fini, "FINI", :Address ],
|
63
|
+
14 => [ :SoName, "SONAME", :Value ],
|
64
|
+
15 => [ :RPath, "RPATH", :Value ],
|
65
|
+
16 => [ :Symbolic, "SYMBOLIC", :Ignore ],
|
66
|
+
17 => [ :Rel, "REL", :Address ],
|
67
|
+
18 => [ :RelSz, "RELSZ", :Value ],
|
68
|
+
19 => [ :RelEnt, "RELENT", :Value ],
|
69
|
+
20 => [ :PltRel, "PLTREL", :Value ],
|
70
|
+
21 => [ :Debug, "DEBUG", :Ignore ],
|
71
|
+
22 => [ :TextRel, "TEXTREL", :Ignore ],
|
72
|
+
23 => [ :JmpRel, "JMPREL", :Address ],
|
73
|
+
24 => [ :BindNow, "BINDNOW", :Ignore ],
|
74
|
+
25 => [ :InitArray, "INIT_ARRAY", :Address ],
|
75
|
+
26 => [ :FiniArray, "FINI_ARRAY", :Address ],
|
76
|
+
27 => [ :InitArraySz, "INIT_ARRAYSZ", :Value ],
|
77
|
+
28 => [ :FiniArraySz, "FINI_ARRAYSZ", :Value ],
|
78
|
+
29 => [ :RunPath, "RUNPATH", :Value ],
|
79
|
+
30 => [ :Flags, "FLAGS", :Value ],
|
80
|
+
32 => [ :PreinitArray, "PREINIT_ARRAY", :Address ],
|
81
|
+
33 => [ :PreinitArraySz, "PREINIT_ARRAYSZ", :Value ],
|
82
|
+
|
83
|
+
# DT_VAL* constants mapping
|
84
|
+
0x6ffffdf5 => [ :GNUPrelinked, "GNU_PRELINKED", :Value ],
|
85
|
+
0x6ffffdf6 => [ :GNUConflictSz, "GNU_CONFLICTSZ", :Value ],
|
86
|
+
0x6ffffdf7 => [ :GNULibListSz, "GNU_LIBLISTSZ", :Value ],
|
87
|
+
0x6ffffdf8 => [ :CheckSum, "CHECKSUM", :Value ],
|
88
|
+
0x6ffffdf9 => [ :PltPadSz, "PLTPADSZ", :Value ],
|
89
|
+
0x6ffffdfa => [ :MoveEnt, "MOVENT", :Value ],
|
90
|
+
0x6ffffdfb => [ :MoveSz, "MOVESZ", :Value ],
|
91
|
+
0x6ffffdfc => [ :Feature1, "FEATURE_1", :Value ],
|
92
|
+
0x6ffffdfd => [ :PosFlag1, "POSFLAG_1", :Value ],
|
93
|
+
0x6ffffdfe => [ :SymInSz, "SYMINSZ", :Value ],
|
94
|
+
0x6ffffdff => [ :SymInEnt, "SYMINENT", :Value ],
|
95
|
+
|
96
|
+
# DT_ADDR* constants mapping
|
97
|
+
0x6ffffef5 => [ :GNUHash, "GNU_HASH", :Address ],
|
98
|
+
0x6ffffef6 => [ :TLSDescPlt, "TLSDESC_PLT", :Address ],
|
99
|
+
0x6ffffef7 => [ :TLSDescGot, "TLSDESC_GOT", :Address ],
|
100
|
+
0x6ffffef8 => [ :GNUConflict, "GNU_CONFLICT", :Address ],
|
101
|
+
0x6ffffef9 => [ :GNULibList, "GNU_LIBLIST", :Address ],
|
102
|
+
0x6ffffefa => [ :Config, "CONFIG", :Address ],
|
103
|
+
0x6ffffefb => [ :DepAudit, "DEPAUDIT", :Address ],
|
104
|
+
0x6ffffefc => [ :PltPad, "PLTPAD", :Address ],
|
105
|
+
0x6ffffefd => [ :MoveTab, "MOVETAB", :Address ],
|
106
|
+
0x6ffffeff => [ :SymInfo, "SYMINFO", :Address ],
|
107
|
+
|
108
|
+
# GNU extension, should be named :GNUVerSym?
|
109
|
+
0x6ffffff0 => [ :VerSym, "VERSYM", :Ignore ],
|
110
|
+
|
111
|
+
0x6ffffff9 => [ :RelACount, "RELACOUNT", :Value ],
|
112
|
+
0x6ffffffa => [ :RelCount, "RELCOUNT", :Value ],
|
113
|
+
|
114
|
+
# Sun extensions, should be named :Sun*?
|
115
|
+
0x6ffffffb => [ :Flags1, "FLAGS_1", :Value ],
|
116
|
+
0x6ffffffc => [ :VerDef, "VERDEF", :Address ],
|
117
|
+
0x6ffffffd => [ :VerDefNum, "VERDEFNUM", :Value ],
|
118
|
+
0x6ffffffe => [ :VerNeed, "VERNEED", :Address ],
|
119
|
+
0x6fffffff => [ :VerNeedNum, "VERNEEDNUM", :Value ],
|
120
|
+
|
121
|
+
# Unknown CPU-specific extensions
|
122
|
+
0x7ffffffd => [ :Auxiliary, "AUXILIARY", :Value ]
|
123
|
+
)
|
124
|
+
|
125
|
+
OsSpecific = 0x6000000d..0x6ffff000
|
126
|
+
ProcSpecific = 0x70000000..0x7fffffff
|
127
|
+
SpecialRanges = {
|
128
|
+
"DT_LOOS" => OsSpecific,
|
129
|
+
"DT_LOPROC" => ProcSpecific
|
130
|
+
}
|
131
|
+
end
|
132
|
+
|
133
|
+
module Flags
|
134
|
+
Origin = 0x00000001
|
135
|
+
Symbolic = 0x00000002
|
136
|
+
Textrel = 0x00000004
|
137
|
+
BindNow = 0x00000008
|
138
|
+
StaticTLS = 0x00000010
|
139
|
+
end
|
140
|
+
|
141
|
+
module Flags1
|
142
|
+
Now = 0x00000001
|
143
|
+
Global = 0x00000002
|
144
|
+
Group = 0x00000004
|
145
|
+
NoDelete = 0x00000008
|
146
|
+
LoadFltr = 0x00000010
|
147
|
+
InitFirst = 0x00000020
|
148
|
+
NoOpen = 0x00000040
|
149
|
+
Origin = 0x00000080
|
150
|
+
Direct = 0x00000100
|
151
|
+
Trans = 0x00000200
|
152
|
+
Interpose = 0x00000400
|
153
|
+
NoDefLib = 0x00000800
|
154
|
+
NoDump = 0x00001000
|
155
|
+
ConfAlt = 0x00002000
|
156
|
+
EndFiltee = 0x00004000
|
157
|
+
DispRelDNE = 0x00008000
|
158
|
+
DispRelPND = 0x00010000
|
159
|
+
end
|
160
|
+
|
161
|
+
module Features1
|
162
|
+
ParInit = 0x00000001
|
163
|
+
ConfExp = 0x00000002
|
164
|
+
end
|
165
|
+
|
166
|
+
module PosFlags1
|
167
|
+
LazyLoad = 0x00000001
|
168
|
+
GroupPerm = 0x00000002
|
169
|
+
end
|
170
|
+
|
171
|
+
# A .dynamic section entry
|
172
|
+
class Entry
|
173
|
+
attr_reader :type, :value
|
174
|
+
|
175
|
+
def initialize(type, file)
|
176
|
+
@file = file
|
177
|
+
@type = type
|
178
|
+
@value = case type.attribute
|
179
|
+
when :Address then @file.read_addr
|
180
|
+
when :Value then @file.elf_class == Class::Elf32 ? @file.read_word : @file.read_xword
|
181
|
+
else @file.read_addr
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
# An entry for a string value
|
187
|
+
class StringEntry < Entry
|
188
|
+
def parsed
|
189
|
+
@parsed = @file['.dynstr'][@value] unless @parsed
|
190
|
+
return @parsed
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
# An entry for a timestamp value
|
195
|
+
class TimestampEntry < Entry
|
196
|
+
def parsed
|
197
|
+
@parsed = Time.at(@value) unless @parsed
|
198
|
+
return @parsed
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
# An entry for the address of another section
|
203
|
+
class SectionLink < Entry
|
204
|
+
def parsed
|
205
|
+
@parsed = @file.find_section_by_addr(@value) unless @parsed
|
206
|
+
return @parsed
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
ClassMap = {
|
211
|
+
Type::Needed => StringEntry,
|
212
|
+
Type::Hash => SectionLink,
|
213
|
+
Type::StrTab => SectionLink,
|
214
|
+
Type::SymTab => SectionLink,
|
215
|
+
Type::Init => SectionLink,
|
216
|
+
Type::Fini => SectionLink,
|
217
|
+
Type::SoName => StringEntry,
|
218
|
+
Type::RPath => StringEntry,
|
219
|
+
Type::RunPath => StringEntry,
|
220
|
+
Type::GNUPrelinked => TimestampEntry,
|
221
|
+
Type::GNUHash => SectionLink,
|
222
|
+
Type::Auxiliary => StringEntry
|
223
|
+
}
|
224
|
+
|
225
|
+
def load_internal
|
226
|
+
elf32 = @file.elf_class == Class::Elf32
|
227
|
+
|
228
|
+
@entries = []
|
229
|
+
|
230
|
+
for i in 1..@numentries
|
231
|
+
type = Type[elf32 ? @file.read_sword : @file.read_sxword]
|
232
|
+
|
233
|
+
@entries << if ClassMap.has_key? type
|
234
|
+
ClassMap[type].new(type, @file)
|
235
|
+
else
|
236
|
+
Entry.new(type, @file)
|
237
|
+
end
|
238
|
+
|
239
|
+
break if type == Type::Null
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
# Iterate over all the entries in the .dynamic section.
|
244
|
+
def each_entry(&block)
|
245
|
+
load unless @entries
|
246
|
+
|
247
|
+
@entries.each(&block)
|
248
|
+
end
|
249
|
+
|
250
|
+
# Return the amount of entries in the .dynamic section.
|
251
|
+
def size
|
252
|
+
load unless @entries
|
253
|
+
|
254
|
+
@entries.size
|
255
|
+
end
|
256
|
+
|
257
|
+
def soname
|
258
|
+
each_entry do |entry|
|
259
|
+
return entry.parsed if entry.type == Type::SoName
|
260
|
+
end
|
261
|
+
|
262
|
+
return nil
|
263
|
+
end
|
264
|
+
|
265
|
+
# Returns the value of DT_RPATH entries in the file
|
266
|
+
def rpath
|
267
|
+
@rpath ||= auxiliary_library_path(Type::RPath)
|
268
|
+
end
|
269
|
+
|
270
|
+
# Returns the value of DT_RUNPATH entries in the file
|
271
|
+
def runpath
|
272
|
+
@runpath ||= auxiliary_library_path(Type::RunPath)
|
273
|
+
end
|
274
|
+
|
275
|
+
# Returns the auxiliary path specified by the given type
|
276
|
+
#
|
277
|
+
# Please never use this function because it does not caches its
|
278
|
+
# values, use Dynamic#rpath or Dynamic#runpath instead.
|
279
|
+
def auxiliary_library_path(type)
|
280
|
+
retval = Array.new
|
281
|
+
|
282
|
+
each_entry do |entry|
|
283
|
+
next unless entry.type == type
|
284
|
+
retval.concat entry.parsed.split(":")
|
285
|
+
end
|
286
|
+
|
287
|
+
return retval.uniq.collect do |path|
|
288
|
+
if path == "$ORIGIN" or path == "${ORIGIN}"
|
289
|
+
Pathname.new(@file.path).dirname
|
290
|
+
else
|
291
|
+
begin
|
292
|
+
Pathname.new(path).realpath
|
293
|
+
rescue Errno::ENOENT
|
294
|
+
path
|
295
|
+
end
|
296
|
+
end.to_s
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
# Returns the complete library path for the current ELF file.
|
301
|
+
#
|
302
|
+
# Since the ELF loaders have somewhat complex rules to identify
|
303
|
+
# the path to load dependencies from, we evalute it on a per-file
|
304
|
+
# basis.
|
305
|
+
def complete_library_path
|
306
|
+
if @complete_library_path.nil?
|
307
|
+
@complete_library_path = Array.new
|
308
|
+
|
309
|
+
# If there is no DT_RUNPATH. RPATH wins over the LD_LIBRARY_PATH
|
310
|
+
@complete_library_path.concat rpath unless runpath.empty?
|
311
|
+
|
312
|
+
@complete_library_path.concat Elf::Utilities.environment_library_path
|
313
|
+
|
314
|
+
# If there is a DT_RUNPATH it wins over the system library path
|
315
|
+
@complete_library_path.concat runpath
|
316
|
+
|
317
|
+
@complete_library_path.concat Elf::Utilities.system_library_path
|
318
|
+
|
319
|
+
@complete_library_path.uniq!
|
320
|
+
end
|
321
|
+
|
322
|
+
return @complete_library_path
|
323
|
+
end
|
324
|
+
|
325
|
+
# Return the ELF library corresponding to the given soname.
|
326
|
+
#
|
327
|
+
# This function gets the system library paths and eventually adds
|
328
|
+
# the rpaths as expressed by the file itself, then look them up to
|
329
|
+
# find the proper library, just like the loader would.
|
330
|
+
def find_library(soname)
|
331
|
+
complete_library_path.each do |path|
|
332
|
+
# For the ELF linker an empty entry in the library path is the
|
333
|
+
# same as "." and means the current working directory, so
|
334
|
+
# replace it.
|
335
|
+
path = "." if path == ""
|
336
|
+
|
337
|
+
if FileTest.exist? "#{path}/#{soname}"
|
338
|
+
begin
|
339
|
+
possible_library = Elf::Utilities::FilePool["#{path}/#{soname}"]
|
340
|
+
|
341
|
+
return possible_library if @file.is_compatible(possible_library)
|
342
|
+
rescue Errno::ENOENT, Errno::EACCES, Errno::EISDIR, Elf::File::NotAnELF
|
343
|
+
# we don't care if the file does not exist and similar.
|
344
|
+
end
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
return nil
|
349
|
+
end
|
350
|
+
|
351
|
+
# Returns an array of needed sonames from .dynamic section
|
352
|
+
#
|
353
|
+
# This function reads the .dynamic section of the file for
|
354
|
+
# DT_NEEDED entries, and fills an array with them.
|
355
|
+
def needed_sonames
|
356
|
+
if @needed_sonames.nil?
|
357
|
+
@needed_sonames = Array.new
|
358
|
+
|
359
|
+
each_entry do |entry|
|
360
|
+
next unless entry.type == Elf::Dynamic::Type::Needed
|
361
|
+
|
362
|
+
@needed_sonames << entry.parsed
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
return @needed_sonames
|
367
|
+
end
|
368
|
+
|
369
|
+
# Returns an hash representing the dependencies of the ELF file.
|
370
|
+
#
|
371
|
+
# This function reads the .dynamic section of the file for
|
372
|
+
# DT_NEEDED entries, then looks for them and add them to an hash.
|
373
|
+
#
|
374
|
+
# Note that nil values in the hash means that the library couldn't
|
375
|
+
# be found on either the runpath of the file or the system library
|
376
|
+
# path.
|
377
|
+
def needed_libraries
|
378
|
+
# Make sure to cache the thing, we don't want to have to parse
|
379
|
+
# this multiple times since we might access it over and over to
|
380
|
+
# check the dependencies.
|
381
|
+
if @needed_libraries.nil?
|
382
|
+
@needed_libraries = Hash.new
|
383
|
+
|
384
|
+
needed_sonames.each do |soname|
|
385
|
+
@needed_libraries[soname] = find_library(soname)
|
386
|
+
end
|
387
|
+
end
|
388
|
+
|
389
|
+
return @needed_libraries
|
390
|
+
end
|
391
|
+
end
|
392
|
+
end
|
data/lib/elf/file.rb
ADDED
@@ -0,0 +1,366 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
# Simple ELF parser for Ruby
|
3
|
+
#
|
4
|
+
# Copyright © 2007-2010 Diego E. "Flameeyes" Pettenò <flameeyes@gmail.com>
|
5
|
+
# Portions inspired by elf.py
|
6
|
+
# Copyright © 2002 Netgraft Corporation
|
7
|
+
# Portions inspired by elf.h
|
8
|
+
# Copyright © 1995-2006 Free Software Foundation, Inc.
|
9
|
+
#
|
10
|
+
# This program is free software; you can redistribute it and/or modify
|
11
|
+
# it under the terms of the GNU General Public License as published by
|
12
|
+
# the Free Software Foundation; either version 2 of the License, or
|
13
|
+
# (at your option) any later version.
|
14
|
+
#
|
15
|
+
# This program is distributed in the hope that it will be useful,
|
16
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
17
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
18
|
+
# GNU General Public License for more details.
|
19
|
+
#
|
20
|
+
# You should have received a copy of the GNU General Public License
|
21
|
+
# along with this generator; if not, write to the Free Software
|
22
|
+
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
23
|
+
|
24
|
+
require 'bytestream-reader'
|
25
|
+
require 'pathname'
|
26
|
+
|
27
|
+
module Elf
|
28
|
+
class File < ::File
|
29
|
+
include BytestreamReader
|
30
|
+
|
31
|
+
class NotAnELF < Exception
|
32
|
+
def initialize
|
33
|
+
super("not a valid ELF file")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class InvalidElfClass < Exception
|
38
|
+
def initialize(klass)
|
39
|
+
super("Invalid Elf Class #{klass}")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class InvalidDataEncoding < Exception
|
44
|
+
def initialize(encoding)
|
45
|
+
super("Invalid Elf Data Encoding #{encoding}")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class UnsupportedElfVersion < Exception
|
50
|
+
def initialize(version)
|
51
|
+
super("Unsupported Elf version #{version}")
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class InvalidOsAbi < Exception
|
56
|
+
def initialize(abi)
|
57
|
+
super("Invalid Elf ABI #{abi}")
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class InvalidElfType < Exception
|
62
|
+
def initialize(type)
|
63
|
+
super("Invalid Elf type #{type}")
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class InvalidMachine < Exception
|
68
|
+
def initialize(machine)
|
69
|
+
super("Invalid Elf machine #{machine}")
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
class Type < Value
|
74
|
+
fill(
|
75
|
+
0 => [ :None, 'No file type' ],
|
76
|
+
1 => [ :Rel, 'Relocatable file' ],
|
77
|
+
2 => [ :Exec, 'Executable file' ],
|
78
|
+
3 => [ :Dyn, 'Shared object file' ],
|
79
|
+
4 => [ :Core, 'Core file' ]
|
80
|
+
)
|
81
|
+
|
82
|
+
OsSpecific = 0xfe00..0xfeff
|
83
|
+
ProcSpecific = 0xff00..0xffff
|
84
|
+
end
|
85
|
+
|
86
|
+
attr_reader :elf_class, :data_encoding, :type, :version, :abi,
|
87
|
+
:abi_version, :machine
|
88
|
+
attr_reader :string_table
|
89
|
+
|
90
|
+
def read_addr
|
91
|
+
case @elf_class
|
92
|
+
when Class::Elf32 then read_u32
|
93
|
+
when Class::Elf64 then read_u64
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def read_off
|
98
|
+
case @elf_class
|
99
|
+
when Class::Elf32 then read_u32
|
100
|
+
when Class::Elf64 then read_u64
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
alias :read_half :read_u16
|
105
|
+
|
106
|
+
alias :read_word :read_u32
|
107
|
+
alias :read_sword :read_s32
|
108
|
+
|
109
|
+
alias :read_xword :read_u64
|
110
|
+
alias :read_sxword :read_s64
|
111
|
+
|
112
|
+
alias :read_section :read_u16
|
113
|
+
alias :read_versym :read_half
|
114
|
+
|
115
|
+
def _checkvalidpath(path)
|
116
|
+
# We're going to check the path we're given for a few reasons,
|
117
|
+
# the first of which is that we do not want to open a pipe or
|
118
|
+
# something like that. If we were just to leave it to File.open,
|
119
|
+
# we would end up stuck waiting for data on a named pipe, for
|
120
|
+
# instance.
|
121
|
+
#
|
122
|
+
# We cannot just use File.file? either because it'll be ignoring
|
123
|
+
# ENOENT by default (which would be bad for us).
|
124
|
+
#
|
125
|
+
# If we were just to use File.ftype we would have to handle
|
126
|
+
# manually the links... since Pathname will properly report
|
127
|
+
# ENOENT for broken links, we're going to keep it this way.
|
128
|
+
path = Pathname.new(path) unless path.is_a? Pathname
|
129
|
+
|
130
|
+
case path.ftype
|
131
|
+
when "directory" then raise Errno::EISDIR
|
132
|
+
when "file" then # do nothing
|
133
|
+
when "link"
|
134
|
+
# we use path.realpath here; the reason is that if
|
135
|
+
# we're to use readlink we're going to have a lot of
|
136
|
+
# trouble to find the correct path. We cannot just
|
137
|
+
# always use realpath as that will run too many stat
|
138
|
+
# calls and have a huge hit on performance.
|
139
|
+
_checkvalidpath(path.realpath)
|
140
|
+
else
|
141
|
+
raise Errno::EINVAL
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
private :_checkvalidpath
|
146
|
+
|
147
|
+
def initialize(path)
|
148
|
+
_checkvalidpath(path)
|
149
|
+
|
150
|
+
super(path, "rb")
|
151
|
+
|
152
|
+
begin
|
153
|
+
begin
|
154
|
+
raise NotAnELF unless readexactly(4) == MagicString
|
155
|
+
rescue EOFError
|
156
|
+
raise NotAnELF
|
157
|
+
end
|
158
|
+
|
159
|
+
begin
|
160
|
+
@elf_class = Class[read_u8]
|
161
|
+
rescue Value::OutOfBound => e
|
162
|
+
raise InvalidElfClass.new(e.val)
|
163
|
+
end
|
164
|
+
|
165
|
+
begin
|
166
|
+
@data_encoding = DataEncoding[read_u8]
|
167
|
+
rescue Value::OutOfBound => e
|
168
|
+
raise InvalidDataEncoding.new(e.val)
|
169
|
+
end
|
170
|
+
|
171
|
+
@version = read_u8
|
172
|
+
raise UnsupportedElfVersion.new(@version) if @version > 1
|
173
|
+
|
174
|
+
begin
|
175
|
+
@abi = OsAbi[read_u8]
|
176
|
+
rescue Value::OutOfBound => e
|
177
|
+
raise InvalidOsAbi.new(e.val)
|
178
|
+
end
|
179
|
+
@abi_version = read_u8
|
180
|
+
|
181
|
+
seek(16, IO::SEEK_SET)
|
182
|
+
set_endian(DataEncoding::BytestreamMapping[@data_encoding])
|
183
|
+
|
184
|
+
begin
|
185
|
+
@type = Type[read_half]
|
186
|
+
rescue Value::OutOfBound => e
|
187
|
+
raise InvalidElfType.new(e.val)
|
188
|
+
end
|
189
|
+
|
190
|
+
begin
|
191
|
+
@machine = Machine[read_half]
|
192
|
+
rescue Value::OutOfBound => e
|
193
|
+
raise InvalidMachine.new(e.val)
|
194
|
+
end
|
195
|
+
|
196
|
+
@version = read_word
|
197
|
+
@entry = read_addr
|
198
|
+
@phoff = read_off
|
199
|
+
shoff = read_off
|
200
|
+
@flags = read_word
|
201
|
+
@ehsize = read_half
|
202
|
+
@phentsize = read_half
|
203
|
+
@phnum = read_half
|
204
|
+
@shentsize = read_half
|
205
|
+
shnum = read_half
|
206
|
+
shstrndx = read_half
|
207
|
+
|
208
|
+
elf32 = elf_class == Class::Elf32
|
209
|
+
@sections = {}
|
210
|
+
|
211
|
+
@sections_data = []
|
212
|
+
seek(shoff)
|
213
|
+
for i in 1..shnum
|
214
|
+
sectdata = {}
|
215
|
+
sectdata[:idx] = i-1
|
216
|
+
sectdata[:name_idx] = read_word
|
217
|
+
sectdata[:type_id] = read_word
|
218
|
+
sectdata[:flags_val] = elf32 ? read_word : read_xword
|
219
|
+
sectdata[:addr] = read_addr
|
220
|
+
sectdata[:offset] = read_off
|
221
|
+
sectdata[:size] = elf32 ? read_word : read_xword
|
222
|
+
sectdata[:link] = read_word
|
223
|
+
sectdata[:info] = read_word
|
224
|
+
sectdata[:addralign] = elf32 ? read_word : read_xword
|
225
|
+
sectdata[:entsize] = elf32 ? read_word : read_xword
|
226
|
+
|
227
|
+
@sections_data << sectdata
|
228
|
+
end
|
229
|
+
|
230
|
+
# When the section header string table index is set to zero,
|
231
|
+
# there is not going to be a string table in the file, this
|
232
|
+
# happens usually when the file is a static ELF file built
|
233
|
+
# directly with an assembler.
|
234
|
+
#
|
235
|
+
# To handle this specific case, set the @string_table attribute
|
236
|
+
# to false, that is distinct from nil, and raise
|
237
|
+
# MissingStringTable on request. If the string table is not yet
|
238
|
+
# loaded raise instead StringTableNotLoaded.
|
239
|
+
if shstrndx == 0 or not self[shstrndx].is_a? StringTable
|
240
|
+
@string_table = false
|
241
|
+
else
|
242
|
+
@string_table = self[shstrndx]
|
243
|
+
|
244
|
+
@sections_names = {}
|
245
|
+
@sections_data.each do |sectdata|
|
246
|
+
@sections_names[@string_table[sectdata[:name_idx]]] = sectdata[:idx]
|
247
|
+
end
|
248
|
+
end
|
249
|
+
rescue ::Exception => e
|
250
|
+
close
|
251
|
+
raise e
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
class MissingSection < Exception
|
256
|
+
def initialize(sect_identifier)
|
257
|
+
super("Requested section #{sect_identifier} not found in the file")
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
def load_section(sect_idx_or_name)
|
262
|
+
if sect_idx_or_name.is_a? Integer
|
263
|
+
raise MissingSection.new(sect_idx_or_name) unless
|
264
|
+
@sections_data[sect_idx_or_name]
|
265
|
+
|
266
|
+
@sections[sect_idx_or_name] = Section.read(self, @sections_data[sect_idx_or_name])
|
267
|
+
else
|
268
|
+
raise MissingSection.new(sect_idx_or_name) unless
|
269
|
+
@sections_names[sect_idx_or_name]
|
270
|
+
|
271
|
+
load_section @sections_names[sect_idx_or_name]
|
272
|
+
|
273
|
+
@sections[sect_idx_or_name] = @sections[@sections_names[sect_idx_or_name]]
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
class StringTableNotLoaded < Exception
|
278
|
+
def initialize(sect_name)
|
279
|
+
super("Requested section '#{sect_name}' but there is no string table yet.")
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
class MissingStringTable < Exception
|
284
|
+
def initialize(sect_name)
|
285
|
+
super("Requested section '#{sect_name}' but the file has no string table.")
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
def [](sect_idx_or_name)
|
290
|
+
if sect_idx_or_name.is_a? String and not @string_table.is_a? Elf::Section
|
291
|
+
raise MissingStringTable.new(sect_idx_or_name) if @string_table == false
|
292
|
+
raise StringTableNotLoaded.new(sect_idx_or_name) if @string_table.nil?
|
293
|
+
end
|
294
|
+
|
295
|
+
load_section(sect_idx_or_name) unless
|
296
|
+
@sections.has_key? sect_idx_or_name
|
297
|
+
|
298
|
+
return @sections[sect_idx_or_name]
|
299
|
+
end
|
300
|
+
|
301
|
+
def each_section
|
302
|
+
@sections_data.each do |sectdata|
|
303
|
+
load_section(sectdata[:idx])
|
304
|
+
yield @sections[sectdata[:idx]]
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
def find_section_by_addr(addr)
|
309
|
+
@sections_data.each do |sectdata|
|
310
|
+
next unless sectdata[:addr] == addr
|
311
|
+
load_section(sectdata[:idx])
|
312
|
+
return @sections[sectdata[:idx]]
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
def has_section?(sect_idx_or_name)
|
317
|
+
|
318
|
+
if sect_idx_or_name.is_a? String and not @string_table.is_a? Elf::Section
|
319
|
+
return false if @string_table == false
|
320
|
+
raise StringTableNotLoaded.new(sect_idx_or_name) if @string_table.nil?
|
321
|
+
end
|
322
|
+
|
323
|
+
if sect_idx_or_name.is_a? Integer
|
324
|
+
return @sections_data[sect_idx_or_name] != nil
|
325
|
+
elsif sect_idx_or_name.is_a? String
|
326
|
+
return @sections_names.has_key?(sect_idx_or_name)
|
327
|
+
else
|
328
|
+
raise TypeError.new("wrong argument type #{sect_idx_or_name.class} (expected String or Integer)")
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
def summary
|
333
|
+
$stdout.puts "ELF file #{path}"
|
334
|
+
$stdout.puts "ELF class: #{@elf_class} #{@data_encoding} ver. #{@version}"
|
335
|
+
$stdout.puts "ELF ABI: #{@abi} ver. #{@abi_version}"
|
336
|
+
$stdout.puts "ELF type: #{@type} machine: #{@machine}"
|
337
|
+
$stdout.puts "Sections:"
|
338
|
+
@sections.values.uniq.each do |sh|
|
339
|
+
sh.summary
|
340
|
+
end
|
341
|
+
|
342
|
+
return nil
|
343
|
+
end
|
344
|
+
|
345
|
+
# Checks whether two ELF files are compatible one with the other for linking
|
346
|
+
#
|
347
|
+
# This function has to check whether two ELF files can be linked
|
348
|
+
# together (either at build time or at load time), and thus checks
|
349
|
+
# for class, encoding, versioning, ABI and machine type.
|
350
|
+
#
|
351
|
+
# Note that it explicitly does not check for ELF file type since
|
352
|
+
# you can link different type of files together, like an
|
353
|
+
# Executable with a Dynamic library.
|
354
|
+
def is_compatible(other)
|
355
|
+
raise TypeError.new("wrong argument type #{other.class} (expected Elf::File)") unless
|
356
|
+
other.is_a? Elf::File
|
357
|
+
|
358
|
+
@elf_class == other.elf_class and
|
359
|
+
@data_encoding == other.data_encoding and
|
360
|
+
@version == other.version and
|
361
|
+
@abi == other.abi and
|
362
|
+
@abi_version == other.abi_version and
|
363
|
+
@machine == other.machine
|
364
|
+
end
|
365
|
+
end
|
366
|
+
end
|