; --------------------------------------------------------------------------------
; @Title: Symbian Process Debugging Script
; @Description: 
;   This script waits for a new process to be started,
;   loads the symbols and halts the process at E32Main()
;   NOTE: Symbian OS and the Symbian OS awareness must be up.
;   Start this script with the process name as argument (without .exe)
;   to run the script as command line version or use /dialog
;   to run the script in a dialog.
;   Examples:
;     do procentry Agenda     ; waits for "Agenda.exe" to be started
;     do procentry /dialog    ; opens a dialog window
;   This script runs ONLY for .exe processes, NOT for .app applications
;   Prerequisites:
;   - Symbian OS must be booted
;   - Symbian awareness must be configured
;   - Symbol Autoloader must be configured
; @Keywords: awareness
; @Author: DIE
; @Copyright: (C) 1989-2019 Lauterbach GmbH, licensed for use with TRACE32(R) only
; --------------------------------------------------------------------------------
; $Id: procentry.cmm 15223 2019-11-05 16:29:45Z bschroefel $


; Define local macros
 LOCAL  &para1 &dialog &procname &breakaddr
 LOCAL  &true &false
 &true=(1==1)
 &false=(1==0)
 &breakaddr=0

 
; Get the script parameter (process name or "/dialog")
 ENTRY &para1


; 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
 )


; Check parameters
 IF "&para1"==""
 (
    ; no parameter given -> print usage
    PRINT "Usage: ""do procentry <processname>"" or ""do app_debug /dialog"""
    ENDDO
 )
 IF STRing.LoWeR("&para1")=="/dialog"
 (
    ; /dialog -> open a dialog to select process
    &dialog=&true
    &procname=""
 )
 ELSE
 (
    ; parameter contains process name
    &dialog=&false
    &procname="&para1"
 )


; Check process name for spaces
 
 IF (STRing.CHAR("&procname",0)=='"')
 (
    IF (STRing.CHAR("&procname",string.len("&procname")-1)=='"')
    (
       // strip quotes from process name
       &procname=&procname
    )
 )
   

; Dialog or Command line version?

 IF &dialog
   GOTO dialog


; --------------------------------------------------------------------------------
; Command line interface

 ; Check if the process already exists in the process list.

 LOCAL &existed
 
 GOSUB checkexisting "&procname"
 ENTRY &existed
 IF &existed
   ENDDO
   
 ; Wait for process to be loaded.
 
 PRINT "Please start process ""&procname""..."
 GOSUB waitforstart "&procname"
 ENTRY &failed
 IF &failed
   ENDDO

 ; Yep! The process is loaded and we found it.
 ; Now load the process symbols.

 PRINT "process ""&procname"" started, loading symbols..."
 GOSUB loadsymbols "&procname"
 ENTRY &main
 IF &main==0
    ENDDO

 ; We got the main entry point of the process.
 ; We let the system run until it reaches E32Main().

 PRINT "waiting for process ""&procname"" to reach E32Main..."
 GOSUB waitformain &main
 
 // That's it, we halted at E32Main()!

 PRINT "done."

 ENDDO
 
 
; --------------------------------------------------------------------------------
; Dialog interface 

dialog:
 DIALOG.view
 (
        HEADER "Debug Process on E32Main"
        POS 0. 0. 24. 3.
        BOX "process name (without .exe)"
        POS 1. 1. 22. 1.
proc:   DEFHOTEDIT "" 
        (
            IF DIALOG.STRing(proc)!=""
                DIALOG.Enable bok
            ELSE
                DIALOG.Disable bok
        )
        POS 2. 3. 8.
bok:    DEFBUTTON "Ok"
        (
            LOCAL &process
            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..."

            GOSUB loadsymbols "&process"
            ENTRY &main
            IF &main==0
                JUMPTO CLOSE

            DIALOG.Set mess "Waiting for reaching E32Main..."

            GOSUB waitformain &main
            
            JUMPTO CLOSE
        )
        POS 14. 3. 8.
        BUTTON "Cancel" "JUMPTO close"
        POS 0. 5. 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
    Break.Delete &breakaddr
    ON PBREAKAT &breakaddr
    &breakaddr=0
 )
 sYmbol.CASE ON
 DIALOG.END
 ENDDO


; --------------------------------------------------------------------------------
; Subroutine: Check if process already exists

