; --------------------------------------------------------------------------------
; @Title: Script to debug a Windows process from main
; @Description:
;   Application Debugging
;
;   This script waits for an application to be started,
;   loads the symbols and halts the application at main()
;
;   NOTE: Windows and the Windows awareness must be up.
;
;   Start this script with the process name (without .exe)
;   as argument to run the script as command line version
;   or use /dialog to run the script in a dialog.
;
;   Examples:
;     do app_debug hello /stopat main     ; waits for "hello.exe" to be started
;     do app_debug /dialog    ; opens a dialog window
;
;   Prerequisites:
;   - Windows must be booted
;   - Windows awareness must be configured
;   - Autoloader must be configured
;
; @Keywords: awareness, windows
; @Author: DIE
; @Copyright: (C) 1989-2018 Lauterbach GmbH, licensed for use with TRACE32(R) only
; --------------------------------------------------------------------------------
; $Id: app_debug.cmm 4754 2019-11-06 10:55:10Z rdienstbeck $


; Define local macros
 LOCAL  &para1 &para2 &para3 &skipHll
 LOCAL  &process &spaceid &dialog
 LOCAL  &breaklabel
 GLOBAL &true &false &breakaddr
 &true=(1==1)
 &false=(1==0)
 &breakaddr=0


; Get the script parameters ("<name> /stopat <label>" or "/dialog")
 ENTRY &para1 &para2 &para3


; Check parameters
 IF "&para1"==""
 (
    ; no parameter given -> print usage
    PRINT "Usage: ""do app_debug <processname> [/stopat <label>]"" or ""do app_debug /dialog"""
    ENDDO 1.
 )
 IF STRing.LoWeR("&para1")=="/dialog"
 (
    ; /dialog -> open a dialog to select process
    &dialog=&true
    &process=""
    &skipHll=TRUE()
    GOSUB parse_params
 )
 ELSE
 (
    ; parameter contains process name
    &dialog=&false
    &process="&para1"
    GOSUB parse_params
    &skipHll=("&para3"=="")
 )

 IF !sYmbol.EXIST(PsActiveProcessHead)
 (
    GOSUB message "Error: This action could not be performed: Please ensure that Kernel symbols are loaded!"
    ENDDO 1.
 )
; Check against TASK.Watch that interferes with this script
 ;if (task.watch.active()==1)
 ;(
 ;   gosub message "Please close TASK.Watch window" "before using this menu item"
 ;   enddo 1.
 ;)

;disable autoloader for libraries and modules
 sYmbol.AutoLOAD.Delete
 TASK.sYmbol.Option AutoLoad NoLibrary
 TASK.sYmbol.Option AutoLoad NoKModule


; Activate autoloader for processes:
 TASK.sYmbol.Option AutoLoad Process

; Ensure windows update
 SCREEN.ALways


; Dialog or Command line version?

 IF &dialog
   GOTO dialog


; --------------------------------------------------------------------------------
; Command line interface

 ; Check if the process already exists in the process list.

 LOCAL &existed &main

 GOSUB checkexisting &process
 ENTRY &existed
 IF &existed
   ENDDO 2.

 ; Wait for process to be loaded.
 ; The process must be started by executing it in the WinCE user interface.

 PRINT "Please start process &process..."
 GOSUB waitforstart &process

 ; Yep! The process is loaded and we found it.
 ; Now load the process symbols.

 PRINT "process &process started, loading symbols..."
 GOSUB loadsymbols &process &breaklabel
 ENTRY &main
 IF "&main"==""
    ENDDO 1.

 ; We got the main entry point of the process.
 ; We let the system run until it reaches main().

 PRINT "waiting for reaching main..."
 GOSUB waitformain &process &main

 // That's it, we halted at main()!

 PRINT "done."

 ENDDO 0.


; --------------------------------------------------------------------------------
; Dialog interface

