objgraph.py 5.03 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 23

# 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
import string
24
import os
Guido van Rossum's avatar
Guido van Rossum committed
25
import getopt
26
import regex
Guido van Rossum's avatar
Guido van Rossum committed
27 28 29 30 31 32 33 34 35

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

# Regular expression to parse "nm -o" output.
#
36
matcher = regex.compile('\(.*\):\t?........ \(.\) \(.*\)$')
Guido van Rossum's avatar
Guido van Rossum committed
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68

# 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):
	if dict.has_key(key):
		dict[key].append(item)
	else:
		dict[key] = [item]

# Return a flattened version of a list of strings: the concatenation
# of its elements with intervening spaces.
#
def flat(list):
	s = ''
	for item in list:
		s = s + ' ' + item
	return s[1:]

# 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.
#
def readinput(file):
	while 1:
69
		s = file.readline()
Guido van Rossum's avatar
Guido van Rossum committed
70 71
		if not s:
			break
72
		# If you get any output from this line,
Guido van Rossum's avatar
Guido van Rossum committed
73
		# it is probably caused by an unexpected input line:
74 75
		if matcher.search(s) < 0: s; continue # Shouldn't happen
		(ra, rb), (r1a, r1b), (r2a, r2b), (r3a, r3b) = matcher.regs[:4]
Guido van Rossum's avatar
Guido van Rossum committed
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163
		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

# Print all names that were undefined in some module and where they are
# defined.
#
def printcallee():
	flist = file2undef.keys()
	flist.sort()
	for file in flist:
		print file + ':'
		elist = file2undef[file]
		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])

# Print for each module the names of the other modules that use it.
#
def printcaller():
	files = file2def.keys()
	files.sort()
	for file in files:
		callers = []
		for label in file2def[file]:
			if undef2file.has_key(label):
				callers = callers + undef2file[label]
		if callers:
			callers.sort()
			print file + ':'
			lastfn = ''
			for fn in callers:
				if fn <> lastfn:
					print '\t' + fn
				lastfn = fn
		else:
			print file + ': unused'

# Print undefine names and where they are used.
#
def printundef():
	undefs = {}
	for file in file2undef.keys():
		for ext in file2undef[file]:
			if not def2file.has_key(ext):
				store(undefs, ext, file)
	elist = undefs.keys()
	elist.sort()
	for ext in elist:
		print ext + ':'
		flist = undefs[ext]
		flist.sort()
		for file in flist:
			print '\t' + file

# Print warning messages about names defined in more than one file.
#
def warndups():
	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

# Main program
#
def main():
	try:
		optlist, args = getopt.getopt(sys.argv[1:], 'cdu')
	except getopt.error:
		sys.stdout = sys.stderr
164 165
		print 'Usage:', os.path.basename(sys.argv[0]),
		print           '[-cdu] [file] ...'
Guido van Rossum's avatar
Guido van Rossum committed
166 167 168 169 170 171 172 173 174
		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:
Guido van Rossum's avatar
Guido van Rossum committed
175
		if opt == '-u':
Guido van Rossum's avatar
Guido van Rossum committed
176
			optu = 1
Guido van Rossum's avatar
Guido van Rossum committed
177
		elif opt == '-c':
Guido van Rossum's avatar
Guido van Rossum committed
178
			optc = 1
Guido van Rossum's avatar
Guido van Rossum committed
179
		elif opt == '-d':
Guido van Rossum's avatar
Guido van Rossum committed
180
			optd = 1
Guido van Rossum's avatar
Guido van Rossum committed
181
	if optu == optc == optd == 0:
Guido van Rossum's avatar
Guido van Rossum committed
182 183 184 185
		optu = optc = optd = 1
	if not args:
		args = ['-']
	for file in args:
Guido van Rossum's avatar
Guido van Rossum committed
186
		if file == '-':
Guido van Rossum's avatar
Guido van Rossum committed
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
			readinput(sys.stdin)
		else:
			readinput(open(file, 'r'))
	#
	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

# Call the main program.
# Use its return value as exit status.
# Catch interrupts to avoid stack trace.
#
try:
	sys.exit(main())
except KeyboardInterrupt:
	sys.exit(1)