getting started with Debugger Engine API

Hi,

I know this page is devoted to win kernel stuff but actually this is the only site where I could find any related information with code
about how to write code using Debugger Engine API documented here:
https://learn.microsoft.com/en-us/windows-hardware/drivers/debugger/introduction

So might I ask some help how to get started with this please. I tried various things but I am still unable to achieve a simple scenario like this:
1 attach to an existing process in user mode
2 break into debugger
3 go
4 detach
5 cleanup resources without getting errors

int main()
{
IDebugClient5* cl = 0;
if (HRESULT hr = DebugCreate(__uuidof(IDebugClient5), (void**)&cl); hr != S_OK)
bye(hr);
IDebugControl7* ctrl = 0;
if (HRESULT hr = cl->QueryInterface(__uuidof(IDebugControl7), (void**)&ctrl); hr != S_OK)
bye(hr);

StdOutputCallbacks stdoutCb;
if (HRESULT hr = cl->SetOutputCallbacks(&stdoutCb); hr != S_OK)
	bye(hr);
StdInputCallbacks stdinputCb;
stdinputCb.ctrl = ctrl;
if (HRESULT hr = cl->SetInputCallbacks(&stdinputCb); hr != S_OK)
	bye(hr);
DebugEventCallbacks dbgCb;
if (HRESULT hr = cl->SetEventCallbacks(&dbgCb); hr != S_OK)
	bye(hr);

char cmd[255];
ULONG inputSize = 0;
static volatile bool loop = true;

while (loop) {
	if (HRESULT hr = ctrl->Input(cmd, 255, &inputSize); hr != S_OK)
		std::cout << "*** WARNING *** Input failed with " << std::hex << hr << "\n";
	cmd[inputSize] = 0;
	if (!std::strcmp(cmd, "bb")) {
		ctrl->SetExecutionStatus(DEBUG_STATUS_BREAK);
		ctrl->WaitForEvent(0, INFINITE);
		continue;
	}
	if (!std::strcmp(cmd, "bye")) {
		break;
	}
	if (HRESULT hr = ctrl->Execute(DEBUG_OUTCTL_THIS_CLIENT, cmd, DEBUG_EXECUTE_ECHO); hr != S_OK) {
		std::cout << "*** WARNING *** Execute failed with " << std::hex << hr << "\n";
	}
}
if (HRESULT hr = ctrl->Release(); hr != S_OK)
	bye(hr);
if (HRESULT hr = cl->Release(); hr != S_OK)
	bye(hr);
std::cout << "Hello World!\n";

}

Actually I would have a lot of questions I don’t know how to organize them:

  1. why is this so difficult? Is this inherently as difficult or just it is me who approach this wrongly?
  2. after attaching to the process I cannot switch to the process while attaching in WinDbg I can switch to the process
  3. why there is no break command for ctrl->Execute? I checked it multiple times… and it seems it does not exist, how to break running application in a command line debugger?
  4. It seems msdn docs are far from being enough to me to learning this stuff where can I find some docs from which I can learn this?
  5. is this hard to write my own debugger? I have the bad feeling anytime I will have a problem with this I will be stuck…
  6. When I just tried to create the client and then the control I was unable to release them I tried all combinations and I got errors for all of them.
    I didn’t even attached to a target??? What is happening here? Actually what I did was something like this
    if (HRESULT hr = DebugCreate(__uuidof(IDebugClient5), (void**)&cl); hr != S_OK)
    bye(hr);
    IDebugControl7* ctrl = 0;
    if (HRESULT hr = cl->QueryInterface(__uuidof(IDebugControl7), (void**)&ctrl); hr != S_OK)
    bye(hr);
    if (HRESULT hr = ctrl->Release(); hr != S_OK)
    bye(hr);
    if (HRESULT hr = cl->Release(); hr != S_OK)
    bye(hr);
  7. Isn’t there any opensource code where this win api is used? but actually I would be happier with a tutorial than a full featured debugger code…

Any help would be really appreciated. Thanks in advance.

OUTPUT

.tlist note*
.tlist note*
WARNING: The debugger does not have a current process or thread
WARNING: Many commands will not work
0n25184 notepad++.exe
[info] cb ChangeDebuggeeState 0x4 2
.attach 0n25184
.attach 0n25184
WARNING: The debugger does not have a current process or thread
WARNING: Many commands will not work
[info] cb ChangeEngineState 800 0
No .natvis files found at C:\Windows\SYSTEM32\Visualizers.
No .natvis files found at C:\Users\Attila\AppData\Local\Dbg\Visualizers.

