1 #! /usr/bin/env python3 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 key in dict: 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 = sorted(file2undef.keys()) 90 for filename in flist: 91 print(filename + ':') 92 elist = file2undef[filename] 93 elist.sort() 94 for ext in elist: 95 if len(ext) >= 8: 96 tabs = '\t' 97 else: 98 tabs = '\t\t' 99 if ext not in def2file: 100 print('\t' + ext + tabs + ' *undefined') 101 else: 102 print('\t' + ext + tabs + flat(def2file[ext])) 103 104 # Print for each module the names of the other modules that use it. 105 # 106 def printcaller(): 107 files = sorted(file2def.keys()) 108 for filename in files: 109 callers = [] 110 for label in file2def[filename]: 111 if label in undef2file: 112 callers = callers + undef2file[label] 113 if callers: 114 callers.sort() 115 print(filename + ':') 116 lastfn = '' 117 for fn in callers: 118 if fn != lastfn: 119 print('\t' + fn) 120 lastfn = fn 121 else: 122 print(filename + ': unused') 123 124 # Print undefined names and where they are used. 125 # 126 def printundef(): 127 undefs = {} 128 for filename in list(file2undef.keys()): 129 for ext in file2undef[filename]: 130 if ext not in def2file: 131 store(undefs, ext, filename) 132 elist = sorted(undefs.keys()) 133 for ext in elist: 134 print(ext + ':') 135 flist = sorted(undefs[ext]) 136 for filename in flist: 137 print('\t' + filename) 138 139 # Print warning messages about names defined in more than one file. 140 # 141 def warndups(): 142 savestdout = sys.stdout 143 sys.stdout = sys.stderr 144 names = sorted(def2file.keys()) 145 for name in names: 146 if len(def2file[name]) > 1: 147 print('warning:', name, 'multiply defined:', end=' ') 148 print(flat(def2file[name])) 149 sys.stdout = savestdout 150 151 # Main program 152 # 153 def main(): 154 try: 155 optlist, args = getopt.getopt(sys.argv[1:], 'cdu') 156 except getopt.error: 157 sys.stdout = sys.stderr 158 print('Usage:', os.path.basename(sys.argv[0]), end=' ') 159 print('[-cdu] [file] ...') 160 print('-c: print callers per objectfile') 161 print('-d: print callees per objectfile') 162 print('-u: print usage of undefined symbols') 163 print('If none of -cdu is specified, all are assumed.') 164 print('Use "nm -o" to generate the input (on IRIX: "nm -Bo"),') 165 print('e.g.: nm -o /lib/libc.a | objgraph') 166 return 1 167 optu = optc = optd = 0 168 for opt, void in optlist: 169 if opt == '-u': 170 optu = 1 171 elif opt == '-c': 172 optc = 1 173 elif opt == '-d': 174 optd = 1 175 if optu == optc == optd == 0: 176 optu = optc = optd = 1 177 if not args: 178 args = ['-'] 179 for filename in args: 180 if filename == '-': 181 readinput(sys.stdin) 182 else: 183 readinput(open(filename, 'r')) 184 # 185 warndups() 186 # 187 more = (optu + optc + optd > 1) 188 if optd: 189 if more: 190 print('---------------All callees------------------') 191 printcallee() 192 if optu: 193 if more: 194 print('---------------Undefined callees------------') 195 printundef() 196 if optc: 197 if more: 198 print('---------------All Callers------------------') 199 printcaller() 200 return 0 201 202 # Call the main program. 203 # Use its return value as exit status. 204 # Catch interrupts to avoid stack trace. 205 # 206 if __name__ == '__main__': 207 try: 208 sys.exit(main()) 209 except KeyboardInterrupt: 210 sys.exit(1) 211