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