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
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 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#
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 = 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#
107def 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#
128def 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#
145def 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#
158def 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#
211if __name__ == '__main__':
212    try:
213        sys.exit(main())
214    except KeyboardInterrupt:
215        sys.exit(1)
216