;--- CDBA, CDB-like debugger which uses the MS debug engine API.
;--- The source may also be used to create NTSDA, the NTSD variant.
;--- The source uses WinInc assembly include files.

	.386
	.model flat,stdcall
	option casemap:none
	option proc:private

	.nolist
	.nocref
WIN32_LEAN_AND_MEAN equ 1
	include windows.inc
	include dbgeng.inc
	include dbghelp.inc	;needed for SYMOPT_ values
	include stdlib.inc
	include string.inc
	include stdio.inc
	.list
	.cref

	includelib kernel32.lib
	includelib msvcrt.lib
	includelib dbgeng.lib
	includelib uuid.lib

DEF_PROCESS_FLAGS equ DEBUG_PROCESS
USEPROCESS2       equ 0	;1=use IDebugClient5 interface

;--- macros

lf	equ 10
cr	equ 13

CStr macro text:VARARG
local xxx
	.const
xxx	db text,0
	.code
	exitm <offset xxx>
	endm

dprintf macro fstr, items:VARARG
ifdef _DEBUG
	pushad
	ifnb <items>
		invoke printf, CStr(fstr), items
	else
		invoke printf, CStr(fstr)
	endif
	popad
endif
	endm

;--- event objects

CDebugEventCallbacks struct
	IDebugEventCallbacks <>
cnt	dword ?
CDebugEventCallbacks ends

CDebugInputCallbacks struct
	IDebugInputCallbacks <>
cnt	dword ?
CDebugInputCallbacks ends

CDebugOutputCallbacks struct
	IDebugOutputCallbacks <>
cnt	dword ?
CDebugOutputCallbacks ends

	.data

pDebugClient  PDEBUG_CLIENT  NULL
pDebugControl PDEBUG_CONTROL NULL
pDebugSymbols PDEBUG_SYMBOLS NULL

;pDebugEventCallbacks  PDEBUG_EVENT_CALLBACKS  NULL
;pDebugInputCallbacks  PDEBUG_INPUT_CALLBACKS  NULL
;pDebugOutputCallbacks PDEBUG_OUTPUT_CALLBACKS NULL

opbuf DEBUG_CREATE_PROCESS_OPTIONS < DEF_PROCESS_FLAGS, 0, 0, 0 >
pszFile       dd NULL
pid           dd 0		;-p process ID
pszSymbolPath dd NULL   ;-y
pszSrcPath    dd NULL   ;-srcpath
pszLogFile    dd NULL   ;-logo 
pszCommand    dd NULL   ;-c
pszCmdFile    dd NULL   ;-cf
attachflgs    dd 0      ;-pv
clflgs        dd 0
bStateChanged db 0

;--- clflgs values
CLF_DUMP              equ 1	;-z
CLF_LINES             equ 2	;-lines
CLF_IGNORE_INITIAL_BP equ 4	;-g
CLF_IGNORE_FINAL_BP   equ 8	;-G
CLF_BP_AT_ENTRY       equ 16;-j
CLF_ATTACH_ID         equ 32;-p

	.const

DebugEventCallbacksVtbl label IDebugEventCallbacksVtbl
	dd [email protected]
	dd [email protected]
	dd [email protected]
	dd [email protected]
	dd [email protected]
	dd [email protected]
	dd [email protected]
	dd [email protected]
	dd [email protected]
	dd [email protected]
	dd [email protected]
	dd [email protected]
	dd [email protected]
	dd [email protected]
	dd [email protected]
	dd [email protected]
	dd [email protected]
	
DebugInputCallbacksVtbl label IDebugInputCallbacksVtbl
	dd [email protected]
	dd [email protected]
	dd [email protected]
	dd [email protected]
	dd [email protected]

DebugOutputCallbacksVtbl label IDebugOutputCallbacksVtbl
	dd [email protected]
	dd [email protected]
	dd [email protected]
	dd [email protected]

;--- filter values for event INITIAL_BREAKPOINT
specfilters label DEBUG_SPECIFIC_FILTER_PARAMETERS
	DEBUG_SPECIFIC_FILTER_PARAMETERS < DEBUG_FILTER_BREAK, DEBUG_FILTER_GO_HANDLED >

ifdef ?NTSD
pgm  equ <"NTSD">
else
pgm  equ <"CDB">
endif

szUsage label byte
	db pgm,"A v0.78, Public Domain, japheth 2009.",lf
	db pgm,"-like debugger based on MS Debug Engine API.",lf
