;-------------------------------------------------------------------------------
; The idea is to make everything concerning debugger in  one separate thread
; and to command it through some commands.
;-------------------------------------------------------------------------------

;-----------------------------------------------------------
; Global variables for synchronization between Fresh and
; debugger.
;-----------------------------------------------------------
uglobal
  dbgStatus  dd ?

  align 16
  dbgEvent   TDebugEvent

  align 4
  dbgContext CONTEXT
  align 4
  dbgSInfo   STARTUPINFO
  align 4
  dbgPInfo   PROCESSINFO

  dbgCommand  dd ?

  intBreakAddr dd ?         ; internal breakpoint.
  intBreakOrig dd ?

  instrBuffer rb 256

  LastKnownLine dd ?

endg


byteInt3 = $cc



;-----------------------------------------------------------
; Thread procedure for debug server.
;-----------------------------------------------------------
;
; TODO: The debug server have to be implemented to get
;       commands from the message queue of the thread
;       not to poll the command variable.
;       this will ensure low resource consumpsion and
;       will not need wait states while server is busy.
;

proc DebuggerThread, .hFileName
begin
; Run the debugged program.
        mov     ecx, sizeof.STARTUPINFO/4
        mov     edi, dbgSInfo
        xor     eax, eax
        rep stosd

        mov     [dbgSInfo.cb], sizeof.STARTUPINFO

        stdcall utf8ToWideChar, [CompiledFileName]
        push    eax

        invoke  CreateProcessW, eax, NULL, NULL, NULL, FALSE,                            \
                               NORMAL_PRIORITY_CLASS or DEBUG_PROCESS or DEBUG_ONLY_THIS_PROCESS or CREATE_NEW_CONSOLE, \
                               NULL, NULL, dbgSInfo, dbgPInfo
        stdcall FreeMem ; from the stack

        test    eax,eax
        jz      .StartError

        stdcall SetDbgStatus, dsRunning
        mov     [dbgCommand], 0

.debuggerloop:
        invoke  WaitForDebugEvent, dbgEvent, 100
        test    eax, eax
        jnz     .event_raised

.commandloop:
        cmp     [dbgCommand], 0
        je      .debuggerloop

        call    ExecuteCommand
        jc      .event_raised

        cmp     [dbgStatus], dsPaused
        je      .commandloop

        cmp     [dbgStatus], dsRunning
        je      .debuggerloop

        xor     eax, eax
        return

;------------- On debugger event raised --------------------------
.event_raised:
        mov     [dbgCommand], 0
        stdcall SetDbgStatus, dsPaused

        invoke  SendMessageW, [hMainForm], DM_EVENTRAISED, 0, 0        ; Notify main window

        mov     [dbgCommand], eax
        call    ExecuteCommand
        jc      .event_raised

        cmp     [dbgStatus], dsFinished
        jne     .debuggerloop

        xor     eax, eax
        return

.StartError:
        mov     eax, 1
        return
endp


ExecuteCommand:
        cmp     [dbgCommand], 0
        je      .exit

        mov     ebx, [dbgCommand]

.docommand:
        cmp     ebx, dbgcContinue
        je      .continue

        cmp     ebx, dbgcContinueNotHandled
        je      .continuenh

        cmp     ebx, dbgcKillProcess
        je      .killprocess

        cmp     ebx, dbgcFinish
        je      .finish

        cmp     ebx, dbgcPause
        je      .pause

        cmp     ebx, dbgcStepInto
        je      .stepinto

        cmp     ebx, dbgcStepOver
        je      .stepover

.endcommand:
        mov     [dbgCommand], 0
.exit:
        clc
        ret

.endcommand_with_event:
        mov     [dbgCommand], 0
        stc
        ret


.stepover:
; find the next assembled line.
        mov     ecx, [ptrDebugInfo]
        mov     ecx, [ecx+TArray.count]
        imul    ecx, sizeof.TDebugLine
        lea     ecx, [ecx+TArray.array]
        add     ecx, [ptrDebugInfo]

        mov     eax, [LastKnownLine]
        mov     edx, [eax+TDebugLine.ptrFile]
        mov     ebx, [eax+TDebugLine.LineNum]

.nextsearch:
        add     eax, sizeof.TDebugLine

        cmp     eax, ecx
        jae     .endcommand

        cmp     edx, [eax+TDebugLine.ptrFile]
        jne     .foundnext
        cmp     ebx, [eax+TDebugLine.LineNum]
        je      .nextsearch

.foundnext:
        mov     ecx, dword [eax+TDebugLine.Address]
        mov     [intBreakAddr], ecx

; put int3 at it.
        invoke  ReadProcessMemory, [dbgPInfo.hProcess], [intBreakAddr], intBreakOrig, 1, NULL
        mov     [instrBuffer], byteInt3
        invoke  WriteProcessMemory, [dbgPInfo.hProcess], [intBreakAddr], instrBuffer, 1, NULL

