[DTrace-devel] [PATCH v2] Allow probes not to attach if they were not "needed"

eugene.loh at oracle.com eugene.loh at oracle.com
Thu Sep 18 06:02:13 UTC 2025


From: Eugene Loh <eugene.loh at oracle.com>

Various kernels prevent the placement of uprobes on particular
instructions -- e.g., on x86 on hlt or multi-byte nops, or on aarch64 on
autiasp.  This means, for example, that one cannot place a pid probe on
every possible instruction.  If a user explicitly places a probe on a
forbidden instruction, an error message indicates there is a problem.

On the other hand, if the user specifies a wildcard in a pid probe
name, we should skip instructions where no uprobe can be placed.

The problem is that we do not find out an instruction is problematic
until we try to attach, which is long after probe descriptions have been
processed.  And when we walk probe descriptions, we do not have
information about which kernels and instructions are problematic.

The motivating problem pertains to pid probes, but we would like
a provider-agnostic solution.

Set an "OPTIONAL" probe flag when a probe is not needed:

* In dt_probe.h, DT_PROBE_FLAG_OPTIONAL is used for the "flags"
  member of dt_probe_t.

* For pid-style probes (including USDT and stapsdt), in
  include/dtrace/pid.h, DT_PID_PSP_FLAG_OPTIONAL is used for
  the pps_flags member of pid_probespec_t.

Remove the old check on "hlt" instructions, since it was not sufficiently
general for other kernels and other problematic instructions.

Existing tests that expose the problem are

    test/unittest/pid/tst.coverage.d
    test/unittest/pid/tst.emptystack.d
    test/unittest/pid/tst.entry_off0.sh
        hlt instruction on x86

    test/unittest/pid/tst.float.d
        multi-byte nop on x86 on newer platforms

    test/unittest/pid/tst.entry_off0.sh
        autiasp instruction on aarch64 on newer platforms
        depending on how the trigger process was built

New tests are added to check that a probe set explicitly on a
problematic instruction will still lead to an error, even if a
probe description before or after it tolerates attach failure.

Signed-off-by: Eugene Loh <eugene.loh at oracle.com>
---
 include/dtrace/pid.h                          |  3 +++
 libdtrace/dt_bpf.c                            |  2 +-
 libdtrace/dt_pid.c                            |  7 +----
 libdtrace/dt_probe.h                          |  3 +++
 libdtrace/dt_prov_uprobe.c                    | 26 ++++++++++++++++---
 .../pid/err.opt_after_needed.aarch64.x        |  3 +++
 test/unittest/pid/err.opt_after_needed.sh     | 25 ++++++++++++++++++
 .../pid/err.opt_after_needed.x86_64.x         |  6 +++++
 .../pid/err.opt_before_needed.aarch64.x       |  3 +++
 test/unittest/pid/err.opt_before_needed.sh    | 25 ++++++++++++++++++
 .../pid/err.opt_before_needed.x86_64.x        |  6 +++++
 test/unittest/pid/err.optional.aarch64.x      |  3 +++
 test/unittest/pid/err.optional.sh             | 25 ++++++++++++++++++
 test/unittest/pid/err.optional.x86_64.x       |  6 +++++
 test/unittest/pid/tst.entry_off0.sh           |  4 +--
 15 files changed, 135 insertions(+), 12 deletions(-)
 create mode 100755 test/unittest/pid/err.opt_after_needed.aarch64.x
 create mode 100755 test/unittest/pid/err.opt_after_needed.sh
 create mode 100755 test/unittest/pid/err.opt_after_needed.x86_64.x
 create mode 100755 test/unittest/pid/err.opt_before_needed.aarch64.x
 create mode 100755 test/unittest/pid/err.opt_before_needed.sh
 create mode 100755 test/unittest/pid/err.opt_before_needed.x86_64.x
 create mode 100755 test/unittest/pid/err.optional.aarch64.x
 create mode 100755 test/unittest/pid/err.optional.sh
 create mode 100755 test/unittest/pid/err.optional.x86_64.x

