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