[DTrace-devel] [PATCH v3 16/21] drti: re-register DOF after fork

Nick Alcock nick.alcock at oracle.com
Tue Jan 16 21:13:12 UTC 2024


DTrace v2 doesn't automatically copy tracing sessions across fork boundaries
the way v1 used to (in the kernel).  Instead. re-register DOF in the forked
child just like it was registered for the original process at ELF
constructor invocation time.

We do this using pthread_atfork, but older glibc puts pthread_atfork in
-lpthread which clients are obviously not required to link with; so we spot
such old glibcs and directly call the corresponding not-exactly-internal
glibc function if found.  This function is always in libc; a call to it is
found in libc_nonshared and it is directly used by systemd, so it's
ABI-stable and safe to call.)

In the process, clean up some messes: moving the device file I/O into a
separate function means we no longer need to close it on error in a dozen
places; don't mess around with temporary variables just to open
/proc/self/maps, which is a literal string (back in the day it used to be
/proc/%i/maps where %i was getpid(), but not for years).

(Technically, calling ioctl() from a pthread_atfork() handler is dodgy as it
is not async-signal-safe. In practice, in this implementation, it's fine.
The use of dprintf() in error messages is more likely to be problematic.)

Signed-off-by: Nick Alcock <nick.alcock at oracle.com>
---
 Makeconfig             |  1 +
 include/sys/compiler.h |  2 ++
 libdtrace/drti.c       | 75 +++++++++++++++++++++++++++++-------------
 3 files changed, 56 insertions(+), 22 deletions(-)

diff --git a/Makeconfig b/Makeconfig
index 08cb48101f9a..77d4700f21c5 100644
--- a/Makeconfig
+++ b/Makeconfig
@@ -95,6 +95,7 @@ $(CONFIG_MK):
 $(eval $(call check-symbol-rule,ELF_GETSHDRSTRNDX,elf_getshdrstrndx,elf))
 $(eval $(call check-symbol-rule,LIBCTF,ctf_open,ctf))
 $(eval $(call check-symbol-rule,STRRSTR,strrstr,c))
+$(eval $(call check-symbol-rule,PTHREAD_ATFORK,pthread_atfork,c))
 $(eval $(call check-symbol-rule,LIBSYSTEMD,sd_notify,systemd))
 ifndef WANTS_LIBFUSE2
 $(eval $(call check-symbol-rule,FUSE_LOG,fuse_set_log_func,fuse3))
diff --git a/include/sys/compiler.h b/include/sys/compiler.h
index 766a955346e8..44eda579ee42 100644
--- a/include/sys/compiler.h
+++ b/include/sys/compiler.h
@@ -39,6 +39,7 @@
 #define _dt_printflike_(string_index,first_to_check) __attribute__((__format__(__printf__,(string_index),(first_to_check))))
 #define _dt_unused_ __attribute__((__unused__))
 #define _dt_noreturn_ __attribute__((__noreturn__))
+#define _dt_weak_ __attribute__((__weak__))
 #define _dt_unlikely_(x) __builtin_expect((x),0)
 #define _dt_barrier_(x) __asm__ __volatile__("": :"r"(x):"memory")
 
@@ -47,6 +48,7 @@
 #define _dt_constructor_(x) _Pragma("init(" #x ")")
 #define _dt_destructor_(x) _Pragma("fini(" #x ")")
 #define _dt_noreturn_
+#define _dt_weak_
 #define _dt_unlikely_(x) (x)
 #define _dt_barrier_(x)
 
diff --git a/libdtrace/drti.c b/libdtrace/drti.c
index e1252b4fe02b..0762fac95a85 100644
--- a/libdtrace/drti.c
+++ b/libdtrace/drti.c
@@ -12,6 +12,7 @@
 #include <sys/dtrace.h>
 #include <sys/compiler.h>
 #include <sys/ioctl.h>
+#include <pthread.h>
 
 #include <gelf.h>
 
@@ -37,6 +38,32 @@ static const char *modname;	/* Name of this load object */
 static int gen;			/* DOF helper generation */
 extern dof_hdr_t __SUNW_dof;	/* DOF defined in the .SUNW_dof section */
 static boolean_t dof_init_debug = B_FALSE;	/* From DTRACE_DOF_INIT_DEBUG */
+static dof_helper_t dh;
+
+static void dtrace_dof_register(void);
+
+static int
+private_pthread_atfork(void (*prepare)(void), void (*parent)(void),
+		       void (*child)(void))
+{
+  /* Sufficiently old glibc doesn't define pthread_atfork in libc, so we have to
+     use an internal interface instead in order to not force all probe users to
+     pull in -lpthread.  This internal interface is used by the pthread_atfork
+     implementation in libc_nonshared.a in all glibcs new enough not to be
+     affected by this problem, so there are no stable-ABI concerns here: the ABI
+     is stable regardless.  */
+
+#ifdef HAVE_PTHREAD_ATFORK
+	return pthread_atfork(prepare, parent, child);
+#else
+	extern int __register_atfork(void (*prepare) (void),
+				     void (*parent) (void),
+				     void (*child) (void), void *dso_handle);
+	extern void *__dso_handle _dt_weak_;
+
+	return __register_atfork(prepare, parent, child, __dso_handle);
+#endif
+}
 
 _dt_constructor_(dtrace_dof_init)
 static void
