[DTrace-devel] [PATCH v2 1/6] Deferred attach of underlying USDT probes
eugene.loh at oracle.com
eugene.loh at oracle.com
Wed Oct 9 05:45:35 UTC 2024
From: Eugene Loh <eugene.loh at oracle.com>
We would like dtrace to trace USDT processes that start
even after the dtrace session has been launched. In that
case, the underlying probe cannot be attached when dtrace
starts up; rather, the BPF program must be attached once
the USDT process has been detected.
Therefore:
*) Make dt_bpf_load_prog() callable outside of dt_bpf.c.
*) Have update_uprobe() call dt_construct(), dt_link(),
dt_bpf_load_prog(), and attach() for any new underlying
probes.
Signed-off-by: Eugene Loh <eugene.loh at oracle.com>
---
libdtrace/dt_bpf.c | 2 +-
libdtrace/dt_bpf.h | 3 +
libdtrace/dt_prov_uprobe.c | 65 +++++++++-
test/unittest/usdt/tst.nusdtprobes.r | 5 +
test/unittest/usdt/tst.nusdtprobes.sh | 172 ++++++++++++++++++++++++++
5 files changed, 244 insertions(+), 3 deletions(-)
create mode 100644 test/unittest/usdt/tst.nusdtprobes.r
create mode 100755 test/unittest/usdt/tst.nusdtprobes.sh
diff --git a/libdtrace/dt_bpf.c b/libdtrace/dt_bpf.c
index 7e4a4461a..7cdd73552 100644
--- a/libdtrace/dt_bpf.c
+++ b/libdtrace/dt_bpf.c
@@ -1132,7 +1132,7 @@ dt_bpf_reloc_prog(dtrace_hdl_t *dtp, const dtrace_difo_t *dp)
*
* Note that DTrace generates BPF programs that are licensed under the GPL.
*/
-static int
+int
dt_bpf_load_prog(dtrace_hdl_t *dtp, const dt_probe_t *prp,
const dtrace_difo_t *dp, uint_t cflags)
{
diff --git a/libdtrace/dt_bpf.h b/libdtrace/dt_bpf.h
index 64f6c00a2..6757d4964 100644
--- a/libdtrace/dt_bpf.h
+++ b/libdtrace/dt_bpf.h
@@ -14,6 +14,7 @@
#include <dtrace/difo.h>
#include <dt_btf.h>
#include <dt_impl.h>
+#include <dt_probe.h>
struct dtrace_hdl;
@@ -89,6 +90,8 @@ extern int dt_bpf_prog_load(struct dtrace_hdl *, const struct dt_probe *prp,
size_t sz);
extern int dt_bpf_raw_tracepoint_open(const void *tp, int fd);
extern int dt_bpf_make_progs(struct dtrace_hdl *, uint_t);
+extern int dt_bpf_load_prog(dtrace_hdl_t *dtp, const dt_probe_t *prp,
+ const dtrace_difo_t *dp, uint_t cflags);
extern int dt_bpf_load_progs(struct dtrace_hdl *, uint_t);
extern void dt_bpf_init(struct dtrace_hdl *dtp);
diff --git a/libdtrace/dt_prov_uprobe.c b/libdtrace/dt_prov_uprobe.c
index 582fca6d1..f2ebf6cab 100644
--- a/libdtrace/dt_prov_uprobe.c
+++ b/libdtrace/dt_prov_uprobe.c
@@ -254,9 +254,13 @@ static int update_uprobe(dtrace_hdl_t *dtp, void *datap)
dt_probe_t *prp_next;
int i, prid = dtp->dt_probe_id;
dt_pcb_t pcb;
+ dtrace_optval_t dest_ok = DTRACEOPT_UNSET;
int fd = dtp->dt_usdt_namesmap_fd;
char probnam[DTRACE_PROVNAMELEN + DTRACE_MODNAMELEN + DTRACE_FUNCNAMELEN + DTRACE_NAMELEN];
+ /* Determine whether destructive actions are okay. */
+ dtrace_getopt(dtp, "destructive", &dest_ok);
+
/* Clear stale pids. */
purge_BPFmap(dtp);
@@ -311,6 +315,61 @@ static int update_uprobe(dtrace_hdl_t *dtp, void *datap)
prp_next = dt_list_next(prp);
+ /* Handle underlying probe. */
+ if (prp->prov->impl == &dt_uprobe) {
+ dtrace_difo_t *dp;
+ int cflags, fd, rc = -1;
+
+ /*
+ * Strictly speaking, we want the value passed in to
+ * dtrace_go(). In practice, its flags pertain to
+ * compilation and disassembly, which at this stage
+ * no longer interest us.
+ */
+ cflags = 0;
+
+ /* Check if the probe is already set up. */
+ if (prp->difo)
+ continue;
+
+ /* Make program. */
+ dp = dt_construct(dtp, prp, cflags, NULL);
+ if (dp == NULL)
+ continue; // FIXME in dt_bpf_make_progs() this is a fatal error; should we do the same here?
+ prp->difo = dp;
+
+ /* Load program. */
+ if (dt_link(dtp, prp, dp, NULL) == -1)
+ continue; // FIXME in dt_bpf_load_progs() this is a fatal error; should we do the same here?
+ if (dp->dtdo_flags & DIFOFLG_DESTRUCTIVE &&
+ dest_ok == DTRACEOPT_UNSET)
+ return dt_set_errno(dtp, EDT_DESTRUCTIVE);
+
+ fd = dt_bpf_load_prog(dtp, prp, dp, cflags);
+ if (fd == -1)
+ continue; // FIXME in dt_bpf_load_progs() this is a fatal error; should we do the same here?
+
+ if (prp->prov->impl->attach)
+ rc = prp->prov->impl->attach(dtp, prp, fd);
+
+ if (rc == -ENOTSUPP) {
+ char *s;
+
+ close(fd);
+ if (asprintf(&s, "Failed to enable %s:%s:%s:%s",
+ prp->desc->prv, prp->desc->mod,
+ prp->desc->fun, prp->desc->prb) == -1)
+ return dt_set_errno(dtp, EDT_ENABLING_ERR);
+ dt_handle_rawerr(dtp, s);
+ free(s);
+ } else if (rc < 0) {
+ close(fd);
+ return dt_set_errno(dtp, EDT_ENABLING_ERR);
+ }
+
+ continue;
+ }
+
/* Make sure it is an overlying USDT probe. */
if (prp->prov->impl != &dt_usdt)
continue;
@@ -398,9 +457,11 @@ static int update_uprobe(dtrace_hdl_t *dtp, void *datap)
dt_bpf_map_update(dtp->dt_usdt_pridsmap_fd, &key, &val);
} else if (val.prid != oval.prid || val.mask != oval.mask) {
/*
- * This can happen when two overlying probes map to the same underlying probe for the same pid.
- * E.g., pid:::entry and pid:::0, or pid:::$offset and usdt:::.
+ * Two different USDT probes should never map to the same
+ * underlying probe for the same pid. Nor should the bit
+ * mask ever change.
*/
+ assert(0);
} else {
/*
* Nothing to do, it already is in the map.
diff --git a/test/unittest/usdt/tst.nusdtprobes.r b/test/unittest/usdt/tst.nusdtprobes.r
new file mode 100644
index 000000000..d894af92e
--- /dev/null
+++ b/test/unittest/usdt/tst.nusdtprobes.r
@@ -0,0 +1,5 @@
+try ""
+try "-xnusdtprobes=40"
+try "-xnusdtprobes=39"
+Files check.txt.sorted and dtrace.out.sorted differ
+success
diff --git a/test/unittest/usdt/tst.nusdtprobes.sh b/test/unittest/usdt/tst.nusdtprobes.sh
new file mode 100755
index 000000000..50f18a6ca
--- /dev/null
+++ b/test/unittest/usdt/tst.nusdtprobes.sh
@@ -0,0 +1,172 @@
+#!/bin/bash
+#
+# Oracle Linux DTrace.
+# Copyright (c) 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.
+#
+# This test verifies the nusdtprobes option.
+# @@timeout: 100
+
+dtrace=$1
+
+# Set up test directory.
+
+DIRNAME=$tmpdir/nusdtprobes.$$.$RANDOM
+mkdir -p $DIRNAME
+cd $DIRNAME
+
+# Make the trigger.
+
+cat << EOF > prov.d
+provider testprov {
+ probe foo0();
+ probe foo1();
+ probe foo2();
+ probe foo3();
+ probe foo4();
+ probe foo5();
+ probe foo6();
+ probe foo7();
+ probe foo8();
+ probe foo9();
+};
+EOF
+
+cat << EOF > main.c
+#include <unistd.h>
+#include "prov.h"
+
+int
+main(int argc, char **argv)
+{
+ while (1) {
+ usleep(1000);
+
+ TESTPROV_FOO0();
+ TESTPROV_FOO1();
+ TESTPROV_FOO2();
+ TESTPROV_FOO3();
+ TESTPROV_FOO4();
+ TESTPROV_FOO5();
+ TESTPROV_FOO6();
+ TESTPROV_FOO7();
+ TESTPROV_FOO8();
+ TESTPROV_FOO9();
+ }
+
+ return 0;
+}
+EOF
+
+# Build the trigger.
+
+$dtrace -h -s prov.d
+if [ $? -ne 0 ]; then
+ echo "failed to generate header file" >&2
+ exit 1
+fi
+cc $test_cppflags -c main.c
+if [ $? -ne 0 ]; then
+ echo "failed to compile test" >&2
+ exit 1
+fi
+$dtrace -G -64 -s prov.d main.o
+if [ $? -ne 0 ]; then
+ echo "failed to create DOF" >&2
+ exit 1
+fi
+cc $test_cppflags -o main main.o prov.o
+if [ $? -ne 0 ]; then
+ echo "failed to link final executable" >&2
+ exit 1
+fi
+
+# Test nusdtprobes settings.
+#
+# We will start teams of processes, each with 4 members, each in turn
+# with 10 USDT probes. So, regardless of how many teams are run in
+# succession, at any one time DTrace needs room for at least 40 USDT
+# probes. The default and -xnusdtprobes=40 settings should work, but
+# -xnusdtprobes=39 should not.
+nteams=2
+nmmbrs=4
+
+for nusdt in "" "-xnusdtprobes=40" "-xnusdtprobes=39"; do
+
+ echo try '"'$nusdt'"'
+
+ # Start DTrace.
+
+ rm -f dtrace.out
+ $dtrace $dt_flags $nusdt -Zq -o dtrace.out -n '
+ testprov*:::
+ {
+ @[probeprov, probemod, probefunc, probename] = count();
+ }' &
+ dtpid=$!
+ sleep 2
+ if [[ ! -d /proc/$dtpid ]]; then
+ echo ERROR dtrace died
+ cat dtrace.out
+ exit 1
+ fi
+
+ # Start teams of processes, only one team at a time.
+
+ rm -f check.txt
+ for (( iteam = 0; iteam < $nteams; iteam++ )); do
+ # Start the team, writing out expected output.
+ sleep 2
+ for (( immbr = 0; immbr < $nmmbrs; immbr++ )); do
+ ./main &
+ pids[$immbr]=$!
+ for j in `seq 0 9`; do
+ echo testprov${pids[$immbr]} main main foo$j >> check.txt
+ done
+ done
+
+ # Kill the team.
+ sleep 3
+ for (( immbr = 0; immbr < $nmmbrs; immbr++ )); do
+ kill ${pids[$immbr]}
+ done
+ done
+
+ # Kill DTrace.
+
+ kill $dtpid
+ wait
+
+ # Strip the count() value out since we do not know its exact value.
+
+ awk 'NF == 5 { print $1, $2, $3, $4 }' dtrace.out | sort > dtrace.out.sorted
+
+ # Check.
+
+ sort check.txt > check.txt.sorted
+ if [ x$nusdt == x"-xnusdtprobes=39" ]; then
+ # Results should not agree with check.txt.
+ if diff -q check.txt.sorted dtrace.out.sorted; then
+ echo ERROR unexpected agreement
+ cat dtrace.out
+ exit 0
+ fi
+ else
+ # Results should agree with check.txt.
+ if ! diff -q check.txt.sorted dtrace.out.sorted; then
+ echo ERROR output disagrees
+ echo === expected ===
+ cat check.txt.sorted
+ echo === got ===
+ cat dtrace.out.sorted
+ echo === diff ===
+ diff check.txt.sorted dtrace.out.sorted
+ exit 1
+ fi
+ fi
+done
+
+echo success
+
+exit 0
--
2.43.5
More information about the DTrace-devel
mailing list