On Fri, Jan 26, 2024 at 4:11 PM Alvaro Herrera <alvhe...@alvh.no-ip.org> wrote:
>

Thanks for reviewing.

> > +    You can get the file name and line number from the logged details by 
> > using
> > +    gdb/addr2line in linux platforms (users must ensure gdb/addr2line is
> > +    already installed).
>
> This doesn't read great.  I mean, what is gdb/addr2line?  I think you
> mean either GDB or addr2line; this could be clearer.

Wrapped them in <productname> tag and reworded the comment a bit.

> >   * internal backtrace support functions in the backtrace.  This requires 
> > that
> > - * this and related functions are not inlined.
> > + * this and related functions are not inlined. If the edata pointer is 
> > valid,
> > + * backtrace information will be set in edata.
> >   */
> > -static void
> > +void
> >  set_backtrace(ErrorData *edata, int num_skip)
> >  {
> >       StringInfoData errtrace;
>
> This seems like a terrible API choice, and the comment change is no
> good.  I suggest you need to create a function that deals only with a
> StringInfo, maybe
>   append_backtrace(StringInfo buf, int num_skip)
> which is used by set_backtrace to print the backtrace in
> edata->backtrace, and a new function log_backtrace() that does the
> ereport(LOG_SERVER_ONLY) thing.

You probably were looking at v21, the above change isn't there in
versions after that. Can you please review the latest version v26
attached here?

We might want this patch extended to the newly added walsummarizer
process which I'll do so in the next version.

--
Bharath Rupireddy
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com
From c00bc969b7e55dff92cdd5b7fef355dc2bc98f8f Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Date: Fri, 26 Jan 2024 15:51:02 +0000
Subject: [PATCH v26] Move sending multiplexed-SIGUSR1 signal code to a
 function

Add a new function hosting the common code for sending
multiplexed-SIGUSR1 signal to a backend process. This function
will also be used as-is by an upcoming commit reducing the code
duplication.
---
 src/backend/storage/ipc/procarray.c | 60 +++++++++++++++++++++++++++++
 src/backend/utils/adt/mcxtfuncs.c   | 49 ++---------------------
 src/include/storage/procarray.h     |  1 +
 3 files changed, 64 insertions(+), 46 deletions(-)

diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index ee2d7f8585..f6465529e7 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -3097,6 +3097,66 @@ HaveVirtualXIDsDelayingChkpt(VirtualTransactionId *vxids, int nvxids, int type)
 	return result;
 }
 
