1#! /usr/bin/env python 2 3# objgraph 4# 5# Read "nm -o" input (on IRIX: "nm -Bo") of a set of libraries or modules 6# and print various interesting listings, such as: 7# 8# - which names are used but not defined in the set (and used where), 9# - which names are defined in the set (and where), 10# - which modules use which other modules, 11# - which modules are used by which other modules. 12# 13# Usage: objgraph [-cdu] [file] ... 14# -c: print callers per objectfile 15# -d: print callees per objectfile 16# -u: print usage of undefined symbols 17# If none of -cdu is specified, all are assumed. 18# Use "nm -o" to generate the input (on IRIX: "nm -Bo"), 19# e.g.: nm -o /lib/libc.a | objgraph 20 21 22import sys 23import os 24import getopt 25import re 26 27# Types of symbols. 28# 29definitions = 'TRGDSBAEC' 30externals = 'UV' 31ignore = 'Nntrgdsbavuc' 32 33# Regular expression to parse "nm -o" output. 34# 35matcher = re.compile('(.*):\t?........ (.) (.*)$') 36 37# Store "item" in "dict" under "key". 38# The dictionary maps keys to lists of items. 39# If there is no list for the key yet, it is created. 40# 41def store(dict, key, item): 42 if dict.has_key(key): 43 dict[key].append(item) 44 else: 45 dict[key] = [item] 46 47# Return a flattened version of a list of strings: the concatenation 48# of its elements with intervening spaces. 49# 50def flat(list): 51 s = '' 52 for item in list: 53 s = s + ' ' + item 54 return s[1:] 55 56# Global variables mapping defined/undefined names to files and back. 57# 58file2undef = {} 59def2file = {} 60file2def = {} 61undef2file = {} 62 63# Read one input file and merge the data into the tables. 64# Argument is an open file. 65# 66def readinput(fp): 67 while 1: 68 s = fp.readline() 69 if not s: 70 break 71 # If you get any output from this line, 72 # it is probably caused by an unexpected input line: 73 if matcher.search(s) < 0: s; continue # Shouldn't happen 74 (ra, rb), (r1a, r1b), (r2a, r2b), (r3a, r3b) = matcher.regs[:4] 75 fn, name, type = s[r1a:r1b], s[r3a:r3b], s[r2a:r2b] 76 if type in definitions: 77 store(def2file, name, fn) 78 store(file2def, fn, name) 79 elif type in externals: 80 store(file2undef, fn, name) 81 store(undef2file, name, fn) 82 elif not type in ignore: 83 print fn + ':' + name + ': unknown type ' + type 84 85# Print all names that were undefined in some module and where they are 86# defined. 87# 88def printcallee(): 89 flist = file2undef.keys() 90 flist.sort() 91 for filename in flist: 92 print filename + ':' 93 elist = file2undef[filename] 94 elist.sort() 95 for ext in elist: 96 if len(ext) >= 8: 97 tabs = '\t' 98 else: 99 tabs = '\t\t' 100 if not def2file.has_key(ext): 101 print '\t' + ext + tabs + ' *undefined' 102 else: 103 print '\t' + ext + tabs + flat(def2file[ext]) 104 105# Print for each module the names of the other modules that use it. 106# 107def printcaller(): 108 files = file2def.keys() 109 files.sort() 110 for filename in files: 111 callers = [] 112 for label in file2def[filename]: 113 if undef2file.has_key(label): 114 callers = callers + undef2file[label] 115 if callers: 116 callers.sort() 117 print filename + ':' 118 lastfn = '' 119 for fn in callers: 120 if fn <> lastfn: 121 print '\t' + fn 122 lastfn = fn 123 else: 124 print filename + ': unused' 125 126# Print undefined names and where they are used. 127# 128def printundef(): 129 undefs = {} 130 for filename in file2undef.keys(): 131 for ext in file2undef[filename]: 132 if not def2file.has_key(ext): 133 store(undefs, ext, filename) 134 elist = undefs.keys() 135 elist.sort() 136 for ext in elist: 137 print ext + ':' 138 flist = undefs[ext] 139 flist.sort() 140 for filename in flist: 141 print '\t' + filename 142 143# Print warning messages about names defined in more than one file. 144# 145def warndups(): 146 savestdout = sys.stdout 147 sys.stdout = sys.stderr 148 names = def2file.keys() 149 names.sort() 150 for name in names: 151 if len(def2file[name]) > 1: 152 print 'warning:', name, 'multiply defined:', 153 print flat(def2file[name]) 154 sys.stdout = savestdout 155 156# Main program 157# 158def main(): 159 try: 160 optlist, args = getopt.getopt(sys.argv[1:], 'cdu') 161 except getopt.error: 162 sys.stdout = sys.stderr 163 print 'Usage:', os.path.basename(sys.argv[0]), 164 print '[-cdu] [file] ...' 165 print '-c: print callers per objectfile' 166 print '-d: print callees per objectfile' 167 print '-u: print usage of undefined symbols' 168 print 'If none of -cdu is specified, all are assumed.' 169 print 'Use "nm -o" to generate the input (on IRIX: "nm -Bo"),' 170 print 'e.g.: nm -o /lib/libc.a | objgraph' 171 return 1 172 optu = optc = optd = 0 173 for opt, void in optlist: 174 if opt == '-u': 175 optu = 1 176 elif opt == '-c': 177 optc = 1 178 elif opt == '-d': 179 optd = 1 180 if optu == optc == optd == 0: 181 optu = optc = optd = 1 182 if not args: 183 args = ['-'] 184 for filename in args: 185 if filename == '-': 186 readinput(sys.stdin) 187 else: 188 readinput(open(filename, 'r')) 189 # 190 warndups() 191 # 192 more = (optu + optc + optd > 1) 193 if optd: 194 if more: 195 print '---------------All callees------------------' 196 printcallee() 197 if optu: 198 if more: 199 print '---------------Undefined callees------------' 200 printundef() 201 if optc: 202 if more: 203 print '---------------All Callers------------------' 204 printcaller() 205 return 0 206 207# Call the main program. 208# Use its return value as exit status. 209# Catch interrupts to avoid stack trace. 210# 211if __name__ == '__main__': 212 try: 213 sys.exit(main()) 214 except KeyboardInterrupt: 215 sys.exit(1) 216