【问题标题】:Infinite loop while debugging a thread调试线程时无限循环
【发布时间】:2017-05-14 18:07:12
【问题描述】:

我正在尝试将硬件断点附加到游戏进程,但我成功了。然后我试图遍历异常并等待我放在那里的那个,它也可以正常工作。问题是它发生后进入无限循环,我无法刹车。你能建议吗? 我这样做的原因是我想在此时停止线程,使用 Context 读取 EAX 值,然后继续该过程。

Header.h 包含此处调用的函数,它们都可以正常工作,因此我现在不包括它。

#include "Header.h" #包括

int main() {

SetDebugPrivilege(TRUE);

DWORD dwProcessID = 0;
DWORD dwGame = 0;

printf("Looking for game process...\n");

while (dwProcessID == 0) {
    dwProcessID = GetProcessID(L"PathOfExile.exe");
    if (dwProcessID != 0)
        dwGame = 1;

    Sleep(100);
}

printf("dwProcessID = %p\n", dwProcessID);

HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwProcessID);
MODULEENTRY32 module;
module.dwSize = sizeof(MODULEENTRY32);
Module32First(snapshot, &module);

printf("PoE base address = %p\n", module.modBaseAddr);

hpChangeBreakpoint = (DWORD*)((char *)module.modBaseAddr + 0x1AAD20);

std::cout << hpChangeBreakpoint << std::endl;

//hpChangeBreakpoint = 0x013FB279;


HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, dwProcessID);

BOOL bDebugging = DebugActiveProcess(dwProcessID);
printf("bDebugging = %d\n", bDebugging);


DWORD dwThreadID = GetProcessThreadID(dwProcessID);

HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, dwThreadID);

CONTEXT parentCtx;

parentCtx.ContextFlags = CONTEXT_DEBUG_REGISTERS;

if (GetThreadContext(hThread, &parentCtx))
{
    parentCtx.Dr0 = (DWORD)hpChangeBreakpoint;
    parentCtx.Dr7 = 0x00000001;

    std::cout << "GetThreadContext successful" << std::endl;

    SetThreadContext(hThread, &parentCtx);
}


DEBUG_EVENT DebugEvent;
DWORD dbgFlag = DBG_CONTINUE;
DWORD *currentHpPointerAddress = nullptr;
DWORD *maxHpPointerAddress = nullptr;
BOOLEAN bQuit = FALSE;

while (!bQuit && WaitForDebugEvent(&DebugEvent, INFINITE))
{
    dbgFlag = DBG_CONTINUE;

    switch (DebugEvent.dwDebugEventCode)
    {

    case EXCEPTION_DEBUG_EVENT:

        switch (DebugEvent.u.Exception.ExceptionRecord.ExceptionCode)
        {

        case EXCEPTION_SINGLE_STEP:
            if (DebugEvent.u.Exception.ExceptionRecord.ExceptionAddress == (void*)hpChangeBreakpoint)
            {
                #define RESUME_FLAG 0x10000

                CONTEXT childContext;
                childContext.ContextFlags = CONTEXT_FULL;
                if (GetThreadContext(hThread, &childContext))
                {
                    childContext.EFlags |= RESUME_FLAG;
                    SetThreadContext(hThread, &childContext);
                    std::cout << "current HP: " << childContext.Ecx << std::endl;

                    currentHpPointerAddress = (DWORD*)((char *)childContext.Edi + 0x8E0);
                    maxHpPointerAddress = (DWORD*)((char *)childContext.Edi + 0x8E4);

                }

                if (GetThreadContext(hThread, &parentCtx))
                {
                    parentCtx.Dr0 = 0;
                    parentCtx.Dr7 = 0x400;
                    SetThreadContext(hThread, &parentCtx);

                    bQuit = TRUE;

                }

            }
            else
                dbgFlag = DBG_EXCEPTION_NOT_HANDLED;

            break;

        default:
            dbgFlag = DBG_EXCEPTION_NOT_HANDLED;
        }


        break;

    case LOAD_DLL_DEBUG_EVENT:
    {
        CloseHandle(DebugEvent.u.LoadDll.hFile);
        break;
    }
    case CREATE_PROCESS_DEBUG_EVENT:
    {
        CloseHandle(DebugEvent.u.CreateProcessInfo.hFile);
        break;
    }
    case EXIT_PROCESS_DEBUG_EVENT:
        break;
    default:
        __nop();
    }

    if (!ContinueDebugEvent(DebugEvent.dwProcessId, DebugEvent.dwThreadId, dbgFlag))
    {
        break;
    }

    if (bQuit)
        DebugActiveProcessStop(dwProcessID);

}


