From: Peter Eisentraut <peter.eisentraut@2ndquadrant.com>
To: pgsql-hackers <pgsql-hackers@postgresql.org>
Cc: Alvaro Herrera <alvherre@2ndquadrant.com>
Cc: Dmitry Dolgov <9erthalion6@gmail.com>
Cc: Thomas Munro <thomas.munro@gmail.com>
Subject: Re: errbacktrace
Date: Tue, 13 Aug 2019 10:12:10 +0200
Message-ID: <35beac83-bf15-9d79-05c4-2dccd0834993@2ndquadrant.com> (raw)
In-Reply-To: <3814a094-7dcd-106b-d2ad-7ef78f563356@2ndquadrant.com>
References: <20190708162851.GA11054@alvherre.pgsql>
<de6cb49b-1969-66f5-ac71-fbff5fd3be25@2ndquadrant.com>
<3814a094-7dcd-106b-d2ad-7ef78f563356@2ndquadrant.com>
On 2019-07-22 20:19, Peter Eisentraut wrote:
> On 2019-07-09 11:43, Peter Eisentraut wrote:
>> After further research I'm thinking about dropping the libunwind
>> support. The backtrace()/backtrace_symbols() API is more widely
>> available: darwin, freebsd, linux, netbsd, openbsd (via port), solaris,
>> and of course it's built-in, whereas libunwind is only available for
>> linux, freebsd, hpux, solaris, and requires an external dependency.
>
> Here is an updated patch without the libunwind support, some minor
> cleanups, documentation, and automatic back traces from assertion failures.
Another updated version.
I have changed the configuration setting to backtrace_functions plural,
so that you can debug more than one location at once. I had originally
wanted to do that but using existing functions like
SplitIdentifierString() resulted in lots of complications with error
handling (inside error handling!). So here I just hand-coded the list
splitting. Seems simple enough.
I think this patch is now good to go from my perspective.
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
From f9d440ec41f49464661960c580c29ce0558a1642 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Tue, 13 Aug 2019 01:47:22 +0200
Subject: [PATCH v4] Add backtrace support
Add some support for automatically showing backtraces in certain error
situations in the server. Backtraces are shown on assertion failure.
A new setting backtrace_functions can be set to a list of C function
names, and all ereport()s and elog()s from the functions will have
backtraces generated. Finally, the function errbacktrace() can be
manually added to an ereport() call to generate a backtrace for that
call.
Discussion: https://www.postgresql.org/message-id/flat/5f48cb47-bf1e-05b6-7aae-3bf2cd01586d@2ndquadrant.com
---
configure | 61 +++++++++++++++-
configure.in | 4 ++
doc/src/sgml/config.sgml | 26 +++++++
src/backend/utils/error/assert.c | 13 ++++
src/backend/utils/error/elog.c | 115 +++++++++++++++++++++++++++++++
src/backend/utils/misc/guc.c | 12 ++++
src/include/pg_config.h.in | 6 ++
src/include/utils/elog.h | 3 +
src/include/utils/guc.h | 1 +
9 files changed, 239 insertions(+), 2 deletions(-)
diff --git a/configure b/configureindex 7a6bfc2339..fbd8224f50 100755--- a/configure+++ b/configure@@ -11662,6 +11662,63 @@ if test "$ac_res" != no; then :
fi
+# *BSD:+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing backtrace_symbols" >&5+$as_echo_n "checking for library containing backtrace_symbols... " >&6; }+if ${ac_cv_search_backtrace_symbols+:} 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 backtrace_symbols ();+int+main ()+{+return backtrace_symbols ();+ ;+ return 0;+}+_ACEOF+for ac_lib in '' execinfo; 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_backtrace_symbols=$ac_res+fi+rm -f core conftest.err conftest.$ac_objext \+ conftest$ac_exeext+ if ${ac_cv_search_backtrace_symbols+:} false; then :+ break+fi+done+if ${ac_cv_search_backtrace_symbols+:} false; then :++else+ ac_cv_search_backtrace_symbols=no+fi+rm conftest.$ac_ext+LIBS=$ac_func_search_save_LIBS+fi+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_backtrace_symbols" >&5+$as_echo "$ac_cv_search_backtrace_symbols" >&6; }+ac_res=$ac_cv_search_backtrace_symbols+if test "$ac_res" != no; then :+ test "$ac_res" = "none required" || LIBS="$ac_res $LIBS"++fi+
if test "$with_readline" = yes; then
@@ -12760,7 +12817,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"
@@ -15143,7 +15200,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.inindex dde3eec89f..f0c5ec9f47 100644--- a/configure.in+++ b/configure.in@@ -1131,6 +1131,8 @@ AC_SEARCH_LIBS(sched_yield, rt)
AC_SEARCH_LIBS(gethostbyname_r, nsl)
# Cygwin:
AC_SEARCH_LIBS(shmget, cygipc)
+# *BSD:+AC_SEARCH_LIBS(backtrace_symbols, execinfo)
if test "$with_readline" = yes; then
PGAC_CHECK_READLINE
@@ -1274,6 +1276,7 @@ AC_CHECK_HEADERS(m4_normalize([
atomic.h
copyfile.h
crypt.h
+ execinfo.h
fp_class.h
getopt.h
ieeefp.h
@@ -1588,6 +1591,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/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgmlindex cdc30fa5e3..10e6c80ac1 100644--- a/doc/src/sgml/config.sgml+++ b/doc/src/sgml/config.sgml@@ -9285,6 +9285,32 @@ <title>Developer Options</title>
</listitem>
</varlistentry>
+ <varlistentry id="guc-backtrace-functions" xreflabel="backtrace_functions">+ <term><varname>backtrace_functions</varname> (<type>string</type>)+ <indexterm>+ <primary><varname>backtrace_functions</varname> configuration parameter</primary>+ </indexterm>+ </term>+ <listitem>+ <para>+ This parameter contains a comma-separated list of C function names.+ If an error is raised and the name of the internal C function where+ the error happens matches a value in the list, then a backtrace is+ written to the server log together with the error message. This can+ be used to debug specific areas of the source code.+ </para>++ <para>+ Backtrace support is not available on all platforms, and the quality+ of the backtraces depends on compilation options.+ </para>++ <para>+ This parameter can only be set by superusers.+ </para>+ </listitem>+ </varlistentry>+
<varlistentry id="guc-ignore-system-indexes" xreflabel="ignore_system_indexes">
<term><varname>ignore_system_indexes</varname> (<type>boolean</type>)
<indexterm>
diff --git a/src/backend/utils/error/assert.c b/src/backend/utils/error/assert.cindex 2050b4355d..14707fc296 100644--- a/src/backend/utils/error/assert.c+++ b/src/backend/utils/error/assert.c@@ -18,6 +18,9 @@
#include "postgres.h"
#include <unistd.h>
+#ifdef HAVE_EXECINFO_H+#include <execinfo.h>+#endif
/*
* ExceptionalCondition - Handles the failure of an Assert()
@@ -42,6 +45,16 @@ ExceptionalCondition(const char *conditionName,
/* Usually this shouldn't be needed, but make sure the msg went out */
fflush(stderr);
+#ifdef HAVE_BACKTRACE_SYMBOLS+ {+ void *buf[100];+ int nframes;++ nframes = backtrace(buf, sizeof(buf));+ backtrace_symbols_fd(buf, nframes, fileno(stderr));+ }+#endif+
#ifdef SLEEP_ON_ASSERT
/*
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.cindex 8b4720ef3a..12a37e4ac5 100644--- a/src/backend/utils/error/elog.c+++ b/src/backend/utils/error/elog.c@@ -62,6 +62,9 @@
#ifdef HAVE_SYSLOG
#include <syslog.h>
#endif
+#ifdef HAVE_EXECINFO_H+#include <execinfo.h>+#endif
#include "access/transam.h"
#include "access/xact.h"
@@ -166,6 +169,7 @@ static char formatted_log_time[FORMATTED_TS_LEN];
} while (0)
+static pg_noinline 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);
@@ -398,6 +402,33 @@ errstart(int elevel, const char *filename, int lineno,
return true;
}
+/*+ * Checks whether the given funcname matches the comma-separated list in backtrace_functions.+ *+ * This is coded separately here instead of using an existing list-splitting+ * function so that we can control the error behavior carefully, since this is+ * called during error processing.+ */+static bool+matches_backtrace_functions(const char *funcname)+{+ char *setting = strdup(backtrace_functions);++ if (!setting)+ return false;++ for (char *token = strtok(setting, ","); token; token = strtok(NULL, ","))+ {+ if (strcmp(token, funcname) == 0)+ {+ free(setting);+ return true;+ }+ }+ free(setting);+ return false;+}+
/*
* errfinish --- end an error-reporting cycle
*
@@ -424,6 +455,12 @@ errfinish(int dummy,...)
*/
oldcontext = MemoryContextSwitchTo(ErrorContext);
+ if (!edata->backtrace &&+ edata->funcname &&+ backtrace_functions &&+ matches_backtrace_functions(edata->funcname))+ 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 +525,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 +837,62 @@ errmsg(const char *fmt,...)
return 0; /* return value does not matter */
}
+/*+ * Add a backtrace to the containing ereport() call. This is intended to be+ * added temporarily during debugging.+ */+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;+}++/*+ * Compute backtrace data and add it to the supplied ErrorData. num_skip+ * specifies how many inner frames to skip. Use this to avoid showing the+ * internal backtrace support functions in the backtrace. This requires that+ * this and related functions are not inlined.+ */+static void+set_backtrace(ErrorData *edata, int num_skip)+{+ StringInfoData errtrace;++ initStringInfo(&errtrace);++#ifdef 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, "\n%s", 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 +1448,11 @@ elog_finish(int elevel, const char *fmt,...)
recursion_depth++;
oldcontext = MemoryContextSwitchTo(edata->assoc_context);
+ if (!edata->backtrace &&+ edata->funcname &&+ matches_backtrace_functions(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.cindex eb78522053..2bb05414e1 100644--- a/src/backend/utils/misc/guc.c+++ b/src/backend/utils/misc/guc.c@@ -513,6 +513,7 @@ int log_min_duration_statement = -1;
int log_temp_files = -1;
double log_xact_sample_rate = 0;
int trace_recovery_messages = LOG;
+char *backtrace_functions;
int temp_file_limit = -1;
@@ -4199,6 +4200,17 @@ static struct config_string ConfigureNamesString[] =
NULL, NULL, NULL
},
+ {+ {"backtrace_functions", PGC_SUSET, DEVELOPER_OPTIONS,+ gettext_noop("Log backtrace for errors in these functions."),+ NULL,+ GUC_NOT_IN_SAMPLE+ },+ &backtrace_functions,+ "",+ 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.inindex 512213aa32..21cd01a66c 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
diff --git a/src/include/utils/elog.h b/src/include/utils/elog.hindex ba0b7f6f79..6a315414e2 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.hindex 6791e0cbc2..af8d5a6cdd 100644--- a/src/include/utils/guc.h+++ b/src/include/utils/guc.h@@ -254,6 +254,7 @@ extern PGDLLIMPORT int client_min_messages;
extern int log_min_duration_statement;
extern int log_temp_files;
extern double log_xact_sample_rate;
+extern char *backtrace_functions;
extern int temp_file_limit;
base-commit: 66bde49d96a9ddacc49dcbdf1b47b5bd6e31ead5
--
2.22.0
Attachments:
[text/plain] v4-0001-Add-backtrace-support.patch (16.1K, ../35beac83-bf15-9d79-05c4-2dccd0834993@2ndquadrant.com/2-v4-0001-Add-backtrace-support.patch)
download | inline diff:
From f9d440ec41f49464661960c580c29ce0558a1642 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Tue, 13 Aug 2019 01:47:22 +0200
Subject: [PATCH v4] Add backtrace support
Add some support for automatically showing backtraces in certain error
situations in the server. Backtraces are shown on assertion failure.
A new setting backtrace_functions can be set to a list of C function
names, and all ereport()s and elog()s from the functions will have
backtraces generated. Finally, the function errbacktrace() can be
manually added to an ereport() call to generate a backtrace for that
call.
Discussion: https://www.postgresql.org/message-id/flat/5f48cb47-bf1e-05b6-7aae-3bf2cd01586d@2ndquadrant.com
---
configure | 61 +++++++++++++++-
configure.in | 4 ++
doc/src/sgml/config.sgml | 26 +++++++
src/backend/utils/error/assert.c | 13 ++++
src/backend/utils/error/elog.c | 115 +++++++++++++++++++++++++++++++
src/backend/utils/misc/guc.c | 12 ++++
src/include/pg_config.h.in | 6 ++
src/include/utils/elog.h | 3 +
src/include/utils/guc.h | 1 +
9 files changed, 239 insertions(+), 2 deletions(-)
diff --git a/configure b/configureindex 7a6bfc2339..fbd8224f50 100755--- a/configure+++ b/configure@@ -11662,6 +11662,63 @@ if test "$ac_res" != no; then :
fi
+# *BSD:+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing backtrace_symbols" >&5+$as_echo_n "checking for library containing backtrace_symbols... " >&6; }+if ${ac_cv_search_backtrace_symbols+:} 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 backtrace_symbols ();+int+main ()+{+return backtrace_symbols ();+ ;+ return 0;+}+_ACEOF+for ac_lib in '' execinfo; 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_backtrace_symbols=$ac_res+fi+rm -f core conftest.err conftest.$ac_objext \+ conftest$ac_exeext+ if ${ac_cv_search_backtrace_symbols+:} false; then :+ break+fi+done+if ${ac_cv_search_backtrace_symbols+:} false; then :++else+ ac_cv_search_backtrace_symbols=no+fi+rm conftest.$ac_ext+LIBS=$ac_func_search_save_LIBS+fi+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_backtrace_symbols" >&5+$as_echo "$ac_cv_search_backtrace_symbols" >&6; }+ac_res=$ac_cv_search_backtrace_symbols+if test "$ac_res" != no; then :+ test "$ac_res" = "none required" || LIBS="$ac_res $LIBS"++fi+
if test "$with_readline" = yes; then
@@ -12760,7 +12817,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"
@@ -15143,7 +15200,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.inindex dde3eec89f..f0c5ec9f47 100644--- a/configure.in+++ b/configure.in@@ -1131,6 +1131,8 @@ AC_SEARCH_LIBS(sched_yield, rt)
AC_SEARCH_LIBS(gethostbyname_r, nsl)
# Cygwin:
AC_SEARCH_LIBS(shmget, cygipc)
+# *BSD:+AC_SEARCH_LIBS(backtrace_symbols, execinfo)
if test "$with_readline" = yes; then
PGAC_CHECK_READLINE
@@ -1274,6 +1276,7 @@ AC_CHECK_HEADERS(m4_normalize([
atomic.h
copyfile.h
crypt.h
+ execinfo.h
fp_class.h
getopt.h
ieeefp.h
@@ -1588,6 +1591,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/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgmlindex cdc30fa5e3..10e6c80ac1 100644--- a/doc/src/sgml/config.sgml+++ b/doc/src/sgml/config.sgml@@ -9285,6 +9285,32 @@ <title>Developer Options</title>
</listitem>
</varlistentry>
+ <varlistentry id="guc-backtrace-functions" xreflabel="backtrace_functions">+ <term><varname>backtrace_functions</varname> (<type>string</type>)+ <indexterm>+ <primary><varname>backtrace_functions</varname> configuration parameter</primary>+ </indexterm>+ </term>+ <listitem>+ <para>+ This parameter contains a comma-separated list of C function names.+ If an error is raised and the name of the internal C function where+ the error happens matches a value in the list, then a backtrace is+ written to the server log together with the error message. This can+ be used to debug specific areas of the source code.+ </para>++ <para>+ Backtrace support is not available on all platforms, and the quality+ of the backtraces depends on compilation options.+ </para>++ <para>+ This parameter can only be set by superusers.+ </para>+ </listitem>+ </varlistentry>+
<varlistentry id="guc-ignore-system-indexes" xreflabel="ignore_system_indexes">
<term><varname>ignore_system_indexes</varname> (<type>boolean</type>)
<indexterm>
diff --git a/src/backend/utils/error/assert.c b/src/backend/utils/error/assert.cindex 2050b4355d..14707fc296 100644--- a/src/backend/utils/error/assert.c+++ b/src/backend/utils/error/assert.c@@ -18,6 +18,9 @@
#include "postgres.h"
#include <unistd.h>
+#ifdef HAVE_EXECINFO_H+#include <execinfo.h>+#endif
/*
* ExceptionalCondition - Handles the failure of an Assert()
@@ -42,6 +45,16 @@ ExceptionalCondition(const char *conditionName,
/* Usually this shouldn't be needed, but make sure the msg went out */
fflush(stderr);
+#ifdef HAVE_BACKTRACE_SYMBOLS+ {+ void *buf[100];+ int nframes;++ nframes = backtrace(buf, sizeof(buf));+ backtrace_symbols_fd(buf, nframes, fileno(stderr));+ }+#endif+
#ifdef SLEEP_ON_ASSERT
/*
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.cindex 8b4720ef3a..12a37e4ac5 100644--- a/src/backend/utils/error/elog.c+++ b/src/backend/utils/error/elog.c@@ -62,6 +62,9 @@
#ifdef HAVE_SYSLOG
#include <syslog.h>
#endif
+#ifdef HAVE_EXECINFO_H+#include <execinfo.h>+#endif
#include "access/transam.h"
#include "access/xact.h"
@@ -166,6 +169,7 @@ static char formatted_log_time[FORMATTED_TS_LEN];
} while (0)
+static pg_noinline 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);
@@ -398,6 +402,33 @@ errstart(int elevel, const char *filename, int lineno,
return true;
}
+/*+ * Checks whether the given funcname matches the comma-separated list in backtrace_functions.+ *+ * This is coded separately here instead of using an existing list-splitting+ * function so that we can control the error behavior carefully, since this is+ * called during error processing.+ */+static bool+matches_backtrace_functions(const char *funcname)+{+ char *setting = strdup(backtrace_functions);++ if (!setting)+ return false;++ for (char *token = strtok(setting, ","); token; token = strtok(NULL, ","))+ {+ if (strcmp(token, funcname) == 0)+ {+ free(setting);+ return true;+ }+ }+ free(setting);+ return false;+}+
/*
* errfinish --- end an error-reporting cycle
*
@@ -424,6 +455,12 @@ errfinish(int dummy,...)
*/
oldcontext = MemoryContextSwitchTo(ErrorContext);
+ if (!edata->backtrace &&+ edata->funcname &&+ backtrace_functions &&+ matches_backtrace_functions(edata->funcname))+ 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 +525,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 +837,62 @@ errmsg(const char *fmt,...)
return 0; /* return value does not matter */
}
+/*+ * Add a backtrace to the containing ereport() call. This is intended to be+ * added temporarily during debugging.+ */+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;+}++/*+ * Compute backtrace data and add it to the supplied ErrorData. num_skip+ * specifies how many inner frames to skip. Use this to avoid showing the+ * internal backtrace support functions in the backtrace. This requires that+ * this and related functions are not inlined.+ */+static void+set_backtrace(ErrorData *edata, int num_skip)+{+ StringInfoData errtrace;++ initStringInfo(&errtrace);++#ifdef 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, "\n%s", 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 +1448,11 @@ elog_finish(int elevel, const char *fmt,...)
recursion_depth++;
oldcontext = MemoryContextSwitchTo(edata->assoc_context);
+ if (!edata->backtrace &&+ edata->funcname &&+ matches_backtrace_functions(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.cindex eb78522053..2bb05414e1 100644--- a/src/backend/utils/misc/guc.c+++ b/src/backend/utils/misc/guc.c@@ -513,6 +513,7 @@ int log_min_duration_statement = -1;
int log_temp_files = -1;
double log_xact_sample_rate = 0;
int trace_recovery_messages = LOG;
+char *backtrace_functions;
int temp_file_limit = -1;
@@ -4199,6 +4200,17 @@ static struct config_string ConfigureNamesString[] =
NULL, NULL, NULL
},
+ {+ {"backtrace_functions", PGC_SUSET, DEVELOPER_OPTIONS,+ gettext_noop("Log backtrace for errors in these functions."),+ NULL,+ GUC_NOT_IN_SAMPLE+ },+ &backtrace_functions,+ "",+ 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.inindex 512213aa32..21cd01a66c 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
diff --git a/src/include/utils/elog.h b/src/include/utils/elog.hindex ba0b7f6f79..6a315414e2 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.hindex 6791e0cbc2..af8d5a6cdd 100644--- a/src/include/utils/guc.h+++ b/src/include/utils/guc.h@@ -254,6 +254,7 @@ extern PGDLLIMPORT int client_min_messages;
extern int log_min_duration_statement;
extern int log_temp_files;
extern double log_xact_sample_rate;
+extern char *backtrace_functions;
extern int temp_file_limit;
base-commit: 66bde49d96a9ddacc49dcbdf1b47b5bd6e31ead5
--
2.22.0
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: pgsql-hackers@postgresql.org
Cc: peter.eisentraut@2ndquadrant.com, alvherre@2ndquadrant.com, 9erthalion6@gmail.com, thomas.munro@gmail.com
Subject: Re: errbacktrace
In-Reply-To: <35beac83-bf15-9d79-05c4-2dccd0834993@2ndquadrant.com>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by DDX for PostgreSQL; see mirroring instructions
for how to clone and mirror all data and code used for this inbox