You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
740 lines
17 KiB
740 lines
17 KiB
// Copyright (c) 1997-1999 Microsoft Corporation
|
|
//
|
|
// stack backtracing stuff
|
|
//
|
|
// 22-Nov-1999 sburns (refactored)
|
|
|
|
|
|
|
|
#include "headers.hxx"
|
|
#include <strsafe.h>
|
|
|
|
|
|
|
|
// prevent us from calling ASSERT in this file: use RTLASSERT instead
|
|
|
|
#ifdef ASSERT
|
|
#undef ASSERT
|
|
#endif
|
|
|
|
|
|
// Since we call some of this code from Burnslib::FireAssertionFailure,
|
|
// we use our own even more private ASSERT
|
|
|
|
#if DBG
|
|
|
|
#define RTLASSERT( exp ) \
|
|
if (!(exp)) \
|
|
RtlAssert( #exp, __FILE__, __LINE__, NULL )
|
|
|
|
#else
|
|
#define RTLASSERT( exp )
|
|
#endif // DBG
|
|
|
|
|
|
|
|
static HMODULE imageHelpDll = 0;
|
|
|
|
|
|
|
|
// function pointers to be dynamically resolved by the Initialize function.
|
|
|
|
typedef DWORD (*SymSetOptionsFunc)(DWORD);
|
|
static SymSetOptionsFunc MySymSetOptions = 0;
|
|
|
|
typedef BOOL (*SymInitializeFunc)(HANDLE, PSTR, BOOL);
|
|
static SymInitializeFunc MySymInitialize = 0;
|
|
|
|
typedef BOOL (*SymCleanupFunc)(HANDLE);
|
|
static SymCleanupFunc MySymCleanup = 0;
|
|
|
|
typedef BOOL (*SymGetModuleInfoFunc)(HANDLE, DWORD64, PIMAGEHLP_MODULE64);
|
|
static SymGetModuleInfoFunc MySymGetModuleInfo = 0;
|
|
|
|
typedef BOOL (*SymGetLineFromAddrFunc)(HANDLE, DWORD64, PDWORD, PIMAGEHLP_LINE64);
|
|
static SymGetLineFromAddrFunc MySymGetLineFromAddr = 0;
|
|
|
|
typedef BOOL (*StackWalkFunc)(
|
|
DWORD, HANDLE, HANDLE, LPSTACKFRAME64, PVOID,
|
|
PREAD_PROCESS_MEMORY_ROUTINE64, PFUNCTION_TABLE_ACCESS_ROUTINE64,
|
|
PGET_MODULE_BASE_ROUTINE64, PTRANSLATE_ADDRESS_ROUTINE64);
|
|
static StackWalkFunc MyStackWalk = 0;
|
|
|
|
typedef BOOL (*SymGetSymFromAddrFunc)(
|
|
HANDLE, DWORD64, PDWORD64, PIMAGEHLP_SYMBOL64);
|
|
static SymGetSymFromAddrFunc MySymGetSymFromAddr = 0;
|
|
|
|
typedef PVOID (*SymFunctionTableAccess64Func)(HANDLE, DWORD64);
|
|
static SymFunctionTableAccess64Func MySymFunctionTableAccess64 = 0;
|
|
|
|
typedef DWORD64 (*SymGetModuleBase64Func)(HANDLE, DWORD64);
|
|
static SymGetModuleBase64Func MySymGetModuleBase64 = 0;
|
|
|
|
|
|
namespace Burnslib
|
|
{
|
|
|
|
namespace StackTrace
|
|
{
|
|
// This must be called before any of the other functions in this
|
|
// namespace
|
|
|
|
void
|
|
Initialize();
|
|
|
|
bool
|
|
IsInitialized()
|
|
{
|
|
return imageHelpDll != 0;
|
|
}
|
|
}
|
|
|
|
} // namespace Burnslib
|
|
|
|
|
|
|
|
// Determines the path to the parent folder of the binary from whence this
|
|
// process was loaded.
|
|
//
|
|
// returns 0 on failure.
|
|
//
|
|
// Caller needs to free the result with delete[].
|
|
|
|
char*
|
|
GetModuleFolderPath()
|
|
{
|
|
HRESULT hr = S_OK;
|
|
char* result = 0;
|
|
|
|
do
|
|
{
|
|
result = new char[MAX_PATH + 1];
|
|
::ZeroMemory(result, MAX_PATH + 1);
|
|
|
|
char tempBuf[MAX_PATH + 1] = {0};
|
|
|
|
DWORD res = ::GetModuleFileNameA(0, tempBuf, MAX_PATH);
|
|
if (res != 0)
|
|
{
|
|
char driveBuf[_MAX_DRIVE] = {0};
|
|
char folderBuf[_MAX_DIR] = {0};
|
|
|
|
_splitpath(tempBuf, driveBuf, folderBuf, 0, 0);
|
|
|
|
char* end1 = 0;
|
|
hr = StringCchCatExA(result, MAX_PATH, driveBuf, &end1, 0, 0);
|
|
BREAK_ON_FAILED_HRESULT(hr);
|
|
|
|
RTLASSERT(end1 < result + MAX_PATH);
|
|
|
|
char* end2 = 0;
|
|
hr = StringCchCatExA(result, MAX_PATH, folderBuf, &end2, 0, 0);
|
|
BREAK_ON_FAILED_HRESULT(hr);
|
|
|
|
RTLASSERT(end2 < result + MAX_PATH);
|
|
|
|
if (end2 - end1 > 1 && *(end2 - 1) == '\\')
|
|
{
|
|
// the folder is not the root folder, which means it also has a
|
|
// trailing \ which we want to remove
|
|
|
|
*(end2 - 1) = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hr = HRESULT_FROM_WIN32(::GetLastError());
|
|
}
|
|
}
|
|
while (0);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
delete[] result;
|
|
result = 0;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
|
|
// Expands an environment variable. Returns 0 on error. Caller must free
|
|
// the result with delete[]
|
|
|
|
char*
|
|
ExpandEnvironmentVar(const char* var)
|
|
{
|
|
RTLASSERT(var);
|
|
RTLASSERT(*var);
|
|
|
|
// determine the length of the expanded string
|
|
|
|
DWORD len = ::ExpandEnvironmentStringsA(var, 0, 0);
|
|
RTLASSERT(len);
|
|
|
|
if (!len)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
char* result = new char[len + 1];
|
|
|
|
// REVIEWED-2002/03/14-sburns correct byte count passed
|
|
|
|
::ZeroMemory(result, len + 1);
|
|
|
|
DWORD len1 =
|
|
::ExpandEnvironmentStringsA(
|
|
var,
|
|
result,
|
|
|
|
// REVIEWED-2002/03/14-sburns correct character count passed
|
|
|
|
len);
|
|
RTLASSERT(len1 + 1 == len);
|
|
|
|
if (!len1)
|
|
{
|
|
delete[] result;
|
|
return 0;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
InitHelper()
|
|
{
|
|
// we want to look for symbols first in the folder the app started from,
|
|
// then on %_NT_SYMBOL_PATH%;%_NT_ALTERNATE_SYMBOL_PATH%;
|
|
|
|
// MAX_PATH * 3 for load + sym + alt sym, + 2 for semis, and +1 for null
|
|
|
|
static const size_t PATH_BUF_SIZE = MAX_PATH * 3 + 2 + 1;
|
|
char* symSearchPath = new char[PATH_BUF_SIZE];
|
|
|
|
// REVIEWED-2002/03/14-sburns correct byte count passed
|
|
|
|
::ZeroMemory(symSearchPath, PATH_BUF_SIZE);
|
|
|
|
HRESULT hr = S_OK;
|
|
do
|
|
{
|
|
char* moduleFolderPath = GetModuleFolderPath();
|
|
char* end = 0;
|
|
hr =
|
|
StringCchCatExA(
|
|
symSearchPath,
|
|
|
|
// -1 to make sure that there's space for a semi at the end
|
|
|
|
PATH_BUF_SIZE - 1,
|
|
moduleFolderPath,
|
|
&end,
|
|
0,
|
|
STRSAFE_IGNORE_NULLS);
|
|
|
|
delete[] moduleFolderPath;
|
|
|
|
BREAK_ON_FAILED_HRESULT(hr);
|
|
|
|
// Since we know there will be space for it, just poke in a semi
|
|
|
|
*end = ';';
|
|
|
|
char* env = ExpandEnvironmentVar("%_NT_SYMBOL_PATH%");
|
|
if (env)
|
|
{
|
|
end = 0;
|
|
hr =
|
|
StringCchCatExA(
|
|
symSearchPath,
|
|
|
|
// -1 to make sure that there's space for a semi at the end
|
|
|
|
PATH_BUF_SIZE - 1,
|
|
env,
|
|
&end,
|
|
0,
|
|
STRSAFE_IGNORE_NULLS);
|
|
|
|
delete[] env;
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// Since we know there will be space for it, just poke in a semi
|
|
|
|
*end = ';';
|
|
}
|
|
else
|
|
{
|
|
// even if this part of the path is absent, use the others
|
|
|
|
hr = S_OK;
|
|
}
|
|
}
|
|
|
|
env = ExpandEnvironmentVar("%_NT_ALTERNATE_SYMBOL_PATH%");
|
|
if (env)
|
|
{
|
|
end = 0;
|
|
|
|
// return code unchecked because even if this part of the path is
|
|
// absent, we will use the others
|
|
|
|
(void) StringCchCatExA(
|
|
symSearchPath,
|
|
PATH_BUF_SIZE,
|
|
env,
|
|
&end,
|
|
0,
|
|
STRSAFE_IGNORE_NULLS);
|
|
|
|
delete[] env;
|
|
}
|
|
}
|
|
while (0);
|
|
|
|
BOOL succeeded =
|
|
MySymInitialize(
|
|
::GetCurrentProcess(),
|
|
SUCCEEDED(hr) ? symSearchPath : 0,
|
|
TRUE);
|
|
|
|
RTLASSERT(succeeded);
|
|
|
|
delete[] symSearchPath;
|
|
}
|
|
|
|
|
|
|
|
void
|
|
Burnslib::StackTrace::Initialize()
|
|
{
|
|
RTLASSERT(!IsInitialized());
|
|
|
|
// load the dbghelp dll -- not the imagehlp dll. The latter is merely a
|
|
// delayload-enabled wrapper of dbghelp, and in low-resource situations
|
|
// loading imagehlp will succeed, but the its delayload of dbghelp will
|
|
// fail, leading to calls to stubs that do nothing.
|
|
// NTRAID#NTBUG9-572904-2002/03/12-sburns
|
|
|
|
imageHelpDll = static_cast<HMODULE>(::LoadLibrary(L"dbghelp.dll"));
|
|
if (!imageHelpDll)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// resolve the function pointers
|
|
|
|
MySymSetOptions =
|
|
reinterpret_cast<SymSetOptionsFunc>(
|
|
::GetProcAddress(imageHelpDll, "SymSetOptions"));
|
|
|
|
MySymInitialize =
|
|
reinterpret_cast<SymInitializeFunc>(
|
|
::GetProcAddress(imageHelpDll, "SymInitialize"));
|
|
|
|
MySymCleanup =
|
|
reinterpret_cast<SymCleanupFunc>(
|
|
::GetProcAddress(imageHelpDll, "SymCleanup"));
|
|
|
|
MySymGetModuleInfo =
|
|
reinterpret_cast<SymGetModuleInfoFunc>(
|
|
::GetProcAddress(imageHelpDll, "SymGetModuleInfo64"));
|
|
|
|
MySymGetLineFromAddr =
|
|
reinterpret_cast<SymGetLineFromAddrFunc>(
|
|
::GetProcAddress(imageHelpDll, "SymGetLineFromAddr64"));
|
|
|
|
MyStackWalk =
|
|
reinterpret_cast<StackWalkFunc>(
|
|
::GetProcAddress(imageHelpDll, "StackWalk64"));
|
|
|
|
MySymGetSymFromAddr =
|
|
reinterpret_cast<SymGetSymFromAddrFunc>(
|
|
::GetProcAddress(imageHelpDll, "SymGetSymFromAddr64"));
|
|
|
|
MySymFunctionTableAccess64 =
|
|
reinterpret_cast<SymFunctionTableAccess64Func>(
|
|
::GetProcAddress(imageHelpDll, "SymFunctionTableAccess64"));
|
|
|
|
MySymGetModuleBase64 =
|
|
reinterpret_cast<SymGetModuleBase64Func>(
|
|
::GetProcAddress(imageHelpDll, "SymGetModuleBase64"));
|
|
|
|
if (
|
|
!MySymSetOptions
|
|
|| !MySymInitialize
|
|
|| !MySymCleanup
|
|
|| !MySymGetModuleInfo
|
|
|| !MySymGetLineFromAddr
|
|
|| !MyStackWalk
|
|
|| !MySymGetSymFromAddr
|
|
|| !MySymFunctionTableAccess64
|
|
|| !MySymGetModuleBase64)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Init the stack trace facilities
|
|
|
|
//lint -e(534) we're not interested in the return value.
|
|
|
|
MySymSetOptions(
|
|
SYMOPT_DEFERRED_LOADS
|
|
| SYMOPT_UNDNAME
|
|
| SYMOPT_LOAD_LINES);
|
|
|
|
InitHelper();
|
|
}
|
|
|
|
|
|
|
|
void
|
|
Burnslib::StackTrace::Cleanup()
|
|
{
|
|
if (IsInitialized())
|
|
{
|
|
BOOL succeeded = MySymCleanup(::GetCurrentProcess());
|
|
|
|
RTLASSERT(succeeded);
|
|
|
|
::FreeLibrary(imageHelpDll);
|
|
imageHelpDll = 0;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// a SEH filter function that walks the stack, and stuffs the offset pointers
|
|
// into the provided array.
|
|
|
|
DWORD
|
|
GetStackTraceFilter(
|
|
DWORD64 stackTrace[],
|
|
size_t traceMax,
|
|
CONTEXT* context,
|
|
size_t levelsToSkip)
|
|
{
|
|
RTLASSERT(Burnslib::StackTrace::IsInitialized());
|
|
RTLASSERT(MyStackWalk);
|
|
RTLASSERT(context);
|
|
|
|
// REVIEWED: correct byte count passed
|
|
|
|
::ZeroMemory(stackTrace, traceMax * sizeof DWORD64);
|
|
|
|
if (!MyStackWalk)
|
|
{
|
|
// initialization failed in some way, so do nothing.
|
|
|
|
return EXCEPTION_EXECUTE_HANDLER;
|
|
}
|
|
|
|
STACKFRAME64 frame;
|
|
DWORD dwMachineType;
|
|
|
|
// REVIEWED: correct byte count passed
|
|
|
|
::ZeroMemory(&frame, sizeof frame);
|
|
|
|
#if defined(_M_IX86)
|
|
dwMachineType = IMAGE_FILE_MACHINE_I386;
|
|
frame.AddrPC.Offset = context->Eip;
|
|
frame.AddrPC.Mode = AddrModeFlat;
|
|
frame.AddrFrame.Offset = context->Ebp;
|
|
frame.AddrFrame.Mode = AddrModeFlat;
|
|
frame.AddrStack.Offset = context->Esp;
|
|
frame.AddrStack.Mode = AddrModeFlat;
|
|
|
|
#elif defined(_M_AMD64)
|
|
dwMachineType = IMAGE_FILE_MACHINE_AMD64;
|
|
frame.AddrPC.Offset = context->Rip;
|
|
frame.AddrPC.Mode = AddrModeFlat;
|
|
frame.AddrStack.Offset = context->Rsp;
|
|
frame.AddrStack.Mode = AddrModeFlat;
|
|
|
|
#elif defined(_M_IA64)
|
|
dwMachineType = IMAGE_FILE_MACHINE_IA64;
|
|
frame.AddrPC.Offset = context->StIIP;
|
|
frame.AddrPC.Mode = AddrModeFlat;
|
|
frame.AddrStack.Offset = context->IntSp;
|
|
frame.AddrStack.Mode = AddrModeFlat;
|
|
|
|
#else
|
|
#error( "unknown target machine" );
|
|
#endif
|
|
|
|
HANDLE process = ::GetCurrentProcess();
|
|
HANDLE thread = ::GetCurrentThread();
|
|
|
|
// On ia64, the context struct can be whacked by StackWalk64 (thanks to
|
|
// drewb for cluing me in to that subtle point). If the context record is a
|
|
// pointer to the one gathered from GetExceptionInformation, whacking it is
|
|
// a Very Bad Thing To Do. Stack corruption results. So in order to get
|
|
// successive calls to work, we have to copy the struct, and let the copy
|
|
// get whacked.
|
|
|
|
CONTEXT dupContext;
|
|
|
|
// REVIEWED-2002/03/06-sburns correct byte count passed.
|
|
|
|
::CopyMemory(&dupContext, context, sizeof dupContext);
|
|
|
|
for (size_t i = 0, top = 0; top < traceMax; ++i)
|
|
{
|
|
BOOL result =
|
|
MyStackWalk(
|
|
dwMachineType,
|
|
process,
|
|
thread,
|
|
&frame,
|
|
&dupContext,
|
|
0,
|
|
MySymFunctionTableAccess64,
|
|
MySymGetModuleBase64,
|
|
0);
|
|
if (!result)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// skip the n most recent frames
|
|
|
|
if (i >= levelsToSkip)
|
|
{
|
|
stackTrace[top++] = frame.AddrPC.Offset;
|
|
}
|
|
}
|
|
|
|
return EXCEPTION_EXECUTE_HANDLER;
|
|
}
|
|
|
|
|
|
|
|
DWORD
|
|
Burnslib::StackTrace::TraceFilter(
|
|
DWORD64 stackTrace[],
|
|
size_t traceMax,
|
|
CONTEXT* context)
|
|
{
|
|
RTLASSERT(stackTrace);
|
|
RTLASSERT(traceMax);
|
|
RTLASSERT(context);
|
|
|
|
if (!Burnslib::StackTrace::IsInitialized())
|
|
{
|
|
Burnslib::StackTrace::Initialize();
|
|
}
|
|
|
|
return
|
|
GetStackTraceFilter(stackTrace, traceMax, context, 0);
|
|
}
|
|
|
|
|
|
|
|
void
|
|
Burnslib::StackTrace::Trace(DWORD64 stackTrace[], size_t traceMax)
|
|
{
|
|
RTLASSERT(stackTrace);
|
|
RTLASSERT(traceMax);
|
|
|
|
if (!Burnslib::StackTrace::IsInitialized())
|
|
{
|
|
Burnslib::StackTrace::Initialize();
|
|
}
|
|
|
|
// the only way to get the context of a running thread is to raise an
|
|
// exception....
|
|
|
|
__try
|
|
{
|
|
RaiseException(0, 0, 0, 0);
|
|
}
|
|
__except (
|
|
GetStackTraceFilter(
|
|
stackTrace,
|
|
traceMax,
|
|
|
|
//lint --e(*) GetExceptionInformation is like a compiler intrinsic
|
|
|
|
(GetExceptionInformation())->ContextRecord,
|
|
|
|
// skip the 2 most recent function calls, as those correspond to
|
|
// this function itself.
|
|
|
|
2))
|
|
{
|
|
// do nothing in the handler
|
|
}
|
|
}
|
|
|
|
|
|
// ISSUE-2002/03/06-sburns consider replacing with strsafe function
|
|
// strncpy that will not overflow the buffer.
|
|
|
|
inline
|
|
void
|
|
SafeStrncpy(char* dest, const char* src, size_t bufmax)
|
|
{
|
|
::ZeroMemory(dest, bufmax);
|
|
strncpy(dest, src, bufmax - 1);
|
|
}
|
|
|
|
|
|
|
|
void
|
|
Burnslib::StackTrace::LookupAddress(
|
|
DWORD64 traceAddress,
|
|
char moduleName[],
|
|
char fullImageName[],
|
|
char symbolName[], // must be SYMBOL_NAME_MAX bytes
|
|
DWORD64* displacement,
|
|
DWORD* line,
|
|
char fullpath[]) // must be MAX_PATH bytes
|
|
{
|
|
if (!Burnslib::StackTrace::IsInitialized())
|
|
{
|
|
Burnslib::StackTrace::Initialize();
|
|
}
|
|
|
|
RTLASSERT(traceAddress);
|
|
|
|
HANDLE process = ::GetCurrentProcess();
|
|
|
|
if (moduleName || fullImageName)
|
|
{
|
|
IMAGEHLP_MODULE64 module;
|
|
|
|
// REVIEWED-2002/03/06-sburns correct byte count passed
|
|
|
|
::ZeroMemory(&module, sizeof module);
|
|
module.SizeOfStruct = sizeof(module);
|
|
if (MySymGetModuleInfo(process, traceAddress, &module))
|
|
{
|
|
if (moduleName)
|
|
{
|
|
SafeStrncpy(moduleName, module.ModuleName, MODULE_NAME_MAX);
|
|
}
|
|
if (fullImageName)
|
|
{
|
|
SafeStrncpy(
|
|
fullImageName,
|
|
module.LoadedImageName,
|
|
MAX_PATH);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (symbolName || displacement)
|
|
{
|
|
|
|
// CODEWORK: use SymFromAddr instead?
|
|
|
|
|
|
// +1 for paranoid terminating null
|
|
|
|
BYTE buf[SYMBOL_NAME_MAX + sizeof IMAGEHLP_SYMBOL64 + 1];
|
|
|
|
// REVIEWED-2002/03/06-sburns correct byte count passed
|
|
|
|
::ZeroMemory(buf, SYMBOL_NAME_MAX + sizeof IMAGEHLP_SYMBOL64 + 1);
|
|
|
|
IMAGEHLP_SYMBOL64* symbol = reinterpret_cast<IMAGEHLP_SYMBOL64*>(buf);
|
|
symbol->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64);
|
|
symbol->MaxNameLength = SYMBOL_NAME_MAX;
|
|
|
|
if (MySymGetSymFromAddr(process, traceAddress, displacement, symbol))
|
|
{
|
|
if (symbolName)
|
|
{
|
|
SafeStrncpy(symbolName, symbol->Name, SYMBOL_NAME_MAX);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (line || fullpath)
|
|
{
|
|
DWORD disp2 = 0;
|
|
IMAGEHLP_LINE64 lineinfo;
|
|
|
|
// REVIEWED-2002/03/06-sburns correct byte count passed
|
|
|
|
::ZeroMemory(&lineinfo, sizeof lineinfo);
|
|
|
|
lineinfo.SizeOfStruct = sizeof(lineinfo);
|
|
|
|
if (MySymGetLineFromAddr(process, traceAddress, &disp2, &lineinfo))
|
|
{
|
|
// disp2 ?= displacement
|
|
|
|
if (line)
|
|
{
|
|
*line = lineinfo.LineNumber;
|
|
}
|
|
if (fullpath)
|
|
{
|
|
SafeStrncpy(fullpath, lineinfo.FileName, MAX_PATH);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
String
|
|
Burnslib::StackTrace::LookupAddress(
|
|
DWORD64 traceAddress,
|
|
const wchar_t* format)
|
|
{
|
|
RTLASSERT(traceAddress);
|
|
RTLASSERT(format);
|
|
|
|
String result;
|
|
|
|
if (!format || !traceAddress)
|
|
{
|
|
return result;
|
|
}
|
|
|
|
char ansiSymbol[Burnslib::StackTrace::SYMBOL_NAME_MAX];
|
|
char ansiModule[Burnslib::StackTrace::MODULE_NAME_MAX];
|
|
char ansiSource[MAX_PATH];
|
|
DWORD64 displacement = 0;
|
|
DWORD line = 0;
|
|
|
|
// REVIEWED-2002/03/06-sburns correct byte counts passed
|
|
|
|
::ZeroMemory(ansiSymbol, Burnslib::StackTrace::SYMBOL_NAME_MAX);
|
|
::ZeroMemory(ansiModule, Burnslib::StackTrace::MODULE_NAME_MAX);
|
|
::ZeroMemory(ansiSource, MAX_PATH);
|
|
|
|
Burnslib::StackTrace::LookupAddress(
|
|
traceAddress,
|
|
ansiModule,
|
|
0,
|
|
ansiSymbol,
|
|
&displacement,
|
|
&line,
|
|
ansiSource);
|
|
|
|
String module(ansiModule);
|
|
String symbol(ansiSymbol);
|
|
String source(ansiSource);
|
|
|
|
result =
|
|
String::format(
|
|
format,
|
|
module.c_str(),
|
|
symbol.c_str(),
|
|
source.c_str(),
|
|
line);
|
|
|
|
return result;
|
|
}
|
|
|