win32调试工具原理OutputDebugString以及如何获取输出信息
在应用程序和调试器之间传递数据是通过一个 4KB 大小的共享内存块完成的,并有一个互斥量和两个事件对象用来保护对他的访问。下面就是相关的四个内核对象:
对象名称 对象类型 DBWinMutex Mutex DBWIN_BUFFER Section (共享内存) DBWIN_BUFFER_READY Event DBWIN_DATA_READY Event
互斥量通常一直保留在系统中,其他三个对象仅当调试器要接收信息才出现。事实上 - 如果一个调试器发现后三个对象已经存在,它会拒绝运行。
当 DBWIN_BUFFER 出现时,会被组织成以下结构。进程 ID 显示信息的来源,字符串数据填充这 4K 的剩余部分。按照约定,信息的末尾总是包括一个 NULL 字节。
struct dbwin_buffer { DWORD dwProcessId; char data[4096-sizeof(DWORD)]; };
当 OutputDebugString() 被应用调用时,它执行以下步骤。注意在任意位置的错误都将放弃整个事情,调试请求被认为是什么也不做(不会发送字符串)。
- 打开 DBWinMutex 并且等待,直到我们取得了独占访问。
- 映射 DBWIN_BUFFER 段到内存中:如果没有发现,则没有调试器在运行,将忽略整个请求。
- 打开 DBWIN_BUFFER_READY 和 DBWIN_DATA_READY 事件对象。就像共享内存段一样,缺少对象意味着没有可用的调试器。
- 等待 DBWIN_BUFFER_READY 事件对象为有信号状态:表示内存缓冲区不再被占用。大部分时候,这一事件对象一被检查就处于有信号状态,但等待缓冲区就绪不会超过 10 秒(超时将放弃请求)。
- 复制数据直到内存缓冲区中接近 4KB,再保存当前进程 ID。总是放置一个 NULL 字节到字符串结尾。
- 通过设置 DBWIN_DATA_READY 事件对象告诉调试器缓冲区就绪。调试器从那儿取走它。
- 释放互斥量。
- 关闭事件对象和段对象,但保留互斥量的句柄以备后用。
在调试器端会简单一点。互斥量根本不需要,如果事件对象和/或共享内存对象已经存在,则假定其他调试器已经在运行。系统中任意时刻只能存在一个调试器。
- 创建共享内存段以及两个事件对象。如果失败,退出。
- 设置 DBWIN_BUFFER_READY 事件对象,由此应用程序得知缓冲区可用。
- 等待 DBWIN_DATA_READY 事件对象变为有信号状态。
- 从内存缓冲区中提取进程 ID 和 NULL 结尾的字符串。
- 转到步骤 2。
这使我们认为这决不是一种低消耗的发送信息的方法,应用程序的运行速度会受到调试器的左右。
#define WIN32_LEAN_AND_MEAN #include <Windows.h> #include <stdio.h> #define IfFalseRet(c) do{if(!(c)){return dwLastError = ::GetLastError();}}while(false) class CHandle { public: CHandle(HANDLE h = NULL): m_h(h) { } ~CHandle() { Release(); } void Release() { if(*this) { ::CloseHandle(m_h); } m_h = NULL; } operator bool() const { return m_h != INVALID_HANDLE_VALUE && m_h != NULL; } operator HANDLE() const { return m_h; } CHandle& operator= (const HANDLE& h) { Release(); m_h = h; return *this; } CHandle& operator= (CHandle& h) { if(this != &h) { HANDLE hSwap = m_h; m_h = h.m_h; h.m_h = hSwap; h.Release(); } return *this; } private: HANDLE m_h; }; LPCTSTR DBWIN_BUFFER = TEXT("DBWIN_BUFFER"); LPCTSTR DBWIN_BUFFER_READY = TEXT("DBWIN_BUFFER_READY"); LPCTSTR DBWIN_DATA_READY = TEXT("DBWIN_DATA_READY"); LPCTSTR DBWIN_MUTEX = TEXT("DBWinMutex"); #pragma pack(push, 1) struct CDBWinBuffer { DWORD dwProcessId; BYTE abData[4096 - sizeof(DWORD)]; }; #pragma pack(pop) bool g_fContinue = true; BOOL CtrlHandler(DWORD fdwCtrlType) { switch(fdwCtrlType) { case CTRL_C_EVENT: case CTRL_CLOSE_EVENT: case CTRL_LOGOFF_EVENT: case CTRL_SHUTDOWN_EVENT: g_fContinue = false; return TRUE; } return FALSE; } int __cdecl main() { DWORD dwLastError = ERROR_SUCCESS; IfFalseRet(SetConsoleCtrlHandler((PHANDLER_ROUTINE)(CtrlHandler), TRUE) == TRUE); CHandle hMutex = OpenMutex(MUTEX_ALL_ACCESS, FALSE, DBWIN_MUTEX); if(!hMutex) { IfFalseRet(GetLastError() == ERROR_FILE_NOT_FOUND); IfFalseRet(hMutex = CreateMutex(NULL, FALSE, DBWIN_MUTEX)); } CHandle hEventBufferReady = OpenEvent(EVENT_MODIFY_STATE, FALSE, DBWIN_BUFFER_READY); if(!hEventBufferReady) { IfFalseRet(GetLastError() == ERROR_FILE_NOT_FOUND); IfFalseRet(hEventBufferReady = CreateEvent(NULL, FALSE, TRUE, DBWIN_BUFFER_READY)); } CHandle hEventDataReady = OpenEvent(EVENT_MODIFY_STATE, FALSE, DBWIN_DATA_READY); if(!hEventDataReady) { IfFalseRet(GetLastError() == ERROR_FILE_NOT_FOUND); IfFalseRet(hEventDataReady = CreateEvent(NULL, FALSE, FALSE, DBWIN_DATA_READY)); } CHandle hFileMappingBuffer = OpenFileMapping(FILE_MAP_READ, FALSE, DBWIN_BUFFER); if(!hFileMappingBuffer) { IfFalseRet(GetLastError() == ERROR_FILE_NOT_FOUND); IfFalseRet(hFileMappingBuffer = CreateFileMapping( INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(CDBWinBuffer), DBWIN_BUFFER)); } CDBWinBuffer* pDbgBuffer = (CDBWinBuffer*)(MapViewOfFile( hFileMappingBuffer, SECTION_MAP_READ, 0, 0, 0)); IfFalseRet(pDbgBuffer); while(g_fContinue) { if(WaitForSingleObject(hEventDataReady, 100) == WAIT_OBJECT_0) { printf("%s", pDbgBuffer->abData); SetEvent(hEventBufferReady); } } UnmapViewOfFile(pDbgBuffer); return dwLastError;
}