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 22 import sys 23 import os 24 import getopt 25 import re 26 27 # Types of symbols. 28 # 29 definitions = 'TRGDSBAEC' 30 externals = 'UV' 31 ignore = 'Nntrgdsbavuc' 32 33 # Regular expression to parse "nm -o" output. 34 # 35 matcher = 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 # 41 def 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 # 50 def 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 # 58 file2undef = {} 59 def2file = {} 60 file2def = {} 61 undef2file = {} 62 63 # Read one input file and merge the data into the tables. 64 # Argument is an open file. 65 # 66 def 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 # 88 def 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 # 107 def 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 # 128 def 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 # 145 def 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 # 158 def 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 # 211 if __name__ == '__main__': 212 try: 213 sys.exit(main()) 214 except KeyboardInterrupt: 215 sys.exit(1) 216