ruby-elf 1.0.3 → 1.0.4

Sign up to get free protection for your applications and to get access to all the features.
data/lib/elf.rb CHANGED
@@ -29,7 +29,7 @@ require 'elf/file'
29
29
  require 'elf/section'
30
30
 
31
31
  module Elf
32
- VERSION = "1.0.3"
32
+ VERSION = "1.0.4"
33
33
 
34
34
  MagicString = "\177ELF"
35
35
 
data/lib/elf/dynamic.rb CHANGED
@@ -248,7 +248,7 @@ module Elf
248
248
  end
249
249
 
250
250
  # Return the amount of entries in the .dynamic section.
251
- def size
251
+ def count
252
252
  load unless @entries
253
253
 
254
254
  @entries.size
data/lib/elf/file.rb CHANGED
@@ -87,6 +87,9 @@ module Elf
87
87
  :abi_version, :machine
88
88
  attr_reader :string_table
89
89
 
90
+ # raw data access
91
+ attr_reader :shoff
92
+
90
93
  def read_addr
91
94
  case @elf_class
92
95
  when Class::Elf32 then read_u32
@@ -196,7 +199,7 @@ module Elf
196
199
  @version = read_word
197
200
  @entry = read_addr
198
201
  @phoff = read_off
199
- shoff = read_off
202
+ @shoff = read_off
200
203
  @flags = read_word
201
204
  @ehsize = read_half
202
205
  @phentsize = read_half
@@ -209,7 +212,7 @@ module Elf
209
212
  @sections = {}
210
213
 
211
214
  @sections_data = []
212
- seek(shoff)
215
+ seek(@shoff)
213
216
  for i in 1..shnum
214
217
  sectdata = {}
215
218
  sectdata[:idx] = i-1
@@ -264,7 +267,7 @@ module Elf
264
267
  raise MissingSection.new(sect_idx_or_name) unless
265
268
  @sections_data[sect_idx_or_name]
266
269
 
267
- @sections[sect_idx_or_name] = Section.read(self, @sections_data[sect_idx_or_name])
270
+ @sections[sect_idx_or_name] = Section.read(self, sect_idx_or_name, @sections_data[sect_idx_or_name])
268
271
  else
269
272
  raise MissingSection.new(sect_idx_or_name) unless
270
273
  @sections_names[sect_idx_or_name]
@@ -287,6 +290,10 @@ module Elf
287
290
  end
288
291
  end
289
292
 
293
+ def sections
294
+ return @sections_data.size
295
+ end
296
+
290
297
  def [](sect_idx_or_name)
291
298
  if sect_idx_or_name.is_a? String and not @string_table.is_a? Elf::Section
292
299
  raise MissingStringTable.new(sect_idx_or_name) if @string_table == false
@@ -363,5 +370,25 @@ module Elf
363
370
  @abi_version == other.abi_version and
364
371
  @machine == other.machine
365
372
  end
373
+
374
+ # Constants used for ARM-architecture files, as described by the
375
+ # official documentation, "ELF for the ARM® Architecture", 28
376
+ # October 2009.
377
+ module ARM
378
+ EFlags_EABI_Mask = 0xFF000000
379
+ EFlags_BE8 = 0x00800000
380
+ end
381
+
382
+ def arm_eabi_version
383
+ return nil if machine != Elf::Machine::ARM
384
+
385
+ return (flags & ARM::EFlags_EABI_Mask) >> 24
386
+ end
387
+
388
+ def arm_be8?
389
+ return nil if machine != Elf::Machine::ARM
390
+
391
+ return (flags & ARM::EFlags_EB8) == ARM::EFlags_EB8
392
+ end
366
393
  end
367
394
  end
data/lib/elf/gnu.rb CHANGED
@@ -39,7 +39,7 @@ module Elf
39
39
  end
40
40
  end
41
41
 
42
- def size
42
+ def count
43
43
  load unless @versions
44
44
 
45
45
  @versions.size
@@ -91,7 +91,7 @@ module Elf
91
91
  end
92
92
  end
93
93
 
94
- def size
94
+ def count
95
95
  load unless @defined_versions
96
96
 
97
97
  @defined_versions.size
@@ -150,7 +150,7 @@ module Elf
150
150
  end
151
151
  end
152
152
 
153
- def size
153
+ def count
154
154
  load unless @needed_versions
155
155
 
156
156
  @needed_versions.size
