Subject: Passive Leak caller detection in DebugNew.cp (long)
posted to :comp.sys.mac.programmer.codewarrior
on May 1999
by Richard Wesley Electric Fish Inc. hawkfish@NeOlSePcAtMricfish.com
Hey All -
About 6 months ago I posted a simple mod to DebugNew.cp that allows you
to see the caller's address with no mods to the code base. At the time,
some kind soul responded that I should check out Spotlight as it does
all this. I had already solved the problem, but I took note of the
suggestion
Moving ahead 6 months, I downloaded the Spotlight demo earlier this
week and it caused the tested application to crash. The documentation
was nonexistent and I got no response from their support line. I would
have bought the product if it worked, but as it did not and I got no
assistance from them, I decided to haul out my old hack. After fixing
it up for Pro 4 it worked great. So for those of you who do not have
the time, money or patience to make Spotlight work for you, here is a
free partial substitute. No warranty expressed or implied.
This is a long posting in three parts. Part 1 is a description of the
hack; part 2 are the two source patches and part 3 is a Jasik Debugger
script I wrote that emulates the behavior of DebugNewReportLeaks with
the added bonus of doing the procedure translation. Enjoy.
----
Part 1: The Hack.
Basically, I noticed that both file and line are 0 for the non
DEBUG_NEW operator new. line == -1 is used for forgotten leaks, but
that does not cause any problems. All I do is replace the two versions
of operator new with assembly versions that pass in the return address
(lr or 4(a6)) as the line parameter. The report function then notices
this case and dumps out the line field as hex labelling it "caller".
You can then look this address up in your debugger (I use Jasik which
makes it really easy) and you have the leaking allocation.
----
Part 2: The Code
There are two patches here, both in DebugNew.cp. The first one
replaces the two oeprator new implementations; the second is inserted
into the middle of the leaks dumping loop:
-- Patch 1
#ifdef powerc
void asm *operator new(size_t size)
{
mflr r0
stw r0,8(SP)
stwu SP,-64(SP)
stw r3,88(SP)
lwz r3,88(SP)
li r4,0
mflr r5 // Move Link register into r5 (== line)
lwz r6,_std_operator_new
li r7,0
bl DebugNewDoAllocate
nop
lwz r0,72(SP)
addi SP,SP,64
mtlr r0
blr
}
void asm *operator new[](size_t size)
{
mflr r0
stw r0,8(SP)
stwu SP,-64(SP)
stw r3,88(SP)
lwz r3,88(SP)
li r4,0
mflr r5 // Move Link register into r5 (== line)
lwz r6,_std_operator_array_new
li r7,1
bl DebugNewDoAllocate
nop
lwz r0,72(SP)
addi SP,SP,64
mtlr r0
blr
}
#else
void asm *operator new(size_t size)
{
link a6,#0
clr.b -(a7)
pea _std_operator_new
move.l 4(a6),-(a7) // Return address is at 4(a6)
clr.l -(a7)
move.l 8(a6),-(a7)
jsr DebugNewDoAllocate
unlk a6
rts
}
void asm *operator new[](size_t size)
{
link a6,#0
move.b #1,-(a7)
pea _std_operator_new
move.l 4(a6),-(a7) // Return address is at 4(a6)
clr.l -(a7)
move.l 8(a6),-(a7)
jsr DebugNewDoAllocate
unlk a6
rts
}
#endif
-- patch 2
else if (curr->line)
fprintf(f," caller: %X, size: %lu", curr->line,
curr->size);
---- Part 3: The Jasik Script
ееееее
{ еее display a list of the leaks from MW DebugNew еее }
( { <- dbl click on the left paren to HiLite, shift-Cmd-[ to execute }
?ReDirect('Leaks');
?kHashTableSize := $1F3;
?count := 0;
?leakCount := 0;
?bytesLeaked := 0;
?i := 0;
WHILE ?i < ?kHashTableSize DO BEGIN
?curr := gBlockHash[?i];
WHILE ?curr <> 0 DO BEGIN
?count := ?count + 1;
if (((BlockHeader(?curr).tag = $D1F3D1F3) OR
(BlockHeader(?curr).tag = $D1F5D1F5)) AND
(LongInt (BlockHeader(?curr).line >= 0)))
THEN BEGIN
?bytesLeaked := ?bytesLeaked + BlockHeader(?curr).size;
?leakCount := ?leakCount + 1;
END;
?curr := BlockHeader(?curr).next;
END;
?i := ?i + 1;
END;
IF ?count <> gDebugNewAllocCount THEN
writeln ('Warning: length of block list different from count of
allocated blocks (internal error).');
writeln ('Maximum #bytes allocated at any point via operator new: ',
gDebugNewAllocMax:LongInt);
IF ?leakCount = 0 THEN BEGIN
writeln('No memory leaks.');
?ReDirect;
PAUSE;
END;
IF ?leakCount = 1 THEN
writeln('There is 1 memory leak of ', ?bytesLeaked:LongInt, 'bytes:')
ELSE
writeln('There are ',?leakCount:LongInt,' memory leaks, totaling ',
?bytesLeaked:LongInt, ' bytes');
writeln(' size caller');
?totalAlloc := 0;
?i := 0;
WHILE ?i < ?kHashTableSize DO BEGIN
?curr := gBlockHash[?i];
WHILE ?curr <> 0 DO BEGIN
if (((BlockHeader(?curr).tag = $D1F3D1F3) OR (BlockHeader(?curr).tag
= $D1F5D1F5)) AND
(LongInt (BlockHeader(?curr).line >= 0)))
THEN BEGIN
write(' ', BlockHeader(?curr).size:LongInt, ' ');
IF BlockHeader(?curr).file <> 0 THEN
write('File: ', BlockHeader(?curr).file:CString, '; Line: ',
BlockHeader(?curr).line)
ELSE IF BlockHeader(?curr).line <> 0 THEN
write(BlockHeader(?curr).line:ProcPtr)
ELSE
write(' ');
IF BlockHeader(?curr).padSize > 0 THEN
write(' (compiler-inserted padding: ',
BlockHeader(?curr).padSize, ')');
writeln;
END;
?totalAlloc := ?totalAlloc + BlockHeader(?curr).size;
?curr := BlockHeader(?curr).next;
END;
?i := ?i + 1;
END;
IF ?totalAlloc <> gDebugNewAllocCurr THEN
writeln('Warning: total allocations in block list different from
gDebugNewAllocCurr.');
?ReDirect;
)
---- End patches
Modified Sept 9, 1999