ony-ruby-elf 1.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 +7 -0
- data/COPYING +339 -0
- data/DONATING +42 -0
- data/README.md +59 -0
- data/lib/bytestream-reader.rb +271 -0
- data/lib/elf.rb +247 -0
- data/lib/elf/dynamic.rb +392 -0
- data/lib/elf/file.rb +407 -0
- data/lib/elf/gnu.rb +174 -0
- data/lib/elf/section.rb +348 -0
- data/lib/elf/stringtable.rb +39 -0
- data/lib/elf/sunw.rb +158 -0
- data/lib/elf/symbol.rb +406 -0
- data/lib/elf/symboltable.rb +90 -0
- data/lib/elf/tools.rb +259 -0
- data/lib/elf/utils/loader.rb +112 -0
- data/lib/elf/utils/offsettable.rb +49 -0
- data/lib/elf/utils/pool.rb +49 -0
- data/lib/elf/value.rb +128 -0
- data/ruby-elf.gemspec +29 -0
- data/tools/assess_duplicate_save.rb +105 -0
- data/tools/link-collisions/harvest.rb +340 -0
- data/tools/link-collisions/suppress.rb +84 -0
- data/tools/rbelf-lddtree.rb +49 -0
- metadata +71 -0
@@ -0,0 +1,90 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
# Simple ELF parser for Ruby
|
3
|
+
#
|
4
|
+
# Copyright © 2007-2010 Diego Elio Pettenò <flameeyes@flameeyes.eu>
|
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 'set'
|
25
|
+
|
26
|
+
module Elf
|
27
|
+
class SymbolTable < Section
|
28
|
+
def load_internal
|
29
|
+
@symbols = []
|
30
|
+
@symbol_names = {}
|
31
|
+
for i in 1..(@numentries)
|
32
|
+
sym = Symbol.new(@file, self, i-1)
|
33
|
+
@symbols << sym
|
34
|
+
@symbol_names[sym.name] = sym.idx
|
35
|
+
end
|
36
|
+
|
37
|
+
return nil
|
38
|
+
end
|
39
|
+
|
40
|
+
# Exception thrown when requesting a symbol that is not in the
|
41
|
+
# table
|
42
|
+
class UnknownSymbol < Exception
|
43
|
+
def initialize(name_or_idx, section)
|
44
|
+
super("Symbol #{name_or_idx} not found in section #{section.name}")
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def [](idx)
|
49
|
+
load unless @symbols
|
50
|
+
|
51
|
+
if idx.is_a?(Numeric)
|
52
|
+
raise UnknownSymbol.new(idx, self) unless @symbols[idx] != nil
|
53
|
+
return @symbols[idx]
|
54
|
+
elsif idx.respond_to?("to_s")
|
55
|
+
idx = idx.to_s
|
56
|
+
raise UnknownSymbol.new(idx, self) unless @symbol_names.has_key?(idx)
|
57
|
+
return @symbols[@symbol_names[idx]]
|
58
|
+
else
|
59
|
+
raise TypeError.new("wrong argument type #{sect_idx_or_name.class} (expected String or Integer)")
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Iterate over each symbols, replaces section.symbol.each
|
64
|
+
def each(&block)
|
65
|
+
symbols.each(&block)
|
66
|
+
end
|
67
|
+
|
68
|
+
include ::Enumerable
|
69
|
+
|
70
|
+
# Return the number of symbols in the section
|
71
|
+
def count
|
72
|
+
symbols.size
|
73
|
+
end
|
74
|
+
|
75
|
+
# Get a set with all the symbols in the table that are defined,
|
76
|
+
# ignoring common, absolute and undefined symbols.
|
77
|
+
def defined_symbols
|
78
|
+
symbols.find_all do |sym|
|
79
|
+
sym.defined?
|
80
|
+
end.to_set
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
def symbols
|
85
|
+
load unless @symbols
|
86
|
+
@symbols
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
data/lib/elf/tools.rb
ADDED
@@ -0,0 +1,259 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
# Copyright © 2008-2010 Diego Elio Pettenò <flameeyes@flameeyes.eu>
|
3
|
+
#
|
4
|
+
# This program is free software; you can redistribute it and/or modify
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
6
|
+
# the Free Software Foundation; either version 2 of the License, or
|
7
|
+
# (at your option) any later version.
|
8
|
+
#
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12
|
+
# GNU General Public License for more details.
|
13
|
+
#
|
14
|
+
# You should have received a copy of the GNU General Public License
|
15
|
+
# along with this generator; if not, write to the Free Software
|
16
|
+
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
17
|
+
|
18
|
+
require 'getoptlong'
|
19
|
+
require 'thread'
|
20
|
+
require 'elf'
|
21
|
+
|
22
|
+
# This file allows to wrap aroudn the most common features of
|
23
|
+
# Ruby-Elf based tools, that follow a series of common traits.
|
24
|
+
#
|
25
|
+
# The tools using this file are tools that inspect a series of ELF
|
26
|
+
# files, passed through command line, stdin, or through a file
|
27
|
+
# parameter; they accept a series of arguments that may or might not
|
28
|
+
# require arguments (in that latter case they are considered on/off
|
29
|
+
# switches), and so on.
|
30
|
+
|
31
|
+
module Elf
|
32
|
+
class Tool
|
33
|
+
def self.inherited(klass)
|
34
|
+
raise Exception.new("Another Tool has been already defined") if @tool_defined
|
35
|
+
@tool_defined = true
|
36
|
+
|
37
|
+
at_exit do
|
38
|
+
unless $!
|
39
|
+
klass.main
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Gets the name of the tool
|
45
|
+
def self.to_s
|
46
|
+
File.basename($0)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Output an error message, prefixed with the tool name.
|
50
|
+
def self.puterror(string)
|
51
|
+
return if @quiet
|
52
|
+
|
53
|
+
@output_mutex.synchronize {
|
54
|
+
$stderr.puts "#{to_s}: #{string}"
|
55
|
+
}
|
56
|
+
end
|
57
|
+
|
58
|
+
# Output a notice about a file, do not prefix with the tool name, do
|
59
|
+
# not print if doing recursive analysis
|
60
|
+
def self.putnotice(message)
|
61
|
+
return if @quiet or @recursive
|
62
|
+
|
63
|
+
@output_mutex.synchronize {
|
64
|
+
$stderr.puts message
|
65
|
+
}
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.after_options
|
69
|
+
end
|
70
|
+
|
71
|
+
# Parse the arguments for the tool; it does not parse the @file
|
72
|
+
# options, since they are only expected to contain file names,
|
73
|
+
# rather than options.
|
74
|
+
def self.parse_arguments
|
75
|
+
opts = GetoptLong.new(*@options)
|
76
|
+
opts.each do |opt, arg|
|
77
|
+
if opt == "--help"
|
78
|
+
# check if we're executing from a tarball or the git repository,
|
79
|
+
# if so we can't use the system man page.
|
80
|
+
manpage = File.expand_path("../../../manpages/#{to_s}.1", __FILE__)
|
81
|
+
manpage = to_s unless File.exist?(manpage)
|
82
|
+
exec("man #{manpage}")
|
83
|
+
end
|
84
|
+
|
85
|
+
attrname = opt.gsub(/^--/, "").gsub("-", "_")
|
86
|
+
attrval = arg.size == 0 ? true : arg
|
87
|
+
|
88
|
+
# If there is a function with the same name of the parameter
|
89
|
+
# defined (with a _cb suffix), call that, otherwise set the
|
90
|
+
# attribute with the same name to the given value.
|
91
|
+
cb = method("#{attrname}_cb") rescue nil
|
92
|
+
case
|
93
|
+
when cb.nil?
|
94
|
+
instance_variable_set("@#{attrname}", attrval)
|
95
|
+
when cb.arity == 0
|
96
|
+
raise ArgumentError("wrong number of arguments in callback (0 for 1)") unless
|
97
|
+
arg.size == 0
|
98
|
+
cb.call
|
99
|
+
when cb.arity == 1
|
100
|
+
# fallback to provide a single "true" parameter if there was no
|
101
|
+
# required argument
|
102
|
+
cb.call(attrval)
|
103
|
+
else
|
104
|
+
raise ArgumentError("wrong number of arguments in callback (#{cb.arity} for #{arg.size})")
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
@parsed_options = true
|
109
|
+
end
|
110
|
+
|
111
|
+
def self.single_target?
|
112
|
+
raise Exception.new("You can't call this until options are parsed") unless @parsed_options
|
113
|
+
|
114
|
+
# We consider having a single target means that we're given exactly
|
115
|
+
# one argument, and that argument is not a targets' list itself.
|
116
|
+
return @targets.size == 1
|
117
|
+
end
|
118
|
+
|
119
|
+
def self.execute(filename)
|
120
|
+
begin
|
121
|
+
analysis(filename)
|
122
|
+
rescue Errno::ENOENT, Errno::EACCES, Errno::EISDIR, Elf::File::NotAnELF,
|
123
|
+
Elf::File::InvalidElfClass, Elf::File::InvalidDataEncoding,
|
124
|
+
Elf::File::UnsupportedElfVersion, Elf::File::InvalidOsAbi, Elf::File::InvalidElfType,
|
125
|
+
Elf::File::InvalidMachine => e
|
126
|
+
# The Errno exceptions have their message ending in " - FILENAME",
|
127
|
+
# so we take the FILENAME out and just use the one we know
|
128
|
+
# already. We also take out the final dot on the phrase so that
|
129
|
+
# we follow the output messages from other tools, like cat.
|
130
|
+
putnotice "#{filename}: #{e.message.gsub(/\.? - .*/, '')}"
|
131
|
+
rescue Exception => e
|
132
|
+
puterror "#{filename}: #{e.message} (#{e.class})\n\t#{e.backtrace.join("\n\t")}"
|
133
|
+
exit -1
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def self.thread_execute(filename)
|
138
|
+
# If our child set @execution_threads to nil, it doesn't really
|
139
|
+
# support running multithreaded, this is the case for instance
|
140
|
+
# of the link collision harvester script, where the db access
|
141
|
+
# and pkey generation has to be synchronous.
|
142
|
+
unless @execution_threads.nil?
|
143
|
+
@execution_threads.add(Thread.new {
|
144
|
+
execute(filename)
|
145
|
+
})
|
146
|
+
else
|
147
|
+
execute(filename)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# Try to execute the analysis function on a given filename argument.
|
152
|
+
def self.try_execute(filename)
|
153
|
+
begin
|
154
|
+
# find the file type so we don't have to look it up many times; if
|
155
|
+
# we're running a recursive scan, we don't want to look into
|
156
|
+
# symlinks as they might create loops or duplicate content, while
|
157
|
+
# we usually want to check them out if they are given directly in
|
158
|
+
# the list of files to analyse
|
159
|
+
file_stat = if @recursive
|
160
|
+
File.lstat(filename)
|
161
|
+
else
|
162
|
+
File.stat(filename)
|
163
|
+
end
|
164
|
+
|
165
|
+
# if the path references a directory, and we're going to run
|
166
|
+
# recursively, descend into that.
|
167
|
+
if @recursive and file_stat.directory?
|
168
|
+
Dir.foreach(filename) do |children|
|
169
|
+
next if children == "." or children == ".."
|
170
|
+
try_execute(File.join(filename, children))
|
171
|
+
end
|
172
|
+
# if the path does not point to a regular file, ignore it
|
173
|
+
elsif not file_stat.file?
|
174
|
+
putnotice "#{filename}: not a regular file"
|
175
|
+
else
|
176
|
+
thread_execute(filename)
|
177
|
+
end
|
178
|
+
rescue Errno::ENOENT, Errno::EACCES, Errno::EISDIR, Elf::File::NotAnELF => e
|
179
|
+
# The Errno exceptions have their message ending in " - FILENAME",
|
180
|
+
# so we take the FILENAME out and just use the one we know
|
181
|
+
# already. We also take out the final dot on the phrase so that
|
182
|
+
# we follow the output messages from other tools, like cat.
|
183
|
+
putnotice "#{filename}: #{e.message.gsub(/\.? - .*/, '')}"
|
184
|
+
rescue SystemExit => e
|
185
|
+
exit e.status
|
186
|
+
rescue Exception => e
|
187
|
+
puterror "#{filename}: #{e.message} (#{e.class})\n\t#{e.backtrace.join("\n\t")}"
|
188
|
+
exit -1
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
# Execute the analysis function on all the elements of an array.
|
193
|
+
def self.execute_on(param)
|
194
|
+
param = ::File.new(param) if param.is_a? String
|
195
|
+
param = param.read.split(/\r?\n/) if param.is_a? IO
|
196
|
+
|
197
|
+
param.each do |filename|
|
198
|
+
try_execute(filename)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
def self.results
|
203
|
+
end
|
204
|
+
|
205
|
+
def self.initialize
|
206
|
+
@output_mutex = Mutex.new
|
207
|
+
@execution_threads = ThreadGroup.new
|
208
|
+
|
209
|
+
@options = [
|
210
|
+
["--help", "-?", GetoptLong::NO_ARGUMENT],
|
211
|
+
["--quiet", "-q", GetoptLong::NO_ARGUMENT],
|
212
|
+
["--recursive", "-R", GetoptLong::NO_ARGUMENT],
|
213
|
+
]
|
214
|
+
end
|
215
|
+
|
216
|
+
def self.main
|
217
|
+
initialize
|
218
|
+
|
219
|
+
begin
|
220
|
+
parse_arguments
|
221
|
+
|
222
|
+
# collect all the arguments passed; if the argument starts
|
223
|
+
# with '@', then open the file and split the lines in further
|
224
|
+
# arguments.
|
225
|
+
@targets = ARGV.collect { |argument|
|
226
|
+
if argument[0..0] == "@"
|
227
|
+
::File.read(argument[1..-1]).split(/\r?\n/)
|
228
|
+
else
|
229
|
+
argument
|
230
|
+
end
|
231
|
+
}.flatten
|
232
|
+
|
233
|
+
after_options
|
234
|
+
|
235
|
+
# if we have no targets (neither direct arguments, nor added
|
236
|
+
# by #after_options, we readthe targets from stdin.
|
237
|
+
if @targets.empty?
|
238
|
+
$stdin.each_line { |input|
|
239
|
+
try_execute(input.sub(/\r?\n/, ''))
|
240
|
+
}
|
241
|
+
else
|
242
|
+
$stdin.close
|
243
|
+
@targets.uniq.each { |target| try_execute(target) }
|
244
|
+
end
|
245
|
+
|
246
|
+
if @execution_threads
|
247
|
+
@execution_threads.list.each do |thread|
|
248
|
+
thread.join
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
results
|
253
|
+
rescue Interrupt
|
254
|
+
puterror "Interrupted"
|
255
|
+
exit 1
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
# Copyright © 2009-2010 Diego Elio Pettenò <flameeyes@flameeyes.eu>
|
3
|
+
#
|
4
|
+
# This program is free software; you can redistribute it and/or modify
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
6
|
+
# the Free Software Foundation; either version 2 of the License, or
|
7
|
+
# (at your option) any later version.
|
8
|
+
#
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12
|
+
# GNU General Public License for more details.
|
13
|
+
#
|
14
|
+
# You should have received a copy of the GNU General Public License
|
15
|
+
# along with this generator; if not, write to the Free Software
|
16
|
+
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
17
|
+
|
18
|
+
require 'elf'
|
19
|
+
require 'elf/utils/pool'
|
20
|
+
|
21
|
+
# This file provides some utilities to deal with the runtime lodader
|
22
|
+
# functions. In particular it provides access to the same kind of
|
23
|
+
# library search as the loader provides.
|
24
|
+
|
25
|
+
module Elf
|
26
|
+
module Utilities
|
27
|
+
@@system_library_path = nil
|
28
|
+
|
29
|
+
# Convenience function to append an array to the list of system
|
30
|
+
# library paths.
|
31
|
+
#
|
32
|
+
# This is just used to avoid repeating the same block of code for
|
33
|
+
# both the data read from /etc/ld.so.conf and from LD_LIBRARY_PATH
|
34
|
+
# environment variable if requested.
|
35
|
+
def self.append_to_library_path(morepaths)
|
36
|
+
morepaths.each do |path|
|
37
|
+
begin
|
38
|
+
# Since we can have symlinks and similar issues, try to
|
39
|
+
# canonicalise the paths so that we can expect them to be
|
40
|
+
# truly unique (and thus never pass through the same directory
|
41
|
+
# twice).
|
42
|
+
@@system_library_path << Pathname.new(path).realpath.to_s
|
43
|
+
rescue Errno::ENOENT, Errno::EACCES
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Return the system library path to look for libraries, just like
|
49
|
+
# the loader would.
|
50
|
+
def self.system_library_path
|
51
|
+
|
52
|
+
# Try to cache the request since we most likely have multiple
|
53
|
+
# request per process and we don't care if the settings change
|
54
|
+
# between them.
|
55
|
+
if @@system_library_path.nil?
|
56
|
+
@@system_library_path = Array.new
|
57
|
+
|
58
|
+
# We have to put by default /lib and /usr/lib since they are
|
59
|
+
# implicit in all systems. In particular for Gentoo/Linux
|
60
|
+
# these two are not in the list on x86 systems (but are on
|
61
|
+
# amd64).
|
62
|
+
#
|
63
|
+
# Since LD_LIBRARY_PATH would win over this, but we expect
|
64
|
+
# /etc/ld.so.conf not to, add them here.
|
65
|
+
append_to_library_path(["/lib", "/usr/lib"])
|
66
|
+
|
67
|
+
# We might not have the ld.so.conf file, if that's the case
|
68
|
+
# just ignore it.
|
69
|
+
begin
|
70
|
+
# This implements for now the glibc-style loader
|
71
|
+
# configuration; in the future it might be reimplemented to
|
72
|
+
# take into consideration different operating systems.
|
73
|
+
::File.open("/etc/ld.so.conf") do |ld_so_conf|
|
74
|
+
ld_so_conf.each_line do |line|
|
75
|
+
# Comment lines in the configuration file are prefixed
|
76
|
+
# with the hash character, and the remaining content is
|
77
|
+
# just a single huge list of paths, separated by colon,
|
78
|
+
# comma, space, tabs or newlines.
|
79
|
+
append_to_library_path(line.gsub(/#.*/, '').split(/[:, \t\n]/))
|
80
|
+
end
|
81
|
+
end
|
82
|
+
rescue Errno::ENOENT
|
83
|
+
end
|
84
|
+
|
85
|
+
# Make sure the resulting list is uniq to avoid scanning the
|
86
|
+
# same directory multiple times.
|
87
|
+
@@system_library_path.uniq!
|
88
|
+
end
|
89
|
+
|
90
|
+
return @@system_library_path
|
91
|
+
end
|
92
|
+
|
93
|
+
# Return the environment library path
|
94
|
+
#
|
95
|
+
# We assume the LD_LIBRARY_PATH variable is not going to change
|
96
|
+
# between calls.
|
97
|
+
#
|
98
|
+
# TODO: systems like Solaris implement further variables like
|
99
|
+
# LD_LIBRARY_PATH_32 and LD_LIBRARY_PATH_64, we should pay
|
100
|
+
# attention to those too.
|
101
|
+
def self.environment_library_path
|
102
|
+
return [] if ENV['LD_LIBRARY_PATH'].nil?
|
103
|
+
|
104
|
+
ENV['LD_LIBRARY_PATH'].split(":").collect do |path|
|
105
|
+
begin
|
106
|
+
Pathname.new(path).realpath.to_s
|
107
|
+
rescue Errno::ENOENT, Errno::EACCES
|
108
|
+
end
|
109
|
+
end.uniq
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|