/** * ... * * Copyright: Copyright Benjamin Thaut 2010 - 2013. * License: Distributed under the * $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). * (See accompanying file LICENSE) * Authors: Benjamin Thaut, Sean Kelly * Source: $(DRUNTIMESRC core/sys/windows/_stacktrace.d) */ module core.sys.windows.stacktrace;version (Windows): import core.demangle;import core.stdc.stdlib;import core.stdc.string;import core.sys.windows.dbghelp;import core.sys.windows.imagehlp /+: ADDRESS_MODE+/;import core.sys.windows.winbase;import core.sys.windows.windef; //debug=PRINTF;debug(PRINTF) import core.stdc.stdio; extern(Windows) void RtlCaptureContext(CONTEXT* ContextRecord) @nogc;extern(Windows) DWORD GetEnvironmentVariableA(LPCSTR lpName, LPSTR pBuffer, DWORD nSize); extern(Windows) alias RtlCaptureStackBackTraceFunc = USHORT function(ULONG FramesToSkip, ULONG FramesToCapture, PVOID *BackTrace, PULONG BackTraceHash) @nogc; private __gshared RtlCaptureStackBackTraceFunc RtlCaptureStackBackTrace;private __gshared CRITICAL_SECTION mutex; // cannot use core.sync.mutex.Mutex unfortunately (cyclic dependency...)private __gshared immutable bool initialized; class StackTrace : Throwable.TraceInfo{public: /** * Constructor * Params: * skip = The number of stack frames to skip. * context = The context to receive the stack trace from. Can be null. */ this(size_t skip, CONTEXT* context) @nogc { if (context is null) { version (Win64) enum INTERNALFRAMES = 3; else version (Win32) enum INTERNALFRAMES = 2; skip += INTERNALFRAMES; //skip the stack frames within the StackTrace class } else { //When a exception context is given the first stack frame is repeated for some reason version (Win64) enum INTERNALFRAMES = 1; else version (Win32) enum INTERNALFRAMES = 1; skip += INTERNALFRAMES; } if (initialized) m_trace = trace(tracebuf[], skip, context); } override int opApply( scope int delegate(ref const(char[])) dg ) const { return opApply( (ref size_t, ref const(char[]) buf) { return dg( buf ); }); } override int opApply( scope int delegate(ref size_t, ref const(char[])) dg ) const { int result; foreach ( i, e; resolve(m_trace) ) { if ( (result = dg( i, e )) != 0 ) break; } return result; } @trusted override string toString() const { string result; foreach ( e; this ) { result ~= e ~ "\n"; } return result; } /** * Receive a stack trace in the form of an address list. One form accepts * an allocated buffer, the other form automatically allocates the buffer. * * Params: * skip = How many stack frames should be skipped. * context = The context that should be used. If null the current context is used. * buffer = The buffer to use for the trace. This should be at least 63 elements. * Returns: * A list of addresses that can be passed to resolve at a later point in time. */ static ulong[] trace(size_t skip = 0, CONTEXT* context = null) { return trace(new ulong[63], skip, context); } /// ditto static ulong[] trace(ulong[] buffer, size_t skip = 0, CONTEXT* context = null) @nogc { EnterCriticalSection(&mutex); scope(exit) LeaveCriticalSection(&mutex); return traceNoSync(buffer, skip, context); } /** * Resolve a stack trace. * Params: * addresses = A list of addresses to resolve. * Returns: * An array of strings with the results. */ @trusted static char[][] resolve(const(ulong)[] addresses) { // FIXME: make @nogc to avoid having to disable resolution within finalizers import core.memory : GC; if (GC.inFinalizer) return null; EnterCriticalSection(&mutex); scope(exit) LeaveCriticalSection(&mutex); return resolveNoSync(addresses); } private: ulong[128] tracebuf; ulong[] m_trace; static ulong[] traceNoSync(ulong[] buffer, size_t skip, CONTEXT* context) @nogc { auto dbghelp = DbgHelp.get(); if (dbghelp is null) return []; // dbghelp.dll not available if (buffer.length >= 63 && RtlCaptureStackBackTrace !is null && context is null) { version (Win64) { auto bufptr = cast(void**)buffer.ptr; } version (Win32) { size_t[63] bufstorage = void; // On windows xp the sum of "frames to skip" and "frames to capture" can't be greater then 63 auto bufptr = cast(void**)bufstorage.ptr; } auto backtraceLength = RtlCaptureStackBackTrace(cast(ULONG)skip, cast(ULONG)(63 - skip), bufptr, null); // If we get a backtrace and it does not have the maximum length use it. // Otherwise rely on tracing through StackWalk64 which is slower but works when no frame pointers are available. if (backtraceLength > 1 && backtraceLength < 63 - skip) { debug(PRINTF) printf("Using result from RtlCaptureStackBackTrace\n"); version (Win32) { foreach (i, ref e; buffer[0 .. backtraceLength]) { e = bufstorage[i]; } } return buffer[0..backtraceLength]; } } HANDLE hThread = GetCurrentThread(); HANDLE hProcess = GetCurrentProcess(); CONTEXT ctxt; if (context is null) { ctxt.ContextFlags = CONTEXT_FULL; RtlCaptureContext(&ctxt); } else { ctxt = *context; } //x86 STACKFRAME64 stackframe; with (stackframe) { version (X86) { enum Flat = ADDRESS_MODE.AddrModeFlat; AddrPC.Offset = ctxt.Eip; AddrPC.Mode = Flat; AddrFrame.Offset = ctxt.Ebp; AddrFrame.Mode = Flat; AddrStack.Offset = ctxt.Esp; AddrStack.Mode = Flat; } else version (X86_64) { enum Flat = ADDRESS_MODE.AddrModeFlat; AddrPC.Offset = ctxt.Rip; AddrPC.Mode = Flat; AddrFrame.Offset = ctxt.Rbp; AddrFrame.Mode = Flat; AddrStack.Offset = ctxt.Rsp; AddrStack.Mode = Flat; } } version (X86) enum imageType = IMAGE_FILE_MACHINE_I386; else version (X86_64) enum imageType = IMAGE_FILE_MACHINE_AMD64; else static assert(0, "unimplemented"); size_t frameNum = 0; size_t nframes = 0; // do ... while so that we don't skip the first stackframe do { if (frameNum >= skip) { buffer[nframes++] = stackframe.AddrPC.Offset; if (nframes >= buffer.length) break; } frameNum++; } while (dbghelp.StackWalk64(imageType, hProcess, hThread, &stackframe, &ctxt, null, null, null, null)); return buffer[0 .. nframes]; } static char[][] resolveNoSync(const(ulong)[] addresses) { auto dbghelp = DbgHelp.get(); if (dbghelp is null) return []; // dbghelp.dll not available HANDLE hProcess = GetCurrentProcess(); static struct BufSymbol { align(1): IMAGEHLP_SYMBOLA64 _base; TCHAR[1024] _buf = void; } BufSymbol bufSymbol=void; IMAGEHLP_SYMBOLA64* symbol = &bufSymbol._base; symbol.SizeOfStruct = IMAGEHLP_SYMBOLA64.sizeof; symbol.MaxNameLength = bufSymbol._buf.length; char[][] trace; foreach (pc; addresses) { char[] res; if (dbghelp.SymGetSymFromAddr64(hProcess, pc, null, symbol) && *symbol.Name.ptr) { DWORD disp; IMAGEHLP_LINEA64 line=void; line.SizeOfStruct = IMAGEHLP_LINEA64.sizeof; if (dbghelp.SymGetLineFromAddr64(hProcess, pc, &disp, &line)) res = formatStackFrame(cast(void*)pc, symbol.Name.ptr, line.FileName, line.LineNumber); else res = formatStackFrame(cast(void*)pc, symbol.Name.ptr); } else res = formatStackFrame(cast(void*)pc); trace ~= res; } return trace; } static char[] formatStackFrame(void* pc) { import core.stdc.stdio : snprintf; char[2+2*size_t.sizeof+1] buf=void; immutable len = snprintf(buf.ptr, buf.length, "0x%p", pc); cast(uint)len < buf.length || assert(0); return buf[0 .. len].dup; } static char[] formatStackFrame(void* pc, char* symName) { char[2048] demangleBuf=void; auto res = formatStackFrame(pc); res ~= " in "; const(char)[] tempSymName = symName[0 .. strlen(symName)]; res ~= demangle(tempSymName, demangleBuf); return res; } static char[] formatStackFrame(void* pc, char* symName, const scope char* fileName, uint lineNum) { import core.stdc.stdio : snprintf; char[11] buf=void; auto res = formatStackFrame(pc, symName); res ~= " at "; res ~= fileName[0 .. strlen(fileName)]; res ~= "("; immutable len = snprintf(buf.ptr, buf.length, "%u", lineNum); cast(uint)len < buf.length || assert(0); res ~= buf[0 .. len]; res ~= ")"; return res; }} private string generateSearchPath(){ __gshared string[3] defaultPathList = ["_NT_SYMBOL_PATH", "_NT_ALTERNATE_SYMBOL_PATH", "SYSTEMROOT"]; string path; char[2048] temp = void; DWORD len; foreach ( e; defaultPathList ) { if ( (len = GetEnvironmentVariableA( e.ptr, temp.ptr, temp.length )) > 0 ) { path ~= temp[0 .. len]; path ~= ";"; } } path ~= "\0"; return path;} shared static this(){ auto dbghelp = DbgHelp.get(); if ( dbghelp is null ) return; // dbghelp.dll not available auto kernel32Handle = LoadLibraryA( "kernel32.dll" ); if (kernel32Handle !is null) { RtlCaptureStackBackTrace = cast(RtlCaptureStackBackTraceFunc) GetProcAddress(kernel32Handle, "RtlCaptureStackBackTrace"); debug(PRINTF) { if (RtlCaptureStackBackTrace !is null) printf("Found RtlCaptureStackBackTrace\n"); } } debug(PRINTF) { API_VERSION* dbghelpVersion = dbghelp.ImagehlpApiVersion(); printf("DbgHelp Version %d.%d.%d\n", dbghelpVersion.MajorVersion, dbghelpVersion.MinorVersion, dbghelpVersion.Revision); } HANDLE hProcess = GetCurrentProcess(); DWORD symOptions = dbghelp.SymGetOptions(); symOptions |= SYMOPT_LOAD_LINES; symOptions |= SYMOPT_FAIL_CRITICAL_ERRORS; symOptions |= SYMOPT_DEFERRED_LOAD; symOptions = dbghelp.SymSetOptions( symOptions ); debug(PRINTF) printf("Search paths: %s\n", generateSearchPath().ptr); if (!dbghelp.SymInitialize(hProcess, generateSearchPath().ptr, TRUE)) return; InitializeCriticalSection(&mutex); initialized = true;}
/**
* ...
*
* Copyright: Copyright Benjamin Thaut 2010 - 2013.
* License: Distributed under the
* $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0).
* (See accompanying file LICENSE)
* Authors: Benjamin Thaut, Sean Kelly
* Source: $(DRUNTIMESRC core/sys/windows/_stacktrace.d)
*/
module core.sys.windows.stacktrace;
version (Windows):
import core.demangle;
import core.stdc.stdlib;
import core.stdc.string;
import core.sys.windows.dbghelp;
import core.sys.windows.imagehlp /+: ADDRESS_MODE+/;
import core.sys.windows.winbase;
import core.sys.windows.windef;
//debug=PRINTF;
debug(PRINTF) import core.stdc.stdio;
extern(Windows) void RtlCaptureContext(CONTEXT* ContextRecord) @nogc;
extern(Windows) DWORD GetEnvironmentVariableA(LPCSTR lpName, LPSTR pBuffer, DWORD nSize);
extern(Windows) alias RtlCaptureStackBackTraceFunc = USHORT function(ULONG FramesToSkip, ULONG FramesToCapture, PVOID *BackTrace, PULONG BackTraceHash) @nogc;
private __gshared RtlCaptureStackBackTraceFunc RtlCaptureStackBackTrace;
private __gshared CRITICAL_SECTION mutex; // cannot use core.sync.mutex.Mutex unfortunately (cyclic dependency...)
private __gshared immutable bool initialized;
class StackTrace : Throwable.TraceInfo
{
public:
/**
* Constructor
* Params:
* skip = The number of stack frames to skip.
* context = The context to receive the stack trace from. Can be null.
*/
this(size_t skip, CONTEXT* context) @nogc
{
if (context is null)
{
version (Win64)
enum INTERNALFRAMES = 3;
else version (Win32)
enum INTERNALFRAMES = 2;
skip += INTERNALFRAMES; //skip the stack frames within the StackTrace class
}
else
{
//When a exception context is given the first stack frame is repeated for some reason
version (Win64)
enum INTERNALFRAMES = 1;
else version (Win32)
enum INTERNALFRAMES = 1;
skip += INTERNALFRAMES;
}
if (initialized)
m_trace = trace(tracebuf[], skip, context);
}
override int opApply( scope int delegate(ref const(char[])) dg ) const
{
return opApply( (ref size_t, ref const(char[]) buf)
{
return dg( buf );
});
}
override int opApply( scope int delegate(ref size_t, ref const(char[])) dg ) const
{
int result;
foreach ( i, e; resolve(m_trace) )
{
if ( (result = dg( i, e )) != 0 )
break;
}
return result;
}
@trusted override string toString() const
{
string result;
foreach ( e; this )
{
result ~= e ~ "\n";
}
return result;
}
/**
* Receive a stack trace in the form of an address list. One form accepts
* an allocated buffer, the other form automatically allocates the buffer.
*
* Params:
* skip = How many stack frames should be skipped.
* context = The context that should be used. If null the current context is used.
* buffer = The buffer to use for the trace. This should be at least 63 elements.
* Returns:
* A list of addresses that can be passed to resolve at a later point in time.
*/
static ulong[] trace(size_t skip = 0, CONTEXT* context = null)
{
return trace(new ulong[63], skip, context);
}
/// ditto
static ulong[] trace(ulong[] buffer, size_t skip = 0, CONTEXT* context = null) @nogc
{
EnterCriticalSection(&mutex);
scope(exit) LeaveCriticalSection(&mutex);
return traceNoSync(buffer, skip, context);
}
/**
* Resolve a stack trace.
* Params:
* addresses = A list of addresses to resolve.
* Returns:
* An array of strings with the results.
*/
@trusted static char[][] resolve(const(ulong)[] addresses)
{
// FIXME: make @nogc to avoid having to disable resolution within finalizers
import core.memory : GC;
if (GC.inFinalizer)
return null;
EnterCriticalSection(&mutex);
scope(exit) LeaveCriticalSection(&mutex);
return resolveNoSync(addresses);
}
private:
ulong[128] tracebuf;
ulong[] m_trace;
static ulong[] traceNoSync(ulong[] buffer, size_t skip, CONTEXT* context) @nogc
{
auto dbghelp = DbgHelp.get();
if (dbghelp is null)
return []; // dbghelp.dll not available
if (buffer.length >= 63 && RtlCaptureStackBackTrace !is null &&
context is null)
{
version (Win64)
{
auto bufptr = cast(void**)buffer.ptr;
}
version (Win32)
{
size_t[63] bufstorage = void; // On windows xp the sum of "frames to skip" and "frames to capture" can't be greater then 63
auto bufptr = cast(void**)bufstorage.ptr;
}
auto backtraceLength = RtlCaptureStackBackTrace(cast(ULONG)skip, cast(ULONG)(63 - skip), bufptr, null);
// If we get a backtrace and it does not have the maximum length use it.
// Otherwise rely on tracing through StackWalk64 which is slower but works when no frame pointers are available.
if (backtraceLength > 1 && backtraceLength < 63 - skip)
{
debug(PRINTF) printf("Using result from RtlCaptureStackBackTrace\n");
version (Win32)
{
foreach (i, ref e; buffer[0 .. backtraceLength])
{
e = bufstorage[i];
}
}
return buffer[0..backtraceLength];
}
}
HANDLE hThread = GetCurrentThread();
HANDLE hProcess = GetCurrentProcess();
CONTEXT ctxt;
if (context is null)
{
ctxt.ContextFlags = CONTEXT_FULL;
RtlCaptureContext(&ctxt);
}
else
{
ctxt = *context;
}
//x86
STACKFRAME64 stackframe;
with (stackframe)
{
version (X86)
{
enum Flat = ADDRESS_MODE.AddrModeFlat;
AddrPC.Offset = ctxt.Eip;
AddrPC.Mode = Flat;
AddrFrame.Offset = ctxt.Ebp;
AddrFrame.Mode = Flat;
AddrStack.Offset = ctxt.Esp;
AddrStack.Mode = Flat;
}
else version (X86_64)
{
enum Flat = ADDRESS_MODE.AddrModeFlat;
AddrPC.Offset = ctxt.Rip;
AddrPC.Mode = Flat;
AddrFrame.Offset = ctxt.Rbp;
AddrFrame.Mode = Flat;
AddrStack.Offset = ctxt.Rsp;
AddrStack.Mode = Flat;
}
}
version (X86) enum imageType = IMAGE_FILE_MACHINE_I386;
else version (X86_64) enum imageType = IMAGE_FILE_MACHINE_AMD64;
else static assert(0, "unimplemented");
size_t frameNum = 0;
size_t nframes = 0;
// do ... while so that we don't skip the first stackframe
do
{
if (frameNum >= skip)
{
buffer[nframes++] = stackframe.AddrPC.Offset;
if (nframes >= buffer.length)
break;
}
frameNum++;
}
while (dbghelp.StackWalk64(imageType, hProcess, hThread, &stackframe,
&ctxt, null, null, null, null));
return buffer[0 .. nframes];
}
static char[][] resolveNoSync(const(ulong)[] addresses)
{
auto dbghelp = DbgHelp.get();
if (dbghelp is null)
return []; // dbghelp.dll not available
HANDLE hProcess = GetCurrentProcess();
static struct BufSymbol
{
align(1):
IMAGEHLP_SYMBOLA64 _base;
TCHAR[1024] _buf = void;
}
BufSymbol bufSymbol=void;
IMAGEHLP_SYMBOLA64* symbol = &bufSymbol._base;
symbol.SizeOfStruct = IMAGEHLP_SYMBOLA64.sizeof;
symbol.MaxNameLength = bufSymbol._buf.length;
char[][] trace;
foreach (pc; addresses)
{
char[] res;
if (dbghelp.SymGetSymFromAddr64(hProcess, pc, null, symbol) &&
*symbol.Name.ptr)
{
DWORD disp;
IMAGEHLP_LINEA64 line=void;
line.SizeOfStruct = IMAGEHLP_LINEA64.sizeof;
if (dbghelp.SymGetLineFromAddr64(hProcess, pc, &disp, &line))
res = formatStackFrame(cast(void*)pc, symbol.Name.ptr,
line.FileName, line.LineNumber);
else
res = formatStackFrame(cast(void*)pc, symbol.Name.ptr);
}
else
res = formatStackFrame(cast(void*)pc);
trace ~= res;
}
return trace;
}
static char[] formatStackFrame(void* pc)
{
import core.stdc.stdio : snprintf;
char[2+2*size_t.sizeof+1] buf=void;
immutable len = snprintf(buf.ptr, buf.length, "0x%p", pc);
cast(uint)len < buf.length || assert(0);
return buf[0 .. len].dup;
}
static char[] formatStackFrame(void* pc, char* symName)
{
char[2048] demangleBuf=void;
auto res = formatStackFrame(pc);
res ~= " in ";
const(char)[] tempSymName = symName[0 .. strlen(symName)];
res ~= demangle(tempSymName, demangleBuf);
return res;
}
static char[] formatStackFrame(void* pc, char* symName,
const scope char* fileName, uint lineNum)
{
import core.stdc.stdio : snprintf;
char[11] buf=void;
auto res = formatStackFrame(pc, symName);
res ~= " at ";
res ~= fileName[0 .. strlen(fileName)];
res ~= "(";
immutable len = snprintf(buf.ptr, buf.length, "%u", lineNum);
cast(uint)len < buf.length || assert(0);
res ~= buf[0 .. len];
res ~= ")";
return res;
}
}
private string generateSearchPath()
{
__gshared string[3] defaultPathList = ["_NT_SYMBOL_PATH",
"_NT_ALTERNATE_SYMBOL_PATH",
"SYSTEMROOT"];
string path;
char[2048] temp = void;
DWORD len;
foreach ( e; defaultPathList )
{
if ( (len = GetEnvironmentVariableA( e.ptr, temp.ptr, temp.length )) > 0 )
{
path ~= temp[0 .. len];
path ~= ";";
}
}
path ~= "\0";
return path;
}
shared static this()
{
auto dbghelp = DbgHelp.get();
if ( dbghelp is null )
return; // dbghelp.dll not available
auto kernel32Handle = LoadLibraryA( "kernel32.dll" );
if (kernel32Handle !is null)
{
RtlCaptureStackBackTrace = cast(RtlCaptureStackBackTraceFunc) GetProcAddress(kernel32Handle, "RtlCaptureStackBackTrace");
debug(PRINTF)
{
if (RtlCaptureStackBackTrace !is null)
printf("Found RtlCaptureStackBackTrace\n");
}
}
debug(PRINTF)
{
API_VERSION* dbghelpVersion = dbghelp.ImagehlpApiVersion();
printf("DbgHelp Version %d.%d.%d\n", dbghelpVersion.MajorVersion, dbghelpVersion.MinorVersion, dbghelpVersion.Revision);
}
HANDLE hProcess = GetCurrentProcess();
DWORD symOptions = dbghelp.SymGetOptions();
symOptions |= SYMOPT_LOAD_LINES;
symOptions |= SYMOPT_FAIL_CRITICAL_ERRORS;
symOptions |= SYMOPT_DEFERRED_LOAD;
symOptions = dbghelp.SymSetOptions( symOptions );
debug(PRINTF) printf("Search paths: %s\n", generateSearchPath().ptr);
if (!dbghelp.SymInitialize(hProcess, generateSearchPath().ptr, TRUE))
return;
InitializeCriticalSection(&mutex);
initialized = true;
}