Home
History
Comparisons
Examples
Documentation
  Help File
Download
News
Wish List
Soapbox

Project:
  Forums
  Tracker
  CVS

Hosted by:
  Sourceforge
  VA Linux

Author

Hawk / Ftwalk Examples: nmqt

This script uses nm to keep a table of global functions for a set of .o files. The table is kept in a cache directory, so that nm only needs to be run if the .o file is changed. The .o files of interest are specified on the command line. The cache mechanism can be disabled by the -nocache command line option.

The names are filtered to provide C++ demangling. The demangle option can be disabled with the -nofilt option.

Once the updated function information is read into memory, the user is prompted. When users enter a function name, the name is looked up. If there is an exact match, the function name and the file in which it is defined are printed. If there is no exact match, any function name that has a partial match is printed.

Users can also look for external function calls by entering u then the function name after a space.

The input l prints all function definitions, u prints all external references, and q exits the program.

Different systems have different output formats for nm. The example code works for Silicon Graphics IRIX 5 and Unixware 2 systems. The differences are handled by initialization functions, which set functors to distinguish the cases.

# initialization for Silicon Graphic IRIX 5
function init_sgi5() {
    Cmd = "nm "
    EgrepFilt = "(Symbols from|\|Proc)"
    Filter = "/usr/lib/c++/c++filt"
    TestFuncs  = functor (l) { l[4] == "Proc" && l[6] == "Text" }
    TestUFuncs = functor (l) { l[4] == "Proc" && l[6] == "Undefined" }
    NmI = 7
}

# initialization for Unixware 2
function init_uw2() {
    Cmd = "nm "
    EgrepFilt = "(Symbols from|\|FUNC|\|NOTY)"
    Filter = whence("c++filt")
    TestFuncs  = functor (l) { l[4] == "FUNC" && l[5] == "GLOB" }
    TestUFuncs = functor (l) { l[4] == "NOTY" && l[7] == "UNDEF" }
    NmI = 8
}

# read cache file in. Funcs is a map from function name to file name.
# UFuncs is a map from function name to list of file names.
function read_cache(cf)
{
    F = open(cf)
    while (Ln = F.get) {
        L = split(Ln, " ")
        FNm = L[1]
        Nm = L[3]
        switch (L[2]) {
          case "T":
            Funcs[Nm] = FNm
          case "U":
            if (Nm in UFuncs)
                UFuncs[Nm] += FNm
            else
                UFuncs[Nm] = list(FNm)
        }
    }
    F.close
}

# compare .o files against cache files, put any files that do not match
# cache into NmList for processing. cache filenames have '_' appended,
# and are timestamp synched to .o files.
function check_nm() {
    if (NoCache)
        NmList = ARGV
    else {
        NmList = list
        if (!isdir(Cache) && mkdir(Cache))
            die("Cannot create cache directory: %(Cache) [%m]")
        for (i in ARGV) {
            CacheF = path(Cache, filename(i) "_")
            if ((St = stat(CacheF)) && St.st_mtime == st_mtime(i))
                read_cache(CacheF)
            else
                NmList += i
        }
    }
}

# common code to close previous cache file, open new one (if nm).
function do_cache(nm) {
    if (G != void) {
        G.close
        utime(CacheF, St)
    }
    if (nm && (St = stat(nm))) {
        CacheF = path(Cache, nm "_")
        G = open(CacheF, "w")
    }
}

# run nm on all files in NmList, copying new information into cache.
# the egrep filter cuts down on the verbiage before c++filt. this is
# pretty slow any way you do it.
function run_nm() {
    G = void
    for (i in NmList) {
        FName = filename(i)
        cmd = sprintf("%s %s | egrep '%s'", Cmd, i, EgrepFilt)
        if (Filter)
            cmd = cmd " | " Filter
        F = popen(cmd)
        if (!NoCache)
            do_cache(FName)
        while ((Ln = F.get) != void) {
            # squeeze out excess spaces and split on "|"
            Ln.gsub(R"[ \t]*|[ \t]*", "|")
            L = split(Ln, "|")
            switch (L.length) {
              case 1:
                if (Ln ~ R"^Symbols from ") {
                    FName = sub(R"Symbols from ([^:]*)$0:", "$0", Ln)
                }
              case NmI:
                Nm = L[NmI]
                if (TestFuncs(L)) {
                    Funcs[Nm] = FName
                    if (G)
                        G.printf("%s T %s\n", FName, Nm)
                }
                else if (TestUFuncs(L)) {
                    if (!(Nm in UFuncs))
                        UFuncs[Nm] = list(FName)
                    else
                        UFuncs[Nm] += FName
                    if (G)
                        G.printf("%s T %s\n", FName, Nm)
                }
            }
        }
        F.close
    }
    do_cache()
    printf("Got %d functions\n", Funcs.length)
    printf("Got %d undefined functions\n", UFuncs.length)
}

# print filename for one/more/all function definitions.
function list_funcs(Nm) {
    if (Nm && Nm in Funcs)
        printf("%s: %s\n", Nm, Funcs[Nm])
    else {
        Found = 0
        for (i in Funcs) {
            if (!Nm || Nm in i) {
                printf("%s: %s\n", i, Funcs[i])
                Found++
            }
        }
        if (!Found)
            print("Not found.")
    }
}

# print filename for one/more/all undefineds.
function list_undefs(Nm) {
    if (Nm && Nm in UFuncs)
        printf("%s: %s\n", Nm, string(l: ", ", UFuncs[Nm]))
    else {
        Found = 0
        for (i in UFuncs) {
            if (!Nm || Nm in i) {
                printf("%s: %s\n", i, string(l: ", ", UFuncs[i]))
                Found++
            }
        }
        if (!Found)
            print("Not found.")
    }
}

BEGIN {
    Funcs = map
    UFuncs = map
    Cache = ".nmqt_cache"
    # initialize according to platform
    switch (version.name) {
      case "sgi5":
        init_sgi5
      case "uw2":
        init_uw2
      default:
        die("program only configured for sgi5/uw2")
    }
    if ("nofilt" in OPTIONS)
        Filter = void
    NoCache = "nocache" in OPTIONS
}

MAIN {
    check_nm
    if (NmList)
        run_nm
    printf("Enter function name (use 'u ...' for undefined).\n> ")
    while (Ln = gets) {
        L = split(Ln, " ")
        switch (L[1]) {
          case "l":         # list all defined functions
            list_funcs("")
          case "u":         # list all undefined functions
            list_undefs(L.length > 1 ? L[2] : "")
          case "q":         # quit
            break
          default:          # "u NAME" queries for undefined, NAME for defined
            list_funcs(L[1])
        }
        printf("> ")
    }
}
This script could be extended to handle .a archive files, global variables, additional queries.