if USEPROCESS2
	db "Needs a somewhat recent version of DbgEng.dll,",lf
	db "which one might find in 'Debugging Tools for Windows'.",lf
endif
	db "usage: ",pgm,"A [ options ] filename",lf
	db "options:",lf
	db "  -2: run debuggee in separate console window",lf
	db "  -c ""<command>"": executes command at first debugger prompt",lf
	db "  -cf <file>: execute script file at first debugger prompt",lf
	db "  -g: ignore initial breakpoint",lf
	db "  -G: ignore final breakpoint",lf
	db "  -j: set breakpoint at program entry",lf
	db "  -lines: load line number info",lf
	db "  -logo <logfile>: open new log file",lf
	db "  -p <pid>: attach to a process",lf
	db "  -pv: noninvasive attachs",lf
	db "  -srcpath <source_path>: set source search path",lf
	db "  -y <symbols_path>: set symbols search path",lf
	db "  -z: assume file is a dump file",lf
	db 0

	.code

;--- get ONE argument from commandline

getarg proc uses esi edi cmdline:ptr BYTE, buffer:ptr byte, maxlen:dword

local bQuote:BYTE

	mov edi, buffer
	mov edx, maxlen
	add edx, edi
	dec edx
	mov bQuote, 0
	.while ( byte ptr [esi] && ( edi < edx ) )
		lodsb
		.if ( al == bQuote )
			.break .if ( al != [esi] )
			inc esi
			stosb
		.elseif ( al == '"' || al == "'" )
			mov bQuote, al
		.elseif ( !bQuote && ( al == ' ' || al == 9 ) )
			.break
		.else
			stosb
		.endif
	.endw
	mov al,0
	stosb
	.while ( byte ptr [esi] == ' ' || byte ptr [esi] == 9 )
		inc esi
	.endw
	mov eax, esi
	ret
	align 4
getarg endp

;--- get arguments from commandline until 'file' argument found

getargs proc uses esi cmdline:ptr byte

local nextarg:dword
local buffer[260]:byte

	mov esi, cmdline
	invoke getarg, esi, addr buffer, sizeof buffer	;skip first arg
	mov esi, eax
	.while ( byte ptr [esi] )
		invoke getarg, esi, addr buffer, sizeof buffer
		mov nextarg, eax
		dprintf <"getargs: >%s<",lf>,addr buffer
		mov al, buffer
		.if ( al == '-' || al == '/' )
			mov esi, offset cmdlineoptions
			.while (dword ptr [esi])
				lodsd
				lea ecx, buffer+1
				invoke strcmp, eax, ecx
				.break .if ( eax == 0 )
				add esi,4 ;skip option address
			.endw
			.if ( eax == 0 )
				lodsd
				call eax
				.if ( CARRY? )
					xor eax, eax
					ret
				.endif
			.else
				invoke printf, CStr("unknown option %s",lf), addr buffer
				xor eax, eax
				ret
			.endif
		.else
			invoke strlen, addr buffer
			inc eax
			invoke malloc, eax
			.if ( !eax )
				invoke printf, CStr("out of memory",lf)
				xor eax,eax
				ret
			.endif
			mov pszFile, eax
			invoke strcpy, pszFile, addr buffer
			.break
		.endif
		mov esi, nextarg
	.endw
	mov eax, esi
	ret

	align 4

cmdlineoptions label dword
	dd CStr("2"),       offset set_2
	dd CStr("c"),       offset set_c
	dd CStr("cf"),      offset set_cf
	dd CStr("G"),       offset set_G
	dd CStr("g"),       offset set_g
	dd CStr("j"),       offset set_j
	dd CStr("lines"),   offset set_lines
	dd CStr("logo"),    offset set_logo
	dd CStr("p"),       offset set_p
	dd CStr("pv"),      offset set_pv
	dd CStr("srcpath"), offset set_srcpath
	dd CStr("y"),       offset set_y
	dd CStr("z"),       offset set_z
	dd NULL

;--- get additional file parameter for an option
;--- eax = address of variable where to store param

getfilearg:
	push eax
	mov esi, nextarg
	invoke getarg, esi, addr buffer, sizeof buffer
	mov nextarg, eax
	invoke strlen, addr buffer
	inc eax
	invoke malloc, eax
	pop edx
	.if ( eax )
		mov [edx], eax
		mov edx, eax
		invoke strcpy, edx, addr buffer
	.endif
	retn

;--- cmdline option stubs

