-> MemPatch v1.1 by Kyzer/CSG
-> records memory allocations

OPT PREPROCESS,OSVERSION=37

MODULE 'dos/dos', 'dos/dosextens', 'exec/memory', 'exec/nodes',
       'exec/ports', 'exec/tasks', 'exec/semaphores', '*clr', '*patch'

-> ReadArgs() template.
#define TEMPLATE 'THRESHOLD/N,LISTSIZE/N,PRI/N'
OBJECT myargs
  minsize, listsize, pri
ENDOBJECT


-> we maintain a list of these allocation structures.
OBJECT allocation
  free:CHAR          -> is this slot available?

  func:CHAR          -> which function is allocating? "M" or "V"

  size               -> size of allocation request
  flags              -> allocation flags
  result             -> result from allocator

->count              -> incremental count
->taskid             -> task/process address
->date:datestamp     -> datestamp

->calladdr           -> calling address
->hunk, offset       -> SegTracker information about calladdr
->segname[80]:ARRAY  -> name of segment that code is calling from.

  taskname[40]:ARRAY -> name of task/process
ENDOBJECT

DEF self, -> our task, and a signal to send to it from one of the
    sig,  -> patches (therefore, we don't need to busywait when
          -> there are no allocations happening)

    -> our patches, two of them
    p1=NIL:PTR TO patch, p2=NIL:PTR TO patch,

    -> a semaphore lock, so only one of the instances of the patch will
    -> access the global variables.
    lock:ss,

    -> our main list. It contains list_size many allocation entries.
    list:PTR TO allocation, list_size,

    -> if a patch finds no free entries, it increments the missed variable
    -> and exits, rather than blocking until a free entry is available.
    -> the main loop checks this variable once per round
    missed=0,
 
    -> the minimum size of allocation to consider logging
    min_size,

->  counter=0,  -> unique countup (not used)

    unknown[20]:STRING  -> for the use of taskname()

->-----------------------------------------------------------------------------

PROC main() HANDLE
  DEF rdargs, args:myargs, oldpri

  IF (sig := AllocSignal(-1)) = -1 THEN RETURN
  self := FindTask(NIL)

  -> initialise our lock
  clr(lock, SIZEOF ss)
  InitSemaphore(lock)

  -> process args
  clr(args, SIZEOF myargs)
  IF rdargs := ReadArgs(TEMPLATE, args, NIL); ELSE; Raise(); ENDIF

  -> threshold for can range from 0 to any size, default is 256
  min_size  := IF args.minsize  THEN Max(Long(args.minsize), 0) ELSE 256

  -> size of 'print buffer' can range from 1 to any size, default is 256
  list_size := IF args.listsize THEN Max(Long(args.listsize), 1) ELSE 256

  -> priority of printing task can range from -128 to 5, default is -40
  oldpri := SetTaskPri(self,
    IF args.pri THEN Bounds(Long(args.pri), -128, 5) ELSE -40
  )

  -> create the list of allocation entries
  NEW list[list_size]

  -> install and turn on the patches
  NEW p1.install(execbase, -198, {mempatch}, "M") -> AllocMem() patch
  NEW p2.install(execbase, -684, {mempatch}, "V") -> AllocVec() patch
  p1.enable()
  p2.enable()

  -> print title banner
  WriteF(
    'MemPatch v1.1 by Kyzer/CSG.\n'+
    'Press CTRL-D/CTRL-E to disable/enable output. Press CTRL-C to exit.\n'+
    'Minimum recording threshold is \d bytes. Buffer holds \d items.\n\n'+
    '\s\n\s---\n',
    min_size, list_size,    

    'Func | Task/Process name              | Size wanted | Flags    | Result',
    '-----|--------------------------------|-------------|----------|-------'
  )

  -> run mainloop until user wants to quit
  mainloop()

  WriteF('Finished.\n')

  p1.disable()
  p2.disable()

  IF p1.remove() AND p2.remove(); ELSE
    WriteF('Patch vectors in use. I''ll keep trying to quit.\n')
  ENDIF

  -> restore old priority to process
  SetTaskPri(self, oldpri)

EXCEPT DO
  PrintFault(IoErr(), NIL)
  IF p2 THEN END p2
  IF p1 THEN END p1
  IF rdargs THEN FreeArgs(rdargs)
  FreeSignal(sig)
ENDPROC

->-----------------------------------------------------------------------------
-> main loop processing items placed in the list (free=FALSE)

