Leaked source code of windows server 2003
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

// 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;
}