+/*
+ * SendProcSignalBackendOrAuxproc -- check if the process with given pid is a
+ * backend or an auxiliary process and send it the SIGUSR1 signal for a given
+ * reason.
+ *
+ * Returns true if sending the signal was successful, false otherwise.
+ */
+bool
+SendProcSignalBackendOrAuxproc(int pid, ProcSignalReason reason)
+{
+	PGPROC	   *proc;
+	BackendId	backendId = InvalidBackendId;
+
+	proc = BackendPidGetProc(pid);
+
+	/*
+	 * See if the process with given pid is a backend or an auxiliary process.
+	 *
+	 * If the given process is a backend, use its backend id in
+	 * SendProcSignal() later to speed up the operation. Otherwise, don't do
+	 * that because auxiliary processes (except the startup process) don't
+	 * have a valid backend id.
+	 */
+	if (proc != NULL)
+		backendId = proc->backendId;
+	else
+		proc = AuxiliaryPidGetProc(pid);
+
+	/*
+	 * BackendPidGetProc() and AuxiliaryPidGetProc() return NULL if the pid
+	 * isn't valid; but by the time we reach kill(), a process for which we
+	 * get a valid proc here might have terminated on its own.  There's no way
+	 * to acquire a lock on an arbitrary process to prevent that. But since
+	 * this mechanism is usually used to debug a backend or an auxiliary
+	 * process running and consuming lots of memory or a long running process,
+	 * that it might end on its own first and its memory contexts are not
+	 * logged or backtrace not logged is not a problem.
+	 */
+	if (proc == NULL)
+	{
+		/*
+		 * This is just a warning so a loop-through-resultset will not abort
+		 * if one backend terminated on its own during the run.
+		 */
+		ereport(WARNING,
+				(errmsg("PID %d is not a PostgreSQL server process", pid)));
+		return false;
+	}
+
+	if (SendProcSignal(pid, reason, backendId) < 0)
+	{
+		/* Again, just a warning to allow loops */
+		ereport(WARNING,
+				(errmsg("could not send signal to process %d: %m", pid)));
+		return false;
+	}
+
+	return true;
+}
+
 /*
  * BackendPidGetProc -- get a backend's PGPROC given its PID
  *
diff --git a/src/backend/utils/adt/mcxtfuncs.c b/src/backend/utils/adt/mcxtfuncs.c
index 4708d73f5f..f7b4f8dac1 100644
--- a/src/backend/utils/adt/mcxtfuncs.c
+++ b/src/backend/utils/adt/mcxtfuncs.c
@@ -145,51 +145,8 @@ Datum
 pg_log_backend_memory_contexts(PG_FUNCTION_ARGS)
 {
 	int			pid = PG_GETARG_INT32(0);
-	PGPROC	   *proc;
-	BackendId	backendId = InvalidBackendId;
+	bool		result;
 
-	proc = BackendPidGetProc(pid);
-
-	/*
-	 * See if the process with given pid is a backend or an auxiliary process.
-	 *
-	 * If the given process is a backend, use its backend id in
-	 * SendProcSignal() later to speed up the operation. Otherwise, don't do
-	 * that because auxiliary processes (except the startup process) don't
-	 * have a valid backend id.
-	 */
-	if (proc != NULL)
-		backendId = proc->backendId;
-	else
-		proc = AuxiliaryPidGetProc(pid);
-
-	/*
-	 * BackendPidGetProc() and AuxiliaryPidGetProc() return NULL if the pid
-	 * isn't valid; but by the time we reach kill(), a process for which we
-	 * get a valid proc here might have terminated on its own.  There's no way
-	 * to acquire a lock on an arbitrary process to prevent that. But since
-	 * this mechanism is usually used to debug a backend or an auxiliary
-	 * process running and consuming lots of memory, that it might end on its
-	 * own first and its memory contexts are not logged is not a problem.
-	 */
-	if (proc == NULL)
-	{
-		/*
-		 * This is just a warning so a loop-through-resultset will not abort
-		 * if one backend terminated on its own during the run.
-		 */
-		ereport(WARNING,
-				(errmsg("PID %d is not a PostgreSQL server process", pid)));
-		PG_RETURN_BOOL(false);
-	}
-
-	if (SendProcSignal(pid, PROCSIG_LOG_MEMORY_CONTEXT, backendId) < 0)
-	{
-		/* Again, just a warning to allow loops */
-		ereport(WARNING,
-				(errmsg("could not send signal to process %d: %m", pid)));
-		PG_RETURN_BOOL(false);
-	}
-
-	PG_RETURN_BOOL(true);
+	result = SendProcSignalBackendOrAuxproc(pid, PROCSIG_LOG_MEMORY_CONTEXT);
+	PG_RETURN_BOOL(result);
 }
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index f3eba9b764..4ac8e2c60c 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -68,6 +68,7 @@ extern PGPROC *BackendPidGetProc(int pid);
 extern PGPROC *BackendPidGetProcWithLock(int pid);
 extern int	BackendXidGetPid(TransactionId xid);
 extern bool IsBackendPid(int pid);