PROC mainloop()
  DEF n, mem:PTR TO allocation, sigs, enab=TRUE

  FOR n := 0 TO list_size-1 DO list[n].free := TRUE

  LOOP
    -> wait for a signal either from the patches or the user
    sigs := Wait(
      Shl(1, sig) OR
      SIGBREAKF_CTRL_C OR
      SIGBREAKF_CTRL_D OR
      SIGBREAKF_CTRL_E
    )

    IF sigs AND SIGBREAKF_CTRL_C THEN RETURN

    IF (sigs AND SIGBREAKF_CTRL_D) AND (enab = TRUE)
      p1.disable()
      p2.disable()
      enab := FALSE
      WriteF('Monitoring disabled. Press CTRL-E to enable it again.\n')
    ENDIF

    IF (sigs AND SIGBREAKF_CTRL_E) AND (enab = FALSE)
      p1.enable()
      p2.enable()
      enab := TRUE
      WriteF('Monitoring enabled. Press CTRL-D to disable it.\n')
    ENDIF

    -> if missed allocations value is nonzero, we should print this
    -> out and reset it. We need to own the semaphore to write to this
    -> variable, lest we miss an instance of the patch
    IF missed
       ObtainSemaphore(lock)
       n := missed
       missed := 0
       ReleaseSemaphore(lock)
       WriteF('Missed \d allocations\n', n)
    ENDIF
    
    -> run through the items in our list and print out the filled-in ones.
    -> after printing them, we set them free. As we are the only task that
    -> can free data, and there's only one of us, there are no forseen
    -> conflict problems for not using the lock.
    mem := list
    FOR n := 1 TO list_size
      IF mem.free = FALSE
        WriteF('[A\c] | \l\s[30] | \l\d[11] | \c\c\c\c\c\c\c\c | $\h\n',
          mem.func, mem.taskname, mem.size,
          IF mem.flags AND MEMF_PUBLIC   THEN "P" ELSE "-",
          IF mem.flags AND MEMF_CHIP     THEN "C" ELSE "-",
          IF mem.flags AND MEMF_FAST     THEN "F" ELSE "-",
          IF mem.flags AND MEMF_LOCAL    THEN "L" ELSE "-",
          IF mem.flags AND MEMF_24BITDMA THEN "D" ELSE "-",
          IF mem.flags AND MEMF_KICK     THEN "K" ELSE "-",
          IF mem.flags AND MEMF_CLEAR    THEN "X" ELSE "-",
          IF mem.flags AND MEMF_REVERSE  THEN "R" ELSE "-",
          mem.result
        )
        mem.free := TRUE  -> free up entry for reuse
      ENDIF
      mem++
    ENDFOR
  ENDLOOP
ENDPROC

->-----------------------------------------------------------------------------

PROC mempatch(regs:PTR TO registers, base, entry, func)
  DEF size, flags, result, mem:PTR TO allocation,
      n:REG, s:REG, l:REG

  size  := regs.d0
  flags := regs.d1
 
  -> call AllocMem()/AllocVec()
	MOVE.L	size,  D0
	MOVE.L	flags, D1
	MOVE.L	entry, A3
	MOVE.L	base,  A6
	JSR	(A3)
	MOVE.L	D0, result

  -> ignore allocations less that min_size
  IF size < min_size THEN RETURN result

  -> initialise the list
  IF mem := list

    -> we have to 'own the bone' so we don't collide with another instance
    -> of the patch.
    ObtainSemaphore(lock)

    -> traverse list for a free entry
    FOR n := 1 TO list_size
      IF mem.free

        -> fill in entry
        mem.func   := func
        mem.size   := size
        mem.flags  := flags
        mem.result := result
        s, l := taskname(FindTask(NIL))
        AstrCopy(mem.taskname, s, Min(l+1, 40))

        -> mark entry as filled
        mem.free := FALSE
        JUMP out
      ENDIF
      mem++ -> next item in list
    ENDFOR
    missed++  -> we came out of the loop unsuccessful - no spaces left

out:ReleaseSemaphore(lock)

    Signal(self, Shl(1, sig))
  ENDIF
ENDPROC result

->-----------------------------------------------------------------------------

PROC taskname(p:PTR TO process)
  -> return the name/len of task/process p,
  DEF cli:PTR TO commandlineinterface, name:PTR TO CHAR

  IF p.task.ln.type = NT_PROCESS THEN
    IF cli := BADDR(p.cli) THEN
      IF name := BADDR(cli.commandname) THEN
        IF name[] THEN RETURN name+1, name[]

  IF name := p.task.ln.name THEN
    IF name[] THEN RETURN name, StrLen(name)

  StringF(unknown, 'Unknown @ 0x\z\h[8]', p)
ENDPROC unknown, 20


-> other stuff

->PROC clinum(p:PTR TO process IS
->  IF p.task.ln.type = NT_PROCESS THEN p.tasknum ELSE -1

->  DEF segname, hunk, offset, stack:PTR TO LONG
->      mem.count    := counter++
->      mem.taskid   := FindTask(NIL)
->      DateStamp(mem.date)

->      mem.calladdr := regs.a7[]
->      Forbid()
->      segname, hunk, offset := findseg(mem.calladdr)
->      AstrCopy(mem.segname, IF segname THEN segname ELSE 'Unknown', 80)
->      mem.hunk := hunk;  mem.offset := offset
->      Permit()
