Skip to content

Commit

Permalink
drti: re-register DOF after fork
Browse files Browse the repository at this point in the history
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@oracle.com>
Reviewed-by: Kris Van Hees <kris.van.hees@oracle.com>
  • Loading branch information
nickalcock authored and kvanhees committed Feb 22, 2024
1 parent 1cb8037 commit 6b59a97
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 24 deletions.
1 change: 1 addition & 0 deletions Makeconfig
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ help-overrides-header::
$(eval $(call check-symbol-rule,ELF_GETSHDRSTRNDX,elf_getshdrstrndx,elf))
$(eval $(call check-symbol-rule,LIBCTF,ctf_open,ctf,t))
$(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,t))
ifndef WANTS_LIBFUSE2
$(eval $(call check-symbol-rule,FUSE_LOG,fuse_set_log_func,fuse3))
Expand Down
4 changes: 3 additions & 1 deletion include/sys/compiler.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* Oracle Linux DTrace.
* Copyright (c) 2011, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved.
* Licensed under the Universal Permissive License v 1.0 as shown at
* http://oss.oracle.com/licenses/upl.
*/
Expand Down Expand Up @@ -37,6 +37,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")

Expand All @@ -45,6 +46,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)

Expand Down
77 changes: 54 additions & 23 deletions libdtrace/drti.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* Oracle Linux DTrace.
* Copyright (c) 2008, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2008, 2024, Oracle and/or its affiliates. All rights reserved.
* Licensed under the Universal Permissive License v 1.0 as shown at
* http://oss.oracle.com/licenses/upl.
*/
Expand All @@ -12,6 +12,7 @@
#include <sys/dtrace.h>
#include <sys/compiler.h>
#include <sys/ioctl.h>
#include <pthread.h>

#include <gelf.h>

Expand All @@ -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
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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)
Expand All @@ -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;
Expand All @@ -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);
}

Expand Down

0 comments on commit 6b59a97

Please sign in to comment.