|
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) |