+extern bool SendProcSignalBackendOrAuxproc(int pid, ProcSignalReason reason);
 
 extern VirtualTransactionId *GetCurrentVirtualXIDs(TransactionId limitXmin,
 												   bool excludeXmin0, bool allDbs, int excludeVacuum,
-- 
2.34.1

From c895756097eee8157d3a0f2c95fac616e92bab40 Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Date: Fri, 26 Jan 2024 17:25:41 +0000
Subject: [PATCH v26] Add function to log the backtrace of specified postgres
 process

This commit adds pg_log_backtrace() function that requests to log
the backtrace to console i.e. stderr of the specified backend or
auxiliary process except logger and statistic collector.

Only superusers are allowed to request to log the backtrace which
is safe from a security standpoint because the backtrace might
contain internal details.

Bump catalog version.
---
 contrib/pg_prewarm/autoprewarm.c          |  2 +
 doc/src/sgml/func.sgml                    | 76 +++++++++++++++++++++++
 src/backend/catalog/system_functions.sql  |  2 +
 src/backend/postmaster/autovacuum.c       |  4 ++
 src/backend/postmaster/bgworker.c         |  2 +
 src/backend/postmaster/bgwriter.c         |  2 +
 src/backend/postmaster/checkpointer.c     |  2 +
 src/backend/postmaster/pgarch.c           |  2 +
 src/backend/postmaster/startup.c          |  2 +
 src/backend/postmaster/walwriter.c        |  2 +
 src/backend/replication/walreceiver.c     |  2 +
 src/backend/replication/walsender.c       |  2 +
 src/backend/storage/ipc/procsignal.c      | 74 ++++++++++++++++++++++
 src/backend/storage/ipc/signalfuncs.c     | 26 ++++++++
 src/backend/tcop/postgres.c               |  2 +
 src/include/catalog/pg_proc.dat           |  5 ++
 src/include/storage/procsignal.h          |  2 +
 src/test/regress/expected/backtrace.out   | 49 +++++++++++++++
 src/test/regress/expected/backtrace_1.out | 55 ++++++++++++++++
 src/test/regress/parallel_schedule        |  2 +-
 src/test/regress/sql/backtrace.sql        | 33 ++++++++++
 21 files changed, 347 insertions(+), 1 deletion(-)
 create mode 100644 src/test/regress/expected/backtrace.out
 create mode 100644 src/test/regress/expected/backtrace_1.out
 create mode 100644 src/test/regress/sql/backtrace.sql

diff --git a/contrib/pg_prewarm/autoprewarm.c b/contrib/pg_prewarm/autoprewarm.c
index 06ee21d496..94e04b78cc 100644
--- a/contrib/pg_prewarm/autoprewarm.c
+++ b/contrib/pg_prewarm/autoprewarm.c
@@ -160,6 +160,8 @@ autoprewarm_main(Datum main_arg)
 	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	BackgroundWorkerUnblockSignals();
 
+	LoadBacktraceFunctions();
+
 	/* Create (if necessary) and attach to our shared memory area. */
 	if (apw_init_shmem())
 		first_time = false;
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 6788ba8ef4..b9dbf49813 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25707,6 +25707,27 @@ SELECT collation for ('foo' COLLATE "de_DE");
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_log_backtrace</primary>
+        </indexterm>
+        <function>pg_log_backtrace</function> ( <parameter>pid</parameter> <type>integer</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Requests to log the backtrace of the backend with the specified
+        process ID. This function can send the request to backends and
+        auxiliary processes except the logger and statistics collector.
+        The backtraces will be logged to <systemitem>stderr</systemitem>.
+        Typically, a backtrace identifies which function a process is
+        currently executing and it is useful for developers to diagnose
+        stuck processes and other problems. This function is supported
+        only if PostgreSQL was built with the ability to capture backtraces,
+        otherwise it will emit a warning.
+       </para></entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
@@ -27300,6 +27321,61 @@ LOG:  Grand total: 1651920 bytes in 201 blocks; 622360 free (88 chunks); 1029560
     because it may generate a large number of log messages.
    </para>
 
+   <para>
+    <function>pg_log_backtrace</function> can be used to log the backtrace of
+    a backend process. For example:
+<programlisting>
+postgres=# select pg_log_backtrace(pg_backend_pid());
+ pg_log_backtrace
+------------------
+ t
+(1 row)
+</programlisting>
+The backtrace will be logged as specified by the logging configuration.
+For example:
+<screen>
+logging current backtrace of process with PID 3499242:
+postgres: ubuntu postgres [local] INSERT(+0x61a355)[0x5559b94de355]
+postgres: ubuntu postgres [local] INSERT(procsignal_sigusr1_handler+0x9e)[0x5559b94de4ef]
+/lib/x86_64-linux-gnu/libc.so.6(+0x42520)[0x7f7e9c0e8520]
+postgres: ubuntu postgres [local] INSERT(+0x868004)[0x5559b972c004]
+postgres: ubuntu postgres [local] INSERT(pfree+0x1c)[0x5559b972e445]
+postgres: ubuntu postgres [local] INSERT(heap_free_minimal_tuple+0x1c)[0x5559b8fa7b48]
+postgres: ubuntu postgres [local] INSERT(+0x8826d6)[0x5559b97466d6]
+postgres: ubuntu postgres [local] INSERT(+0x881126)[0x5559b9745126]
+postgres: ubuntu postgres [local] INSERT(tuplestore_putvalues+0x83)[0x5559b9744ed5]
+postgres: ubuntu postgres [local] INSERT(ExecMakeTableFunctionResult+0x68b)[0x5559b9284295]
+postgres: ubuntu postgres [local] INSERT(+0x3dd14a)[0x5559b92a114a]
+postgres: ubuntu postgres [local] INSERT(+0x3c1a5c)[0x5559b9285a5c]
+postgres: ubuntu postgres [local] INSERT(ExecScan+0x77)[0x5559b9285ad5]
+postgres: ubuntu postgres [local] INSERT(+0x3dd4f4)[0x5559b92a14f4]
+postgres: ubuntu postgres [local] INSERT(+0x3bd485)[0x5559b9281485]
+postgres: ubuntu postgres [local] INSERT(+0x3f96f0)[0x5559b92bd6f0]
+postgres: ubuntu postgres [local] INSERT(+0x3ff25e)[0x5559b92c325e]
+postgres: ubuntu postgres [local] INSERT(+0x3bd485)[0x5559b9281485]
+postgres: ubuntu postgres [local] INSERT(+0x3b07a4)[0x5559b92747a4]
+postgres: ubuntu postgres [local] INSERT(+0x3b3594)[0x5559b9277594]
+postgres: ubuntu postgres [local] INSERT(standard_ExecutorRun+0x1f4)[0x5559b9274e8c]
+postgres: ubuntu postgres [local] INSERT(ExecutorRun+0x5d)[0x5559b9274c95]
+postgres: ubuntu postgres [local] INSERT(+0x64ee3a)[0x5559b9512e3a]
+postgres: ubuntu postgres [local] INSERT(+0x6509c9)[0x5559b95149c9]
+postgres: ubuntu postgres [local] INSERT(PortalRun+0x378)[0x5559b9513ec0]
+postgres: ubuntu postgres [local] INSERT(+0x648c56)[0x5559b950cc56]
+postgres: ubuntu postgres [local] INSERT(PostgresMain+0x80c)[0x5559b9511c46]
+postgres: ubuntu postgres [local] INSERT(+0x576647)[0x5559b943a647]
+postgres: ubuntu postgres [local] INSERT(+0x575ed3)[0x5559b9439ed3]
+postgres: ubuntu postgres [local] INSERT(+0x572388)[0x5559b9436388]
+postgres: ubuntu postgres [local] INSERT(PostmasterMain+0x14b3)[0x5559b9435ae5]
+postgres: ubuntu postgres [local] INSERT(+0x43b563)[0x5559b92ff563]
+/lib/x86_64-linux-gnu/libc.so.6(+0x29d90)[0x7f7e9c0cfd90]
+/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0x80)[0x7f7e9c0cfe40]
+postgres: ubuntu postgres [local] INSERT(_start+0x25)[0x5559b8f8f005]
+</screen>
+    One can obtain the file name and line number from the logged details by
+    using <productname>GDB</productname> or
+    <productname>addr2line</productname> in most Linux systems.
+   </para>
+
   </sect2>
 
   <sect2 id="functions-admin-backup">
diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql
index 346cfb98a0..3d8ea385fb 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -757,6 +757,8 @@ REVOKE EXECUTE ON FUNCTION pg_ls_logicalmapdir() FROM PUBLIC;
 
 REVOKE EXECUTE ON FUNCTION pg_ls_replslotdir(text) FROM PUBLIC;
 
+REVOKE EXECUTE ON FUNCTION pg_log_backtrace(integer) FROM PUBLIC;
+
 --
 -- We also set up some things as accessible to standard roles.
 --
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 2c3099f76f..b404049b15 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -479,6 +479,8 @@ AutoVacLauncherMain(int argc, char *argv[])
 
 	SetProcessingMode(NormalProcessing);
 
+	LoadBacktraceFunctions();
+
 	/*
 	 * Create a memory context that we will do all our work in.  We do this so
 	 * that we can reset the context during error recovery and thereby avoid
@@ -1534,6 +1536,8 @@ AutoVacWorkerMain(int argc, char *argv[])
 	/* Early initialization */
 	BaseInit();
 
+	LoadBacktraceFunctions();
+
 	/*
 	 * If an exception is encountered, processing resumes here.
 	 *
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index 67f92c24db..3b989990fb 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -772,6 +772,8 @@ BackgroundWorkerMain(void)
 	pqsignal(SIGUSR2, SIG_IGN);
 	pqsignal(SIGCHLD, SIG_DFL);
 
+	LoadBacktraceFunctions();
+
 	/*
 	 * If an exception is encountered, processing resumes here.
 	 *
diff --git a/src/backend/postmaster/bgwriter.c b/src/backend/postmaster/bgwriter.c
index d7d6cc0cd7..4f6f62b404 100644
--- a/src/backend/postmaster/bgwriter.c
+++ b/src/backend/postmaster/bgwriter.c
@@ -112,6 +112,8 @@ BackgroundWriterMain(void)
 	 */
 	pqsignal(SIGCHLD, SIG_DFL);
 
+	LoadBacktraceFunctions();
+
 	/*
 	 * We just started, assume there has been either a shutdown or
 	 * end-of-recovery snapshot.
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index 5e949fc885..26d1367909 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -198,6 +198,8 @@ CheckpointerMain(void)
 	 */
 	pqsignal(SIGCHLD, SIG_DFL);
 
+	LoadBacktraceFunctions();
+
 	/*
 	 * Initialize so that first time-driven event happens at the correct time.
 	 */
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index 67693b0580..9fa476974a 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -232,6 +232,8 @@ PgArchiverMain(void)
 	/* Unblock signals (they were blocked when the postmaster forked us) */
 	sigprocmask(SIG_SETMASK, &UnBlockSig, NULL);
 
+	LoadBacktraceFunctions();
+
 	/* We shouldn't be launched unnecessarily. */
 	Assert(XLogArchivingActive());
 
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index d53c37d062..87bf59ce9e 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -258,6 +258,8 @@ StartupProcessMain(void)
 	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, StartupProcTriggerHandler);
 
