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