以编程方式生成的Windbg转储可以';无法调试
我有一个简单的程序:以编程方式生成的Windbg转储可以';无法调试,windbg,dump,crash-dumps,Windbg,Dump,Crash Dumps,我有一个简单的程序: int ExecuteCommand(wchar_t* commandLine) { STARTUPINFO si; PROCESS_INFORMATION pi; BOOL bRet; DWORD lpExitCode; memset(&si, 0, sizeof(si)); si.cb = sizeof(si); si.dwFlags = STARTF_USESHOWWINDOW; si.wSh
int ExecuteCommand(wchar_t* commandLine)
{
STARTUPINFO si;
PROCESS_INFORMATION pi;
BOOL bRet;
DWORD lpExitCode;
memset(&si, 0, sizeof(si));
si.cb = sizeof(si);
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = SW_HIDE;
bRet = CreateProcess(
NULL, // pointer to name of executable module
commandLine, // pointer to command line string
NULL, // process security attributes
NULL, // thread security attributes
FALSE, // handle inheritance flag
NORMAL_PRIORITY_CLASS, // creation flags
NULL, // pointer to new environment block
NULL, // pointer to current directory name
&si, // pointer to STARTUPINFO
&pi // pointer to PROCESS_INFORMATION
);
if(bRet) WaitForSingleObject(pi.hProcess, INFINITE); // wait for process to finish
GetExitCodeProcess(pi.hProcess, &lpExitCode);
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
return lpExitCode;
}
void CreateCoreDump()
{
wchar_t buffer[256];
wsprintf(buffer, _T("windbg -p %d -c \".dump /mfh /u C:\\Tmp\\crashdump.dmp\""), GetCurrentProcessId());
ExecuteCommand(buffer);
}
DWORD ExceptionFilter()
{
CreateCoreDump();
return EXCEPTION_CONTINUE_SEARCH;
}
int _tmain(int argc, _TCHAR* argv[])
{
__try
{
int* p = NULL;
*p = 100;
}
__except(ExceptionFilter())
{
}
return 0;
}
当出现异常时,它将使用函数CreateCoreDump生成核心转储。虽然可以成功生成转储文件,但它似乎毫无用处:如果我使用windbg打开此转储文件,则调用堆栈中没有任何内容强> 但是,如果我直接在windbg中调试此应用程序,并在调用CreateCoreDump的行上设置断点,然后运行windbg命令:
.dump /mfh C:\Tmp\mydump.dmp
用WinDbg打开这个转储文件,我可以看到完整的调用堆栈
我是否在生成转储文件或使用windbg调试转储文件时出错 在异常发生后附加调试器时,调试器不会看到异常事件。它创建了一个具有断点的线程,因此该线程上的堆栈如下所示:
0:001> kc
Call Site
ntdll!DbgBreakPoint
ntdll!DbgUiRemoteBreakin+0x38
kernel32!BaseThreadInitThunk+0xd
ntdll!RtlUserThreadStart+0x1d
This dump file has an exception of interest stored in it.
The stored exception information can be accessed via .ecxr.
如果手动将当前线程设置为线程0(使用~0s
),您将看到堆栈
0:001> ~0s
ntdll!ZwWaitForSingleObject+0xa:
00000000`76e5135a c3 ret
0:000> kc
Call Site
ntdll!ZwWaitForSingleObject
KERNELBASE!WaitForSingleObjectEx
tmp!ExceptionFilter
tmp!main$filt$0
ntdll!__C_specific_handler
ntdll!RtlpExecuteHandlerForException
ntdll!RtlDispatchException
ntdll!KiUserExceptionDispatch
tmp!main
tmp!__mainCRTStartup
kernel32!BaseThreadInitThunk
ntdll!RtlUserThreadStart
当您在调试器下启动程序时,会发生两件事,第一,只有一个线程,第二,调试器知道异常,因此它将打印如下内容:
0:001> kc
Call Site
ntdll!DbgBreakPoint
ntdll!DbgUiRemoteBreakin+0x38
kernel32!BaseThreadInitThunk+0xd
ntdll!RtlUserThreadStart+0x1d
This dump file has an exception of interest stored in it.
The stored exception information can be accessed via .ecxr.
它告诉您需要使用
.ecxr
命令来访问感兴趣的线程。在这种情况下,您不需要这样做,因为当前调试器线程已经是您想要的线程。您必须将异常记录添加到转储。例如,我更改了示例以检索过滤器中的异常信息,并在生成转储时在命令行上传递它
void CreateCoreDump(LPEXCEPTION_POINTERS p)
{
wchar_t buffer[256];
// I used the command line debugger, cdb, and added a "qd" command for it to exit after dumping.
wsprintf(buffer, _T("cdb.exe -p %d -c \".dump /mfh /u /xt 0x%x /xp 0x%p C:\\Tmp\\crashdump.dmp\";qd"), GetCurrentProcessId(), GetCurrentThreadId(), p);
ExecuteCommand(buffer);
}
DWORD ExceptionFilter(LPEXCEPTION_POINTERS p)
{
CreateCoreDump(p);
return EXCEPTION_CONTINUE_SEARCH;
}
int _tmain(int argc, _TCHAR* argv[])
{
__try
{
int* p = NULL;
*p = 100;
}
__except(ExceptionFilter(GetExceptionInformation()))
{
}
return 0;
}
然后,当您在windgb中打开转储时,调试器知道异常事件。您可以使用.ecxr
在异常点设置当前线程和堆栈
0:000> .ecxr
eax=00000000 ebx=00000000 ecx=6ec4471c edx=00000000 esi=00000001 edi=010c337c
eip=010c108b esp=0038f5e8 ebp=0038f818 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010246
test!wmain+0x14:
010c108b c70064000000 mov dword ptr [eax],64h ds:002b:00000000=????????
0:000> kc
test!wmain
test!__tmainCRTStartup
kernel32!BaseThreadInitThunk
ntdll!__RtlUserThreadStart
ntdll!_RtlUserThreadStart
谢谢你,约翰,很管用。您提到“调试器没有看到异常事件”,这是因为转储文件是由代码创建的,对吗?当调试器连接到进程时,它正在侦听调试事件(请参阅)。当异常发生时附加调试器时,它将看到异常调试事件,并知道进程中存在异常。事件通知发生在异常处理之前(第一次),如果在链中找不到异常处理程序,则可能发生第二次。附加调试器时,它发生在第一次机会通知之后。您可以使用
-g
和-e事件
让调试器看到第二次机会异常(如果其他人未捕获该异常)。旗帜。-e
告诉调试器在附加给定事件时向其发送信号,因此需要创建可继承的通知事件,将其句柄(作为十进制数)作为-e
参数的值发送,并在创建子进程后等待事件。