data/lib/elf/section.rb CHANGED
@@ -39,7 +39,7 @@ module Elf
39
39
  Abs = 0xfff1 # Absolute symbols
40
40
  Common = 0xfff2 # Common symbols
41
41
  XIndex = 0xffff
42
-
42
+
43
43
  class UnknownType < Exception
44
44
  def initialize(type_id, section_name)
45
45
  @type_id = type_id
@@ -55,32 +55,40 @@ module Elf
55
55
  # This function assumes that the elf file is aligned ad the
56
56
  # start of a section header, and returns the file moved at the
57
57
  # start of the next header.
58
- def Section.read(elf, sectdata)
58
+ def Section.read(elf, index, sectdata)
59
59
  begin
60
60
  if Type::ProcSpecific.include?(sectdata[:type_id])
61
- case elf.machine
62
- when Elf::Machine::ARM
63
- type = Type::ProcARM[sectdata[:type_id]]
64
- else
61
+ begin
62
+ case elf.machine
63
+ when Elf::Machine::ARM
64
+ type = Type::ProcARM[sectdata[:type_id]]
65
+ else
66
+ type = Type[sectdata[:type_id]]
67
+ end
68
+ rescue Value::OutOfBound
65
69
  type = Type[sectdata[:type_id]]
66
70
  end
67
71
  elsif Type::OsSpecific.include?(sectdata[:type_id])
68
- # Unfortunately, even though OS ABIs are well-defined for both
69
- # GNU/Linux and Solaris, they don't seem to get used at all.
70
- #
71
- # For this reason, instead of basing ourselves on (just) the
72
- # OS ABI, the name of the section is used to identify the type
73
- # of section to use
74
-
75
- # Don't set the name if there is no string table loaded
76
- name = elf.string_table ? elf.string_table[sectdata[:name_idx]] : ""
77
- if elf.abi == Elf::OsAbi::Solaris or
78
- name =~ /^\.SUNW_/
79
- type = Type::SunW[sectdata[:type_id]]
80
- elsif elf.abi == Elf::OsAbi::Linux or
81
- name =~ /^\.gnu\./
82
- type = Type::GNU[sectdata[:type_id]]
83
- else
72
+ begin
73
+ # Unfortunately, even though OS ABIs are well-defined for both
74
+ # GNU/Linux and Solaris, they don't seem to get used at all.
75
+ #
76
+ # For this reason, instead of basing ourselves on (just) the
77
+ # OS ABI, the name of the section is used to identify the type
78
+ # of section to use
79
+
80
+ # Don't set the name if there is no string table loaded
81
+ name = elf.string_table ? elf.string_table[sectdata[:name_idx]] : ""
82
+ if elf.abi == Elf::OsAbi::Solaris or
83
+ name =~ /^\.SUNW_/
84
+ type = Type::SunW[sectdata[:type_id]]
85
+ elsif elf.abi == Elf::OsAbi::Linux or
86
+ name =~ /^\.gnu\./
87
+ type = Type::GNU[sectdata[:type_id]]
88
+ else
89
+ type = Type[sectdata[:type_id]]
90
+ end
91
+ rescue Value::OutOfBound
84
92
  type = Type[sectdata[:type_id]]
85
93
  end
86
94
  else
@@ -96,16 +104,18 @@ module Elf
96
104
  ) if type.nil?
97
105
 
98
106
  if Type::Class[type]
99
- return Type::Class[type].new(elf, sectdata, type)
107
+ return Type::Class[type].new(elf, index, sectdata, type)
100
108
  else
101
- return Section.new(elf, sectdata, type)
109
+ return Section.new(elf, index, sectdata, type)
102
110
  end
103
111
  end
104
112
 
105
- attr_reader :offset, :addr, :type, :size, :file
113
+ attr_reader :file, :index, :type, :addr, :offset, :size
114
+ attr_reader :addralign, :entsize, :info
106
115
 
107
- def initialize(elf, sectdata, type)
116
+ def initialize(elf, index, sectdata, type)
108
117
  @file = elf
118
+ @index = index
109
119
  @type = type
110
120
  @name = sectdata[:name_idx]
111
121
  @flags_val = sectdata[:flags_val]
@@ -143,6 +153,7 @@ module Elf
143
153
 
144
154
  # Alias to_s to name so that using this in a string will report the name
145
155
  alias :to_s :name
156
+ alias :to_i :index
146
157
 
147
158
  def link
148
159
  # We didn't get the linked section header yet
@@ -153,18 +164,6 @@ module Elf
153
164
  @link