+	LoadBacktraceFunctions();
+
 	/*
 	 * Reset some signals that are accepted by postmaster but not here
 	 */
diff --git a/src/backend/postmaster/walwriter.c b/src/backend/postmaster/walwriter.c
index 75c9f8707b..5b2e643c94 100644
--- a/src/backend/postmaster/walwriter.c
+++ b/src/backend/postmaster/walwriter.c
@@ -112,6 +112,8 @@ WalWriterMain(void)
 	 */
 	pqsignal(SIGCHLD, SIG_DFL);
 
+	LoadBacktraceFunctions();
+
 	/*
 	 * Create a memory context that we will do all our work in.  We do this so
 	 * that we can reset the context during error recovery and thereby avoid
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index 728059518e..623b162244 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -287,6 +287,8 @@ WalReceiverMain(void)
 	/* Reset some signals that are accepted by postmaster but not here */
 	pqsignal(SIGCHLD, SIG_DFL);
 
+	LoadBacktraceFunctions();
+
 	/* Load the libpq-specific functions */
 	load_file("libpqwalreceiver", false);
 	if (WalReceiverFunctions == NULL)
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index aa80f3de20..33cec58db8 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -3430,6 +3430,8 @@ WalSndSignals(void)
 
 	/* Reset some signals that are accepted by postmaster but not here */
 	pqsignal(SIGCHLD, SIG_DFL);