set_2:
	or opbuf.CreateFlags, CREATE_NEW_CONSOLE
	retn
set_c:
	mov eax, offset pszCommand
	call getfilearg
	clc
	retn
set_cf:
	mov eax, offset pszCmdFile
	call getfilearg
	clc
	retn
set_G:
	or clflgs, CLF_IGNORE_FINAL_BP
	retn
set_g:
	or clflgs, CLF_IGNORE_INITIAL_BP
	retn
set_j:
	or clflgs, CLF_BP_AT_ENTRY
	retn
set_lines:
	or clflgs, CLF_LINES
	retn
set_logo:
	mov eax, offset pszLogFile
	call getfilearg
	clc
	retn
set_p:
	mov esi, nextarg
	invoke getarg, esi, addr buffer, sizeof buffer
	mov nextarg, eax
	.if ( buffer == 0 )
		invoke printf, CStr("process ID missing",lf)
		stc
		retn
	.endif
	invoke atoi, addr buffer
	.if ( eax == 0 )
		invoke printf, CStr("bad process ID",lf)
		stc
		retn
	.endif
	or clflgs, CLF_ATTACH_ID
	mov pid, eax
	retn
set_pv:
	or attachflgs, DEBUG_ATTACH_NONINVASIVE
	retn
set_srcpath:
	mov eax, offset pszSrcPath
	call getfilearg
	clc
	retn
set_y:
	mov eax, offset pszSymbolPath
	call getfilearg
	clc
	retn
set_z:
	or clflgs, CLF_DUMP
	retn
	align 4

getargs endp

;--- make Ctrl-C break into debugger when debuggee is running

myctrlhandler proc dwCtrlType:dword

local dwStatus:dword

	.if ( pDebugControl )
if 0
		invoke vf( pDebugControl, IDebugControl, GetExecutionStatus ), addr dwStatus
		mov eax, dwStatus
		.if ( eax == DEBUG_STATUS_GO || eax == DEBUG_STATUS_STEP_OVER || \
			eax == DEBUG_STATUS_STEP_INTO || eax == DEBUG_STATUS_STEP_BRANCH )
			invoke vf( pDebugControl, IDebugControl, SetInterrupt ), DEBUG_INTERRUPT_ACTIVE
			invoke printf, CStr("^C",lf)
		.endif
else
		invoke vf( pDebugControl, IDebugControl, SetInterrupt ), DEBUG_INTERRUPT_ACTIVE
		invoke printf, CStr("^C",lf)
endif
		mov eax,1
		ret
	.endif
	xor eax, eax
	ret
	align 4
myctrlhandler endp

main proc c uses ebx

local rc:byte
local cmdline:dword
local dwRead:dword
local buffer[256]:byte
local szCurrDir[260]:byte

ifdef ?NTSD
	invoke AllocConsole
	invoke freopen, CStr("CONIN$"), CStr("r"), stdin
	invoke freopen, CStr("CONOUT$"), CStr("w"), stdout
endif
	mov rc,1
	invoke GetCommandLine
	dprintf <"cmdline:>%s<",lf>, eax
	invoke getargs, eax
	and eax, eax
	jz exit_
	.if ( pszFile == NULL && pid == 0 )
		invoke printf, CStr("%s"), addr szUsage
		jmp exit_
	.endif
	mov cmdline, eax

;--- create objects IDebugClient(5), IDebugControl, IDebugSymbols

if USEPROCESS2
	invoke DebugCreate, addr IID_IDebugClient5, addr pDebugClient
else
	invoke DebugCreate, addr IID_IDebugClient, addr pDebugClient
endif
	.if ( eax != S_OK )
if USEPROCESS2
		invoke printf, CStr("unable to create IDebugClient5 object. DbgEng.dll too old?",lf)
else
		invoke printf, CStr("unable to create IDebugClient object.",lf)
endif
		jmp exit_
	.endif
	invoke vf( pDebugClient, IDebugClient, QueryInterface), addr IID_IDebugControl, addr pDebugControl
	.if ( eax != S_OK )
		invoke printf, CStr("IDebugClient:QueryInterface failed [%X]",lf), eax
		jmp exit_
	.endif
	invoke vf( pDebugClient, IDebugClient, QueryInterface), addr IID_IDebugSymbols, addr pDebugSymbols
	.if ( eax != S_OK )
		invoke printf, CStr("IDebugClient:QueryInterface failed [%X]",lf), eax
		jmp exit_
	.endif

