SimpleWin32Server/SimpleWin32Server.cpp
This example is a simple gdbserver-compatible stub that can debug Win32 processes. It supports multi-threading and DLL load events.
/*
A simple Win32 API-based gdbserver implementation.
This server can run a Win32 EXE in debug mode and allow debugging it with GDB via the "target remote" command.
The main functionality of the server is contained in the Win32GDBTarget class implementing the GDBServerFoundation::ISyncGDBTarget interface.
The stub uses the following Win32 API to debug the started process:
* DebugActiveProcess()
* WaitForDebugEvent() / ContinueDebugEvent()
* GetThreadContext() / SetThreadContext()
* ReadProcessMemory() / WriteProcessMemory()
To see the server in action, build a simple EXE with debug information using gcc:
g++ -O0 -ggdb test.cpp -o test.exe
Then start the server app:
SimpleWin32Server test.exe
and finally start debugging it with GDB:
gdb test.exe
target remote :2000
b main
continue
The server is very simple, it does not handle all debug events and does not close all handles. It is provided only as an example for the GDBServerFoundation library.
*/
#include "stdafx.h"
#include <stdio.h>
#include "../../GDBServer.h"
#include "../../GDBStub.h"
#include "../../IGDBTarget.h"
#include "registers-i386.h"
#include <TlHelp32.h>
using namespace BazisLib;
using namespace GDBServerFoundation;
bool LaunchDebuggedProcess(LPCTSTR pszProcess, PROCESS_INFORMATION *pProcInfo)
{
STARTUPINFO startupInfo = {sizeof(STARTUPINFO), };
TCHAR tsz[MAX_PATH];
_tcsncpy(tsz, pszProcess, __countof(tsz));
if (!CreateProcess(NULL, tsz, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &startupInfo, pProcInfo))
return false;
return true;
}
struct ContextEntry
{
int RegisterIndex;
unsigned ContextOffset;
};
static ContextEntry s_ContextRegisterOffsets[] = {
{i386::rgEAX, FIELD_OFFSET(CONTEXT, Eax)},
{i386::rgECX, FIELD_OFFSET(CONTEXT, Ecx)},
{i386::rgEDX, FIELD_OFFSET(CONTEXT, Edx)},
{i386::rgEBX, FIELD_OFFSET(CONTEXT, Ebx)},
{i386::rgESP, FIELD_OFFSET(CONTEXT, Esp)},
{i386::rgEBP, FIELD_OFFSET(CONTEXT, Ebp)},
{i386::rgESI, FIELD_OFFSET(CONTEXT, Esi)},
{i386::rgEDI, FIELD_OFFSET(CONTEXT, Edi)},
{i386::rgEIP, FIELD_OFFSET(CONTEXT, Eip)},
{i386::rgEFLAGS, FIELD_OFFSET(CONTEXT, EFlags)},
{i386::rgCS, FIELD_OFFSET(CONTEXT, SegCs)},
{i386::rgSS, FIELD_OFFSET(CONTEXT, SegSs)},
{i386::rgDS, FIELD_OFFSET(CONTEXT, SegDs)},
{i386::rgES, FIELD_OFFSET(CONTEXT, SegEs)},
{i386::rgFS, FIELD_OFFSET(CONTEXT, SegFs)},
{i386::rgGS, FIELD_OFFSET(CONTEXT, SegGs)},
};
C_ASSERT(__countof(s_ContextRegisterOffsets) == __countof(i386::_RawRegisterList));
{
private:
{
if (!ContinueDebugEvent(m_DebugEvent.dwProcessId, m_DebugEvent.dwThreadId, DBG_CONTINUE))
if (!WaitForDebugEvent())
return kGDBUnknownError;
}
bool EnableSingleStep(int threadID, bool enable)
{
CONTEXT context;
if (!GetContextByThreadID(threadID, &context))
return false;
if (enable)
context.EFlags |= 0x0100;
else
context.EFlags &= ~0x0100;
if (!SetContextByThreadID(threadID, &context))
return false;
return true;
}
{
if (!EnableSingleStep(threadID, true))
return kGDBUnknownError;
return ResumeAndWait(threadID);
}
virtual GDBStatus SendBreakInRequestAsync()
{
if (!DebugBreakProcess(m_hProcess))
return kGDBUnknownError;
return kGDBSuccess;
}
private:
DWORD m_dwPID;
DEBUG_EVENT m_DebugEvent;
HANDLE m_hProcess;
std::set<int> m_Threads;
private:
bool WaitForDebugEvent(bool ignoreUnsupportedEvents = true)
{
for (;;)
{
if (!::WaitForDebugEvent(&m_DebugEvent, INFINITE))
return false;
printf("Debug event #%d, thread 0x%x\n", m_DebugEvent.dwDebugEventCode, m_DebugEvent.dwThreadId);
switch(m_DebugEvent.dwDebugEventCode)
{
case EXCEPTION_DEBUG_EVENT:
if (m_DebugEvent.u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_SINGLE_STEP)
if (!EnableSingleStep(m_DebugEvent.dwThreadId, false))
break;
break;
case CREATE_THREAD_DEBUG_EVENT:
if (m_DebugEvent.dwThreadId)
m_Threads.insert(m_DebugEvent.dwThreadId);
break;
case EXIT_THREAD_DEBUG_EVENT:
if (m_DebugEvent.dwThreadId);
m_Threads.erase(m_DebugEvent.dwThreadId);
break;
}
if (ignoreUnsupportedEvents && !IsDebugEventSupportedByGDB(m_DebugEvent.dwDebugEventCode))
{
if (!ContinueDebugEvent(m_DebugEvent.dwProcessId, m_DebugEvent.dwThreadId, DBG_CONTINUE))
return false;
continue;
}
return true;
}
}
bool IsDebugEventSupportedByGDB(DWORD dwEvent)
{
switch(dwEvent)
{
case EXCEPTION_DEBUG_EVENT:
case LOAD_DLL_DEBUG_EVENT:
case UNLOAD_DLL_DEBUG_EVENT:
case OUTPUT_DEBUG_STRING_EVENT:
case EXIT_PROCESS_DEBUG_EVENT:
case RIP_EVENT:
return true;
default:
return false;
}
}
public:
Win32GDBTarget(HANDLE hProcess, HANDLE hThreadToResume = INVALID_HANDLE_VALUE)
: m_hProcess(hProcess)
{
m_dwPID = GetProcessId(hProcess);
if (!DebugActiveProcess(m_dwPID))
m_dwPID = 0;
if (hThreadToResume != INVALID_HANDLE_VALUE)
ResumeThread(hThreadToResume);
WaitForDebugEvent(false);
}
~Win32GDBTarget()
{
if (m_dwPID)
DebugActiveProcessStop(m_dwPID);
}
protected:
{
pRec->ProcessID = m_DebugEvent.dwProcessId;
pRec->ThreadID = m_DebugEvent.dwThreadId;
switch(m_DebugEvent.dwDebugEventCode)
{
case EXCEPTION_DEBUG_EVENT:
switch(m_DebugEvent.u.Exception.ExceptionRecord.ExceptionCode)
{
case EXCEPTION_ACCESS_VIOLATION:
break;
case EXCEPTION_BREAKPOINT:
case EXCEPTION_SINGLE_STEP:
break;
default:
break;
}
break;
case LOAD_DLL_DEBUG_EVENT:
case UNLOAD_DLL_DEBUG_EVENT:
break;
case EXIT_PROCESS_DEBUG_EVENT:
break;
default:
pRec->Reason = kUnspecified;
break;
}
return kGDBSuccess;
}
private:
bool GetContextByThreadID(int threadID, CONTEXT *pContext)
{
HANDLE hThread = OpenThread(THREAD_GET_CONTEXT, FALSE, threadID);
if (hThread == INVALID_HANDLE_VALUE)
return false;
pContext->ContextFlags = CONTEXT_ALL;
BOOL successful = GetThreadContext(hThread, pContext);
CloseHandle(hThread);
return successful != 0;
}
bool SetContextByThreadID(int threadID, const CONTEXT *pContext)
{
HANDLE hThread = OpenThread(THREAD_SET_CONTEXT, FALSE, threadID);
if (hThread == INVALID_HANDLE_VALUE)
return false;
BOOL successful = SetThreadContext(hThread, pContext);
CloseHandle(hThread);
return successful != 0;
}
public: //Register API
{
return &i386::RegisterList;
}
//This method can be left unimplemented. In this case GDB will issue a 'g' packet to read all target registers when the target is stopped.
{
//return kGDBNotSupported;
CONTEXT context;
if (!GetContextByThreadID(threadID, &context))
return kGDBUnknownError;
registers[i386::rgEIP] = RegisterValue(context.Eip, 4);
registers[i386::rgESP] = RegisterValue(context.Esp, 4);
registers[i386::rgEBP] = RegisterValue(context.Ebp, 4);
return kGDBSuccess;
}
{
CONTEXT context;
if (!GetContextByThreadID(threadID, &context))
return kGDBUnknownError;
for (size_t i = 0; i < i386::RegisterList.RegisterCount; i++)
{
ASSERT(registers[i].SizeInBytes == 4);
ASSERT(s_ContextRegisterOffsets[i].RegisterIndex == i);
registers[i].Valid = true;
memcpy(registers[i].Value, ((char *)&context) + s_ContextRegisterOffsets[i].ContextOffset, registers[i].SizeInBytes);
}
return kGDBSuccess;
}
{
CONTEXT context;
if (!GetContextByThreadID(threadID, &context))
return kGDBUnknownError;
for (size_t i = 0; i < i386::RegisterList.RegisterCount; i++)
{
if (!registers[i].Valid)
continue;
ASSERT(registers[i].SizeInBytes == 4);
ASSERT(s_ContextRegisterOffsets[i].RegisterIndex == i);
memcpy(((char *)&context) + s_ContextRegisterOffsets[i].ContextOffset, registers[i].Value, registers[i].SizeInBytes);
}
if (!SetContextByThreadID(threadID, &context))
return kGDBUnknownError;
return kGDBSuccess;
}
public: //Memory API
{
C_ASSERT(sizeof(SIZE_T) == sizeof(size_t));
if (!ReadProcessMemory(m_hProcess, (LPCVOID)Address, pBuffer, *pSizeInBytes, (SIZE_T *)pSizeInBytes))
return kGDBUnknownError;
return kGDBSuccess;
}
{
SIZE_T done = 0;
if (!WriteProcessMemory(m_hProcess, (LPVOID)Address, pBuffer, sizeInBytes, &done))
return kGDBUnknownError;
if (done != sizeInBytes)
return kGDBUnknownError;
return kGDBSuccess;
}
public: //Optional API
virtual GDBStatus GetDynamicLibraryList(std::vector<DynamicLibraryRecord> &libraries)
{
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, m_dwPID);
if (hSnapshot == INVALID_HANDLE_VALUE)
return kGDBUnknownError;
MODULEENTRY32 module = {sizeof(module), };
if (Module32First(hSnapshot, &module))
while(Module32Next(hSnapshot, &module)) //Skip the first module, as it's the actual EXE
{
DynamicLibraryRecord rec;
rec.FullPath = BazisLib::StringToANSISTLString(TempStrPointerWrapper(module.szExePath));
rec.LoadAddress = (ULONGLONG)module.modBaseAddr + 0x1000; //GDB requires this offset to map symbols correctly
libraries.push_back(rec);
}
CloseHandle(hSnapshot);
return kGDBSuccess;
}
virtual GDBStatus GetThreadList(std::vector<ThreadRecord> &threads)
{
for each(int id in m_Threads)
{
ThreadRecord rec;
rec.ThreadID = id;
threads.push_back(rec);
}
return kGDBSuccess;
}
virtual GDBStatus SetThreadModeForNextCont(int threadID, DebugThreadMode mode, OUT bool *pNeedRestoreCall, IN OUT INT_PTR *pRestoreCookie)
{
switch(mode)
{
return kGDBSuccess;
if (!EnableSingleStep(threadID, true))
return kGDBUnknownError;
return kGDBSuccess;
*pNeedRestoreCall = true;
//Intentionally no break
{
HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, threadID);
if (hThread == INVALID_HANDLE_VALUE)
return kGDBUnknownError;
bool ok = ((mode == dtmSuspend) ? SuspendThread(hThread) : ResumeThread(hThread)) != -1;
CloseHandle(hThread);
}
default:
}
}
virtual GDBStatus Terminate()
{
}
virtual GDBStatus CreateBreakpoint(BreakpointType type, ULONGLONG Address, unsigned kind, OUT INT_PTR *pCookie)
{
return kGDBNotSupported;
}
{
return kGDBNotSupported;
}
};
{
FILE *pLogFile;
virtual StubResponse HandleRequest(const BazisLib::TempStringA &requestType, char splitterChar, const BazisLib::TempStringA &requestData)
{
printf(">> %s%c%s\n", DynamicStringA(requestType).c_str(), splitterChar, DynamicStringA(requestData).c_str());
fprintf(pLogFile, ">> %s%c%s\n", DynamicStringA(requestType).c_str(), splitterChar, DynamicStringA(requestData).c_str());
StubResponse response = __super::HandleRequest(requestType, splitterChar, requestData);
fflush(pLogFile);
return response;
}
public:
StubWithLogging(ISyncGDBTarget *pTarget)
: GDBStub(pTarget, true)
{
pLogFile = fopen("gdbserver.log", "w");
}
~StubWithLogging()
{
fclose(pLogFile);
}
};
{
PROCESS_INFORMATION m_Process;
bool m_bVerbose;
public:
Win32StubFactory(const PROCESS_INFORMATION &proc, bool verbose)
: m_Process(proc)
, m_bVerbose(verbose)
{
}
{
printf("GDB connected.\n");
pServer->StopListening();
Win32GDBTarget *pTarget = new Win32GDBTarget(m_Process.hProcess, m_Process.hThread);
if (m_bVerbose)
return new StubWithLogging(pTarget);
else
}
virtual void OnProtocolError(const TCHAR *errorDescription)
{
_tprintf(_T("Protocol error: %s\n"), errorDescription);
}
};
int _tmain(int argc, _TCHAR* argv[])
{
if (argc < 2)
{
printf("Usage: SimpleWin32Server <exe name> [--verbose]");
return 1;
}
bool verbose = false;
if (argc >= 3 && !_tcscmp(argv[2], _T("--verbose")))
verbose = true;
PROCESS_INFORMATION info;
if (!LaunchDebuggedProcess(argv[1], &info))
{
_tprintf(_T("Cannot launch %s\n"), argv[1]);
return 1;
}
GDBServer srv(new Win32StubFactory(info, verbose));
printf("Listening in port 2000. You can connect GDB now\n");
srv.Start(2000);
srv.WaitForTermination();
return 0;
}