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.
@@ -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