;--- create and set callback objects Event, Input, Output

	invoke malloc, sizeof CDebugEventCallbacks
	.if ( !eax )
		invoke printf, CStr("out of memory",lf)
		jmp exit_
	.endif
	mov [eax].CDebugEventCallbacks.lpVtbl, offset DebugEventCallbacksVtbl
	mov [eax].CDebugEventCallbacks.cnt, 0
	invoke vf( pDebugClient, IDebugClient, SetEventCallbacks ), eax
	.if ( eax != S_OK )
		invoke printf, CStr("IDebugClient:SetEventCallbacks failed [%X]",lf), eax
	.endif

	invoke malloc, sizeof CDebugInputCallbacks
	.if ( !eax )
		invoke printf, CStr("out of memory",lf)
		jmp exit_
	.endif
	mov [eax].CDebugInputCallbacks.lpVtbl, offset DebugInputCallbacksVtbl
	mov [eax].CDebugInputCallbacks.cnt, 0
	invoke vf( pDebugClient, IDebugClient, SetInputCallbacks ), eax
	.if ( eax != S_OK )
		invoke printf, CStr("IDebugClient:SetInputCallbacks failed [%X]",lf), eax
	.endif

	invoke malloc, sizeof CDebugOutputCallbacks
	.if ( !eax )
		invoke printf, CStr("out of memory",lf)
		jmp exit_
	.endif
	mov [eax].CDebugOutputCallbacks.lpVtbl, offset DebugOutputCallbacksVtbl
	mov [eax].CDebugOutputCallbacks.cnt, 0
	invoke vf( pDebugClient, IDebugClient, SetOutputCallbacks ), eax
	.if ( eax != S_OK )
		invoke printf, CStr("IDebugClient:SetOutputCallbacks failed [%X]",lf), eax
	.endif
if 0
	invoke vf( pDebugClient, IDebugClient, SetOutputMask ), DEBUG_OUTPUT_NORMAL or DEBUG_OUTPUT_ERROR or DEBUG_OUTPUT_WARNING or DEBUG_OUTPUT_VERBOSE or DEBUG_OUTPUT_PROMPT
	.if ( eax != S_OK )
		invoke printf, CStr("IDebugClient:SetOutputMask failed [%X]",lf), eax
	.endif
endif

;--- open log file if requested
	.if ( pszLogFile )
		invoke vf( pDebugControl, IDebugControl, OpenLogFile ), pszLogFile, FALSE
		.if ( eax != S_OK )
			invoke printf, CStr("IDebugControl:OpenLogFile(%s) failed [%X]",lf), pszLogFile, eax
		.endif
	.endif

;--- set specific filters
;--- make debugger break at initial bp
	.if ( !(clflgs & CLF_IGNORE_INITIAL_BP) )
		invoke vf( pDebugControl, IDebugControl, SetSpecificFilterParameters ), DEBUG_FILTER_INITIAL_BREAKPOINT, 1, addr specfilters
		.if ( eax != S_OK )
			invoke printf, CStr("IDebugControl:SetSpecificFilterParameters() failed [%X]",lf), eax
		.endif
	.endif

	invoke SetConsoleCtrlHandler, offset myctrlhandler, TRUE

;--- set optional symbol path and source path

	.if ( pszSymbolPath )
		invoke vf( pDebugSymbols, IDebugSymbols, SetSymbolPath ), pszSymbolPath
		.if ( eax != S_OK )
			invoke printf, CStr("IDebugSymbols:SetSymbolPath(%s) failed [%X]",lf), pszSymbolPath, eax
		.endif
	.endif
	.if ( pszSrcPath )
		invoke vf( pDebugSymbols, IDebugSymbols, SetSourcePath ), pszSrcPath
		.if ( eax != S_OK )
			invoke printf, CStr("IDebugSymbols:SetSourcePath(%s) failed [%X]",lf), pszSrcPath, eax
		.endif
	.endif
;--- make symbol options compatible with MS CDB
	invoke vf( pDebugSymbols, IDebugSymbols, AddSymbolOptions ), \
		SYMOPT_CASE_INSENSITIVE or SYMOPT_UNDNAME or SYMOPT_DEFERRED_LOADS or SYMOPT_FAIL_CRITICAL_ERRORS
	.if ( eax != S_OK )
		invoke printf, CStr("IDebugSymbols:AddSymbolOptions() failed [%X]",lf), eax
	.endif

