From ea79d7d04e940e70fa5b63f1be5b5272d287677c Mon Sep 17 00:00:00 2001 From: Peter Eisentraut Date: Tue, 18 Jun 2019 00:36:20 +0200 Subject: [PATCH v2] Add errbacktrace() and backtrace_function GUC --- configure | 101 ++++++++++++++++++++++++++++- configure.in | 17 +++++ src/Makefile.global.in | 1 + src/backend/utils/error/elog.c | 115 +++++++++++++++++++++++++++++++++ src/backend/utils/misc/guc.c | 10 +++ src/include/pg_config.h.in | 9 +++ src/include/utils/elog.h | 3 + src/include/utils/guc.h | 1 + 8 files changed, 255 insertions(+), 2 deletions(-) diff --git a/configure b/configure index 8d47071e4a..a8394ecfd9 100755 --- a/configure +++ b/configure @@ -710,6 +710,7 @@ with_uuid with_systemd with_selinux with_openssl +with_libunwind with_ldap with_krb_srvnam krb_srvtab @@ -853,6 +854,7 @@ with_pam with_bsd_auth with_ldap with_bonjour +with_libunwind with_openssl with_selinux with_systemd @@ -1557,6 +1559,7 @@ Optional Packages: --with-bsd-auth build with BSD Authentication support --with-ldap build with LDAP support --with-bonjour build with Bonjour support + --with-libunwind build with libunwind support --with-openssl build with OpenSSL support --with-selinux build with SELinux support --with-systemd build with systemd support @@ -7861,6 +7864,39 @@ fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_bonjour" >&5 $as_echo "$with_bonjour" >&6; } +# +# libunwind +# +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build with libunwind" >&5 +$as_echo_n "checking whether to build with libunwind... " >&6; } + + + +# Check whether --with-libunwind was given. +if test "${with_libunwind+set}" = set; then : + withval=$with_libunwind; + case $withval in + yes) + +$as_echo "#define USE_LIBUNWIND 1" >>confdefs.h + + ;; + no) + : + ;; + *) + as_fn_error $? "no argument expected for --with-libunwind option" "$LINENO" 5 + ;; + esac + +else + with_libunwind=no + +fi + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_libunwind" >&5 +$as_echo "$with_libunwind" >&6; } # # OpenSSL @@ -12224,6 +12260,67 @@ fi fi +if test "$with_libunwind" = yes; then + # On macOS, the library does not exist, the functionality is built + # into the OS. + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing unw_getcontext" >&5 +$as_echo_n "checking for library containing unw_getcontext... " >&6; } +if ${ac_cv_search_unw_getcontext+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_func_search_save_LIBS=$LIBS +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char unw_getcontext (); +int +main () +{ +return unw_getcontext (); + ; + return 0; +} +_ACEOF +for ac_lib in '' unwind; do + if test -z "$ac_lib"; then + ac_res="none required" + else + ac_res=-l$ac_lib + LIBS="-l$ac_lib $ac_func_search_save_LIBS" + fi + if ac_fn_c_try_link "$LINENO"; then : + ac_cv_search_unw_getcontext=$ac_res +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext + if ${ac_cv_search_unw_getcontext+:} false; then : + break +fi +done +if ${ac_cv_search_unw_getcontext+:} false; then : + +else + ac_cv_search_unw_getcontext=no +fi +rm conftest.$ac_ext +LIBS=$ac_func_search_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_unw_getcontext" >&5 +$as_echo "$ac_cv_search_unw_getcontext" >&6; } +ac_res=$ac_cv_search_unw_getcontext +if test "$ac_res" != no; then : + test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" + +fi + +fi + if test "$with_libxml" = yes ; then { $as_echo "$as_me:${as_lineno-$LINENO}: checking for xmlSaveToBuffer in -lxml2" >&5 $as_echo_n "checking for xmlSaveToBuffer in -lxml2... " >&6; } @@ -12793,7 +12890,7 @@ $as_echo "#define HAVE_STDBOOL_H 1" >>confdefs.h fi -for ac_header in atomic.h copyfile.h crypt.h fp_class.h getopt.h ieeefp.h ifaddrs.h langinfo.h mbarrier.h poll.h sys/epoll.h sys/ipc.h sys/prctl.h sys/procctl.h sys/pstat.h sys/resource.h sys/select.h sys/sem.h sys/shm.h sys/sockio.h sys/tas.h sys/un.h termios.h ucred.h utime.h wchar.h wctype.h +for ac_header in atomic.h copyfile.h crypt.h execinfo.h fp_class.h getopt.h ieeefp.h ifaddrs.h langinfo.h mbarrier.h poll.h sys/epoll.h sys/ipc.h sys/prctl.h sys/procctl.h sys/pstat.h sys/resource.h sys/select.h sys/sem.h sys/shm.h sys/sockio.h sys/tas.h sys/un.h termios.h ucred.h utime.h wchar.h wctype.h do : as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh` ac_fn_c_check_header_mongrel "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default" @@ -15176,7 +15273,7 @@ fi LIBS_including_readline="$LIBS" LIBS=`echo "$LIBS" | sed -e 's/-ledit//g' -e 's/-lreadline//g'` -for ac_func in cbrt clock_gettime copyfile fdatasync getifaddrs getpeerucred getrlimit mbstowcs_l memmove poll posix_fallocate ppoll pstat pthread_is_threaded_np readlink setproctitle setproctitle_fast setsid shm_open strchrnul strsignal symlink sync_file_range uselocale utime utimes wcstombs_l +for ac_func in backtrace_symbols cbrt clock_gettime copyfile fdatasync getifaddrs getpeerucred getrlimit mbstowcs_l memmove poll posix_fallocate ppoll pstat pthread_is_threaded_np readlink setproctitle setproctitle_fast setsid shm_open strchrnul strsignal symlink sync_file_range uselocale utime utimes wcstombs_l do : as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh` ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var" diff --git a/configure.in b/configure.in index 74938d4190..1349daf54a 100644 --- a/configure.in +++ b/configure.in @@ -831,6 +831,15 @@ PGAC_ARG_BOOL(with, bonjour, no, [AC_DEFINE([USE_BONJOUR], 1, [Define to 1 to build with Bonjour support. (--with-bonjour)])]) AC_MSG_RESULT([$with_bonjour]) +# +# libunwind +# +AC_MSG_CHECKING([whether to build with libunwind]) +PGAC_ARG_BOOL(with, libunwind, no, + [build with libunwind support], + [AC_DEFINE([USE_LIBUNWIND], 1, [Define to 1 to build with libunwind support. (--with-libunwind)])]) +AC_MSG_RESULT([$with_libunwind]) +AC_SUBST(with_libunwind)[]dnl # # OpenSSL @@ -1223,6 +1232,12 @@ if test "$with_pam" = yes ; then AC_CHECK_LIB(pam, pam_start, [], [AC_MSG_ERROR([library 'pam' is required for PAM])]) fi +if test "$with_libunwind" = yes; then + # On macOS, the library does not exist, the functionality is built + # into the OS. + AC_SEARCH_LIBS(unw_getcontext, [unwind]) +fi + if test "$with_libxml" = yes ; then AC_CHECK_LIB(xml2, xmlSaveToBuffer, [], [AC_MSG_ERROR([library 'xml2' (version >= 2.6.23) is required for XML support])]) fi @@ -1298,6 +1313,7 @@ AC_CHECK_HEADERS(m4_normalize([ atomic.h copyfile.h crypt.h + execinfo.h fp_class.h getopt.h ieeefp.h @@ -1612,6 +1628,7 @@ LIBS_including_readline="$LIBS" LIBS=`echo "$LIBS" | sed -e 's/-ledit//g' -e 's/-lreadline//g'` AC_CHECK_FUNCS(m4_normalize([ + backtrace_symbols cbrt clock_gettime copyfile diff --git a/src/Makefile.global.in b/src/Makefile.global.in index b9d86acaa9..6e14a4b217 100644 --- a/src/Makefile.global.in +++ b/src/Makefile.global.in @@ -190,6 +190,7 @@ with_systemd = @with_systemd@ with_gssapi = @with_gssapi@ with_krb_srvnam = @with_krb_srvnam@ with_ldap = @with_ldap@ +with_libunwind = @with_libunwind@ with_libxml = @with_libxml@ with_libxslt = @with_libxslt@ with_llvm = @with_llvm@ diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c index 8b4720ef3a..901287c335 100644 --- a/src/backend/utils/error/elog.c +++ b/src/backend/utils/error/elog.c @@ -62,6 +62,12 @@ #ifdef HAVE_SYSLOG #include #endif +#ifdef HAVE_EXECINFO_H +#include +#endif +#ifdef USE_LIBUNWIND +#include +#endif #include "access/transam.h" #include "access/xact.h" @@ -166,6 +172,7 @@ static char formatted_log_time[FORMATTED_TS_LEN]; } while (0) +static void set_backtrace(ErrorData *edata, int num_skip); static const char *err_gettext(const char *str) pg_attribute_format_arg(1); static void set_errdata_field(MemoryContextData *cxt, char **ptr, const char *str); static void write_console(const char *line, int len); @@ -424,6 +431,12 @@ errfinish(int dummy,...) */ oldcontext = MemoryContextSwitchTo(ErrorContext); + if (!edata->backtrace && + edata->funcname && + backtrace_function[0] && + strcmp(backtrace_function, edata->funcname) == 0) + set_backtrace(edata, 2); + /* * Call any context callback functions. Errors occurring in callback * functions will be treated as recursive errors --- this ensures we will @@ -488,6 +501,8 @@ errfinish(int dummy,...) pfree(edata->hint); if (edata->context) pfree(edata->context); + if (edata->backtrace) + pfree(edata->backtrace); if (edata->schema_name) pfree(edata->schema_name); if (edata->table_name) @@ -798,6 +813,85 @@ errmsg(const char *fmt,...) return 0; /* return value does not matter */ } +int +errbacktrace(void) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + MemoryContext oldcontext; + + recursion_depth++; + CHECK_STACK_DEPTH(); + oldcontext = MemoryContextSwitchTo(edata->assoc_context); + + set_backtrace(edata, 1); + + MemoryContextSwitchTo(oldcontext); + recursion_depth--; + + return 0; +} + +static void +set_backtrace(ErrorData *edata, int num_skip) +{ + StringInfoData errtrace; + + initStringInfo(&errtrace); + +#ifdef USE_LIBUNWIND + { + unw_context_t context; + unw_cursor_t cursor; + int n = 0; + + unw_getcontext(&context); + unw_init_local(&cursor, &context); + + while (unw_step(&cursor)) + { + unw_word_t ip, off; + char symbol[256] = ""; + + if (n < num_skip - 1) + { + n++; + continue; + } + + unw_get_reg(&cursor, UNW_REG_IP, &ip); + unw_get_proc_name(&cursor, symbol, sizeof(symbol), &off); + + appendStringInfo(&errtrace, + "#%-2d %s+0x%llx [0x%016llx]\n", + n - 1, + symbol, + (unsigned long long) off, + (unsigned long long) ip); + + n++; + } + } +#elif defined(HAVE_EXECINFO_H) && defined(HAVE_BACKTRACE_SYMBOLS) + { + void *buf[100]; + int nframes; + char **strfrms; + + nframes = backtrace(buf, sizeof(buf)); + strfrms = backtrace_symbols(buf, nframes); + if (strfrms == NULL) + return; + + for (int i = num_skip; i < nframes; i++) + appendStringInfo(&errtrace, "%s\n", strfrms[i]); + free(strfrms); + } +#else + appendStringInfoString(&errtrace, "backtrace not supported"); +#endif + + edata->backtrace = errtrace.data; +} /* * errmsg_internal --- add a primary error message text to the current error @@ -1353,6 +1447,12 @@ elog_finish(int elevel, const char *fmt,...) recursion_depth++; oldcontext = MemoryContextSwitchTo(edata->assoc_context); + if (!edata->backtrace && + edata->funcname && + backtrace_function[0] && + strcmp(backtrace_function, edata->funcname) == 0) + set_backtrace(edata, 2); + edata->message_id = fmt; EVALUATE_MESSAGE(edata->domain, message, false, false); @@ -1509,6 +1609,8 @@ CopyErrorData(void) newedata->hint = pstrdup(newedata->hint); if (newedata->context) newedata->context = pstrdup(newedata->context); + if (newedata->backtrace) + newedata->backtrace = pstrdup(newedata->backtrace); if (newedata->schema_name) newedata->schema_name = pstrdup(newedata->schema_name); if (newedata->table_name) @@ -1547,6 +1649,8 @@ FreeErrorData(ErrorData *edata) pfree(edata->hint); if (edata->context) pfree(edata->context); + if (edata->backtrace) + pfree(edata->backtrace); if (edata->schema_name) pfree(edata->schema_name); if (edata->table_name) @@ -1622,6 +1726,8 @@ ThrowErrorData(ErrorData *edata) newedata->hint = pstrdup(edata->hint); if (edata->context) newedata->context = pstrdup(edata->context); + if (edata->backtrace) + newedata->backtrace = pstrdup(edata->backtrace); /* assume message_id is not available */ if (edata->schema_name) newedata->schema_name = pstrdup(edata->schema_name); @@ -1689,6 +1795,8 @@ ReThrowError(ErrorData *edata) newedata->hint = pstrdup(newedata->hint); if (newedata->context) newedata->context = pstrdup(newedata->context); + if (newedata->backtrace) + newedata->backtrace = pstrdup(newedata->backtrace); if (newedata->schema_name) newedata->schema_name = pstrdup(newedata->schema_name); if (newedata->table_name) @@ -2914,6 +3022,13 @@ send_message_to_server_log(ErrorData *edata) append_with_tabs(&buf, edata->context); appendStringInfoChar(&buf, '\n'); } + if (edata->backtrace) + { + log_line_prefix(&buf, edata); + appendStringInfoString(&buf, _("BACKTRACE: ")); + append_with_tabs(&buf, edata->backtrace); + appendStringInfoChar(&buf, '\n'); + } if (Log_error_verbosity >= PGERROR_VERBOSE) { /* assume no newlines in funcname or filename... */ diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 1208eb9a68..328e5b6699 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -514,6 +514,7 @@ int log_temp_files = -1; double log_statement_sample_rate = 1.0; double log_xact_sample_rate = 0; int trace_recovery_messages = LOG; +char *backtrace_function; int temp_file_limit = -1; @@ -4213,6 +4214,15 @@ static struct config_string ConfigureNamesString[] = NULL, NULL, NULL }, + { + {"backtrace_function", PGC_SUSET, DEVELOPER_OPTIONS, + gettext_noop("Log backtrace for errors in this function."), + }, + &backtrace_function, + "", + NULL, NULL, NULL + }, + /* End-of-list marker */ { {NULL, 0, 0, NULL, NULL}, NULL, NULL, NULL, NULL, NULL diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in index 6cd4cfed0a..6959ce6c61 100644 --- a/src/include/pg_config.h.in +++ b/src/include/pg_config.h.in @@ -96,6 +96,9 @@ /* Define to 1 if you have the header file. */ #undef HAVE_ATOMIC_H +/* Define to 1 if you have the `backtrace_symbols' function. */ +#undef HAVE_BACKTRACE_SYMBOLS + /* Define to 1 if you have the `BIO_get_data' function. */ #undef HAVE_BIO_GET_DATA @@ -201,6 +204,9 @@ /* Define to 1 if you have the header file. */ #undef HAVE_EDITLINE_READLINE_H +/* Define to 1 if you have the header file. */ +#undef HAVE_EXECINFO_H + /* Define to 1 if you have the `fdatasync' function. */ #undef HAVE_FDATASYNC @@ -922,6 +928,9 @@ /* Define to 1 to build with LDAP support. (--with-ldap) */ #undef USE_LDAP +/* Define to 1 to build with libunwind support. (--with-libunwind) */ +#undef USE_LIBUNWIND + /* Define to 1 to build with XML support. (--with-libxml) */ #undef USE_LIBXML diff --git a/src/include/utils/elog.h b/src/include/utils/elog.h index dbfd8efd26..b58aee792a 100644 --- a/src/include/utils/elog.h +++ b/src/include/utils/elog.h @@ -189,6 +189,8 @@ extern int errcontext_msg(const char *fmt,...) pg_attribute_printf(1, 2); extern int errhidestmt(bool hide_stmt); extern int errhidecontext(bool hide_ctx); +extern int errbacktrace(void); + extern int errfunction(const char *funcname); extern int errposition(int cursorpos); @@ -362,6 +364,7 @@ typedef struct ErrorData char *detail_log; /* detail error message for server log only */ char *hint; /* hint message */ char *context; /* context message */ + char *backtrace; /* backtrace */ const char *message_id; /* primary message's id (original string) */ char *schema_name; /* name of schema */ char *table_name; /* name of table */ diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h index e709177c37..032f4f16ab 100644 --- a/src/include/utils/guc.h +++ b/src/include/utils/guc.h @@ -255,6 +255,7 @@ extern int log_min_duration_statement; extern int log_temp_files; extern double log_statement_sample_rate; extern double log_xact_sample_rate; +extern char *backtrace_function; extern int temp_file_limit; -- 2.22.0