#! /usr/bin/python import sys,os import re from popen2 import popen2 import string import getopt symbol={} graph = {} def sname(addr): try: s="%s" % symbol[addr] except KeyError: if graph.has_key(addr) and symbol.has_key(graph[addr]): s="call %s" % symbol[graph[addr]] else: try: s=hex(addr) except: print "???:",addr return addr return s class Func: def __init__(self, name, addr): self.calls = {} self.jumps = {} self.parts = [] self.name = name self.start = addr self.end = 0 self.dist=9999999 def add_jump(self, addr, dest): self.jumps[addr] = dest self.parts.append(addr) def add_call(self, addr, dest): self.calls[addr] = dest self.parts.append(addr) def add_part(self, name): self.parts.append(name) def compile(self): try: self.parts.index(self.start) except ValueError: self.parts.append(self.start) try: self.parts.index(self.end) except ValueError: self.parts.append(self.end) self.parts.sort() def point(self, addr): # if self.calls.has_key(addr): # n = '"%s" [label = "call %s"]' % (sname(addr), sname(self.calls[addr])) # else: n = '"%s"' % sname(addr) return n def subgraph(self): aliases = string.join(map(lambda x: '"%s" [label = "call %s"];' % (sname(x[0]), sname(x[1])),self.calls.items())) chain = reduce(lambda x,y,self=self: x+' -> \n'+self.point(y), self.parts[1:], self.point(self.parts[0])) return 'subgraph "cluster_%s" { label="%s %i"; style=filled; %s %s; }\n' % (self.name,self.name,self.dist, aliases, chain) def usage(): print "%s [-d depth] [-r root] -i infile" % sys.argv[0] def main(): try: infile = "" depth = -1 radius = -1 entrypoint=None uniq = 0 do_calls = 1 do_jumps = 1 opts=getopt.getopt(sys.argv[1:],"i:e:d:r:ucj") for opt, parm in opts[0]: if opt == "-i": infile = parm elif opt == "-e": try: entrypoint = long(parm,16) except ValueError: entrypoint = parm elif opt == "-u": uniq = 1 elif opt == "-c": do_calls = 0 elif opt == "-j": do_jumps = 0 elif opt == "-d": try: depth = int(parm) except ValueError,str: raise getopt.error,(("bad depth: %s" % str), None) elif opt == "-r": try: radius = int(parm) except ValueError,str: raise getopt.error,(("bad radius: %s" % str), None) if not infile: raise getopt.error, ("missing infile", None) except getopt.error,str: print "Error:", str print usage() sys.exit(0) out,inf=popen2("readelf -l %s" % infile) findentry=re.compile("Entry point 0x([0-9a-f]{1,8})") elfentrypoint=-1 l="x" while l: l = out.readline() m = findentry.search(l) if m: elfentrypoint = long(m.expand("\\1"),16) print "Found ELF entrypoint :", sname(elfentrypoint) break # Read symbols dynsymb=re.compile("^([0-9a-f]{8}).* ([^ ]+)$") out,inf=popen2("objdump -t -T %s" % infile) print "################ Read symbols" l="x" while l: l = out.readline() m = dynsymb.search(l) if m: addr = long(m.expand("\\1"),16) symb = m.expand("\\2")[:-1] print "%s is at %x" % (symb,addr) symbol[addr]=symb # Read asm code print "################ Read asm" out,inf=popen2("objdump -j .text -d %s" % infile) symb=re.compile("^([0-9a-f]{8}) <(.*)>:$") #FIXME: dest addr can be != than 7 digits callfunc=re.compile("^([ 0-9a-f]{8}):.*call *(0x)?([0-9a-f]{1,8})") jump=re.compile("^([ 0-9a-f]{8}):.*(j[a-z]{1,2}) *(0x)?([0-9a-f]{1,8})") ret=re.compile("^([ 0-9a-f]{8}):.*ret") anyaddr=re.compile("^([ 0-9a-f]{7}[048c]):.*") current = None last_addr = 0 flist = [] l="x" while l: l=out.readline() if not current: m = anyaddr.search(l) if m: print l addr = long(m.expand("\\1"),16) current = Func(sname(addr), addr) flist.append(current) print "new func", sname(addr) m = symb.search(l) if m: addr = long(m.expand("\\1"),16) sym = m.expand("\\2") if current and not current.end: current.end = last_addr current = Func(sym,addr) flist.append(current) last_addr = addr print "New func: %s" % sym continue m = callfunc.search(l) if m and do_calls: addr = long(m.expand("\\1"),16) dest = long(m.expand("\\3"),16) current.add_call(addr,dest) print "--> ",sname(addr),"call", sname(dest) last_addr = addr continue m = jump.search(l) if m and do_jumps: addr = long(m.expand("\\1"),16) jmptype = m.expand("\\2") dest = long(m.expand("\\4"),16) current.add_jump(addr,dest) print "--> ",sname(addr),jmptype, sname(dest) m = ret.search(l) if m: addr = long(m.expand("\\1"),16) current.end = addr current = None last_addr = addr print "ret", sname(addr) continue m = anyaddr.search(l) if m: last_addr = long(m.expand("\\1"),16) print "####### Making the graph" if current: current.end = last_addr for f in flist: for src,dst in f.calls.items()+f.jumps.items(): for d in flist: if d.start <= dst and dst <= d.end: try: d.parts.index(dst) except ValueError: d.parts.append(dst) break for f in flist: f.compile() for f in flist: for p in f.parts: graph[p] = f graph[f.start] = f # for f in flist: # f.calls = filter(lambda x, graph=graph: graph.has_key(x[1]),f.calls) # f.jumps = filter(lambda x, graph=graph: graph.has_key(x[1]),f.jumps) for f in flist: print f.name,":", sname(f.start),"->", sname(f.end), map(lambda x: sname(x[1]),f.calls.items()) for ent in [entrypoint, "main", elfentrypoint, 0x08049420, "init", "_init", ".text", flist[0].start]: if graph.has_key(ent): entrypoint=ent break print "Entrypoint used is : ",entrypoint # if depth >= 0: # Func.selected = 0 # global select # def select(cur, n, depth=depth, graph=graph): # f = graph[cur] # if f.selected: # return # f.selected = 1 # if n < depth: # if do_calls: # for g in f.calls: # select(g[1],n+1) # if do_jumps: # # for g in f.calls: # select(g[1],n+1) # # else: # f.calls=[] # f.jumps=[] # # select(entrypoint,0) # # fs=graph.keys()[:] # # for f in fs: # if not graph[f].selected: # del(graph[f]) if radius >= 0: Func.dist=9999999 graph[entrypoint].dist=0 for i in range(radius): for f in flist: for ad,gad in f.calls.items()+f.jumps.items(): g=graph[gad] f.dist = min(f.dist, g.dist+1) g.dist = min(g.dist, f.dist+1) for f in flist: f.calls = filter(lambda x,graph=graph, radius=radius: graph[x[1]].dist <= radius, f.calls) f.jumps = filter(lambda x,graph=graph, radius=radius: graph[x[1]].dist <= radius, f.jumps) glist = flist flist = [] for f in glist: if f.dist <= radius: flist.append(f) outfile=os.tempnam() print "temp file is %s" % outfile fd=open(outfile,"w") fd.write('digraph "%s" {\n' % infile.split("/")[-1]) for f in flist: fd.write(f.subgraph()) for f in flist: for src,dst in f.calls.items(): if not graph.has_key(dst): print "orphaned call [%s]. Deleted." % sname(dst) del(f.calls[src]) for src,dst in f.jumps.items(): if not graph.has_key(dst): print "orphaned jump [%s]. Deleted." % sname(dst) del(f.jumps[src]) for f in flist: for src,dst in f.calls.items(): fd.write('"%s" -> "%s";\n' % (sname(src),sname(dst))) try: fd.write('"%s" -> "%s";\n' % (sname(graph[dst].end),sname(src))) except: print "error: %s not found" % sname(dst) pass for src,dst in f.jumps.items(): fd.write('"%s" -> "%s";\n' % (sname(src),sname(dst))) fd.write("}\n") fd.close() os.system("dotty %s" % outfile) try: main() except: import pdb (type, value, tb) = sys.exc_info() print tb raise type,value,tb pdb.post_mortem(tb)