;--- try to open the file

	.if ( clflgs & CLF_DUMP )
		invoke vf( pDebugClient, IDebugClient, OpenDumpFile ), pszFile
		.if ( eax != S_OK )
			invoke printf, CStr("IDebugClient:OpenDumpFile(%s) failed [%X]",lf), pszFile, eax
			jmp exit_
		.endif
	.else
		.if ( clflgs & CLF_LINES )
			invoke vf( pDebugSymbols, IDebugSymbols, AddSymbolOptions ), SYMOPT_LOAD_LINES
		.endif
		xor ecx, ecx
		.if ( clflgs & CLF_ATTACH_ID )
			invoke vf( pDebugClient, IDebugClient, AttachProcess ), ecx::ecx,\
				pid, attachflgs
			.if ( eax != S_OK )
				invoke printf, CStr("IDebugClient:AttachProcess2(%u) failed [%X]",lf), pid, eax
				jmp exit_
			.endif
		.else
if USEPROCESS2
;--- requires IDebugClient5
			invoke vf( pDebugClient, IDebugClient5, CreateProcess2 ), ecx::ecx, cmdline, addr opbuf, \
				sizeof DEBUG_CREATE_PROCESS_OPTIONS, NULL, NULL
			.if ( eax != S_OK )
				invoke printf, CStr("IDebugClient:CreateProcess2(%s) failed [%X]",lf), pszFile, eax
				jmp exit_
			.endif
else
			invoke vf( pDebugClient, IDebugClient, CreateProcess ), ecx::ecx, cmdline, \
				opbuf.CreateFlags
			.if ( eax != S_OK )
				invoke printf, CStr("IDebugClient:CreateProcess(%s) failed [%X]",lf), pszFile, eax
				jmp exit_
			.endif
endif
		.endif
	.endif
	invoke vf( pDebugControl, IDebugControl, WaitForEvent ), 0, INFINITE
	mov rc,0

;--- process optional -c and -cf arguments
	.if ( pszCommand )
		invoke vf( pDebugControl, IDebugControl, Execute ), DEBUG_OUTCTL_THIS_CLIENT, pszCommand, 0
	.endif
	.if ( pszCmdFile )
		invoke vf( pDebugControl, IDebugControl, ExecuteCommandFile ), DEBUG_OUTCTL_THIS_CLIENT, pszCmdFile, DEBUG_EXECUTE_ECHO
	.endif

;--- main loop: read input and let the debug engine interpret it

	.while (1)
		invoke vf( pDebugControl, IDebugControl, GetExecutionStatus ), addr dwRead
		.if ( eax != S_OK )
			invoke printf, CStr("IDebugControl:GetExecutionStatus failed [%X]",lf), eax
			.break
		.endif

		mov eax, dwRead
;--- exit loop if user pressed 'q'
		.break .if ( eax == DEBUG_STATUS_NO_DEBUGGEE )

;--- if a debuggee runs, call WaitEvent and don't prompt
		.if ( eax == DEBUG_STATUS_GO || eax == DEBUG_STATUS_STEP_OVER || \
			eax == DEBUG_STATUS_STEP_INTO || eax == DEBUG_STATUS_STEP_BRANCH )
			invoke vf( pDebugControl, IDebugControl, WaitForEvent ), 0, INFINITE
			.continue .if ( eax == S_OK )
			; E_UNEXPECTED is returned if target has finished
			.if ( eax != E_UNEXPECTED )
				invoke printf, CStr("IDebugControl:WaitForEvent failed [%X]",lf), eax
			.endif
			.break
		.endif

;--- if debuggee state has changed, display registers and current instruction
		.if ( bStateChanged )
			mov bStateChanged, 0
			invoke vf( pDebugControl, IDebugControl, OutputCurrentState ), DEBUG_OUTCTL_THIS_CLIENT, DEBUG_CURRENT_DEFAULT
		.endif
		
;--- prompt for user input and run it
		invoke vf( pDebugControl, IDebugControl, OutputPrompt ), DEBUG_OUTCTL_THIS_CLIENT, NULL
		invoke vf( pDebugControl, IDebugControl, Input ), addr buffer, sizeof buffer, addr dwRead
		.if ( eax != S_OK )
			invoke printf, CStr("IDebugControl:Input failed [%X]",lf), eax
		.elseif ( dwRead )
			invoke vf( pDebugControl, IDebugControl, Execute ), DEBUG_OUTCTL_THIS_CLIENT, addr buffer, 0
		.endif
	.endw

