
D front-end changes: - Import dmd v2.103.1. - Deprecated invalid special token sequences inside token strings. D runtime changes: - Import druntime v2.103.1. Phobos changes: - Import phobos v2.103.1. gcc/d/ChangeLog: * dmd/MERGE: Merge upstream dmd a45f4e9f43. * dmd/VERSION: Bump version to v2.103.1. libphobos/ChangeLog: * libdruntime/MERGE: Merge upstream druntime a45f4e9f43. * src/MERGE: Merge upstream phobos 106038f2e.
434 lines
14 KiB
D
434 lines
14 KiB
D
/**
|
|
* ...
|
|
*
|
|
* 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 USHORT function(ULONG FramesToSkip, ULONG FramesToCapture, PVOID *BackTrace, PULONG BackTraceHash) @nogc RtlCaptureStackBackTraceFunc;
|
|
|
|
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)
|
|
static enum INTERNALFRAMES = 3;
|
|
else version (Win32)
|
|
static 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)
|
|
static enum INTERNALFRAMES = 1;
|
|
else version (Win32)
|
|
static 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)];
|
|
// Deal with dmd mangling of long names for OMF 32 bits builds
|
|
// Note that `target.d` only defines `CRuntime_DigitalMars` for OMF builds
|
|
version (CRuntime_DigitalMars)
|
|
{
|
|
size_t decodeIndex = 0;
|
|
tempSymName = decodeDmdString(tempSymName, decodeIndex);
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
|
|
|
|
// Workaround OPTLINK bug (Bugzilla 8263)
|
|
extern(Windows) BOOL FixupDebugHeader(HANDLE hProcess, ULONG ActionCode,
|
|
ulong CallbackContext, ulong UserContext)
|
|
{
|
|
if (ActionCode == CBA_READ_MEMORY)
|
|
{
|
|
auto p = cast(IMAGEHLP_CBA_READ_MEMORY*)CallbackContext;
|
|
if (!(p.addr & 0xFF) && p.bytes == 0x1C &&
|
|
// IMAGE_DEBUG_DIRECTORY.PointerToRawData
|
|
(*cast(DWORD*)(p.addr + 24) & 0xFF) == 0x20)
|
|
{
|
|
immutable base = DbgHelp.get().SymGetModuleBase64(hProcess, p.addr);
|
|
// IMAGE_DEBUG_DIRECTORY.AddressOfRawData
|
|
if (base + *cast(DWORD*)(p.addr + 20) == p.addr + 0x1C &&
|
|
*cast(DWORD*)(p.addr + 0x1C) == 0 &&
|
|
*cast(DWORD*)(p.addr + 0x20) == ('N'|'B'<<8|'0'<<16|'9'<<24))
|
|
{
|
|
debug(PRINTF) printf("fixup IMAGE_DEBUG_DIRECTORY.AddressOfRawData\n");
|
|
memcpy(p.buf, cast(void*)p.addr, 0x1C);
|
|
*cast(DWORD*)(p.buf + 20) = cast(DWORD)(p.addr - base) + 0x20;
|
|
*p.bytesread = 0x1C;
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
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;
|
|
|
|
dbghelp.SymRegisterCallback64(hProcess, &FixupDebugHeader, 0);
|
|
|
|
InitializeCriticalSection(&mutex);
|
|
initialized = true;
|
|
}
|