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 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 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# 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 = 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# 106def 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# 126def 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# 141def 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# 153def 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# 206if __name__ == '__main__': 207 try: 208 sys.exit(main()) 209 except KeyboardInterrupt: 210 sys.exit(1) 211