checkexisting:
 LOCAL &procname &basename &len &space
 ENTRY &procname
 &procname=&procname        ; strip quotes

 IF STATE.RUN()
    Break
    
 IF task.proc.codeaddr("&procname")!=0xffffffff
 (
    LOCAL &spid &addr
    &spid=task.proc.spaceid("&procname")
    &addr=task.proc.codeaddr("&procname")
    sYmbol.AutoLOAD.CLEAR &spid:&addr
    sYmbol.AutoLOAD.CHECK
    sYmbol.AutoLOAD.TOUCH &spid:&addr
    &basename=task.proc.basename("&procname")
    sYmbol.CASE OFF
    IF sYmbol.COUNT(\\&basename\*\E32Main)>0
      GOSUB message "Process '&procname' already running. Symbols loaded."
    ELSE
      GOSUB message "Process '&procname' already running. No symbol file found."
    sYmbol.CASE ON
    RETURN &true
 )

; Delete possibly existing breakpoints of previous process runs
 
 // convert spaces to underscores for symbol path
 &len=STRing.LENgth("&progname")
 &space=STRing.SCAN("&progname"," ",0)
 WHILE &space!=-1
 (
     ; conversion necessary
     &progname=STRing.CUT("&progname",&space-&len)+"_"+STRing.CUT("&progname",&space+1)
     &space=STRing.SCAN("&progname"," ",0)
 )

 IF sYmbol.COUNT(\\&progname\*\E32Main)>0
   Break.Delete sYmbol.SECRANGE(\\&progname\ER_RO)

 RETURN &false


; --------------------------------------------------------------------------------
; Subroutine: Wait for start of process

waitforstart:
 LOCAL &procname
 ENTRY &procname
 &procname=&procname        ; strip quotes

; Wait for process to be loaded.

 ; ExecHandler::ProcessResume() is called for every process to start -
 ; the conditional breakpoint halts only, if the desired
 ; process is found in the process table.

 IF sYmbol.EXIST(ExecHandler::ProcessResume)                     ; ekern
    &breakaddr=ADDRESS.OFFSET(ExecHandler::ProcessResume)
 ELSE IF sYmbol.EXIST(ExecHandler::ProcessResume(DProcess*))     ; .symbol
    &breakaddr=ADDRESS.OFFSET(`ExecHandler::ProcessResume(DProcess*)`)
 ELSE IF task.sm.eventhandler()!=0                          ; kdebug
 (
    TASK.StopMode.Event ThrStart
    &breakaddr=task.sm.eventhandler()
 )
 ELSE
 (
    GOSUB message "Sorry: process resume function not found."
    RETURN &true
 )

 Break.Delete &breakaddr        ; delete previous set breakpoints
 Break.Set R:&breakaddr /CONDition task.proc.codeaddr("&procname")!=0xffffffff
 
 ; wait until process is loaded

 ON PBREAKAT &breakaddr GOTO continue1  ; if breakpoint reached: continue
 Go         ; let the target run and load the process
 
 STOP       ; halt script until breakpoint reached
 
 ; breakpoint hit, continue script
continue1:

 Break.Delete &breakaddr    // delete breakpoint
 ON PBREAKAT &breakaddr     // delete script action
 &breakaddr=0

 RETURN &false


; --------------------------------------------------------------------------------
; Subroutine: Load symbols of process

loadsymbols:
 LOCAL &procname &basename &spid &addr
 ENTRY &procname
 &procname=&procname        ; strip quotes

; Yep! The process is loaded and we found it.

; Now load the process symbols

 &spid=task.proc.spaceid("&procname")
 &addr=task.proc.codeaddr("&procname")
 sYmbol.AutoLOAD.CLEAR &spid:&addr      ; clear possibly previous set
 sYmbol.AutoLOAD.CHECK                  ; force new autoloader list
 sYmbol.AutoLOAD.TOUCH &spid:&addr      ; force loading of process symbols
 
 sYmbol.CASE OFF    ; to allow case insensitive process names


; Now set a breakpoint at it's main entry point.
 
 ; There may be more "E32Main" symbols in the system,
 ; we're searching for the right one.
 
 &basename=task.proc.basename("&procname")
 
 IF sYmbol.COUNT(\\&basename\*\E32Main)==0
 (
   GOSUB message "Symbol 'E32Main' of process '&procname' not found"
   sYmbol.CASE ON
   RETURN 0
 )
   
 Eval 0
 sYmbol.ForEach "Eval address.offset(*)" \\&basename\*\E32Main
 &breakaddr=EVAL()
 
 RETURN &breakaddr

; --------------------------------------------------------------------------------
; Subroutine: Wait for reaching main

waitformain:
 ENTRY &main
 
 &breakaddr=&main
 
 Break.Set &breakaddr       ; set breakpoint on E32Main

 ON PBREAKAT &breakaddr GOTO continue2      ; if breakpoint reached: continue
 Go         ; let the target run and start the process

 STOP       ; halt script until breakpoint reached

 ; breakpoint hit, continue script
continue2: 

 Break.Delete &breakaddr    // delete breakpoint
 ON PBREAKAT &breakaddr     // delete script action
 &breakaddr=0
 
 sYmbol.CASE ON
 
 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
