Source file mtrace.icn
#<p>
#  Read the output log from the GCC mtrace facility (man mtrace)
#    and produce a summary of problems that have been found.
#    Maps problems back to source file and line where possible.
#  Three types of errors can be detected and reported:<br><br>
#  -- cases where memory is allocated, but not freed<br>
#  -- cases where an attempt is made to free already freed memory<br>
#  -- cases where an attempt is made to free memory that was never allocated<br>
#</p>
#<p>
# For example, here is the output from this program when run
#   on a test program:
#<pre>
#Allocation that is not freed: 
#        0x8831f90[    1024 bytes] <- test.c:[197]
#        0x88337c0[    1024 bytes] <- test.c:[197]
#        0x8832fb0[    1024 bytes] <- test.c:[197]
#        0x8831780[    1024 bytes] <- test.c:[197]
#        0x88327a0[    1024 bytes] <- test.c:[197]
#        0x88333d8[      50 bytes] <- test.c:[159]
#
#Duplicated free: 
#        0x98c1378[    1024 bytes] <- test.c:[209] <test.c[197]>
#        0x98c1b88[    1024 bytes] <- test.c:[209] <test.c[197]>
#        0x98c2398[    1024 bytes] <- test.c:[209] <test.c[197]>
#        0x98c2ba8[    1024 bytes] <- test.c:[209] <test.c[197]>
#        0x98c33b8[    1024 bytes] <- test.c:[209] <test.c[197]>
#
#</pre>
#(There were no attempts to free unallocated memory in this example)
# The first field of each record is the memory address of the
# region that is being allocated/freed, the second (when present) is
# the size of that allocation, the third is the source code file
# and the fourth is the line number in that file where the allocation/free
# took place.
# Note that the report on each duplicate free provides the location
# where the free was attempted and also the location where the allocation
# took place as a fifth field.
#</p>

import util

global noMalloc, noFree, dupFree

procedure helpMesg(errMsg)
    write(&errout, errMsg)
    write(&errout, "Usage: mtrace --log=LOGFILE")
    stop()
end

procedure main(args)

    traceFileName := zapPrefix(!args, "--log=")
    if /traceFileName then helpMesg("Missing --log=LOGFILE argument")
    traceFile := open(traceFileName) |
                 helpMesg("Can't open '"||traceFileName||"'")

    process(traceFile)

    show("Free of non-allocated pointer: ", noMalloc)
    show("Allocation that is not freed: ", noFree)
    show("Duplicated free: ", dupFree)

end

#<p>
# Displays all the record lines for a specific category.
# <[param prompt -- header line identifying the trace category]>
# <[param A -- list of trace records]>
#</p>
procedure show(prompt, A)
    if *A > 0 then {
        write(prompt)
        every write("\t",!A)
        write()
        }
end

#<p>
# traceRecords are used within the <tt>process</tt> procedure
#   to track the allocation/free cycles for memory requests.
#</p>
record traceRecord(fName, size, memAddr, alloAddr, freeAddr)

#<p>
#  Process the lines in a gcc mtrace log file.
#  This procedure builds up three lists of information based
#  on the contents of that log file:<br><br>
#  -- a list of those malloc()s that don't have associated
#      frees (global variable <tt>noFree</tt>)<br>
#  -- a list of those free()s that attempt to free memory
#      that was never allocated (<tt>noMalloc</tt>)<br>
#  -- a list of those free()s that attempt to free memory
#      that as already be freed (<tt>dupFree</tt>)<br>
#</p>
procedure process(f)
    local name, traceMap, line, codeLoc, action, memLoc, size, tr

    traceMap := table()
    noMalloc := []
    noFree   := []
    dupFree  := []

    every line := !f do {
        if line == ("= Start"|"= End") then next
        else {
            line ? {
                name := (move(2), tab(upto(':')))
                codeLoc := (tab(upto('[')+1),tab(upto(']')))
                action := 2(move(2),move(1),move(1))
                memLoc := tab(upto(' \t')|0)
                tab(many(' \t'))
                size := tab(0)
                }
             size ?:= (="0x","16r")||tab(0)	# Size to Unicon hex form

             if "+" == action then {	        # malloc
                 traceMap[memLoc] :=
                      traceRecord(name, size, memLoc, codeLoc, &null)
                 }
             else if "-" == action then {       # free
                 tr := traceMap[memLoc] | &null
                 if /tr then {		        # No matching malloc?
                     put(noMalloc, mkRec(name, codeLoc, memLoc))
                     }
                 else if \tr.freeAddr then {	# Already freed?
                     put(dupFree, mkFreeRec(name, codeLoc, memLoc, tr))
                     }
                 else {
                     tr.freeAddr := memLoc	# Normal free
                     }
                 }
             }
          }
    
    every tr := !traceMap do {
        if /tr.freeAddr then {	# No free for malloc?
            put(noFree, mkRec(tr.fName, tr.alloAddr, tr.memAddr, tr.size))
            }
        }

end

#<p>
# Construct a report line from the information parsed from the
#    gcc mtrace log.  This includes determining the source file
#    name and line number within that source file where the
#    allocation/free took place that caused this record to exist.
# <[param fName -- name of executable unit involved]>
# <[param codeLoc -- hexadecimal address within that unit]>
# <[param alloLoc -- location in heap of the allocation
# <[param alloSize -- size (if known) of the allocation
#</p>
procedure mkRec(fName, codeLoc, alloLoc, alloSize)
    local result, lineNo, fileName

    result := getFileAndLine(fName, codeLoc)
    fileName := result[1]
    lineNo   := result[2]

    # Include the allocation size, if known
    alloSize := ("["||rPad(0+\alloSize,8)||" bytes]") | ""

    # Now display the source file and line number and address of allocation
    return alloLoc||alloSize||" <- "||fileName||":["||lineNo||"]"

end

#<p>
# Construct a record of a duplicated free.
#  Includes both the source location of the free, but also of the
#    original allocation.
#</p>
procedure mkFreeRec(fName, codeLoc, alloLoc, tr)
    local target, result

    target := mkRec(fName, codeLoc, alloLoc, tr.size)
    result := getFileAndLine(fName, tr.alloAddr)
    return target ||" <"|| result[1] ||"["||result[2]||"]>"
end

#<p>
#  Produces a list containing the source file name and source line
#    number (in order), given the object file name and byte offset
#    into the code.
#</p>
procedure getFileAndLine(fName, codeLoc)
    local cmdLine, cmd, lineNo, fileName

    # Start by figuring out where the allocation took place...
    #   (Use gdb to do all the heavy lifting - then extract the results)
    cmdLine := "gdb -q "||
           fName|| "<<HERE_FILE\ninfo line *"||codeLoc||"\nquit\nHERE_FILE"
    cmd := open(cmdLine,"rp")
    read(cmd)		# Skip first line
    lineNo := "???"
    fileName := "<unknown>"
    read(cmd) ? {	# extract source file name and line number
        lineNo   := (tab(find("Line ")),  ="Line ",  tab(upto(' ')))
        fileName := (tab(find(" of \"")), =" of \"", tab(upto('"')))
        }
    close(cmd)

    return [fileName, lineNo]
end

This page produced by UniDoc on 2021/04/15 @ 23:59:43.