Home | History | Annotate | Download | only in scripts
      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