@@ -48,12 +75,9 @@ dtrace_dof_init(void)
 #else
 	Elf32_Ehdr *elf;
 #endif
-	dof_helper_t dh;
 	struct link_map *lmp = NULL;
 	Lmid_t lmid = -1;
-	int fd;
 	const char *p;
-	char mfn[PATH_MAX];		/* "/proc/<pid>/maps" */
 	char str[4096];			/* read buffer */
 	char *enm = NULL;		/* pointer to target executable name */
 	FILE *fp;
@@ -68,30 +92,22 @@ dtrace_dof_init(void)
 	if ((p = getenv("DTRACE_DOF_INIT_DEVNAME")) != NULL)
 		devname = p;
 
-	if ((fd = open(devname, O_RDWR)) < 0) {
-		if (dof_init_debug)
-			dprintf(2, "DRTI: Failed to open helper device %s\n",
-				devname);
-		return;
-	}
-
 #if 0
 	if (dlinfo(RTLD_SELF, RTLD_DI_LINKMAP, &lmp) == -1) {
 		dprintf(2, "DRTI: Couldn't discover module name or address.\n");
-                goto out;
+		return;
 	}
 
 	if (dlinfo(RTLD_SELF, RTLD_DI_LMID, &lmid) == -1) {
 		dprintf(2, "DRTI: Couldn't discover link map ID.\n");
-                goto out;
+		return;
 	}
 #else
 	lmid = 0;			/* We need a way to determine this. */
 
-	snprintf(mfn, sizeof(mfn), "/proc/self/maps");
-	if ((fp = fopen(mfn, "re")) == NULL) {
+	if ((fp = fopen("/proc/self/maps", "re")) == NULL) {
 		dprintf(2, "DRTI: Failed to open maps file.\n");
-                goto out;
+		return;
 	}
 	while (fgets(str, sizeof(str), fp) != NULL) {
 		uintptr_t	start, end;
@@ -119,7 +135,7 @@ dtrace_dof_init(void)
 	if (_dt_unlikely_(enm == NULL)) {
 		fclose(fp);
 		dprintf(2, "DRTI: Couldn't discover module name or address.\n");
-		goto out;
+		return;
 	}
 
 	/* Now start at the beginning & look for 1st segment of the target */
@@ -150,7 +166,7 @@ dtrace_dof_init(void)
 #endif
 	if (_dt_unlikely_(lmp == NULL)) {
 		dprintf(2, "DRTI: Couldn't discover module name or address.\n");
-                goto out;
+		return;
 	}
 
 	if ((modname = strrchr(lmp->l_name, '/')) == NULL)
@@ -164,7 +180,7 @@ dtrace_dof_init(void)
 	    dof->dofh_ident[DOF_ID_MAG3] != DOF_MAG_MAG3) {
 		dprintf(2, "DRTI: .SUNW_dof section corrupt in %s.\n",
 			lmp->l_name);
-                goto out;
+		return;
 	}
 
 	elf = (void *)lmp->l_addr;
@@ -179,14 +195,29 @@ dtrace_dof_init(void)
 		(void) snprintf(dh.dofhp_mod, sizeof (dh.dofhp_mod),
 		    "LM%lu`%s", lmid, modname);
 	}
+	dtrace_dof_register();
+	private_pthread_atfork(NULL, NULL, dtrace_dof_register);
+}
+
+static void
+dtrace_dof_register(void)
+{
+	int fd;
+
+	if ((fd = open(devname, O_RDWR, O_CLOEXEC)) < 0) {
+		if (dof_init_debug)
+			dprintf(2, "DRTI: Failed to open helper device %s\n",
+				devname);
+		return;
+	}
 
 	if ((gen = ioctl(fd, DTRACEHIOC_ADDDOF, &dh)) == -1)
-		dprintf(2, "DRTI: Ioctl failed for DOF at %p\n", (void *)dof);
+		dprintf(2, "DRTI: Ioctl failed for DOF at %llx\n",
+			(long long unsigned) dh.dofhp_addr);
 	else if (dof_init_debug)
-		dprintf(2, "DRTI: Ioctl OK for DOF at %p (gen %d)\n",
-			(void *)dof, gen);
+		dprintf(2, "DRTI: Ioctl OK for DOF at %llx (gen %d)\n",
+			(long long unsigned) dh.dofhp_addr, gen);
 
- out:
 	close(fd);
 }
 
-- 
2.43.0.272.gce700b77fd




More information about the DTrace-devel mailing list