+
+	LoadBacktraceFunctions();
 }
 
 /* Report shared-memory space needed by WalSndShmemInit */
diff --git a/src/backend/storage/ipc/procsignal.c b/src/backend/storage/ipc/procsignal.c
index e84619e5a5..32f1e27750 100644
--- a/src/backend/storage/ipc/procsignal.c
+++ b/src/backend/storage/ipc/procsignal.c
@@ -16,6 +16,9 @@
 
 #include <signal.h>
 #include <unistd.h>
+#ifdef HAVE_EXECINFO_H
+#include <execinfo.h>
+#endif
 
 #include "access/parallel.h"
 #include "port/pg_bitutils.h"
@@ -97,6 +100,10 @@ typedef struct
 #define BARRIER_CLEAR_BIT(flags, type) \
 	((flags) &= ~(((uint32) 1) << (uint32) (type)))
 
+#ifdef HAVE_BACKTRACE_SYMBOLS
+static bool backtrace_functions_loaded = false;
+#endif
+
 static ProcSignalHeader *ProcSignal = NULL;
 static ProcSignalSlot *MyProcSignalSlot = NULL;
 
@@ -609,6 +616,70 @@ ResetProcSignalBarrierBits(uint32 flags)
 	InterruptPending = true;
 }
 
+/*
+ * HandleLogBacktraceInterrupt - Handle receipt of an interrupt requesting to
+ * log a backtrace.
+ *
+ * We capture the backtrace within this signal handler and emit to stderr. Note
+ * that we ensured the backtrace-related functions are signal-safe, see
+ * LoadBacktraceFunctions() for more details.
+ *
+ * Emitting backtrace to stderr as opposed to writing to server log has an
+ * advantage - we don't need to allocate any dynamic memory while capturing
+ * backtrace which makes the signal handler safe.
+ */
+static void
+HandleLogBacktraceInterrupt(void)
+{
+#ifdef HAVE_BACKTRACE_SYMBOLS
+	void	   *buf[100];
+	int			nframes;
+
+	/* Quickly exit if backtrace-related functions aren't loaded. */
+	if (!backtrace_functions_loaded)
+		return;
+
+	nframes = backtrace(buf, lengthof(buf));
+
+	write_stderr("logging current backtrace of process with PID %d:\n",
+				 MyProcPid);
+	backtrace_symbols_fd(buf, nframes, fileno(stderr));
+#endif
+}
+
+/*
+ * LoadBacktraceFunctions - call a backtrace-related function to ensure the
+ * shared library implementing them is loaded beforehand.
+ *
+ * Any backtrace-related functions when called for the first time dynamically
+ * loads the shared library, which usually triggers a call to malloc, making
+ * them unsafe to use in signal handlers.
+ *
+ * This functions is an attempt to make backtrace-related functions signal
+ * safe.
+ *
+ * NOTE: This function is supposed to be called in the early life of a process,
+ * preferably after SIGUSR1 handler is setup and before the backtrace-related
+ * functions are used in signal handlers. It is not supposed to be called from
+ * within a signal handler.
+ */
+void
+LoadBacktraceFunctions(void)
+{
+#ifdef HAVE_BACKTRACE_SYMBOLS
+	void	   *buf[100];
+
+	/*
+	 * It is enough to call any one backtrace-related function to ensure that
+	 * the corresponding shared library is dynamically loaded if not done
+	 * already.
+	 */
+	backtrace(buf, lengthof(buf));
+
+	backtrace_functions_loaded = true;
+#endif
+}
+
 /*
  * CheckProcSignal - check to see if a particular reason has been
  * signaled, and clear the signal flag.  Should be called after receiving
@@ -661,6 +732,9 @@ procsignal_sigusr1_handler(SIGNAL_ARGS)
 	if (CheckProcSignal(PROCSIG_PARALLEL_APPLY_MESSAGE))
 		HandleParallelApplyMessageInterrupt();
 
+	if (CheckProcSignal(PROCSIG_LOG_BACKTRACE))
+		HandleLogBacktraceInterrupt();
+
 	if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT_DATABASE))
 		HandleRecoveryConflictInterrupt(PROCSIG_RECOVERY_CONFLICT_DATABASE);
 
diff --git a/src/backend/storage/ipc/signalfuncs.c b/src/backend/storage/ipc/signalfuncs.c
index 81d1a59659..836b36e5bb 100644
--- a/src/backend/storage/ipc/signalfuncs.c
+++ b/src/backend/storage/ipc/signalfuncs.c
@@ -316,3 +316,29 @@ pg_rotate_logfile_v2(PG_FUNCTION_ARGS)
 	SendPostmasterSignal(PMSIGNAL_ROTATE_LOGFILE);
 	PG_RETURN_BOOL(true);
 }
+
+/*
+ * pg_log_backtrace - signal a backend or an auxiliary process to log its
+ * current backtrace to stderr.
+ *
+ * By default, only superusers are allowed to request to log the backtrace
+ * which is safe from a security standpoint because the backtrace might contain
+ * internal details. However, a superuser can grant the execute permission to
+ * anyone, if it wishes.
+ */
+Datum
+pg_log_backtrace(PG_FUNCTION_ARGS)
+{
+	int			pid = PG_GETARG_INT32(0);
+	bool		result;
+
+#ifndef HAVE_BACKTRACE_SYMBOLS
+	ereport(WARNING,
+			errmsg("backtrace generation is not supported by this installation"),
+			errhint("You need to rebuild PostgreSQL using a library containing backtrace_symbols."));
+	PG_RETURN_BOOL(false);
+#endif
+
+	result = SendProcSignalBackendOrAuxproc(pid, PROCSIG_LOG_BACKTRACE);
+	PG_RETURN_BOOL(result);
+}
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 1a34bd3715..e316987db5 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -4189,6 +4189,8 @@ PostgresMain(const char *dbname, const char *username)
 		 */
 		pqsignal(SIGCHLD, SIG_DFL); /* system() requires this on some
 									 * platforms */