dialog:
 DIALOG.view
 (
        HEADER "Debug New Process..."
        POS 0. 0. 24. 5.
        BOX "process name (without .exe)"
        POS 1. 1. 22. 1.
proc:   DEFHOTEDIT ""
        (
            IF DIALOG.STRing(proc)!=""
                DIALOG.Enable bok
            ELSE
                DIALOG.Disable bok
        )
        POS 0. 2. 24. 0.
        BOX "stop at"
        POS 1. 3. 22. 1.
stopl:  EDIT "main" "&skipHll=FALSE()"
        POS 2. 5.5  8.
bok:    DEFBUTTON "Ok"
        (
            LOCAL &process &existed &main
            DIALOG.Disable bok
            DIALOG.Disable proc
            &process=DIALOG.STRing(proc)
            DIALOG.Set mess "Checking process &process..."

            GOSUB checkexisting &process
            ENTRY &existed
            IF &existed
                JUMPTO close

            DIALOG.Set mess "Please start process &process"

            GOSUB waitforstart &process

            DIALOG.Set mess "Process &process started. Loading symbols..."

            &breaklabel=DIALOG.STRING(stopl)
            GOSUB loadsymbols &process &breaklabel
            ENTRY &main
            IF "&main"==""
                JUMPTO close

            DIALOG.Set mess "Waiting for reaching main..."
            &para3="&breaklabel"
            GOSUB waitformain &process &main

            JUMPTO close
        )
        POS 14. 5.5 8.
        BUTTON "Cancel" "JUMPTO close"
        POS 0. 7. 24. 1.
mess:   EDIT "Please enter process name" ""
        CLOSE "JUMPTO close"
 )

 DIALOG.Disable bok
 DIALOG.Disable mess

 STOP

close:

 IF &breakaddr!=0
 (
    IF STATE.RUN()
        Break.direct
    Break.Delete &breakaddr
    ON PBREAKAT &breakaddr
    &breakaddr=0
 )

 DIALOG.END
 ENDDO 0.


; --------------------------------------------------------------------------------
; Subroutine: Check if process already exists

checkexisting:
 ENTRY &process

 IF STATE.RUN()
    Break.direct
 IF (TASK.PROC.SPACEID("&process")&0xFFFFFFFF)!=0xFFFFFFFF
 (
    sYmbol.AutoLOAD.CLEAR "&process"
    sYmbol.AutoLOAD.CHECK
    sYmbol.AutoLOAD.TOUCH "&process"
    IF sYmbol.EXIST(\\&process)
        GOSUB message "Process &process already running." "Symbols loaded."
    ELSE
        GOSUB message "Process &process already running." "No symbol file found."
    RETURN &true
 )

; Delete possibly existing breakpoints of previous process runs

 IF sYmbol.EXIST("\\&process")
   Break.Delete sYmbol.SECRANGE(\\&process\.text)

 RETURN &false


; --------------------------------------------------------------------------------
; Subroutine: Wait for start of process

waitforstart:
 LOCAL &process &createthread
 ENTRY &process

; Wait for process to be loaded.
; The process must be started by executing it in the WinCE user interface.

 ; We cannot load process symbols as long as we do not
 ; know the address translation. The MMU for the new
 ; process is set up, right before it is started
 ; at NtCreateThread()

 &createthread="NtCreateThreadEx"
 IF !sYmbol.EXIST(&createthread)
 (
   &createthread="_NtCreateThreadEx@44"
   IF !sYmbol.EXIST(&createthread)
   (
     &createthread="NtCreateThreadEx"
     IF !sYmbol.EXIST(&createthread)
     (
          &createthread="_NtCreateThread@32"
     )
   )
 )

 ; we need access to the kernel symbols, ensure they are loaded

 IF !sYmbol.EXIST(&createthread)
 (
   TASK.sYmbol.Option AutoLoad KModule
   sYmbol.AutoLOAD.CHECK
   sYmbol.AutoLOAD.TOUCH "ntkr*"
   IF !sYmbol.EXIST(&createthread)
   (
     GOSUB message "Error: thread creation location not found!"
     ENDDO 1.
   )
 )

 ; The thread creation is called for every process to start -
 ; the conditional breakpoint halts only, if the desired process
 ; is found in the process table.

 &breakaddr=ADDRESS.OFFSET(&createthread)
 Break.Delete &breakaddr                    ; delete previous set breakpoints
 Break.Set &breakaddr /Program /CONDition (TASK.PROC.SPACEID("&process")&0xFFFFFFFF)!=0xFFFFFFFF

 ON PBREAKAT &breakaddr GOTO wfs_continue   ; if breakpoint reached: continue

 Go.direct         ; let the target run and load the process

 STOP       ; halt script until breakpoint reached

 ; breakpoint hit, continue script
wfs_continue:

 Break.Delete &breakaddr    // delete breakpoint
 &breakaddr=0
 ON PBREAKAT &breakaddr     // delete script action

 RETURN


