gpt4 book ai didi

c++ - 调试以 CREATE_SUSPENDED 启动的任何进程时很少出现 EXCEPTION_ACCESS_VIOLATION

转载 作者:太空狗 更新时间:2023-10-29 21:09:37 26 4
gpt4 key购买 nike

在编写基于 x86 WinAPI 的调试器时,我遇到了一种罕见的情况,即在我使用 native 调试器附加到调试器(通常运行良好)后突然以 EXCEPTION_ACCESS_VIOLATION 终止。我可以在任何应用程序上稳定地重现它(在 .NET Hello World 风格的应用程序和多台 Windows 10 机器上的 notepad.exe 上试过)。

基本上我写了一个简单的 WaitForDebugEvent 循环:

CreateProcessW(L"C:\\Windows\\SYSWOW64\\notepad.exe", […], CREATE_SUSPENDED, […]);
DebugActiveProcess(processId);

DEBUG_EVENT debugEvent = {};
while (WaitForDebugEvent(&debugEvent, INFINITE)) {
switch (debugEvent.dwDebugEventCode) {
// log all the events
}
ContinueDebugEvent(debugEvent.dwProcessId, debugEvent.dwThreadId, DBG_EXCEPTION_NOT_HANDLED);
}

DebugActiveProcessStop(processId);

(here's the full listing:我不会把它全部粘贴在这里,因为那里有一些额外的非必要样板文件;MCVE 有 136 行长)

为了举例,我将只记录所有调试器事件并检测被调试者是否准备好“正常进行”或者它是否会因异常而终止。

大多数时候,我的调试 session 看起来像这样:

  • CREATE_PROCESS_DEBUG_EVENT(报告进程及其初始线程的创建)
  • LOAD_DLL_DEBUG_EVENT(我一直无法获得此 DLL 的名称,但这在 MSDN 中有记录)
  • CREATE_THREAD_DEBUG_EVENT(我怀疑这是调试器注入(inject)的线程)
  • LOAD_DLL_DEBUG_EVENT […] — 在此之后,许多 DLL 被加载到目标进程中,一切正常,进程按预期工作

但有时(大约占所有运行的 1.5%),事件顺序会发生变化:

  • CREATE_PROCESS_DEBUG_EVENT
  • LOAD_DLL_DEBUG_EVENT
  • CREATE_THREAD_DEBUG_EVENT
  • EXCEPTION_DEBUG_EVENT:EXCEPTION_ACCESS_VIOLATION(我一直无法收集到详细信息:它报告 DEP 违规,地址为空)

之后,我无法继续调试,因为我的被调试者处于异常状态,很快就会终止。如果没有附加我的调试器,我永远无法捕捉到 notepad.exe 崩溃(我怀疑它是那么糟糕并且会无缘无故地崩溃),所以我怀疑我的调试器导致了这些异常。

一个奇怪的细节是,我可以通过在 WaitForDebugEvent 之后立即调用 Sleep(1) 来“修复”这种情况。所以,这可能是某种竞争条件,但是什么之间的竞争条件呢?在调试器线程和被调试器中的其他线程之间?这是一件事吗?那么我们应该如何调试其他应用程序呢?如果它是一回事,那么实际的调试器如何工作?

我无法使用为 x64 CPU 编译的相同代码(以及调试 x64 进程)重现该问题。

究竟是什么导致了这种错误行为?我仔细阅读了有关我调用的 API 函数的文档,并在线检查了一些其他调试器示例,但仍然无法找到我的调试器有什么问题:看起来我遵循了所有正确的约定。

我曾尝试使用 WinDBG 调试我的调试对象,而它仍然在我的调试器中暂停,但没有成功。首先,很难用另一个调试器附加到被调试者(WinDBG 只允许使用非侵入模式,这看起来功能较少?),并且进程线程的调用堆栈通常没有意义。

重现步骤

结帐 this repository ,用MSVC编译然后在cmd中执行:

Debug\NetRuntimeWaiter.exe > log.txt

重要的是将输出重定向到日志文件而不是在终端中显示它:否则,日志写入器的时间会发生变化,并且问题不会重现(由于我之前提到的可能的竞争条件? ).

通常程序会在大约 10 秒内启动和终止 1000 个记事本,1000 次调用中有 10-15 次会保持错误状态(即 EXCEPTION_ACCESS_VIOLATION)。

最佳答案

DebugActiveProcess (和未记录的 DbgUiDebugActiveProcessDebugActiveProcess 内部调用)有严重的设计问题:调用 NtDebugActiveProcess 后,它在目标进程中创建远程线程,通过DbgUiIssueRemoteBreakin 调用 - 结果在目标进程中创建了新线程 - DbgUiRemoteBreakin - 此线程调用 DbgBreakPoint然后是 RtlExitUserThread

所有这些都没有记录和解释,只有来自 DebugActiveProcess 的注释:

After all of this is done, the system resumes all threads in the process. When the first thread in the process resumes, it executes a breakpoint instruction that causes an EXCEPTION_DEBUG_EVENT debugging event to be sent to the debugger.

当然这是错误的。为什么 DbgUiRemoteBreakin 第一个 (??) 线程?哪个线程首先恢复未定义。为什么不完全写 - 我们在进程中创建额外的(但不是第一个)线程?并且这个线程执行断点。

然而,当进程已经在运行时——创建这个额外的线程不会产生问题。但是如果我们在暂停状态下创建进程,然后调用 DebugActiveProcess - DbgUiRemoteBreakin 真正成为进程中的第一个执行线程,进程初始化是在这个线程上完成的,而不是创建第一个线程。在 xp 上,这总是导致在连接到 csrss 阶段时进程初始化失败。 (csrss 等待仅在进程中的第一个创建线程上连接到它)。在以后的系统上,这是固定的,进程可以照常执行。但可以也不能,因为初始化它的线程正在退出。它可能会导致微妙的问题。

此处的解决方案 - 不使用 DebugActiveProcess但是 NtDebugActiveProcess 在它的位置。我们可以创建或通过 DbgUiConnectToDbg() 创建调试对象,然后通过 DbgUiGetThreadDebugObject() 获取它(系统将调试对象存储在线程 TEB 中)或直接调用 NtCreateDebugObject

同样,如果我们从另一个进程 (B) 创建被调试进程,我们接下来可以做:

  • 将调试器进程中的调试对象复制到此 B 进程
  • 在调用之前调用 DbgUiSetThreadDebugObject(hDdg)CreateProcessWDEBUG_ONLY_THIS_PROCESSDEBUG_PROCESS
  • 系统将使用 DbgUiGetThreadDebugObject() 获取调试对象从您的线程并将其传递给低级进程创建 api
  • 通过以下方式从您的线程中删除调试对象DbgUiSetThreadDebugObject(0)

真的不管是谁用调试对象创建进程。无论谁处理发布到此调试对象的事件。

您可以从 ntdbg.h 中获取的所有未记录的 API 定义然后链接到 ntdll.libntdllp.lib

关于c++ - 调试以 CREATE_SUSPENDED 启动的任何进程时很少出现 EXCEPTION_ACCESS_VIOLATION,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57933993/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com