Author: Armin Rigo <ar...@tunes.org> Branch: py3k Changeset: r87497:0863ca6a99cc Date: 2016-10-01 12:20 +0200 http://bitbucket.org/pypy/pypy/changeset/0863ca6a99cc/
Log: hg merge py3k-faulthandler Merge the py3k version of faulthandler. diff --git a/pypy/config/pypyoption.py b/pypy/config/pypyoption.py --- a/pypy/config/pypyoption.py +++ b/pypy/config/pypyoption.py @@ -40,13 +40,14 @@ "binascii", "_multiprocessing", '_warnings', "_collections", "_multibytecodec", "_continuation", "_cffi_backend", "_csv", "_pypyjson", "_posixsubprocess", # "cppyy", "micronumpy" - "faulthandler", "_jitlog", + "_jitlog", ]) from rpython.jit.backend import detect_cpu try: if detect_cpu.autodetect().startswith('x86'): working_modules.add('_vmprof') + working_modules.add('faulthandler') except detect_cpu.ProcessorAutodetectError: pass @@ -93,6 +94,7 @@ ('objspace.usemodules.thread', True)], 'cpyext': [('objspace.usemodules.array', True)], 'cppyy': [('objspace.usemodules.cpyext', True)], + 'faulthandler': [('objspace.usemodules._vmprof', True)], } module_suggests = { # the reason you want _rawffi is for ctypes, which @@ -118,7 +120,8 @@ "_hashlib" : ["pypy.module._ssl.interp_ssl"], "_minimal_curses": ["pypy.module._minimal_curses.fficurses"], "_continuation": ["rpython.rlib.rstacklet"], - "_vmprof" : ["pypy.module._vmprof.interp_vmprof"], + "_vmprof" : ["pypy.module._vmprof.interp_vmprof"], + "faulthandler" : ["pypy.module._vmprof.interp_vmprof"], "_lzma" : ["pypy.module._lzma.interp_lzma"], } diff --git a/pypy/interpreter/app_main.py b/pypy/interpreter/app_main.py --- a/pypy/interpreter/app_main.py +++ b/pypy/interpreter/app_main.py @@ -31,6 +31,7 @@ arg ...: arguments passed to program in sys.argv[1:] PyPy options and arguments: --info : print translation information about this PyPy executable +-X faulthandler: attempt to display tracebacks when PyPy crashes """ # Missing vs CPython: PYTHONHOME USAGE2 = """ @@ -518,6 +519,14 @@ sys._xoptions = dict(x.split('=', 1) if '=' in x else (x, True) for x in options['_xoptions']) + if 'faulthandler' in sys.builtin_module_names: + if 'faulthandler' in sys._xoptions or os.getenv('PYTHONFAULTHANDLER'): + import faulthandler + try: + faulthandler.enable(2) # manually set to stderr + except ValueError: + pass # ignore "2 is not a valid file descriptor" + ## if not we_are_translated(): ## for key in sorted(options): ## print '%40s: %s' % (key, options[key]) diff --git a/pypy/module/faulthandler/__init__.py b/pypy/module/faulthandler/__init__.py --- a/pypy/module/faulthandler/__init__.py +++ b/pypy/module/faulthandler/__init__.py @@ -1,22 +1,38 @@ +import sys from pypy.interpreter.mixedmodule import MixedModule + class Module(MixedModule): appleveldefs = { } interpleveldefs = { - 'enable': 'interp_faulthandler.enable', - 'disable': 'interp_faulthandler.disable', - 'is_enabled': 'interp_faulthandler.is_enabled', - 'register': 'interp_faulthandler.register', + 'enable': 'handler.enable', + 'disable': 'handler.disable', + 'is_enabled': 'handler.is_enabled', +# 'register': 'interp_faulthandler.register', +# + 'dump_traceback': 'handler.dump_traceback', +# + '_read_null': 'handler.read_null', + '_sigsegv': 'handler.sigsegv', + '_sigfpe': 'handler.sigfpe', + '_sigabrt': 'handler.sigabrt', + '_stack_overflow': 'handler.stack_overflow', + } - 'dump_traceback': 'interp_faulthandler.dump_traceback', + def setup_after_space_initialization(self): + """NOT_RPYTHON""" + if self.space.config.translation.thread: + self.extra_interpdef('dump_traceback_later', + 'handler.dump_traceback_later') + self.extra_interpdef('cancel_dump_traceback_later', + 'handler.cancel_dump_traceback_later') + if sys.platform != 'win32': + self.extra_interpdef('register', 'handler.register') + self.extra_interpdef('unregister', 'handler.unregister') - '_read_null': 'interp_faulthandler.read_null', - '_sigsegv': 'interp_faulthandler.sigsegv', - '_sigfpe': 'interp_faulthandler.sigfpe', - '_sigabrt': 'interp_faulthandler.sigabrt', - #'_sigbus': 'interp_faulthandler.sigbus', - #'_sigill': 'interp_faulthandler.sigill', - '_fatal_error': 'interp_faulthandler.fatal_error', - } + def shutdown(self, space): + from pypy.module.faulthandler import handler + handler.finish(space) + MixedModule.shutdown(self, space) diff --git a/pypy/module/faulthandler/cintf.py b/pypy/module/faulthandler/cintf.py new file mode 100644 --- /dev/null +++ b/pypy/module/faulthandler/cintf.py @@ -0,0 +1,99 @@ +import py +from rpython.rtyper.lltypesystem import lltype, llmemory, rffi, rstr +from rpython.translator import cdir +from rpython.translator.tool.cbuild import ExternalCompilationInfo + + +cwd = py.path.local(__file__).dirpath() +eci = ExternalCompilationInfo( + includes=[cwd.join('faulthandler.h')], + include_dirs=[str(cwd), cdir], + separate_module_files=[cwd.join('faulthandler.c')]) + +eci_later = eci.merge(ExternalCompilationInfo( + pre_include_bits=['#define PYPY_FAULTHANDLER_LATER\n'])) +eci_user = eci.merge(ExternalCompilationInfo( + pre_include_bits=['#define PYPY_FAULTHANDLER_USER\n'])) + +def direct_llexternal(*args, **kwargs): + kwargs.setdefault('_nowrapper', True) + kwargs.setdefault('compilation_info', eci) + return rffi.llexternal(*args, **kwargs) + + +DUMP_CALLBACK = lltype.Ptr(lltype.FuncType( + [rffi.INT, rffi.SIGNEDP, lltype.Signed], lltype.Void)) + +pypy_faulthandler_setup = direct_llexternal( + 'pypy_faulthandler_setup', [DUMP_CALLBACK], rffi.CCHARP) + +pypy_faulthandler_teardown = direct_llexternal( + 'pypy_faulthandler_teardown', [], lltype.Void) + +pypy_faulthandler_enable = direct_llexternal( + 'pypy_faulthandler_enable', [rffi.INT, rffi.INT], rffi.CCHARP) + +pypy_faulthandler_disable = direct_llexternal( + 'pypy_faulthandler_disable', [], lltype.Void) + +pypy_faulthandler_is_enabled = direct_llexternal( + 'pypy_faulthandler_is_enabled', [], rffi.INT) + +pypy_faulthandler_write = direct_llexternal( + 'pypy_faulthandler_write', [rffi.INT, rffi.CCHARP], lltype.Void) + +pypy_faulthandler_write_int = direct_llexternal( + 'pypy_faulthandler_write_int', [rffi.INT, lltype.Signed], lltype.Void) + +pypy_faulthandler_dump_traceback = direct_llexternal( + 'pypy_faulthandler_dump_traceback', + [rffi.INT, rffi.INT, llmemory.Address], lltype.Void) + +pypy_faulthandler_dump_traceback_later = direct_llexternal( + 'pypy_faulthandler_dump_traceback_later', + [rffi.LONGLONG, rffi.INT, rffi.INT, rffi.INT], rffi.CCHARP, + compilation_info=eci_later) + +pypy_faulthandler_cancel_dump_traceback_later = direct_llexternal( + 'pypy_faulthandler_cancel_dump_traceback_later', [], lltype.Void) + +pypy_faulthandler_check_signum = direct_llexternal( + 'pypy_faulthandler_check_signum', + [rffi.LONG], rffi.INT, + compilation_info=eci_user) + +pypy_faulthandler_register = direct_llexternal( + 'pypy_faulthandler_register', + [rffi.INT, rffi.INT, rffi.INT, rffi.INT], rffi.CCHARP, + compilation_info=eci_user) + +pypy_faulthandler_unregister = direct_llexternal( + 'pypy_faulthandler_unregister', + [rffi.INT], rffi.INT, + compilation_info=eci_user) + + +# for tests... + +pypy_faulthandler_read_null = direct_llexternal( + 'pypy_faulthandler_read_null', [], lltype.Void) + +pypy_faulthandler_read_null_releasegil = direct_llexternal( + 'pypy_faulthandler_read_null', [], lltype.Void, + _nowrapper=False, releasegil=True) + +pypy_faulthandler_sigsegv = direct_llexternal( + 'pypy_faulthandler_sigsegv', [], lltype.Void) + +pypy_faulthandler_sigsegv_releasegil = direct_llexternal( + 'pypy_faulthandler_sigsegv', [], lltype.Void, + _nowrapper=False, releasegil=True) + +pypy_faulthandler_sigfpe = direct_llexternal( + 'pypy_faulthandler_sigfpe', [], lltype.Void) + +pypy_faulthandler_sigabrt = direct_llexternal( + 'pypy_faulthandler_sigabrt', [], lltype.Void) + +pypy_faulthandler_stackoverflow = direct_llexternal( + 'pypy_faulthandler_stackoverflow', [lltype.Float], lltype.Float) diff --git a/pypy/module/faulthandler/dumper.py b/pypy/module/faulthandler/dumper.py new file mode 100644 --- /dev/null +++ b/pypy/module/faulthandler/dumper.py @@ -0,0 +1,54 @@ +from rpython.rtyper.lltypesystem import lltype, rffi +from rpython.rlib import rgc +from rpython.rlib.rvmprof import traceback + +from pypy.interpreter.pycode import PyCode +from pypy.module.faulthandler.cintf import pypy_faulthandler_write +from pypy.module.faulthandler.cintf import pypy_faulthandler_write_int + + +MAX_STRING_LENGTH = 500 + +global_buf = lltype.malloc(rffi.CCHARP.TO, MAX_STRING_LENGTH, flavor='raw', + immortal=True, zero=True) + +def _dump(fd, s): + assert isinstance(s, str) + l = len(s) + if l >= MAX_STRING_LENGTH: + l = MAX_STRING_LENGTH - 1 + i = 0 + while i < l: + global_buf[i] = s[i] + i += 1 + global_buf[l] = '\x00' + pypy_faulthandler_write(fd, global_buf) + +def _dump_int(fd, i): + pypy_faulthandler_write_int(fd, i) + + +def dump_code(pycode, loc, fd): + if pycode is None: + _dump(fd, " File ???") + else: + _dump(fd, ' File "') + _dump(fd, pycode.co_filename) + _dump(fd, '" in ') + _dump(fd, pycode.co_name) + _dump(fd, ", from line ") + _dump_int(fd, pycode.co_firstlineno) + if loc == traceback.LOC_JITTED: + _dump(fd, " [jitted]") + elif loc == traceback.LOC_JITTED_INLINED: + _dump(fd, " [jit inlined]") + _dump(fd, "\n") + + +@rgc.no_collect +def _dump_callback(fd, array_p, array_length): + """We are as careful as we can reasonably be here (i.e. not 100%, + but hopefully close enough). In particular, this is written as + RPython but shouldn't allocate anything. + """ + traceback.walk_traceback(PyCode, dump_code, fd, array_p, array_length) diff --git a/pypy/module/faulthandler/faulthandler.c b/pypy/module/faulthandler/faulthandler.c --- a/pypy/module/faulthandler/faulthandler.c +++ b/pypy/module/faulthandler/faulthandler.c @@ -1,20 +1,625 @@ +#include "faulthandler.h" #include <stdlib.h> -#include "faulthandler.h" +#include <stdio.h> +#include <signal.h> +#include <assert.h> +#include <errno.h> +#include <string.h> +#include <unistd.h> +#include <sys/resource.h> +#include <math.h> -int -pypy_faulthandler_read_null(void) +#ifdef RPYTHON_LL2CTYPES +# include "../../../rpython/rlib/rvmprof/src/rvmprof.h" +#else +# include "common_header.h" +# include "structdef.h" +# include "rvmprof.h" +#endif +#include "src/threadlocal.h" + +#define MAX_FRAME_DEPTH 100 +#define FRAME_DEPTH_N RVMPROF_TRACEBACK_ESTIMATE_N(MAX_FRAME_DEPTH) + + +typedef struct sigaction _Py_sighandler_t; + +typedef struct { + const int signum; + volatile int enabled; + const char* name; + _Py_sighandler_t previous; +} fault_handler_t; + +static struct { + int initialized; + int enabled; + volatile int fd, all_threads; + volatile pypy_faulthandler_cb_t dump_traceback; +} fatal_error; + +static stack_t stack; + + +static fault_handler_t faulthandler_handlers[] = { +#ifdef SIGBUS + {SIGBUS, 0, "Bus error", }, +#endif +#ifdef SIGILL + {SIGILL, 0, "Illegal instruction", }, +#endif + {SIGFPE, 0, "Floating point exception", }, + {SIGABRT, 0, "Aborted", }, + /* define SIGSEGV at the end to make it the default choice if searching the + handler fails in faulthandler_fatal_error() */ + {SIGSEGV, 0, "Segmentation fault", } +}; +static const int faulthandler_nsignals = + sizeof(faulthandler_handlers) / sizeof(fault_handler_t); + +RPY_EXTERN +void pypy_faulthandler_write(int fd, const char *str) { - volatile int *x; - volatile int y; - - x = NULL; - y = *x; - return y; + (void)write(fd, str, strlen(str)); } -void -pypy_faulthandler_sigsegv(void) +RPY_EXTERN +void pypy_faulthandler_write_int(int fd, long value) { + char buf[48]; + sprintf(buf, "%ld", value); + pypy_faulthandler_write(fd, buf); +} + + +RPY_EXTERN +void pypy_faulthandler_dump_traceback(int fd, int all_threads, + void *ucontext) +{ + pypy_faulthandler_cb_t fn; + intptr_t array_p[FRAME_DEPTH_N], array_length; + + fn = fatal_error.dump_traceback; + if (!fn) + return; + +#ifndef RPYTHON_LL2CTYPES + if (all_threads && _RPython_ThreadLocals_AcquireTimeout(10000) == 0) { + /* This is known not to be perfectly safe against segfaults if we + don't hold the GIL ourselves. Too bad. I suspect that CPython + has issues there too. + */ + struct pypy_threadlocal_s *my, *p; + int blankline = 0; + char buf[40]; + + my = (struct pypy_threadlocal_s *)_RPy_ThreadLocals_Get(); + p = _RPython_ThreadLocals_Head(); + p = _RPython_ThreadLocals_Enum(p); + while (p != NULL) { + if (blankline) + pypy_faulthandler_write(fd, "\n"); + blankline = 1; + + pypy_faulthandler_write(fd, my == p ? "Current thread" : "Thread"); + sprintf(buf, " 0x%lx", (unsigned long)p->thread_ident); + pypy_faulthandler_write(fd, buf); + pypy_faulthandler_write(fd, " (most recent call first):\n"); + + array_length = vmprof_get_traceback(p->vmprof_tl_stack, + my == p ? ucontext : NULL, + array_p, FRAME_DEPTH_N); + fn(fd, array_p, array_length); + + p = _RPython_ThreadLocals_Enum(p); + } + _RPython_ThreadLocals_Release(); + } + else { + pypy_faulthandler_write(fd, "Stack (most recent call first):\n"); + array_length = vmprof_get_traceback(NULL, ucontext, + array_p, FRAME_DEPTH_N); + fn(fd, array_p, array_length); + } +#else + pypy_faulthandler_write(fd, "(no traceback when untranslated)\n"); +#endif +} + +static void +faulthandler_dump_traceback(int fd, int all_threads, void *ucontext) +{ + static volatile int reentrant = 0; + + if (reentrant) + return; + reentrant = 1; + pypy_faulthandler_dump_traceback(fd, all_threads, ucontext); + reentrant = 0; +} + + +/************************************************************/ + + +#ifdef PYPY_FAULTHANDLER_LATER +#include "src/thread.h" +static struct { + int fd; + long long microseconds; + int repeat, exit; + /* The main thread always holds this lock. It is only released when + faulthandler_thread() is interrupted before this thread exits, or at + Python exit. */ + struct RPyOpaque_ThreadLock cancel_event; + /* released by child thread when joined */ + struct RPyOpaque_ThreadLock running; +} thread_later; + +static void faulthandler_thread(void) +{ +#ifndef _WIN32 + /* we don't want to receive any signal */ + sigset_t set; + sigfillset(&set); + pthread_sigmask(SIG_SETMASK, &set, NULL); +#endif + + RPyLockStatus st; + char buf[64]; + unsigned long hour, minutes, seconds, fraction; + long long t; + + do { + st = RPyThreadAcquireLockTimed(&thread_later.cancel_event, + thread_later.microseconds, 0); + if (st == RPY_LOCK_ACQUIRED) { + RPyThreadReleaseLock(&thread_later.cancel_event); + break; + } + /* Timeout => dump traceback */ + assert(st == RPY_LOCK_FAILURE); + + /* getting to know which thread holds the GIL is not as simple + * as in CPython, so for now we don't */ + + t = thread_later.microseconds; + fraction = (unsigned long)(t % 1000000); + t /= 1000000; + seconds = (unsigned long)(t % 60); + t /= 60; + minutes = (unsigned long)(t % 60); + t /= 60; + hour = (unsigned long)t; + if (fraction == 0) + sprintf(buf, "Timeout (%lu:%02lu:%02lu)!\n", + hour, minutes, seconds); + else + sprintf(buf, "Timeout (%lu:%02lu:%02lu.%06lu)!\n", + hour, minutes, seconds, fraction); + + pypy_faulthandler_write(thread_later.fd, buf); + pypy_faulthandler_dump_traceback(thread_later.fd, 1, NULL); + + if (thread_later.exit) + _exit(1); + } while (thread_later.repeat); + + /* The only way out */ + RPyThreadReleaseLock(&thread_later.running); +} + +RPY_EXTERN +char *pypy_faulthandler_dump_traceback_later(long long microseconds, int repeat, + int fd, int exit) +{ + pypy_faulthandler_cancel_dump_traceback_later(); + + thread_later.fd = fd; + thread_later.microseconds = microseconds; + thread_later.repeat = repeat; + thread_later.exit = exit; + + RPyThreadAcquireLock(&thread_later.running, 1); + + if (RPyThreadStart(&faulthandler_thread) == -1) { + RPyThreadReleaseLock(&thread_later.running); + return "unable to start watchdog thread"; + } + return NULL; +} +#endif /* PYPY_FAULTHANDLER_LATER */ + +RPY_EXTERN +void pypy_faulthandler_cancel_dump_traceback_later(void) +{ +#ifdef PYPY_FAULTHANDLER_LATER + /* Notify cancellation */ + RPyThreadReleaseLock(&thread_later.cancel_event); + + /* Wait for thread to join (or does nothing if no thread is running) */ + RPyThreadAcquireLock(&thread_later.running, 1); + RPyThreadReleaseLock(&thread_later.running); + + /* The main thread should always hold the cancel_event lock */ + RPyThreadAcquireLock(&thread_later.cancel_event, 1); +#endif /* PYPY_FAULTHANDLER_LATER */ +} + + +/************************************************************/ + + +#ifdef PYPY_FAULTHANDLER_USER +typedef struct { + int enabled; + int fd; + int all_threads; + int chain; + _Py_sighandler_t previous; +} user_signal_t; + +static user_signal_t *user_signals; + +#ifndef NSIG +# if defined(_NSIG) +# define NSIG _NSIG /* For BSD/SysV */ +# elif defined(_SIGMAX) +# define NSIG (_SIGMAX + 1) /* For QNX */ +# elif defined(SIGMAX) +# define NSIG (SIGMAX + 1) /* For djgpp */ +# else +# define NSIG 64 /* Use a reasonable default value */ +# endif +#endif + +static void faulthandler_user(int signum, siginfo_t *info, void *ucontext); + +static int +faulthandler_register(int signum, int chain, _Py_sighandler_t *p_previous) +{ + struct sigaction action; + action.sa_handler = faulthandler_user; + sigemptyset(&action.sa_mask); + /* if the signal is received while the kernel is executing a system + call, try to restart the system call instead of interrupting it and + return EINTR. */ + action.sa_flags = SA_RESTART | SA_SIGINFO; + if (chain) { + /* do not prevent the signal from being received from within its + own signal handler */ + action.sa_flags = SA_NODEFER; + } + if (stack.ss_sp != NULL) { + /* Call the signal handler on an alternate signal stack + provided by sigaltstack() */ + action.sa_flags |= SA_ONSTACK; + } + return sigaction(signum, &action, p_previous); +} + +static void faulthandler_user(int signum, siginfo_t *info, void *ucontext) +{ + int save_errno; + user_signal_t *user = &user_signals[signum]; + + if (!user->enabled) + return; + + save_errno = errno; + faulthandler_dump_traceback(user->fd, user->all_threads, ucontext); + + if (user->chain) { + (void)sigaction(signum, &user->previous, NULL); + errno = save_errno; + + /* call the previous signal handler */ + raise(signum); + + save_errno = errno; + (void)faulthandler_register(signum, user->chain, NULL); + } + + errno = save_errno; +} + +RPY_EXTERN +int pypy_faulthandler_check_signum(long signum) +{ + unsigned int i; + + for (i = 0; i < faulthandler_nsignals; i++) { + if (faulthandler_handlers[i].signum == signum) { + return -1; + } + } + if (signum < 1 || NSIG <= signum) { + return -2; + } + return 0; +} + +RPY_EXTERN +char *pypy_faulthandler_register(int signum, int fd, int all_threads, int chain) +{ + user_signal_t *user; + _Py_sighandler_t previous; + int err; + + if (user_signals == NULL) { + user_signals = malloc(NSIG * sizeof(user_signal_t)); + if (user_signals == NULL) + return "out of memory"; + memset(user_signals, 0, NSIG * sizeof(user_signal_t)); + } + + user = &user_signals[signum]; + user->fd = fd; + user->all_threads = all_threads; + user->chain = chain; + + if (!user->enabled) { + err = faulthandler_register(signum, chain, &previous); + if (err) + return strerror(errno); + + user->previous = previous; + user->enabled = 1; + } + return NULL; +} + +RPY_EXTERN +int pypy_faulthandler_unregister(int signum) +{ + user_signal_t *user; + + if (user_signals == NULL) + return 0; + + user = &user_signals[signum]; + if (user->enabled) { + user->enabled = 0; + (void)sigaction(signum, &user->previous, NULL); + user->fd = -1; + return 1; + } + else + return 0; +} +#endif /* PYPY_FAULTHANDLER_USER */ + + +/************************************************************/ + + +/* Handler for SIGSEGV, SIGFPE, SIGABRT, SIGBUS and SIGILL signals. + + Display the current Python traceback, restore the previous handler and call + the previous handler. + + On Windows, don't explicitly call the previous handler, because the Windows + signal handler would not be called (for an unknown reason). The execution of + the program continues at faulthandler_fatal_error() exit, but the same + instruction will raise the same fault (signal), and so the previous handler + will be called. + + This function is signal-safe and should only call signal-safe functions. */ + +static void +faulthandler_fatal_error(int signum, siginfo_t *info, void *ucontext) +{ + int fd = fatal_error.fd; + int i; + fault_handler_t *handler = NULL; + int save_errno = errno; + + for (i = 0; i < faulthandler_nsignals; i++) { + handler = &faulthandler_handlers[i]; + if (handler->signum == signum) + break; + } + /* If not found, we use the SIGSEGV handler (the last one in the list) */ + + /* restore the previous handler */ + if (handler->enabled) { + (void)sigaction(signum, &handler->previous, NULL); + handler->enabled = 0; + } + + pypy_faulthandler_write(fd, "Fatal Python error: "); + pypy_faulthandler_write(fd, handler->name); + pypy_faulthandler_write(fd, "\n\n"); + + faulthandler_dump_traceback(fd, fatal_error.all_threads, ucontext); + + errno = save_errno; +#ifdef MS_WINDOWS + if (signum == SIGSEGV) { + /* don't explicitly call the previous handler for SIGSEGV in this signal + handler, because the Windows signal handler would not be called */ + return; + } +#endif + /* call the previous signal handler: it is called immediately if we use + sigaction() thanks to SA_NODEFER flag, otherwise it is deferred */ + raise(signum); +} + + +RPY_EXTERN +char *pypy_faulthandler_setup(pypy_faulthandler_cb_t dump_callback) +{ + if (fatal_error.initialized) + return NULL; + assert(!fatal_error.enabled); + fatal_error.dump_traceback = dump_callback; + + /* Try to allocate an alternate stack for faulthandler() signal handler to + * be able to allocate memory on the stack, even on a stack overflow. If it + * fails, ignore the error. */ + stack.ss_flags = 0; + stack.ss_size = SIGSTKSZ; + stack.ss_sp = malloc(stack.ss_size); + if (stack.ss_sp != NULL) { + int err = sigaltstack(&stack, NULL); + if (err) { + free(stack.ss_sp); + stack.ss_sp = NULL; + } + } + +#ifdef PYPY_FAULTHANDLER_LATER + if (!RPyThreadLockInit(&thread_later.cancel_event) || + !RPyThreadLockInit(&thread_later.running)) + return "failed to initialize locks"; + RPyThreadAcquireLock(&thread_later.cancel_event, 1); +#endif + + fatal_error.fd = -1; + fatal_error.initialized = 1; + + return NULL; +} + +RPY_EXTERN +void pypy_faulthandler_teardown(void) +{ + if (fatal_error.initialized) { + +#ifdef PYPY_FAULTHANDLER_LATER + pypy_faulthandler_cancel_dump_traceback_later(); + RPyThreadReleaseLock(&thread_later.cancel_event); + RPyOpaqueDealloc_ThreadLock(&thread_later.running); + RPyOpaqueDealloc_ThreadLock(&thread_later.cancel_event); +#endif + +#ifdef PYPY_FAULTHANDLER_USER + int signum; + for (signum = 0; signum < NSIG; signum++) + pypy_faulthandler_unregister(signum); + /* don't free 'user_signals', the gain is very minor and it can + lead to rare crashes if another thread is still busy */ +#endif + + pypy_faulthandler_disable(); + fatal_error.initialized = 0; + if (stack.ss_sp) { + stack.ss_flags = SS_DISABLE; + sigaltstack(&stack, NULL); + free(stack.ss_sp); + stack.ss_sp = NULL; + } + } +} + +RPY_EXTERN +char *pypy_faulthandler_enable(int fd, int all_threads) +{ + /* Install the handler for fatal signals, faulthandler_fatal_error(). */ + int i; + fatal_error.fd = fd; + fatal_error.all_threads = all_threads; + + if (!fatal_error.enabled) { + fatal_error.enabled = 1; + + for (i = 0; i < faulthandler_nsignals; i++) { + int err; + struct sigaction action; + fault_handler_t *handler = &faulthandler_handlers[i]; + + action.sa_sigaction = faulthandler_fatal_error; + sigemptyset(&action.sa_mask); + /* Do not prevent the signal from being received from within + its own signal handler */ + action.sa_flags = SA_NODEFER | SA_SIGINFO; + if (stack.ss_sp != NULL) { + /* Call the signal handler on an alternate signal stack + provided by sigaltstack() */ + action.sa_flags |= SA_ONSTACK; + } + err = sigaction(handler->signum, &action, &handler->previous); + if (err) { + return strerror(errno); + } + handler->enabled = 1; + } + } + return NULL; +} + +RPY_EXTERN +void pypy_faulthandler_disable(void) +{ + int i; + if (fatal_error.enabled) { + fatal_error.enabled = 0; + for (i = 0; i < faulthandler_nsignals; i++) { + fault_handler_t *handler = &faulthandler_handlers[i]; + if (!handler->enabled) + continue; + (void)sigaction(handler->signum, &handler->previous, NULL); + handler->enabled = 0; + } + } + fatal_error.fd = -1; +} + +RPY_EXTERN +int pypy_faulthandler_is_enabled(void) +{ + return fatal_error.enabled; +} + + +/************************************************************/ + + +/* for tests... */ + +static void +faulthandler_suppress_crash_report(void) +{ +#ifdef MS_WINDOWS + UINT mode; + + /* Configure Windows to not display the Windows Error Reporting dialog */ + mode = SetErrorMode(SEM_NOGPFAULTERRORBOX); + SetErrorMode(mode | SEM_NOGPFAULTERRORBOX); +#endif + +#ifndef MS_WINDOWS + struct rlimit rl; + + /* Disable creation of core dump */ + if (getrlimit(RLIMIT_CORE, &rl) != 0) { + rl.rlim_cur = 0; + setrlimit(RLIMIT_CORE, &rl); + } +#endif + +#ifdef _MSC_VER + /* Visual Studio: configure abort() to not display an error message nor + open a popup asking to report the fault. */ + _set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT); +#endif +} + +RPY_EXTERN +int pypy_faulthandler_read_null(void) +{ + int *volatile x; + + faulthandler_suppress_crash_report(); + x = NULL; + return *x; +} + +RPY_EXTERN +void pypy_faulthandler_sigsegv(void) +{ + faulthandler_suppress_crash_report(); #if defined(MS_WINDOWS) /* For SIGSEGV, faulthandler_fatal_error() restores the previous signal handler and then gives back the execution flow to the program (without @@ -34,12 +639,13 @@ #endif } -int -pypy_faulthandler_sigfpe(void) +RPY_EXTERN +int pypy_faulthandler_sigfpe(void) { /* Do an integer division by zero: raise a SIGFPE on Intel CPU, but not on PowerPC. Use volatile to disable compile-time optimizations. */ volatile int x = 1, y = 0, z; + faulthandler_suppress_crash_report(); z = x / y; /* If the division by zero didn't raise a SIGFPE (e.g. on PowerPC), raise it manually. */ @@ -49,29 +655,25 @@ return z; } -void -pypy_faulthandler_sigabrt() +RPY_EXTERN +void pypy_faulthandler_sigabrt(void) { -#ifdef _MSC_VER - /* Visual Studio: configure abort() to not display an error message nor - open a popup asking to report the fault. */ - _set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT); -#endif + faulthandler_suppress_crash_report(); abort(); } -#ifdef SIGBUS -void -pypy_faulthandler_sigbus(void) +static double fh_stack_overflow(double levels) { - raise(SIGBUS); + if (levels > 2.5) { + return (sqrt(fh_stack_overflow(levels - 1.0)) + + fh_stack_overflow(levels * 1e-10)); + } + return 1e100 + levels; } -#endif -#ifdef SIGILL -void -pypy_faulthandler_sigill(void) +RPY_EXTERN +double pypy_faulthandler_stackoverflow(double levels) { - raise(SIGILL); + faulthandler_suppress_crash_report(); + return fh_stack_overflow(levels); } -#endif diff --git a/pypy/module/faulthandler/faulthandler.h b/pypy/module/faulthandler/faulthandler.h --- a/pypy/module/faulthandler/faulthandler.h +++ b/pypy/module/faulthandler/faulthandler.h @@ -1,19 +1,40 @@ #ifndef PYPY_FAULTHANDLER_H #define PYPY_FAULTHANDLER_H -#include <signal.h> #include "src/precommondefs.h" +#include <stdint.h> + + +typedef void (*pypy_faulthandler_cb_t)(int fd, intptr_t *array_p, + intptr_t length); + +RPY_EXTERN char *pypy_faulthandler_setup(pypy_faulthandler_cb_t dump_callback); +RPY_EXTERN void pypy_faulthandler_teardown(void); + +RPY_EXTERN char *pypy_faulthandler_enable(int fd, int all_threads); +RPY_EXTERN void pypy_faulthandler_disable(void); +RPY_EXTERN int pypy_faulthandler_is_enabled(void); + +RPY_EXTERN void pypy_faulthandler_write(int fd, const char *str); +RPY_EXTERN void pypy_faulthandler_write_int(int fd, long value); + +RPY_EXTERN void pypy_faulthandler_dump_traceback(int fd, int all_threads, + void *ucontext); + +RPY_EXTERN char *pypy_faulthandler_dump_traceback_later( + long long microseconds, int repeat, int fd, int exit); +RPY_EXTERN void pypy_faulthandler_cancel_dump_traceback_later(void); + +RPY_EXTERN int pypy_faulthandler_check_signum(long signum); +RPY_EXTERN char *pypy_faulthandler_register(int, int, int, int); +RPY_EXTERN int pypy_faulthandler_unregister(int signum); + RPY_EXTERN int pypy_faulthandler_read_null(void); RPY_EXTERN void pypy_faulthandler_sigsegv(void); RPY_EXTERN int pypy_faulthandler_sigfpe(void); -RPY_EXTERN void pypy_faulthandler_sigabrt(); -#ifdef SIGBUS -RPY_EXTERN void pypy_faulthandler_sigbus(void); -#endif +RPY_EXTERN void pypy_faulthandler_sigabrt(void); +RPY_EXTERN double pypy_faulthandler_stackoverflow(double); -#ifdef SIGILL -RPY_EXTERN void pypy_faulthandler_sigill(void); -#endif #endif /* PYPY_FAULTHANDLER_H */ diff --git a/pypy/module/faulthandler/handler.py b/pypy/module/faulthandler/handler.py new file mode 100644 --- /dev/null +++ b/pypy/module/faulthandler/handler.py @@ -0,0 +1,209 @@ +import os +from rpython.rtyper.lltypesystem import lltype, llmemory, rffi +from rpython.rlib.rposix import is_valid_fd +from rpython.rlib.rarithmetic import widen, ovfcheck_float_to_longlong +from rpython.rlib.objectmodel import keepalive_until_here +from rpython.rtyper.annlowlevel import llhelper + +from pypy.interpreter.error import OperationError, oefmt +from pypy.interpreter.error import exception_from_saved_errno +from pypy.interpreter.gateway import unwrap_spec +from pypy.module.faulthandler import cintf, dumper + + +class Handler(object): + def __init__(self, space): + "NOT_RPYTHON" + self.space = space + self._cleanup_() + + def _cleanup_(self): + self.fatal_error_w_file = None + self.dump_traceback_later_w_file = None + self.user_w_files = None + + def check_err(self, p_err): + if p_err: + raise oefmt(self.space.w_RuntimeError, 'faulthandler: %8', + rffi.charp2str(p_err)) + + def get_fileno_and_file(self, w_file): + space = self.space + if space.is_none(w_file): + w_file = space.sys.get('stderr') + if space.is_none(w_file): + raise oefmt(space.w_RuntimeError, "sys.stderr is None") + elif space.isinstance_w(w_file, space.w_int): + fd = space.c_int_w(w_file) + if fd < 0 or not is_valid_fd(fd): + raise oefmt(space.w_ValueError, + "file is not a valid file descriptor") + return fd, None + + fd = space.c_int_w(space.call_method(w_file, 'fileno')) + try: + space.call_method(w_file, 'flush') + except OperationError as e: + if e.async(space): + raise + pass # ignore flush() error + return fd, w_file + + def setup(self): + dump_callback = llhelper(cintf.DUMP_CALLBACK, dumper._dump_callback) + self.check_err(cintf.pypy_faulthandler_setup(dump_callback)) + + def enable(self, w_file, all_threads): + fileno, w_file = self.get_fileno_and_file(w_file) + self.setup() + self.fatal_error_w_file = w_file + self.check_err(cintf.pypy_faulthandler_enable( + rffi.cast(rffi.INT, fileno), + rffi.cast(rffi.INT, all_threads))) + + def disable(self): + cintf.pypy_faulthandler_disable() + self.fatal_error_w_file = None + + def is_enabled(self): + return bool(widen(cintf.pypy_faulthandler_is_enabled())) + + def dump_traceback(self, w_file, all_threads): + fileno, w_file = self.get_fileno_and_file(w_file) + self.setup() + cintf.pypy_faulthandler_dump_traceback( + rffi.cast(rffi.INT, fileno), + rffi.cast(rffi.INT, all_threads), + llmemory.NULL) + keepalive_until_here(w_file) + + def dump_traceback_later(self, timeout, repeat, w_file, exit): + space = self.space + timeout *= 1e6 + try: + microseconds = ovfcheck_float_to_longlong(timeout) + except OverflowError: + raise oefmt(space.w_OverflowError, "timeout value is too large") + if microseconds <= 0: + raise oefmt(space.w_ValueError, "timeout must be greater than 0") + fileno, w_file = self.get_fileno_and_file(w_file) + self.setup() + self.check_err(cintf.pypy_faulthandler_dump_traceback_later( + rffi.cast(rffi.LONGLONG, microseconds), + rffi.cast(rffi.INT, repeat), + rffi.cast(rffi.INT, fileno), + rffi.cast(rffi.INT, exit))) + self.dump_traceback_later_w_file = w_file + + def cancel_dump_traceback_later(self): + cintf.pypy_faulthandler_cancel_dump_traceback_later() + self.dump_traceback_later_w_file = None + + def check_signum(self, signum): + err = rffi.cast(lltype.Signed, + cintf.pypy_faulthandler_check_signum(signum)) + if err < 0: + space = self.space + if err == -1: + raise oefmt(space.w_RuntimeError, + "signal %d cannot be registered, " + "use enable() instead", signum) + else: + raise oefmt(space.w_ValueError, "signal number out of range") + + def register(self, signum, w_file, all_threads, chain): + self.check_signum(signum) + fileno, w_file = self.get_fileno_and_file(w_file) + self.setup() + self.check_err(cintf.pypy_faulthandler_register( + rffi.cast(rffi.INT, signum), + rffi.cast(rffi.INT, fileno), + rffi.cast(rffi.INT, all_threads), + rffi.cast(rffi.INT, chain))) + if self.user_w_files is None: + self.user_w_files = {} + self.user_w_files[signum] = w_file + + def unregister(self, signum): + self.check_signum(signum) + change = cintf.pypy_faulthandler_unregister( + rffi.cast(rffi.INT, signum)) + if self.user_w_files is not None: + self.user_w_files.pop(signum, None) + return rffi.cast(lltype.Signed, change) == 1 + + def finish(self): + cintf.pypy_faulthandler_teardown() + self._cleanup_() + + +def finish(space): + "Finalize the faulthandler logic (called from shutdown())" + space.fromcache(Handler).finish() + + +@unwrap_spec(all_threads=int) +def enable(space, w_file=None, all_threads=0): + "enable(file=sys.stderr, all_threads=True): enable the fault handler" + space.fromcache(Handler).enable(w_file, all_threads) + +def disable(space): + "disable(): disable the fault handler" + space.fromcache(Handler).disable() + +def is_enabled(space): + "is_enabled()->bool: check if the handler is enabled" + return space.wrap(space.fromcache(Handler).is_enabled()) + +@unwrap_spec(all_threads=int) +def dump_traceback(space, w_file=None, all_threads=0): + """dump the traceback of the current thread into file + including all threads if all_threads is True""" + space.fromcache(Handler).dump_traceback(w_file, all_threads) + +@unwrap_spec(timeout=float, repeat=int, exit=int) +def dump_traceback_later(space, timeout, repeat=0, w_file=None, exit=0): + """dump the traceback of all threads in timeout seconds, + or each timeout seconds if repeat is True. If exit is True, + call _exit(1) which is not safe.""" + space.fromcache(Handler).dump_traceback_later(timeout, repeat, w_file, exit) + +def cancel_dump_traceback_later(space): + """cancel the previous call to dump_traceback_later().""" + space.fromcache(Handler).cancel_dump_traceback_later() + +@unwrap_spec(signum=int, all_threads=int, chain=int) +def register(space, signum, w_file=None, all_threads=1, chain=0): + space.fromcache(Handler).register(signum, w_file, all_threads, chain) + +@unwrap_spec(signum=int) +def unregister(space, signum): + return space.wrap(space.fromcache(Handler).unregister(signum)) + + +# for tests... + +@unwrap_spec(release_gil=int) +def read_null(space, release_gil=0): + if release_gil: + cintf.pypy_faulthandler_read_null_releasegil() + else: + cintf.pypy_faulthandler_read_null() + +@unwrap_spec(release_gil=int) +def sigsegv(space, release_gil=0): + if release_gil: + cintf.pypy_faulthandler_sigsegv_releasegil() + else: + cintf.pypy_faulthandler_sigsegv() + +def sigfpe(space): + cintf.pypy_faulthandler_sigfpe() + +def sigabrt(space): + cintf.pypy_faulthandler_sigabrt() + +@unwrap_spec(levels=int) +def stack_overflow(space, levels=100000000): + levels = float(levels) + return space.wrap(cintf.pypy_faulthandler_stackoverflow(levels)) diff --git a/pypy/module/faulthandler/interp_faulthandler.py b/pypy/module/faulthandler/interp_faulthandler.py deleted file mode 100644 --- a/pypy/module/faulthandler/interp_faulthandler.py +++ /dev/null @@ -1,127 +0,0 @@ -import os -import py - -from pypy.interpreter.gateway import interp2app, unwrap_spec, WrappedDefault -from rpython.rtyper.lltypesystem import lltype, rffi -from rpython.translator import cdir -from rpython.translator.tool.cbuild import ExternalCompilationInfo -from pypy.interpreter.error import OperationError, oefmt - -MAX_NTHREADS = 100 - -cwd = py.path.local(__file__).dirpath() -eci = ExternalCompilationInfo( - includes=[cwd.join('faulthandler.h')], - include_dirs=[str(cwd), cdir], - separate_module_files=[cwd.join('faulthandler.c')]) - -def llexternal(*args, **kwargs): - kwargs.setdefault('releasegil', False) - kwargs.setdefault('compilation_info', eci) - return rffi.llexternal(*args, **kwargs) - -pypy_faulthandler_read_null = llexternal( - 'pypy_faulthandler_read_null', [], lltype.Void) -pypy_faulthandler_read_null_nogil = llexternal( - 'pypy_faulthandler_read_null', [], lltype.Void, - releasegil=True) -pypy_faulthandler_sigsegv = llexternal( - 'pypy_faulthandler_sigsegv', [], lltype.Void) -pypy_faulthandler_sigfpe = llexternal( - 'pypy_faulthandler_sigfpe', [], lltype.Void) -pypy_faulthandler_sigabrt = llexternal( - 'pypy_faulthandler_sigabrt', [], lltype.Void) -pypy_faulthandler_sigbus = llexternal( - 'pypy_faulthandler_sigbus', [], lltype.Void) -pypy_faulthandler_sigill = llexternal( - 'pypy_faulthandler_sigill', [], lltype.Void) - -class FatalErrorState(object): - def __init__(self, space): - self.enabled = False - self.all_threads = True - -@unwrap_spec(w_file=WrappedDefault(None), - w_all_threads=WrappedDefault(True)) -def enable(space, w_file, w_all_threads): - state = space.fromcache(FatalErrorState) - state.enabled = True - state.all_threads = bool(space.int_w(w_all_threads)) - -def disable(space): - state = space.fromcache(FatalErrorState) - state.enabled = False - -def is_enabled(space): - return space.wrap(space.fromcache(FatalErrorState).enabled) - -def register(space, __args__): - pass - - -@unwrap_spec(w_file=WrappedDefault(None), - w_all_threads=WrappedDefault(True)) -def dump_traceback(space, w_file, w_all_threads): - current_ec = space.getexecutioncontext() - if space.int_w(w_all_threads): - ecs = space.threadlocals.getallvalues() - else: - ecs = {0: current_ec} - - if space.is_none(w_file): - w_file = space.sys.get('stderr') - fd = space.c_filedescriptor_w(w_file) - - nthreads = 0 - for thread_ident, ec in ecs.items(): - if nthreads: - os.write(fd, "\n") - if nthreads >= MAX_NTHREADS: - os.write(fd, "...\n") - break - if ec is current_ec: - os.write(fd, "Current thread 0x%x:\n" % thread_ident) - else: - os.write(fd, "Thread 0x%x:\n" % thread_ident) - - frame = ec.gettopframe() - while frame: - code = frame.pycode - lineno = frame.get_last_lineno() - if code: - os.write(fd, " File \"%s\", line %s in %s\n" % ( - code.co_filename, lineno, code.co_name)) - else: - os.write(fd, " File ???, line %s in ???\n" % ( - lineno,)) - - frame = frame.f_backref() - - -@unwrap_spec(w_release_gil=WrappedDefault(False)) -def read_null(space, w_release_gil): - if space.is_true(w_release_gil): - pypy_faulthandler_read_null_nogil() - else: - pypy_faulthandler_read_null() - -def sigsegv(): - pypy_faulthandler_sigsegv() - -def sigfpe(): - pypy_faulthandler_sigfpe() - -def sigabrt(): - pypy_faulthandler_sigabrt() - -#def sigbus(): -# pypy_faulthandler_sigbus() - -#def sigill(): -# pypy_faulthandler_sigill() - -@unwrap_spec(msg=str) -def fatal_error(space, msg): - os.write(2, "Fatal Python error: %s\n" % msg); - dump_traceback(space, space.wrap(None), space.wrap(True)) - raise RuntimeError(msg) diff --git a/pypy/module/faulthandler/test/test_faulthander.py b/pypy/module/faulthandler/test/test_faulthander.py --- a/pypy/module/faulthandler/test/test_faulthander.py +++ b/pypy/module/faulthandler/test/test_faulthander.py @@ -1,12 +1,7 @@ -from pypy.module.faulthandler import interp_faulthandler - -class TestFaultHandler: - def test_fatal_error(self, space): - raises(RuntimeError, interp_faulthandler.fatal_error, space, "Message") class AppTestFaultHandler: spaceconfig = { - "usemodules": ["faulthandler"] + "usemodules": ["faulthandler", "_vmprof"] } def test_enable(self): diff --git a/pypy/module/sys/__init__.py b/pypy/module/sys/__init__.py --- a/pypy/module/sys/__init__.py +++ b/pypy/module/sys/__init__.py @@ -136,6 +136,7 @@ space.setitem(self.w_dict, space.wrap('thread_info'), thread_info) def setup_after_space_initialization(self): + "NOT_RPYTHON" space = self.space if not space.config.translating: diff --git a/rpython/rlib/rvmprof/cintf.py b/rpython/rlib/rvmprof/cintf.py --- a/rpython/rlib/rvmprof/cintf.py +++ b/rpython/rlib/rvmprof/cintf.py @@ -56,6 +56,11 @@ [rffi.INT], lltype.Void, compilation_info=eci, _nowrapper=True) + vmprof_get_traceback = rffi.llexternal("vmprof_get_traceback", + [PVMPROFSTACK, llmemory.Address, + rffi.SIGNEDP, lltype.Signed], + lltype.Signed, compilation_info=eci, + _nowrapper=True) return CInterface(locals()) @@ -154,3 +159,9 @@ def restore_rvmprof_stack(x): vmprof_tl_stack.setraw(x) + +# +# traceback support + +def get_rvmprof_stack(): + return vmprof_tl_stack.get_or_make_raw() diff --git a/rpython/rlib/rvmprof/rvmprof.py b/rpython/rlib/rvmprof/rvmprof.py --- a/rpython/rlib/rvmprof/rvmprof.py +++ b/rpython/rlib/rvmprof/rvmprof.py @@ -1,6 +1,6 @@ import sys, os from rpython.rlib.objectmodel import specialize, we_are_translated -from rpython.rlib import jit, rposix +from rpython.rlib import jit, rposix, rgc from rpython.rlib.rvmprof import cintf from rpython.rtyper.annlowlevel import cast_instance_to_gcref from rpython.rtyper.annlowlevel import cast_base_ptr_to_instance diff --git a/rpython/rlib/rvmprof/src/rvmprof.h b/rpython/rlib/rvmprof/src/rvmprof.h --- a/rpython/rlib/rvmprof/src/rvmprof.h +++ b/rpython/rlib/rvmprof/src/rvmprof.h @@ -1,3 +1,4 @@ +#include <stdint.h> RPY_EXTERN char *vmprof_init(int, double, char *); RPY_EXTERN void vmprof_ignore_signals(int); @@ -8,3 +9,6 @@ RPY_EXTERN int vmprof_stack_append(void*, long); RPY_EXTERN long vmprof_stack_pop(void*); RPY_EXTERN void vmprof_stack_free(void*); +RPY_EXTERN intptr_t vmprof_get_traceback(void *, void *, intptr_t*, intptr_t); + +#define RVMPROF_TRACEBACK_ESTIMATE_N(num_entries) (2 * (num_entries) + 4) diff --git a/rpython/rlib/rvmprof/src/vmprof_common.h b/rpython/rlib/rvmprof/src/vmprof_common.h --- a/rpython/rlib/rvmprof/src/vmprof_common.h +++ b/rpython/rlib/rvmprof/src/vmprof_common.h @@ -128,3 +128,15 @@ return 0; } #endif + +RPY_EXTERN +intptr_t vmprof_get_traceback(void *stack, void *ucontext, + intptr_t *result_p, intptr_t result_length) +{ + int n; + intptr_t pc = ucontext ? GetPC((ucontext_t *)ucontext) : 0; + if (stack == NULL) + stack = get_vmprof_stack(); + n = get_stack_trace(stack, result_p, result_length - 2, pc); + return (intptr_t)n; +} diff --git a/rpython/rlib/rvmprof/test/test_traceback.py b/rpython/rlib/rvmprof/test/test_traceback.py new file mode 100644 --- /dev/null +++ b/rpython/rlib/rvmprof/test/test_traceback.py @@ -0,0 +1,113 @@ +import re +from rpython.rlib import rvmprof, jit +from rpython.rlib.rvmprof import traceback +from rpython.translator.interactive import Translation +from rpython.rtyper.lltypesystem import lltype + + +def test_direct(): + class MyCode: + pass + def get_name(mycode): + raise NotImplementedError + rvmprof.register_code_object_class(MyCode, get_name) + # + @rvmprof.vmprof_execute_code("mycode", lambda code, level: code, + _hack_update_stack_untranslated=True) + def mainloop(code, level): + if level > 0: + mainloop(code, level - 1) + else: + p, length = traceback.traceback(20) + traceback.walk_traceback(MyCode, my_callback, 42, p, length) + lltype.free(p, flavor='raw') + # + seen = [] + def my_callback(code, loc, arg): + seen.append((code, loc, arg)) + # + code1 = MyCode() + rvmprof.register_code(code1, "foo") + mainloop(code1, 2) + # + assert seen == [(code1, traceback.LOC_INTERPRETED, 42), + (code1, traceback.LOC_INTERPRETED, 42), + (code1, traceback.LOC_INTERPRETED, 42)] + +def test_compiled(): + class MyCode: + pass + def get_name(mycode): + raise NotImplementedError + rvmprof.register_code_object_class(MyCode, get_name) + + @rvmprof.vmprof_execute_code("mycode", lambda code, level: code) + def mainloop(code, level): + if level > 0: + mainloop(code, level - 1) + else: + p, length = traceback.traceback(20) + traceback.walk_traceback(MyCode, my_callback, 42, p, length) + lltype.free(p, flavor='raw') + + def my_callback(code, loc, arg): + print code, loc, arg + return 0 + + def f(argv): + code1 = MyCode() + rvmprof.register_code(code1, "foo") + mainloop(code1, 2) + return 0 + + t = Translation(f, None, gc="boehm") + t.compile_c() + stdout = t.driver.cbuilder.cmdexec('') + r = re.compile("[<]MyCode object at 0x([0-9a-f]+)[>] 0 42\n") + got = r.findall(stdout) + assert got == [got[0]] * 3 + +def test_jitted(): + class MyCode: + pass + def get_name(mycode): + raise NotImplementedError + rvmprof.register_code_object_class(MyCode, get_name) + + jitdriver = jit.JitDriver(greens=['code'], reds='auto', + is_recursive=True, + get_unique_id=lambda code: rvmprof.get_unique_id(code)) + + @rvmprof.vmprof_execute_code("mycode", lambda code, level, total_i: code) + def mainloop(code, level, total_i): + i = 20 + while i > 0: + jitdriver.jit_merge_point(code=code) + i -= 1 + if level > 0: + mainloop(code, level - 1, total_i + i) + if level == 0 and total_i == 0: + p, length = traceback.traceback(20) + traceback.walk_traceback(MyCode, my_callback, 42, p, length) + lltype.free(p, flavor='raw') + + def my_callback(code, loc, arg): + print code, loc, arg + return 0 + + def f(argv): + jit.set_param(jitdriver, "inlining", 0) + code1 = MyCode() + rvmprof.register_code(code1, "foo") + mainloop(code1, 2, 0) + return 0 + + t = Translation(f, None, gc="boehm") + t.rtype() + t.driver.pyjitpl_lltype() + t.compile_c() + stdout = t.driver.cbuilder.cmdexec('') + r = re.compile("[<]MyCode object at 0x([0-9a-f]+)[>] (\d) 42\n") + got = r.findall(stdout) + addr = got[0][0] + assert got == [(addr, '1'), (addr, '1'), (addr, '0')] diff --git a/rpython/rlib/rvmprof/traceback.py b/rpython/rlib/rvmprof/traceback.py new file mode 100644 --- /dev/null +++ b/rpython/rlib/rvmprof/traceback.py @@ -0,0 +1,65 @@ +""" +Semi-public interface to gather and print a raw traceback, e.g. +from the faulthandler module. +""" + +from rpython.rlib.rvmprof import cintf, rvmprof +from rpython.rlib.objectmodel import specialize +from rpython.rtyper.lltypesystem import lltype, llmemory, rffi + + +def traceback(estimate_number_of_entries): + """Build and return a vmprof-like traceback, as a pair (array_p, + array_length). The caller must free array_p. Not for signal handlers: + for these, call vmprof_get_traceback() from C code. + """ + _cintf = rvmprof._get_vmprof().cintf + size = estimate_number_of_entries * 2 + 4 + stack = cintf.get_rvmprof_stack() + array_p = lltype.malloc(rffi.SIGNEDP.TO, size, flavor='raw') + NULL = llmemory.NULL + array_length = _cintf.vmprof_get_traceback(stack, NULL, array_p, size) + return (array_p, array_length) + + +LOC_INTERPRETED = 0 +LOC_JITTED = 1 +LOC_JITTED_INLINED = 2 + + +@specialize.arg(0, 1) +def _traceback_one(CodeClass, callback, arg, code_id, loc): + found_code = None + if code_id != 0: + all_code_wrefs = CodeClass._vmprof_weak_list.get_all_handles() + i = len(all_code_wrefs) - 1 + while i >= 0: + code = all_code_wrefs[i]() + if code is not None and code._vmprof_unique_id == code_id: + found_code = code + break + i -= 1 + callback(found_code, loc, arg) + +@specialize.arg(0, 1) +def walk_traceback(CodeClass, callback, arg, array_p, array_length): + """Invoke 'callback(code_obj, loc, arg)' for every traceback entry. + 'code_obj' may be None if it can't be determined. 'loc' is one + of the LOC_xxx constants. + """ + i = 0 + while i < array_length - 1: + tag = array_p[i] + tagged_value = array_p[i + 1] + if tag == rvmprof.VMPROF_CODE_TAG: + loc = LOC_INTERPRETED + _traceback_one(CodeClass, callback, arg, tagged_value, loc) + elif tag == rvmprof.VMPROF_JITTED_TAG: + if i + 2 >= array_length: # skip last entry, can't determine if + break # it's LOC_JITTED_INLINED or LOC_JITTED + if array_p[i + 2] == rvmprof.VMPROF_JITTED_TAG: + loc = LOC_JITTED_INLINED + else: + loc = LOC_JITTED + _traceback_one(CodeClass, callback, arg, tagged_value, loc) + i += 2 diff --git a/rpython/translator/c/src/threadlocal.c b/rpython/translator/c/src/threadlocal.c --- a/rpython/translator/c/src/threadlocal.c +++ b/rpython/translator/c/src/threadlocal.c @@ -14,12 +14,22 @@ static int check_valid(void); +int _RPython_ThreadLocals_AcquireTimeout(int max_wait_iterations) { + while (1) { + long old_value = pypy_lock_test_and_set(&pypy_threadlocal_lock, 1); + if (old_value == 0) + break; + /* busy loop */ + if (max_wait_iterations == 0) + return -1; + if (max_wait_iterations > 0) + --max_wait_iterations; + } + assert(check_valid()); + return 0; +} void _RPython_ThreadLocals_Acquire(void) { - long old_value; - do { - old_value = pypy_lock_test_and_set(&pypy_threadlocal_lock, 1); - } while (old_value != 0); /* busy loop */ - assert(check_valid()); + _RPython_ThreadLocals_AcquireTimeout(-1); } void _RPython_ThreadLocals_Release(void) { assert(check_valid()); @@ -60,11 +70,7 @@ { /* assume that at most one pypy_threadlocal_s survived, the current one */ struct pypy_threadlocal_s *cur; -#ifdef USE___THREAD - cur = &pypy_threadlocal; -#else cur = (struct pypy_threadlocal_s *)_RPy_ThreadLocals_Get(); -#endif if (cur && cur->ready == 42) { cur->next = cur->prev = &linkedlist_head; linkedlist_head.next = linkedlist_head.prev = cur; diff --git a/rpython/translator/c/src/threadlocal.h b/rpython/translator/c/src/threadlocal.h --- a/rpython/translator/c/src/threadlocal.h +++ b/rpython/translator/c/src/threadlocal.h @@ -21,6 +21,7 @@ RPY_EXTERN void _RPython_ThreadLocals_Acquire(void); RPY_EXTERN void _RPython_ThreadLocals_Release(void); +RPY_EXTERN int _RPython_ThreadLocals_AcquireTimeout(int max_wait_iterations); /* Must acquire/release the thread-local lock around a series of calls to the following function */ @@ -48,14 +49,14 @@ #define OP_THREADLOCALREF_ADDR(r) \ do { \ - r = (char *)&pypy_threadlocal; \ + r = (void *)&pypy_threadlocal; \ if (pypy_threadlocal.ready != 42) \ r = _RPython_ThreadLocals_Build(); \ } while (0) #define _OP_THREADLOCALREF_ADDR_SIGHANDLER(r) \ do { \ - r = (char *)&pypy_threadlocal; \ + r = (void *)&pypy_threadlocal; \ if (pypy_threadlocal.ready != 42) \ r = NULL; \ } while (0) @@ -66,6 +67,8 @@ #define RPY_THREADLOCALREF_GET(FIELD) pypy_threadlocal.FIELD +#define _RPy_ThreadLocals_Get() (&pypy_threadlocal) + /* ------------------------------------------------------------ */ #else @@ -89,14 +92,14 @@ #define OP_THREADLOCALREF_ADDR(r) \ do { \ - r = (char *)_RPy_ThreadLocals_Get(); \ + r = (void *)_RPy_ThreadLocals_Get(); \ if (!r) \ r = _RPython_ThreadLocals_Build(); \ } while (0) #define _OP_THREADLOCALREF_ADDR_SIGHANDLER(r) \ do { \ - r = (char *)_RPy_ThreadLocals_Get(); \ + r = (void *)_RPy_ThreadLocals_Get(); \ } while (0) #define RPY_THREADLOCALREF_ENSURE() \ _______________________________________________ pypy-commit mailing list pypy-commit@python.org https://mail.python.org/mailman/listinfo/pypy-commit