Microsoft (R) Windows Debugger Version 10.0.19041.1503 AMD64
Copyright (c) Microsoft Corporation. All rights reserved.

Attach will occur on next execution
[info] cb ChangeDebuggeeState 0x4 2

bb
[info] cb ChangeEngineState 10 1
*** wait with pending attach
Unable to add extension DLL: ntsdexts
Unable to add extension DLL: uext
Unable to add extension DLL: exts
SECURE: File not allowed to be loaded - C:\Windows\SYSTEM32\dbghelp.dll
Error code: Win32 error 0n5
The call to LoadLibrary(ext) failed, Win32 error 0n2
“The system cannot find the file specified.”
Please check your debugger configuration and/or network access.
[info] cb ChangeEngineState 400 0
[info] cb SessionStatus
[info] cb ChangeDebuggeeState 0xffffffff 0

************* Path validation summary **************
Response Time (ms) Location
Deferred srv*
Symbol search path is: srv*
Executable search path is:
ModLoad: 00007ff6b3960000 00007ff6b3f70000 C:\Program Files\Notepad++\notepad++.exe
[info] cb ChangeEngineState 10 100000006
[info] cb ChangeEngineState 1 0
[info] cb ChangeDebuggeeState 0xffffffff 0
[info] cb CreateProcess
[info] cb ChangeEngineState 10 6

