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