Skip to content

Instantly share code, notes, and snippets.

@astarasikov
Last active August 29, 2015 14:21
Show Gist options
  • Save astarasikov/355bf825f130fe4b5633 to your computer and use it in GitHub Desktop.
Save astarasikov/355bf825f130fe4b5633 to your computer and use it in GitHub Desktop.
Visualize ELF dependency graph in Graphviz dot
#!/usr/bin/env ruby2.1
#@bin_dir="/home/me/bin/foo"
@bin_dir = ""
# context = {
# :exports => {
# "foo" => [
# "file2.ois",
# "file3.ois",
# ]
# }
# :imports => {
# "file1.ois" => [
# "foo",
# "bar"
# ]
# }
# }
#
def is_ignored_symbol(symname)
return true if symname.start_with? '.'
false
end
def store_export(ctx, filename, symname)
## strip local prefix
filename = filename.sub(@bin_dir, "")
ctx[:exports] ||= {}
ctx[:exports][symname] ||= []
ctx[:exports][symname].push(filename)
end
def store_import(ctx, filename, symname)
## strip local prefix
filename = filename.sub(@bin_dir, "")
ctx[:imports] ||= {}
ctx[:imports][filename] ||= []
ctx[:imports][filename].push(symname)
end
def parse_objdump_output(ctx, filename, objdump_output)
found_symtab = false
objdump_output.split("\n").each do |line|
if not found_symtab then
if line.start_with? "SYMBOL TABLE:" then
found_symtab = true
end
next
end
## empty line means we've reached the end of the table
break if line == ""
break if line == "no symbols"
columns = line.split(" ")
next if columns.length < 2
addr = columns[columns.length - 2]
symname = columns[columns.length - 1]
next if is_ignored_symbol(symname)
if (addr.to_i(16) == 0) then
# zero address is import
store_import(ctx, filename, symname)
else
# non-zero address is export
store_export(ctx, filename, symname)
end
#print "#{addr} => #{symname}\n"
end
end
def add_file(ctx, file)
begin
objdump_output = `objdump -t #{file}`
parse_objdump_output(ctx, file, objdump_output)
rescue Exception => err
puts "add_file: #{err}"
end
end
def scan_dir(ctx, path)
Dir.foreach(path) do |item|
begin
next if item == '.' or item == '..'
file_path = path + '/' + item
add_file(ctx, file_path) if File.file? file_path
scan_dir(ctx, file_path) if File.directory? file_path
rescue Exception => err
puts "scan_dir: #{err}"
end
end
end
def print_dependency_link(from, to, by_symname)
print "\"#{from}\" -> \"#{to}\" [label=\"#{by_symname}\"]\n"
end
def print_dependency_grap(ctx)
imports = ctx[:imports]
return if nil == imports
print "digraph G {\n"
imports.each_key do |objname|
obj_data = imports[objname]
next if nil == obj_data
obj_data.each do |symname|
candidate_exports = ctx[:exports][symname]
if nil == candidate_exports then
print_dependency_link(objname, "UNDEFINED", symname)
next
end
candidate_exports.each do |tgt_objname|
print_dependency_link(objname, tgt_objname, symname)
end
end
end
print "}\n"
end
@bin_dir = ARGV[0]
@ctx = {}
scan_dir(@ctx, @bin_dir)
print_dependency_grap(@ctx)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment