* ===========================     MemTrace     ===========================
*
* Monitors calls to AllocMem and FreeMem for tracking down wayward memory.
* Uses two buffers to store the size, address, and requesting task.  Each
* buffer area "wraps around" when filled; data may be lost, but no harm
* will be done.
*
* Two commands are accepted from the input stream:
*    'B' ==> prints out the buffer (after switching buffers)
*    'E' ==> exits from the program.
*
* Written by William S. Hawes (1987)
* ========================================================================

         SECTION  MemTrace,CODE
         INCLUDE  "exec/types.i"
         INCLUDE  "exec/nodes.i"
         INCLUDE  "exec/libraries.i"

* Macro to call a library function (assumes library pointer is in A6)
CALLSYS  MACRO    * FunctionName
         CALLLIB  _LVO\1
         ENDM

* Macro to define an external library entry point (offset)
XLIB     MACRO    * FunctionName
         XREF     _LVO\1
         ENDM

         XREF     _AbsExecBase
         XREF     _printf

         XDEF     _DOSBase
         XDEF     _stdout

         XLIB     AllocMem
         XLIB     CloseLibrary
         XLIB     Disable
         XLIB     Enable
         XLIB     FindTask
         XLIB     FreeMem
         XLIB     OpenLibrary
         XLIB     SetFunction

         XLIB     Input
         XLIB     Output
         XLIB     Read

STACKBUF EQU      32
MAXCNT   EQU      128                     ; maximum number of entries

start:   movea.l  _AbsExecBase,a6
         lea      -STACKBUF(sp),sp

         lea      DOSlib(pc),a1
         moveq    #0,d0
         CALLSYS  OpenLibrary
         move.l   d0,d6
         move.l   d0,_DOSBase
         beq      Exit                 ; error??

         ; Allocate the buffer areas (before patching the functions!)

         move.l   #MAXCNT*16,d0        ; MAXCNT 16-byte entries
         move.l   d0,Save2             ; store offset
         add.l    d0,d0                ; two buffers ...
         move.l   #(1<<16)!(1<<0),d1   ; MEMF_PUBLIC!MEMF_CLEAR
         CALLSYS  AllocMem
         move.l   d0,Save1             ; first buffer
         add.l    d0,Save2             ; second buffer
         move.l   d0,Buffer            ; current buffer
         beq      CloseDOS             ; error ...

         bsr      Zap                  ; zap the functions ...

         ; Get the input and output streams

         exg      d6,a6
         CALLSYS  Input
         move.l   d0,d4
         CALLSYS  Output
         move.l   d0,_stdout
         exg      d6,a6

         ; Wait for a character: 'B' or 'E'

1$:      move.l   d4,d1                ; input filehandle
         move.l   sp,d2                ; buffer area
         moveq    #STACKBUF,d3         ; buffer size
         exg      d6,a6
         CALLSYS  Read
         exg      d6,a6
         tst.l    d0                   ; get one?
         beq.s    1$                   ; no

         move.b   (sp),d0              ; the character ...
         bclr     #5,d0                ; uppercase
         subi.b   #'B',d0              ; 'Begin'?
         beq.s    2$                   ; yes
         subq.b   #'E'-'B',d0          ; 'End'?
         beq.s    10$                  ; yes
         bra.s    1$                   ; ignore

         ; Swap the buffer areas so the one we're printing doesn't get
         ; overwritten.

2$:      movea.l  Save1,a0             ; first buffer area
         movea.l  Save2,a1             ; second buffer area
         cmpa.l   Buffer,a0            ; using first buffer?
         beq.s    4$                   ; yes
3$:      exg      a0,a1                ; no ... swap pointers

4$:      clr.l    (a1)                 ; reset count in new buffer
         move.l   a1,Buffer            ; switch buffers
         move.l   (a0),d2              ; number of entries
         lea      16(a0),a2            ; start of save frame array

         ; Label the columns ... 

         pea      label(pc)
         jsr      _printf
         addq.l   #4,sp
         bra.s    7$                   ; jump in ...

         ; Loop through the captured size/task/address items.  First, we
         ; select the format string.

5$:      lea      afmt(pc),a0          ; AllocMem format
         tst.l    0(a2)                ; allocation?
         beq.s    6$                   ; yes
         lea      ffmt(pc),a0          ; FreeMem format

6$:      move.l   4(a2),-(sp)          ; size
         move.l   12(a2),-(sp)         ; address
         move.l   8(a2),-(sp)          ; task ID
         move.l   a0,-(sp)             ; format string
         jsr      _printf
         lea      16(sp),sp
         lea      16(a2),a2            ; advance array pointer
7$:      dbra     d2,5$                ; loop back
         bra      1$

         ; Restore the library functions and exit ...

10$:     bsr      UnZap                ; unpatch the entries

         movea.l  Save1,a1             ; buffer
         move.l   #MAXCNT*32,d0        ; total length
         CALLSYS  FreeMem

         ; Close the DOS library 

