New thread continuing from
<https://www.postgresql.org/message-id/d4903af2-e7b7-b551-71f8-3e4a6bdc2...@2ndquadrant.com>.

Here is a extended version of Álvaro's patch that adds an errbacktrace()
function.  You can do two things with this:

- Manually attach it to an ereport() call site that you want to debug.

- Set a configuration parameter like backtrace_function = 'int8in' to
debug ereport()/elog() calls in a specific function.

There was also mention of settings that would automatically produce
backtraces for PANICs etc.  Those could surely be added if there is
enough interest.

For the implementation, I support both backtrace() provided by the OS as
well as using libunwind.  The former seems to be supported by a number
of platforms, including glibc, macOS, and FreeBSD, so maybe we don't
need the libunwind suport.  I haven't found any difference in quality in
the backtraces between the two approaches, but surely that is highly
dependent on the exact configuration.

I would welcome testing in all direction with this, to see how well it
works in different circumstances.

-- 
Peter Eisentraut              http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
From ea79d7d04e940e70fa5b63f1be5b5272d287677c Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <pe...@eisentraut.org>
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 <syslog.h>
 #endif
+#ifdef HAVE_EXECINFO_H
+#include <execinfo.h>
+#endif
+#ifdef USE_LIBUNWIND
+#include <libunwind.h>
+#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] = "<unknown>";
+
+                       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 <atomic.h> 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 <editline/readline.h> header file. */
 #undef HAVE_EDITLINE_READLINE_H
 
+/* Define to 1 if you have the <execinfo.h> 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

Reply via email to