ony-ruby-elf 1.1.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.
- checksums.yaml +7 -0
- data/COPYING +339 -0
- data/DONATING +42 -0
- data/README.md +59 -0
- data/lib/bytestream-reader.rb +271 -0
- data/lib/elf.rb +247 -0
- data/lib/elf/dynamic.rb +392 -0
- data/lib/elf/file.rb +407 -0
- data/lib/elf/gnu.rb +174 -0
- data/lib/elf/section.rb +348 -0
- data/lib/elf/stringtable.rb +39 -0
- data/lib/elf/sunw.rb +158 -0
- data/lib/elf/symbol.rb +406 -0
- data/lib/elf/symboltable.rb +90 -0
- data/lib/elf/tools.rb +259 -0
- data/lib/elf/utils/loader.rb +112 -0
- data/lib/elf/utils/offsettable.rb +49 -0
- data/lib/elf/utils/pool.rb +49 -0
- data/lib/elf/value.rb +128 -0
- data/ruby-elf.gemspec +29 -0
- data/tools/assess_duplicate_save.rb +105 -0
- data/tools/link-collisions/harvest.rb +340 -0
- data/tools/link-collisions/suppress.rb +84 -0
- data/tools/rbelf-lddtree.rb +49 -0
- metadata +71 -0
@@ -0,0 +1,49 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
# Offset-based string table
|
3
|
+
#
|
4
|
+
# Copyright © 2007-2012 Diego Elio Pettenò <flameeyes@flameeyes.eu>
|
5
|
+
#
|
6
|
+
# This program is free software; you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU General Public License as published by
|
8
|
+
# the Free Software Foundation; either version 2 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# This program is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License
|
17
|
+
# along with this generator; if not, write to the Free Software
|
18
|
+
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
19
|
+
|
20
|
+
module Elf
|
21
|
+
module Utilities
|
22
|
+
class OffsetTable
|
23
|
+
class InvalidIndex < Exception
|
24
|
+
def initialize(idx, max_idx)
|
25
|
+
super("Invalid index #{idx} (maximum index: #{max_idx})")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def initialize(content, separator)
|
30
|
+
@content = content
|
31
|
+
@separator = separator
|
32
|
+
end
|
33
|
+
|
34
|
+
def size
|
35
|
+
@content.size
|
36
|
+
end
|
37
|
+
|
38
|
+
def [](idx)
|
39
|
+
raise InvalidIndex.new(idx, size) if idx >= size
|
40
|
+
|
41
|
+
# find the first occurrence of the separator starting from the
|
42
|
+
# given index
|
43
|
+
endidx = @content.index(@separator, idx)
|
44
|
+
|
45
|
+
return @content[idx..endidx].chomp(@separator)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
# Copyright © 2009 Alex Legler <a3li@gentoo.org>
|
3
|
+
# Copyright © 2009-2010 Diego Elio Pettenò <flameeyes@flameeyes.eu>
|
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
|
+
require 'weakref'
|
22
|
+
|
23
|
+
module Elf::Utilities
|
24
|
+
# Pool for ELF files.
|
25
|
+
#
|
26
|
+
# This pool is useful for tools that recurse over a tree of
|
27
|
+
# dependencies to avoid creating multiple instances of Elf::File
|
28
|
+
# being open and accessing the same file.
|
29
|
+
#
|
30
|
+
# Notes:
|
31
|
+
# - Instances might be re-created if no strong references left to specific
|
32
|
+
# Elf::File
|
33
|
+
# - New instance will be created on access if previous one were closed
|
34
|
+
class FilePool
|
35
|
+
@pool = Hash.new
|
36
|
+
|
37
|
+
def self.[](file)
|
38
|
+
realfile = Pathname.new(file).realpath
|
39
|
+
|
40
|
+
cached = @pool[realfile].__getobj__ rescue nil
|
41
|
+
if cached.nil? || cached.closed?
|
42
|
+
cached = Elf::File.new(realfile)
|
43
|
+
@pool[realfile] = WeakRef.new(cached)
|
44
|
+
end
|
45
|
+
|
46
|
+
cached
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/lib/elf/value.rb
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
# Simple ELF parser for Ruby
|
3
|
+
#
|
4
|
+
# Copyright © 2007-2010 Diego Elio Pettenò <flameeyes@flameeyes.eu>
|
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
|
+
module Elf
|
25
|
+
class Value
|
26
|
+
class OutOfBound < Exception
|
27
|
+
attr_reader :val
|
28
|
+
|
29
|
+
def initialize(val)
|
30
|
+
@val = val
|
31
|
+
@appendix = ""
|
32
|
+
end
|
33
|
+
|
34
|
+
def message
|
35
|
+
"Value #{@val} out of bound#{@appendix}"
|
36
|
+
end
|
37
|
+
|
38
|
+
def append_message(s)
|
39
|
+
@appendix << "\n#{s}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def initialize(val, params)
|
44
|
+
@val = val
|
45
|
+
@mnemonic = params[0].to_s
|
46
|
+
@desc = params[1]
|
47
|
+
end
|
48
|
+
|
49
|
+
attr_reader :desc, :val, :mnemonic
|
50
|
+
alias :to_i :val
|
51
|
+
alias :to_s :desc
|
52
|
+
|
53
|
+
def ==(other)
|
54
|
+
self.class == other.class and @val == other.to_i
|
55
|
+
end
|
56
|
+
|
57
|
+
def Value.[](idx)
|
58
|
+
return @enums[idx] if @enums[idx]
|
59
|
+
|
60
|
+
# If the class has defined special ranges, handle them; a
|
61
|
+
# special range is a range of values for which unknown values
|
62
|
+
# are allowed (because they are bound to specific usage we don't
|
63
|
+
# know about — where on the other hand unknown values outside of
|
64
|
+
# these ranges are frown upon); different type of values have
|
65
|
+
# different special ranges, each with its own base name, so
|
66
|
+
# leave that to be decided by the class itself.
|
67
|
+
if self.const_defined?("SpecialRanges")
|
68
|
+
self::SpecialRanges.each_pair do |base, range|
|
69
|
+
return self::Unknown.new(idx, sprintf("%s+%07x", base, idx-range.first)) if range.include? idx
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
raise OutOfBound.new(idx)
|
74
|
+
end
|
75
|
+
|
76
|
+
def Value.from_string(str)
|
77
|
+
str = str.downcase
|
78
|
+
|
79
|
+
each do |value|
|
80
|
+
return value if value.mnemonic.downcase == str
|
81
|
+
end
|
82
|
+
|
83
|
+
return nil
|
84
|
+
end
|
85
|
+
|
86
|
+
def Value.has_key?(idx)
|
87
|
+
@enums.has_key?(idx)
|
88
|
+
end
|
89
|
+
|
90
|
+
def Value.fill(*hash)
|
91
|
+
if hash.size == 1 && hash[0].is_a?(Hash)
|
92
|
+
hash = hash[0]
|
93
|
+
end
|
94
|
+
|
95
|
+
@enums = { }
|
96
|
+
|
97
|
+
hash.each_pair do |index, value|
|
98
|
+
@enums[index] = self.new(index, value)
|
99
|
+
const_set(value[0], @enums[index])
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def Value.each(&block)
|
104
|
+
@enums.each_value(&block)
|
105
|
+
end
|
106
|
+
|
107
|
+
private_class_method :fill
|
108
|
+
|
109
|
+
# Class for unknown values
|
110
|
+
#
|
111
|
+
# This class is used to provide a way to access at least basic
|
112
|
+
# data for values that are not known but are known valid (like OS-
|
113
|
+
# or CPU-specific types for files, sections and symbols).
|
114
|
+
#
|
115
|
+
# It mimics the basis of a Value but is custom-filled by the using
|
116
|
+
# code.
|
117
|
+
class Unknown
|
118
|
+
def initialize(val, desc)
|
119
|
+
@val = val
|
120
|
+
@desc = desc
|
121
|
+
end
|
122
|
+
|
123
|
+
attr_reader :desc, :val
|
124
|
+
alias :to_i :val
|
125
|
+
alias :to_s :desc
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
data/ruby-elf.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# -*- ruby -*- coding: utf-8 -*-
|
2
|
+
$:.unshift(File.dirname(__FILE__) + '/lib')
|
3
|
+
require 'elf'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = 'ony-ruby-elf'
|
7
|
+
s.version = Elf::VERSION
|
8
|
+
s.summary = "Pure Ruby ELF file parser and utilities"
|
9
|
+
|
10
|
+
s.requirements << 'none'
|
11
|
+
s.require_path = 'lib'
|
12
|
+
s.homepage = "http://www.flameeyes.eu/projects/ruby-elf"
|
13
|
+
s.license = "GPL-2 or later"
|
14
|
+
s.author = "Diego Elio Pettenò"
|
15
|
+
s.email = "flameeyes@flameeyes.eu"
|
16
|
+
|
17
|
+
s.description = <<EOF
|
18
|
+
Ruby-Elf is a pure-Ruby library for parse and fetch information about
|
19
|
+
ELF format used by Linux, FreeBSD, Solaris and other Unix-like
|
20
|
+
operating systems, and include a set of analysis tools helpful for
|
21
|
+
both optimisations and verification of compiled ELF files.
|
22
|
+
EOF
|
23
|
+
|
24
|
+
s.files = %w{COPYING README.md DONATING ruby-elf.gemspec}
|
25
|
+
s.files += Dir['lib/**/*.rb']
|
26
|
+
s.files += Dir['bin/**/*.rb']
|
27
|
+
s.files += Dir['tools/**/*.rb']
|
28
|
+
s.files += Dir['manpages/*.1']
|
29
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
# Copyright © 2008-2010 Diego Elio Pettenò <flameeyes@flameeyes.eu>
|
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,340 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
# Copyright © 2007-2010 Diego Elio Pettenò <flameeyes@flameeyes.eu>
|
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
|
+
require 'elf/tools'
|
30
|
+
|
31
|
+
module Elf::Tools
|
32
|
+
class CollisionsHarvester < Elf::Tool
|
33
|
+
def self.initialize
|
34
|
+
super
|
35
|
+
# this script doesn't support running multithreaded, since we
|
36
|
+
# need synchronous access to the database itself, so for now
|
37
|
+
# simply make sure that it never is executed with threads.
|
38
|
+
@execution_threads = nil
|
39
|
+
|
40
|
+
@options |= [
|
41
|
+
["--no-scan-ldpath", "-L", GetoptLong::NO_ARGUMENT ],
|
42
|
+
["--scan-path", "-p", GetoptLong::NO_ARGUMENT ],
|
43
|
+
["--suppressions", "-s", GetoptLong::REQUIRED_ARGUMENT ],
|
44
|
+
["--multimplementations", "-m", GetoptLong::REQUIRED_ARGUMENT ],
|
45
|
+
["--elf-machine", "-M", GetoptLong::REQUIRED_ARGUMENT ],
|
46
|
+
["--postgres-username", "-U", GetoptLong::REQUIRED_ARGUMENT ],
|
47
|
+
["--postgres-password", "-P", GetoptLong::REQUIRED_ARGUMENT ],
|
48
|
+
["--postgres-hostname", "-H", GetoptLong::REQUIRED_ARGUMENT ],
|
49
|
+
["--postgres-port", "-T", GetoptLong::REQUIRED_ARGUMENT ],
|
50
|
+
["--postgres-database", "-D", GetoptLong::REQUIRED_ARGUMENT ],
|
51
|
+
["--output", "-o", GetoptLong::REQUIRED_ARGUMENT ],
|
52
|
+
["--analyze-only", "-A", GetoptLong::NO_ARGUMENT ],
|
53
|
+
]
|
54
|
+
|
55
|
+
# we remove the -R option since we always want to be recursive in our search
|
56
|
+
@options.delete_if { |opt| opt[1] == "-R" }
|
57
|
+
@recursive = true
|
58
|
+
@analyze_only = false
|
59
|
+
|
60
|
+
@suppression_files = File.exist?('suppressions') ? [ 'suppressions' ] : []
|
61
|
+
@multimplementation_files = File.exist?('multimplementations') ? [ 'multimplementations' ] : []
|
62
|
+
|
63
|
+
# Total suppressions are for directories to skip entirely
|
64
|
+
# Partial suppressions are the ones that apply only to a subset
|
65
|
+
# of symbols.
|
66
|
+
@total_suppressions = []
|
67
|
+
@partial_suppressions = []
|
68
|
+
|
69
|
+
@multimplementations = []
|
70
|
+
|
71
|
+
@output = "collisions.log"
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.suppressions_cb(arg)
|
75
|
+
unless File.exist? arg
|
76
|
+
puterror("no such file or directory - #{arg}")
|
77
|
+
exit -1
|
78
|
+
end
|
79
|
+
@suppression_files << arg
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.multimplementations_cb(arg)
|
83
|
+
unless File.exist? arg
|
84
|
+
puterror("no such file or directory - #{arg}")
|
85
|
+
exit -1
|
86
|
+
end
|
87
|
+
@multimplementation_files << arg
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.elf_machine_cb(arg)
|
91
|
+
machine_str = (arg[0..2].upcase == "EM_" ? arg[3..-1] : arg).delete("_")
|
92
|
+
machine_val = Elf::Machine.from_string(machine_str)
|
93
|
+
|
94
|
+
if machine_val.nil?
|
95
|
+
puterror("unknown machine string - #{arg}")
|
96
|
+
else
|
97
|
+
@machines ||= []
|
98
|
+
@machines << machine_val
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def self.after_options
|
103
|
+
pg_params = {
|
104
|
+
:dbname => @postgres_database,
|
105
|
+
:host => @postgres_hostname,
|
106
|
+
:password => @postgres_password,
|
107
|
+
:port => @postgres_port,
|
108
|
+
:user => @postgres_username,
|
109
|
+
}
|
110
|
+
|
111
|
+
@suppression_files.each do |suppression|
|
112
|
+
File.open(suppression) do |file|
|
113
|
+
file.each_line do |line|
|
114
|
+
path, symbols = line.
|
115
|
+
gsub(/#\s.*/, '').
|
116
|
+
strip.
|
117
|
+
split(/\s+/, 2)
|
118
|
+
|
119
|
+
next unless path
|
120
|
+
|
121
|
+
if not symbols or symbols == ""
|
122
|
+
@total_suppressions << Regexp.new(path)
|
123
|
+
else
|
124
|
+
@partial_suppressions << [Regexp.new(path), Regexp.new(symbols)]
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
@total_suppressions = Regexp.union(@total_suppressions)
|
131
|
+
|
132
|
+
@multimplementation_files.each do |multimplementation|
|
133
|
+
@multimplementations |= \
|
134
|
+
File.read(multimplementation).split(/\r?\n/).collect do |line|
|
135
|
+
implementation, paths = line.
|
136
|
+
gsub(/#\s.*/, '').
|
137
|
+
strip.
|
138
|
+
split(/\s+/, 2)
|
139
|
+
|
140
|
+
next if (implementation.nil? or paths.nil?)
|
141
|
+
|
142
|
+
[ implementation, Regexp.new(paths) ]
|
143
|
+
end
|
144
|
+
end
|
145
|
+
@multimplementations.delete_if { |x| x.nil? }
|
146
|
+
|
147
|
+
@targets |=
|
148
|
+
( !@no_scan_ldpath ? Elf::Utilities.system_library_path : [] ) |
|
149
|
+
( (@scan_path and ENV['PATH']) ? ENV['PATH'].split(":") : [] )
|
150
|
+
|
151
|
+
return if @analyse_only
|
152
|
+
|
153
|
+
@db = PGconn.open(pg_params)
|
154
|
+
|
155
|
+
@db.exec(<<EOF)
|
156
|
+
BEGIN TRANSACTION;
|
157
|
+
|
158
|
+
DROP TABLE IF EXISTS symbols, multimplementations, objects CASCADE;
|
159
|
+
DROP EXTENSION IF EXISTS plpgsql CASCADE;
|
160
|
+
|
161
|
+
CREATE EXTENSION plpgsql;
|
162
|
+
CREATE TABLE objects (
|
163
|
+
id SERIAL PRIMARY KEY,
|
164
|
+
name VARCHAR(4096),
|
165
|
+
abi VARCHAR(255),
|
166
|
+
exported BOOLEAN,
|
167
|
+
UNIQUE(name, abi)
|
168
|
+
);
|
169
|
+
CREATE TABLE multimplementations (
|
170
|
+
id INTEGER REFERENCES objects(id) ON DELETE CASCADE,
|
171
|
+
path VARCHAR(4096),
|
172
|
+
UNIQUE(path)
|
173
|
+
);
|
174
|
+
CREATE TABLE symbols (
|
175
|
+
object INTEGER REFERENCES objects(id) ON DELETE CASCADE,
|
176
|
+
symbol TEXT,
|
177
|
+
PRIMARY KEY(object, symbol)
|
178
|
+
);
|
179
|
+
|
180
|
+
CREATE VIEW symbol_count AS
|
181
|
+
SELECT symbol, abi, COUNT(*) AS occurrences, BOOL_OR(objects.exported) AS exported
|
182
|
+
FROM symbols INNER JOIN objects ON symbols.object = objects.id GROUP BY symbol, abi;
|
183
|
+
CREATE VIEW duplicate_symbols AS
|
184
|
+
SELECT * FROM symbol_count
|
185
|
+
WHERE occurrences > 1 AND exported = 't'
|
186
|
+
ORDER BY occurrences DESC, symbol ASC;
|
187
|
+
|
188
|
+
PREPARE getinstances (text, text) AS
|
189
|
+
SELECT name FROM symbols INNER JOIN objects ON symbols.object = objects.id
|
190
|
+
WHERE symbol = $1 AND abi = $2 ORDER BY name;
|
191
|
+
|
192
|
+
CREATE FUNCTION implementation (
|
193
|
+
p_implementation TEXT,
|
194
|
+
p_abi TEXT,
|
195
|
+
p_exported BOOLEAN,
|
196
|
+
OUT implementation_id INTEGER,
|
197
|
+
OUT created BOOLEAN
|
198
|
+
) AS $$
|
199
|
+
BEGIN
|
200
|
+
SELECT INTO implementation_id id FROM objects
|
201
|
+
WHERE name = p_implementation AND abi = p_abi;
|
202
|
+
|
203
|
+
IF implementation_id IS NULL THEN
|
204
|
+
created := TRUE;
|
205
|
+
|
206
|
+
INSERT INTO objects(name, abi, exported)
|
207
|
+
VALUES(p_implementation, p_abi, p_exported);
|
208
|
+
SELECT INTO implementation_id
|
209
|
+
currval(pg_get_serial_sequence('objects', 'id'));
|
210
|
+
END IF;
|
211
|
+
END;
|
212
|
+
$$ LANGUAGE 'plpgsql';
|
213
|
+
|
214
|
+
CREATE FUNCTION multimplementation (
|
215
|
+
p_id INTEGER,
|
216
|
+
p_filepath TEXT
|
217
|
+
) RETURNS BOOLEAN AS $$
|
218
|
+
BEGIN
|
219
|
+
INSERT INTO multimplementations (id, path) VALUES(p_id, p_filepath);
|
220
|
+
RETURN 't';
|
221
|
+
EXCEPTION
|
222
|
+
WHEN unique_violation THEN
|
223
|
+
RETURN 'f';
|
224
|
+
END;
|
225
|
+
$$ LANGUAGE 'plpgsql';
|
226
|
+
|
227
|
+
CREATE FUNCTION symbol (
|
228
|
+
p_object INTEGER,
|
229
|
+
p_symbol TEXT
|
230
|
+
) RETURNS VOID AS '
|
231
|
+
BEGIN
|
232
|
+
INSERT INTO symbols VALUES(p_object, p_symbol);
|
233
|
+
RETURN;
|
234
|
+
EXCEPTION
|
235
|
+
WHEN unique_violation THEN
|
236
|
+
RETURN;
|
237
|
+
END;
|
238
|
+
' LANGUAGE 'plpgsql';
|
239
|
+
|
240
|
+
COMMIT;
|
241
|
+
EOF
|
242
|
+
end
|
243
|
+
|
244
|
+
def self.db_exec(query)
|
245
|
+
@db.exec(query)
|
246
|
+
end
|
247
|
+
|
248
|
+
def self.analysis(filename)
|
249
|
+
return if filename =~ @total_suppressions
|
250
|
+
|
251
|
+
begin
|
252
|
+
Elf::File.open(filename) do |elf|
|
253
|
+
unless ($machines.nil? or $machines.include?(elf.machine)) and
|
254
|
+
(elf.has_section?('.dynsym') and elf.has_section?('.dynstr') and
|
255
|
+
elf.has_section?('.dynamic')) and
|
256
|
+
(elf[".dynsym"].class == Elf::SymbolTable)
|
257
|
+
return
|
258
|
+
end
|
259
|
+
|
260
|
+
local_suppressions = Regexp.union((@partial_suppressions.dup.delete_if{ |s| filename.to_s !~ s[0] }).collect { |s| s[1] })
|
261
|
+
|
262
|
+
name = filename
|
263
|
+
abi = "#{elf.elf_class} #{elf.abi} #{elf.machine}".gsub("'", "''")
|
264
|
+
|
265
|
+
@multimplementations.each do |implementation, paths|
|
266
|
+
# Get the full matchdata because we might need to get the matches.
|
267
|
+
match = paths.match(filename)
|
268
|
+
|
269
|
+
next unless match
|
270
|
+
|
271
|
+
while implementation =~ /\$([0-9]+)/ do
|
272
|
+
match_idx = $1.to_i
|
273
|
+
replacement = match[match_idx]
|
274
|
+
replacement = "" if replacement.nil?
|
275
|
+
implementation = implementation.gsub("$#{match_idx}", replacement)
|
276
|
+
end
|
277
|
+
|
278
|
+
name = implementation
|
279
|
+
break
|
280
|
+
end
|
281
|
+
|
282
|
+
shared = (filename != name) || (elf['.dynamic'].soname != nil)
|
283
|
+
|
284
|
+
res = db_exec("SELECT * FROM implementation('#{name}', '#{abi}', '#{shared}')")
|
285
|
+
impid = res[0]["implementation_id"]
|
286
|
+
|
287
|
+
if filename != name
|
288
|
+
# If this is a collapsed multimplementation, add it to the list
|
289
|
+
res = db_exec("SELECT multimplementation(#{impid}, '#{filename}') AS created");
|
290
|
+
end
|
291
|
+
|
292
|
+
# skip over the file if we already visited it (either directly
|
293
|
+
# or as a multimplementation.
|
294
|
+
next if res[0]["created"] != "t"
|
295
|
+
|
296
|
+
query = ""
|
297
|
+
elf['.dynsym'].each do |sym|
|
298
|
+
begin
|
299
|
+
next if sym.idx == 0 or
|
300
|
+
sym.bind != Elf::Symbol::Binding::Global or
|
301
|
+
sym.section.nil? or
|
302
|
+
sym.value == 0 or
|
303
|
+
sym.section.is_a? Integer or
|
304
|
+
sym.section.name == '.init' or
|
305
|
+
sym.section.name == '.bss'
|
306
|
+
|
307
|
+
next if (sym.name =~ local_suppressions);
|
308
|
+
|
309
|
+
query << "SELECT symbol('#{impid}', '#{sym.name}@#{sym.version}');"
|
310
|
+
rescue Exception
|
311
|
+
$stderr.puts "Mangling symbol #{sym.name}"
|
312
|
+
raise
|
313
|
+
end
|
314
|
+
end
|
315
|
+
db_exec("BEGIN TRANSACTION;" + query + "COMMIT;") unless query.empty?
|
316
|
+
end
|
317
|
+
rescue Exception => e
|
318
|
+
putnotice "#{filename}: #{e.message}"
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
def self.results
|
323
|
+
db_exec(<<EOF)
|
324
|
+
BEGIN TRANSACTION;
|
325
|
+
CREATE INDEX objects_name ON objects(name);
|
326
|
+
CREATE INDEX symbols_symbol ON symbols(symbol);
|
327
|
+
COMMIT;
|
328
|
+
EOF
|
329
|
+
|
330
|
+
outfile = File.new(@output, "w")
|
331
|
+
|
332
|
+
db_exec("SELECT * FROM duplicate_symbols").each do |row|
|
333
|
+
outfile.puts "Symbol #{row['symbol']} (#{row['abi']}) present #{row['occurrences']} times"
|
334
|
+
db_exec( "EXECUTE getinstances ('#{row['symbol']}', '#{row['abi'].gsub("'", "''")}')" ).each do |path|
|
335
|
+
outfile.puts " #{path['name']}"
|
336
|
+
end
|
337
|
+
end
|
338
|
+
end
|
339
|
+
end
|
340
|
+
end
|