[pypy-commit] pypy faulthandler: Manual graft of the py3k-faulthandler branch

arigo pypy.commits at gmail.com
Sat Oct 1 05:16:52 EDT 2016


Author: Armin Rigo <arigo at tunes.org>
Branch: faulthandler
Changeset: r87491:15ef8db2a0e7
Date: 2016-10-01 11:16 +0200
http://bitbucket.org/pypy/pypy/changeset/15ef8db2a0e7/

Log:	Manual graft of the py3k-faulthandler branch

diff --git a/pypy/config/pypyoption.py b/pypy/config/pypyoption.py
--- a/pypy/config/pypyoption.py
+++ b/pypy/config/pypyoption.py
@@ -43,6 +43,7 @@
 try:
     if detect_cpu.autodetect().startswith('x86'):
         working_modules.add('_vmprof')
+        working_modules.add('faulthandler')
 except detect_cpu.ProcessorAutodetectError:
     pass
 
@@ -89,6 +90,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
@@ -114,7 +116,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"],
     }
 
 def get_module_validator(modname):
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
@@ -33,6 +33,7 @@
 --info : print translation information about this PyPy executable
 -X track-resources : track the creation of files and sockets and display
                      a warning if they are not closed explicitly
+-X faulthandler    : attempt to display tracebacks when PyPy crashes
 """
 # Missing vs CPython: PYTHONHOME, PYTHONCASEOK
 USAGE2 = """
@@ -233,12 +234,22 @@
         import pypyjit
         pypyjit.set_param(jitparam)
 
+def run_faulthandler():
+    if 'faulthandler' in sys.builtin_module_names:
+        import faulthandler
+        try:
+            faulthandler.enable(2)   # manually set to stderr
+        except ValueError:
+            pass      # ignore "2 is not a valid file descriptor"
+
 def set_runtime_options(options, Xparam, *args):
     if Xparam == 'track-resources':
         sys.pypy_set_track_resources(True)
+    elif Xparam == 'faulthandler':
+        run_faulthandler()
     else:
         print >> sys.stderr, 'usage: %s -X [options]' % (get_sys_executable(),)
-        print >> sys.stderr, '[options] can be: track-resources'
+        print >> sys.stderr, '[options] can be: track-resources, faulthandler'
         raise SystemExit
 
 class CommandLineError(Exception):
@@ -527,6 +538,9 @@
             print >> sys.stderr, (
                 "Warning: pypy does not implement py3k warnings")
 
+    if os.getenv('PYTHONFAULTHANDLER'):
+        run_faulthandler()
+
 ##    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
new file mode 100644
--- /dev/null
+++ b/pypy/module/faulthandler/__init__.py
@@ -0,0 +1,38 @@
+import sys
+from pypy.interpreter.mixedmodule import MixedModule
+
+
+class Module(MixedModule):
+    appleveldefs = {
+    }
+
+    interpleveldefs = {
+        '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',
+    }
+
+    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')
+
+    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")
+
+
+ at 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
new file mode 100644
--- /dev/null
+++ b/pypy/module/faulthandler/faulthandler.c
@@ -0,0 +1,679 @@
+#include "faulthandler.h"
+#include <stdlib.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>
+
+#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)
+{
+    (void)write(fd, str, strlen(str));
+}
+
+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
+       explicitly calling the previous error handler). In a normal case, the
+       SIGSEGV was raised by the kernel because of a fault, and so if the
+       program retries to execute the same instruction, the fault will be
+       raised again.
+
+       Here the fault is simulated by a fake SIGSEGV signal raised by the
+       application. We have to raise SIGSEGV at lease twice: once for
+       faulthandler_fatal_error(), and one more time for the previous signal
+       handler. */
+    while(1)
+        raise(SIGSEGV);
+#else
+    raise(SIGSEGV);
+#endif
+}
+
+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. */
+    raise(SIGFPE);
+    /* This line is never reached, but we pretend to make something with z
+       to silence a compiler warning. */
+    return z;
+}
+
+RPY_EXTERN
+void pypy_faulthandler_sigabrt(void)
+{
+    faulthandler_suppress_crash_report();
+    abort();
+}
+
+static double fh_stack_overflow(double levels)
+{
+    if (levels > 2.5) {
+        return (sqrt(fh_stack_overflow(levels - 1.0))
+                + fh_stack_overflow(levels * 1e-10));
+    }
+    return 1e100 + levels;
+}
+
+RPY_EXTERN
+double pypy_faulthandler_stackoverflow(double levels)
+{
+    faulthandler_suppress_crash_report();
+    return fh_stack_overflow(levels);
+}
diff --git a/pypy/module/faulthandler/faulthandler.h b/pypy/module/faulthandler/faulthandler.h
new file mode 100644
--- /dev/null
+++ b/pypy/module/faulthandler/faulthandler.h
@@ -0,0 +1,40 @@
+#ifndef PYPY_FAULTHANDLER_H
+#define PYPY_FAULTHANDLER_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(void);
+RPY_EXTERN double pypy_faulthandler_stackoverflow(double);
+
+
+#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()
+
+
+ at 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())
+
+ at 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)
+
+ at 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()
+
+ at 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)
+
+ at unwrap_spec(signum=int)
+def unregister(space, signum):
+    return space.wrap(space.fromcache(Handler).unregister(signum))
+
+
+# for tests...
+
+ at 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()
+
+ at 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()
+
+ at 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/test/test_faulthander.py b/pypy/module/faulthandler/test/test_faulthander.py
new file mode 100644
--- /dev/null
+++ b/pypy/module/faulthandler/test/test_faulthander.py
@@ -0,0 +1,18 @@
+
+class AppTestFaultHandler:
+    spaceconfig = {
+        "usemodules": ["faulthandler", "_vmprof"]
+    }
+
+    def test_enable(self):
+        import faulthandler, sys
+        faulthandler.enable()
+        assert faulthandler.is_enabled() is True
+        faulthandler.enable(file=sys.stderr, all_threads=True)
+        faulthandler.disable()
+        assert faulthandler.is_enabled() is False
+
+    def test_dump_traceback(self):
+        import faulthandler, sys
+        faulthandler.dump_traceback()
+        faulthandler.dump_traceback(file=sys.stderr, all_threads=True)
diff --git a/pypy/module/faulthandler/test/test_ztranslation.py b/pypy/module/faulthandler/test/test_ztranslation.py
new file mode 100644
--- /dev/null
+++ b/pypy/module/faulthandler/test/test_ztranslation.py
@@ -0,0 +1,4 @@
+from pypy.objspace.fake.checkmodule import checkmodule
+
+def test_faulthandler_translates():
+    checkmodule('faulthandler')
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
+
+
+ at 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)
+
+ at 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()             \


More information about the pypy-commit mailing list