17db96d56Sopenharmony_ci#! /usr/bin/env python3
27db96d56Sopenharmony_ci
37db96d56Sopenharmony_ci# objgraph
47db96d56Sopenharmony_ci#
57db96d56Sopenharmony_ci# Read "nm -o" input of a set of libraries or modules and print various
67db96d56Sopenharmony_ci# interesting listings, such as:
77db96d56Sopenharmony_ci#
87db96d56Sopenharmony_ci# - which names are used but not defined in the set (and used where),
97db96d56Sopenharmony_ci# - which names are defined in the set (and where),
107db96d56Sopenharmony_ci# - which modules use which other modules,
117db96d56Sopenharmony_ci# - which modules are used by which other modules.
127db96d56Sopenharmony_ci#
137db96d56Sopenharmony_ci# Usage: objgraph [-cdu] [file] ...
147db96d56Sopenharmony_ci# -c: print callers per objectfile
157db96d56Sopenharmony_ci# -d: print callees per objectfile
167db96d56Sopenharmony_ci# -u: print usage of undefined symbols
177db96d56Sopenharmony_ci# If none of -cdu is specified, all are assumed.
187db96d56Sopenharmony_ci# Use "nm -o" to generate the input
197db96d56Sopenharmony_ci# e.g.: nm -o /lib/libc.a | objgraph
207db96d56Sopenharmony_ci
217db96d56Sopenharmony_ci
227db96d56Sopenharmony_ciimport sys
237db96d56Sopenharmony_ciimport os
247db96d56Sopenharmony_ciimport getopt
257db96d56Sopenharmony_ciimport re
267db96d56Sopenharmony_ci
277db96d56Sopenharmony_ci# Types of symbols.
287db96d56Sopenharmony_ci#
297db96d56Sopenharmony_cidefinitions = 'TRGDSBAEC'
307db96d56Sopenharmony_ciexternals = 'UV'
317db96d56Sopenharmony_ciignore = 'Nntrgdsbavuc'
327db96d56Sopenharmony_ci
337db96d56Sopenharmony_ci# Regular expression to parse "nm -o" output.
347db96d56Sopenharmony_ci#
357db96d56Sopenharmony_cimatcher = re.compile('(.*):\t?........ (.) (.*)$')
367db96d56Sopenharmony_ci
377db96d56Sopenharmony_ci# Store "item" in "dict" under "key".
387db96d56Sopenharmony_ci# The dictionary maps keys to lists of items.
397db96d56Sopenharmony_ci# If there is no list for the key yet, it is created.
407db96d56Sopenharmony_ci#
417db96d56Sopenharmony_cidef store(dict, key, item):
427db96d56Sopenharmony_ci    if key in dict:
437db96d56Sopenharmony_ci        dict[key].append(item)
447db96d56Sopenharmony_ci    else:
457db96d56Sopenharmony_ci        dict[key] = [item]
467db96d56Sopenharmony_ci
477db96d56Sopenharmony_ci# Return a flattened version of a list of strings: the concatenation
487db96d56Sopenharmony_ci# of its elements with intervening spaces.
497db96d56Sopenharmony_ci#
507db96d56Sopenharmony_cidef flat(list):
517db96d56Sopenharmony_ci    s = ''
527db96d56Sopenharmony_ci    for item in list:
537db96d56Sopenharmony_ci        s = s + ' ' + item
547db96d56Sopenharmony_ci    return s[1:]
557db96d56Sopenharmony_ci
567db96d56Sopenharmony_ci# Global variables mapping defined/undefined names to files and back.
577db96d56Sopenharmony_ci#
587db96d56Sopenharmony_cifile2undef = {}
597db96d56Sopenharmony_cidef2file = {}
607db96d56Sopenharmony_cifile2def = {}
617db96d56Sopenharmony_ciundef2file = {}
627db96d56Sopenharmony_ci
637db96d56Sopenharmony_ci# Read one input file and merge the data into the tables.
647db96d56Sopenharmony_ci# Argument is an open file.
657db96d56Sopenharmony_ci#
667db96d56Sopenharmony_cidef readinput(fp):
677db96d56Sopenharmony_ci    while 1:
687db96d56Sopenharmony_ci        s = fp.readline()
697db96d56Sopenharmony_ci        if not s:
707db96d56Sopenharmony_ci            break
717db96d56Sopenharmony_ci        # If you get any output from this line,
727db96d56Sopenharmony_ci        # it is probably caused by an unexpected input line:
737db96d56Sopenharmony_ci        if matcher.search(s) < 0: s; continue # Shouldn't happen
747db96d56Sopenharmony_ci        (ra, rb), (r1a, r1b), (r2a, r2b), (r3a, r3b) = matcher.regs[:4]
757db96d56Sopenharmony_ci        fn, name, type = s[r1a:r1b], s[r3a:r3b], s[r2a:r2b]
767db96d56Sopenharmony_ci        if type in definitions:
777db96d56Sopenharmony_ci            store(def2file, name, fn)
787db96d56Sopenharmony_ci            store(file2def, fn, name)
797db96d56Sopenharmony_ci        elif type in externals:
807db96d56Sopenharmony_ci            store(file2undef, fn, name)
817db96d56Sopenharmony_ci            store(undef2file, name, fn)
827db96d56Sopenharmony_ci        elif not type in ignore:
837db96d56Sopenharmony_ci            print(fn + ':' + name + ': unknown type ' + type)
847db96d56Sopenharmony_ci
857db96d56Sopenharmony_ci# Print all names that were undefined in some module and where they are
867db96d56Sopenharmony_ci# defined.
877db96d56Sopenharmony_ci#
887db96d56Sopenharmony_cidef printcallee():
897db96d56Sopenharmony_ci    flist = sorted(file2undef.keys())
907db96d56Sopenharmony_ci    for filename in flist:
917db96d56Sopenharmony_ci        print(filename + ':')
927db96d56Sopenharmony_ci        elist = file2undef[filename]
937db96d56Sopenharmony_ci        elist.sort()
947db96d56Sopenharmony_ci        for ext in elist:
957db96d56Sopenharmony_ci            if len(ext) >= 8:
967db96d56Sopenharmony_ci                tabs = '\t'
977db96d56Sopenharmony_ci            else:
987db96d56Sopenharmony_ci                tabs = '\t\t'
997db96d56Sopenharmony_ci            if ext not in def2file:
1007db96d56Sopenharmony_ci                print('\t' + ext + tabs + ' *undefined')
1017db96d56Sopenharmony_ci            else:
1027db96d56Sopenharmony_ci                print('\t' + ext + tabs + flat(def2file[ext]))
1037db96d56Sopenharmony_ci
1047db96d56Sopenharmony_ci# Print for each module the names of the other modules that use it.
1057db96d56Sopenharmony_ci#
1067db96d56Sopenharmony_cidef printcaller():
1077db96d56Sopenharmony_ci    files = sorted(file2def.keys())
1087db96d56Sopenharmony_ci    for filename in files:
1097db96d56Sopenharmony_ci        callers = []
1107db96d56Sopenharmony_ci        for label in file2def[filename]:
1117db96d56Sopenharmony_ci            if label in undef2file:
1127db96d56Sopenharmony_ci                callers = callers + undef2file[label]
1137db96d56Sopenharmony_ci        if callers:
1147db96d56Sopenharmony_ci            callers.sort()
1157db96d56Sopenharmony_ci            print(filename + ':')
1167db96d56Sopenharmony_ci            lastfn = ''
1177db96d56Sopenharmony_ci            for fn in callers:
1187db96d56Sopenharmony_ci                if fn != lastfn:
1197db96d56Sopenharmony_ci                    print('\t' + fn)
1207db96d56Sopenharmony_ci                lastfn = fn
1217db96d56Sopenharmony_ci        else:
1227db96d56Sopenharmony_ci            print(filename + ': unused')
1237db96d56Sopenharmony_ci
1247db96d56Sopenharmony_ci# Print undefined names and where they are used.
1257db96d56Sopenharmony_ci#
1267db96d56Sopenharmony_cidef printundef():
1277db96d56Sopenharmony_ci    undefs = {}
1287db96d56Sopenharmony_ci    for filename in list(file2undef.keys()):
1297db96d56Sopenharmony_ci        for ext in file2undef[filename]:
1307db96d56Sopenharmony_ci            if ext not in def2file:
1317db96d56Sopenharmony_ci                store(undefs, ext, filename)
1327db96d56Sopenharmony_ci    elist = sorted(undefs.keys())
1337db96d56Sopenharmony_ci    for ext in elist:
1347db96d56Sopenharmony_ci        print(ext + ':')
1357db96d56Sopenharmony_ci        flist = sorted(undefs[ext])
1367db96d56Sopenharmony_ci        for filename in flist:
1377db96d56Sopenharmony_ci            print('\t' + filename)
1387db96d56Sopenharmony_ci
1397db96d56Sopenharmony_ci# Print warning messages about names defined in more than one file.
1407db96d56Sopenharmony_ci#
1417db96d56Sopenharmony_cidef warndups():
1427db96d56Sopenharmony_ci    savestdout = sys.stdout
1437db96d56Sopenharmony_ci    sys.stdout = sys.stderr
1447db96d56Sopenharmony_ci    names = sorted(def2file.keys())
1457db96d56Sopenharmony_ci    for name in names:
1467db96d56Sopenharmony_ci        if len(def2file[name]) > 1:
1477db96d56Sopenharmony_ci            print('warning:', name, 'multiply defined:', end=' ')
1487db96d56Sopenharmony_ci            print(flat(def2file[name]))
1497db96d56Sopenharmony_ci    sys.stdout = savestdout
1507db96d56Sopenharmony_ci
1517db96d56Sopenharmony_ci# Main program
1527db96d56Sopenharmony_ci#
1537db96d56Sopenharmony_cidef main():
1547db96d56Sopenharmony_ci    try:
1557db96d56Sopenharmony_ci        optlist, args = getopt.getopt(sys.argv[1:], 'cdu')
1567db96d56Sopenharmony_ci    except getopt.error:
1577db96d56Sopenharmony_ci        sys.stdout = sys.stderr
1587db96d56Sopenharmony_ci        print('Usage:', os.path.basename(sys.argv[0]), end=' ')
1597db96d56Sopenharmony_ci        print('[-cdu] [file] ...')
1607db96d56Sopenharmony_ci        print('-c: print callers per objectfile')
1617db96d56Sopenharmony_ci        print('-d: print callees per objectfile')
1627db96d56Sopenharmony_ci        print('-u: print usage of undefined symbols')
1637db96d56Sopenharmony_ci        print('If none of -cdu is specified, all are assumed.')
1647db96d56Sopenharmony_ci        print('Use "nm -o" to generate the input')
1657db96d56Sopenharmony_ci        print('e.g.: nm -o /lib/libc.a | objgraph')
1667db96d56Sopenharmony_ci        return 1
1677db96d56Sopenharmony_ci    optu = optc = optd = 0
1687db96d56Sopenharmony_ci    for opt, void in optlist:
1697db96d56Sopenharmony_ci        if opt == '-u':
1707db96d56Sopenharmony_ci            optu = 1
1717db96d56Sopenharmony_ci        elif opt == '-c':
1727db96d56Sopenharmony_ci            optc = 1
1737db96d56Sopenharmony_ci        elif opt == '-d':
1747db96d56Sopenharmony_ci            optd = 1
1757db96d56Sopenharmony_ci    if optu == optc == optd == 0:
1767db96d56Sopenharmony_ci        optu = optc = optd = 1
1777db96d56Sopenharmony_ci    if not args:
1787db96d56Sopenharmony_ci        args = ['-']
1797db96d56Sopenharmony_ci    for filename in args:
1807db96d56Sopenharmony_ci        if filename == '-':
1817db96d56Sopenharmony_ci            readinput(sys.stdin)
1827db96d56Sopenharmony_ci        else:
1837db96d56Sopenharmony_ci            with open(filename) as f:
1847db96d56Sopenharmony_ci                readinput(f)
1857db96d56Sopenharmony_ci    #
1867db96d56Sopenharmony_ci    warndups()
1877db96d56Sopenharmony_ci    #
1887db96d56Sopenharmony_ci    more = (optu + optc + optd > 1)
1897db96d56Sopenharmony_ci    if optd:
1907db96d56Sopenharmony_ci        if more:
1917db96d56Sopenharmony_ci            print('---------------All callees------------------')
1927db96d56Sopenharmony_ci        printcallee()
1937db96d56Sopenharmony_ci    if optu:
1947db96d56Sopenharmony_ci        if more:
1957db96d56Sopenharmony_ci            print('---------------Undefined callees------------')
1967db96d56Sopenharmony_ci        printundef()
1977db96d56Sopenharmony_ci    if optc:
1987db96d56Sopenharmony_ci        if more:
1997db96d56Sopenharmony_ci            print('---------------All Callers------------------')
2007db96d56Sopenharmony_ci        printcaller()
2017db96d56Sopenharmony_ci    return 0
2027db96d56Sopenharmony_ci
2037db96d56Sopenharmony_ci# Call the main program.
2047db96d56Sopenharmony_ci# Use its return value as exit status.
2057db96d56Sopenharmony_ci# Catch interrupts to avoid stack trace.
2067db96d56Sopenharmony_ci#
2077db96d56Sopenharmony_ciif __name__ == '__main__':
2087db96d56Sopenharmony_ci    try:
2097db96d56Sopenharmony_ci        sys.exit(main())
2107db96d56Sopenharmony_ci    except KeyboardInterrupt:
2117db96d56Sopenharmony_ci        sys.exit(1)
212