objgraph.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. #! /usr/bin/env python3
  2. # objgraph
  3. #
  4. # Read "nm -o" input of a set of libraries or modules and print various
  5. # interesting listings, such as:
  6. #
  7. # - which names are used but not defined in the set (and used where),
  8. # - which names are defined in the set (and where),
  9. # - which modules use which other modules,
  10. # - which modules are used by which other modules.
  11. #
  12. # Usage: objgraph [-cdu] [file] ...
  13. # -c: print callers per objectfile
  14. # -d: print callees per objectfile
  15. # -u: print usage of undefined symbols
  16. # If none of -cdu is specified, all are assumed.
  17. # Use "nm -o" to generate the input
  18. # e.g.: nm -o /lib/libc.a | objgraph
  19. import sys
  20. import os
  21. import getopt
  22. import re
  23. # Types of symbols.
  24. #
  25. definitions = 'TRGDSBAEC'
  26. externals = 'UV'
  27. ignore = 'Nntrgdsbavuc'
  28. # Regular expression to parse "nm -o" output.
  29. #
  30. matcher = re.compile('(.*):\t?........ (.) (.*)$')
  31. # Store "item" in "dict" under "key".
  32. # The dictionary maps keys to lists of items.
  33. # If there is no list for the key yet, it is created.
  34. #
  35. def store(dict, key, item):
  36. if key in dict:
  37. dict[key].append(item)
  38. else:
  39. dict[key] = [item]
  40. # Return a flattened version of a list of strings: the concatenation
  41. # of its elements with intervening spaces.
  42. #
  43. def flat(list):
  44. s = ''
  45. for item in list:
  46. s = s + ' ' + item
  47. return s[1:]
  48. # Global variables mapping defined/undefined names to files and back.
  49. #
  50. file2undef = {}
  51. def2file = {}
  52. file2def = {}
  53. undef2file = {}
  54. # Read one input file and merge the data into the tables.
  55. # Argument is an open file.
  56. #
  57. def readinput(fp):
  58. while 1:
  59. s = fp.readline()
  60. if not s:
  61. break
  62. # If you get any output from this line,
  63. # it is probably caused by an unexpected input line:
  64. if matcher.search(s) < 0: s; continue # Shouldn't happen
  65. (ra, rb), (r1a, r1b), (r2a, r2b), (r3a, r3b) = matcher.regs[:4]
  66. fn, name, type = s[r1a:r1b], s[r3a:r3b], s[r2a:r2b]
  67. if type in definitions:
  68. store(def2file, name, fn)
  69. store(file2def, fn, name)
  70. elif type in externals:
  71. store(file2undef, fn, name)
  72. store(undef2file, name, fn)
  73. elif not type in ignore:
  74. print(fn + ':' + name + ': unknown type ' + type)
  75. # Print all names that were undefined in some module and where they are
  76. # defined.
  77. #
  78. def printcallee():
  79. flist = sorted(file2undef.keys())
  80. for filename in flist:
  81. print(filename + ':')
  82. elist = file2undef[filename]
  83. elist.sort()
  84. for ext in elist:
  85. if len(ext) >= 8:
  86. tabs = '\t'
  87. else:
  88. tabs = '\t\t'
  89. if ext not in def2file:
  90. print('\t' + ext + tabs + ' *undefined')
  91. else:
  92. print('\t' + ext + tabs + flat(def2file[ext]))
  93. # Print for each module the names of the other modules that use it.
  94. #
  95. def printcaller():
  96. files = sorted(file2def.keys())
  97. for filename in files:
  98. callers = []
  99. for label in file2def[filename]:
  100. if label in undef2file:
  101. callers = callers + undef2file[label]
  102. if callers:
  103. callers.sort()
  104. print(filename + ':')
  105. lastfn = ''
  106. for fn in callers:
  107. if fn != lastfn:
  108. print('\t' + fn)
  109. lastfn = fn
  110. else:
  111. print(filename + ': unused')
  112. # Print undefined names and where they are used.
  113. #
  114. def printundef():
  115. undefs = {}
  116. for filename in list(file2undef.keys()):
  117. for ext in file2undef[filename]:
  118. if ext not in def2file:
  119. store(undefs, ext, filename)
  120. elist = sorted(undefs.keys())
  121. for ext in elist:
  122. print(ext + ':')
  123. flist = sorted(undefs[ext])
  124. for filename in flist:
  125. print('\t' + filename)
  126. # Print warning messages about names defined in more than one file.
  127. #
  128. def warndups():
  129. savestdout = sys.stdout
  130. sys.stdout = sys.stderr
  131. names = sorted(def2file.keys())
  132. for name in names:
  133. if len(def2file[name]) > 1:
  134. print('warning:', name, 'multiply defined:', end=' ')
  135. print(flat(def2file[name]))
  136. sys.stdout = savestdout
  137. # Main program
  138. #
  139. def main():
  140. try:
  141. optlist, args = getopt.getopt(sys.argv[1:], 'cdu')
  142. except getopt.error:
  143. sys.stdout = sys.stderr
  144. print('Usage:', os.path.basename(sys.argv[0]), end=' ')
  145. print('[-cdu] [file] ...')
  146. print('-c: print callers per objectfile')
  147. print('-d: print callees per objectfile')
  148. print('-u: print usage of undefined symbols')
  149. print('If none of -cdu is specified, all are assumed.')
  150. print('Use "nm -o" to generate the input')
  151. print('e.g.: nm -o /lib/libc.a | objgraph')
  152. return 1
  153. optu = optc = optd = 0
  154. for opt, void in optlist:
  155. if opt == '-u':
  156. optu = 1
  157. elif opt == '-c':
  158. optc = 1
  159. elif opt == '-d':
  160. optd = 1
  161. if optu == optc == optd == 0:
  162. optu = optc = optd = 1
  163. if not args:
  164. args = ['-']
  165. for filename in args:
  166. if filename == '-':
  167. readinput(sys.stdin)
  168. else:
  169. readinput(open(filename, 'r'))
  170. #
  171. warndups()
  172. #
  173. more = (optu + optc + optd > 1)
  174. if optd:
  175. if more:
  176. print('---------------All callees------------------')
  177. printcallee()
  178. if optu:
  179. if more:
  180. print('---------------Undefined callees------------')
  181. printundef()
  182. if optc:
  183. if more:
  184. print('---------------All Callers------------------')
  185. printcaller()
  186. return 0
  187. # Call the main program.
  188. # Use its return value as exit status.
  189. # Catch interrupts to avoid stack trace.
  190. #
  191. if __name__ == '__main__':
  192. try:
  193. sys.exit(main())
  194. except KeyboardInterrupt:
  195. sys.exit(1)