+
+		LoadBacktraceFunctions();
 	}
 
 	/* Early initialization */
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 29af4ce65d..b13e808bcb 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -12170,4 +12170,9 @@
   proargtypes => 'int2',
   prosrc => 'gist_stratnum_identity' },
 
+# function to get the backtrace of server process
+{ oid => '9661', descr => 'log backtrace of server process',
+  proname => 'pg_log_backtrace', provolatile => 'v', prorettype => 'bool',
+  proargtypes => 'int4', prosrc => 'pg_log_backtrace' },
+
 ]
diff --git a/src/include/storage/procsignal.h b/src/include/storage/procsignal.h
index 52dcb4c2ad..76033ccafc 100644
--- a/src/include/storage/procsignal.h
+++ b/src/include/storage/procsignal.h
@@ -36,6 +36,7 @@ typedef enum
 	PROCSIG_BARRIER,			/* global barrier interrupt  */
 	PROCSIG_LOG_MEMORY_CONTEXT, /* ask backend to log the memory contexts */
 	PROCSIG_PARALLEL_APPLY_MESSAGE, /* Message from parallel apply workers */
+	PROCSIG_LOG_BACKTRACE,		/* ask backend to log the current backtrace */
 
 	/* Recovery conflict reasons */
 	PROCSIG_RECOVERY_CONFLICT_FIRST,
