SymEnumSymbols returns ERROR_SUCCESS 但没有给出任何结果
SymEnumSymbols returns ERROR_SUCCESS but gives no results
我正在尝试从已加载的 DLL 中枚举符号。对于那些感兴趣的人,这是 CPPCoverage project 的一部分,对于某些功能,我需要符号数据。
问题分解
当启动进程或加载DLL时,需要枚举一些已计划的新功能的符号。
基本上就是创建一个进程,dbghelp
用于获取符号信息。接下来,使用 SymEnumSymbols
迭代符号。有两个时刻会发生这种情况:
- 进程启动时(
CREATE_PROCESS_DEBUG_EVENT
)
- 加载 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 小组没有一起工作的另一个例子。
我正在尝试从已加载的 DLL 中枚举符号。对于那些感兴趣的人,这是 CPPCoverage project 的一部分,对于某些功能,我需要符号数据。
问题分解
当启动进程或加载DLL时,需要枚举一些已计划的新功能的符号。
基本上就是创建一个进程,dbghelp
用于获取符号信息。接下来,使用 SymEnumSymbols
迭代符号。有两个时刻会发生这种情况:
- 进程启动时(
CREATE_PROCESS_DEBUG_EVENT
) - 加载 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 小组没有一起工作的另一个例子。