objgraph.py 5.88 KB
Newer Older
1
#! /usr/bin/env python
Guido van Rossum's avatar
Guido van Rossum committed
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22

# objgraph
#
# Read "nm -o" input (on IRIX: "nm -Bo") of a set of libraries or modules
# and print various interesting listings, such as:
#
# - which names are used but not defined in the set (and used where),
# - which names are defined in the set (and where),
# - which modules use which other modules,
# - which modules are used by which other modules.
#
# Usage: objgraph [-cdu] [file] ...
# -c: print callers per objectfile
# -d: print callees per objectfile
# -u: print usage of undefined symbols
# If none of -cdu is specified, all are assumed.
# Use "nm -o" to generate the input (on IRIX: "nm -Bo"),
# e.g.: nm -o /lib/libc.a | objgraph


import sys
23
import os
Guido van Rossum's avatar
Guido van Rossum committed
24
import getopt
25
import re
Guido van Rossum's avatar
Guido van Rossum committed
26 27 28 29 30 31 32 33 34

# Types of symbols.
#
definitions = 'TRGDSBAEC'
externals = 'UV'
ignore = 'Nntrgdsbavuc'

# Regular expression to parse "nm -o" output.
#
35
matcher = re.compile('(.*):\t?........ (.) (.*)$')
Guido van Rossum's avatar
Guido van Rossum committed
36 37 38 39 40 41

# Store "item" in "dict" under "key".
# The dictionary maps keys to lists of items.
# If there is no list for the key yet, it is created.
#
def store(dict, key, item):
Tim Peters's avatar
Tim Peters committed
42 43 44 45
    if dict.has_key(key):
        dict[key].append(item)
    else:
        dict[key] = [item]
Guido van Rossum's avatar
Guido van Rossum committed
46 47 48 49 50

# Return a flattened version of a list of strings: the concatenation
# of its elements with intervening spaces.
#
def flat(list):
Tim Peters's avatar
Tim Peters committed
51 52 53 54
    s = ''
    for item in list:
        s = s + ' ' + item
    return s[1:]
Guido van Rossum's avatar
Guido van Rossum committed
55 56 57 58 59 60 61 62 63 64 65

# Global variables mapping defined/undefined names to files and back.
#
file2undef = {}
def2file = {}
file2def = {}
undef2file = {}

# Read one input file and merge the data into the tables.
# Argument is an open file.
#
66
def readinput(fp):
Tim Peters's avatar
Tim Peters committed
67
    while 1:
68
        s = fp.readline()
Tim Peters's avatar
Tim Peters committed
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
        if not s:
            break
        # If you get any output from this line,
        # it is probably caused by an unexpected input line:
        if matcher.search(s) < 0: s; continue # Shouldn't happen
        (ra, rb), (r1a, r1b), (r2a, r2b), (r3a, r3b) = matcher.regs[:4]
        fn, name, type = s[r1a:r1b], s[r3a:r3b], s[r2a:r2b]
        if type in definitions:
            store(def2file, name, fn)
            store(file2def, fn, name)
        elif type in externals:
            store(file2undef, fn, name)
            store(undef2file, name, fn)
        elif not type in ignore:
            print fn + ':' + name + ': unknown type ' + type
Guido van Rossum's avatar
Guido van Rossum committed
84 85 86 87 88

# Print all names that were undefined in some module and where they are
# defined.
#
def printcallee():
Tim Peters's avatar
Tim Peters committed
89 90
    flist = file2undef.keys()
    flist.sort()
91 92 93
    for filename in flist:
        print filename + ':'
        elist = file2undef[filename]
Tim Peters's avatar
Tim Peters committed
94 95 96 97 98 99 100 101 102 103
        elist.sort()
        for ext in elist:
            if len(ext) >= 8:
                tabs = '\t'
            else:
                tabs = '\t\t'
            if not def2file.has_key(ext):
                print '\t' + ext + tabs + ' *undefined'
            else:
                print '\t' + ext + tabs + flat(def2file[ext])
Guido van Rossum's avatar
Guido van Rossum committed
104 105 106 107

