SymEnumSymbols returns ERROR_SUCCESS 但没有给出任何结果

SymEnumSymbols returns ERROR_SUCCESS but gives no results

我正在尝试从已加载的 DLL 中枚举符号。对于那些感兴趣的人,这是 CPPCoverage project 的一部分,对于某些功能,我需要符号数据。

问题分解

当启动进程或加载DLL时,需要枚举一些已计划的新功能的符号。

基本上就是创建一个进程,dbghelp用于获取符号信息。接下来,使用 SymEnumSymbols 迭代符号。有两个时刻会发生这种情况:

  1. 进程启动时(CREATE_PROCESS_DEBUG_EVENT)
  2. 加载 DLL 时 (LOAD_DLL_DEBUG_EVENT)

在 (2) 期间一切正常。但是,在(1)期间不能枚举符号。

行为是一切正常,直到 SymEnumSymbols 调用。 return 值告诉我有错误,但是 GetLastError return 成功了。另外,没有调用回调函数。

更奇怪的是,调用 SymGetSymFromName 确实有效。

最小测试用例

static BOOL CALLBACK EnumerateSymbols(
                          PSYMBOL_INFO pSymInfo, ULONG SymbolSize, PVOID UserContext)
{
    std::cout << "Symbol: " << pSymInfo->Name << std::endl;
    return TRUE;
}

void Test()
{
    SymSetOptions(SYMOPT_LOAD_ANYTHING);

    STARTUPINFO si;
    PROCESS_INFORMATION pi;
    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);
    ZeroMemory(&pi, sizeof(pi));

    auto str = "FullPathToSomeExeWithPDB.exe";
    auto result = CreateProcess(str, NULL, NULL, NULL, FALSE,
                                DEBUG_PROCESS, NULL, NULL, &si, &pi);
    if (result == 0)
    {
        auto err = GetLastError();
        std::cout << "Error running process: " << err << std::endl;
        return;
    }

    if (!SymInitialize(pi.hProcess, NULL, FALSE))
    {
        auto err = GetLastError();
        std::cout << "Symbol initialization failed: " << err << std::endl;
        return;
    }

    bool first = false;
    DEBUG_EVENT debugEvent = { 0 };
    while (!first)
    {
        if (!WaitForDebugEvent(&debugEvent, INFINITE))
        {
            auto err = GetLastError();
            std::cout << "Wait for debug event failed: " << err << std::endl;
            return;
        }
        if (debugEvent.dwDebugEventCode == CREATE_PROCESS_DEBUG_EVENT)
        {
            auto dllBase = SymLoadModuleEx(
                pi.hProcess,
                debugEvent.u.CreateProcessInfo.hFile,
                str,
                NULL,
                reinterpret_cast<DWORD64>(debugEvent.u.CreateProcessInfo.lpBaseOfImage),
                0,
                NULL,
                0);

            if (!dllBase)
            {
                auto err = GetLastError();
                std::cout << "Loading the module failed: " << err << std::endl;
                return;
            }

            if (!SymEnumSymbols(pi.hProcess, dllBase, NULL, EnumerateSymbols, nullptr))
            {
                auto err = GetLastError();
                std::cout << "Error: " << err << std::endl;
            }

            first = true;
        }
    } 
    // cleanup code is omitted
}

在@SimonMournier 发表评论后,我运行 进行了很多其他测试。最终,我能够弄清楚这里的问题是什么。事实证明,Visual Studio 中的链接器标志 /DEBUG:FastLink 实际上导致了问题。

经过一番 google'ing 我在社区论坛上发现了这条通知:https://developercommunity.visualstudio.com/content/problem/4631/dia-sdk-still-doesnt-support-debugfastlink.html

[...] Windows debuggers team has been informed to build a new dbghelp.dll with VS 2017 PDB/DIA static libraries and the next public release of Windows SDK (or debugger kits) will contain dbghelp.dll that is able to deal with fastlink PDBs. The latest VS 2017 pre-release would install a new dbghelp.dll under VS installation directory that works with fastlink PDBs.

所以,简而言之,它根本无法与 Visual Studio 2015 一起使用,因为 DIA 根本不支持它。当我们升级到 VS2017 时,它会自动修复。此外,当与 /DEBUG 链接时,一切都会正常进行。

Brr,真是个笨蛋。我在 VS2017 中得到了一个重现,使用从 Win32 控制台项目模板构建的简单目标可执行文件。我尝试过的任何事情都无法说服 SymEnumSymbols() 枚举任何符号。接下来我扩展了代码,还捕获了 LOAD_DLL_DEBUG_EVENT 通知:

if (debugEvent.dwDebugEventCode == LOAD_DLL_DEBUG_EVENT) {
    auto base = SymLoadModule64(pi.hProcess, debugEvent.u.LoadDll.hFile, NULL, NULL, NULL, 0);
    if (!base) {
        auto err = GetLastError();
        std::cout << err << std::endl;
     }
    else {
        CloseHandle(debugEvent.u.LoadDll.hFile);
        SymEnumSymbols(pi.hProcess, base, NULL, EnumerateSymbols, nullptr);
    }
}

除了在 SymInitialize() 中正确设置符号搜索路径外,它工作得很好,并正确列出了 ntdll.dll 等中的符号

结论:PDB文件有问题

得到了回报。从 VS2015 开始,微软一直在修补 PDB 文件生成。他们添加了 /DEBUG:FASTLINK option。请注意,链接文档具有误导性,它也是 VS2015 中的默认设置。操作系统的 DbgHelp.dll 版本无法正确枚举生成的 PDB 文件。 GetLastError() 代码非常具有误导性,我在上面花费了太多时间,我认为它仅表示 "I successfully enumerated nothing"。请注意如何为其他 DbgHelp api 函数(如 SymSetContext 和 SymLoadModuleEx)记录此代码。

在 VS2015 中使用“项目”>“属性”>“链接器”>“调试”>“生成调试信息”= "Optimize for debugging (/DEBUG)"。

在 VS2017 中使用“项目”>“属性”>“链接器”>“调试”>“生成调试信息”= "Generate Debug Information optimized for sharing and publishing (/DEBUG:FULL)"。

强调这些设置对目标项目很重要,而不是调试器项目。理想情况下,会有一个 DbgHelp.dll 版本也可以从 PDB 的快速链接版本读取调试信息。我找不到一个,SDK 8.1 和 SDK 10 附带的那些都没有解决问题。 DevDiv 和 Windows 小组没有一起工作的另一个例子。