; then continue the program.
        invoke  ContinueDebugEvent, [dbgEvent.dwProcessId], [dbgEvent.dwThreadId], DBG_CONTINUE

; wait for event to become.
        invoke  WaitForDebugEvent, dbgEvent, 1000   ; wait to stop again.
        push    eax

; restore original instruction.
        invoke  WriteProcessMemory, [dbgPInfo.hProcess], [intBreakAddr], intBreakOrig, 1, NULL

; check for timeout.
        pop     eax
        test    eax, eax
        jnz     .have_event

        stdcall SetDbgStatus, dsRunning    ; if timeout then go pause the process. something goes wrong.
        jmp     .endcommand

; check for EXCEPTION_BREAKPOINT. If another event, go pause.
.have_event:
        cmp     [dbgEvent.dwDebugEventCode], EXCEPTION_DEBUG_EVENT
        jne     .endcommand_with_event

        cmp     [dbgEvent.Exception.ExceptionRecord.ExceptionCode], EXCEPTION_BREAKPOINT
        jne     .endcommand_with_event

        mov     [dbgContext.ContextFlags], CONTEXT_FULL
        invoke  GetThreadContext, [dbgPInfo.hThread], dbgContext

; it is breakpoint, but is it the right breakpoint?
        mov     eax, [dbgContext.regEip]
        dec     eax
        cmp     eax, [intBreakAddr]
        jne     .eipok
        mov     [dbgContext.regEip], eax
        invoke  SetThreadContext, [dbgPInfo.hThread], dbgContext

.eipok:
; update editors and display
        stdcall FindAddressPosition, [dbgContext.regEip]
        test    eax,eax
        jz      .unknownposition

        mov     [LastKnownLine], eax
        stdcall PosEditor, [eax+TDebugLine.ptrFile], [eax+TDebugLine.LineNum]
        invoke  SendMessageW, [hLister], LM_SHOWLIST, eax, 0

        invoke  ShowWindow, [hPropEditor], SW_SHOW
        jmp     .endcommand

.unknownposition:
        invoke  ShowWindow, [hPropEditor], SW_HIDE
        jmp     .continue


.stepinto:
        mov     [dbgContext.ContextFlags], CONTEXT_CONTROL
        invoke  GetThreadContext, [dbgPInfo.hThread], dbgContext
        or      [dbgContext.regFlag], $100
        invoke  SetThreadContext, [dbgPInfo.hThread], dbgContext
        invoke  ContinueDebugEvent, [dbgEvent.dwProcessId], [dbgEvent.dwThreadId], DBG_CONTINUE
        jmp     .endcommand


.continue:
        invoke  ContinueDebugEvent, [dbgEvent.dwProcessId], [dbgEvent.dwThreadId], DBG_CONTINUE
        stdcall SetDbgStatus, dsRunning
        jmp     .endcommand

.continuenh:
        invoke  ContinueDebugEvent, [dbgEvent.dwProcessId], [dbgEvent.dwThreadId], DBG_EXCEPTION_NOT_HANDLED
        stdcall SetDbgStatus, dsRunning
        jmp     .endcommand

.getcontext:
        invoke  GetThreadContext, [dbgPInfo.hThread], dbgContext
        jmp     .endcommand

.pause:
        invoke  SuspendThread, [dbgPInfo.hThread]
        mov     [dbgContext.ContextFlags], CONTEXT_CONTROL
        invoke  GetThreadContext, [dbgPInfo.hThread], dbgContext
        or      [dbgContext.regFlag], $100
        invoke  SetThreadContext, [dbgPInfo.hThread], dbgContext
        invoke  ResumeThread, [dbgPInfo.hThread]
        jmp     .endcommand


.killprocess:
        invoke  TerminateProcess, [dbgPInfo.hProcess], -1
        mov     [dbgEvent.dwDebugEventCode], EXIT_PROCESS_DEBUG_EVENT
        mov     [dbgEvent.ExitProcess.dwExitCode], -1
        invoke  SendMessageW, [hMainForm], DM_EVENTRAISED, 0, 0

.finish:
        invoke  ContinueDebugEvent, [dbgEvent.dwProcessId], [dbgEvent.dwThreadId], DBG_CONTINUE
        stdcall SetDbgStatus, dsFinished

        invoke  CloseHandle, [dbgPInfo.hThread]
        invoke  CloseHandle, [dbgPInfo.hProcess]

        ret



proc SetDbgStatus, .status
begin
        mov     eax, [.status]
        cmp     [dbgStatus], eax
        je      .finish

        mov     [dbgStatus], eax
        invoke  PostMessageW, [hMainForm], DM_STATUSCHANGED, 0, 0

.finish:
        return
endp