调试线程时无限循环

Infinite loop while debugging a thread

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

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

#include "Header.h" #include

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;

}

当我 运行 它时,游戏运行正常,直到断点发生,当断点发生时,我得到无限 "breakpoint" cout 的垃圾邮件,当我调试它时,这一行: 如果 (DebugEvent.u.Exception.ExceptionRecord.ExceptionAddress == (void*)hpChangeBreakpoint)

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

你 listen/know 关于 Resume Flag (RF) 吗?您需要将其设置为 step over 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_EVENT 上关闭和 EXIT_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);
    }
}

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

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

言归正传,死循环还在。我已经添加了 RF 标志,但我才刚刚开始通读相关文章,所以除了添加它之外,我也明白为什么。同时,您能再给我一个提示,说明为什么它可能仍然无法正常工作吗? 此外,我添加了 LOAD_DLL_DEBUG_EVENT 处理程序,并且我将立即关闭处理程序,因为此时我不需要对其执行任何其他操作(是吗?)。我不完全明白的是我应该何时关闭从 CREATE_PROCESS 和 CREATE_THREAD 调试事件接收到的处理程序?我正在努力了解调试器是如何工作的,这是我使用它的第 4 天,但正如我所看到的,这就是发生的事情:

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

我的实际代码仍在无限循环中循环并打印 "Breakpoint":

#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;

}