exit_:
	.if ( pDebugClient )
		.if ( pDebugSymbols )
			invoke vf( pDebugSymbols, IUnknown, Release )
		.endif
		.if ( pDebugControl )
			invoke vf( pDebugControl, IUnknown, Release )
		.endif
		invoke vf( pDebugClient, IUnknown, Release )
	.endif
ifdef ?NTSD

@DefProto _CRTIMP, _getch, c, , <>

	.if ( rc )
		invoke printf, CStr(pgm,"A exiting - press enter ---")
		.repeat
			invoke _getch
		.until eax == cr
	.endif
	invoke FreeConsole
endif
	movzx eax, rc
	ret
	align 4

main endp

;--- IDebugEventCallbacks methods

[email protected] proc uses esi edi this_:ptr, pIID:ptr IID, pObject:ptr ptr
	dprintf <"[email protected] called",lf>
	mov ecx,4
	mov esi, pIID
	mov edi, offset IID_IDebugEventCallbacks
	repz cmpsd
	jz ok
	mov ecx,4
	mov esi, pIID
	mov edi, offset IID_IUnknown
	repz cmpsd
	jz ok
	mov eax, E_NOINTERFACE
	ret
ok:
	invoke vf( this_, IUnknown, AddRef )
	mov edx, this_
	mov ecx, pObject
	mov [ecx], edx
	mov eax, S_OK
	ret
	align 4
[email protected] endp

[email protected] proc this_:ptr
	dprintf <"[email protected] called",lf>
	mov ecx, this_
	inc [ecx].CDebugEventCallbacks.cnt
	mov eax, [ecx].CDebugEventCallbacks.cnt
	ret
	align 4
[email protected] endp

[email protected] proc this_:ptr
	dprintf <"[email protected] called",lf>
	mov ecx, this_
	dec [ecx].CDebugEventCallbacks.cnt
	mov eax, [ecx].CDebugEventCallbacks.cnt
	.if ( eax == 0 )
		invoke free, ecx
		xor eax, eax
	.endif
	ret
	align 4
[email protected] endp

[email protected] proc this_:ptr, Mask_:ptr DWORD
	dprintf <"[email protected] called",lf>
	mov ecx, this_
	mov edx, Mask_
	mov dword ptr [edx], DEBUG_EVENT_BREAKPOINT or DEBUG_EVENT_EXCEPTION or \
		DEBUG_EVENT_CREATE_PROCESS or DEBUG_EVENT_EXIT_PROCESS or \ 
		DEBUG_EVENT_LOAD_MODULE or DEBUG_EVENT_UNLOAD_MODULE or \ 
		DEBUG_EVENT_SYSTEM_ERROR or DEBUG_EVENT_SESSION_STATUS or \
		DEBUG_EVENT_CHANGE_DEBUGGEE_STATE or DEBUG_EVENT_CHANGE_ENGINE_STATE
	mov eax, S_OK
	ret
	align 4
[email protected] endp

;--- breakpoints, exceptions and system errors break into the debugger

[email protected] proc this_:ptr, brp:ptr ptr
	dprintf <"[email protected] called",lf>
	mov eax, DEBUG_STATUS_BREAK
	ret
	align 4
[email protected] endp

[email protected] proc this_:ptr, exc:ptr ptr, firstchance:DWORD
	dprintf <"[email protected](%X, %u)",lf>, exc, firstchance
	mov eax, DEBUG_STATUS_BREAK
	ret
	align 4
[email protected] endp

[email protected] proc this_:ptr, handle:qword, dataofs:qword, startofs:qword
	dprintf <"[email protected](%I64X, %I64X, %I64X)",lf>, handle, dataofs, startofs
	mov eax, DEBUG_STATUS_NO_CHANGE
	ret
	align 4
[email protected] endp

[email protected] proc this_:ptr, exitcode:dword
	dprintf <"[email protected](%u)",lf>, exitcode
	mov eax, DEBUG_STATUS_NO_CHANGE
	ret
	align 4
[email protected] endp

[email protected] proc this_:ptr, filehandle:qword, handle:qword,
		base:qword, modsize:dword, modname:ptr BYTE, imagename:ptr BYTE,
		checksum:dword, timedate:dword, initialthread:qword, threaddata:qword, 
		startofs:qword

local brkp:PDEBUG_BREAKPOINT

	dprintf <"[email protected](%I64X, %s, %I64X)",lf>, base, imagename, startofs
