1 #! /usr/bin/env python3
2
3 # objgraph
4 #
5 # Read "nm -o" input of a set of libraries or modules and print various
6 # 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
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 key in dict:
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 = sorted(file2undef.keys())
90 for filename in flist:
91 print(filename + ':')
92 elist = file2undef[filename]
93 elist.sort()
94 for ext in elist:
95 if len(ext) >= 8:
96 tabs = '\t'
97 else:
98 tabs = '\t\t'
99 if ext not in def2file:
100 print('\t' + ext + tabs + ' *undefined')
101 else:
102 print('\t' + ext + tabs + flat(def2file[ext]))
103
104 # Print for each module the names of the other modules that use it.
105 #
106 def printcaller():
107 files = sorted(file2def.keys())
108 for filename in files:
109 callers = []
110 for label in file2def[filename]:
111 if label in undef2file:
112 callers = callers + undef2file[label]
113 if callers:
114 callers.sort()
115 print(filename + ':')
116 lastfn = ''
117 for fn in callers:
118 if fn != lastfn:
119 print('\t' + fn)
120 lastfn = fn
121 else:
122 print(filename + ': unused')
123
124 # Print undefined names and where they are used.
125 #
126 def printundef():
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())
133 for ext in elist:
134 print(ext + ':')
135 flist = sorted(undefs[ext])
136 for filename in flist:
137 print('\t' + filename)
138
139 # Print warning messages about names defined in more than one file.
140 #
141 def warndups():
142 savestdout = sys.stdout
143 sys.stdout = sys.stderr
144 names = sorted(def2file.keys())
145 for name in names:
146 if len(def2file[name]) > 1:
147 print('warning:', name, 'multiply defined:', end=' ')
148 print(flat(def2file[name]))
149 sys.stdout = savestdout
150
151 # Main program
152 #
153 def main():
154 try:
155 optlist, args = getopt.getopt(sys.argv[1:], 'cdu')
156 except getopt.error:
157 sys.stdout = sys.stderr
158 print('Usage:', os.path.basename(sys.argv[0]), end=' ')
159 print('[-cdu] [file] ...')
160 print('-c: print callers per objectfile')
161 print('-d: print callees per objectfile')
162 print('-u: print usage of undefined symbols')
163 print('If none of -cdu is specified, all are assumed.')
164 print('Use "nm -o" to generate the input')
165 print('e.g.: nm -o /lib/libc.a | objgraph')
166 return 1
167 optu = optc = optd = 0
168 for opt, void in optlist:
169 if opt == '-u':
170 optu = 1
171 elif opt == '-c':
172 optc = 1
173 elif opt == '-d':
174 optd = 1
175 if optu == optc == optd == 0:
176 optu = optc = optd = 1
177 if not args:
178 args = ['-']
179 for filename in args:
180 if filename == '-':
181 readinput(sys.stdin)
182 else:
183 with open(filename) as f:
184 readinput(f)
185 #
186 warndups()
187 #
188 more = (optu + optc + optd > 1)
189 if optd:
190 if more:
191 print('---------------All callees------------------')
192 printcallee()
193 if optu:
194 if more:
195 print('---------------Undefined callees------------')
196 printundef()
197 if optc:
198 if more:
199 print('---------------All Callers------------------')
200 printcaller()
201 return 0
202
203 # Call the main program.
204 # Use its return value as exit status.
205 # Catch interrupts to avoid stack trace.
206 #
207 if __name__ == '__main__':
208 try:
209 sys.exit(main())
210 except KeyboardInterrupt:
211 sys.exit(1)