ruby-elf 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,90 @@
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 '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 size
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
+
@@ -0,0 +1,228 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Copyright © 2008-2010 Diego E. "Flameeyes" Pettenò <flameeyes@gmail.com>
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
+ # Gets the name of the tool
32
+ def self.to_s
33
+ File.basename($0)
34
+ end
35
+
36
+ # Output an error message, prefixed with the tool name.
37
+ def self.puterror(string)
38
+ return if @quiet
39
+
40
+ @output_mutex.synchronize {
41
+ $stderr.puts "#{to_s}: #{string}"
42
+ }
43
+ end
44
+
45
+ # Output a notice about a file, do not prefix with the tool name, do
46
+ # not print if doing recursive analysis
47
+ def self.putnotice(message)
48
+ return if @quiet or @recursive
49
+
50
+ @output_mutex.synchronize {
51
+ $stderr.puts message
52
+ }
53
+ end
54
+
55
+ # Parse the arguments for the tool; it does not parse the @file
56
+ # options, since they are only expected to contain file names,
57
+ # rather than options.
58
+ def self.parse_arguments
59
+ opts = Options + [
60
+ ["--help", "-?", GetoptLong::NO_ARGUMENT],
61
+ ["--quiet", "-q", GetoptLong::NO_ARGUMENT],
62
+ ["--recursive", "-R", GetoptLong::NO_ARGUMENT],
63
+ ]
64
+
65
+ opts = GetoptLong.new(*opts)
66
+ opts.each do |opt, arg|
67
+ if opt == "--help"
68
+ # check if we're executing from a tarball or the git repository,
69
+ # if so we can't use the system man page.
70
+ require 'pathname'
71
+ filepath = Pathname.new($0)
72
+ localman = filepath.dirname + "../manpages" + filepath.basename.sub(".rb", ".1")
73
+ if localman.exist?
74
+ exec("man #{localman.to_s}")
75
+ else
76
+ exec("man #{to_s}")
77
+ end
78
+ end
79
+
80
+ attrname = opt.gsub(/^--/, "").gsub("-", "_")
81
+ attrval = arg.size == 0 ? true : arg
82
+
83
+ # If there is a function with the same name of the parameter
84
+ # defined (with a _cb suffix), call that, otherwise set the
85
+ # attribute with the same name to the given value.
86
+ cb = method("#{attrname}_cb") rescue nil
87
+ case
88
+ when cb.nil?
89
+ instance_variable_set("@#{attrname}", attrval)
90
+ when cb.arity == 0
91
+ raise ArgumentError("wrong number of arguments in callback (0 for 1)") unless
92
+ arg.size == 0
93
+ cb.call
94
+ when cb.arity == 1
95
+ # fallback to provide a single "true" parameter if there was no
96
+ # required argument
97
+ cb.call(attrval)
98
+ else
99
+ raise ArgumentError("wrong number of arguments in callback (#{cb.arity} for #{arg.size})")
100
+ end
101
+ end
102
+ end
103
+
104
+ def execute(filename)
105
+ begin
106
+ analysis(filename)
107
+ rescue Errno::ENOENT, Errno::EACCES, Errno::EISDIR, Elf::File::NotAnELF,
108
+ Elf::File::InvalidElfClass, Elf::File::InvalidDataEncoding,
109
+ Elf::File::UnsupportedElfVersion, Elf::File::InvalidOsAbi, Elf::File::InvalidElfType,
110
+ Elf::File::InvalidMachine => e
111
+ # The Errno exceptions have their message ending in " - FILENAME",
112
+ # so we take the FILENAME out and just use the one we know
113
+ # already. We also take out the final dot on the phrase so that
114
+ # we follow the output messages from other tools, like cat.
115
+ putnotice "#{filename}: #{e.message.gsub(/\.? - .*/, '')}"
116
+ rescue Exception => e
117
+ puterror "#{filename}: #{e.message} (#{e.class})\n\t#{e.backtrace.join("\n\t")}"
118
+ exit -1
119
+ end
120
+ end
121
+
122
+ # Try to execute the analysis function on a given filename argument.
123
+ def self.try_execute(filename)
124
+ begin
125
+ # if the file name starts with '@', it is not a target, but a file
126
+ # with a list of targets, so load it with execute_on_file.
127
+ if filename[0..0] == "@"
128
+ execute_on_file(filename[1..-1])
129
+ return
130
+ end
131
+
132
+ # find the file type so we don't have to look it up many times; if
133
+ # we're running a recursive scan, we don't want to look into
134
+ # symlinks as they might create loops or duplicate content, while
135
+ # we usually want to check them out if they are given directly in
136
+ # the list of files to analyse
137
+ file_stat = if @recursive
138
+ File.lstat(filename)
139
+ else
140
+ File.stat(filename)
141
+ end
142
+
143
+ # if the path references a directory, and we're going to run
144
+ # recursively, descend into that.
145
+ if @recursive and file_stat.directory?
146
+ Dir.foreach(filename) do |children|
147
+ next if children == "." or children == ".."
148
+ try_execute(File.join(filename, children))
149
+ end
150
+ # if the path does not point to a regular file, ignore it
151
+ elsif not file_stat.file?
152
+ putnotice "#{filename}: not a regular file"
153
+ else
154
+ @execution_threads.add(Thread.new {
155
+ execute(filename)
156
+ })
157
+ end
158
+ rescue Errno::ENOENT, Errno::EACCES, Errno::EISDIR, Elf::File::NotAnELF => e
159
+ # The Errno exceptions have their message ending in " - FILENAME",
160
+ # so we take the FILENAME out and just use the one we know
161
+ # already. We also take out the final dot on the phrase so that
162
+ # we follow the output messages from other tools, like cat.
163
+ putnotice "#{filename}: #{e.message.gsub(/\.? - .*/, '')}"
164
+ rescue SystemExit => e
165
+ exit e.status
166
+ rescue Exception => e
167
+ puterror "#{filename}: #{e.message} (#{e.class})\n\t#{e.backtrace.join("\n\t")}"
168
+ exit -1
169
+ end
170
+ end
171
+
172
+ # Execute the analysis function on all the elements of an array.
173
+ def self.execute_on_array(array)
174
+ array.each do |filename|
175
+ try_execute(filename)
176
+ end
177
+ end
178
+
179
+ # Execute the analysis function on all the lines of a file
180
+ def self.execute_on_file(file)
181
+ @single_target = false
182
+
183
+ file = $stdin if file == "-"
184
+ file = File.new(file) if file.class == String
185
+
186
+ file.each_line do |line|
187
+ try_execute(line.chomp("\n"))
188
+ end
189
+ end
190
+
191
+ def self.main
192
+ begin
193
+ @output_mutex = Mutex.new
194
+ @execution_threads = ThreadGroup.new
195
+
196
+ before_options if respond_to? :before_options
197
+ parse_arguments
198
+ after_options if respond_to? :after_options
199
+
200
+ # We set the @single_target attribute to true if we're given a
201
+ # single filename as a target, so that tools like elfgrep can
202
+ # avoid printing again the filename on the output. Since we could
203
+ # be given a single @-prefixed file to use as a list, we'll reset
204
+ # @single_target in self.execute_on_file
205
+ @single_target = (ARGV.size == 1)
206
+
207
+ if ARGV.size == 0
208
+ execute_on_file($stdin)
209
+ else
210
+ execute_on_array(ARGV)
211
+ end
212
+
213
+ @execution_threads.list.each do |thread|
214
+ thread.join
215
+ end
216
+
217
+ results if respond_to? :results
218
+ rescue Interrupt
219
+ puterror "Interrupted"
220
+ exit 1
221
+ end
222
+ end
223
+
224
+ at_exit do
225
+ unless $!
226
+ main
227
+ end
228
+ end
@@ -0,0 +1,112 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Copyright © 2009-2010 Diego E. "Flameeyes" Pettenò <flameeyes@gmail.com>
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
@@ -0,0 +1,37 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Copyright © 2009 Alex Legler <a3li@gentoo.org>
3
+ # Copyright © 2009-2010 Diego E. "Flameeyes" Pettenò <flameeyes@gmail.com>
4
+ #
5
+ # This program is free software; you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation; either version 2 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # This program is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with this generator; if not, write to the Free Software
17
+ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18
+
19
+ require 'elf'
20
+ require 'pathname'
21
+
22
+ module Elf::Utilities
23
+ # Pool for ELF files.
24
+ #
25
+ # This pool is useful for tools that recurse over a tree of
26
+ # dependencies to avoid creating multiple instances of Elf::File
27
+ # accessing the same file.
28
+ class FilePool
29
+ @pool = Hash.new
30
+
31
+ def self.[](file)
32
+ realfile = Pathname.new(file).realpath
33
+
34
+ @pool[realfile] ||= Elf::File.new(realfile)
35
+ end
36
+ end
37
+ end