From db967dca64f42597a7e8bcaabea886bb69a5d041 Mon Sep 17 00:00:00 2001 From: Diego Russo Date: Fri, 24 Apr 2026 14:47:34 +0100 Subject: [PATCH] GH-126910: Add GNU backtrace support for unwinding JIT frames --- Include/internal/pycore_jit_publish.h | 27 ++++++ Include/internal/pycore_jit_unwind.h | 14 ++- Include/internal/pycore_optimizer.h | 3 +- Lib/test/test_frame_pointer_unwind.py | 62 ++++++++++-- Makefile.pre.in | 1 + Modules/_testinternalcapi.c | 64 ++++++++++++- PCbuild/_freeze_module.vcxproj | 1 + PCbuild/_freeze_module.vcxproj.filters | 3 + PCbuild/pythoncore.vcxproj | 1 + PCbuild/pythoncore.vcxproj.filters | 3 + Python/jit.c | 53 ++-------- Python/jit_publish.c | 128 +++++++++++++++++++++++++ Python/jit_unwind.c | 67 ++++++++++++- Python/optimizer.c | 2 +- 14 files changed, 366 insertions(+), 63 deletions(-) create mode 100644 Include/internal/pycore_jit_publish.h create mode 100644 Python/jit_publish.c diff --git a/Include/internal/pycore_jit_publish.h b/Include/internal/pycore_jit_publish.h new file mode 100644 index 00000000000..1c9cd174d0a --- /dev/null +++ b/Include/internal/pycore_jit_publish.h @@ -0,0 +1,27 @@ +#ifndef Py_INTERNAL_JIT_PUBLISH_H +#define Py_INTERNAL_JIT_PUBLISH_H + +#ifndef Py_BUILD_CORE +# error "this header requires Py_BUILD_CORE define" +#endif + +#include + +typedef struct _PyJitCodeRegistration _PyJitCodeRegistration; + +#ifdef _Py_JIT + +/* Return a teardown handle when any backend stores registration state. + * A NULL result is valid when publication succeeded only through backends + * with no unregister step, such as perf map output. + */ +_PyJitCodeRegistration *_PyJit_RegisterCode(const void *code_addr, + size_t code_size, + const char *entry, + const char *filename); + +void _PyJit_UnregisterCode(_PyJitCodeRegistration *registration); + +#endif // _Py_JIT + +#endif // Py_INTERNAL_JIT_PUBLISH_H diff --git a/Include/internal/pycore_jit_unwind.h b/Include/internal/pycore_jit_unwind.h index 2d325ad9562..67c996bfd66 100644 --- a/Include/internal/pycore_jit_unwind.h +++ b/Include/internal/pycore_jit_unwind.h @@ -10,9 +10,12 @@ #if defined(_Py_JIT) && defined(__linux__) && defined(__ELF__) # define PY_HAVE_JIT_GDB_UNWIND +# define PY_HAVE_JIT_GNU_BACKTRACE_UNWIND #endif -#if defined(PY_HAVE_PERF_TRAMPOLINE) || defined(PY_HAVE_JIT_GDB_UNWIND) +#if defined(PY_HAVE_PERF_TRAMPOLINE) \ + || defined(PY_HAVE_JIT_GDB_UNWIND) \ + || defined(PY_HAVE_JIT_GNU_BACKTRACE_UNWIND) #if defined(PY_HAVE_JIT_GDB_UNWIND) extern PyMutex _Py_jit_debug_mutex; @@ -63,6 +66,13 @@ void *_PyJitUnwind_GdbRegisterCode(const void *code_addr, void _PyJitUnwind_GdbUnregisterCode(void *handle); -#endif // defined(PY_HAVE_PERF_TRAMPOLINE) || defined(PY_HAVE_JIT_GDB_UNWIND) +#if defined(PY_HAVE_JIT_GNU_BACKTRACE_UNWIND) +void *_PyJitUnwind_GnuBacktraceRegisterCode(const void *code_addr, + size_t code_size); + +void _PyJitUnwind_GnuBacktraceUnregisterCode(void *handle); +#endif + +#endif // JIT unwind support #endif // Py_INTERNAL_JIT_UNWIND_H diff --git a/Include/internal/pycore_optimizer.h b/Include/internal/pycore_optimizer.h index a0727c045e5..69f913ec9c3 100644 --- a/Include/internal/pycore_optimizer.h +++ b/Include/internal/pycore_optimizer.h @@ -9,6 +9,7 @@ extern "C" { #endif #include "pycore_typedefs.h" // _PyInterpreterFrame +#include "pycore_jit_publish.h" #include "pycore_uop.h" // _PyUOpInstruction #include "pycore_uop_ids.h" #include "pycore_stackref.h" // _PyStackRef @@ -198,7 +199,7 @@ typedef struct _PyExecutorObject { uint32_t code_size; size_t jit_size; void *jit_code; - void *jit_gdb_handle; + _PyJitCodeRegistration *jit_registration; _PyExitData exits[1]; } _PyExecutorObject; diff --git a/Lib/test/test_frame_pointer_unwind.py b/Lib/test/test_frame_pointer_unwind.py index 2f9ce2bf049..cd5888b6d9a 100644 --- a/Lib/test/test_frame_pointer_unwind.py +++ b/Lib/test/test_frame_pointer_unwind.py @@ -70,7 +70,7 @@ def _frame_pointers_expected(machine): return None -def _build_stack_and_unwind(): +def _build_stack_and_unwind(unwinder): import operator def build_stack(n, unwinder, warming_up_caller=False): @@ -89,7 +89,7 @@ def build_stack(n, unwinder, warming_up_caller=False): result = operator.call(build_stack, n - 1, unwinder, warming_up) return result - stack = build_stack(10, _testinternalcapi.manual_frame_pointer_unwind) + stack = build_stack(10, unwinder) return stack @@ -112,8 +112,9 @@ def _classify_stack(stack, jit_enabled): return annotated, python_frames, jit_frames, other_frames -def _annotate_unwind(): - stack = _build_stack_and_unwind() +def _annotate_unwind(unwinder_name="manual_frame_pointer_unwind"): + unwinder = getattr(_testinternalcapi, unwinder_name) + stack = _build_stack_and_unwind(unwinder) jit_enabled = hasattr(sys, "_jit") and sys._jit.is_enabled() jit_backend = _testinternalcapi.get_jit_backend() ranges = _testinternalcapi.get_jit_code_ranges() if jit_enabled else [] @@ -132,13 +133,14 @@ def _annotate_unwind(): "jit_frames": jit_frames, "other_frames": other_frames, "jit_backend": jit_backend, + "unwinder": unwinder_name, }) -def _manual_unwind_length(**env): +def _unwind_result(unwinder_name, **env): code = ( "from test.test_frame_pointer_unwind import _annotate_unwind; " - "print(_annotate_unwind());" + f"print(_annotate_unwind({unwinder_name!r}));" ) run_env = os.environ.copy() run_env.update(env) @@ -197,7 +199,7 @@ def test_manual_unwind_respects_frame_pointers(self): for env, using_jit in envs: with self.subTest(env=env): - result = _manual_unwind_length(**env) + result = _unwind_result("manual_frame_pointer_unwind", **env) jit_frames = result["jit_frames"] python_frames = result.get("python_frames", 0) jit_backend = result.get("jit_backend") @@ -240,5 +242,51 @@ def test_manual_unwind_respects_frame_pointers(self): ) +@support.requires_gil_enabled("test requires the GIL enabled") +@unittest.skipIf(support.is_wasi, "test not supported on WASI") +@unittest.skipUnless(sys.platform == "linux", "GNU backtrace unwinding test requires Linux") +class GnuBacktraceUnwindTests(unittest.TestCase): + + def setUp(self): + super().setUp() + try: + _testinternalcapi.gnu_backtrace_unwind() + except RuntimeError as exc: + if "not supported" in str(exc): + self.skipTest("gnu backtrace unwinding not supported on this platform") + raise + + def test_gnu_backtrace_unwinds_through_jit_frames(self): + jit_available = hasattr(sys, "_jit") and sys._jit.is_available() + envs = [({"PYTHON_JIT": "0"}, False)] + if jit_available: + envs.append(({"PYTHON_JIT": "1"}, True)) + + for env, using_jit in envs: + with self.subTest(env=env): + result = _unwind_result("gnu_backtrace_unwind", **env) + python_frames = result.get("python_frames", 0) + jit_frames = result.get("jit_frames", 0) + jit_backend = result.get("jit_backend") + + self.assertGreater( + python_frames, + 0, + f"expected to find Python frames in GNU backtrace with env {env}", + ) + if using_jit and jit_backend == "jit": + self.assertGreater( + jit_frames, + 0, + f"expected GNU backtrace to include JIT frames with env {env}", + ) + else: + self.assertEqual( + jit_frames, + 0, + f"unexpected JIT frames counted in GNU backtrace with env {env}", + ) + + if __name__ == "__main__": unittest.main() diff --git a/Makefile.pre.in b/Makefile.pre.in index fc44399434f..7a31db5372a 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -470,6 +470,7 @@ PYTHON_OBJS= \ Python/instruction_sequence.o \ Python/intrinsics.o \ Python/jit.o \ + Python/jit_publish.o \ $(JIT_OBJS) \ Python/legacy_tracing.o \ Python/lock.o \ diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index a07675bb66d..dc0cfcbf3a5 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -47,6 +47,9 @@ #if defined(HAVE_DLADDR) && !defined(__wasi__) # include #endif +#if defined(HAVE_EXECINFO_H) +# include +#endif #ifdef MS_WINDOWS # include # include @@ -58,6 +61,7 @@ static const uintptr_t min_frame_pointer_addr = 0x1000; +#define MAX_UNWIND_FRAMES 200 static PyObject * @@ -328,7 +332,6 @@ get_jit_backend(PyObject *self, PyObject *Py_UNUSED(args)) static PyObject * manual_unwind_from_fp(uintptr_t *frame_pointer) { - Py_ssize_t max_depth = 200; int stack_grows_down = _Py_STACK_GROWS_DOWN; if (frame_pointer == NULL) { @@ -340,14 +343,20 @@ manual_unwind_from_fp(uintptr_t *frame_pointer) return NULL; } - for (Py_ssize_t depth = 0; - depth < max_depth && frame_pointer != NULL; - depth++) - { + Py_ssize_t depth = 0; + while (frame_pointer != NULL) { uintptr_t fp_addr = (uintptr_t)frame_pointer; if ((fp_addr % sizeof(uintptr_t)) != 0) { break; } + if (depth >= MAX_UNWIND_FRAMES) { + Py_DECREF(result); + PyErr_Format( + PyExc_RuntimeError, + "manual frame pointer unwind returned more than %d frames", + MAX_UNWIND_FRAMES); + return NULL; + } uintptr_t return_addr = frame_pointer[1]; PyObject *addr_obj = PyLong_FromUnsignedLongLong(return_addr); @@ -361,6 +370,7 @@ manual_unwind_from_fp(uintptr_t *frame_pointer) return NULL; } Py_DECREF(addr_obj); + depth++; uintptr_t *next_fp = (uintptr_t *)frame_pointer[0]; // Stop if the frame pointer is extremely low. @@ -383,6 +393,49 @@ manual_unwind_from_fp(uintptr_t *frame_pointer) return result; } + +#if defined(HAVE_EXECINFO_H) && defined(HAVE_BACKTRACE) +static PyObject * +gnu_backtrace_unwind(PyObject *self, PyObject *Py_UNUSED(args)) +{ + void *addresses[MAX_UNWIND_FRAMES + 1]; + int frame_count = backtrace(addresses, (int)Py_ARRAY_LENGTH(addresses)); + if (frame_count < 0) { + PyErr_SetString(PyExc_RuntimeError, "backtrace() failed"); + return NULL; + } + if (frame_count > MAX_UNWIND_FRAMES) { + PyErr_Format( + PyExc_RuntimeError, + "backtrace() returned more than %d frames", + MAX_UNWIND_FRAMES); + return NULL; + } + + PyObject *result = PyList_New(frame_count); + if (result == NULL) { + return NULL; + } + for (int i = 0; i < frame_count; i++) { + PyObject *addr_obj = PyLong_FromUnsignedLongLong((uintptr_t)addresses[i]); + if (addr_obj == NULL) { + Py_DECREF(result); + return NULL; + } + PyList_SET_ITEM(result, i, addr_obj); + } + return result; +} +#else +static PyObject * +gnu_backtrace_unwind(PyObject *self, PyObject *Py_UNUSED(args)) +{ + PyErr_SetString(PyExc_RuntimeError, + "gnu_backtrace_unwind is not supported on this platform"); + return NULL; +} +#endif + #if defined(__GNUC__) || defined(__clang__) static PyObject * manual_frame_pointer_unwind(PyObject *self, PyObject *args) @@ -2915,6 +2968,7 @@ static PyMethodDef module_functions[] = { {"classify_stack_addresses", classify_stack_addresses, METH_VARARGS}, {"get_jit_code_ranges", get_jit_code_ranges, METH_NOARGS}, {"get_jit_backend", get_jit_backend, METH_NOARGS}, + {"gnu_backtrace_unwind", gnu_backtrace_unwind, METH_NOARGS}, {"manual_frame_pointer_unwind", manual_frame_pointer_unwind, METH_NOARGS}, {"test_bswap", test_bswap, METH_NOARGS}, {"test_popcount", test_popcount, METH_NOARGS}, diff --git a/PCbuild/_freeze_module.vcxproj b/PCbuild/_freeze_module.vcxproj index 953973a2ad3..a8126d44c70 100644 --- a/PCbuild/_freeze_module.vcxproj +++ b/PCbuild/_freeze_module.vcxproj @@ -237,6 +237,7 @@ + diff --git a/PCbuild/_freeze_module.vcxproj.filters b/PCbuild/_freeze_module.vcxproj.filters index 13db4d93f54..c84c32ccd18 100644 --- a/PCbuild/_freeze_module.vcxproj.filters +++ b/PCbuild/_freeze_module.vcxproj.filters @@ -265,6 +265,9 @@ Source Files + + Source Files + Source Files diff --git a/PCbuild/pythoncore.vcxproj b/PCbuild/pythoncore.vcxproj index fae4a90b453..1c4a468d389 100644 --- a/PCbuild/pythoncore.vcxproj +++ b/PCbuild/pythoncore.vcxproj @@ -648,6 +648,7 @@ + diff --git a/PCbuild/pythoncore.vcxproj.filters b/PCbuild/pythoncore.vcxproj.filters index 04b6641ae30..a631d42fe5b 100644 --- a/PCbuild/pythoncore.vcxproj.filters +++ b/PCbuild/pythoncore.vcxproj.filters @@ -1481,6 +1481,9 @@ Python + + Python + Source Files diff --git a/Python/jit.c b/Python/jit.c index 8b555105129..5c8f8785773 100644 --- a/Python/jit.c +++ b/Python/jit.c @@ -15,7 +15,7 @@ #include "pycore_interpframe.h" #include "pycore_interpolation.h" #include "pycore_intrinsics.h" -#include "pycore_jit_unwind.h" +#include "pycore_jit_publish.h" #include "pycore_lazyimportobject.h" #include "pycore_list.h" #include "pycore_long.h" @@ -61,40 +61,6 @@ jit_error(const char *message) PyErr_Format(PyExc_RuntimeWarning, "JIT %s (%d)", message, hint); } -/* - * Publish JIT code to optional tooling backends. - * - * The return value is a backend-specific deregistration handle, not a - * success/failure indicator. NULL means there is nothing to unregister later: - * perf does not need a handle, and GDB registration failures are intentionally - * non-fatal because tooling support must not make JIT compilation fail. - */ -static void * -jit_record_code(const void *code_addr, size_t code_size, - const char *entry, const char *filename) -{ -#ifdef PY_HAVE_PERF_TRAMPOLINE - _PyPerf_Callbacks callbacks; - _PyPerfTrampoline_GetCallbacks(&callbacks); - if (callbacks.write_state == _Py_perfmap_jit_callbacks.write_state) { - _PyPerfJit_WriteNamedCode( - code_addr, code_size, entry, filename); - return NULL; - } -#endif - -#if defined(PY_HAVE_JIT_GDB_UNWIND) - return _PyJitUnwind_GdbRegisterCode( - code_addr, code_size, entry, filename); -#else - (void)code_addr; - (void)code_size; - (void)entry; - (void)filename; - return NULL; -#endif -} - static int address_in_executor_array(_PyExecutorObject **ptrs, size_t count, uintptr_t addr) { @@ -750,10 +716,11 @@ _PyJIT_Compile(_PyExecutorObject *executor, const _PyUOpInstruction trace[], siz } executor->jit_code = memory; executor->jit_size = total_size; - executor->jit_gdb_handle = jit_record_code(memory, - code_size + state.trampolines.size, - "jit", - "executor"); + executor->jit_registration = _PyJit_RegisterCode( + memory, + code_size + state.trampolines.size, + "jit", + "executor"); return 0; } @@ -766,12 +733,8 @@ _PyJIT_Free(_PyExecutorObject *executor) if (memory) { executor->jit_code = NULL; executor->jit_size = 0; -#if defined(PY_HAVE_JIT_GDB_UNWIND) - if (executor->jit_gdb_handle != NULL) { - _PyJitUnwind_GdbUnregisterCode(executor->jit_gdb_handle); - executor->jit_gdb_handle = NULL; - } -#endif + _PyJit_UnregisterCode(executor->jit_registration); + executor->jit_registration = NULL; if (jit_free(memory, size)) { PyErr_FormatUnraisable("Exception ignored while " "freeing JIT memory"); diff --git a/Python/jit_publish.c b/Python/jit_publish.c new file mode 100644 index 00000000000..ffff3941a8d --- /dev/null +++ b/Python/jit_publish.c @@ -0,0 +1,128 @@ +#include "Python.h" + +#include "pycore_ceval.h" +#include "pycore_jit_publish.h" +#include "pycore_jit_unwind.h" + +#ifdef _Py_JIT + +struct _PyJitCodeRegistration { + void *gdb_handle; + void *gnu_backtrace_handle; +}; + +static void +jit_register_perf_code(const void *code_addr, size_t code_size, + const char *entry, const char *filename) +{ +#ifdef PY_HAVE_PERF_TRAMPOLINE + _PyPerf_Callbacks callbacks; + _PyPerfTrampoline_GetCallbacks(&callbacks); + if (callbacks.write_state == _Py_perfmap_jit_callbacks.write_state) { + _PyPerfJit_WriteNamedCode( + code_addr, code_size, entry, filename); + } +#else + (void)code_addr; + (void)code_size; + (void)entry; + (void)filename; +#endif +} + +static void +jit_register_gdb_code(_PyJitCodeRegistration *registration, + const void *code_addr, size_t code_size, + const char *entry, const char *filename) +{ +#if defined(PY_HAVE_JIT_GDB_UNWIND) + registration->gdb_handle = _PyJitUnwind_GdbRegisterCode( + code_addr, code_size, entry, filename); +#else + (void)registration; + (void)code_addr; + (void)code_size; + (void)entry; + (void)filename; +#endif +} + +static void +jit_unregister_gdb_code(_PyJitCodeRegistration *registration) +{ +#if defined(PY_HAVE_JIT_GDB_UNWIND) + if (registration->gdb_handle != NULL) { + _PyJitUnwind_GdbUnregisterCode(registration->gdb_handle); + registration->gdb_handle = NULL; + } +#else + (void)registration; +#endif +} + +static void +jit_register_gnu_backtrace_code(_PyJitCodeRegistration *registration, + const void *code_addr, size_t code_size) +{ +#if defined(PY_HAVE_JIT_GNU_BACKTRACE_UNWIND) + registration->gnu_backtrace_handle = + _PyJitUnwind_GnuBacktraceRegisterCode(code_addr, code_size); +#else + (void)registration; + (void)code_addr; + (void)code_size; +#endif +} + +static void +jit_unregister_gnu_backtrace_code(_PyJitCodeRegistration *registration) +{ +#if defined(PY_HAVE_JIT_GNU_BACKTRACE_UNWIND) + if (registration->gnu_backtrace_handle != NULL) { + _PyJitUnwind_GnuBacktraceUnregisterCode( + registration->gnu_backtrace_handle); + registration->gnu_backtrace_handle = NULL; + } +#else + (void)registration; +#endif +} + +_PyJitCodeRegistration * +_PyJit_RegisterCode(const void *code_addr, size_t code_size, + const char *entry, const char *filename) +{ + jit_register_perf_code(code_addr, code_size, entry, filename); + + _PyJitCodeRegistration *registration = PyMem_RawCalloc( + 1, sizeof(*registration)); + if (registration == NULL) { + return NULL; + } + + jit_register_gdb_code( + registration, code_addr, code_size, entry, filename); + jit_register_gnu_backtrace_code( + registration, code_addr, code_size); + if (registration->gdb_handle == NULL && + registration->gnu_backtrace_handle == NULL) + { + PyMem_RawFree(registration); + return NULL; + } + return registration; +} + +void +_PyJit_UnregisterCode(_PyJitCodeRegistration *registration) +{ + if (registration == NULL) { + return; + } + + jit_unregister_gnu_backtrace_code(registration); + jit_unregister_gdb_code(registration); + PyMem_RawFree(registration); +} + +#endif // _Py_JIT diff --git a/Python/jit_unwind.c b/Python/jit_unwind.c index 09002feafec..646106f0a96 100644 --- a/Python/jit_unwind.c +++ b/Python/jit_unwind.c @@ -16,11 +16,22 @@ # endif #endif -#if defined(PY_HAVE_PERF_TRAMPOLINE) || defined(PY_HAVE_JIT_GDB_UNWIND) +#if defined(PY_HAVE_PERF_TRAMPOLINE) \ + || defined(PY_HAVE_JIT_GDB_UNWIND) \ + || defined(PY_HAVE_JIT_GNU_BACKTRACE_UNWIND) #if defined(PY_HAVE_JIT_GDB_UNWIND) # include #endif +#if defined(PY_HAVE_JIT_GNU_BACKTRACE_UNWIND) +/* + * libgcc exposes frame registration entry points, but GCC's public headers + * on some distributions do not declare them even though the symbols are + * available in libgcc_s. + */ +void __register_frame(const void *); +void __deregister_frame(const void *); +#endif #include #include @@ -983,4 +994,56 @@ _PyJitUnwind_GdbUnregisterCode(void *handle) #endif } -#endif // defined(PY_HAVE_PERF_TRAMPOLINE) || defined(PY_HAVE_JIT_GDB_UNWIND) +#if defined(PY_HAVE_JIT_GNU_BACKTRACE_UNWIND) +void * +_PyJitUnwind_GnuBacktraceRegisterCode(const void *code_addr, size_t code_size) +{ + if (code_addr == NULL || code_size == 0) { + return NULL; + } + + size_t eh_frame_size = _PyJitUnwind_EhFrameSize(1); + if (eh_frame_size == 0) { + return NULL; + } + size_t total_size = eh_frame_size + sizeof(uint32_t); + if (total_size < eh_frame_size) { + return NULL; + } + + /* + * libgcc's __register_frame walks a .eh_frame section until it finds a + * zero-length terminator entry, so keep an extra zeroed word after the + * generated CIE/FDE pair. + * + * See GCC's libgcc/unwind-dw2-fde.c (__register_frame) and + * libgcc/unwind-dw2-fde.h (last_fde/next_fde): + * https://github.com/gcc-mirror/gcc/blob/master/libgcc/unwind-dw2-fde.c + * https://github.com/gcc-mirror/gcc/blob/master/libgcc/unwind-dw2-fde.h + */ + uint8_t *eh_frame = PyMem_RawCalloc(1, total_size); + if (eh_frame == NULL) { + return NULL; + } + if (_PyJitUnwind_BuildEhFrame( + eh_frame, eh_frame_size, code_addr, code_size, 1) == 0) { + PyMem_RawFree(eh_frame); + return NULL; + } + + __register_frame(eh_frame); + return eh_frame; +} + +void +_PyJitUnwind_GnuBacktraceUnregisterCode(void *handle) +{ + if (handle == NULL) { + return; + } + __deregister_frame(handle); + PyMem_RawFree(handle); +} +#endif // defined(PY_HAVE_JIT_GNU_BACKTRACE_UNWIND) + +#endif // JIT unwind support diff --git a/Python/optimizer.c b/Python/optimizer.c index 11658fca0da..bd5c9696134 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -1448,7 +1448,7 @@ allocate_executor(int exit_count, int length) res->trace = (_PyUOpInstruction *)(res->exits + exit_count); res->code_size = length; res->exit_count = exit_count; - res->jit_gdb_handle = NULL; + res->jit_registration = NULL; return res; }