while (1)
{
    WORD currentHP = 0;
    WORD maxHP = 0;
    if (
        ReadProcessMemory(hProcess, currentHpPointerAddress, &currentHP, sizeof(currentHP), NULL) == 0
        || ReadProcessMemory(hProcess, maxHpPointerAddress, &maxHP, sizeof(maxHP), NULL) == 0
        )
    {
        printf("Failed to read memory: %u\n", GetLastError());
    }
    else {
        std::cout << "HP: " << currentHP << " / " << maxHP << std::endl;
    }

    Sleep(1000);
}

system("pause>nul");
return 0;

}

当我运行它时,游戏运行良好,直到断点发生,当它发生时,我得到无限的“断点”cout 的垃圾邮件,当我调试它时,这一行: if (DebugEvent.u.Exception.ExceptionRecord.ExceptionAddress == (void*)hpChangeBreakpoint)

总是正确的,但是 dwFirstChance 标志是 1,所以它总是一个新的异常?在这个无限循环中唯一改变的是 hThread,总是不同的。由于我缺乏知识,我觉得我错过了一些东西,因此将不胜感激任何帮助或正确方向的提示。谢谢!

【问题讨论】:

    标签: c++ windows debugging reverse-engineering


    【解决方案1】:

    你在听/知道Resume Flag (RF) 吗?您需要将其设置为越过DrX brealpoint

    所以代码必须在下一个

    #define RESUME_FLAG 0x10000
    
    CONTEXT Context = {};
    Context.ContextFlags = CONTEXT_CONTROL;// not need CONTEXT_FULL here;
    if (GetThreadContext(hThread, &Context))
    {
        Context.EFlags |= RESUME_FLAG; // !!! this line is key point
        SetThreadContext(hThread, &Context);
    }
    

    这将从win2003 或windows vista 开始工作。不幸的是,XP 不允许你在CONTEXT 中设置这个标志。所以在这里你需要删除 Dr0 断点以跳过它(或修补 XP 内核 - 在 ntoskrnl 代码中搜索 0x003E0DD7 DWORD 并将其替换为 0x003F0DD7 - 这是 Eflags 掩码 - 不同RESUME_FLAG)

    还提供一些优化建议 - 你不需要每次EXCEPTION_DEBUG_EVENT 时都调用OpenThread

    一开始你已经有了这个线程句柄

    HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, dwThreadID);
    

    在你拨打SetThreadContext之后,不要关闭它

    并且异常只能发生在这个线程的上下文中,所有其他线程不受这个影响。

    在第二次你永远不会关闭线程句柄,在EXCEPTION_DEBUG_EVENT 中打开 - 所以你已经有资源泄漏。

    调试器在CREATE_THREAD_DEBUG_EVENTCREATE_PROCESS_DEBUG_EVENT 上获得了线程句柄,并且必须 关闭它(或者只是或通常维护它并在EXIT_THREAD_DEBUG_EVENTEXIT_PROCESS_DEBUG_EVENT 上关闭)

    你没有处理LOAD_DLL_DEBUG_EVENT,因此没有关闭文件句柄。

    您的代码有大量的句柄泄漏

    SuspendThread / ResumeThread - 为了什么?!绝对没有意义 - 线程(以及进程中的所有线程)此时已经挂起


    struct CThread : LIST_ENTRY
    {
        HANDLE _hThread;
        ULONG _dwThreadId;
    
        CThread(HANDLE hThread, ULONG dwThreadId)
        {
            _hThread = hThread;
            _dwThreadId = dwThreadId;
        }
    
        ~CThread()
        {
            //CloseHandle(_hThread);// will be closed in ContinueDebugEvent
        }
    
        static CThread* get(ULONG dwThreadId, PLIST_ENTRY ThreadListHead, CThread* pHintThread)
        {
            if (pHintThread && pHintThread->_dwThreadId == dwThreadId)
            {
                return pHintThread;
            }
    
            PLIST_ENTRY entry = ThreadListHead;
    
            while ((entry = entry->Flink) != ThreadListHead)
            {
                pHintThread = static_cast<CThread*>(entry);
    
                if (pHintThread->_dwThreadId == dwThreadId)
                {
                    return pHintThread;
                }
            }
    
            return 0;//??
        }
    
        static void DeleteAll(PLIST_ENTRY ThreadListHead)
        {
            PLIST_ENTRY entry = ThreadListHead->Flink;
    
            while (entry != ThreadListHead)
            {
                CThread* pThread = static_cast<CThread*>(entry);
    
                entry = entry->Flink;
    
                delete pThread;
            }
        }
    };
    
    
    void RunDebuggerLoop()
    {
        BOOL bQuit = FALSE;
    
        LIST_ENTRY ThreadListHead = { &ThreadListHead, &ThreadListHead };
    
        CThread* pThread = 0;
    
        DEBUG_EVENT de;
    
        BOOLEAN bFirst = TRUE;
    
        while (!bQuit && WaitForDebugEvent(&de, INFINITE))
        {
            NTSTATUS status = DBG_CONTINUE;
    
            switch(de.dwDebugEventCode)
            {
            case EXCEPTION_DEBUG_EVENT:
                if (
                    !de.u.Exception.dwFirstChance 
                    || 
                    !(pThread = CThread::get(de.dwThreadId, &ThreadListHead, pThread))
                    )
                {
                    bQuit = TRUE;
                    continue;
                }
    
                status = DBG_EXCEPTION_NOT_HANDLED;
    
                switch (de.u.Exception.ExceptionRecord.ExceptionCode)
                {
                case STATUS_BREAKPOINT:
                case STATUS_WX86_BREAKPOINT:
                    if (bFirst)
                    {
                        bFirst = FALSE;
                        status = DBG_CONTINUE;
                    }
                    break;
    
                case STATUS_SINGLE_STEP:
                case STATUS_WX86_SINGLE_STEP:
                    {
                        ::CONTEXT ctx = {};
                        ctx.ContextFlags = CONTEXT_CONTROL;
                        if (GetThreadContext(pThread->_hThread, &ctx))
                        {
                            ctx.EFlags |= RESUME_FLAG;
                            SetThreadContext(pThread->_hThread, &ctx);
                        }
                    }
                    break;
    
                case STATUS_ACCESS_VIOLATION:
                    if (de.u.Exception.ExceptionRecord.NumberParameters > 1)
                    {
                        ULONG_PTR ptr = de.u.Exception.ExceptionRecord.ExceptionInformation[1];
                    }
    
                    break;                              
                }
    
                break;
    
            case CREATE_PROCESS_DEBUG_EVENT:
                CloseHandle(de.u.CreateProcessInfo.hFile);
                //CloseHandle(de.u.CreateProcessInfo.hProcess);// will be auto closed in ContinueDebugEvent
                de.u.CreateThread.hThread = de.u.CreateProcessInfo.hThread;
    
            case CREATE_THREAD_DEBUG_EVENT:
                if (pThread = new CThread(de.u.CreateThread.hThread, de.dwThreadId))
                {
                    InsertHeadList(&ThreadListHead, pThread);
                }
                break;
    
            case EXIT_THREAD_DEBUG_EVENT:
                if (pThread = CThread::get(de.dwThreadId, &ThreadListHead, pThread))
                {
                    RemoveEntryList(pThread);
                    delete pThread;
                    pThread = 0;
                }
                break;
    
            case LOAD_DLL_DEBUG_EVENT:
                CloseHandle(de.u.LoadDll.hFile);
                break;
    
            case EXIT_PROCESS_DEBUG_EVENT:
                bQuit = TRUE;
                break;
    
            case OUTPUT_DEBUG_STRING_EVENT:
            case UNLOAD_DLL_DEBUG_EVENT:
                __nop();
                break;
            default:
                __nop();
            }
    
            if (!ContinueDebugEvent(de.dwProcessId, de.dwThreadId, status))
            {
                break;
            }
        }
    
        CThread::DeleteAll(&ThreadListHead);
    }
    void Ep()
    {
        // tag by * in begin of CommandLine
        PWSTR CommandLine = GetCommandLine();
    
        if (!CommandLine || *CommandLine != '*')
        {
            // debugger case
    
            WCHAR FileName[MAX_PATH];
            if (ULONG n = GetModuleFileName(0, FileName, RTL_NUMBER_OF(FileName)))
            {
                if (n < MAX_PATH)
                {
                    PROCESS_INFORMATION pi;
                    STARTUPINFO si = { sizeof(si) };
                    PWSTR newCommandLine = (PWSTR)alloca((wcslen(CommandLine) + 2)*sizeof(WCHAR));
                    *newCommandLine = '*';
                    wcscpy(newCommandLine + 1, CommandLine);
    
                    if (CreateProcessW(FileName, newCommandLine, 0, 0, 0, DEBUG_PROCESS, 0, 0, &si, &pi))
                    {
                        CloseHandle(pi.hThread);
                        CloseHandle(pi.hProcess);
    
                        RunDebuggerLoop();
                    }
                }
            }
            ExitProcess(0);
        }
        else
        {
            // main case
    
            wcscpy(CommandLine, CommandLine + 1);
    
            OutputDebugStringA("AAAAAA\n");
    
            if (MessageBoxW(0, L"xxx", CommandLine, MB_YESNO) == IDYES)
            {
                OutputDebugStringW(L"WWWWWWWW\n");
            }
            ExitProcess(0);
        }
    }
    

    【讨论】:

      【解决方案2】:

      非常感谢您花时间回答我。如果有些问题很奇怪,我很抱歉,但我是一名 JS 开发人员,我在这里做的是我的爱好。我所知道的是,与我的 JS 相比,它感觉就像一个不同且更深的世界.. ;)

      我确实编辑了代码,也删除了您提到的多余内容。线程的挂起/恢复在那里,因为它们之间我有一些内存修改,但根据你所说的,无论如何,线程在这一点上被挂起,即使我会修改内存,也不需要它们?

      回到正题,死循环还在。我已经添加了 RF 标志,但我刚刚开始阅读有关它的文章,所以除了添加它之外,我也明白为什么。同时,您能否再给我一个提示,说明为什么它可能仍然无法正常工作? 此外,我添加了 LOAD_DLL_DEBUG_EVENT 处理,并且我将立即关闭处理程序,因为此时我不需要对它做任何其他事情(是吗?)。我不完全明白什么时候应该关闭从 CREATE_PROCESS 和 CREATE_THREAD 调试事件收到的处理程序?我正在尝试总结调试器的工作原理,到目前为止,这是我使用它的第 4 天,但正如我所见,这就是发生的事情:

      WaitForDebugEvent 接收到一个调试事件,只要它不是我的,它就由 ContinueDebugEvent 处理,带有 DBG_EXCEPTION_NOT_HANDLED,所以它被传回并由游戏处理。 最后,WaitForDebugEvent 收到我的调试事件,即 EXCEPTION_SINGLE_STEP,我在那里做我的事情,然后让它继续使用 DBG_CONTINUE - 它将异常标记为由我处理,并且系统继续跟踪它。对吗?

      我的实际代码仍在无限循环中循环并打印“断点”:

      #include "Header.h"
      #include <iostream>
      
      int main() {
      
      
      hpChangeBreakpoint = 0x013FB279;
      
      SetDebugPrivilege(TRUE);
      
      DWORD dwProcessID = 0;
      DWORD dwGame = 0;
      
      printf("Looking for game process...\n");
      
      while (dwProcessID == 0) {
          dwProcessID = GetProcessID(L"PathOfExile.exe");
          if (dwProcessID != 0)
              dwGame = 1;
      
          Sleep(100);
      }
      
      printf("dwProcessID = %p\n", dwProcessID);
      
      HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, dwProcessID);
      
      BOOL bDebugging = DebugActiveProcess(dwProcessID);
      printf("bDebugging = %d\n", bDebugging);
      
      
      DWORD dwThreadID = GetProcessThreadID(dwProcessID);
      
      HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, dwThreadID);
      
      CONTEXT context;
      
      context.ContextFlags = CONTEXT_DEBUG_REGISTERS;
      
      if (GetThreadContext(hThread, &context))
      {
          context.Dr0 = hpChangeBreakpoint;
          context.Dr7 = 0x00000001;
      
          std::cout << "GetThreadContext successful" << std::endl;
      
          SetThreadContext(hThread, &context);
      }
      
      
      DEBUG_EVENT DebugEvent;
      BOOL bContinueDebugging = false;
      
      
      
      for(;;)
      {
          WaitForDebugEvent(&DebugEvent, INFINITE);
      
          switch (DebugEvent.dwDebugEventCode)
          {
      
          case EXCEPTION_DEBUG_EVENT:
      
              switch (DebugEvent.u.Exception.ExceptionRecord.ExceptionCode)
              {
              case EXCEPTION_SINGLE_STEP:
                  if (DebugEvent.u.Exception.ExceptionRecord.ExceptionAddress == (void*)hpChangeBreakpoint)
                  {
                      #define RESUME_FLAG 0x10000
                      CONTEXT Context;
                      Context.ContextFlags = CONTEXT_CONTROL;
                      Context.EFlags |= RESUME_FLAG;
      
      
                      std::cout << "Breakpoint" << std::endl;
      
                      bContinueDebugging = true;
                  }
                  if (bContinueDebugging)
                  {
                      // DBG_CONTINUE to tell the program we have handled the exception
                      ContinueDebugEvent(DebugEvent.dwProcessId, DebugEvent.dwThreadId, DBG_CONTINUE);
                      bContinueDebugging = false;
                  }
                  else // if the exception was not handled by our exception-handler, we want the program to handle it, so..
                      ContinueDebugEvent(DebugEvent.dwProcessId, DebugEvent.dwThreadId, DBG_EXCEPTION_NOT_HANDLED);
      
                  break;
              default:
                  ContinueDebugEvent(DebugEvent.dwProcessId, DebugEvent.dwThreadId, DBG_EXCEPTION_NOT_HANDLED);
              }
      
      
              break;
      
          case LOAD_DLL_DEBUG_EVENT:
          {
              std::cout << "load dll debug event" << std::endl;
              CloseHandle(DebugEvent.u.LoadDll.hFile);
              break;
          }
          default:
              ContinueDebugEvent(DebugEvent.dwProcessId, DebugEvent.dwThreadId, DBG_EXCEPTION_NOT_HANDLED);
          }
      
      }
      
      system("pause>nul");
      return 0;
      

      }

      【讨论】:

      • “回到正题,死循环还在。我已经添加了RF标志”——但你没有调用“SetThreadContext”!!简单地初始化CONTEXT Context 有什么意义 - 无需调用“SetThreadContext” - 首先修复此问题,以解决主要问题。如果你不是在 XP 下运行。 “无论如何,线程在此时暂停” - 在您调用 ContinueDebugEvent 之前 - 所有进程都已暂停 - 您不需要调用 SuspendThread / ResumeThread “即使我会修改内存,也不需要它们?” - 是的,不需要
      • 哦,我的狗,太明显了 :) 让我开始吧,会让你知道的!非常感谢 RbMm!
      • 它看起来像通过伙伴! :) 谢谢!你还有另一个问题 - 在通过此异常后,有一个 EXCEPTION_ACCESS_VIOLATION,如果我不处理它,在第二次尝试后它会使应用程序崩溃。会不会和我正在做的事情有关?
      • 是的,我的错误 :) 我忘了说你调用 GetThreadContext(hThread, &context);太:)
      • 我更新了自己的代码 sn-p。你没有在调用SetThreadContext 之前初始化CONTEXT Context 结构,结果是Context.Eip 堆栈中的垃圾(0xcccccccc 值)
      猜你喜欢
      • 1970-01-01
      • 2020-09-26
      • 2014-11-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-09-11
      • 2019-04-12
      相关资源
      最近更新 更多