#!/usr/bin/python """ADV - ADV is a Dependency Viewer A small python script to visualize dependencies between object files. Usage: adv [options] [libraries]+ Options: -h, --help display this help -v, --verbose be more verbose. Can be repeated several time to increase the verbosity level -b, --boxed keep objects of the same lib inside a box -p, --cross-prefix adv uses nm and ar to inspect libraries and objects. When working on cross-compiled libraries, you can specify the tools' prefix eg. arm-linux-""" import sys, getopt, os, re from subprocess import Popen, PIPE __author__ = "Damien Lespiau " __version__ = "0.1" __date__ = "20081010" __copyright__ = "Copyright (C) 2008 Damien Lespiau" __license__ = "GPL v3" _verbose = 0 _cross_prefix = "" class Color: color_scheme = ('pastel19', 9) index = 1 # we actually start with the second color def __init__(self): pass def pick(): Color.index += 1 if Color.index > Color.color_scheme[1]: Color.index = 1 return ("style=filled, colorscheme=%s, fillcolor=%d, color=%d" % (Color.color_scheme[0], Color.index, Color.index)) pick = staticmethod(pick) class Node: def __init__(self, name, deps): self.node_name = name self.node_deps = deps def node_emit(self, indent=1): tabs = "\t" * indent for dep in self.node_deps: print "%s\"%s\"->\"%s\";" % (tabs, self.node_name, dep.node_name) class Cluster: id_prefix = ('', 'cluster_') def __init__(self, name, nodes): self.cluster_id = sanitize_name(name) self.cluster_name = name self.cluster_nodes = nodes def cluster_emit(self, indent=1, boxed=0): tabs = "\t" * indent print "%ssubgraph %s%s {" % (tabs, Cluster.id_prefix[boxed], self.cluster_id) tabs = "\t" * (indent + 1) if not boxed: color_attr = Color.pick() print "%snode [%s]" % (tabs, color_attr); for node in self.cluster_nodes: node.node_emit(indent + 1) print "%slabel = \"%s\";" % (tabs, self.cluster_name) tabs = "\t" * indent print "%s}" % tabs class Graph: def __init__(self, name): self.clusters = [] self.nodes = [] self.name = name def add_cluster(self, cluster): self.clusters.append(cluster) def add_node(self, node): self.nodes.append(node) def emit(self, boxed=0): print "digraph %s {" % self.name for cluster in self.clusters: cluster.cluster_emit(boxed=boxed) for node in self.nodes: node.node_emit() print '}' class Symbol: def __init__(self, name, visibility): self.name = name self.visibility = visibility def __str__(self): return self.visibility + " " + self.name class Object(Node): def __init__(self, name, size): self.name = name self.size = size self.symbols = [] self.undefs = [] self.dependencies = [] Node.__init__(self, self.get_human_name() + ' (' + self.get_human_size() + ')', self.dependencies) def add_symbol(self, symbol): if symbol.visibility == 'U': self.undefs.append(symbol) else: self.symbols.append(symbol) def has_symbol(self, name): for symbol in self.symbols: if symbol.name == name: return True return False def add_dependency(self, object): if object not in self.dependencies: verbose(3, "%s -> %s" % (self.name, object.name)) self.dependencies.append(object) def get_human_size(self): return "%d KiB" % (self.size / 1024) def get_human_name(self): return libtool_unmangle(self.name) def __str__(self): return (self.name + "(" + self.size + ")" + "\n " + "\n ".join([str(sym) for sym in self.symbols])) class Archive(Cluster, Node): def __init__(self, file): self.file = file self.name = os.path.basename(file) self.objects = [] Cluster.__init__(self, self.name, self.objects) def add_object(self, object): self.objects.append(object) def get_object_size(self, object): global _cross_prefix pipe = Popen(_cross_prefix + 'ar tv ' + self.file, shell=True, stdout=PIPE) stdout, stderr = pipe.communicate() re_size = re.compile(r'.* [0-9]+/[0-9]+ *([0-9]+) .* ' + object) for line in stdout.splitlines(): found_obj = re_size.match(line) if found_obj: return int(found_obj.group(1)) return 0 def populate(self): global _cross_prefix verbose(1, "Analysing %s" % self.name) pipe = Popen(_cross_prefix + 'nm ' + self.file, shell=True, stdout=PIPE) stdout, stderr = pipe.communicate() re_object = re.compile('(.*):') re_symbol = re.compile('([0-9a-f]+) T (.*)') re_undef = re.compile(r'[ \t]+U (.*)') for line in stdout.splitlines(): found_obj = re_object.match(line) if found_obj: obj_name = found_obj.group(1) obj_size = self.get_object_size(obj_name) obj = Object(obj_name, obj_size) self.add_object(obj) continue found_sym = re_symbol.match(line) if found_sym: sym_name = found_sym.group(2) sym = Symbol(sym_name, 'T') obj.add_symbol(sym) continue found_undef = re_undef.match(line) if found_undef: sym_name = found_undef.group(1) sym = Symbol(sym_name, 'U') obj.add_symbol(sym) class Solver: def __init__(self, archives): self.archives = archives self.symtab = {} # construct the symbol -> object dict for archive in self.archives: for object in archive.objects: for symbol in object.symbols: self.symtab[symbol.name] = object def solve_symbol(self, from_object, symbol): if self.symtab.has_key(symbol.name): from_object.add_dependency(self.symtab[symbol.name]) def solve_object(self, object): verbose(2, "Solving %s dependencies" % object.name) for symbol in object.undefs: self.solve_symbol(object, symbol) def solve_archive(self, archive): verbose(1, "Soving %s dependencies" % archive.name) for object in archive.objects: self.solve_object(object) def run(self): for archive in self.archives: self.solve_archive(archive) def __str__(self): return (self.name + "\n " + "\n ".join([str(obj) for obj in self.objects])) def libtool_unmangle(name): expr = re.compile('[0-9a-zA-Z_]+_la-(.*)$') found_ltname = expr.match(name) if found_ltname: return found_ltname.group(1) return name def sanitize_name(name): expr = re.compile('[^0-9a-zA-Z]') return expr.sub('_', name) def verbose(level, msg): global _verbose if _verbose >= level: print "// [INFO] " + msg def usage(exit_value): print __doc__ sys.exit(exit_value) def main(argv): archives = [] opt_boxed = 0 try: opts, args = getopt.getopt(argv, "hvp:b", ["help", "verbose", "cross-prefix=", "boxed"]) except getopt.GetoptError: usage(1) for opt, arg in opts: if opt in ("-h", "--help"): usage(0) elif opt in ("-v", "--verbose"): global _verbose _verbose += 1 elif opt in ("-p", "--cross-prefix"): global _cross_prefix verbose(1, "Using %s as cross prefix" % arg) _cross_prefix = arg elif opt in ("-b", "--boxed"): opt_boxed = 1 for file in args: if not os.access(file, os.F_OK): continue archive = Archive(file) archive.populate() # add the archive if we could parse at least one object if len(archive.objects) > 0: archives.append(archive) if len(archives) == 0: usage(1) s = Solver(archives) s.run() # for archive in archives: # print str(archive) # build the graph graph = Graph('G') for archive in archives: graph.add_cluster(archive) graph.emit(boxed=opt_boxed) if __name__ == "__main__": main(sys.argv[1:])