(root)/
Python-3.11.7/
Tools/
scripts/
objgraph.py
       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)