@@ -71,5 +72,6 @@ extern void WaitForProcSignalBarrier(uint64 generation);
 extern void ProcessProcSignalBarrier(void);
 
 extern void procsignal_sigusr1_handler(SIGNAL_ARGS);
+extern void LoadBacktraceFunctions(void);
 
 #endif							/* PROCSIGNAL_H */
diff --git a/src/test/regress/expected/backtrace.out b/src/test/regress/expected/backtrace.out
new file mode 100644
index 0000000000..5341a6adfe
--- /dev/null
+++ b/src/test/regress/expected/backtrace.out
@@ -0,0 +1,49 @@
+--
+-- pg_log_backtrace()
+--
+-- Backtraces are logged to stderr and not returned to the function.
+-- Furthermore, their contents can vary depending on the timing. However,
+-- we can at least verify that the code doesn't fail, and that the
+-- permissions are set properly.
+--
+SELECT pg_log_backtrace(pg_backend_pid());
+ pg_log_backtrace 
+------------------
+ t
+(1 row)
+
+SELECT pg_log_backtrace(pid) FROM pg_stat_activity
+  WHERE backend_type = 'checkpointer';
+ pg_log_backtrace 
+------------------
+ t
+(1 row)
+
+CREATE ROLE regress_log_backtrace;
+SELECT has_function_privilege('regress_log_backtrace',
+  'pg_log_backtrace(integer)', 'EXECUTE'); -- no
+ has_function_privilege 
+------------------------
+ f
+(1 row)
+
+GRANT EXECUTE ON FUNCTION pg_log_backtrace(integer)
+  TO regress_log_backtrace;
+SELECT has_function_privilege('regress_log_backtrace',
+  'pg_log_backtrace(integer)', 'EXECUTE'); -- yes
+ has_function_privilege 
+------------------------
+ t
+(1 row)
+
+SET ROLE regress_log_backtrace;
+SELECT pg_log_backtrace(pg_backend_pid());
+ pg_log_backtrace 
+------------------
+ t
+(1 row)
+
+RESET ROLE;
+REVOKE EXECUTE ON FUNCTION pg_log_backtrace(integer)
+  FROM regress_log_backtrace;
+DROP ROLE regress_log_backtrace;
diff --git a/src/test/regress/expected/backtrace_1.out b/src/test/regress/expected/backtrace_1.out
new file mode 100644
index 0000000000..899f330224
--- /dev/null
+++ b/src/test/regress/expected/backtrace_1.out
@@ -0,0 +1,55 @@
+--
+-- pg_log_backtrace()
+--
+-- Backtraces are logged to stderr and not returned to the function.
+-- Furthermore, their contents can vary depending on the timing. However,
+-- we can at least verify that the code doesn't fail, and that the
+-- permissions are set properly.
+--
+SELECT pg_log_backtrace(pg_backend_pid());
+WARNING:  backtrace generation is not supported by this installation
+HINT:  You need to rebuild PostgreSQL using a library containing backtrace_symbols.
+ pg_log_backtrace 
+------------------
+ f
+(1 row)
+
+SELECT pg_log_backtrace(pid) FROM pg_stat_activity
+  WHERE backend_type = 'checkpointer';
+WARNING:  backtrace generation is not supported by this installation
+HINT:  You need to rebuild PostgreSQL using a library containing backtrace_symbols.
+ pg_log_backtrace 
+------------------
+ f
+(1 row)
+
+CREATE ROLE regress_log_backtrace;
+SELECT has_function_privilege('regress_log_backtrace',
+  'pg_log_backtrace(integer)', 'EXECUTE'); -- no
+ has_function_privilege 
+------------------------
+ f
+(1 row)
+
+GRANT EXECUTE ON FUNCTION pg_log_backtrace(integer)
+  TO regress_log_backtrace;
+SELECT has_function_privilege('regress_log_backtrace',
+  'pg_log_backtrace(integer)', 'EXECUTE'); -- yes
+ has_function_privilege 
+------------------------
+ t
+(1 row)
+
+SET ROLE regress_log_backtrace;
+SELECT pg_log_backtrace(pg_backend_pid());
+WARNING:  backtrace generation is not supported by this installation
+HINT:  You need to rebuild PostgreSQL using a library containing backtrace_symbols.
+ pg_log_backtrace 
+------------------
+ f
+(1 row)
+
+RESET ROLE;
+REVOKE EXECUTE ON FUNCTION pg_log_backtrace(integer)
+  FROM regress_log_backtrace;
+DROP ROLE regress_log_backtrace;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 1d8a414eea..9ad90d725b 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -78,7 +78,7 @@ test: brin_bloom brin_multi
 # psql depends on create_am
 # amutils depends on geometry, create_index_spgist, hash_index, brin
 # ----------
