使用 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 目标,但它没有错误地工作并且给了我我需要的东西。