154
165
  end
155
166
 
156
- # Return a set of flag items, easier to check for single elements.
157
- def flags
158
- return @flags if @flags
159
-
160
- @flags = Set.new
161
- Flags.each do |flag|
162
- flags.add(flag) if (@flags_val & flag.val) == flag.val
163
- end
164
-
165
- @flags
166
- end
167
-
168
167
  def load
169
168
  oldpos = @file.tell
170
169
  @file.seek(@offset, IO::SEEK_SET)
@@ -193,13 +192,55 @@ module Elf
193
192
  0x40000000 => [ :Ordered, 'Special ordering requirement' ],
194
193
  0x80000000 => [ :Exclude, 'Section is excluded unless referenced or allocated' ]
195
194
  )
196
-
195
+
197
196
  # OS-specific flags mask
198
197
  MaskOS = 0x0ff00000
199
198
  # Processor-specific flags mask
200
199
  MaskProc = 0xf0000000
201
200
  end
202
201
 
202
+ # Return a set of flag items, easier to check for single elements.
203
+ def flags
204
+ return @flags if @flags
205
+
206
+ @flags = Set.new
207
+ Flags.each do |flag|
208
+ flags.add(flag) if (@flags_val & flag.val) == flag.val
209
+ end
210
+
211
+ @flags
212
+ end
213
+
214
+ FlagsToChars = [
215
+ [Flags::Write, "W"],
216
+ [Flags::Alloc, "A"],
217
+ [Flags::ExecInstr, "X"],
218
+ [Flags::Merge, "M"],
219
+ [Flags::Strings, "S"],
220
+ [Flags::InfoLink, "I"],
221
+ [Flags::LinkOrder, "L"],
222
+ [Flags::Group, "G"],
223
+ [Flags::TLS, "T" ],
224
+ [Flags::Exclude, "E"],
225
+ ]
226
+
227
+ def flags_s
228
+ return @flags_s if @flags_s
229
+
230
+ @flags_s = FlagsToChars.collect { |flag, char|
231
+ flags.include?(flag) ? char : ""
232
+ }.join
233
+
234
+ @flags_s << 'o' if (@flags_val & Flags::MaskOS) != 0
235
+ @flags_s << 'p' if (@flags_val & Flags::MaskProc) != 0
236
+
237
+ return @flags_s
238
+ end
239
+
240
+ def flags_i
241
+ @flags_val
242
+ end
243
+
203
244
  # Return the nm(1) code for the section.
204
245
  #
205
246
  # This function is usually mostly used by Elf::Symbol#nm_code. It
@@ -290,7 +331,11 @@ module Elf
290
331
 
291
332
  class ProcARM < Value
292
333
  fill(
293
- 0x70000003 => [ :ARMAttributes, 'ARM Attributes' ]
334
+ 0x70000001 => [ :ARMExIdx, 'ARM Exception Index' ],
335
+ 0x70000002 => [ :ARMPreemptMap, 'ARM BDAPI DLL dynamic linking pre-emption map' ],
336
+ 0x70000003 => [ :ARMAttributes, 'ARM Attributes' ],
337
+ 0x70000004 => [ :ARMDebugOverlay, 'ARM Debug Overlay' ],
338
+ 0x70000005 => [ :ARMOverlaySection, 'ARM Overlay Table' ]
294
339
  )
295
340
  end
296
341
 
data/lib/elf/symbol.rb CHANGED
@@ -21,8 +21,6 @@
21
21
  # along with this generator; if not, write to the Free Software
22
22
  # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
23
23
 
24
- require 'elf/symbol/demangler_gcc3'
25
-
26
24
  module Elf
27
25
  class Symbol
28
26
  class Binding < Value
@@ -344,20 +342,28 @@ module Elf
344
342
  return true
345
343
  end
346
344
 
347
- Demanglers = [ Elf::Symbol::Demangler::GCC3 ]
348
- def demangle
349
- return @demangled if @demangled
345
+ begin
346
+ require 'elf/symbol/demangler_gcc3'
350
347
 
351
- Demanglers.each do |demangler|
352
- break if (@demangled ||= demangler.demangle(name))
353
- end
348
+ Demanglers = [ Elf::Symbol::Demangler::GCC3 ]
349
+ def demangle
350
+ return @demangled if @demangled
351
+
352
+ Demanglers.each do |demangler|
353
+ break if (@demangled ||= demangler.demangle(name))
354
+ end
354
355
 