r
r
rax=0000000000001006 rbx=000000be7bafec88 rcx=000000be7bafec88
rdx=0000000000000000 rsi=00000217a939f790 rdi=0000000000000001
rip=00007ffeab141104 rsp=000000be7bafeb78 rbp=0000000000000000
r8=000000be7bafeb18 r9=000000be7b8fd000 r10=0000000000000bf0
r11=0000000000100bf0 r12=0000000000000001 r13=0000000000000000
r14=0000000000000000 r15=0000000000000001
iopl=0 nv up ei pl zr na po nc
cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
00007ffeab141104 c3 ret [info] cb ChangeDebuggeeState 0x4 2 g [info] cb ChangeEngineState 10 1 g [info] cb ChangeDebuggeeState 0x4 2 r r [info] cb ChangeDebuggeeState 0x4 2 bl bl [info] cb ChangeDebuggeeState 0x4 2 bb [info] cb ChangeEngineState 10 1 [info] cb ChangeSymbolState ModLoad: 00007ffead5f0000 00007ffead7e8000 C:\Windows\SYSTEM32\ntdll.dll [info] cb ChangeEngineState 10 100000006 [info] cb ChangeEngineState 1 0 [info] cb ChangeDebuggeeState 0xffffffff 0 [info] cb LoadModule [info] cb ChangeEngineState 10 1 [info] cb ChangeEngineState 10 100000006 [info] cb ChangeEngineState 1 1 [info] cb ChangeDebuggeeState 0xffffffff 0 [info] cb CreateThread [info] cb ChangeEngineState 10 1 [info] cb ChangeSymbolState ... further module loads ModLoad: 00007ffe9ca40000 00007ffe`9ccf1000 C:\Windows\SYSTEM32\iertutil.dll
[info] cb ChangeEngineState 10 100000006
[info] cb ChangeEngineState 1 0
[info] cb ChangeDebuggeeState 0xffffffff 0
[info] cb LoadModule
[info] cb ChangeEngineState 10 1
[info] cb ChangeEngineState 10 100000006
[info] cb ChangeEngineState 1 2
[info] cb ChangeDebuggeeState 0xffffffff 0
[info] cb CreateThread
[info] cb ChangeEngineState 10 1
[info] cb ChangeDebuggeeState 0x1 10
[info] cb ChangeEngineState 10 1
[info] cb ChangeEngineState 10 100000006
[info] cb ChangeEngineState 1 2
[info] cb ChangeDebuggeeState 0xffffffff 0
[info] cb ExitThread
[info] cb ChangeEngineState 10 1

I am deadlocked in waitforevent somehow.

Well, you have certainly misunderstood how the callbacks work. You don’t create an empty object and pass it in. This is a COM interface. Your main program should be a C++ object that derives from IDebugInputCallbacks and IDebugOutputCallbacks and IDebugEventCallbacks. You pass that same object to all three of the SetXxxCallbacks methods. When events occur, the engine will call into the functions that you implemented (for example, StartInput and Endinput for IDebugInputCallbacks). So, you aren’t seeing any of the callbacks, because you didn’t implement the functions.

Yes, it’s danged hard to write a debugger. It always has been. But it can certainly be done.

First thank you reading my post. I really appreciate it.

Regarding the callbacks those are not empty objects just wanted to somehow limit my first post so I skipped those classs implementations

class DebugEventCallbacks : public IDebugEventCallbacks { <in this case I’ve just copied the provided base class in dbgeng.h and just wrote some prints and set some debug status to break from the original do not change last status>}

class StdOutputCallbacks : public IDebugOutputCallbacks
{
public:
STDMETHOD(QueryInterface)(THIS_ IN REFIID InterfaceId, OUT PVOID*
Interface) {
*Interface = NULL;
if (IsEqualIID(InterfaceId, _uuidof(IUnknown)) ||
IsEqualIID(InterfaceId, uuidof(IDebugOutputCallbacks))) {
Interface = (IDebugOutputCallbacks)this;
AddRef();
return S_OK;
}
else {
return E_NOINTERFACE;
}
}
STDMETHOD
(ULONG, AddRef)(THIS) {
return 1;
}
STDMETHOD
(ULONG, Release)(THIS) {
return 0;
}
STDMETHOD(Output)(THIS
IN ULONG Mask, IN PCSTR Text) {
UNREFERENCED_PARAMETER(Mask);
fputs(Text, stdout);
return S_OK;
}
};

I know there is no real cleanup. Actually as an overview msdn doc is really good but why there are no examples. Like how to usually implement addref and release ref. Ok I think I should increment a counter and in release delete this(?) if counter reach 0?

class StdInputCallbacks : public IDebugInputCallbacks
{
STDMETHOD(QueryInterface)(THIS_ IN REFIID InterfaceId, OUT PVOID*
Interface) {
*Interface = NULL;
if (IsEqualIID(InterfaceId, __uuidof(IUnknown)) ||
IsEqualIID(InterfaceId, uuidof(IDebugOutputCallbacks))) {
Interface = (IDebugOutputCallbacks)this;
AddRef();
return S_OK;
}
else {
return E_NOINTERFACE;
}
}
STDMETHOD
(ULONG, AddRef)(THIS) {
return 1;
}
STDMETHOD
(ULONG, Release)(THIS) {
return 0;
}

// IDebugInputCallbacks.

// A call to the StartInput method is a request for
// a line of input from any client.  The returned input
// should always be zero-terminated.  The buffer size
// provided is only a guideline.  A client can return
// more if necessary and the engine will truncate it
// before returning from IDebugControl::Input.
// The return value is ignored.
STDMETHOD(StartInput)(
	THIS_
	_In_ ULONG BufferSize
	) {
	// std::cerr << "*** INFO *** new input requested\n";
	std::cout << "> ";
	input.clear();
	std::getline(std::cin, input);
	HRESULT hr = ctrl->ReturnInput(input.c_str());
	if (hr != S_OK)
		std::cerr << "*** WANING *** " << input << " is  rejected\n";
	return hr;
};
// The return value is ignored.
STDMETHOD(EndInput)(
	THIS
	) {
	// std::cerr << "*** INFO *** input request ended\n";
	return S_OK;
};

public:
IDebugControl7* ctrl;
std::string input;
};

regarding the cleanup I realized that the release return value is not an error code but the reference count so okay now I see it was stupid to expect S_OK
so one problem solved.

Why there is no command for breaking into debuggee while it is running here: https://learn.microsoft.com/en-us/windows-hardware/drivers/debugger/commands
I also have found a github project where debug engine was used and mentioned it is not complete e.g. break is not implemented.
Why break is so special? Because it happens while debugger is not active?
What if someone debug using command line and press go without setting a breakpoint or setting it e.g. in a wrong function he couldn’t break and he
has to stop debugging and start over?

Okay to write something short an actually exactly answerable question:
is this the right way to implement break?
ctrl->SetExecutionStatus(DEBUG_STATUS_BREAK);
ctrl->WaitForEvent(0, INFINITE);
do I need to know or do anything else?

There is no windbg command for breaking into a running process because the debugger is not to be in control. The application is running free and the command line is unavailable. The command for breaking in is the Ctrl-Break key. There’s also a menu option to do it.