[DTrace-devel] [PATCH v2 10/23] dtprobed, usdt: move usdt probe creation/deletion to dtrace

Nick Alcock nick.alcock at oracle.com
Mon Nov 27 16:47:16 UTC 2023


The original plan to track USDT probe existence via the existence of a
corresponding uprobe has been stymied by multiple problems:

 - removal of probes is difficult: dtprobed knows when all processes that
   registered DOF have unregistered it again, but that doesn't mean it's
   allowed to remove any of the corresponding uprobes. You can't remove a
   uprobe if it has any BPF associated with it, and the thing that does that
   is dtrace itself, which might well remove the BPF long after the
   processes that contained the corresponding uprobes are all dead: so
   dtrace is the only thing that can safely remove uprobes.

 - insertion of uprobes is limiting: passing the DTrace-side probe name down
   via probe arguments was clever, but alas probe arguments have much
   shorter maximum lengths than DTrace probes, so some valid DTrace probes
   cannot be registered as uprobes and end up with truncated names

 - it doesn't scale: the new probe registration/removal grinds to a halt
   after a few tens of thousands of uprobes are registered

The solution to this is to remove all the uprobe registration code from
dtprobed (goodbye, libcommon/uprobes.c) and migrate it all into
libdtrace/dt_prov_uprobe.c.  This is convenient because it's where PID probe
registration already happens, and it turns out we can reuse almost all the
code (and it even fixes a pre-existing bug, with uprobes naturally being
named based on inode-relative offset rather than address).

dtprobed loses all its probe-creation code and gains code to maintain the
DOF stash by calling the machinery added in the commit before this one; it
also handles DTRACEHIOC_REMOVE requests now (removing the DOF from the
stash), reparses DOF on upgrade as necessary, and cleans up DOF belonging to
dead PIDs now and then (every 128 requests, an arbitrary value).

When the testsuite is running a cleanup is done every five requests instead,
and hitting DTrace with a signal forces it to reparse all its DOF even
though it hasn't been upgraded, to test the reparsing machinery). Tests that
use this will be added in future commits.

As for DTrace itself, we keep the underlying/overlying probe distinction
from the dtprobed-uprobe-creation days, because one distinction which *does*
still exist is that pid probes are created by fiat of the dtrace user, while
usdt probes are restricted to those baked into the program: the "something
in dt_pid.c creates underlying probes corresponding to USDT probes" stuff
still works. But the method we use to find the USDT probes changes. Instead
of pre-scanning the /sys/kernel/debug/tracing/uprobe_events list and then
looking for probes that relate to the current process, we can just scan the
dof-pid directory in the DOF stash that relates to the specific PID we are
interested in, pull in the DOF, and generate probes from it.  Much simpler
and much less work.

But the pre-parsing has one tiny caveat. All the DOF we need is already
pre-parsed via the seccomp-jailed child of dtprobed and written to
files under /run/dtrace/stash/probes/$pid, but if dtprobed was upgraded
since this instance of DTrace started, and the new dtprobed has a newer
struct dof_parsed, we want to *ignore* any such (too-new) parsed DOF and not
register the corresponding probes.  The previous commit introduced a new
DOF_PARSED_VERSION #define which is bumped whenever struct dof_parsed
changes and gets stuck at the start of all parsed DOF in the DOF stash: we
just need to compare that to what's baked into this copy of DTrace, and
avoid using any parsed DOF for which those are different.

Right now this is nearly impossible to observe, because the DOF is almost
entirely scanned at startup, except for incoming dlopen()s (we don't observe
newly-started processes or anything): but a long-running dtrace watching a
process that is experiencing dlopen()s might still see this. In future, when
systemwide tracing is implemented and we can observe new processes with DOF
appearing while DTrace runs, this will become more significant.

Other tweaks in this commit:

 - rationalize the parser_in_pipe/parser_out_pipe stuff in dtprobed: rather
   than being arrays, this is all hidden and the parser_in_pipe is simply
   the thing we read from while the parser_out_pipe is what we write to.

 - move all the (dev, ino) determination out of process_dof() so we can
   use it as the reparsing function when reparse_dof() is called, as well
   as using it to do the initial parsing; at reparse time the (dev, ino)
   comes from directory names in the DOF stash, not inspection of the actual
   mappings via the libproc API

 - the FUSE loops were buggy and didn't handle dtprobed getting hit by a
   signal well: we were spotting -EINTR when a fuse_session_receive_buf()
   happened, but dtprobed spends almost all its time blocked on poll() and
   that wasn't handling -EINTR at all.  Now we're planning to intentionally
   hit dtprobed with signals during testing, this needs fixing.

 - add a -s option to dtprobed to allow the DOF stash directory to be
   set. This is mostly going to be used by in-tree testing, which points the
   DOF stash to a directory under build/ to avoid collisions with any
   dtprobed that may be running systemwide.  DTrace gains a new dofstashpath
   option for the same reason.  There is no need to document these: like
   dtprobed's -n option and the DTRACE_DOF_INIT_DEVNAME environment
   variable, these are really only testsuite internal implementation
   details.

Signed-off-by: Nick Alcock <nick.alcock at oracle.com>
---
 dtprobed/dtprobed.c                | 253 +++++++++----
 include/dtrace/pid.h               |   6 +-
 libcommon/Build                    |   4 +-
 libcommon/dof_parser.h             |   2 +-
 libcommon/dof_parser_host.c        |   6 +-
 libcommon/uprobes.c                | 343 ------------------
 libcommon/uprobes.h                |  35 --
 libdtrace/dt_impl.h                |   9 +-
 libdtrace/dt_open.c                |   4 +-
 libdtrace/dt_options.c             |  18 +
 libdtrace/dt_pid.c                 | 560 ++++++++++++++++-------------
 libdtrace/dt_pid.h                 |   1 -
 libdtrace/dt_proc.h                |   1 -
 libdtrace/dt_prov_uprobe.c         | 135 +++++--
 runtest.sh                         |  14 +-
 test/unittest/usdt/tst.dlclose2.sh |   2 +-
 16 files changed, 624 insertions(+), 769 deletions(-)
 delete mode 100644 libcommon/uprobes.c
 delete mode 100644 libcommon/uprobes.h

diff --git a/dtprobed/dtprobed.c b/dtprobed/dtprobed.c
index 64fb9fa47b19..9bf8a18f59e2 100644
--- a/dtprobed/dtprobed.c
+++ b/dtprobed/dtprobed.c
@@ -1,5 +1,5 @@
 /*
- * Oracle Linux DTrace; DOF-consumption and USDT-probe-creation daemon.
+ * Oracle Linux DTrace; DOF-consumption and storage daemon.
  * Copyright (c) 2022, 2023, 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.
@@ -8,6 +8,7 @@
 #include <sys/param.h>
 #include <sys/uio.h>
 #include <sys/wait.h>
+#include <assert.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <malloc.h>
@@ -54,7 +55,7 @@
 
 #include <dt_list.h>
 #include "dof_parser.h"
-#include "uprobes.h"
+#include "dof_stash.h"
 #include "libproc.h"
 
 #include "seccomp-assistance.h"
@@ -62,7 +63,7 @@
 #define DOF_MAXSZ 512 * 1024 * 1024
 #define DOF_CHUNKSZ 64 * 1024
 
-#define CLEANUP_INTERVAL 128
+static int cleanup_interval = 128;
 
 static struct fuse_session *cuse_session;
 
@@ -71,8 +72,8 @@ static int foreground;
 void dt_debug_dump(int unused) {} 		/* For libproc.  */
 
 static pid_t parser_pid;
-static int parser_in_pipe[2];
-static int parser_out_pipe[2];
+static int parser_in_pipe;
+static int parser_out_pipe;
 static int sync_fd = -1;
 static int timeout = 5000; 			/* In seconds.  */
 static int rq_count = 0;
@@ -81,13 +82,14 @@ static void helper_ioctl(fuse_req_t req, int cmd, void *arg,
 			 struct fuse_file_info *fi, unsigned int flags,
 			 const void *in_buf, size_t in_bufsz, size_t out_bufsz);
 
-static int process_dof(fuse_req_t req, int out, int in, pid_t pid,
-		       dof_helper_t *dh, const void *in_buf);
-
 static const struct cuse_lowlevel_ops dtprobed_clop = {
 	.ioctl = helper_ioctl,
 };
 