355
- # We're going to remove top-namespace specifications as we don't
356
- # need them, but it's easier for the demangler to still emit
357
- # them.
358
- @demangled.gsub!(/(^| |\()::/, '\1') if @demangled
356
+ # We're going to remove top-namespace specifications as we don't
357
+ # need them, but it's easier for the demangler to still emit
358
+ # them.
359
+ @demangled.gsub!(/(^| |\()::/, '\1') if @demangled
359
360
 
360
- return @demangled ||= name
361
+ return @demangled ||= name
362
+ end
363
+ rescue LoadError
364
+ def demangle
365
+ return name
366
+ end
361
367
  end
362
368
 
363
369
  private
@@ -68,7 +68,7 @@ module Elf
68
68
  include ::Enumerable
69
69
 
70
70
  # Return the number of symbols in the section
71
- def size
71
+ def count
72
72
  symbols.size
73
73
  end
74
74
 
data/lib/elf/tools.rb CHANGED
@@ -28,197 +28,231 @@ require 'elf'
28
28
  # require arguments (in that latter case they are considered on/off
29
29
  # switches), and so on.
30
30
 
31
- # Gets the name of the tool
32
- def self.to_s
33
- File.basename($0)
34
- end
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
35
36
 
36
- # Output an error message, prefixed with the tool name.
37
- def self.puterror(string)
38
- return if @quiet
37
+ at_exit do
38
+ unless $!
39
+ klass.main
40
+ end
41
+ end
42
+ end
39
43
 
40
- @output_mutex.synchronize {
41
- $stderr.puts "#{to_s}: #{string}"
42
- }
43
- end
44
+ # Gets the name of the tool
45
+ def self.to_s
46
+ File.basename($0)
47
+ end
44
48
 
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
+ # Output an error message, prefixed with the tool name.
50
+ def self.puterror(string)
51
+ return if @quiet
49
52
 
50
- @output_mutex.synchronize {
51
- $stderr.puts message
52
- }
53
- end
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
54
62
 
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
- manpage = File.expand_path("../../../manpages/#{to_s}.1", __FILE__)
71
- manpage = to_s unless File.exist?(manpage)
72
- exec("man #{manpage}")
63
+ @output_mutex.synchronize {
64
+ $stderr.puts message
65
+ }
73
66
  end
74
67
 
75
- attrname = opt.gsub(/^--/, "").gsub("-", "_")
76
- attrval = arg.size == 0 ? true : arg
77
-
78
- # If there is a function with the same name of the parameter
79
- # defined (with a _cb suffix), call that, otherwise set the
80
- # attribute with the same name to the given value.
81
- cb = method("#{attrname}_cb") rescue nil
82
- case
83
- when cb.nil?
84
- instance_variable_set("@#{attrname}", attrval)
85
- when cb.arity == 0
86
- raise ArgumentError("wrong number of arguments in callback (0 for 1)") unless
87
- arg.size == 0
88
- cb.call
89
- when cb.arity == 1
90
- # fallback to provide a single "true" parameter if there was no
91
- # required argument
92
- cb.call(attrval)
93
- else
94
- raise ArgumentError("wrong number of arguments in callback (#{cb.arity} for #{arg.size})")
68
+ def self.after_options
95
69
  end
96
- end
97
70
 
98
- @parsed_options = true
99
- end
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
100
84
 
101
- def self.single_target?
102
- raise Exception.new("You can't call this until options are parsed") unless @parsed_options
85
+ attrname = opt.gsub(/^--/, "").gsub("-", "_")
86
+ attrval = arg.size == 0 ? true : arg
103
87
 
104
- # We consider having a single target means that we're given exactly
105
- # one argument, and that argument is not a targets' list itself.
106
- return (ARGV.size == 1 and ARGV[0] !~ /^@/)
107
- end
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
108
107
 
109
- def execute(filename)
110
- begin
111
- analysis(filename)
112
- rescue Errno::ENOENT, Errno::EACCES, Errno::EISDIR, Elf::File::NotAnELF,
113
- Elf::File::InvalidElfClass, Elf::File::InvalidDataEncoding,
114
- Elf::File::UnsupportedElfVersion, Elf::File::InvalidOsAbi, Elf::File::InvalidElfType,
115
- Elf::File::InvalidMachine => e
116
- # The Errno exceptions have their message ending in " - FILENAME",
117
- # so we take the FILENAME out and just use the one we know
118
- # already. We also take out the final dot on the phrase so that
119
- # we follow the output messages from other tools, like cat.
120
- putnotice "#{filename}: #{e.message.gsub(/\.? - .*/, '')}"
121
- rescue Exception => e
122
- puterror "#{filename}: #{e.message} (#{e.class})\n\t#{e.backtrace.join("\n\t")}"
123
- exit -1
124
- end
125
- end
108
+ @parsed_options = true
109
+ end
126
110
 
127
- # Try to execute the analysis function on a given filename argument.
128
- def self.try_execute(filename)
129
- begin
130
- # if the file name starts with '@', it is not a target, but a file
131
- # with a list of targets, so load it with execute_on_file.
132
- if filename[0..0] == "@"
133
- execute_on_file(filename[1..-1])
134
- return
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
135
117
  end
136
118
 
137
- # find the file type so we don't have to look it up many times; if
138
- # we're running a recursive scan, we don't want to look into
139
- # symlinks as they might create loops or duplicate content, while
140
- # we usually want to check them out if they are given directly in
141
- # the list of files to analyse
142
- file_stat = if @recursive
143
- File.lstat(filename)
144
- else
145
- File.stat(filename)
146
- end
147
-
148
- # if the path references a directory, and we're going to run
149
- # recursively, descend into that.
150
- if @recursive and file_stat.directory?
151
- Dir.foreach(filename) do |children|
152
- next if children == "." or children == ".."
153
- try_execute(File.join(filename, children))
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
154
134
  end
155
- # if the path does not point to a regular file, ignore it
156
- elsif not file_stat.file?
157
- putnotice "#{filename}: not a regular file"
158
- else
159
- @execution_threads.add(Thread.new {
160
- execute(filename)
161
- })
162
135
  end
163
- rescue Errno::ENOENT, Errno::EACCES, Errno::EISDIR, Elf::File::NotAnELF => e
164
- # The Errno exceptions have their message ending in " - FILENAME",
165
- # so we take the FILENAME out and just use the one we know
166
- # already. We also take out the final dot on the phrase so that
167
- # we follow the output messages from other tools, like cat.
168
- putnotice "#{filename}: #{e.message.gsub(/\.? - .*/, '')}"
169
- rescue SystemExit => e
170
- exit e.status
171
- rescue Exception => e
172
- puterror "#{filename}: #{e.message} (#{e.class})\n\t#{e.backtrace.join("\n\t")}"
173
- exit -1
174
- end
175
- end
176
136
 
177
- # Execute the analysis function on all the elements of an array.
178
- def self.execute_on_array(array)
179
- array.each do |filename|
180
- try_execute(filename)
181
- end
182
- end
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
183
150
 
184
- # Execute the analysis function on all the lines of a file
185
- def self.execute_on_file(file)
186
- file = $stdin if file == "-"
187
- file = File.new(file) if file.class == String
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
188
164
 
189
- file.each_line do |line|
190
- try_execute(line.chomp("\n"))
191
- end
192
- end
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
193
191
 
194
- def self.main
195
- begin
196
- @output_mutex = Mutex.new
197
- @execution_threads = ThreadGroup.new
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
198
196
 
199
- before_options if respond_to? :before_options
200
- parse_arguments
201
- after_options if respond_to? :after_options
197
+ param.each do |filename|
198
+ try_execute(filename)
199
+ end
200
+ end
202
201
 
203
- if ARGV.size == 0
204
- execute_on_file($stdin)
205
- else
206
- execute_on_array(ARGV)
202
+ def self.results
207
203
  end
208
204
 
209
- @execution_threads.list.each do |thread|
210
- thread.join
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
+ ]
211
214
  end
212
215
 
213
- results if respond_to? :results
214
- rescue Interrupt
215
- puterror "Interrupted"
216
- exit 1
217
- end
218
- end
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.new(argument[1..-1]).read.split(/\r?\n/)
228
+ else
229
+ argument
230
+ end
231
+ }.flatten
232
+
233
+ after_options
219
234
 
220
- at_exit do
221
- unless $!
222
- main
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
+ @targets.uniq.each { |target| try_execute(target) }
243
+ end
244
+
245
+ if @execution_threads
246
+ @execution_threads.list.each do |thread|
247
+ thread.join
248
+ end
249
+ end
250
+
251
+ results
252
+ rescue Interrupt
253
+ puterror "Interrupted"
254
+ exit 1
255
+ end
256
+ end
223
257
  end
224
258
  end