Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

VC++ stack trace does not resolve function names on production

I recently implemented stack trace logging using boost's new stacktrace library:

int debugErrorCallback(int status, const char* func_name, const char* err_msg, const char* file_name, int line, void* userdata)
{
    boost::stacktrace::stacktrace stacktrace(4, 10); //skipped 4 frames include cv::error, this function and 2 in boost::stacktrace ctor
    std::cout << boost::stacktrace::detail::to_string(stacktrace.as_vector().data(), 
                                                      stacktrace.size()) << std::endl;
}

Having tested it on my development machine with perfect results:

0# cv::arithm_op at d:\src\opencv_24\modules\core\src\arithm.cpp:1293
1# cv::addWeighted at d:\src\opencv_24\modules\core\src\arithm.cpp:2127
2# MyApplication::myFunction at d:\src\path\to\my\file.cpp:226
3# MyApplication::myOtherFunction at d:\src\path\to\my\other_file.cpp:146
...

I deployed new application version to production. Unfortunately, on production machine stack frames in my module are only resolved to the module name:

0# cv::addWeighted in opencv_core2413
1# cv::addWeighted in opencv_core2413
2# 0x00007FF7D0A0B56B in MyApplication
3# 0x00007FF7D0A0B2ED in MyApplication
...

I have debugged boost implementation of stacktrace to find that the stack functions' addresses are retrieved with RtlCaptureStackBackTrace, which seems to be working fine. Function names are then retrieved in boost::stacktrace::detail::to_string -> get_name_impl function in frame_msvc.ipp file, which internally uses IDebugSymbols object from library Dbgeng. This interface declares a function IDebugSymbols::GetNameByOffset which is supposed to retrieve function name in format: Module!function. Unfortunately, it only does so on my development environment, while only returning module name on production systems. It seems like it's unable to retrieve symbols data for some reason, despite having deployed the MyApplication.pdb file along with the application.

As you might have noticed, function name (without source file and line number, which I could live without) is retrieved for statically linked opencv library, but not for my own sources.

I could not find what are the requirements for this implementation to work. Does anyone have any idea how to make this work regardless of runtime system?

EDIT2:

It turns out that the source of the problem is in fact inability to load the .pdb file. For some reason IDebugClient, IDebugControl or IDebugSymbols, whichever is supposed to load the symbols file (the are created in sequence during initialization of debugging_symbols class that is used to retrieve symbols in boost::stacktrace::detail::to_string), only looks for the it in the location written in the "Debugger Directories" section of the executable header. This is the path that VS creates in during linking (I tested it by copying the file to the same path on remote system and only then it worked).

Is there a way to make it load .pdb file in the local directory of the executable? Or perhaps it is possible to save relative path for the symbols file in the executable header?

EDIT: Adding MCVE:

#include "boost/stacktrace.hpp"
int main()
{
    boost::stacktrace::stacktrace stacktrace;
    std::cout << boost::stacktrace::detail::to_string(stacktrace.as_vector().data(), stacktrace.size()) << std::endl;
    return 0;
}

And linker options:

/OUT:"path\x64\Release\MiscTest.exe" /MANIFEST /LTCG:incremental /NXCOMPAT /PDB:"path\x64\Release\MiscTest.pdb" /DYNAMICBASE "kernel32.lib" "user32.lib" "gdi32.lib" "winspool.lib" "comdlg32.lib" "advapi32.lib" "shell32.lib" "ole32.lib" "oleaut32.lib" "uuid.lib" "odbc32.lib" "odbccp32.lib" /DEBUG:FULL /MACHINE:X64 /OPT:REF /INCREMENTAL:NO /PGD:"path\x64\Release\MiscTest.pgd" /SUBSYSTEM:CONSOLE /MANIFESTUAC:"level='asInvoker' uiAccess='false'" /ManifestFile:"x64\Release\MiscTest.exe.intermediate.manifest" /OPT:ICF /ERRORREPORT:PROMPT /NOLOGO /TLBID:1 
like image 522
slawekwin Avatar asked Mar 06 '23 10:03

slawekwin


1 Answers

It seems that the reason is that IDebugSymbols does not load symbol file located in the directory of the executable, but only the one saved in Portable Executable header (it worked when I copied the .pdb file into the same path as it is on my development system).

I worked around this issue by creating my own IDebugSymbols instance (I copied the code creating it from frame_msvc.ipp debugging_symbols::try_init_com) and modifying the last line to add "." (current directory) to symbol search path with IDebugSymbols::AppendSymbolPath:

if (S_OK == iclient->QueryInterface(__uuidof(IDebugSymbols), idebug.to_void_ptr_ptr()))
    idebug->AppendSymbolPath(".");

This needs to be done only once during the process lifetime - it appears that the symbols paths string (or the symbols themselves) is cached, but I could not find documentation confirming it. Alternatively, the existence of "." part in the path could be checked with IDebugSymbols::GetSymbolPath during every debugging_symbols::try_init_com call. I also tested that IDebugSymbols::AppendSymbolPath checks current paths string and does not append duplicates (again could not find documentation to back this up) so it might be enough to always just try to append the "." during object initialization.

like image 155
slawekwin Avatar answered Mar 16 '23 19:03

slawekwin