+static int
+process_dof(pid_t pid, int out, int in, dev_t dev, ino_t inum, dof_helper_t *dh,
+	    const void *in_buf, size_t in_bufsz, int reparsing);
+
 static void
 log_msg(enum fuse_log_level level, const char *fmt, va_list ap)
 {
@@ -321,9 +323,16 @@ parse_dof(int in, int out)
 static void
 dof_parser_start(void)
 {
-	if ((pipe(parser_in_pipe) < 0) ||
-	    (pipe(parser_out_pipe) < 0))
-		daemon_perr(sync_fd, "cannot create DOF parser pipes", errno);
+	int parser_in[2], parser_out[2];
+	if ((pipe(parser_in) < 0) ||
+	    (pipe(parser_out) < 0)) {
+		fuse_log(FUSE_LOG_ERR, "cannot create DOF parser pipes: %s",
+			 strerror(errno));
+		exit(1);
+	}
+
+	parser_out_pipe = parser_in[1];
+	parser_in_pipe = parser_out[0];
 
 	switch (parser_pid = fork()) {
 	case -1: {
@@ -337,8 +346,8 @@ dof_parser_start(void)
 		 * seccomp jail.
 		 */
 		close(session_fd(cuse_session));
-		close(parser_in_pipe[1]);
-		close(parser_out_pipe[0]);
+		close(parser_in[1]);
+		close(parser_out[0]);
 		if (!foreground) {
 			close(sync_fd);
 			sync_fd = -1;
@@ -364,14 +373,14 @@ dof_parser_start(void)
 			if (syscall(SYS_seccomp, SECCOMP_SET_MODE_STRICT, 0, NULL) < 0)
 				_exit(1);
 
-		while (parse_dof(parser_in_pipe[0], parser_out_pipe[1]))
+		while (parse_dof(parser_in[0], parser_out[1]))
 			;
 		_exit(0);
 	}
 	}
 
-	close(parser_in_pipe[0]);
-	close(parser_out_pipe[1]);
+	close(parser_in[0]);
+	close(parser_out[1]);
 }
 
 /*
@@ -389,15 +398,15 @@ dof_parser_tidy(int restart)
 	if (errno != ESRCH)
 		while (waitpid(parser_pid, &status, 0) < 0 && errno == EINTR);
 
-	close(parser_in_pipe[1]);
-	close(parser_out_pipe[0]);
+	close(parser_in_pipe);
+	close(parser_out_pipe);
 
 	if (restart)
 		dof_parser_start();
 }
 
 static dof_parsed_t *
-dof_read(fuse_req_t req, int in)
+dof_read(pid_t pid, int in)
 {
 	dof_parsed_t *reply = dof_parser_host_read(in, timeout);
 
@@ -410,8 +419,7 @@ dof_read(fuse_req_t req, int in)
 	if (reply->type == DIT_ERR) {
 		errno = reply->err.err_no;
 		fuse_log(FUSE_LOG_WARNING, "%i: dtprobed: DOF parsing error: "
-			 "%s\n", fuse_req_ctx(req)->pid,
-			 reply->err.err);
+			 "%s\n", pid, reply->err.err);
 		free(reply);
 		reply = NULL;
 	}
@@ -420,23 +428,39 @@ dof_read(fuse_req_t req, int in)
 }
 
 /*
- * Create probes as requested by the dof_parsed_t parsed from the DOF.
- * The DOF parser has already applied the l_addr offset derived from the client
- * process's dynamic linker.
+ * Get the prmap_t of a passed-in address's mapping.
  */
-static void
-create_probe(ps_prochandle *P, dof_parsed_t *provider, dof_parsed_t *probe,
-    dof_parsed_t *tp)
+static const int
+mapping_dev_inum(pid_t pid, uintptr_t addr, dev_t *dev, ino_t *inum)
 {
-	const char *mod, *fun, *prb;
+	ps_prochandle *P;
+	const prmap_t *mapp;
+	int err = 0;
 
-	mod = probe->probe.name;
-	fun = mod + strlen(mod) + 1;
-	prb = fun + strlen(fun) + 1;
+	if ((P = Pgrab(pid, 2, 0, NULL, &err)) == NULL) {
+		fuse_log(FUSE_LOG_ERR, "%i: dtprobed: process grab failed: %s\n",
+			 pid, strerror(err));
+		return -1;
+	}
 
-	free(uprobe_create_from_addr(P, tp->tracepoint.addr,
-		tp->tracepoint.is_enabled, provider->provider.name,
-		mod, fun, prb));
+	mapp = Paddr_to_map(P, addr);
+
+	err = -1;
+        if (mapp == NULL) {
+		fuse_log(FUSE_LOG_ERR, "%i: dtprobed: cannot look up mapping (process dead?)\n",
+			 pid);
+		goto out;
+	}
+
+        *dev = mapp->pr_dev;
+	*inum = mapp->pr_inum;
+
+	err = 0;
+out:
+	Prelease(P, PS_RELEASE_NORMAL);
+	Pfree(P);
+
+	return err;
 }
 
 /*
@@ -453,6 +477,9 @@ helper_ioctl(fuse_req_t req, int cmd, void *arg,
 	dtprobed_userdata_t *userdata = get_userdata(pid);
 	const char *errmsg;
 	const void *buf;
+	dev_t dev = 0;
+	ino_t inum = 0;
+	int gen;
 
 	/*
 	 * We can just ignore FUSE_IOCTL_COMPAT: the 32-bit and 64-bit versions
@@ -462,8 +489,11 @@ helper_ioctl(fuse_req_t req, int cmd, void *arg,
 	switch (cmd) {
 	case DTRACEHIOC_ADDDOF:
 		break;
-	case DTRACEHIOC_REMOVE: /* TODO */
-		fuse_reply_ioctl(req, 0, NULL, 0);
+	case DTRACEHIOC_REMOVE:
+                gen = dof_stash_remove(pid, (uintptr_t) arg);
+		fuse_log(FUSE_LOG_DEBUG, "DTRACEHIOC_REMOVE from PID %i, generation %lu\n",
+			 pid, (uintptr_t) arg);
+		fuse_reply_ioctl(req, gen, NULL, 0);
 		return;
 	default: errmsg = "invalid ioctl";;
 		fuse_log(FUSE_LOG_WARNING, "%i: dtprobed: %s %x\n",
@@ -636,11 +666,15 @@ helper_ioctl(fuse_req_t req, int cmd, void *arg,
 	if (userdata->buf)
 		buf = userdata->buf;
 
-	if (process_dof(req, parser_in_pipe[1], parser_out_pipe[0], pid,
-			&userdata->dh, buf) < 0)
+	if ((mapping_dev_inum(pid, userdata->dh.dofhp_dof, &dev, &inum)) < 0)
 		goto process_err;
 
-	if (fuse_reply_ioctl(req, 0, NULL, 0) < 0)
+	if ((gen = process_dof(pid, parser_out_pipe, parser_in_pipe,
+			       dev, inum, &userdata->dh, buf,
+			       userdata->dof_hdr.dofh_loadsz, 0)) < 0)
+		goto process_err;
+
+	if (fuse_reply_ioctl(req, gen, NULL, 0) < 0)
 		goto process_err;
 
 	free(userdata->buf);
@@ -651,8 +685,9 @@ helper_ioctl(fuse_req_t req, int cmd, void *arg,
 	 * Periodically clean up old userdata (applying to pids with no live
 	 * transaction, or pids which no longer exist).
 	 */
-	if (rq_count++ > CLEANUP_INTERVAL) {
+	if (rq_count++ > cleanup_interval) {
 		cleanup_userdata();
+		dof_stash_prune_dead();
 		rq_count = 0;
 	}
 
@@ -681,24 +716,21 @@ helper_ioctl(fuse_req_t req, int cmd, void *arg,
 }
 
 /*
- * Process some DOF, passing it to the parser and creating probes from it.
+ * Process some DOF, passing it to the parser and stashing it away for later.
+ *
+ * If reparsing is set, we are re-parsing existing DOF and should only update
+ * the parsed DOF representation.
  */
 static int
-process_dof(fuse_req_t req, int out, int in, pid_t pid,
-	    dof_helper_t *dh, const void *in_buf)
+process_dof(pid_t pid, int out, int in, dev_t dev, ino_t inum, dof_helper_t *dh,
+	    const void *in_buf, size_t in_bufsz, int reparsing)
 {
-	int perr = 0;
-	ps_prochandle *P;
 	dof_parsed_t *provider;
-	const char *errmsg;
 	size_t i;
 	size_t tries = 0;
-
-	if ((P = Pgrab(pid, 2, 0, NULL, &perr)) == NULL) {
-		fuse_log(FUSE_LOG_ERR, "%i: dtprobed: process grab failed: %s\n",
-			 pid, strerror(perr));
-		goto proc_err;
-	}
+	int gen = 0;
+	const char *errmsg;
+	dt_list_t accum = {0};
 
 	do {
 		errmsg = "DOF parser write failed";
@@ -714,7 +746,7 @@ process_dof(fuse_req_t req, int out, int in, pid_t pid,
 		 */
 
 		errmsg = "parsed DOF read failed";
-		provider = dof_read(req, parser_out_pipe[0]);
+		provider = dof_read(pid, in);
 		if (!provider) {
 			if (tries++ > 1)
 				goto err;
@@ -726,47 +758,62 @@ process_dof(fuse_req_t req, int out, int in, pid_t pid,
 		break;
 	} while (!provider);
 
+	if (dof_stash_push_parsed(&accum, provider) < 0)
+		goto oom;
+
 	for (i = 0; i < provider->provider.nprobes; i++) {
-		dof_parsed_t *probe = dof_read(req, in);
+		dof_parsed_t *probe = dof_read(pid, in);
 		size_t j;
 
 		errmsg = "no probes in this provider, or parse state corrupt";
 		if (!probe || probe->type != DIT_PROBE)
 			goto err;
 
+		if (dof_stash_push_parsed(&accum, probe) < 0)
+			goto oom;
+
 		for (j = 0; j < probe->probe.ntp; j++) {
-			dof_parsed_t *tp = dof_read(req, in);
+			dof_parsed_t *tp = dof_read(pid, in);
 
 			errmsg = "no tracepoints in a probe, or parse state corrupt";
 			if (!tp || tp->type != DIT_TRACEPOINT)
 				goto err;
 
-			/*
-			 * Ignore errors here: we want to create as many probes
-			 * as we can, even if creation of some of them fails.
-			 */
-			create_probe(P, provider, probe, tp);
-			free(tp);
+			if (dof_stash_push_parsed(&accum, tp) < 0)
+				goto oom;
 		}
-		free(probe);
 	}
-	free(provider);
 
-	Prelease(P, PS_RELEASE_NORMAL);
-	Pfree(P);
+	if (!reparsing)
+		if ((gen = dof_stash_add(pid, dev, inum, dh, in_buf, in_bufsz)) < 0)
+			goto fileio;
 
-	return 0;
+	if (dof_stash_write_parsed(pid, dev, inum, &accum) < 0) {
+		dof_stash_flush(&accum);
+		dof_stash_remove(pid, gen);
+		goto fileio;
+	}
+	dof_stash_flush(&accum);
+
+	return gen;
+
+oom:
+	fuse_log(FUSE_LOG_ERR, "%i: out of memory parsing DOF\n", pid);
+	goto proc_err;
 
 err:
 	fuse_log(FUSE_LOG_ERR, "%i: dtprobed: parser error: %s\n", pid, errmsg);
+	goto proc_err;
 
-	Prelease(P, PS_RELEASE_NORMAL);
-	Pfree(P);
+fileio:
+	fuse_log(FUSE_LOG_ERR, "%i: dtprobed: I/O error stashing DOF: %s\n",
+		 pid, strerror(errno));
 
 proc_err:
+	dof_stash_flush(&accum);
 	dof_parser_tidy(1);
 	return -1;
-}	
+}
 
 #if HAVE_LIBFUSE3
 static int
@@ -780,8 +827,11 @@ loop(void)
 	fds[0].events = POLLIN;
 
 	while (!fuse_session_exited(cuse_session)) {
-		if ((ret = poll(fds, 1, -1)) < 0)
+		if (poll(fds, 1, -1) < 0) {
+			if (errno == EINTR)
+				continue;
 			break;
+		}
 
 		if (fds[0].revents != 0) {
 			if ((ret = fuse_session_receive_buf(cuse_session,
@@ -789,7 +839,7 @@ loop(void)
 				if (ret == -EINTR)
 					continue;
 
-				break;
+                                break;
 			}
 
 			fuse_session_process_buf(cuse_session, &fbuf);
@@ -823,8 +873,11 @@ loop(void)
 		struct fuse_buf fbuf = { .mem = buf, .size = bufsize };
 		struct fuse_chan *tmpch = cuse_chan;
 
-		if ((ret = poll(fds, 1, -1)) < 0)
+		if (poll(fds, 1, -1) < 0) {
+			if (errno == EINTR)
+				continue;
 			break;
+		}
 
 		if (fds[0].revents != 0) {
 			if ((ret = fuse_session_receive_buf(cuse_session,
@@ -849,6 +902,19 @@ loop(void)
 }
 #endif
 
+/*
+ * Force a reparse of all parsed DOF on demand.
+ *
+ * Only hooked up during in-source-tree testing.
+ */
+static void
+force_reparse(int sig)
+{
+	fuse_log(FUSE_LOG_DEBUG, "Forced reparse\n");
+	reparse_dof(parser_out_pipe, parser_in_pipe, process_dof, 1);
+	fuse_log(FUSE_LOG_DEBUG, "Forced reparse complete\n");
+}
+
 static void
 usage(void)
 {
@@ -861,6 +927,7 @@ main(int argc, char *argv[])
 {
 	int opt;
 	char *devname = "dtrace/helper";
+	char *statedir = NULL;
 	int ret;
 	struct sigaction sa = {0};
 
@@ -871,7 +938,7 @@ main(int argc, char *argv[])
 	char *fuse_argv[] = { argv[0], "-f", "-s", NULL, NULL };
 	int fuse_argc = 3;
 
-	while ((opt = getopt(argc, argv, "Fdn:t:")) != -1) {
+	while ((opt = getopt(argc, argv, "Fs:dn:t:")) != -1) {
 		switch (opt) {
 		case 'F':
 			foreground = 1;
@@ -885,6 +952,13 @@ main(int argc, char *argv[])
 				fuse_argv[fuse_argc++] = "-d";
 			}
 			break;
+		case 's':
+			/*
+			 * This option is purely for the testsuite, so does not
+			 * appear in the usage() description.
+			 */
+			statedir = strdup(optarg);
+			break;
 		case 't':
 			timeout = atoi(optarg);
 			if (timeout <= 0) {
@@ -934,8 +1008,41 @@ main(int argc, char *argv[])
 	sa.sa_handler = SIG_IGN;
 	(void) sigaction(SIGPIPE, &sa, NULL);
 
+	/*
+	 * When doing (in-tree) testing, use a shorter cleanup interval, and
+	 * hook up a signal allowing the test scripts to force a DOF cleanup.
+	 */
+	if (getenv("_DTRACE_TESTING")) {
+		struct sigaction tmp = {0};
+
+		cleanup_interval = 5;
+		tmp.sa_handler = force_reparse;
+		tmp.sa_flags = SA_RESTART;
+		(void) sigaction(SIGUSR2, &tmp, NULL);
+	}
+
 	dof_parser_start();
 
+	if (dof_stash_init(statedir) < 0)
+		exit(1);
+
+	/*
+	 * Who knows what DOF-containing processes have died since the daemon
+	 * was running?  Clean them up.
+	 */
+	dof_stash_prune_dead();
+
+        /*
+	 * Make sure that old parsed DOF is deleted and reparsed: it might come
+	 * from a prior daemon invocation, with an older daemon version with a
+	 * different parsed DOF representation.  If we can't do this, don't try
+	 * restarting the daemon: it'll just fail again in the same way.
+	 */
+	if (reparse_dof(parser_out_pipe, parser_in_pipe, process_dof, 0) < 0) {
+		teardown_device();
+		exit(1);
+	}
+
 	if (!foreground) {
 		close(sync_fd);
 		sync_fd = -1;
diff --git a/include/dtrace/pid.h b/include/dtrace/pid.h
index 5e9668251dfd..001b2e28d8c7 100644
--- a/include/dtrace/pid.h
+++ b/include/dtrace/pid.h
@@ -28,10 +28,10 @@ typedef enum pid_probetype {
 
 typedef struct pid_probespec {
 	pid_probetype_t pps_type;		/* probe type */
-	char *pps_prv;				/* provider (without pid) */
+	char *pps_prv;				/* provider (with pid) */
 	char *pps_mod;				/* probe module (object) */
-	char *pps_fun;				/* probe function */
-	char *pps_prb;				/* probe name (if provided) */
+	const char *pps_fun;			/* probe function */
+	const char *pps_prb;			/* probe name (if provided) */
 	dev_t pps_dev;				/* object device node */
 	ino_t pps_inum;				/* object inode */
 	char *pps_fn;				/* object full filename */
diff --git a/libcommon/Build b/libcommon/Build
index c481cea3af81..ad858737618d 100644
--- a/libcommon/Build
+++ b/libcommon/Build
@@ -1,5 +1,5 @@
 # Oracle Linux DTrace.
-# Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2022, 2023, 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.
 
@@ -9,5 +9,5 @@ LIBS += libcommon
 libcommon_TARGET = libcommon
 libcommon_DIR := $(current-dir)
 libcommon_CPPFLAGS := -Ilibcommon -Ilibproc
-libcommon_SOURCES = dof_parser.c dof_parser_host.c uprobes.c dt_list.c
+libcommon_SOURCES = dof_parser.c dof_parser_host.c dt_list.c
 libcommon_LIBSOURCES = libcommon
diff --git a/libcommon/dof_parser.h b/libcommon/dof_parser.h
index 41ef5a193b7f..5ac177e85db7 100644
--- a/libcommon/dof_parser.h
+++ b/libcommon/dof_parser.h
@@ -111,7 +111,7 @@ typedef struct dof_parsed {
  *
  * Returns 0 on success or a positive errno value on error.
  */
-int dof_parser_host_write(int out, dof_helper_t *dh, dof_hdr_t *dof);
+int dof_parser_host_write(int out, const dof_helper_t *dh, dof_hdr_t *dof);
 
 /*
  * Read a single DOF structure from a parser pipe.  Wait at most TIMEOUT seconds
diff --git a/libcommon/dof_parser_host.c b/libcommon/dof_parser_host.c
index 4cf5b63b4eb6..2bcb1f27bff6 100644
--- a/libcommon/dof_parser_host.c
+++ b/libcommon/dof_parser_host.c
@@ -51,15 +51,15 @@ dof_parser_write_one(int out, const void *buf_, size_t size)
  * Returns 0 on success or a positive errno value on error.
  */
 int
-dof_parser_host_write(int out, dof_helper_t *dh, dof_hdr_t *dof)
+dof_parser_host_write(int out, const dof_helper_t *dh, dof_hdr_t *dof)
 {
 	int err;
 
-	if ((err = dof_parser_write_one(out, (char *)dh,
+	if ((err = dof_parser_write_one(out, (const char *)dh,
 					sizeof(dof_helper_t))) < 0)
 		return err;
 
-	return dof_parser_write_one(out, (char *)dof,
+	return dof_parser_write_one(out, (const char *)dof,
 				    dof->dofh_loadsz);
 }
 
diff --git a/libcommon/uprobes.c b/libcommon/uprobes.c
deleted file mode 100644
index 3bb423fca6d7..000000000000
--- a/libcommon/uprobes.c
+++ /dev/null
@@ -1,343 +0,0 @@
-/*
- * Oracle Linux DTrace.
- * Copyright (c) 2019, 2023, 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.
- */
-
-#include <ctype.h>
-#include <errno.h>
-#include <inttypes.h>
-#include <stdio.h>
-#include <string.h>
-#include <unistd.h>
-#include <libproc.h>
-#include <assert.h>
-#include <tracefs.h>
-
-/*
- * Return a uprobe spec for a given address in a given process handle.
- */
-char *
-uprobe_spec_by_addr(ps_prochandle *P, uint64_t addr, prmap_t *mapp_)
-{
-	char			*spec = NULL;
-	const prmap_t		*mapp, *first_mapp;
-	char			*mapfile_name = NULL;
-
-	mapp = Paddr_to_map(P, addr);
-	if (mapp == NULL)
-		goto out;
-
-	first_mapp = mapp->pr_file->first_segment;
-
-	/*
-	 * Use a name in /proc/$pid/map_files: this will work even if the
-	 * destination is in a different filesystem namespace.  Never use the
-	 * absolute path: not only might this not exist, but an *entirely
-	 * different file* might be found there in the namespace in which we
-	 * are running: prf_mapname is derived from /proc/$pid/maps, and the
-	 * names in there are not relative to the namespace of the reader
-	 * at all.
-	 */
-	mapfile_name = Pmap_mapfile_name(P, mapp);
-	if (!mapfile_name)
-		goto out;
-
-	/*
-	 * No need for error-checking here: we do the same on error
-	 * and success.
-	 */
-	asprintf(&spec, "%s:0x%lx", mapfile_name, addr - first_mapp->pr_vaddr);
-
-	if (mapp_)
-		memcpy(mapp_, mapp, sizeof(prmap_t));
-
-out:
-	free(mapfile_name);
-	return spec;
-}
-
-static const char hexdigits[] = "0123456789abcdef";
-
-/*
- * Encode a NAME suitably for representation in a uprobe.  All non-alphanumeric,
- * non-_ characters are replaced with __XX where XX is the hex encoding of the
- * ASCII code of the byte. __ itself is replaced with ___.
- */
-char *
-uprobe_encode_name(const char *name)
-{
-	const char *p = name;
-	char *out_p;
-	char *encoded;
-	size_t sz = strlen(name);
-
-	/*
-	 * Compute size changes needed.
-	 */
-
-	while ((p = strstr(p, "__")) != NULL) {
-		sz++;
-		p += 2;
-	}
-
-	for (p = name; *p != '\0'; p++) {
-		if (!isalpha(*p) && !isdigit(*p) && *p != '_')
-			sz += 3;
-	}
-
-	encoded = malloc(sz + 1);
-	if (!encoded)
-		return NULL;
-	out_p = encoded;
-
-	/* Apply translations.  */
-
-	for (p = name; *p != '\0'; p++) {
-		int hexencode = 0, underencode = 0;
-
-		if (!isalpha(*p) && !isdigit(*p) && *p != '_')
-			hexencode = 1;
-		if (p[0] == '_' && p[1] == '_' && p[2] != '\0')
-			underencode = 1;
-
-		if (underencode) {
-			*out_p++ = '_';
-			*out_p++ = '_';
-			*out_p++ = '_';
-			p++;
-			continue;
-		}
-
-		if (hexencode) {
-			*out_p++ = '_';
-			*out_p++ = '_';
-			*out_p++ = hexdigits[*p >> 4];
-			*out_p++ = hexdigits[*p & 0xf];
-		}
-		else
-			*out_p++ = *p;
-	}
-	*out_p = '\0';
-
-	return encoded;
-}
-
-/*
- * Decode a NAME: the converse of uprobe_encode_name.
- */
-char *
-uprobe_decode_name(const char *name)
-{
-	const char *p = name;
-	char *new_p, *out_p;
-	char *decoded;
-	size_t sz = strlen(name);
-
-	/*
-	 * Compute size changes needed.
-	 */
-
-	while ((p = strstr(p, "__")) != NULL) {
-		if (p[3] == '_') {
-			sz--;
-			p += 3;
-		}
-		else if (strspn(&p[2], hexdigits) >= 2) {
-			sz -= 3;
-			p += 4;
-		}
-	}
-
-	decoded = malloc(sz + 1);
-	if (!decoded)
-		return NULL;
-	out_p = decoded;
-
-	/* Apply translations.  */
-
-	p = name;
-	while ((new_p = strstr(p, "__")) != NULL) {
-
-		/*
-		 * Copy unchanged bytes.
-		 */
-		memcpy(out_p, p, new_p - p);
-		out_p += new_p - p;
-		p = new_p;
-
-		if (p[3] == '_') {
-			*out_p++ = '_';
-			*out_p++ = '_';
-			p += 3;
-		} else if (strspn(&p[2], hexdigits) >= 2) {
-			if (isdigit(p[2]))
-				*out_p = (p[2] - '0') << 4;
-			else
-				*out_p = (p[2] - 'a' + 10) << 4;
-			if (isdigit(p[3]))
-				*out_p += p[3] - '0';
-			else
-				*out_p += p[3] - 'a' + 10;
-			p += 4;
-			out_p++;
-		}
-		else {
-			*out_p++ = '_';
-			*out_p++ = '_';
-			p += 2;
-		}
-	}
-	/*
-	 * Copy the remainder.
-	 */
-	strcpy(out_p, p);
-
-	return decoded;
-}
-
-char *
-uprobe_name(dev_t dev, ino_t ino, uint64_t addr, int isret, int is_enabled)
-{
-	char	*name;
-
-	if (asprintf(&name, "dt_pid%s/%c_%llx_%llx_%lx", is_enabled?"_is_enabled":"",
-		     isret ? 'r' : 'p', (unsigned long long)dev,
-		     (unsigned long long)ino, (unsigned long)addr) < 0)
-		return NULL;
-
-	return name;
-}
-
-/*
- * Create a uprobe for a given device, address, and spec: the uprobe may be a
- * uretprobe.  Return the probe's name as a new dynamically-allocated string, or
- * NULL on error.  If prv/mod/fun/prb are all set, they are passed down as the
- * name of the corresponding DTrace probe.
- */
-char *
-uprobe_create_named(dev_t dev, ino_t ino, uint64_t addr, const char *spec, int isret,
-		    int is_enabled, const char *prv, const char *mod, const char *fun,
-		    const char *prb)
-{
-	int	fd = -1;
-	int	rc = -1;
-	char	*name, *args = NULL;
-
-	if (prv && mod && fun && prb) {
-		char *eprv, *emod, *efun, *eprb;
-		int failed = 0;
-
-		eprv = uprobe_encode_name(prv);
-		emod = uprobe_encode_name(mod);
-		efun = uprobe_encode_name(fun);
-		eprb = uprobe_encode_name(prb);
-
-		if (eprv && emod && efun && eprb) {
-			if (asprintf(&args, "P%s=\\1 M%s=\\2 F%s=\\3 N%s=\\4",
-				     eprv, emod, efun, eprb) < 0)
-				failed = 1;
-		} else
-			failed = 1;
-
-		free(eprv);
-		free(emod);
-		free(efun);
-		free(eprb);
-
-		if (failed)
-			return NULL;
-	}
-
-	name = uprobe_name(dev, ino, addr, isret, is_enabled);
-	if (!name)
-		goto out;
-
-	/* Add the uprobe. */
-	fd = open(TRACEFS "uprobe_events", O_WRONLY | O_APPEND);
-	if (fd == -1)
-		goto out;
-
-	rc = dprintf(fd, "%c:%s %s %s\n", isret ? 'r' : 'p', name, spec,
-		     args ? args : "");
-
-out:
-	if (fd != -1)
-		close(fd);
-	free(args);
-	if (rc < 0) {
-		free(name);
-		return NULL;
-	}
-
-	return name;
-}
-
-/*
- * Like uprobe_create, but do not specify the name of a corresponding DTrace
- * probe.  (Used when the caller already knows what probe will be needed, and
- * there is no possibility of another DTrace having to pick it up from the
- * systemwide uprobe list.)
- */
-char *
-uprobe_create(dev_t dev, ino_t ino, uint64_t addr, const char *spec, int isret,
-	int is_enabled)
-{
-	return uprobe_create_named(dev, ino, addr, spec, isret, is_enabled,
-				   NULL, NULL, NULL, NULL);
-}
-
-/*
- * Create a uprobe given a particular process and address.  Return the probe's
- * name as a new dynamically-allocated string, or NULL on error.  If
- * prv/mod/fun/prb are set, they are passed down as the name of the
- * corresponding DTrace probe.
- */
-char *
-uprobe_create_from_addr(ps_prochandle *P, uint64_t addr, int is_enabled,
-			const char *prv, const char *mod, const char *fun,
-			const char *prb)
-{
-	char *spec;
-	char *name;
-	prmap_t mapp;
-
-	spec = uprobe_spec_by_addr(P, addr, &mapp);
-	if (!spec)
-		return NULL;
-
-	addr -= mapp.pr_file->first_segment->pr_vaddr;
-	name = uprobe_create_named(mapp.pr_dev, mapp.pr_inum, addr, spec, 0,
-				   is_enabled, prv, mod, fun, prb);
-	free(spec);
-	return name;
-}
-
-/*
- * Destroy a uprobe for a given device and address.
- */
-int
-uprobe_delete(dev_t dev, ino_t ino, uint64_t addr, int isret, int is_enabled)
-{
-	int	fd = -1;
-	int	rc = -1;
-	char	*name;
-
-	name = uprobe_name(dev, ino, addr, isret, is_enabled);
-	if (!name)
-		goto out;
-
-	fd = open(TRACEFS "uprobe_events", O_WRONLY | O_APPEND);
-	if (fd == -1)
-		goto out;
-
-	rc = dprintf(fd, "-:%s\n", name);
-
-out:
-	if (fd != -1)
-		close(fd);
-	free(name);
-
-	return rc < 0 ? -1 : 0;
-}
diff --git a/libcommon/uprobes.h b/libcommon/uprobes.h
deleted file mode 100644
index dac2872e5268..000000000000
--- a/libcommon/uprobes.h
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Oracle Linux DTrace; simple uprobe helper functions
- * Copyright (c) 2022, 2023, 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.
- */
-
-#ifndef	_UPROBES_H
-#define	_UPROBES_H
-
-#include <sys/types.h>
-#include <inttypes.h>
-#include <libproc.h>
-#include <unistd.h>
-
-extern char *uprobe_spec_by_addr(ps_prochandle *P, uint64_t addr,
-				 prmap_t *mapp);
-extern char *uprobe_name(dev_t dev, ino_t ino, uint64_t addr, int isret,
-			 int is_enabled);
-extern char *uprobe_create_named(dev_t dev, ino_t ino, uint64_t addr,
-				 const char *spec, int isret, int is_enabled,
-				 const char *prv, const char *mod,
-				 const char *fun, const char *prb);
-extern char *uprobe_create(dev_t dev, ino_t ino, uint64_t addr, const char *spec,
-			   int isret, int is_enabled);
-extern char *uprobe_create_from_addr(ps_prochandle *P, uint64_t addr,
-				     int is_enabled, const char *prv,
-				     const char *mod, const char *fun,
-				     const char *prb);
-extern int uprobe_delete(dev_t dev, ino_t ino, uint64_t addr, int isret,
-			 int is_enabled);
-extern char *uprobe_encode_name(const char *);
-extern char *uprobe_decode_name(const char *);
-
-#endif /* _UPROBES_H */
diff --git a/libdtrace/dt_impl.h b/libdtrace/dt_impl.h
index 98a7491022f4..7d0dc380edd4 100644
--- a/libdtrace/dt_impl.h
+++ b/libdtrace/dt_impl.h
@@ -88,7 +88,6 @@ struct dt_pfdict;		/* see <dt_printf.h> */
 struct dt_arg;			/* see below */
 struct dt_provider;		/* see <dt_provider.h> */
 struct dt_probe;		/* see <dt_probe.h> */
-struct pid_probespec;		/* see <pid.h> */
 struct dt_pebset;		/* see <dt_peb.h> */
 struct dt_xlator;		/* see <dt_xlator.h> */
 
@@ -355,13 +354,6 @@ struct dtrace_hdl {
 	size_t dt_probes_sz;	/* size of array of probes */
 	uint32_t dt_probe_id;	/* next available probe id */
 
-	/*
-	 * uprobes potentially of interest: some may be instantiated as
-	 * dtrace probes.
-	 */
-	struct pid_probespec *dt_uprobespecs;
-	size_t dt_uprobespecs_sz; /* size of array of uprobes */
-
 	struct dt_probe *dt_error; /* ERROR probe */
 
 	dt_htab_t *dt_provs;	/* hash table of dt_provider_t's */
@@ -399,6 +391,7 @@ struct dtrace_hdl {
 	dt_list_t dt_lib_path;	/* linked-list forming library search path */
 	char *dt_module_path;	/* pathname of kernel module root */
 	dt_version_t dt_kernver;/* kernel version, used in the libpath */
+	char *dt_dofstash_path;	/* Path to the DOF stash.  */
 	uid_t dt_useruid;	/* lowest non-system uid: set via -xuseruid */
 	char *dt_sysslice;	/* the systemd system slice: set via -xsysslice */
 	uint_t dt_lazyload;	/* boolean:  set via -xlazyload */
diff --git a/libdtrace/dt_open.c b/libdtrace/dt_open.c
index b521d8870aad..d86bc8f14141 100644
--- a/libdtrace/dt_open.c
+++ b/libdtrace/dt_open.c
@@ -609,6 +609,7 @@ const dtrace_pattr_t _dtrace_prvdesc = {
 static const char *_dtrace_defcpp = "cpp"; /* default cpp(1) to invoke */
 static const char *_dtrace_defld = "ld";   /* default ld(1) to invoke */
 static const char *_dtrace_defproc = "/proc";   /* default /proc path */
+static const char *_dtrace_defdofstash = "/run/dtrace";   /* default DOF stash path */
 static const char *_dtrace_defsysslice = ":/system.slice/"; /* default systemd
 							       system slice */
 
@@ -744,6 +745,7 @@ dt_vopen(int version, int flags, int *errp,
 	dtp->dt_ld_path = strdup(_dtrace_defld);
 	Pset_procfs_path(_dtrace_defproc);
 	dtp->dt_sysslice = strdup(_dtrace_defsysslice);
+	dtp->dt_dofstash_path = strdup(_dtrace_defdofstash);
 	dtp->dt_useruid = DTRACE_USER_UID;
 	dtp->dt_vector = vector;
 	dtp->dt_varg = arg;
@@ -1285,7 +1287,6 @@ dtrace_close(dtrace_hdl_t *dtp)
 	dt_pfdict_destroy(dtp);
 	dt_dof_fini(dtp);
 	dt_probe_fini(dtp);
-	dt_pid_free_uprobespecs(dtp);
 
 	/*
 	 * FIXME:
@@ -1308,6 +1309,7 @@ dtrace_close(dtrace_hdl_t *dtp)
 	free(dtp->dt_cpp_path);
 	free(dtp->dt_ld_path);
 	free(dtp->dt_sysslice);
+	free(dtp->dt_dofstash_path);
 
 	free(dtp->dt_freopen_filename);
 	free(dtp->dt_sprintf_buf);
diff --git a/libdtrace/dt_options.c b/libdtrace/dt_options.c
index 33995a6f8a55..3631dfe2ee2d 100644
--- a/libdtrace/dt_options.c
+++ b/libdtrace/dt_options.c
@@ -419,6 +419,23 @@ dt_opt_disasm(dtrace_hdl_t *dtp, const char *arg, uintptr_t option)
 	return 0;
 }
 
+/*ARGSUSED*/
+static int
+dt_opt_dofstash_path(dtrace_hdl_t *dtp, const char *arg, uintptr_t option)
+{
+	char *path;
+
+	if (arg == NULL)
+		return dt_set_errno(dtp, EDT_BADOPTVAL);
+
+	if ((path = strdup(arg)) == NULL)
+		return dt_set_errno(dtp, EDT_NOMEM);
+	free(dtp->dt_dofstash_path);
+	dtp->dt_dofstash_path = path;
+
+	return 0;
+}
+
 /*ARGSUSED*/
 static int
 dt_opt_evaltime(dtrace_hdl_t *dtp, const char *arg, uintptr_t option)
@@ -1094,6 +1111,7 @@ static const dt_option_t _dtrace_ctoptions[] = {
 	{ "debugassert", dt_opt_debug_assert },
 	{ "define", dt_opt_cpp_opts, (uintptr_t)"-D" },
 	{ "disasm", dt_opt_disasm },
+	{ "dofstashpath", dt_opt_dofstash_path },
 	{ "droptags", dt_opt_droptags },
 	{ "dtypes", dt_opt_dtypes },
 	{ "empty", dt_opt_cflags, DTRACE_C_EMPTY },
diff --git a/libdtrace/dt_pid.c b/libdtrace/dt_pid.c
index 70e0330f6f77..afa59e72b0f7 100644
--- a/libdtrace/dt_pid.c
+++ b/libdtrace/dt_pid.c
@@ -5,20 +5,21 @@
  * http://oss.oracle.com/licenses/upl.
  */
 
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/sysmacros.h>
+#include <stddef.h>
 #include <assert.h>
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
 #include <string.h>
 #include <stdlib.h>
 #include <stdio.h>
-#include <errno.h>
-#include <ctype.h>
-#include <alloca.h>
-#include <libgen.h>
-#include <stddef.h>
-#include <sys/ioctl.h>
-#include <sys/sysmacros.h>
+#include <glob.h>
 
 #include <port.h>
-#include <uprobes.h>
+#include <dof_parser.h>
 
 #include <dt_impl.h>
 #include <dt_program.h>
@@ -27,7 +28,7 @@
 #include <dt_string.h>
 
 /*
- * Information on a PID or USDT probe.
+ * Information on a PID probe.
  */
 typedef struct dt_pid_probe {
 	dtrace_hdl_t *dpp_dtp;
@@ -58,15 +59,12 @@ static char *
 dt_pid_objname(Lmid_t lmid, const char *obj)
 {
 	char *buf;
-	int len;
 
 	if (lmid == LM_ID_BASE)
 		return strdup(obj);
 
-	len = snprintf(NULL, 0, "LM%lx`%s", lmid, obj) + 1;
-	buf = malloc(len);
-	if (buf)
-		snprintf(buf, len, "LM%lx`%s", lmid, obj);
+	if (asprintf(&buf, "LM%lx`%s", lmid, obj) < 0)
+		return NULL;
 
 	return buf;
 }
@@ -95,33 +93,19 @@ dt_pid_error(dtrace_hdl_t *dtp, dt_pcb_t *pcb, dt_proc_t *dpr,
 	return 1;
 }
 
-void
-dt_pid_free_uprobespecs(dtrace_hdl_t *dtp)
-{
-	size_t i;
-
-	if (!dtp->dt_uprobespecs)
-		return;
-
-	for (i = 0; i < dtp->dt_uprobespecs_sz; i++) {
-		free(dtp->dt_uprobespecs[i].pps_prv);
-		free(dtp->dt_uprobespecs[i].pps_mod);
-		free(dtp->dt_uprobespecs[i].pps_fun);
-		free(dtp->dt_uprobespecs[i].pps_prb);
-	}
-
-	free(dtp->dt_uprobespecs);
-	dtp->dt_uprobespecs = NULL;
-}
-
 static int
 dt_pid_create_fbt_probe(struct ps_prochandle *P, dtrace_hdl_t *dtp,
     pid_probespec_t *psp, const GElf_Sym *symp, pid_probetype_t type)
 {
 	const dt_provider_t	*pvp;
+	int			ret;
 
-	psp->pps_prv = "pid";
-	psp->pps_type = type;
+	if (asprintf(&psp->pps_prv, "pid%d", psp->pps_pid) < 0) {
+		dt_dprintf("Out of memory allocating provider name\n");
+		return -1;
+	}
+
+        psp->pps_type = type;
 	psp->pps_off = symp->st_value - psp->pps_vaddr;
 	psp->pps_size = (size_t)symp->st_size;
 	psp->pps_gstr[0] = '\0';		/* no glob pattern */
@@ -129,9 +113,12 @@ dt_pid_create_fbt_probe(struct ps_prochandle *P, dtrace_hdl_t *dtp,
 	/* Make sure we have a PID provider. */
 	pvp = dtp->dt_prov_pid;
 	if (pvp == NULL) {
-		pvp = dt_provider_lookup(dtp, psp->pps_prv);
-		if (pvp == NULL)
+		pvp = dt_provider_lookup(dtp, "pid");
+		if (pvp == NULL) {
+			dt_dprintf("can't look up pid provider\n");
+			free(psp->pps_prv);
 			return 0;
+		}
 
 		dtp->dt_prov_pid = pvp;
 	}
@@ -140,7 +127,9 @@ dt_pid_create_fbt_probe(struct ps_prochandle *P, dtrace_hdl_t *dtp,
 
 	/* Create a probe using 'psp'. */
 
-	return pvp->impl->provide_probe(dtp, psp);
+	ret = pvp->impl->provide_probe(dtp, psp);
+	free(psp->pps_prv);
+	return ret;
 }
 
 static int
@@ -195,7 +184,7 @@ dt_pid_per_sym(dt_pid_probe_t *pp, const GElf_Sym *symp, const char *func)
 	psp->pps_inum = pp->dpp_inum;
 	psp->pps_fn = strdup(pp->dpp_fname);
 	psp->pps_vaddr = pp->dpp_vaddr;
-	psp->pps_fun = (char *) func;
+	psp->pps_fun = func;
 
 	if (!isdash && gmatch("return", pp->dpp_name)) {
 		if (dt_pid_create_fbt_probe(pp->dpp_pr, dtp, psp, symp,
@@ -588,172 +577,97 @@ dt_pid_create_pid_probes(dtrace_probedesc_t *pdp, dtrace_hdl_t *dtp,
 
 	return ret;
 }
-
 /*
- * Scan the uprobe list and remember its contents.
- *
- * This avoids us having to rescan the whole thing every time we create every
- * single probe in turn.
+ * Read a file into a buffer and return it.
  */
-static int
-dt_pid_scan_uprobes(dtrace_hdl_t *dtp, dt_pcb_t *pcb)
+static void *
+read_file(const char *name, size_t *size)
 {
-	typedef struct uprobe_line
-	{
-		dt_list_t list;
-		char *line;
-		int is_enabled;
-	} uprobe_line_t;
-	dt_list_t lines = {0};
-	uprobe_line_t *linep, *old_linep;
-	size_t i = 0;
-	int ret = 0;
-
-	FILE *f;
+	int fd;
+	struct stat s;
 	char *buf = NULL;
-	size_t sz;
+	char *bufptr;
+	int len;
 
-	f = fopen(TRACEFS "uprobe_events", "r");
-	if (!f) {
-		dt_dprintf("cannot open " TRACEFS "uprobe_events: %s\n",
-		    strerror(errno));
-		return -1;
+	if ((fd = open(name, O_RDONLY | O_CLOEXEC)) < 0) {
+		dt_dprintf("cannot open %s while scanning for USDT DOF: %s\n",
+			   name, strerror(errno));
+		return NULL;
 	}
 
-	/*
-	 * We are only interested in pid uprobes, not any other uprobes that may
-	 * exist.  Some of these may be for pid probes, some for usdt: we keep
-	 * track of all of them regardless.
-	 *
-	 * Suck in the list of uprobes in one go, since we need to run over it
-	 * twice (once to count pids and allocate space, once to populate them)
-	 * and it might change between reads.
-	 */
+        if (fstat(fd, &s) < 0) {
+		dt_dprintf("cannot stat while scanning for USDT DOF: %s\n",
+			   strerror(errno));
+		goto err;
+	}
+	if ((buf = malloc(s.st_size)) == NULL) {
+		dt_dprintf("Out of memory allocating %zi bytes while scanning for USDT DOF\n",
+			   s.st_size);
+		goto err;
+	}
+	*size = s.st_size;
 
-#define UPROBE_PREFIX "p:dt_pid/p_"
-#define UPROBE_IS_ENABLED_PREFIX "p:dt_pid_is_enabled/p_"
-
-	while (getline(&buf, &sz, f) >= 0) {
-		uprobe_line_t *line;
-		int is_enabled;
-
-		if (strncmp(buf, UPROBE_PREFIX,
-			strlen(UPROBE_PREFIX)) == 0)
-			is_enabled = 0;
-		else if (strncmp(buf, UPROBE_IS_ENABLED_PREFIX,
-			strlen(UPROBE_IS_ENABLED_PREFIX)) == 0)
-			is_enabled = 1;
-		else
+        bufptr = buf;
+	while ((len = read(fd, bufptr, s.st_size)) < s.st_size) {
+		if (len < 0) {
+			if (errno != EINTR) {
+				dt_dprintf("Cannot read USDT DOF: %s\n",
+					   strerror(errno));
+				goto err;
+			}
 			continue;
-
-		line = dt_zalloc(dtp, sizeof (struct uprobe_line));
-		if (!line) {
-			fclose(f);
-			goto err; 		/* errno is set for us. */
 		}
-
-		line->line = buf;
-		line->is_enabled = is_enabled;
-		dt_list_append(&lines, line);
-		sz = 0;
-		dtp->dt_uprobespecs_sz++;
-		buf = NULL;
+		s.st_size -= len;
+		bufptr += len;
 	}
-	fclose(f);
-
-	dtp->dt_uprobespecs = dt_calloc(dtp, dtp->dt_uprobespecs_sz,
-	    sizeof(pid_probespec_t));
-	if (!dtp->dt_uprobespecs)
-		goto err;			/* errno is set for us.  */
-
-	/*
-	 * Now we know how many specs exist, parse and create them.
-	 */
-	for (linep = dt_list_next(&lines); linep != NULL;
-	     linep = dt_list_next(linep)) {
-		uint64_t off;
-		const char *fmt;
-		unsigned long long dev, inum;
-		char *spec = NULL;
-		char *eprv = NULL, *emod = NULL, *efun = NULL, *eprb = NULL;
-		char *prv = NULL, *mod = NULL, *fun = NULL, *prb = NULL;
-		pid_probespec_t *psp;
-
-#define UPROBE_PROBE_FMT "%llx_%llx_%lx %ms P%m[^= ]=\\1 M%m[^= ]=\\2 F%m[^= ]=\\3 N%m[^= ]=\\4"
-#define UPROBE_FMT UPROBE_PREFIX UPROBE_PROBE_FMT
-#define UPROBE_IS_ENABLED_FMT UPROBE_IS_ENABLED_PREFIX UPROBE_PROBE_FMT
-
-		if (!linep->is_enabled)
-			fmt = UPROBE_FMT;
-		else
-			fmt = UPROBE_IS_ENABLED_FMT;
-
-		switch (sscanf(linep->line, fmt, &dev, &inum,
-			       &off, &spec, &eprv, &emod, &efun, &eprb)) {
-		case 8: /* Includes dtrace probe names: decode them. */
-			prv = uprobe_decode_name(eprv);
-			mod = uprobe_decode_name(emod);
-			fun = uprobe_decode_name(efun);
-			prb = uprobe_decode_name(eprb);
-			break;
-		case 4: /* No dtrace probe name - not a USDT probe. */
-			goto next;
-		default:
-			if ((strlen(linep->line) > 0) &&
-			    (linep->line[strlen(linep->line)-1] == '\n'))
-				linep->line[strlen(linep->line)-1] = 0;
-			dt_dprintf("Cannot parse %s as a DTrace uprobe name\n",
-			    linep->line);
-			dtp->dt_uprobespecs_sz--;
-			goto next;
-		}
-
-		psp = &dtp->dt_uprobespecs[i++];
-		psp->pps_type = linep->is_enabled ? DTPPT_IS_ENABLED : DTPPT_OFFSETS;
-
-		/*
-		 * These components are only used for creation of an underlying
-		 * probe with no overlying counterpart: usually these are those
-		 * not explicitly listed in the D program, which will never be
-		 * enabled.  In future this may change.
-		 */
-		psp->pps_prv = prv;
-		psp->pps_mod = mod;
-		psp->pps_fun = fun;
-		psp->pps_prb = prb;
-
-		/*
-		 * Always used.
-		 */
-		psp->pps_dev = dev;
-		psp->pps_inum = inum;
-		psp->pps_off = off;
-	next:
-		free(eprv); free(emod); free(efun); free(eprb);
-		free(spec);
-	}
-
-	goto out;
-
-err:
-	ret = -1;
-
-out:
-	old_linep = NULL;
-
-	for (linep = dt_list_next(&lines); linep != NULL;
-	     linep = dt_list_next(linep)) {
-		free(linep->line);
-		free(old_linep);
-		old_linep = linep;
-	}
-	free(old_linep);
-
-	return ret;
+	close(fd);
+	return buf;
+  err:
+	free(buf);
+	close(fd);
+	return NULL;
 }
 
 /*
- * Rescan the PID uprobe list and create suitable underlying probes.
+ * Split out the pieces of a probe name from a DOF stash path (which is
+ * modified).  The pieces' lifetime is no longer than that of the input string.
+ */
+static int
+split_stash_probe_name(char *path, char **prv, char **mod,
+		       char **fun, char **prb)
+{
+	char *tmp;
+
+	tmp = strrchr(path, '/');
+	if (tmp == NULL)
+		return -1;
+	*tmp = 0;
+	*prb = tmp + 1;
+
+	tmp = strrchr(path, '/');
+	if (tmp == NULL)
+		return -1;
+	*tmp = 0;
+	*fun = tmp + 1;
+
+        tmp = strrchr(path, '/');
+	if (tmp == NULL)
+		return -1;
+	*tmp = 0;
+	*mod = tmp + 1;
+
+        tmp = strrchr(path, '/');
+	if (tmp == NULL)
+		return -1;
+	*tmp = 0;
+
+        *prv = tmp + 1;
+
+	return 0;
+}
+
+/*
+ * Create an underlying probe relating to the probe passed on input.
  *
  * If dpr is set, just set up probes relating to mappings found in that one
  * process.  (dpr must in this case be locked.)
@@ -762,81 +676,219 @@ out:
  * probes is not an error.)
  */
 static int
-dt_pid_create_usdt_probes(dtrace_hdl_t *dtp, dt_proc_t *dpr, dt_pcb_t *pcb)
+dt_pid_create_usdt_probe(dtrace_hdl_t *dtp, dt_proc_t *dpr, dtrace_probedesc_t *pdp,
+			 dt_pcb_t *pcb)
 {
 	const dt_provider_t *pvp;
-	size_t i;
 	int ret = 0;
+	char *probepath = NULL;
+	glob_t probeglob = {0};
 
 	/*
 	 * Systemwide probing: not yet implemented.
 	 */
-	assert(dpr != NULL);
+	assert(dpr != NULL && dpr->dpr_proc);
+	assert(MUTEX_HELD(&dpr->dpr_lock));
 
-	dt_dprintf("Scanning for usdt probes matching %i\n", dpr->dpr_pid);
+	dt_dprintf("Scanning for usdt probes in %i matching %s:%s:%s\n",
+		   dpr->dpr_pid, pdp->mod, pdp->fun, pdp->prb);
+
+	pvp = dt_provider_lookup(dtp, "usdt");
+	assert(pvp != NULL);
+
+	if (Pstate(dpr->dpr_proc) == PS_DEAD)
+		return 0;
 
 	/*
-	 * For now, we only read the list of probes once.  In time we will
-	 * reread it whenever necessary.
+	 * Look for DOF matching this probe in the global probe DOF stash, in
+	 * /run/dtrace/probes/$pid/$prv/$mod/$fun/$prb: glob expansion means
+	 * that this may relate to multiple probes.
+	 *
+	 * Using this is safe because the parsed DOF is guaranteed up to date
+	 * with the current DTrace, being reparsed by the currently-running
+	 * daemon, and was parsed in a seccomp jail.  The most a process can do
+	 * by messing with this is force probes to be dropped in the wrong place
+	 * in itself: and if a process wants to perturb tracing of itself there
+	 * are many simpler ways, such as overwriting the DOF symbol before the
+	 * ELF constructor runs, etc.
+	 *
+	 * Note: future use of parsed DOF (after DTrace has been running for a
+	 * while) may not be safe, since the daemon may be newer than DTrace
+	 * and thus have newer parsed DOF. A version comparison will suffice to
+	 * check that: for safety we do it here too.
 	 */
-	pvp = dtp->dt_prov_usdt;
-	if (!pvp) {
-		pvp = dt_provider_lookup(dtp, "usdt");
-		assert(pvp != NULL);
-		dtp->dt_prov_usdt = pvp;
-		if (dt_pid_scan_uprobes(dtp, pcb) < 0)
-			return -1;		/* errno is set for us.  */
-	}
 
 	assert(pvp->impl != NULL && pvp->impl->provide_probe != NULL);
 
-	/*
-	 * We are only interested in pid uprobes, not any other uprobes that may
-	 * exist.  Some of these may be for pid probes, some for usdt: we create
-	 * underlying probes for all of them, if we are interested in creating
-	 * mappings for that process at all.
-	 */
-	for (i = 0; i < dtp->dt_uprobespecs_sz; i++) {
-		pid_probespec_t *psp = &dtp->dt_uprobespecs[i];
-
-		/*
-		 * Filter out probes not related to the process of interest.
-		 */
-		if (dpr && dpr->dpr_proc) {
-			assert(MUTEX_HELD(&dpr->dpr_lock));
-			if (Pinode_to_file_map(dpr->dpr_proc, psp->pps_dev,
-				psp->pps_inum) == NULL)
-				continue;
-
-			/*
-			 * This is overwritten repeatedly with each relevant PID
-			 * in turn.
-			 */
-			psp->pps_pid = Pgetpid(dpr->dpr_proc);
-		}
-
-		/*
-		 * Create an underlying probe using psp, if not already present.
-		 *
-		 * Complain if any probe cannot be created: at this stage we
-		 * cannot reliably tell whether a corresponding overlying probe
-		 * will be created (since dt_setcontext only calls us for the
-		 * first one in any given provider).
-		 */
-
-		dt_dprintf("providing %s:%s:%s:%s\n", psp->pps_prv, psp->pps_mod,
-			   psp->pps_fun, psp->pps_prb);
-		if (pvp->impl->provide_probe(dtp, psp) < 0) {
-			dt_pid_error(dtp, pcb, dpr, D_PROC_USDT,
-				     "failed to instantiate %sprobe %s for pid %d: %s",
-				     psp->pps_type == DTPPT_IS_ENABLED ?
-				     "is-enabled ": "", psp->pps_prb, dpr->dpr_pid,
-				     dtrace_errmsg(dtp, dtrace_errno(dtp)));
-			ret = -1;
-		}
+	if (strchr(pdp->prv, '.') != NULL ||
+	    strchr(pdp->mod, '.') != NULL ||
+	    strchr(pdp->fun, '.') != NULL ||
+	    strchr(pdp->prb, '.') != NULL) {
+		dt_dprintf("Probe component contains dots: cannot be a USDT probe.\n");
+		return 0;
 	}
 
+	if (asprintf(&probepath, "%s/probes/%i/%s/%s/%s/%s", dtp->dt_dofstash_path,
+		     dpr->dpr_pid, pdp->prv[0] == '\0' ? "*" : pdp->prv,
+		     pdp->mod[0] == '\0' ? "*" : pdp->mod,
+		     pdp->fun[0] == '\0' ? "*" : pdp->fun,
+		     pdp->prb[0] == '\0' ? "*" : pdp->prb) < 0)
+		goto scan_err;
+
+	switch(glob(probepath, GLOB_NOSORT | GLOB_ERR | GLOB_PERIOD, NULL, &probeglob)) {
+	case GLOB_NOSPACE:
+	case GLOB_ABORTED:
+		/*
+		 * Directory missing?  PID not present or has no DOF, which is
+		 * fine, though it might lead to a match failure later on.
+		 */
+		if (errno == ENOENT)
+			return 0;
+
+                dt_dprintf("Cannot glob probe components in %s: %s\n", probepath, strerror(errno));
+		goto scan_err;
+	case GLOB_NOMATCH:
+		/* No probes match, which is fine. */
+		return 0;
+	}
+
+	for (size_t i = 0; i < probeglob.gl_pathc; i++) {
+		char *dof_buf = NULL, *p;
+		struct stat s;
+		char *path;
+		size_t dof_buf_size, seen_size = 0;
+		uint64_t *dof_version;
+		uint64_t *ntpp;
+		char *prv, *mod, *fun, *prb;
+
+		/*
+		 * Regular files only: in particular, skip . and ..,
+		 * which can appear due to GLOB_PERIOD.
+		 */
+		if ((lstat(probeglob.gl_pathv[i], &s) < 0) ||
+		    (!S_ISREG(s.st_mode)))
+			continue;
+
+		path = strdup(probeglob.gl_pathv[i]);
+		if (path == NULL)
+			goto per_mapping_err;
+
+                dof_buf = read_file(path, &dof_buf_size);
+		if (dof_buf == NULL)
+			goto per_mapping_err;
+		dof_version = (uint64_t *) dof_buf;
+		if (*dof_version != DOF_PARSED_VERSION) {
+			dt_dprintf("Parsed DOF version incorrect (daemon / running DTrace version skew?) %lli (daemon) versus %i (DTrace)\n",
+				   (long long) *dof_version, DOF_PARSED_VERSION);
+			goto per_mapping_err;
+		}
+		p = dof_buf + sizeof(uint64_t);
+		ntpp = (uint64_t *) p;
+		p += sizeof(uint64_t);
+		dof_buf_size -= (sizeof(uint64_t) * 2);
+
+		if (split_stash_probe_name(path, &prv, &mod, &fun, &prb) < 0) {
+			/*
+			 * We have to use gl_pathv again here since
+			 * split_stash_probe_name may have modified path
+			 * already.
+			 */
+			dt_dprintf("Cannot split probe name out of DOF stash path %s: malformed\n",
+				   probeglob.gl_pathv[i]);
+			goto parse_err;
+		}
+
+		/*
+		 * Got some parsed DOF for this probe's tracepoints.
+		 */
+		for (size_t j = 0; j < *ntpp; j++) {
+			dof_parsed_t *tp = (dof_parsed_t *) p;
+			pid_probespec_t psp;
+			const prmap_t *pmp;
+
+			if (dof_buf_size < seen_size) {
+				dt_dprintf("DOF too small when adding probes (seen %zi bytes)\n",
+					   seen_size);
+				goto parse_err;
+			}
+
+			if (tp->type != DIT_TRACEPOINT) {
+				dt_dprintf("Type is %i\n", tp->type);
+				goto parse_err;
+			}
+
+			/*
+			 * Check for process death in the inner loop to handle
+			 * the process dying while its DOF is being pulled in.
+			 */
+			if (Pstate(dpr->dpr_proc) == PS_DEAD)
+				continue;
+
+			pmp = Paddr_to_map(dpr->dpr_proc, tp->tracepoint.addr);
+			if (!pmp) {
+				dt_dprintf("%i: cannot determine 0x%lx's mapping\n",
+					   Pgetpid(dpr->dpr_proc), tp->tracepoint.addr);
+				continue;
+			}
+
+			p += tp->size;
+			seen_size += tp->size;
+
+			psp.pps_fn = Pmap_mapfile_name(dpr->dpr_proc, pmp);
+			if (psp.pps_fn == NULL) {
+				dt_pid_error(dtp, pcb, dpr, D_PROC_USDT,
+					     "Cannot get name of mapping containing "
+					     "%sprobe %s for pid %d\n",
+					     tp->tracepoint.is_enabled ? "is-enabled ": "",
+					     psp.pps_prb, dpr->dpr_pid);
+				free(psp.pps_prv);
+				goto oom;
+			}
+
+			psp.pps_type = tp->tracepoint.is_enabled ? DTPPT_IS_ENABLED : DTPPT_OFFSETS;
+			psp.pps_prv = prv;
+			psp.pps_mod = (char *) mod;
+			psp.pps_fun = fun;
+			psp.pps_prb = prb;
+			psp.pps_dev = pmp->pr_dev;
+			psp.pps_inum = pmp->pr_inum;
+			psp.pps_pid = dpr->dpr_pid;
+			psp.pps_off = tp->tracepoint.addr - pmp->pr_file->first_segment->pr_vaddr;
+
+			dt_dprintf("providing %s:%s:%s:%s\n", psp.pps_prv,
+				   psp.pps_mod, psp.pps_fun, psp.pps_prb);
+			if (pvp->impl->provide_probe(dtp, &psp) < 0) {
+				dt_pid_error(dtp, pcb, dpr, D_PROC_USDT,
+					     "failed to instantiate %sprobe %s for pid %d: %s",
+					     tp->tracepoint.is_enabled ? "is-enabled ": "",
+					     psp.pps_prb, dpr->dpr_pid,
+					     dtrace_errmsg(dtp, dtrace_errno(dtp)));
+				ret = -1;
+			}
+			free(psp.pps_fn);
+		}
+
+		free(path);
+		free(dof_buf);
+		continue;
+
+	  parse_err:
+		dt_dprintf("Parsed DOF corrupt. This should never happen.\n");
+	  oom: ;
+	  per_mapping_err:
+		free(path);
+		free(dof_buf);
+		globfree(&probeglob);
+		return -1;
+	}
+
+        globfree(&probeglob);
 	return ret;
+
+scan_err:
+	dt_dprintf("Cannot read DOF stash directory %s: %s\n",
+		   probepath, strerror(errno));
+	return -1;
 }
 
 #if 0 /* Almost certainly unnecessary in this form */
@@ -964,7 +1016,8 @@ dt_pid_create_probes(dtrace_probedesc_t *pdp, dtrace_hdl_t *dtp, dt_pcb_t *pcb)
 	 * If it's not strictly a pid provider, we might match a USDT provider.
 	 */
 	if (strcmp(provname, pdp->prv) != 0) {
-		if (dt_proc_grab_lock(dtp, pid, DTRACE_PROC_WAITING) < 0) {
+		if (dt_proc_grab_lock(dtp, pid, DTRACE_PROC_WAITING |
+				      DTRACE_PROC_SHORTLIVED) < 0) {
 			dt_pid_error(dtp, pcb, NULL, D_PROC_GRAB,
 			    "failed to grab process %d", (int)pid);
 			return -1;
@@ -973,10 +1026,7 @@ dt_pid_create_probes(dtrace_probedesc_t *pdp, dtrace_hdl_t *dtp, dt_pcb_t *pcb)
 		dpr = dt_proc_lookup(dtp, pid);
 		assert(dpr != NULL);
 
-		if (!dpr->dpr_usdt) {
-			err = dt_pid_create_usdt_probes(dtp, dpr, pcb);
-			dpr->dpr_usdt = B_TRUE;
-		}
+		err = dt_pid_create_usdt_probe(dtp, dpr, pdp, pcb);
 
 		/*
 		 * Put the module name in its canonical form.
@@ -1026,7 +1076,7 @@ dt_pid_create_probes_module(dtrace_hdl_t *dtp, dt_proc_t *dpr)
 			 * a USDT provider.
 			 */
 			if (strcmp(provname, pdp->prv) != 0) {
-				if (dt_pid_create_usdt_probes(dtp, dpr, NULL) < 0)
+				if (dt_pid_create_usdt_probe(dtp, dpr, pdp, NULL) < 0)
 					ret = 1;
 				else
 					dt_pid_fix_mod(NULL, pdp, dtp, dpr->dpr_pid);
diff --git a/libdtrace/dt_pid.h b/libdtrace/dt_pid.h
index e06186eb458b..72e6a59970c2 100644
--- a/libdtrace/dt_pid.h
+++ b/libdtrace/dt_pid.h
@@ -21,7 +21,6 @@ extern int dt_pid_create_probes(dtrace_probedesc_t *, dtrace_hdl_t *,
 extern int dt_pid_create_probes_module(dtrace_hdl_t *, dt_proc_t *);
 extern pid_t dt_pid_get_pid(const dtrace_probedesc_t *, dtrace_hdl_t *, dt_pcb_t *,
 			    dt_proc_t *);
-extern void dt_pid_free_uprobespecs(dtrace_hdl_t *);
 
 #ifdef	__cplusplus
 }
diff --git a/libdtrace/dt_proc.h b/libdtrace/dt_proc.h
index 1ca1aeb6f797..1c2e79b1b6b4 100644
--- a/libdtrace/dt_proc.h
+++ b/libdtrace/dt_proc.h
@@ -41,7 +41,6 @@ typedef struct dt_proc {
 	uint_t dpr_refs;		/* reference count */
 	uint8_t dpr_stop;		/* stop mask: see flag bits below */
 	uint8_t dpr_done;		/* done flag: ctl thread has exited */
-	uint8_t dpr_usdt;		/* usdt flag: usdt probes created */
 	uint8_t dpr_created;            /* proc flag: true if we created this
 					   process, false if we grabbed it */
 	uint8_t dpr_monitoring;		/* true if we should background-monitor
diff --git a/libdtrace/dt_prov_uprobe.c b/libdtrace/dt_prov_uprobe.c
index 5dc5c75cc300..413e19cfce06 100644
--- a/libdtrace/dt_prov_uprobe.c
+++ b/libdtrace/dt_prov_uprobe.c
@@ -20,7 +20,6 @@
 #include "dt_probe.h"
 #include "dt_pid.h"
 #include "dt_string.h"
-#include "uprobes.h"
 
 /* Provider name for the underlying probes. */
 static const char	prvname[] = "uprobe";
@@ -28,10 +27,9 @@ static const char	prvname_is_enabled[] = "uprobe__is_enabled";
 
 #define UPROBE_EVENTS	TRACEFS "uprobe_events"
 
-#define PP_IS_MINE	1
-#define PP_IS_RETURN	2
-#define PP_IS_FUNCALL	4
-#define PP_IS_ENABLED	8
+#define PP_IS_RETURN	1
+#define PP_IS_FUNCALL	2
+#define PP_IS_ENABLED	4
 
 typedef struct dt_uprobe {
 	dev_t		dev;
@@ -177,8 +175,7 @@ static dt_probe_t *create_underlying(dtrace_hdl_t *dtp,
 		upp->dev = psp->pps_dev;
 		upp->inum = psp->pps_inum;
 		upp->off = psp->pps_off;
-		if (psp->pps_fn)
-			upp->fn = strdup(psp->pps_fn);
+		upp->fn = strdup(psp->pps_fn);
 		upp->tp = dt_tp_alloc(dtp);
 		if (upp->tp == NULL)
 			goto fail;
@@ -213,25 +210,22 @@ fail:
 static int provide_probe(dtrace_hdl_t *dtp, const pid_probespec_t *psp,
 			 const char *prb, const dt_provimpl_t *pvops, int flags)
 {
-	char			prv[DTRACE_PROVNAMELEN];
 	dt_provider_t		*pvp;
 	dtrace_probedesc_t	pd;
 	dt_uprobe_t		*upp;
 	dt_probe_t		*prp, *uprp;
 	list_probe_t		*pop, *pup;
 
-	snprintf(prv, sizeof(prv), "%s%d", psp->pps_prv, psp->pps_pid);
-
 	pd.id = DTRACE_IDNONE;
-	pd.prv = prv;
+	pd.prv = psp->pps_prv;
 	pd.mod = psp->pps_mod;
 	pd.fun = psp->pps_fun;
 	pd.prb = prb;
 
 	/* Get (or create) the provider for the PID of the probe. */
-	pvp = dt_provider_lookup(dtp, prv);
+	pvp = dt_provider_lookup(dtp, psp->pps_prv);
 	if (pvp == NULL) {
-		pvp = dt_provider_create(dtp, prv, pvops, &pattr, NULL);
+		pvp = dt_provider_create(dtp, psp->pps_prv, pvops, &pattr, NULL);
 		if (pvp == NULL)
 			return -1;
 	}
@@ -316,7 +310,7 @@ static int provide_pid_probe(dtrace_hdl_t *dtp, const pid_probespec_t *psp)
 		return -1;
 	}
 
-	return provide_probe(dtp, psp, prb, &dt_usdt, PP_IS_MINE);
+	return provide_probe(dtp, psp, prb, &dt_usdt, 0);
 }
 
 static int provide_usdt_probe(dtrace_hdl_t *dtp, const pid_probespec_t *psp)
@@ -585,6 +579,57 @@ static int trampoline_is_enabled(dt_pcb_t *pcb, uint_t exitlbl)
 	return 0;
 }
 
+static char *uprobe_name(dev_t dev, ino_t ino, uint64_t addr, int flags)
+{
+	char	*name;
+
+	if (asprintf(&name, "dt_pid%s/%c_%llx_%llx_%lx",
+		     flags & PP_IS_ENABLED ? "_is_enabled" : "",
+		     flags & PP_IS_RETURN ? 'r' : 'p', (unsigned long long)dev,
+		     (unsigned long long)ino, (unsigned long)addr) < 0)
+		return NULL;
+
+	return name;
+}
+
+/*
+ * Create a uprobe for a given dev/ino, mapping filename, and address: the
+ * uprobe may be a uretprobe or an is-enabled probe.  Return the probe's name as
+ * a new dynamically-allocated string, or NULL on error.
+ */
+static char *uprobe_create(dev_t dev, ino_t ino, const char *mapping_fn,
+			   uint64_t addr, int flags)
+{
+	int	fd = -1;
+	int	rc = -1;
+	char	*name;
+	char	*spec;
+
+	if (asprintf(&spec, "%s:0x%lx", mapping_fn, addr) < 0)
+		return NULL;
+
+	name = uprobe_name(dev, ino, addr, flags);
+	if (!name)
+		goto out;
+
+	/* Add the uprobe. */
+	fd = open(TRACEFS "uprobe_events", O_WRONLY | O_APPEND);
+	if (fd == -1)
+		goto out;
+
+	rc = dprintf(fd, "%c:%s %s\n", flags & PP_IS_RETURN ? 'r' : 'p', name, spec);
+
+out:
+	if (fd != -1)
+		close(fd);
+	if (rc < 0) {
+		free(name);
+		return NULL;
+	}
+
+	return name;
+}
+
 static int attach(dtrace_hdl_t *dtp, const dt_probe_t *prp, int bpf_fd)
 {
 	dt_uprobe_t	*upp = prp->prv_data;
@@ -597,29 +642,20 @@ static int attach(dtrace_hdl_t *dtp, const dt_probe_t *prp, int bpf_fd)
 	if (dt_tp_is_created(tpp))
 		goto attach_bpf;
 
-	if (upp->flags & PP_IS_MINE) {
-		char	*spec;
+	assert(upp->fn != NULL);
 
-		assert(upp->fn != NULL);
+	prb = uprobe_create(upp->dev, upp->inum, upp->fn, upp->off,
+			    upp->flags);
 
-		if (asprintf(&spec, "%s:0x%lx", upp->fn, upp->off) < 0)
-			return -ENOENT;
-
-		prb = uprobe_create(upp->dev, upp->inum, upp->off, spec,
-				    upp->flags & PP_IS_RETURN, 0);
-		free(spec);
-
-		/*
-		 * If the uprobe creation failed, it is possible it already
-		 * existed because someone else created it.  Tey to access its
-		 * tracefs info and if that fail, we really failed.
-		 */
-	}
+	/*
+	 * If the uprobe creation failed, it is possible it already
+	 * existed because someone else created it.  Try to access its
+	 * tracefs info and if that fails, we really failed.
+	 */
 
 	if (prb == NULL)
 		prb = uprobe_name(upp->dev, upp->inum, upp->off,
-				  upp->flags & PP_IS_RETURN,
-				  upp->flags & PP_IS_ENABLED);
+				  upp->flags);
 
 	/* open format file */
 	rc = asprintf(&fn, "%s%s/format", EVENTSFS, prb);
@@ -651,6 +687,35 @@ static int probe_info(dtrace_hdl_t *dtp, const dt_probe_t *prp,
 	return 0;
 }
 
+/*
+ * Destroy a uprobe for a given device and address.
+ */
+static int
+uprobe_delete(dev_t dev, ino_t ino, uint64_t addr, int flags)
+{
+	int	fd = -1;
+	int	rc = -1;
+	char	*name;
+
+	name = uprobe_name(dev, ino, addr, flags);
+	if (!name)
+		goto out;
+
+	fd = open(TRACEFS "uprobe_events", O_WRONLY | O_APPEND);
+	if (fd == -1)
+		goto out;
+
+
+	rc = dprintf(fd, "-:%s\n", name);
+
+out:
+	if (fd != -1)
+		close(fd);
+	free(name);
+
+	return rc < 0 ? -1 : 0;
+}
+
 /*
  * Try to clean up system resources that may have been allocated for this
  * probe.
@@ -672,11 +737,7 @@ static void detach(dtrace_hdl_t *dtp, const dt_probe_t *prp)
 
 	dt_tp_detach(dtp, tpp);
 
-	if (!(upp->flags & PP_IS_MINE))
-		return;
-
-	uprobe_delete(upp->dev, upp->inum, upp->off, upp->flags & PP_IS_RETURN,
-		      upp->flags & PP_IS_ENABLED);
+	uprobe_delete(upp->dev, upp->inum, upp->off, upp->flags);
 }
 
 /*
diff --git a/runtest.sh b/runtest.sh
index ff0bc8db9a1e..92ef6e02463d 100755
--- a/runtest.sh
+++ b/runtest.sh
@@ -551,14 +551,22 @@ resultslog()
     rm -f $testdebug
 }
 
+# Flip a few env vars to tell dtrace to produce reproducible output.
+export _DTRACE_TESTING=t
+export LANGUAGE=C
+
 if [[ -z $USE_INSTALLED ]]; then
     dtrace="$(pwd)/build*/dtrace"
     test_libdir="$(pwd)/build/dlibs"
     test_ldflags="-L$(pwd)/build"
     test_incflags="-Iinclude -Iuts/common -Ibuild -Ilibdtrace -DARCH_$arch"
     helper_device="dtrace/test-$$"
-    dtprobed_flags="-n $helper_device -F"
+    # Pre-existing directories from earlier tests are just fine!
+    # dtprobed will clean things up.
+    mkdir -p $(pwd)/build/run/dtrace
+    dtprobed_flags="-n $helper_device -s$(pwd)/build/run/dtrace -F"
     export DTRACE_DOF_INIT_DEVNAME="/dev/$helper_device"
+    export DTRACE_OPT_DOFSTASHPATH="$(pwd)/build/run/dtrace"
 
     if [[ -z $(eval echo $dtrace) ]]; then
     	echo "No dtraces available." >&2
@@ -580,10 +588,6 @@ else
 fi
 export dtrace
 
-# Flip a few env vars to tell dtrace to produce reproducible output.
-export _DTRACE_TESTING=t
-export LANGUAGE=C
-
 # Figure out if the preprocessor supports -fno-diagnostics-show-option: if it
 # does, add a bunch of options designed to make GCC output look like it used
 # to.
diff --git a/test/unittest/usdt/tst.dlclose2.sh b/test/unittest/usdt/tst.dlclose2.sh
index 782e42555736..02b26210075d 100755
--- a/test/unittest/usdt/tst.dlclose2.sh
+++ b/test/unittest/usdt/tst.dlclose2.sh
@@ -5,7 +5,7 @@
 # Licensed under the Universal Permissive License v 1.0 as shown at
 # http://oss.oracle.com/licenses/upl.
 #
-# @@xfail: dtv2, no wildcard usdt probes yet
+# @@xfail: dtv2, no wildcard usdt probes across pids yet
 #
 PATH=/usr/bin:/usr/sbin:$PATH
 
-- 
2.42.0.271.g85384428f1




More information about the DTrace-devel mailing list