if 0
	invoke printf, CStr("process entry address: %I64X",lf), startofs
endif
;--- set breakpoint on program entry?
	.if ( clflgs & CLF_BP_AT_ENTRY )
		mov eax, dword ptr startofs+0
		or eax, dword ptr startofs+4
		.if ( eax )
			invoke vf( pDebugControl, IDebugControl, AddBreakpoint ), DEBUG_BREAKPOINT_CODE, DEBUG_ANY_ID, addr brkp
			.if ( eax == S_OK )
				invoke vf( brkp, IDebugBreakpoint, SetOffset ), startofs
				invoke vf( brkp, IDebugBreakpoint, AddFlags ), DEBUG_BREAKPOINT_ENABLED or DEBUG_BREAKPOINT_ONE_SHOT
			.else
				invoke printf, CStr("IDebugControl:AddBreakpoint failed [%X]",lf), eax
			.endif
		.endif
	.endif
	mov eax, DEBUG_STATUS_NO_CHANGE
	ret
	align 4
[email protected] endp

[email protected] proc this_:ptr, exitcode:dword
	dprintf <"[email protected](%u)",lf>, exitcode
	.if ( clflgs & CLF_IGNORE_FINAL_BP )
		mov eax, DEBUG_STATUS_NO_CHANGE
	.else
		mov eax, DEBUG_STATUS_BREAK
	.endif
	ret
	align 4
[email protected] endp

[email protected] proc this_:ptr, fh:qword, base:qword, modsize:dword, 
		modname:ptr BYTE, imagename:ptr BYTE, checksum:dword, timedate:dword
	dprintf <"[email protected](%I64X, %s)",lf>, base, imagename
	mov eax, DEBUG_STATUS_NO_CHANGE
	ret
	align 4
[email protected] endp

[email protected] proc this_:ptr, imagename:ptr BYTE, base:qword
	dprintf <"[email protected](%s, %X)",lf>, imagename, base
	mov eax, DEBUG_STATUS_NO_CHANGE
	ret
	align 4
[email protected] endp

[email protected] proc this_:ptr, error:dword, level:dword
	dprintf <"[email protected] called",lf>
	mov eax, DEBUG_STATUS_BREAK
	ret
	align 4
[email protected] endp

[email protected] proc this_:ptr, status:dword
	dprintf <"[email protected](%X)",lf>, status
	mov eax, DEBUG_STATUS_NO_CHANGE	;return value is ignored
	ret
	align 4
[email protected] endp

[email protected] proc this_:ptr, flags:dword, arg:qword
	dprintf <"[email protected](%X, %I64X)",lf>, flags, arg
	mov bStateChanged, 1
	mov eax, DEBUG_STATUS_NO_CHANGE	;return value is ignored
	ret
	align 4
[email protected] endp

[email protected] proc this_:ptr, flags:dword, arg:qword
	dprintf <"[email protected](%X, %I64X)",lf>, flags, arg
	mov eax, DEBUG_STATUS_NO_CHANGE	;return value is ignored
	ret
	align 4
[email protected] endp

[email protected] proc this_:ptr, flags:dword, arg:qword
	dprintf <"[email protected](%X, %I64X)",lf>, flags, arg
	mov eax, DEBUG_STATUS_NO_CHANGE	;return value is ignored
	ret
	align 4
[email protected] endp

;--- IDebugInputCallbacks methods

[email protected] proc uses esi edi this_:ptr, pIID:ptr IID, pObject:ptr ptr
	dprintf <"[email protected] called",lf>
	mov ecx,4
	mov esi, pIID
	mov edi, offset IID_IDebugInputCallbacks
	repz cmpsd
	jz ok
	mov ecx,4
	mov esi, pIID
	mov edi, offset IID_IUnknown
	repz cmpsd
	jz ok
	mov eax, E_NOINTERFACE
	ret
ok:
	invoke vf( this_, IUnknown, AddRef )
	mov edx, this_
	mov ecx, pObject
	mov [ecx], edx
	mov eax, S_OK
	ret
	align 4
[email protected] endp

[email protected] proc this_:ptr
	dprintf <"[email protected] called",lf>
	mov ecx, this_
	inc [ecx].CDebugInputCallbacks.cnt
	mov eax, [ecx].CDebugInputCallbacks.cnt
	ret
	align 4
[email protected] endp