diff --git a/include/dtrace/pid.h b/include/dtrace/pid.h
index 8ddb1167e..4a239b076 100644
--- a/include/dtrace/pid.h
+++ b/include/dtrace/pid.h
@@ -47,6 +47,7 @@ typedef struct pid_probespec {
 	size_t pps_xargvlen;			/* (high estimate of) length of array */
 	int8_t *pps_argmap;			/* mapped arg indexes */
 	char *pps_sargv;			/* list of arg sources */
+	int pps_flags;				/* flags */
 
 	/*
 	 * Fields below this point do not apply to underlying probes.
@@ -55,4 +56,6 @@ typedef struct pid_probespec {
 	uint64_t pps_nameoff;			/* offset to use for name */
 } pid_probespec_t;
 
+#define DT_PID_PSP_FLAG_OPTIONAL	1	/* probe is optional */
+
 #endif /* _DTRACE_PID_H */
diff --git a/libdtrace/dt_bpf.c b/libdtrace/dt_bpf.c
index 31781ac9f..0223764a6 100644
--- a/libdtrace/dt_bpf.c
+++ b/libdtrace/dt_bpf.c
@@ -1385,7 +1385,7 @@ dt_bpf_load_progs(dtrace_hdl_t *dtp, uint_t cflags)
 		if (prp->prov->impl->attach)
 			rc = prp->prov->impl->attach(dtp, prp, fd);
 
