ruby-elf 1.0.3 → 1.0.4

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