[email protected] proc this_:ptr
	dprintf <"[email protected] called",lf>
	mov ecx, this_
	dec [ecx].CDebugInputCallbacks.cnt
	mov eax, [ecx].CDebugInputCallbacks.cnt
	.if ( eax == 0 )
		invoke free, ecx
		xor eax, eax
	.endif
	ret
	align 4
[email protected] endp

;--- read one line from file or pipe

ReadLine proc uses edi fh:dword, buffer:dword, maxlen:dword, bytesread:ptr dword

local dwRead:dword

	invoke fflush, stdout
	mov edi, buffer
	.while ( maxlen )
		invoke ReadFile, fh, edi, 1, addr dwRead, 0
		.break .if ( eax == 0 || dwRead == 0 )
		.continue .if ( byte ptr [edi] == cr )
		inc edi
		dec maxlen
		.break .if ( byte ptr [edi-1] == lf )
	.endw
	sub edi, buffer
	mov edx, bytesread
	mov [edx], edi
	mov eax, edi
	ret
	align 4
ReadLine endp

[email protected] proc uses ebx this_:ptr, BufferSize:DWORD

local dwRead:dword
local dwEsp:dword

	invoke GetStdHandle, STD_INPUT_HANDLE
	mov ebx, eax
	dprintf <"[email protected](%u)",lf>, BufferSize
	mov dwEsp, esp
	mov eax, BufferSize
	add eax, 3+4
	and al, 0FCh
	sub esp, eax
	invoke GetFileType, ebx 
	mov edx, esp
	mov byte ptr [edx], 0
	mov dwRead, 0
;--- if input isn't a device, read ONE line only
	.if ( eax != FILE_TYPE_CHAR )
		invoke ReadLine, ebx, edx, BufferSize, addr dwRead
	.else
		invoke ReadFile, ebx, edx, BufferSize, addr dwRead, NULL
	.endif
	mov ecx, dwRead
	.if ( ecx )
		; remove CR/LF
		.while (ecx && ( byte ptr [esp+ecx-1] == lf || byte ptr [esp+ecx-1] == cr ))
			mov byte ptr [esp+ecx-1],0
			dec ecx
		.endw
	.endif
	invoke vf( pDebugControl, IDebugControl, ReturnInput ), esp
	.if ( eax != S_OK )
		invoke printf, CStr("IDebugControl:ReturnInput failed [%X]",lf), eax
	.endif
	mov edx, esp
	dprintf <"[email protected]: dwRead=%u, read=>%s<",lf>, dwRead, edx
	mov esp, dwEsp
exit_:
	mov eax, S_OK
	ret
	align 4
[email protected] endp

[email protected] proc this_:ptr
;	dprintf <"[email protected] called",lf>
	mov eax, S_OK
	ret
	align 4
[email protected] endp

;--- IDebugOutputCallbacks methods

[email protected] proc uses esi edi this_:ptr, pIID:ptr IID, pObject:ptr ptr
	dprintf <"[email protected] called",lf>
	mov ecx,4
	mov esi, pIID
	mov edi, offset IID_IDebugOutputCallbacks
	repz cmpsd
	jz ok
	mov ecx,4
	mov esi, pIID
	mov edi, offset IID_IUnknown
	repz cmpsd
	jz ok
	mov eax, E_NOINTERFACE
	ret
ok:
	invoke vf( this_, IUnknown, AddRef )
	mov edx, this_
	mov ecx, pObject
	mov [ecx], edx
	mov eax, S_OK
	ret
	align 4
[email protected] endp

[email protected] proc this_:ptr
	dprintf <"[email protected] called",lf>
	mov ecx, this_
	inc [ecx].CDebugOutputCallbacks.cnt
	mov eax, [ecx].CDebugOutputCallbacks.cnt
	ret
	align 4
[email protected] endp

[email protected] proc this_:ptr
	dprintf <"[email protected] called",lf>
	mov ecx, this_
	dec [ecx].CDebugOutputCallbacks.cnt
	mov eax, [ecx].CDebugOutputCallbacks.cnt
	.if ( eax == 0 )
		invoke free, ecx
		xor eax, eax
	.endif
	ret
	align 4
[email protected] endp

[email protected] proc c this_:ptr, Mask_:DWORD, Text:ptr BYTE
;	dprintf <"[email protected] called",lf>
	invoke printf, CStr("%s"), Text
	mov eax, S_OK
	ret
	align 4
[email protected] endp

start proc c

	invoke main
	invoke ExitProcess, eax

start endp

	end start