-		if (rc < 0) {
+		if (rc < 0 && !(prp->flags & DT_PROBE_FLAG_OPTIONAL)) {
 			close(fd);
 			dt_attach_error(dtp, rc,
 					prp->desc->prv, prp->desc->mod,
diff --git a/libdtrace/dt_pid.c b/libdtrace/dt_pid.c
index ffc52132f..7d6cfb4dc 100644
--- a/libdtrace/dt_pid.c
+++ b/libdtrace/dt_pid.c
@@ -386,15 +386,10 @@ dt_pid_per_sym(dt_pid_probe_t *pp, const GElf_Sym *symp, const char *func)
 #define disasm(x, y) 4
 #endif
 
+		psp->pps_flags |= DT_PID_PSP_FLAG_OPTIONAL;
 		for (off = 0; off < symp->st_size; off += disasm(off, &disasm_info)) {
 			char offstr[32];
 
-#if defined(__amd64)
-			/* Newer kernels do not allow uprobes on "hlt" instructions. */
-			if ((unsigned int)disasm_info.buffer[off] == 0xf4)
-				continue;
-#endif
-
 			snprintf(offstr, sizeof(offstr), "%lx", off);
 			if (!gmatch(offstr, pp->dpp_name))
 				continue;
diff --git a/libdtrace/dt_probe.h b/libdtrace/dt_probe.h
index 2a78cb9ca..637875183 100644
--- a/libdtrace/dt_probe.h
+++ b/libdtrace/dt_probe.h
@@ -53,10 +53,13 @@ typedef struct dt_probe {
 	uint8_t *mapping;		/* translated argument mapping */
 	dtrace_typeinfo_t *argv;	/* output argument types */
 	int argc;			/* output argument count */
+	int flags;			/* flags for the probe */
 	dt_probe_instance_t *pr_inst;	/* list of functions and offsets */
 	dtrace_difo_t *difo;		/* BPF probe program */
 } dt_probe_t;
 
+#define DT_PROBE_FLAG_OPTIONAL	1	/* probe is optional */
+
 extern dt_probe_t *dt_probe_lookup2(dt_provider_t *, const char *);
 extern dt_probe_t *dt_probe_create(dtrace_hdl_t *, dt_ident_t *, int,
     dt_node_t *, uint_t, dt_node_t *, uint_t);
diff --git a/libdtrace/dt_prov_uprobe.c b/libdtrace/dt_prov_uprobe.c
index 07b26f20f..3b7145a50 100644
--- a/libdtrace/dt_prov_uprobe.c
+++ b/libdtrace/dt_prov_uprobe.c
@@ -957,9 +957,16 @@ static dt_probe_t *create_underlying(dtrace_hdl_t *dtp,
 				       upp);
 		if (uprp == NULL)
 			goto fail;
-	} else
+
+		if (psp->pps_flags & DT_PID_PSP_FLAG_OPTIONAL)
+			uprp->flags |= DT_PROBE_FLAG_OPTIONAL;
+	} else {
 		upp = uprp->prv_data;
 
+		if (!(psp->pps_flags & DT_PID_PSP_FLAG_OPTIONAL))
+			uprp->flags &= ~DT_PROBE_FLAG_OPTIONAL;
+	}
+
 	/*
 	 * Only one USDT probe can correspond to each underlying probe.
 	 */
@@ -1049,6 +1056,12 @@ static int provide_probe(dtrace_hdl_t *dtp, const pid_probespec_t *psp,
 	/* Look up the overlying probe. */
 	prp = dt_probe_lookup(dtp, &pd);
 	if (prp != NULL) {
+		/*
+		 * If not optional, pass that info on.
+		 */
+		if (!(psp->pps_flags & DT_PID_PSP_FLAG_OPTIONAL))
+			prp->flags &= ~DT_PROBE_FLAG_OPTIONAL;
+
 		/*
 		 * Probe already exists.  If it's already in the underlying
 		 * probe's probe list, there is nothing left to do.
@@ -1079,10 +1092,17 @@ static int provide_probe(dtrace_hdl_t *dtp, const pid_probespec_t *psp,
 	 */
 
 	pup->probe = uprp;
-	if (prp == NULL)
+	if (prp == NULL) {
 		prp = dt_probe_insert(dtp, pvp, pd.prv, pd.mod, pd.fun, pd.prb,
 				      pup);
-	else
+
+		/*
+		 * If not optional, pass that info on.
+		 */
+		if (psp->pps_flags & DT_PID_PSP_FLAG_OPTIONAL)
+			prp->flags |= DT_PROBE_FLAG_OPTIONAL;
+
+	} else
 		dt_list_append((dt_list_t *)prp->prv_data, pup);
 
 	if (prp == NULL) {
diff --git a/test/unittest/pid/err.opt_after_needed.aarch64.x b/test/unittest/pid/err.opt_after_needed.aarch64.x
new file mode 100755
index 000000000..bd5b41a36
--- /dev/null
+++ b/test/unittest/pid/err.opt_after_needed.aarch64.x
@@ -0,0 +1,3 @@
+#!/bin/sh
+echo "skip on aarch64"
+exit 2
diff --git a/test/unittest/pid/err.opt_after_needed.sh b/test/unittest/pid/err.opt_after_needed.sh
new file mode 100755
index 000000000..3ee65cd93
--- /dev/null
+++ b/test/unittest/pid/err.opt_after_needed.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+#
+# Oracle Linux DTrace.
+# Copyright (c) 2025, 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.
+#
+
+# We should not be able to trace on a hlt instruction, which is forbidden
+# by the kernel.
+#
+# (Test tst.entry_off0.sh should be okay, since there the probe on that
+# instruction is optional, and dtrace will silently skip over such
+# problematic instructions.  Here, however, the probe on the instruction
+# is explicitly requested by the user.)
+
+dtrace=$1
+trig=`pwd`/test/triggers/ustack-tst-basic
+off=`${OBJDUMP} -d $trig | awk '/hlt/ {sub(/:/, ""); print $1}'`
+
+$dtrace $dt_flags -c $trig -n 'pid$target:a.out:-:'$off',pid$target:a.out::
+{
+	trace("hlt instruction");
+	exit(0);
+}'
diff --git a/test/unittest/pid/err.opt_after_needed.x86_64.x b/test/unittest/pid/err.opt_after_needed.x86_64.x
new file mode 100755
index 000000000..158336d98
--- /dev/null
+++ b/test/unittest/pid/err.opt_after_needed.x86_64.x
@@ -0,0 +1,6 @@
+#!/bin/sh
+if ! ${OBJDUMP} -d test/triggers/ustack-tst-basic | grep hlt; then
+    echo "did not find hlt instruction in trigger"
+    exit 2
+fi
+exit 0
diff --git a/test/unittest/pid/err.opt_before_needed.aarch64.x b/test/unittest/pid/err.opt_before_needed.aarch64.x
new file mode 100755
index 000000000..bd5b41a36
--- /dev/null
+++ b/test/unittest/pid/err.opt_before_needed.aarch64.x
@@ -0,0 +1,3 @@
+#!/bin/sh
+echo "skip on aarch64"
+exit 2
diff --git a/test/unittest/pid/err.opt_before_needed.sh b/test/unittest/pid/err.opt_before_needed.sh
new file mode 100755
index 000000000..f1f172a18
--- /dev/null
+++ b/test/unittest/pid/err.opt_before_needed.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+#
+# Oracle Linux DTrace.
+# Copyright (c) 2025, 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.
+#
+
+# We should not be able to trace on a hlt instruction, which is forbidden
+# by the kernel.
+#
+# (Test tst.entry_off0.sh should be okay, since there the probe on that
+# instruction is optional, and dtrace will silently skip over such
+# problematic instructions.  Here, however, the probe on the instruction
+# is explicitly requested by the user.)
+
+dtrace=$1
+trig=`pwd`/test/triggers/ustack-tst-basic
+off=`${OBJDUMP} -d $trig | awk '/hlt/ {sub(/:/, ""); print $1}'`
+
+$dtrace $dt_flags -c $trig -n 'pid$target:a.out::,pid$target:a.out:-:'$off'
+{
+	trace("hlt instruction");
+	exit(0);
+}'
diff --git a/test/unittest/pid/err.opt_before_needed.x86_64.x b/test/unittest/pid/err.opt_before_needed.x86_64.x
new file mode 100755
index 000000000..158336d98
--- /dev/null
+++ b/test/unittest/pid/err.opt_before_needed.x86_64.x
@@ -0,0 +1,6 @@
+#!/bin/sh
+if ! ${OBJDUMP} -d test/triggers/ustack-tst-basic | grep hlt; then
+    echo "did not find hlt instruction in trigger"
+    exit 2
+fi
+exit 0
diff --git a/test/unittest/pid/err.optional.aarch64.x b/test/unittest/pid/err.optional.aarch64.x
new file mode 100755
index 000000000..bd5b41a36
--- /dev/null
+++ b/test/unittest/pid/err.optional.aarch64.x
@@ -0,0 +1,3 @@
+#!/bin/sh
+echo "skip on aarch64"
+exit 2
diff --git a/test/unittest/pid/err.optional.sh b/test/unittest/pid/err.optional.sh
new file mode 100755
index 000000000..1dbef4e45
--- /dev/null
+++ b/test/unittest/pid/err.optional.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+#
+# Oracle Linux DTrace.
+# Copyright (c) 2025, 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.
+#
+
+# We should not be able to trace on a hlt instruction, which is forbidden
+# by the kernel.
+#
+# (Test tst.entry_off0.sh should be okay, since there the probe on that
+# instruction is optional, and dtrace will silently skip over such
+# problematic instructions.  Here, however, the probe on the instruction
+# is explicitly requested by the user.)
+
+dtrace=$1
+trig=`pwd`/test/triggers/ustack-tst-basic
+off=`${OBJDUMP} -d $trig | awk '/hlt/ {sub(/:/, ""); print $1}'`
+
+$dtrace $dt_flags -c $trig -n 'pid$target:a.out:-:'$off'
+{
+	trace("hlt instruction");
+	exit(0);
+}'
diff --git a/test/unittest/pid/err.optional.x86_64.x b/test/unittest/pid/err.optional.x86_64.x
new file mode 100755
index 000000000..158336d98
--- /dev/null
+++ b/test/unittest/pid/err.optional.x86_64.x
@@ -0,0 +1,6 @@
+#!/bin/sh
+if ! ${OBJDUMP} -d test/triggers/ustack-tst-basic | grep hlt; then
+    echo "did not find hlt instruction in trigger"
+    exit 2
+fi
+exit 0
diff --git a/test/unittest/pid/tst.entry_off0.sh b/test/unittest/pid/tst.entry_off0.sh
index 1a4c3d207..304be4568 100755
--- a/test/unittest/pid/tst.entry_off0.sh
+++ b/test/unittest/pid/tst.entry_off0.sh
@@ -1,14 +1,14 @@
 #!/bin/bash
 #
 # Oracle Linux DTrace.
-# Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2024, 2025, 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.
 #
 
 # Although the D script takes only "one second," it takes a long time to
 # shut down.  Until that has been solved, increase the timeout for the test.
-# @@timeout: 120
+# @@timeout: 240
 
 dtrace=$1
 
-- 
2.47.3




More information about the DTrace-devel mailing list