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,105 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- coding: utf-8 -*-
3
+ # Copyright © 2008-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
+ # Simple script to assess the amount of space saved by duplicate removal of
20
+ # entries in symbols' tables.
21
+
22
+ require 'elf'
23
+
24
+ file_list = nil
25
+
26
+ # If there are no arguments passed through the command line
27
+ # consider it like we're going to act on stdin.
28
+ if not file_list and ARGV.size == 0
29
+ file_list = $stdin
30
+ end
31
+
32
+ def assess_save(file)
33
+ begin
34
+ Elf::File.open(file) do |elf|
35
+ seenstr = Set.new
36
+
37
+ symsec = elf['.dynsym']
38
+ strsec = elf['.dynstr']
39
+
40
+ next unless symsec and strsec
41
+
42
+ # The NULL-entry can be aliased on the last string anyway by
43
+ # letting it point to sectionsize-1
44
+ fullsize = 0
45
+
46
+ symsec.each do |sym|
47
+ next if seenstr.include? sym.name
48
+ seenstr.add sym.name
49
+ fullsize += sym.name.length+1
50
+ end
51
+
52
+ # Dynamic executables and shared objects keep more data into the
53
+ # .dynstr than static executables, in particular they have symbols
54
+ # versions, their soname and their NEEDED sections strings.
55
+ versec = elf['.gnu.version_d']
56
+ if versec
57
+ versec.each do |veridx, ver|
58
+ ver[:names].each do |vername|
59
+ next if seenstr.include? vername
60
+ seenstr.add vername
61
+ fullsize += vername.length+1
62
+ end
63
+ end
64
+ end
65
+
66
+ versec = elf['.gnu.version_r']
67
+ if versec
68
+ versec.each do |veridx, ver|
69
+ next if seenstr.include? ver[:name]
70
+ seenstr.add ver[:name]
71
+ fullsize += ver[:name].length+1
72
+ end
73
+ end
74
+
75
+ elf['.dynamic'].each_entry do |entry|
76
+ case entry[:type]
77
+ when Elf::Dynamic::Type::Needed, Elf::Dynamic::Type::SoName,
78
+ Elf::Dynamic::Type::RPath, Elf::Dynamic::Type::RunPath
79
+
80
+ next if seenstr.include? entry[:parsed]
81
+ seenstr.add entry[:parsed]
82
+ fullsize += entry[:parsed].length+1
83
+ end
84
+ end
85
+
86
+ puts "#{file}: current size #{strsec.size}, full size #{fullsize} difference #{fullsize-strsec.size}"
87
+ end
88
+ rescue Errno::ENOENT
89
+ $stderr.puts "assess_duplicate_save.rb: #{file}: no such file"
90
+ rescue Errno::EISDIR
91
+ $stderr.puts "assess_duplicate_save.rb: #{file}: is a directory"
92
+ rescue Elf::File::NotAnELF
93
+ $stderr.puts "assess_duplicate_save.rb: #{file}: not a valid ELF file."
94
+ end
95
+ end
96
+
97
+ if file_list
98
+ file_list.each_line do |file|
99
+ assess_save(file.rstrip)
100
+ end
101
+ else
102
+ ARGV.each do |file|
103
+ assess_save(file)
104
+ end
105
+ end
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- coding: utf-8 -*-
3
+ # Copyright © 2007-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 'getoptlong'
20
+ require 'pg'
21
+
22
+ opts = GetoptLong.new(
23
+ ["--output", "-o", GetoptLong::REQUIRED_ARGUMENT],
24
+ ["--postgres-username", "-U", GetoptLong::REQUIRED_ARGUMENT ],
25
+ ["--postgres-password", "-P", GetoptLong::REQUIRED_ARGUMENT ],
26
+ ["--postgres-hostname", "-H", GetoptLong::REQUIRED_ARGUMENT ],
27
+ ["--postgres-port", "-T", GetoptLong::REQUIRED_ARGUMENT ],
28
+ ["--postgres-database", "-D", GetoptLong::REQUIRED_ARGUMENT ]
29
+ )
30
+
31
+ outfile = $stdout
32
+
33
+ pg_params = {}
34
+
35
+ opts.each do |opt, arg|
36
+ case opt
37
+ when '--output'
38
+ outfile = File.new(arg, "w")
39
+ when '--postgres-username' then pg_params[:user] = arg
40
+ when '--postgres-password' then pg_params[:password] = arg
41
+ when '--postgres-hostname' then pg_params[:host] = arg
42
+ when '--postgres-port' then pg_params[:port] = arg
43
+ when '--postgres-database' then pg_params[:dbname] = arg
44
+ end
45
+ end
46
+
47
+ db = PGconn.open(pg_params)
48
+
49
+ db.exec("PREPARE getinstances (text, text) AS
50
+ SELECT name FROM symbols INNER JOIN objects ON symbols.object = objects.id WHERE symbol = $1 AND abi = $2 ORDER BY name")
51
+
52
+ db.exec("SELECT * FROM duplicate_symbols").each do |row|
53
+ outfile.puts "Symbol #{row['symbol']} (#{row['abi']}) present #{row['occurrences']} times"
54
+ db.exec( "EXECUTE getinstances ('#{row['symbol']}', '#{row['abi']}')" ).each do |path|
55
+ outfile.puts " #{path['name']}"
56
+ end
57
+ end
@@ -0,0 +1,367 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- coding: utf-8 -*-
3
+ # Copyright © 2007-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
+ # This script is used to harvest the symbols defined in the shared
20
+ # objects of the whole system.
21
+
22
+ require 'getoptlong'
23
+ require 'set'
24
+ require 'pathname'
25
+ require 'pg'
26
+
27
+ require 'elf'
28
+ require 'elf/utils/loader'
29
+
30
+ opts = GetoptLong.new(
31
+ ["--no-scan-ldpath", "-L", GetoptLong::NO_ARGUMENT ],
32
+ ["--scan-path", "-p", GetoptLong::NO_ARGUMENT ],
33
+ ["--suppressions", "-s", GetoptLong::REQUIRED_ARGUMENT ],
34
+ ["--multiplementations", "-m", GetoptLong::REQUIRED_ARGUMENT ],
35
+ ["--scan-directory", "-d", GetoptLong::REQUIRED_ARGUMENT ],
36
+ ["--recursive-scan", "-r", GetoptLong::NO_ARGUMENT ],
37
+ ["--elf-machine", "-M", GetoptLong::REQUIRED_ARGUMENT ],
38
+ ["--postgres-username", "-U", GetoptLong::REQUIRED_ARGUMENT ],
39
+ ["--postgres-password", "-P", GetoptLong::REQUIRED_ARGUMENT ],
40
+ ["--postgres-hostname", "-H", GetoptLong::REQUIRED_ARGUMENT ],
41
+ ["--postgres-port", "-T", GetoptLong::REQUIRED_ARGUMENT ],
42
+ ["--postgres-database", "-D", GetoptLong::REQUIRED_ARGUMENT ]
43
+ )
44
+
45
+ suppression_files = File.exist?('suppressions') ? [ 'suppressions' ] : []
46
+ multimplementation_files = File.exist?('multimplementations') ? [ 'multimplementations' ] : []
47
+ scan_path = false
48
+ scan_ldpath = true
49
+ recursive_scan = false
50
+ scan_directories = []
51
+ $machines = []
52
+
53
+ pg_params = {}
54
+
55
+ opts.each do |opt, arg|
56
+ case opt
57
+ when '--suppressions'
58
+ unless File.exist? arg
59
+ $stderr.puts "harvest.rb: no such file or directory - #{arg}"
60
+ exit -1
61
+ end
62
+ suppression_files << arg
63
+ when "--multiplementations"
64
+ unless File.exist? arg
65
+ $stderr.puts "harvest.rb: no such file or directory - #{arg}"
66
+ exit -1
67
+ end
68
+ multimplementation_files << arg
69
+ when '--scan-path'
70
+ scan_path = true
71
+ when '--no-scan-ldpath'
72
+ scan_ldpath = false
73
+ when '--scan-directory'
74
+ scan_directories << arg
75
+ when '--recursive-scan'
76
+ recursive_scan = true
77
+ when "--elf-machine"
78
+ machine_str = arg.dup
79
+
80
+ # Remove the EM_ prefix if present
81
+ machine_str = machine_str[3..-1] if arg[0..2].upcase == "EM_"
82
+
83
+ # Remove underscores if present (we don't keep them in our
84
+ # constants)
85
+ machine_str.delete!("_")
86
+
87
+ machine_val = Elf::Machine.from_string(machine_str)
88
+
89
+ if machine_val.nil?
90
+ $stderr.puts "harvest.rb: unknown machine string - #{arg}"
91
+ else
92
+ $machines << machine_val unless machine_val.nil?
93
+ end
94
+ when '--postgres-username' then pg_params[:user] = arg
95
+ when '--postgres-password' then pg_params[:password] = arg
96
+ when '--postgres-hostname' then pg_params[:host] = arg
97
+ when '--postgres-port' then pg_params[:port] = arg
98
+ when '--postgres-database' then pg_params[:dbname] = arg
99
+ end
100
+ end
101
+
102
+ $machines = nil if $machines.empty?
103
+
104
+ db = PGconn.open(pg_params)
105
+
106
+ db.exec("DROP TABLE IF EXISTS symbols, multimplementations, objects CASCADE")
107
+
108
+ db.exec("CREATE TABLE objects ( id INTEGER PRIMARY KEY, name VARCHAR(4096), abi VARCHAR(255), exported BOOLEAN, UNIQUE(name, abi) )")
109
+ db.exec("CREATE TABLE multimplementations ( id INTEGER REFERENCES objects(id) ON DELETE CASCADE, path VARCHAR(4096), UNIQUE(path) )")
110
+ db.exec("CREATE TABLE symbols ( object INTEGER REFERENCES objects(id) ON DELETE CASCADE, symbol TEXT,
111
+ PRIMARY KEY(object, symbol) )")
112
+
113
+ db.exec("CREATE VIEW symbol_count AS
114
+ SELECT symbol, abi, COUNT(*) AS occurrences, BOOL_OR(objects.exported) AS exported FROM symbols INNER JOIN objects ON symbols.object = objects.id GROUP BY symbol, abi")
115
+ db.exec("CREATE VIEW duplicate_symbols AS
116
+ SELECT * FROM symbol_count WHERE occurrences > 1 AND exported = 't' ORDER BY occurrences DESC, symbol ASC")
117
+
118
+ db.exec("PREPARE newmulti (int, text) AS
119
+ INSERT INTO multimplementations (id, path) VALUES($1, $2)")
120
+ db.exec("PREPARE newobject (int, text, text, boolean) AS
121
+ INSERT INTO objects(id, name, abi, exported) VALUES($1, $2, $3, $4)")
122
+ db.exec("PREPARE newsymbol (int, text) AS
123
+ INSERT INTO symbols VALUES($1, $2)")
124
+
125
+ db.exec("PREPARE checkimplementation(text, text) AS
126
+ SELECT id FROM objects WHERE name = $1 AND abi = $2")
127
+ db.exec("PREPARE checkdupsymbol (int, text) AS
128
+ SELECT 1 FROM symbols WHERE object = $1 AND symbol = $2")
129
+
130
+ # Total suppressions are for directories to skip entirely
131
+ # Partial suppressions are the ones that apply only to a subset
132
+ # of symbols.
133
+ $total_suppressions = []
134
+ $partial_suppressions = []
135
+
136
+ suppression_files.each do |suppression|
137
+ File.open(suppression) do |file|
138
+ file.each_line do |line|
139
+ path, symbols = line.
140
+ gsub(/#\s.*/, '').
141
+ strip.
142
+ split(/\s+/, 2)
143
+
144
+ next unless path
145
+
146
+ if not symbols or symbols == ""
147
+ $total_suppressions << Regexp.new(path)
148
+ else
149
+ $partial_suppressions << [Regexp.new(path), Regexp.new(symbols)]
150
+ end
151
+ end
152
+ end
153
+ end
154
+
155
+ multimplementations = []
156
+
157
+ multimplementation_files.each do |multimplementation|
158
+ File.open(multimplementation) do |file|
159
+ file.each_line do |line|
160
+ implementation, paths = line.
161
+ gsub(/#\s.*/, '').
162
+ strip.
163
+ split(/\s+/, 2)
164
+
165
+ next unless implementation
166
+ next unless paths
167
+
168
+ multimplementations << [ implementation, Regexp.new(paths) ]
169
+ end
170
+ end
171
+ end
172
+
173
+ so_files = Set.new
174
+
175
+ # Extend Pathname with a so_files method
176
+ class Pathname
177
+ def maybe_queue
178
+ return nil if symlink?
179
+
180
+ $total_suppressions.each do |supp|
181
+ return nil if to_s =~ supp
182
+ end
183
+
184
+ elf = nil
185
+
186
+ begin
187
+ elf = Elf::File.open(self)
188
+
189
+ if ($machines.nil? or $machines.include?(elf.machine)) and
190
+ (elf.has_section?('.dynsym') and elf.has_section?('.dynstr') and
191
+ elf.has_section?('.dynamic')) and
192
+ (elf[".dynsym"].class == Elf::SymbolTable)
193
+ return to_s
194
+ else
195
+ return nil
196
+ end
197
+ # Explicitly list this so that it won't pollute the output
198
+ rescue Elf::File::NotAnELF
199
+ return nil
200
+ rescue Exception => e
201
+ $stderr.puts "harvest.rb: #{e.message} - #{self.to_s}"
202
+ ensure
203
+ elf.close unless elf.nil?
204
+ end
205
+ end
206
+
207
+ def so_files(recursive = true)
208
+ res = Set.new
209
+ children.each do |entry|
210
+ begin
211
+ case entry.ftype
212
+ when "directory"
213
+ res.merge entry.so_files if recursive
214
+ when "file"
215
+ res << entry.maybe_queue
216
+ end
217
+ # When using C-c to stop, well, stop.
218
+ rescue Interrupt
219
+ raise
220
+ rescue Exception => e
221
+ $stderr.puts "Ignoring #{entry} (#{e.message})"
222
+ next
223
+ end
224
+ end
225
+
226
+ return res
227
+ end
228
+ end
229
+
230
+ if scan_ldpath
231
+ Elf::Utilities.system_library_path.each do |path|
232
+ begin
233
+ so_files.merge Pathname.new(path).so_files
234
+ rescue Errno::ENOENT
235
+ $stderr.puts "harvest.rb: No such file or directory - #{path}"
236
+ next
237
+ end
238
+ end
239
+ end
240
+
241
+ if scan_path and ENV['PATH']
242
+ ENV['PATH'].split(":").each do |path|
243
+ begin
244
+ so_files.merge Pathname.new(path).so_files(false)
245
+ rescue Errno::ENOENT
246
+ $stderr.puts "harvest.rb: No such file or directory - #{path}"
247
+ next
248
+ end
249
+ end
250
+ end
251
+
252
+ scan_directories.each do |path|
253
+ begin
254
+ so_files.merge Pathname.new(path).so_files(recursive_scan)
255
+ rescue Errno::ENOENT
256
+ $stderr.puts "harvest.rb: No such file or directory - #{path}"
257
+ next
258
+ end
259
+ end
260
+
261
+ # if there are explicit files listed in the standard input, scan those
262
+ # right away.
263
+ ARGV.each do |path|
264
+ so_files << Pathname.new(path).maybe_queue
265
+ end
266
+
267
+ # finally if none of the above matched, try checking for data on the
268
+ # standard input
269
+ if ARGV.size == 0 and not scan_path and scan_directories.size == 0
270
+ $stdin.each_line do |path|
271
+ so_files << Pathname.new(path.chomp("\n")).maybe_queue
272
+ end
273
+ end
274
+
275
+ so_files.delete(nil)
276
+
277
+ db.exec("BEGIN TRANSACTION")
278
+ val = 0
279
+
280
+ begin
281
+ require 'progressbar'
282
+
283
+ pbar = ProgressBar.new("harvest", so_files.size)
284
+ rescue LoadError, NameError
285
+ end
286
+
287
+ so_files.each do |so|
288
+ local_suppressions = $partial_suppressions.dup.delete_if { |s| not so.to_s =~ s[0] }
289
+
290
+ begin
291
+ Elf::File.open(so) do |elf|
292
+ name = so
293
+ abi = "#{elf.elf_class} #{elf.abi} #{elf.machine.to_s.gsub("'", "\\'" )}"
294
+
295
+ impid = nil
296
+
297
+ multimplementations.each do |implementation, paths|
298
+ # Get the full matchdata because we might need to get the matches.
299
+ match = paths.match(so)
300
+
301
+ next unless match
302
+
303
+ while implementation =~ /\$([0-9]+)/ do
304
+ match_idx = $1.to_i
305
+ replacement = match[match_idx]
306
+ replacement = "" if replacement.nil?
307
+ implementation = implementation.gsub("$#{match_idx}", replacement)
308
+ end
309
+
310
+ name = implementation
311
+ db.exec("EXECUTE checkimplementation('#{implementation}', '#{abi}')").each do |row|
312
+ impid = row['id']
313
+ end
314
+ break
315
+ end
316
+
317
+ shared = (so != name) || (elf['.dynamic'].soname != nil)
318
+
319
+ unless impid
320
+ val += 1
321
+ impid = val
322
+
323
+ db.exec("EXECUTE newobject(#{impid}, '#{name}', '#{abi}', '#{shared}')")
324
+ end
325
+
326
+ db.exec("EXECUTE newmulti(#{impid}, '#{so}')") if so != name
327
+
328
+ elf['.dynsym'].each do |sym|
329
+ begin
330
+ next if sym.idx == 0 or
331
+ sym.bind != Elf::Symbol::Binding::Global or
332
+ sym.section.nil? or
333
+ sym.value == 0 or
334
+ sym.section.is_a? Integer or
335
+ sym.section.name == '.init' or
336
+ sym.section.name == '.bss'
337
+
338
+ skip = false
339
+
340
+ local_suppressions.each do |supp|
341
+ if sym.name =~ supp[1]
342
+ skip = true
343
+ break
344
+ end
345
+ end
346
+
347
+ next if skip or (db.exec("EXECUTE checkdupsymbol('#{impid}', '#{sym.name}@#{sym.version}')").num_tuples > 0)
348
+
349
+ db.exec("EXECUTE newsymbol('#{impid}', '#{sym.name}@#{sym.version}')")
350
+
351
+ rescue Exception
352
+ $stderr.puts "Mangling symbol #{sym.name}"
353
+ raise
354
+ end
355
+ end
356
+ end
357
+ rescue Exception
358
+ $stderr.puts "Checking #{so}"
359
+ raise
360
+ end
361
+
362
+ pbar.inc if pbar
363
+ end
364
+
365
+ db.exec("CREATE INDEX objects_name ON objects(name)")
366
+ db.exec("CREATE INDEX symbols_symbol ON symbols(symbol)")
367
+ db.exec("COMMIT")