CloseDOS:
         movea.l  d6,a1
         CALLSYS  CloseLibrary

Exit:
         lea      STACKBUF(sp),sp
         rts

         ; Zap the EXEC library AllocMem and FreeMem functions ...

Zap:     CALLSYS  Disable              ; no interruptions, please
         move.w   #_LVOAllocMem,a0     ; offset
         movea.l  a6,a1                ; library
         move.l   #AllocChk,d0         ; new (monitor) function
         CALLSYS  SetFunction
         move.l   d0,AllocFun          ; save original value

         move.w   #_LVOFreeMem,a0
         movea.l  a6,a1
         move.l   #FreeChk,d0          ; monitor function
         CALLSYS  SetFunction
         move.l   d0,FreeFun
         CALLSYS  Enable
         rts

         ; Restore the library entry points ...

UnZap:   CALLSYS  Disable
         move.w   #_LVOAllocMem,a0
         movea.l  a6,a1
         move.l   AllocFun,d0
         CALLSYS  SetFunction

         move.w   #_LVOFreeMem,a0
         movea.l  a6,a1
         move.l   FreeFun,d0
         CALLSYS  SetFunction
         CALLSYS  Enable
         rts

         ; Static storage area ...

_stdout  dc.l     0
_DOSBase dc.l     0
AllocFun dc.l     0                    ; real AllocMem function
FreeFun  dc.l     0                    ; real FreeMem
Buffer   dc.l     0                    ; current buffer
Save1    dc.l     0                    ; first buffer
Save2    dc.l     0                    ; second buffer

label    dc.b     '           AllocMem     FreeMem  ',10
         dc.b     'Task ID   Addr  Size   Addr  Size',10,0
afmt     dc.b     '%6lx: %6lx  %4ld             ',10,0
ffmt     dc.b     '%6lx:              %6lx  %4ld',10,0
DOSlib   dc.b     'dos.library',0
         CNOP     0,2

         ; AllocMem monitor function.  This code will be executed by
         ; any function that calls AllocMem.

AllocChk:
         movem.l  d2/a2,-(sp)
         movem.l  d0/d1,-(sp)

         ; Disable interrupts while we check where to put the data ...

         CALLSYS  Disable
         movea.l  Buffer,a2            ; current buffer
         cmpi.l   #MAXCNT-1,(a2)       ; count too big?
         bcs.s    1$                   ; no
         clr.l    (a2)                 ; reset
1$:      move.l   (a2),d2              ; count
         lsl.l    #4,d2                ; scale for 16 bytes
         addq.l   #1,(a2)              ; increment count
         lea      16(a2),a2            ; first save frame
         CALLSYS  Enable

         ; See who asked for it ...

         suba.l   a1,a1                ; see
         CALLSYS  FindTask
         movea.l  d0,a0
         movem.l  (sp)+,d0/d1          ; restore size and attributes

         ; Special (conditional) code to trap certain references ...

         IFD      SPECIAL
         cmp.l    #20,d0               ; 20 bytes?
         bne.s    2$                   ; no 
         trap     #11                  ; gotcha!
2$:
         ENDC

         lea      0(a2,d2.l),a2        ; start of frame
         clr.l    (a2)+                ; clear flag
         move.l   d0,(a2)+             ; save size
         move.l   a0,(a2)+             ; save task
         movea.l  AllocFun,a0
         jsr      (a0)                 ; call AllocMem
         move.l   d0,(a2)              ; save address
         movem.l  (sp)+,d2/a2
         rts

         ; Monitor FreeMem activity ...

FreeChk:
         movem.l  d2/a2,-(sp)
         movem.l  d0/a1,-(sp)          ; save size and address

         CALLSYS  Disable
         movea.l  Buffer,a2            ; buffer area
         cmpi.l   #MAXCNT-1,(a2)       ; count too big?
         bcs.s    1$                   ; no
         clr.l    (a2)                 ; reset it
1$:      move.l   (a2),d2              ; current item count
         lsl.l    #4,d2                ; scale for 16 bytes
         addq.l   #1,(a2)              ; increment count
         lea      16(a2),a2            ; first save frame
         CALLSYS  Enable

         ; See who asked for it ...

         suba.l   a1,a1
         CALLSYS  FindTask
         movea.l  d0,a0                ; requesting task

         movem.l  (sp)+,d0/a1
         lea      0(a2,d2.l),a2        ; start of frame
         move.l   #-1,(a2)+            ; set flag
         move.l   d0,(a2)+             ; save size
         move.l   a0,(a2)+             ; save task
         move.l   a1,(a2)              ; save address
         movea.l  FreeFun,a0
         jsr      (a0)                 ; call FreeMem
         movem.l  (sp)+,d2/a2
         rts

         END
