使用 Visual Studio PDB 确定函数的源代码文件名和行
Determining source code filename and line for function using Visual Studio PDB
我正在尝试使用函数定义的地址和 visual studio 为定义函数的模块生成的 pdb 数据库文件,以编程方式确定函数定义的文件名和源代码行。
例如,我在模块 shared.dll
中定义了一个函数 Lua::asset::get_supported_export_file_extensions
,我想确定它的源代码位置。
为了获得函数的相对虚拟地址 (rva),我从绝对虚拟函数地址中减去模块基地址,如下所示:
static MODULEENTRY32 GetModuleInfo(std::uint32_t ProcessID, const char* ModuleName)
{
void* hSnap = nullptr;
MODULEENTRY32 Mod32 = {0};
if ((hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, ProcessID)) == INVALID_HANDLE_VALUE)
return Mod32;
Mod32.dwSize = sizeof(MODULEENTRY32);
while (Module32Next(hSnap, &Mod32))
{
if (!strcmp(ModuleName, Mod32.szModule))
{
CloseHandle(hSnap);
return Mod32;
}
}
CloseHandle(hSnap);
return {0};
}
int main(int argc,char *argv[])
{
auto dllInfo = GetModuleInfo(GetCurrentProcessId(),"shared.dll");
auto rva = (DWORD)((uint64_t)&Lua::asset::get_supported_export_file_extensions -(uint64_t)dllInfo.modBaseAddr);
[...]
}
这给了我函数的 rva 0x44508。
为了确认地址,我尝试使用 Windows SDK 中的 dbh debugging tool 来查找函数的地址(以及模块的基地址):
shared [1000000]: enum shared!*get_supported_export_file_extensions*
index address name
1 1e376c0 : `Lua::asset::get_supported_export_file_extensions'::`1'::dtor[=12=]
3 18409c0 : Lua::asset::get_supported_export_file_extensions
shared [1000000]: scope 18409c0
name : path\to\object_files\lasset.obj
addr : 0
size : 0
flags : 0
type : 0
modbase : 1000000
value : 0
reg : 0
scope : SymTagNull (0)
tag : SymTagCompiland (2)
index : 6
如果我从函数地址中减去基地址,我希望它能给我相同的 rva,但它却给了我 0x18409c0 -0x1000000 = 0x8409c0
的 rva
为方便起见,我将地址称为:
0x44508 = calculated address
0x8409c0 = dbh address
然后我使用 Debug Interface Access SDK 在 pdb 中查找两个地址以确定我得到不同结果的原因:
static BOOL find_function_in_pdb(DWORD rva,enum SymTagEnum tag)
{
std::string pdbFilePath = "path/to/shared.pdb";
CComPtr<IDiaDataSource> pSource;
if(FAILED(CoInitializeEx(NULL,COINIT_MULTITHREADED)))
return FALSE;
auto hr = CoCreateInstance(
CLSID_DiaSource,
NULL,
CLSCTX_INPROC_SERVER,
__uuidof(IDiaDataSource),
(void **) &pSource
);
if(FAILED(hr))
return FALSE;
wchar_t wszFilename[_MAX_PATH];
mbstowcs(wszFilename,pdbFilePath.data(),sizeof(wszFilename) /sizeof(wszFilename[0]));
if(FAILED(pSource->loadDataFromPdb(wszFilename)))
return FALSE;
IDiaSession *session;
IDiaSymbol *globalSymbol = nullptr;
IDiaEnumTables *enumTables = nullptr;
IDiaEnumSymbolsByAddr *enumSymbolsByAddr = nullptr;
if(FAILED(pSource->openSession(&session)))
return FALSE;
if(FAILED(session->get_globalScope(&globalSymbol)))
return FALSE;
if(FAILED(session->getEnumTables(&enumTables)))
return FALSE;
if(FAILED(session->getSymbolsByAddr(&enumSymbolsByAddr)))
return FALSE;
IDiaSymbol *symbol;
if(session->findSymbolByRVA(rva,tag,&symbol) == S_OK)
{
BSTR name;
symbol->get_name(&name);
std::cout<<"Name: "<<ConvertBSTRToMBS(name)<<std::endl;
ULONGLONG length = 0;
if(symbol->get_length(&length) == S_OK)
{
IDiaEnumLineNumbers *lineNums[100];
if(session->findLinesByRVA(rva,length,lineNums) == S_OK)
{
auto &l = lineNums[0];
CComPtr<IDiaLineNumber> line;
IDiaLineNumber *lineNum;
ULONG fetched = 0;
for(uint8_t i=0;i<5;++i) {
if(l->Next(i,&lineNum,&fetched) == S_OK && fetched == 1)
{
DWORD l;
IDiaSourceFile *srcFile;
if(lineNum->get_sourceFile(&srcFile) == S_OK)
{
BSTR fileName;
srcFile->get_fileName(&fileName);
std::cout<<"File: "<<ConvertBSTRToMBS(fileName)<<std::endl;
}
if(lineNum->get_lineNumber(&l) == S_OK)
std::cout<<"Line: "<<+l<<std::endl;
}
}
}
}
}
return TRUE;
}
int main(int argc,char *argv[])
{
find_function_in_pdb(0x44508 /* calculated address */,SymTagEnum::SymTagPublicSymbol);
find_function_in_pdb(0x8409c0 /* dbh address */,SymTagEnum::SymTagFunction);
[...]
}
它确实找到了两个地址并且都指向一个名称与我的函数匹配的符号,但是计算地址处的符号是 SymTagPublicSymbol
而 dbh 地址处的符号是 SymTagFunction
.
我猜这意味着计算出的地址是针对 public 符号和私有符号的 dbh 地址? (https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/public-and-private-symbols)
到目前为止一切顺利,问题是 public 符号 没有 有任何与之关联的源代码信息,但私有符号有。假设到目前为止我是正确的(我不太确定),我的问题归结为:
如何从 public symbol/address 中获取私有 symbol/address?我需要一个可以以编程方式实现的解决方案。
经过更多的实验,我找到了解决方案:
IDiaSymbol *publicSymbol;
DWORD publicRva = 0x44508;
if(session->findSymbolByRVA(publicRva,SymTagEnum::SymTagPublicSymbol,&publicSymbol) == S_OK)
{
DWORD privateRva;
IDiaSymbol *privateSymbol;
if(
publicSymbol->get_targetRelativeVirtualAddress(&privateRva) == S_OK &&
session->findSymbolByRVA(privateRva,SymTagEnum::SymTagFunction,&privateSymbol) == S_OK
)
{
// Do stuff with private symbol
}
}
get_targetRelativeVirtualAddress
给我 0x8409c0
,包含源代码信息的私有符号的地址。
至于为什么会这样,我不知道。根据文档,get_targetRelativeVirtualAddress 只应该对 SymTagThunk
符号和 returns 一个“thunk 目标”的 rva 有效。我不认为 public 符号 是 一个 thunk 目标,但它没有错误地工作并且给了我我需要的东西。
我正在尝试使用函数定义的地址和 visual studio 为定义函数的模块生成的 pdb 数据库文件,以编程方式确定函数定义的文件名和源代码行。
例如,我在模块 shared.dll
中定义了一个函数 Lua::asset::get_supported_export_file_extensions
,我想确定它的源代码位置。
为了获得函数的相对虚拟地址 (rva),我从绝对虚拟函数地址中减去模块基地址,如下所示:
static MODULEENTRY32 GetModuleInfo(std::uint32_t ProcessID, const char* ModuleName)
{
void* hSnap = nullptr;
MODULEENTRY32 Mod32 = {0};
if ((hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, ProcessID)) == INVALID_HANDLE_VALUE)
return Mod32;
Mod32.dwSize = sizeof(MODULEENTRY32);
while (Module32Next(hSnap, &Mod32))
{
if (!strcmp(ModuleName, Mod32.szModule))
{
CloseHandle(hSnap);
return Mod32;
}
}
CloseHandle(hSnap);
return {0};
}
int main(int argc,char *argv[])
{
auto dllInfo = GetModuleInfo(GetCurrentProcessId(),"shared.dll");
auto rva = (DWORD)((uint64_t)&Lua::asset::get_supported_export_file_extensions -(uint64_t)dllInfo.modBaseAddr);
[...]
}
这给了我函数的 rva 0x44508。
为了确认地址,我尝试使用 Windows SDK 中的 dbh debugging tool 来查找函数的地址(以及模块的基地址):
shared [1000000]: enum shared!*get_supported_export_file_extensions*
index address name
1 1e376c0 : `Lua::asset::get_supported_export_file_extensions'::`1'::dtor[=12=]
3 18409c0 : Lua::asset::get_supported_export_file_extensions
shared [1000000]: scope 18409c0
name : path\to\object_files\lasset.obj
addr : 0
size : 0
flags : 0
type : 0
modbase : 1000000
value : 0
reg : 0
scope : SymTagNull (0)
tag : SymTagCompiland (2)
index : 6
如果我从函数地址中减去基地址,我希望它能给我相同的 rva,但它却给了我 0x18409c0 -0x1000000 = 0x8409c0
为方便起见,我将地址称为:
0x44508 = calculated address
0x8409c0 = dbh address
然后我使用 Debug Interface Access SDK 在 pdb 中查找两个地址以确定我得到不同结果的原因:
static BOOL find_function_in_pdb(DWORD rva,enum SymTagEnum tag)
{
std::string pdbFilePath = "path/to/shared.pdb";
CComPtr<IDiaDataSource> pSource;
if(FAILED(CoInitializeEx(NULL,COINIT_MULTITHREADED)))
return FALSE;
auto hr = CoCreateInstance(
CLSID_DiaSource,
NULL,
CLSCTX_INPROC_SERVER,
__uuidof(IDiaDataSource),
(void **) &pSource
);
if(FAILED(hr))
return FALSE;
wchar_t wszFilename[_MAX_PATH];
mbstowcs(wszFilename,pdbFilePath.data(),sizeof(wszFilename) /sizeof(wszFilename[0]));
if(FAILED(pSource->loadDataFromPdb(wszFilename)))
return FALSE;
IDiaSession *session;
IDiaSymbol *globalSymbol = nullptr;
IDiaEnumTables *enumTables = nullptr;
IDiaEnumSymbolsByAddr *enumSymbolsByAddr = nullptr;
if(FAILED(pSource->openSession(&session)))
return FALSE;
if(FAILED(session->get_globalScope(&globalSymbol)))
return FALSE;
if(FAILED(session->getEnumTables(&enumTables)))
return FALSE;
if(FAILED(session->getSymbolsByAddr(&enumSymbolsByAddr)))
return FALSE;
IDiaSymbol *symbol;
if(session->findSymbolByRVA(rva,tag,&symbol) == S_OK)
{
BSTR name;
symbol->get_name(&name);
std::cout<<"Name: "<<ConvertBSTRToMBS(name)<<std::endl;
ULONGLONG length = 0;
if(symbol->get_length(&length) == S_OK)
{
IDiaEnumLineNumbers *lineNums[100];
if(session->findLinesByRVA(rva,length,lineNums) == S_OK)
{
auto &l = lineNums[0];
CComPtr<IDiaLineNumber> line;
IDiaLineNumber *lineNum;
ULONG fetched = 0;
for(uint8_t i=0;i<5;++i) {
if(l->Next(i,&lineNum,&fetched) == S_OK && fetched == 1)
{
DWORD l;
IDiaSourceFile *srcFile;
if(lineNum->get_sourceFile(&srcFile) == S_OK)
{
BSTR fileName;
srcFile->get_fileName(&fileName);
std::cout<<"File: "<<ConvertBSTRToMBS(fileName)<<std::endl;
}
if(lineNum->get_lineNumber(&l) == S_OK)
std::cout<<"Line: "<<+l<<std::endl;
}
}
}
}
}
return TRUE;
}
int main(int argc,char *argv[])
{
find_function_in_pdb(0x44508 /* calculated address */,SymTagEnum::SymTagPublicSymbol);
find_function_in_pdb(0x8409c0 /* dbh address */,SymTagEnum::SymTagFunction);
[...]
}
它确实找到了两个地址并且都指向一个名称与我的函数匹配的符号,但是计算地址处的符号是 SymTagPublicSymbol
而 dbh 地址处的符号是 SymTagFunction
.
我猜这意味着计算出的地址是针对 public 符号和私有符号的 dbh 地址? (https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/public-and-private-symbols)
到目前为止一切顺利,问题是 public 符号 没有 有任何与之关联的源代码信息,但私有符号有。假设到目前为止我是正确的(我不太确定),我的问题归结为:
如何从 public symbol/address 中获取私有 symbol/address?我需要一个可以以编程方式实现的解决方案。
经过更多的实验,我找到了解决方案:
IDiaSymbol *publicSymbol;
DWORD publicRva = 0x44508;
if(session->findSymbolByRVA(publicRva,SymTagEnum::SymTagPublicSymbol,&publicSymbol) == S_OK)
{
DWORD privateRva;
IDiaSymbol *privateSymbol;
if(
publicSymbol->get_targetRelativeVirtualAddress(&privateRva) == S_OK &&
session->findSymbolByRVA(privateRva,SymTagEnum::SymTagFunction,&privateSymbol) == S_OK
)
{
// Do stuff with private symbol
}
}
get_targetRelativeVirtualAddress
给我 0x8409c0
,包含源代码信息的私有符号的地址。
至于为什么会这样,我不知道。根据文档,get_targetRelativeVirtualAddress 只应该对 SymTagThunk
符号和 returns 一个“thunk 目标”的 rva 有效。我不认为 public 符号 是 一个 thunk 目标,但它没有错误地工作并且给了我我需要的东西。