From 083fbc29b2271f890699acba05a4e64cc9c4ab95 Mon Sep 17 00:00:00 2001 From: chyezh Date: Mon, 22 Jul 2024 13:13:05 +0800 Subject: [PATCH 1/2] enhance: wrap go signal up to handle the signal thrown by non-go thread - add backtrace.c for using backtrace_full call - add wrapped signal handler to handle SIGBUS/SIGFPE/SIGSEGV to see non-go thread throws Signed-off-by: chyezh --- backtrace.c | 123 ++++++++++++++++++++++++++ symbolizer.c | 237 ++++++++++++++++++++++++++++++++++---------------- symbolizer.go | 6 +- 3 files changed, 290 insertions(+), 76 deletions(-) create mode 100644 backtrace.c diff --git a/backtrace.c b/backtrace.c new file mode 100644 index 0000000..d21efeb --- /dev/null +++ b/backtrace.c @@ -0,0 +1,123 @@ +// +build !windows,!darwin + +/* backtrace.c -- Entry point for stack backtrace library. + Copyright (C) 2012-2024 Free Software Foundation, Inc. + Written by Ian Lance Taylor, Google. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + (1) Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + (2) Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + (3) The name of the author may not be used to + endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. */ + +#include "backtrace.h" + +#include + +#include "config.h" +#include "internal.h" +#include "unwind.h" + +/* The main backtrace_full routine. */ + +/* Data passed through _Unwind_Backtrace. */ + +struct backtrace_data { + /* Number of frames to skip. */ + int skip; + /* Library state. */ + struct backtrace_state *state; + /* Callback routine. */ + backtrace_full_callback callback; + /* Error callback routine. */ + backtrace_error_callback error_callback; + /* Data to pass to callback routines. */ + void *data; + /* Value to return from backtrace_full. */ + int ret; + /* Whether there is any memory available. */ + int can_alloc; +}; + +/* Unwind library callback routine. This is passed to + _Unwind_Backtrace. */ + +static _Unwind_Reason_Code unwind(struct _Unwind_Context *context, + void *vdata) { + struct backtrace_data *bdata = (struct backtrace_data *)vdata; + uintptr_t pc; + int ip_before_insn = 0; + +#ifdef HAVE_GETIPINFO + pc = _Unwind_GetIPInfo(context, &ip_before_insn); +#else + pc = _Unwind_GetIP(context); +#endif + + if (bdata->skip > 0) { + --bdata->skip; + return _URC_NO_REASON; + } + + if (!ip_before_insn) --pc; + + if (!bdata->can_alloc) + bdata->ret = bdata->callback(bdata->data, pc, NULL, 0, NULL); + else + bdata->ret = backtrace_pcinfo(bdata->state, pc, bdata->callback, + bdata->error_callback, bdata->data); + if (bdata->ret != 0) return _URC_END_OF_STACK; + + return _URC_NO_REASON; +} + +/* Get a stack backtrace. */ + +int __attribute__((noinline)) backtrace_full( + struct backtrace_state *state, int skip, backtrace_full_callback callback, + backtrace_error_callback error_callback, void *data) { + struct backtrace_data bdata; + void *p; + + bdata.skip = skip + 1; + bdata.state = state; + bdata.callback = callback; + bdata.error_callback = error_callback; + bdata.data = data; + bdata.ret = 0; + + /* If we can't allocate any memory at all, don't try to produce + file/line information. */ + p = backtrace_alloc(state, 4096, NULL, NULL); + if (p == NULL) + bdata.can_alloc = 0; + else { + backtrace_free(state, p, 4096, NULL, NULL); + bdata.can_alloc = 1; + } + + _Unwind_Backtrace(unwind, &bdata); + return bdata.ret; +} diff --git a/symbolizer.c b/symbolizer.c index 8a38a99..b96dc67 100644 --- a/symbolizer.c +++ b/symbolizer.c @@ -2,111 +2,198 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -#include "config.h" - +#include #include +#include +#include #include #include +#include +#include #include "backtrace.h" +#include "config.h" #include "internal.h" -static void createStateErrorCallback(void* data, const char* msg, int errnum) { -} +static void createStateErrorCallback(void* data, const char* msg, int errnum) {} -static struct backtrace_state *cgoBacktraceState; +static struct backtrace_state* cgoBacktraceState; // Initialize the backtrace state. void cgoSymbolizerInit(char* filename) { - cgoBacktraceState = backtrace_create_state(filename, 1, createStateErrorCallback, NULL); + cgoBacktraceState = + backtrace_create_state(filename, 1, createStateErrorCallback, NULL); } struct cgoSymbolizerArg { - uintptr_t pc; - const char* file; - uintptr_t lineno; - const char* func; - uintptr_t entry; - uintptr_t more; - uintptr_t data; + uintptr_t pc; + const char* file; + uintptr_t lineno; + const char* func; + uintptr_t entry; + uintptr_t more; + uintptr_t data; }; struct cgoSymbolizerMore { - struct cgoSymbolizerMore *more; + struct cgoSymbolizerMore* more; - const char* file; - uintptr_t lineno; - const char* func; + const char* file; + uintptr_t lineno; + const char* func; }; // Called via backtrace_pcinfo. -static int callback(void* data, uintptr_t pc, const char* filename, int lineno, const char* function) { - struct cgoSymbolizerArg* arg = (struct cgoSymbolizerArg*)(data); - struct cgoSymbolizerMore* more; - struct cgoSymbolizerMore** pp; - if (arg->file == NULL) { - arg->file = filename; - arg->lineno = lineno; - arg->func = function; - return 0; - } - more = backtrace_alloc(cgoBacktraceState, sizeof(*more), NULL, NULL); - if (more == NULL) { - return 1; - } - more->more = NULL; - more->file = filename; - more->lineno = lineno; - more->func = function; - for (pp = (struct cgoSymbolizerMore**)(&arg->data); *pp != NULL; pp = &(*pp)->more) { - } - *pp = more; - arg->more = 1; - return 0; +static int callback(void* data, uintptr_t pc, const char* filename, int lineno, + const char* function) { + struct cgoSymbolizerArg* arg = (struct cgoSymbolizerArg*)(data); + struct cgoSymbolizerMore* more; + struct cgoSymbolizerMore** pp; + if (arg->file == NULL) { + arg->file = filename; + arg->lineno = lineno; + arg->func = function; + return 0; + } + more = backtrace_alloc(cgoBacktraceState, sizeof(*more), NULL, NULL); + if (more == NULL) { + return 1; + } + more->more = NULL; + more->file = filename; + more->lineno = lineno; + more->func = function; + for (pp = (struct cgoSymbolizerMore**)(&arg->data); *pp != NULL; + pp = &(*pp)->more) { + } + *pp = more; + arg->more = 1; + return 0; } // Called via backtrace_pcinfo. // Just ignore errors and let the caller indicate missing information. -static void errorCallback(void* data, const char* msg, int errnum) { -} +static void errorCallback(void* data, const char* msg, int errnum) {} // Called via backtrace_syminfo. // Just set the entry field. -static void syminfoCallback(void* data, uintptr_t pc, const char* symname, uintptr_t symval, uintptr_t symsize) { - struct cgoSymbolizerArg* arg = (struct cgoSymbolizerArg*)(data); - arg->entry = symval; +static void syminfoCallback(void* data, uintptr_t pc, const char* symname, + uintptr_t symval, uintptr_t symsize) { + struct cgoSymbolizerArg* arg = (struct cgoSymbolizerArg*)(data); + arg->entry = symval; } // For the details of how this is called see runtime.SetCgoTraceback. void cgoSymbolizer(void* parg) { - struct cgoSymbolizerArg* arg = (struct cgoSymbolizerArg*)(parg); - if (arg->data != 0) { - struct cgoSymbolizerMore* more = (struct cgoSymbolizerMore*)(arg->data); - arg->file = more->file; - arg->lineno = more->lineno; - arg->func = more->func; - arg->more = more->more != NULL; - arg->data = (uintptr_t)(more->more); - - // If returning the last file/line, we can set the - // entry point field. - if (!arg->more) { - backtrace_syminfo(cgoBacktraceState, arg->pc, syminfoCallback, errorCallback, (void*)arg); - } - - return; - } - arg->file = NULL; - arg->lineno = 0; - arg->func = NULL; - arg->more = 0; - if (cgoBacktraceState == NULL || arg->pc == 0) { - return; - } - backtrace_pcinfo(cgoBacktraceState, arg->pc, callback, errorCallback, (void*)(arg)); - - // If returning only one file/line, we can set the entry point field. - if (!arg->more) { - backtrace_syminfo(cgoBacktraceState, arg->pc, syminfoCallback, errorCallback, (void*)arg); - } + struct cgoSymbolizerArg* arg = (struct cgoSymbolizerArg*)(parg); + if (arg->data != 0) { + struct cgoSymbolizerMore* more = (struct cgoSymbolizerMore*)(arg->data); + arg->file = more->file; + arg->lineno = more->lineno; + arg->func = more->func; + arg->more = more->more != NULL; + arg->data = (uintptr_t)(more->more); + + // If returning the last file/line, we can set the + // entry point field. + if (!arg->more) { + backtrace_syminfo(cgoBacktraceState, arg->pc, syminfoCallback, + errorCallback, (void*)arg); + } + + return; + } + arg->file = NULL; + arg->lineno = 0; + arg->func = NULL; + arg->more = 0; + if (cgoBacktraceState == NULL || arg->pc == 0) { + return; + } + backtrace_pcinfo(cgoBacktraceState, arg->pc, callback, errorCallback, + (void*)(arg)); + + // If returning only one file/line, we can set the entry point field. + if (!arg->more) { + backtrace_syminfo(cgoBacktraceState, arg->pc, syminfoCallback, + errorCallback, (void*)arg); + } +} + +void backtraceErrorCallback(void* data, const char* msg, int errnum) { + printf("Error %d occurred when getting the stacktrace: %s", errnum, msg); +} + +int backtraceCallback(void* data, uintptr_t pc, const char* filename, int lineno, + const char* function) { + printf("%s\n\t%s:%d pc=0x%lx\n", function, filename, lineno, pc); + return 0; +} + +void printBacktrace(int signo, siginfo_t* info, void* context) { + if (!cgoBacktraceState) { + perror("cgoBacktraceState is not initialized"); + return; + } + ucontext_t* uc = (ucontext_t*)(context); + + printf("\nSIGNAL CATCH BY NON-GO SIGNAL HANDLER\n"); + printf("SIGNO: %d; SIGNAME: %s; SI_CODE: %d; SI_ADDR: %p\nBACKTRACE:\n", + signo, strsignal(signo), info->si_code, info->si_addr); + // skip the wrapHandler, sigaction. + backtrace_full(cgoBacktraceState, 3, backtraceCallback, + backtraceErrorCallback, NULL); + printf("\n\n"); +} + +#define MAX_SIGNAL 32 + +static void (*oldSignalHandler[MAX_SIGNAL])(int signo, siginfo_t* info, + void* context); + +static void wrapHandler(int signo, siginfo_t* info, void* context) { + printBacktrace(signo, info, context); + oldSignalHandler[signo](signo, info, context); +} + +void cgoInstallNonGoHandlerForSignum(int signum) { + if (signum >= MAX_SIGNAL) { + perror("signum is too large"); + } + + // get old signal handler, it should be a default go signal handler. + struct sigaction old_action; + memset(&old_action, 0, sizeof(old_action)); + sigemptyset(&old_action.sa_mask); + if (sigaction(signum, NULL, &old_action) == -1) { + perror("get old signal handler failed"); + exit(EXIT_FAILURE); + } + + // check if the old signal handler is setted + if (old_action.sa_handler == SIG_DFL) { + fprintf(stderr, + "Go runtime signal handler is not setted, please set it first\n"); + exit(EXIT_FAILURE); + } + + // check if SA_ONSTACK is setted. + if (old_action.sa_flags & SA_ONSTACK == 0) { + fprintf(stderr, "Go runtime signal handler is setted with SA_ONSTACK\n"); + exit(EXIT_FAILURE); + } + + oldSignalHandler[signum] = old_action.sa_sigaction; + old_action.sa_sigaction = &wrapHandler; + + if (sigaction(signum, &old_action, NULL) == -1) { + perror("set up new signal handler failed"); + exit(EXIT_FAILURE); + } +} + +void cgoInstallNonGoHandler() { + cgoInstallNonGoHandlerForSignum(SIGSEGV); + cgoInstallNonGoHandlerForSignum(SIGBUS); + cgoInstallNonGoHandlerForSignum(SIGFPE); } diff --git a/symbolizer.go b/symbolizer.go index 652aec4..6d21d7d 100644 --- a/symbolizer.go +++ b/symbolizer.go @@ -6,13 +6,16 @@ // This will be used to provide a symbolic backtrace of cgo functions. // This package does not export any symbols. // To use it, add a line like -// import _ "github.com/ianlancetaylor/cgosymbolizer" +// +// import _ "github.com/ianlancetaylor/cgosymbolizer" +// // somewhere in your program. package cgosymbolizer // extern void cgoSymbolizerInit(char*); // extern void cgoTraceback(void*); // extern void cgoSymbolizer(void*); +// extern void cgoInstallNonGoHandler(); import "C" import ( @@ -23,5 +26,6 @@ import ( func init() { C.cgoSymbolizerInit(C.CString(os.Args[0])) + C.cgoInstallNonGoHandler() runtime.SetCgoTraceback(0, unsafe.Pointer(C.cgoTraceback), nil, unsafe.Pointer(C.cgoSymbolizer)) } From ce12e7c777dc7d9e7f00c06d68f658d52fe8263d Mon Sep 17 00:00:00 2001 From: chyezh Date: Mon, 22 Jul 2024 15:18:10 +0800 Subject: [PATCH 2/2] fix: flush the stdout and stderr after backtrace Signed-off-by: chyezh --- symbolizer.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/symbolizer.c b/symbolizer.c index b96dc67..28b2003 100644 --- a/symbolizer.c +++ b/symbolizer.c @@ -121,12 +121,13 @@ void cgoSymbolizer(void* parg) { } void backtraceErrorCallback(void* data, const char* msg, int errnum) { - printf("Error %d occurred when getting the stacktrace: %s", errnum, msg); + fprintf(stderr, "Error %d occurred when getting the stacktrace: %s", errnum, + msg); } -int backtraceCallback(void* data, uintptr_t pc, const char* filename, int lineno, - const char* function) { - printf("%s\n\t%s:%d pc=0x%lx\n", function, filename, lineno, pc); +int backtraceCallback(void* data, uintptr_t pc, const char* filename, + int lineno, const char* function) { + fprintf(stderr, "%s\n\t%s:%d pc=0x%lx\n", function, filename, lineno, pc); return 0; } @@ -137,13 +138,15 @@ void printBacktrace(int signo, siginfo_t* info, void* context) { } ucontext_t* uc = (ucontext_t*)(context); - printf("\nSIGNAL CATCH BY NON-GO SIGNAL HANDLER\n"); - printf("SIGNO: %d; SIGNAME: %s; SI_CODE: %d; SI_ADDR: %p\nBACKTRACE:\n", - signo, strsignal(signo), info->si_code, info->si_addr); + fprintf(stderr, "\nSIGNAL CATCH BY NON-GO SIGNAL HANDLER\n"); + fprintf(stderr, + "SIGNO: %d; SIGNAME: %s; SI_CODE: %d; SI_ADDR: %p\nBACKTRACE:\n", + signo, strsignal(signo), info->si_code, info->si_addr); // skip the wrapHandler, sigaction. backtrace_full(cgoBacktraceState, 3, backtraceCallback, backtraceErrorCallback, NULL); - printf("\n\n"); + fprintf(stderr, "\n\n"); + fflush(stderr); } #define MAX_SIGNAL 32