This repository has been archived on 2023-08-20. You can view files and clone it, but cannot push or open issues or pull requests.
yap-6.3/os/pl-cstack.c
Vitor Santos Costa 7cf4e9442c SWI portability
2013-01-16 12:28:37 +00:00

742 lines
17 KiB
C

/* $Id$
Part of SWI-Prolog
Author: Jan Wielemaker
E-mail: J.Wielemaker@uva.nl
WWW: http://www.swi-prolog.org
Copyright (C): 1985-2011, University of Amsterdam
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifdef _WIN64
#define _WIN32_WINNT 0x0501 /* get RtlCaptureContext() */
#endif
#include "pl-incl.h"
#include "os/pl-cstack.h"
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
The task of the library is to save the <N> most recent C stack traces
for later retrieval. I.e., although this library can be used to print
the stack in case of a crash, it is intended to _save_ the stack on a
critical event such as GC and retrieve it later if it turns out that an
error occurs.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
#define SAVE_TRACES 10
/*******************************
* LIBUNWIND *
*******************************/
#if !defined(BTRACE_DONE) && defined(HAVE_LIBUNWIND)
#define BTRACE_DONE 1
#define UNW_LOCAL_ONLY
#include <libunwind.h>
#define MAX_DEPTH 10
typedef struct
{ char name[32]; /* function called */
unw_word_t offset; /* offset in function */
} frame_info;
typedef struct
{ const char *name; /* label of the backtrace */
int depth; /* # frames collectec */
frame_info frame[MAX_DEPTH]; /* per-frame info */
} btrace_stack;
typedef struct btrace
{ btrace_stack dumps[SAVE_TRACES]; /* ring of buffers */
int current; /* next to fill */
} btrace;
void
btrace_destroy(struct btrace *bt)
{ free(bt);
}
static btrace *
get_trace_store(void)
{ GET_LD
if ( !LD->btrace_store )
{ btrace *s = malloc(sizeof(*s));
if ( s )
{ memset(s, 0, sizeof(*s));
LD->btrace_store = s;
}
}
return LD->btrace_store;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
next_btrace_id() produces the id for the next backtrace and sets
bt->current to the subsequent id. Although bt is thread-local, it may be
called from a signal handler or (Windows) exception. We cannot use
locking because the mutex functions are not async signal safe. So, we
use atomic instructions if possible. Otherwise, we ensure consistency of
the datastructures, but we may overwrite an older stack trace.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
static int
next_btrace_id(btrace *bt)
{ int current;
#ifdef COMPARE_AND_SWAP
int next;
do
{ current = bt->current;
next = current+1;
if ( next == SAVE_TRACES )
next = 0;
} while ( !COMPARE_AND_SWAP(&bt->current, current, next) );
#else
current = bt->current++ % SAVE_TRACES;
if ( bt->current >= SAVE_TRACES )
bt->current %= SAVE_TRACES;
#endif
return current;
}
void
save_backtrace(const char *why)
{ btrace *bt = get_trace_store();
if ( bt )
{ btrace_stack *s;
unw_cursor_t cursor; unw_context_t uc;
int depth;
int current = next_btrace_id(bt);
s = &bt->dumps[current];
unw_getcontext(&uc);
unw_init_local(&cursor, &uc);
for(depth=0; unw_step(&cursor) > 0 && depth < MAX_DEPTH; depth++)
{ unw_get_proc_name(&cursor,
s->frame[depth].name, sizeof(s->frame[depth].name),
&s->frame[depth].offset);
}
s->name = why;
s->depth = depth;
}
}
static void
print_trace(btrace *bt, int me)
{ btrace_stack *s = &bt->dumps[me];
if ( s && s->name )
{ int depth;
Sdprintf("Stack trace labeled \"%s\":\n", s->name);
for(depth=0; depth<s->depth; depth++)
{ Sdprintf(" [%d] %s+%p\n", depth,
s->frame[depth].name,
(void*)s->frame[depth].offset);
}
} else
{ Sdprintf("No stack trace\n");
}
}
void
print_backtrace(int last) /* 1..SAVE_TRACES */
{ btrace *bt = get_trace_store();
if ( bt )
{ int me = bt->current-last;
if ( me < 0 )
me += SAVE_TRACES;
print_trace(bt, me);
} else
{ Sdprintf("No backtrace store?\n");
}
}
void
print_backtrace_named(const char *why)
{ btrace *bt = get_trace_store();
if ( bt )
{ int me = bt->current-1;
for(;;)
{ if ( --me < 0 )
me += SAVE_TRACES;
if ( bt->dumps[me].name && strcmp(bt->dumps[me].name, why) == 0 )
{ print_trace(bt, me);
return;
}
if ( me == bt->current-1 )
break;
}
}
Sdprintf("No backtrace named %s\n", why);
}
#endif /*HAVE_LIBUNWIND*/
/*******************************
* GLIBC *
*******************************/
#if !defined(BTRACE_DONE) && defined(HAVE_EXECINFO_H) && !defined(DMALLOC)
#define BTRACE_DONE 1
#include <execinfo.h>
#include <string.h>
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
This implementation uses the libgcc unwinding capabilities.
Disabled of dmalloc is used because the free of the memory allocated by
backtrace_symbols() is considered an error by dmalloc.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
typedef struct btrace
{ char **symbols[SAVE_TRACES];
const char *why[SAVE_TRACES];
size_t sizes[SAVE_TRACES];
int current;
} btrace;
void
btrace_destroy(struct btrace *bt)
{ int i;
for(i=0; i<SAVE_TRACES; i++)
{ if ( bt->symbols[i] )
free(bt->symbols[i]);
}
free(bt);
}
static btrace *
get_trace_store(void)
{ GET_LD
if ( !LD->btrace_store )
{ btrace *s = malloc(sizeof(*s));
if ( s )
{ memset(s, 0, sizeof(*s));
LD->btrace_store = s;
}
}
return LD->btrace_store;
}
/* Copy of same function above. Relies on a different btrace structure.
Ideally, this should be shared :-(
*/
static int
next_btrace_id(btrace *bt)
{ int current;
#ifdef COMPARE_AND_SWAP
int next;
do
{ current = bt->current;
next = current+1;
if ( next == SAVE_TRACES )
next = 0;
} while ( !COMPARE_AND_SWAP(&bt->current, current, next) );
#else
current = bt->current++ % SAVE_TRACES;
if ( bt->current >= SAVE_TRACES )
bt->current %= SAVE_TRACES;
#endif
return current;
}
void
save_backtrace(const char *why)
{ btrace *bt = get_trace_store();
if ( bt )
{ void *array[100];
size_t frames;
int current = next_btrace_id(bt);
frames = backtrace(array, sizeof(array)/sizeof(void *));
bt->sizes[current] = frames;
if ( bt->symbols[current] )
free(bt->symbols[current]);
bt->symbols[current] = backtrace_symbols(array, frames);
bt->why[current] = why;
}
}
static void
print_trace(btrace *bt, int me)
{ size_t i;
if ( bt->why[me] )
{ Sdprintf("Stack trace labeled \"%s\":\n", bt->why[me]);
for(i=0; i<bt->sizes[me]; i++)
Sdprintf(" [%d] %s\n", i, bt->symbols[me][i]);
} else
{ Sdprintf("No stack trace\n");
}
}
void
print_backtrace(int last) /* 1..SAVE_TRACES */
{ btrace *bt = get_trace_store();
if ( bt )
{ int me = bt->current-last;
if ( me < 0 )
me += SAVE_TRACES;
print_trace(bt, me);
} else
{ Sdprintf("No backtrace store?\n");
}
}
void
print_backtrace_named(const char *why)
{ btrace *bt = get_trace_store();
if ( bt )
{ int me = bt->current-1;
for(;;)
{ if ( --me < 0 )
me += SAVE_TRACES;
if ( bt->why[me] && strcmp(bt->why[me], why) == 0 )
{ print_trace(bt, me);
return;
}
if ( me == bt->current-1 )
break;
}
}
Sdprintf("No backtrace named %s\n", why);
}
#endif /*HAVE_EXECINFO_H*/
/*******************************
* ADD AS HANDLER *
*******************************/
#ifdef BTRACE_DONE
static void
crashHandler(int sig)
{ Sdprintf("\nSWI-Prolog [thread %d]: received fatal signal %d (%s)\n",
PL_thread_self(), sig, signal_name(sig));
save_backtrace("crash");
print_backtrace_named("crash");
abort();
}
void
initBackTrace(void)
{
#ifdef SIGSEGV
PL_signal(SIGSEGV, crashHandler);
#endif
#ifdef SIGILL
PL_signal(SIGILL, crashHandler);
#endif
#ifdef SIGBUS
PL_signal(SIGBUS, crashHandler);
#endif
#ifdef SIGFPE
PL_signal(SIGFPE, crashHandler);
#endif
}
#endif
/*******************************
* WINDOWS IMPLEMENTATION *
*******************************/
#if !defined(BTRACE_DONE) && defined(__WINDOWS__) && defined(HAVE_DBGHELP_H)
#include <windows.h>
#include <dbghelp.h>
#define MAX_SYMBOL_LEN 1024
#define MAX_DEPTH 10
#define BTRACE_DONE 1
#define MAX_FUNCTION_NAME_LENGTH 32
/* Note that the module name may include the full path in some versions
of dbghelp. For me, 32 was not enough to see the module name in some
cases.
*/
#define MAX_MODULE_NAME_LENGTH 64
#define LOCK() PL_LOCK(L_CSTACK)
#define UNLOCK() PL_UNLOCK(L_CSTACK)
typedef struct
{ char name[MAX_FUNCTION_NAME_LENGTH]; /* function called */
DWORD64 offset; /* offset in function */
char module[MAX_MODULE_NAME_LENGTH]; /* module of function */
DWORD module_reason; /* reason for module being absent */
} frame_info;
typedef struct
{ const char *name; /* label of the backtrace */
int depth; /* # frames collectec */
frame_info frame[MAX_DEPTH]; /* per-frame info */
} btrace_stack;
typedef struct btrace
{ btrace_stack dumps[SAVE_TRACES]; /* ring of buffers */
int current; /* next to fill */
} btrace;
void
btrace_destroy(struct btrace *bt)
{ free(bt);
}
static btrace *
get_trace_store(void)
{ GET_LD
if ( !LD->btrace_store )
{ btrace *s = malloc(sizeof(*s));
if ( s )
{ memset(s, 0, sizeof(*s));
LD->btrace_store = s;
}
}
return LD->btrace_store;
}
/* Copy of same function above. Relies on a different btrace structure.
Ideally, this should be shared :-(
*/
static int
next_btrace_id(btrace *bt)
{ int current;
#ifdef COMPARE_AND_SWAP
int next;
do
{ current = bt->current;
next = current+1;
if ( next == SAVE_TRACES )
next = 0;
} while ( !COMPARE_AND_SWAP(&bt->current, current, next) );
#else
current = bt->current++ % SAVE_TRACES;
if ( bt->current >= SAVE_TRACES )
bt->current %= SAVE_TRACES;
#endif
return current;
}
int backtrace(btrace_stack* trace, PEXCEPTION_POINTERS pExceptionInfo)
{ STACKFRAME64 frame;
CONTEXT context;
int rc = 0;
HANDLE hThread = GetCurrentThread();
HANDLE hProcess = GetCurrentProcess();
char symbolScratch[sizeof(SYMBOL_INFO) + MAX_SYMBOL_LEN];
SYMBOL_INFO* symbol = (SYMBOL_INFO*)&symbolScratch;
IMAGEHLP_MODULE64 moduleInfo;
DWORD64 offset;
DWORD imageType;
int skip = 0;
int depth = 0;
if (pExceptionInfo == NULL)
{ memset(&context, 0, sizeof(CONTEXT));
// If we dont have the context, then we can get the current one from the CPU
// However, we should skip the first N frames, since these relate to the
// exception handler itself
// Obviously N is a magic number - it might differ if this code is modified!
#if _WIN32_WINNT > 0x0500
// Good, just use RtlCaptureContext
skip = 2;
RtlCaptureContext(&context);
#else
// For earlier than WinXPsp1 we have to do some weird stuff
// For win32, we can use inline assembly to get eip, esp and ebp but
// the MSVC2005 compiler refuses to emit inline assembly for AMD64
// Luckily, the oldest AMD64 build of Windows is XP, so we should be able to
// use RtlCaptureContext!
#ifdef WIN64
#error You appear to have a 64 bit build of a pre-XP version of Windows?!
#else
skip = 2;
__asm
{ call steal_eip
steal_eip:
pop eax
mov context.Eip, eax
mov eax, ebp
mov context.Ebp, eax
mov eax, esp
mov context.Esp, eax
}
#endif
#endif
} else
{ context = *(pExceptionInfo->ContextRecord);
}
ZeroMemory(&frame, sizeof( STACKFRAME64));
memset(&moduleInfo,0,sizeof(IMAGEHLP_MODULE64));
moduleInfo.SizeOfStruct = sizeof(IMAGEHLP_MODULE64);
rc = SymInitialize(hProcess, NULL, TRUE);
if (rc == 0)
return 0;
#ifdef _WIN64
imageType = IMAGE_FILE_MACHINE_AMD64;
frame.AddrPC.Offset = context.Rip;
frame.AddrFrame.Offset = context.Rsp;
frame.AddrStack.Offset = context.Rsp;
#else
imageType = IMAGE_FILE_MACHINE_I386;
frame.AddrPC.Offset = context.Eip;
frame.AddrFrame.Offset = context.Ebp;
frame.AddrStack.Offset = context.Esp;
#endif
frame.AddrPC.Mode = AddrModeFlat;
frame.AddrFrame.Mode = AddrModeFlat;
frame.AddrStack.Mode = AddrModeFlat;
while(depth < MAX_DEPTH &&
(rc = StackWalk64(imageType,
hProcess,
hThread,
&frame,
&context,
NULL,
SymFunctionTableAccess64,
SymGetModuleBase64,
NULL)) != 0)
{ int hasModule = 0;
BOOL hasSymbol = FALSE;
if (skip > 0)
{ skip--;
continue;
}
memset(symbol, 0, sizeof(SYMBOL_INFO) + MAX_SYMBOL_LEN);
symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
symbol->MaxNameLen = MAX_SYMBOL_LEN;
trace->frame[depth].offset = frame.AddrPC.Offset;
hasModule = SymGetModuleInfo64(hProcess, frame.AddrPC.Offset, &moduleInfo);
if (hasModule == 0)
{
// Note that this CAN be caused by a very out of date dbghelp.dll,
// like the one that ships with Windows XP
// Dropping version 6.x into the bin directory can magically
// make this work. At least we will have the offset
trace->frame[depth].name[0] = '\0';
trace->frame[depth].module[0] = '\0';
trace->frame[depth].module_reason = GetLastError();
} else
{ hasSymbol = SymFromAddr(hProcess, frame.AddrPC.Offset, &offset, symbol);
strncpy(trace->frame[depth].module,
moduleInfo.ImageName,
MAX_MODULE_NAME_LENGTH);
trace->frame[depth].module[MAX_MODULE_NAME_LENGTH-1] = '\0';
trace->frame[depth].module_reason = 0;
if (hasSymbol)
{ strncpy(trace->frame[depth].name,
symbol->Name,
MAX_FUNCTION_NAME_LENGTH);
trace->frame[depth].name[MAX_FUNCTION_NAME_LENGTH-1] = '\0';
} else
{ trace->frame[depth].name[0] = '\0';
}
}
depth++;
}
return depth;
}
void
win_save_backtrace(const char *why, PEXCEPTION_POINTERS pExceptionInfo)
{ btrace *bt = get_trace_store();
if ( bt )
{ int current = next_btrace_id(bt);
btrace_stack *s = &bt->dumps[current];
LOCK();
s->depth = backtrace(s, pExceptionInfo);
UNLOCK();
s->name = why;
}
}
void save_backtrace(const char *why)
{ win_save_backtrace(why, NULL);
}
static void
print_trace(btrace *bt, int me)
{ btrace_stack *s = &bt->dumps[me];
if ( s->name )
{ int depth;
Sdprintf("Stack trace labeled \"%s\":\n", s->name);
for(depth=0; depth<s->depth; depth++)
{ Sdprintf(" [%d] <%s>:%s+%p\n", depth,
(s->frame[depth].module[0] == 0) ? "unknown module"
: s->frame[depth].module,
s->frame[depth].name,
(void*)s->frame[depth].offset);
}
} else
{ Sdprintf("No stack trace\n");
}
}
void
print_backtrace(int last) /* 1..SAVE_TRACES */
{ btrace *bt = get_trace_store();
if ( bt )
{ int me = bt->current-last;
if ( me < 0 )
me += SAVE_TRACES;
print_trace(bt, me);
} else
{ Sdprintf("No backtrace store?\n");
}
}
void
print_backtrace_named(const char *why)
{ btrace *bt = get_trace_store();
if ( bt )
{ int me = bt->current-1;
for(;;)
{ if ( --me < 0 )
me += SAVE_TRACES;
if ( bt->dumps[me].name && strcmp(bt->dumps[me].name, why) == 0 )
{ print_trace(bt, me);
return;
}
if ( me == bt->current-1 )
break;
}
}
Sdprintf("No backtrace named %s\n", why);
}
static LONG WINAPI crashHandler(PEXCEPTION_POINTERS pExceptionInfo)
{ win_save_backtrace("crash", pExceptionInfo);
print_backtrace_named("crash");
abort();
return EXCEPTION_CONTINUE_SEARCH; /* ? */
}
void
initBackTrace(void)
{ SetUnhandledExceptionFilter(crashHandler);
}
#endif /*__WINDOWS__*/
/*******************************
* FALLBACK IMPLEMENTATION *
*******************************/
#ifndef BTRACE_DONE
void
save_backtrace(const char *why)
{
}
void
btrace_destroy(struct btrace *bt)
{
}
void
print_backtrace(int last)
{ Sdprintf("%s:%d C-stack dumps are not supported on this platform\n",
__FILE__, __LINE__);
}
void
print_backtrace_named(const char *why)
{ Sdprintf("%s:%d C-stack dumps are not supported on this platform\n",
__FILE__, __LINE__);
}
void
initBackTrace(void)
{
}
#endif /*BTRACE_DONE*/