; --------------------------------------------------------------------------------
; Subroutine: Load symbols of process

loadsymbols:
 LOCAL &process &main
 ENTRY &process &main

; Yep! The process is loaded and we found it.

; Now load the process symbols to the space id of the process

 sYmbol.AutoLOAD.CLEAR "&process"   ; clear possibly previous set
 sYmbol.AutoLoad.CHECK              ; force new autoloader list
 sYmbol.AutoLoad.TOUCH "&process"   ; force loading of process symbols


; Now set a breakpoint at the main entry point.

 ; NOTE: The code is still not available, so we MUST set
 ; onchip breakpoints, because those are the only ones
 ; operating on virtual addresses

 IF ("&main"=="")
 (
 ; There may be more "main" symbols in the system,
 ; we are searching for the right one.

   IF sYmbol.COUNT(\\&process\*\main)>0
      sYmbol.ForEach "eval ""*""" \\&process\*\main
   ELSE IF sYmbol.COUNT(\\&process\*\wmain)>0
      sYmbol.ForEach "eval ""*""" \\&process\*\wmain
   ELSE IF sYmbol.COUNT(\\&process\*\WinMain)>0
      sYmbol.ForEach "eval ""*""" \\&process\*\WinMain
   ELSE
   (
      GOSUB message "Symbol 'main' of process &process doesn't exist!"
      RETURN     ; return with empty return parameter
   )
 )
 ELSE
 (
   IF sYmbol.COUNT(\\&process\*\&main)>0
   (
     sYmbol.ForEach "eval ""*""" \\&process\*\&main
   )
   ELSE
   (
      GOSUB message "Symbol '&main' of process &process doesn't exist!"
      RETURN     ; return with empty return parameter
   )
 )
 &main=EVAL.STRing()

 RETURN &main


; --------------------------------------------------------------------------------
; Subroutine: Wait for reaching main

waitformain:
 LOCAL &process &main &breakaddr
 ENTRY &process &main


 IF (&skipHll)
 (
 ;; try to set breakpoint on 1 line below main()
 ;; to let the page load (demand paging!)
   &breakaddr="&main\1"
 )
 ELSE
 (
   ;set breakpoint at label entered by user
   &breakaddr="&main"
 )
 ;ON ERROR goto wfm_noline
 ;Break.Set &breakaddr /Program /Onchip /TASK "&process"  ; no thread yet created!
 Break.Set &breakaddr /Program
 ;goto wfm_setevent

;wfm_noline:

 ; settint BP on main + 1 line failed, probably no debug symbols
 ; try direct main

 ;&breakaddr="&main"
 ;ON ERROR ; remove error handler
 ;Break.Set &breakaddr /Program /Onchip /TASK "&process"

;wfm_setevent:

 ; if breakpoint reached: continue
 ON PBREAKAT ADDRESS.OFFSET(&breakaddr) GOTO wfm_continue

 Go.direct         ; let the target run and start the process

 STOP       ; halt script until breakpoint reached

 ; breakpoint hit, continue script
wfm_continue:

 Break.Delete &breakaddr    // delete breakpoint
 &breakaddr=0
 ON PBREAKAT &breakaddr     // delete script action

 ; reload process symbols to get correct address range

 ;sYmbol.AutoLoad.CHECK              ; force new autoloader list
 ;sYmbol.AutoLoad.TOUCH "&process"   ; force loading of process symbols

 RETURN


; --------------------------------------------------------------------------------
; Subroutine: print message in dialog box or command line

message:
 LOCAL &msg1 &msg2
 ENTRY &msg1 &msg2
 IF &dialog
    DIALOG.OK &msg1 &msg2
 ELSE
    PRINT &msg1 " " &msg2
 RETURN

; --------------------------------------------------------------------------------
;         Subroutine: parse script parameters
; --------------------------------------------------------------------------------

parse_params:
   LOCAL &arg &i
   &i=2.
   RePeaT
   (
     &&arg=STRING.CUT("&para&i",-1)
     IF "&arg"==""
       RETURN

     IF STRing.LoWeR("&arg")=="/stopat"
     (
       &i=&i+1
       &&arg=STRING.CUT("&para&i",-1)
       &breaklabel="&arg"
     )
     &i=&i+1
   )
  WHILE &i<=3
  RETURN
