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,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")