[PATCH 3/3] libdtrace: match BEGIN/END PID in the tracer's own PID namespace

Jacob Carlborg <[email protected]> Mon, 20 Jul 2026 15:31:00 +0200
Newsgroups dev.linux.lists.dtrace
Message-ID <[email protected]>
Without this fix, running any probe hangs with no output,
like `dtrace -n 'BEGIN { trace("hi"); exit(0); }'`.

The issues is that the `BEGIN/END` probes are implemented as uprobes
on DTrace's own `BEGIN_probe()/END_probe()` functions in `dt_work.c`.
`bpf_get_current_pid_tgid()` reports the TGID in the initial PID
namespace, which does not match `getpid()` when DTrace runs inside a
PID namespace. Using the `--pid=host` flag when running OrbStack does
not help, because it shares the daemon's namespace, not the kernel
init namespace. OrbStack's own supervisor runs in a nested PID
namespace. On bare metal `--pid=host` does reach the init namespaces
and the bug wouldn't appear.

This fix is when DTrace is in a non-initial PID namespace and the
kernel is at least 5.7, use `bpf_get_ns_current_pid_tgid` instead of
`bpf_get_current_pid_tgid`. `bpf_get_ns_current_pid_tgid` is
namespace aware.

Signed-off-by: Jacob Carlborg <[email protected]>
---
 libdtrace/dt_prov_dtrace.c | 58 ++++++++++++++++++++++++++++++++++----
 1 file changed, 53 insertions(+), 5 deletions(-)

diff --git a/libdtrace/dt_prov_dtrace.c b/libdtrace/dt_prov_dtrace.c
index 4b788507..179ee4d6 100644
--- a/libdtrace/dt_prov_dtrace.c
+++ b/libdtrace/dt_prov_dtrace.c
@@ -9,6 +9,7 @@
 #include <assert.h>
 #include <errno.h>
 #include <string.h>
+#include <sys/stat.h>
 
 #include <bpf_asm.h>
 
@@ -129,12 +130,59 @@ static int trampoline(dt_pcb_t *pcb, uint_t exitlbl)
 	 * the trampoline to minimize the cost of pointless firings in other
 	 * tracers, even though this means preserving the context in %r1 around
 	 * the call.
+	 *
+	 * bpf_get_current_pid_tgid() reports the TGID in the initial PID
+	 * namespace, which does not match getpid() when DTrace runs inside a
+	 * PID namespace (e.g. in a container) -- so the BEGIN/END probes would
+	 * never recognise their own firing and tracing would never activate.
+	 * When we are in a non-initial PID namespace on a kernel that provides
+	 * it (5.7+), use bpf_get_ns_current_pid_tgid() with DTrace's own PID
+	 * namespace so the value is reported in the same namespace as getpid().
+	 * Otherwise fall back to bpf_get_current_pid_tgid(), leaving the
+	 * initial-namespace case (and kernels < 5.7) exactly as before.
 	 */
-	emit(dlp, BPF_MOV_REG(BPF_REG_6, BPF_REG_1));
-	emit(dlp, BPF_CALL_HELPER(BPF_FUNC_get_current_pid_tgid));
-	emit(dlp, BPF_ALU64_IMM(BPF_RSH, BPF_REG_0, 32));
-	emit(dlp, BPF_BRANCH_IMM(BPF_JNE, BPF_REG_0, getpid(), pcb->pcb_fastlbl));
-	emit(dlp, BPF_MOV_REG(BPF_REG_1, BPF_REG_6));
+	{
+		struct stat	st;
+		int		use_ns = 0;
+		uint64_t	dev = 0, ino = 0;
+
+		/*
+		 * The initial PID namespace has a fixed inode number
+		 * (PROC_PID_INIT_INO, 0xEFFFFFFC).  If we are in it, getpid()
+		 * already agrees with bpf_get_current_pid_tgid() and no
+		 * namespace lookup is needed.
+		 */
+		if (stat("/proc/self/ns/pid", &st) == 0 &&
+		    st.st_ino != 0xEFFFFFFCULL &&
+		    pcb->pcb_hdl->dt_kernver >= DT_VERSION_NUMBER(5, 7, 0)) {
+			use_ns = 1;
+			dev = st.st_dev;
+			ino = st.st_ino;
+		}
+
+		emit(dlp, BPF_MOV_REG(BPF_REG_6, BPF_REG_1));
+		if (use_ns) {
+			/*
+			 * bpf_get_ns_current_pid_tgid(dev, ino, &nsinfo, sz)
+			 * fills a struct bpf_pidns_info { u32 pid; u32 tgid; };
+			 * the tgid is at offset 4.
+			 */
+			dt_cg_xsetx(dlp, NULL, DT_LBL_NONE, BPF_REG_1, dev);
+			dt_cg_xsetx(dlp, NULL, DT_LBL_NONE, BPF_REG_2, ino);
+			emit(dlp, BPF_MOV_REG(BPF_REG_3, BPF_REG_FP));
+			emit(dlp, BPF_ALU64_IMM(BPF_ADD, BPF_REG_3, DT_TRAMP_SP_SLOT(0)));
+			emit(dlp, BPF_MOV_IMM(BPF_REG_4, 8));
+			emit(dlp, BPF_CALL_HELPER(BPF_FUNC_get_ns_current_pid_tgid));
+			emit(dlp, BPF_LOAD(BPF_W, BPF_REG_0, BPF_REG_FP,
+					   DT_TRAMP_SP_SLOT(0) + 4));
+		} else {
+			emit(dlp, BPF_CALL_HELPER(BPF_FUNC_get_current_pid_tgid));
+			emit(dlp, BPF_ALU64_IMM(BPF_RSH, BPF_REG_0, 32));
+		}
+		emit(dlp, BPF_BRANCH_IMM(BPF_JNE, BPF_REG_0, getpid(),
+					 pcb->pcb_fastlbl));
+		emit(dlp, BPF_MOV_REG(BPF_REG_1, BPF_REG_6));
+	}
 
 	dt_cg_tramp_prologue_act(pcb, act);
 
-- 
2.50.1 (Apple Git-155)