-test: create_table_like alter_generic alter_operator misc async dbsize merge misc_functions sysviews tsrf tid tidscan tidrangescan collate.icu.utf8 incremental_sort create_role without_overlaps
+test: create_table_like alter_generic alter_operator misc async dbsize merge misc_functions sysviews tsrf tid tidscan tidrangescan collate.icu.utf8 incremental_sort create_role without_overlaps backtrace
 
 # collate.*.utf8 tests cannot be run in parallel with each other
 test: rules psql psql_crosstab amutils stats_ext collate.linux.utf8 collate.windows.win1252
diff --git a/src/test/regress/sql/backtrace.sql b/src/test/regress/sql/backtrace.sql
new file mode 100644
index 0000000000..674e41c998
--- /dev/null
+++ b/src/test/regress/sql/backtrace.sql
@@ -0,0 +1,33 @@
+--
+-- pg_log_backtrace()
+--
+-- Backtraces are logged to stderr and not returned to the function.
+-- Furthermore, their contents can vary depending on the timing. However,
+-- we can at least verify that the code doesn't fail, and that the
+-- permissions are set properly.
+--
+
+SELECT pg_log_backtrace(pg_backend_pid());
+
+SELECT pg_log_backtrace(pid) FROM pg_stat_activity
+  WHERE backend_type = 'checkpointer';
+
+CREATE ROLE regress_log_backtrace;
+
+SELECT has_function_privilege('regress_log_backtrace',
+  'pg_log_backtrace(integer)', 'EXECUTE'); -- no
+
+GRANT EXECUTE ON FUNCTION pg_log_backtrace(integer)
+  TO regress_log_backtrace;
+
+SELECT has_function_privilege('regress_log_backtrace',
+  'pg_log_backtrace(integer)', 'EXECUTE'); -- yes
+
+SET ROLE regress_log_backtrace;
+SELECT pg_log_backtrace(pg_backend_pid());
+RESET ROLE;
+
+REVOKE EXECUTE ON FUNCTION pg_log_backtrace(integer)
+  FROM regress_log_backtrace;
+
+DROP ROLE regress_log_backtrace;
-- 
2.34.1

Reply via email to