#!/usr/bin/tclsh # Copyright (C) 2001-2023 Artifex Software, Inc. # All Rights Reserved. # # This software is provided AS-IS with no warranty, either express or # implied. # # This software is distributed under license and may not be copied, # modified or distributed except as expressly authorized under the terms # of the license contained in the file LICENSE in this distribution. # # Refer to licensing information at http://www.artifex.com or contact # Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco, # CA 94129, USA, for further information. # # This tool helps detect memory leaks in a -ZA trace from Ghostscript. # It reads a memory trace from stdin and prints unmatched allocations on # stdout. Currently it is slightly specialized for our PCL5 environment, # in that it looks for the string "allocated" in the trace to mark the # beginning of the interesting region, and the string "Final time" to # mark the end. Usage: # <> -Z:A ... >t.log # leaks.tcl t.report # We keep track of the trace in the following global arrays: # A() holds a string of the form line#:line of the last # allocation event that allocated a block at address addr. # lines() holds other interesting lines of the input trace file - # the "allocated" and "Final" lines, and anomalous alloc/free events. # next holds the line number of the next line. proc init_leaks {} { global A lines next catch {unset A} catch {unset lines} set next 0 } # The addMXN procedures handle input events as follows: # M=1 for allocator events, M=0 for other events # X=+ for allocation, X=- for deallocation # N=1 if A(addr) exists, N=0 if not proc add1+0 {il addr} {global A;set A($addr) $il} proc add1+1 {il addr} { global A lines regexp {^([0-9]+):(.*)$} $A($addr) all i l puts "**** Warning: reallocation: $il" puts "**** Previous allocation: $A($addr)" set lines($i) $l set A($addr) $il } proc add1-1 {il addr} {global A;unset A($addr)} proc add1-0 {il addr} { global lines if {$addr == "0"} {return} regexp {^([0-9]+):(.*)$} $il ignore i l puts "**** Warning: no alloc event: $il" set lines($i) $l } proc add0+0 {il addr} { if [regexp {Final|allocated} $il] { uplevel {set lines($n) $l} if [regexp "Final time" $il] {uplevel {set go 0}} } } proc add0+1 {il addr} [info body add0+0] proc add0-0 {il addr} [info body add0+0] proc add0-1 {il addr} [info body add0+0] proc read_trace {{fname %stdin}} { global A lines next set n $next set i 0 if {$fname == "%stdin"} { set in stdin } else { set in [open $fname] } # Skip to the first "allocated" line. See below for why we bother # checking for EOF. while {[gets $in l] >= 0} { incr i if [regexp "memory allocated" $l] break incr n } if {$i == 0} { puts stderr "Empty input file!" if {$fname != "%stdin"} {close $in} exit } set lines($n) $l incr n set sign + ;# arbitrary, + or - set addr "" ;# arbitrary set go 1 # When processing a syntactically correct trace file, add0+0 will set # go to 0 when it detects the "Final time" line; but we add a check here # just so invalid files won't loop forever. while {$go && [gets $in l] >= 0} { add[regexp {^\[a.*([+-]).*\].*0x([0-9a-f]+)} $l all sign addr]${sign}[info exists A($addr)] "$n:$l" $addr incr n } if {!$go} { # This is normal termination. The last line has not been stored. incr n -1 set lines($n) $l incr n } if {$fname != "%stdin"} { close $in } set next $n } proc print_leaks {} { global A lines foreach addr [array names A] { regexp {^([0-9]+):(.*)$} $A($addr) all i l set lines($i) $l } foreach i [lsort -integer [array names lines]] { puts "$i: $lines($i)" } } if {$argv0 != "tclsh"} { init_leaks read_trace print_leaks }