From b1c557adfae2fa3f798425f10d85e8da30168068 Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Date: Wed, 30 Nov 2022 05:35:47 +0000
Subject: [PATCH v22] Add function to log the backtrace of the 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 +
 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     | 87 ++++++++++++++++++++++++
 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 +
 16 files changed, 146 insertions(+)

diff --git a/contrib/pg_prewarm/autoprewarm.c b/contrib/pg_prewarm/autoprewarm.c
index d02a6a1ba0..17911351af 100644
--- a/contrib/pg_prewarm/autoprewarm.c
+++ b/contrib/pg_prewarm/autoprewarm.c
@@ -177,6 +177,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/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql
index 52517a6531..8bcfcda02f 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -739,6 +739,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 601834d4b4..95bc9f4934 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -463,6 +463,8 @@ AutoVacLauncherMain(int argc, char *argv[])
 	pqsignal(SIGFPE, FloatExceptionHandler);
 	pqsignal(SIGCHLD, SIG_DFL);
 
+	LoadBacktraceFunctions();
+
 	/*
 	 * Create a per-backend PGPROC struct in shared memory, except in the
 	 * EXEC_BACKEND case where this was done in SubPostmasterMain. We must do
@@ -1541,6 +1543,8 @@ AutoVacWorkerMain(int argc, char *argv[])
 	pqsignal(SIGFPE, FloatExceptionHandler);
 	pqsignal(SIGCHLD, SIG_DFL);
 
+	LoadBacktraceFunctions();
+
 	/*
 	 * Create a per-backend PGPROC struct in shared memory, except in the
 	 * EXEC_BACKEND case where this was done in SubPostmasterMain. We must do
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index 0d72de24b0..69b58afb81 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -788,6 +788,8 @@ StartBackgroundWorker(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 91e6f6ea18..76e74a44c3 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 5fc076fc14..dfa2d3f58d 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -207,6 +207,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 fffb6a599c..d307a842ef 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -229,6 +229,8 @@ PgArchiverMain(void)
 	/* Unblock signals (they were blocked when the postmaster forked us) */
 	PG_SETMASK(&UnBlockSig);
 
+	LoadBacktraceFunctions();
+
 	/* We shouldn't be launched unnecessarily. */
 	Assert(XLogArchivingActive());
 
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index f99186eab7..cddc545a38 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -261,6 +261,8 @@ StartupProcessMain(void)
 	 */
 	PG_SETMASK(&UnBlockSig);
 
+	LoadBacktraceFunctions();
+
 	/*
 	 * Do what we came for.
 	 */
diff --git a/src/backend/postmaster/walwriter.c b/src/backend/postmaster/walwriter.c
index beb46dcb55..0b7404d836 100644
--- a/src/backend/postmaster/walwriter.c
+++ b/src/backend/postmaster/walwriter.c
@@ -115,6 +115,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 ad383dbcaa..a75fba13b0 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -288,6 +288,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 c11bb3716f..98facff929 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -3245,6 +3245,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 7767657f27..59b986cfc8 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"
@@ -96,6 +99,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;
 
@@ -608,6 +615,76 @@ 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];
+
+	/*
+	 * XXX: It is a bit of overkill to check if the shared library implementing
+	 * backtrace-related functions is loaded already. Instead, we go ahead and
+	 * call one function.
+	 */
+
+	/*
+	 * 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
@@ -657,6 +734,16 @@ procsignal_sigusr1_handler(SIGNAL_ARGS)
 	if (CheckProcSignal(PROCSIG_LOG_MEMORY_CONTEXT))
 		HandleLogMemoryContextInterrupt();
 
+	/*
+	 * XXX: Since the log backtrace signal handler itself does the required
+	 * job, returning without setting the latch may be a good idea here.
+	 * However, it is better not to deviate from the tradition of a signal
+	 * handler setting the latch to wake up the processes that are waiting on
+	 * it.
+	 */
+	if (CheckProcSignal(PROCSIG_LOG_BACKTRACE))
+		HandleLogBacktraceInterrupt();
+
 	if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT_DATABASE))
 		RecoveryConflictInterrupt(PROCSIG_RECOVERY_CONFLICT_DATABASE);
 
diff --git a/src/backend/storage/ipc/signalfuncs.c b/src/backend/storage/ipc/signalfuncs.c
index 6e310b14eb..5bb68be9f6 100644
--- a/src/backend/storage/ipc/signalfuncs.c
+++ b/src/backend/storage/ipc/signalfuncs.c
@@ -303,3 +303,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 3082093d1e..4bc1621733 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -4109,6 +4109,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 f9301b2627..04c8b011f8 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11854,4 +11854,9 @@
   prorettype => 'bytea', proargtypes => 'pg_brin_minmax_multi_summary',
   prosrc => 'brin_minmax_multi_summary_send' },
 
+# function to get the backtrace of server process
+{ oid => '6105', 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 ee636900f3..0ee63f7faf 100644
--- a/src/include/storage/procsignal.h
+++ b/src/include/storage/procsignal.h
@@ -35,6 +35,7 @@ typedef enum
 	PROCSIG_WALSND_INIT_STOPPING,	/* ask walsenders to prepare for shutdown  */
 	PROCSIG_BARRIER,			/* global barrier interrupt  */
 	PROCSIG_LOG_MEMORY_CONTEXT, /* ask backend to log the memory contexts */
+	PROCSIG_LOG_BACKTRACE,		/* ask backend to log the current backtrace */
 
 	/* Recovery conflict reasons */
 	PROCSIG_RECOVERY_CONFLICT_DATABASE,
@@ -67,5 +68,6 @@ extern void WaitForProcSignalBarrier(uint64 generation);
 extern void ProcessProcSignalBarrier(void);
 
 extern void procsignal_sigusr1_handler(SIGNAL_ARGS);
+extern void LoadBacktraceFunctions(void);
 
 #endif							/* PROCSIGNAL_H */
-- 
2.34.1

