objgraph.py 5.84 KB
Newer Older
1
#! /usr/bin/env python3
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):
42
    if key in dict:
Tim Peters's avatar
Tim Peters committed
43 44 45
        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
        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:
83
            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():
89
    flist = sorted(file2undef.keys())
90
    for filename in flist:
91
        print(filename + ':')
92
        elist = file2undef[filename]
Tim Peters's avatar
Tim Peters committed
93 94 95 96 97 98
        elist.sort()
        for ext in elist:
            if len(ext) >= 8:
                tabs = '\t'
            else:
                tabs = '\t\t'
99
            if ext not in def2file:
100
                print('\t' + ext + tabs + ' *undefined')
Tim Peters's avatar
Tim Peters committed
101
            else:
102
                print('\t' + ext + tabs + flat(def2file[ext]))
Guido van Rossum's avatar
Guido van Rossum committed
103 104 105 106

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

124
# Print undefined names and where they are used.
Guido van Rossum's avatar
Guido van Rossum committed
125 126
#
def printundef():
Tim Peters's avatar
Tim Peters committed
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())
Tim Peters's avatar
Tim Peters committed
133
    for ext in elist:
134
        print(ext + ':')
135
        flist = sorted(undefs[ext])
136
        for filename in flist:
137
            print('\t' + filename)
Guido van Rossum's avatar
Guido van Rossum committed
138 139 140 141

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

# Main program
#
def main():
Tim Peters's avatar
Tim Peters committed
154 155 156 157
    try:
        optlist, args = getopt.getopt(sys.argv[1:], 'cdu')
    except getopt.error:
        sys.stdout = sys.stderr
158 159 160 161 162 163 164 165
        print('Usage:', os.path.basename(sys.argv[0]), end=' ')
        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')
Tim Peters's avatar
Tim Peters committed
166 167 168 169 170 171 172 173 174 175 176 177 178
        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 = ['-']
179 180
    for filename in args:
        if filename == '-':
Tim Peters's avatar
Tim Peters committed
181 182
            readinput(sys.stdin)
        else:
183
            readinput(open(filename, 'r'))
Tim Peters's avatar
Tim Peters committed
184 185 186 187 188 189
    #
    warndups()
    #
    more = (optu + optc + optd > 1)
    if optd:
        if more:
190
            print('---------------All callees------------------')
Tim Peters's avatar
Tim Peters committed
191 192 193
        printcallee()
    if optu:
        if more:
194
            print('---------------Undefined callees------------')
Tim Peters's avatar
Tim Peters committed
195 196 197
        printundef()
    if optc:
        if more:
198
            print('---------------All Callers------------------')
Tim Peters's avatar
Tim Peters committed
199 200
        printcaller()
    return 0
Guido van Rossum's avatar
Guido van Rossum committed
201 202 203 204 205

# Call the main program.
# Use its return value as exit status.
# Catch interrupts to avoid stack trace.
#
206 207 208 209 210
if __name__ == '__main__':
    try:
        sys.exit(main())
    except KeyboardInterrupt:
        sys.exit(1)