# Print for each module the names of the other modules that use it.
#
def printcaller():
Tim Peters's avatar
Tim Peters committed
108 109
    files = file2def.keys()
    files.sort()
110
    for filename in files:
Tim Peters's avatar
Tim Peters committed
111
        callers = []
112
        for label in file2def[filename]:
Tim Peters's avatar
Tim Peters committed
113 114 115 116
            if undef2file.has_key(label):
                callers = callers + undef2file[label]
        if callers:
            callers.sort()
117
            print filename + ':'
Tim Peters's avatar
Tim Peters committed
118 119 120 121 122 123
            lastfn = ''
            for fn in callers:
                if fn <> lastfn:
                    print '\t' + fn
                lastfn = fn
        else:
124
            print filename + ': unused'
Guido van Rossum's avatar
Guido van Rossum committed
125

126
# Print undefined names and where they are used.
Guido van Rossum's avatar
Guido van Rossum committed
127 128
#
def printundef():
Tim Peters's avatar
Tim Peters committed
129
    undefs = {}
130 131
    for filename in file2undef.keys():
        for ext in file2undef[filename]:
Tim Peters's avatar
Tim Peters committed
132
            if not def2file.has_key(ext):
133
                store(undefs, ext, filename)
Tim Peters's avatar
Tim Peters committed
134 135 136 137 138 139
    elist = undefs.keys()
    elist.sort()
    for ext in elist:
        print ext + ':'
        flist = undefs[ext]
        flist.sort()
140 141
        for filename in flist:
            print '\t' + filename
Guido van Rossum's avatar
Guido van Rossum committed
142 143 144 145

# Print warning messages about names defined in more than one file.
#
def warndups():
Tim Peters's avatar
Tim Peters committed
146 147 148 149 150 151 152 153 154
    savestdout = sys.stdout
    sys.stdout = sys.stderr
    names = def2file.keys()
    names.sort()
    for name in names:
        if len(def2file[name]) > 1:
            print 'warning:', name, 'multiply defined:',
            print flat(def2file[name])
    sys.stdout = savestdout
Guido van Rossum's avatar
Guido van Rossum committed
155 156 157 158

# Main program
#
def main():
Tim Peters's avatar
Tim Peters committed
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183
    try:
        optlist, args = getopt.getopt(sys.argv[1:], 'cdu')
    except getopt.error:
        sys.stdout = sys.stderr
        print 'Usage:', os.path.basename(sys.argv[0]),
        print           '[-cdu] [file] ...'
        print '-c: print callers per objectfile'
        print '-d: print callees per objectfile'
        print '-u: print usage of undefined symbols'
        print 'If none of -cdu is specified, all are assumed.'
        print 'Use "nm -o" to generate the input (on IRIX: "nm -Bo"),'
        print 'e.g.: nm -o /lib/libc.a | objgraph'
        return 1
    optu = optc = optd = 0
    for opt, void in optlist:
        if opt == '-u':
            optu = 1
        elif opt == '-c':
            optc = 1
        elif opt == '-d':
            optd = 1
    if optu == optc == optd == 0:
        optu = optc = optd = 1
    if not args:
        args = ['-']
184 185
    for filename in args:
        if filename == '-':
Tim Peters's avatar
Tim Peters committed
186 187
            readinput(sys.stdin)
        else:
188
            readinput(open(filename, 'r'))
Tim Peters's avatar
Tim Peters committed
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205
    #
    warndups()
    #
    more = (optu + optc + optd > 1)
    if optd:
        if more:
            print '---------------All callees------------------'
        printcallee()
    if optu:
        if more:
            print '---------------Undefined callees------------'
        printundef()
    if optc:
        if more:
            print '---------------All Callers------------------'
        printcaller()
    return 0
Guido van Rossum's avatar
Guido van Rossum committed
206 207 208 209 210

# Call the main program.
# Use its return value as exit status.
# Catch interrupts to avoid stack trace.
#
211 212 213 214 215
if __name__ == '__main__':
    try:
        sys.exit(main())
    except KeyboardInterrupt:
        sys.exit(1)