Linux DTrace development list
 help / color / mirror / Atom feed
* [PATCH 0/6] dtrace: support user-space symbol resolution via uresolve()
@ 2026-06-05 22:12 Alan Maguire
  2026-06-05 22:12 ` [PATCH 1/6] libdtrace: Pack declared bitfields into C storage units Alan Maguire
                   ` (5 more replies)
  0 siblings, 6 replies; 10+ messages in thread
From: Alan Maguire @ 2026-06-05 22:12 UTC (permalink / raw)
  To: dtrace; +Cc: dtrace-devel, Alan Maguire

Resolution of userspace symbols is added via subfunction

uresolve("name"[,fallback])

which returns the runtime-resolved address of the symbol for use
in probe context like copyin()ing values.

At cg time DTrace records the relative symbol offset and at runtime
we compute the actual location by utilizing the probe context
userspace instruction pointer and the cg-recorded probe offset;

runtime_symbol_addr = symbol_offset + userspace_pc - probe_offset

Note that this series - since it adds support for systemwide
probes to uresolve() - depends on the patches in [1], so a
clean devel branch will need the systemwide probe patches
applied.

Patches 1 and 2 fix a few issues encountered when using uresolve()
to help construct python stacks.  Patch 3 adds the recording of
the probe offset used in the above computation in machine state.
Patch 4 adds uresolve() support, while patch 5 adds negative and
positive tests for symbol resolution.  Patch 6 documents uresolve().

One specific application of this is to walk python stacks; for
python3.6 we can uresolve("_PyThreadState_Current") and walk
stack frames from there.

[1] https://lore.kernel.org/dtrace/20260304080125.649031-1-alan.maguire@oracle.com/

Alan Maguire (6):
  libdtrace: Pack declared bitfields into C storage units
  libdtrace: do not taint scalar alloca member loads
  dtrace: add 'probeoff' variable to record relative offset of probes
  dtrace: Add uresolve(symbol_name[, optional_fallback]) for symbol
    lookup
  test,funcs: add uresolve() tests
  docs: document uresolve() in the user guide

 bpf/get_bvar.c                                |   7 +
 doc/userguide/index.md                        |   1 +
 doc/userguide/reference/dtrace_functions.md   |   4 +
 doc/userguide/reference/function_uresolve.md  |  61 +++
 include/dtrace/dif_defines.h                  |   4 +-
 libdtrace/dt_bpf.c                            |   1 +
 libdtrace/dt_cg.c                             | 503 +++++++++++++++++-
 libdtrace/dt_dctx.h                           |   2 +
 libdtrace/dt_decl.c                           | 198 +++++--
 libdtrace/dt_decl.h                           |   6 +
 libdtrace/dt_open.c                           |   4 +
 libdtrace/dt_parser.c                         |  15 +-
 libdtrace/dt_probe.h                          |   1 +
 libdtrace/dt_prov_uprobe.c                    |  66 ++-
 test/unittest/bitfields/tst.SizeofBitField.d  |   6 +-
 test/unittest/bitfields/tst.SizeofBitField.r  |  12 +-
 .../tst.alloca-struct-scalar-load-taint.d     |  35 ++
 test/unittest/funcs/uresolve/err.nofallback.d |  21 +
 test/unittest/funcs/uresolve/err.nofallback.r |   2 +
 test/unittest/funcs/uresolve/tst.fallback.d   |  25 +
 test/unittest/funcs/uresolve/tst.fallback.r   |   2 +
 test/unittest/funcs/uresolve/tst.positive.sh  | 144 +++++
 22 files changed, 1054 insertions(+), 66 deletions(-)
 create mode 100644 doc/userguide/reference/function_uresolve.md
 create mode 100644 test/unittest/funcs/alloca/tst.alloca-struct-scalar-load-taint.d
 create mode 100644 test/unittest/funcs/uresolve/err.nofallback.d
 create mode 100644 test/unittest/funcs/uresolve/err.nofallback.r
 create mode 100644 test/unittest/funcs/uresolve/tst.fallback.d
 create mode 100644 test/unittest/funcs/uresolve/tst.fallback.r
 create mode 100755 test/unittest/funcs/uresolve/tst.positive.sh

-- 
2.43.5


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH 1/6] libdtrace: Pack declared bitfields into C storage units
  2026-06-05 22:12 [PATCH 0/6] dtrace: support user-space symbol resolution via uresolve() Alan Maguire
@ 2026-06-05 22:12 ` Alan Maguire
  2026-06-06  1:54   ` Kris Van Hees
  2026-06-05 22:12 ` [PATCH 2/6] libdtrace: do not taint scalar alloca member loads Alan Maguire
                   ` (4 subsequent siblings)
  5 siblings, 1 reply; 10+ messages in thread
From: Alan Maguire @ 2026-06-05 22:12 UTC (permalink / raw)
  To: dtrace; +Cc: dtrace-devel, Alan Maguire, OpenAI Codex

dt_decl_member() represented each D-declared bitfield as a standalone
integer type sized to the field width, then let libctf auto-place the
member.  That made adjacent bitfields consume whole bytes instead of
sharing the declared C storage unit.  Unnamed padding fields such as
`unsigned int :24` were therefore treated as additional storage, causing
ABI-visible layout drift; for example CPython's PyASCIIObject was sized
as 56 bytes instead of 48 for python 3.6 when associated python3.6
headers were included.

The problem in that case was the "state" bitfield which had the form:

  struct {
      unsigned int interned:2;
      unsigned int kind:3;
      unsigned int compact:1;
      unsigned int ascii:1;
      unsigned int ready:1;
      unsigned int :24;
  } state;

i.e. it should have been a packed unsigned int (4 bytes) but DTrace
was using adding 8 bytes to the representation size, also throwing
off field offsets that followed.

Track bitfield allocation state while declaring struct/union members.
For each storage unit, add an empty-name CTF member using the original
base type to force the containing type to the C ABI size, and add named
bitfields as CTF slices at explicit bit offsets within that unit.  Reset
the allocation state for zero-width unnamed fields and non-bitfield
members.

Also make bitfield code generation choose load/store sizes from both the
field width and its intra-byte offset, since packed fields can now cross
byte boundaries.

Update the bitfield sizeof test expectations to C ABI sizes and add a
regression test for unnamed padding matching the PyASCIIObject layout:
state size 4, wstr offset 40, total size 48.

Signed-off-by: Alan Maguire <alan.maguire@oracle.com>
Assisted-by: OpenAI Codex (GPT-5) <codex@openai.com>
---
 libdtrace/dt_cg.c                            |  33 ++--
 libdtrace/dt_decl.c                          | 198 ++++++++++++++++---
 libdtrace/dt_decl.h                          |   6 +
 test/unittest/bitfields/tst.SizeofBitField.d |   6 +-
 test/unittest/bitfields/tst.SizeofBitField.r |  12 +-
 5 files changed, 201 insertions(+), 54 deletions(-)

diff --git a/libdtrace/dt_cg.c b/libdtrace/dt_cg.c
index a77c022c..0859e9cb 100644
--- a/libdtrace/dt_cg.c
+++ b/libdtrace/dt_cg.c
@@ -92,6 +92,12 @@ clp2(size_t x)
 	return x + 1;
 }
 
+static size_t
+dt_cg_bitfield_size(uint_t offset, uint_t bits)
+{
+	return clp2(P2ROUNDUP((offset % NBBY) + bits, NBBY) / NBBY);
+}
+
 /*
  * Determine the load size for the specified node and CTF type, and return the
  * equivalent BPF size specifier.
@@ -104,13 +110,13 @@ dt_cg_ldsize(dt_node_t *dnp, ctf_file_t *ctfp, ctf_id_t type, ssize_t *ret_size)
 
 	/*
 	 * If we're loading a bit-field, the size of our load is found by
-	 * rounding cte_bits up to a byte boundary and then finding the
-	 * nearest power of two to this value (see clp2(), above).
+	 * rounding the bit offset and size up to a byte boundary and then
+	 * finding the nearest power of two to this value (see clp2(), above).
 	 */
 	if (dnp &&
 	    (dnp->dn_flags & DT_NF_BITFIELD) &&
 	    ctf_type_encoding(ctfp, type, &e) != CTF_ERR)
-		size = clp2(P2ROUNDUP(e.cte_bits, NBBY) / NBBY);
+		size = dt_cg_bitfield_size(e.cte_offset, e.cte_bits);
 	else
 		size = ctf_type_size(ctfp, type);
 
@@ -3586,7 +3592,8 @@ dt_cg_field_get(dt_node_t *dnp, dt_irlist_t *dlp, dt_regset_t *drp,
 	if (dnp->dn_flags & DT_NF_REF)
 		return;
 
-	op = dt_cg_ldsize(dnp, fp, mp->ctm_type, &size);
+	size = dt_cg_bitfield_size(offset, e.cte_bits);
+	op = bpf_ldst_size(size, 0);
 	if (dnp->dn_left->dn_flags & (DT_NF_ALLOCA | DT_NF_DPTR))
 		emit(dlp, BPF_LOAD(op, reg, reg, 0));
 	else
@@ -3653,8 +3660,7 @@ dt_cg_field_get(dt_node_t *dnp, dt_irlist_t *dlp, dt_regset_t *drp,
 	 * nearest power of two to this value (see clp2(), above).
 	 */
 #ifdef _BIG_ENDIAN
-	shift = clp2(P2ROUNDUP(e.cte_bits, NBBY) / NBBY) * NBBY -
-		(offset + e.cte_bits);
+	shift = size * NBBY - (offset + e.cte_bits);
 #else
 	shift = offset;
 #endif
@@ -3686,7 +3692,7 @@ dt_cg_field_set(dt_node_t *src, dt_irlist_t *dlp,
 {
 	uint64_t cmask, fmask, shift;
 	int r1, r2;
-	size_t offset;
+	size_t offset, size;
 
 	ctf_membinfo_t m;
 	ctf_encoding_t e;
@@ -3731,9 +3737,10 @@ dt_cg_field_set(dt_node_t *src, dt_irlist_t *dlp,
 	 * pass through the containing bits and zero the field bits.
 	 */
 #ifdef _BIG_ENDIAN
-	shift = clp2(P2ROUNDUP(e.cte_bits, NBBY) / NBBY) * NBBY -
-		(offset % NBBY + e.cte_bits);
+	size = dt_cg_bitfield_size(offset, e.cte_bits);
+	shift = size * NBBY - (offset % NBBY + e.cte_bits);
 #else
+	size = dt_cg_bitfield_size(offset, e.cte_bits);
 	shift = offset % NBBY;
 #endif
 	fmask = (1ULL << e.cte_bits) - 1;
@@ -3748,7 +3755,7 @@ dt_cg_field_set(dt_node_t *src, dt_irlist_t *dlp,
 	 * r2 <<= shift
 	 * r1 |= r2
 	 */
-	emit(dlp, BPF_LOAD(dt_cg_ldsize(dst, fp, m.ctm_type, NULL), r1, dst->dn_reg, 0));
+	emit(dlp, BPF_LOAD(bpf_ldst_size(size, 0), r1, dst->dn_reg, 0));
 	dt_cg_setx(dlp, r2, cmask);
 	emit(dlp, BPF_ALU64_REG(BPF_AND, r1, r2));
 	dt_cg_setx(dlp, r2, fmask);
@@ -3770,12 +3777,12 @@ dt_cg_store(dt_node_t *src, dt_irlist_t *dlp, dt_regset_t *drp, dt_node_t *dst)
 
 	/*
 	 * If we're storing into a bit-field, the size of our store is found by
-	 * rounding dst's cte_bits up to a byte boundary and then finding the
-	 * nearest power of two to this value (see clp2(), above).
+	 * rounding dst's bit offset and size up to a byte boundary and then
+	 * finding the nearest power of two to this value (see clp2(), above).
 	 */
 	if ((dst->dn_flags & DT_NF_BITFIELD) &&
 	    ctf_type_encoding(dst->dn_ctfp, dst->dn_type, &e) != CTF_ERR)
-		size = clp2(P2ROUNDUP(e.cte_bits, NBBY) / NBBY);
+		size = dt_cg_bitfield_size(e.cte_offset, e.cte_bits);
 	else
 		size = dt_node_type_size(dst);
 
diff --git a/libdtrace/dt_decl.c b/libdtrace/dt_decl.c
index 1f4936dd..29c58189 100644
--- a/libdtrace/dt_decl.c
+++ b/libdtrace/dt_decl.c
@@ -16,6 +16,8 @@
 #include <dt_module.h>
 #include <dt_impl.h>
 
+static void dt_decl_bf_reset(dt_scope_t *);
+
 static dt_decl_t *
 dt_decl_check(dt_decl_t *ddp)
 {
@@ -129,6 +131,7 @@ dt_decl_pop(void)
 	dsp->ds_type = CTF_ERR;
 	dsp->ds_class = DT_DC_DEFAULT;
 	dsp->ds_enumval = -1;
+	dt_decl_bf_reset(dsp);
 
 	return ddp;
 }
@@ -483,6 +486,89 @@ dt_decl_sou(uint_t kind, char *name)
 	return ddp;
 }
 
+static ulong_t
+dt_decl_bf_align(ulong_t off, ulong_t align)
+{
+	return align == 0 ? off : ((off + align - 1) / align) * align;
+}
+
+static void
+dt_decl_bf_reset(dt_scope_t *dsp)
+{
+	dsp->ds_bf_active = 0;
+	dsp->ds_bf_base = CTF_ERR;
+	dsp->ds_bf_unit_off = 0;
+	dsp->ds_bf_unit_bits = 0;
+	dsp->ds_bf_unit_align = 0;
+	dsp->ds_bf_next = 0;
+}
+
+static ulong_t
+dt_decl_bf_size(dt_scope_t *dsp)
+{
+	ssize_t size = ctf_type_size(dsp->ds_ctfp, dsp->ds_type);
+
+	if (size < 0) {
+		xyerror(D_UNKNOWN, "failed to determine struct size: %s\n",
+		    ctf_errmsg(ctf_errno(dsp->ds_ctfp)));
+	}
+
+	return (ulong_t)size * CHAR_BIT;
+}
+
+static void
+dt_decl_bf_anchor(dt_scope_t *dsp, ctf_id_t base, ulong_t off,
+    const char *idname)
+{
+	if (ctf_add_member_offset(dsp->ds_ctfp, dsp->ds_type, "",
+	    base, off) == CTF_ERR) {
+		xyerror(D_UNKNOWN, "failed to define storage for "
+		    "bit-field '%s': %s\n", idname,
+		    ctf_errmsg(ctf_errno(dsp->ds_ctfp)));
+	}
+}
+
+static void
+dt_decl_bf_start(dt_scope_t *dsp, ctf_id_t base, uint_t unit_bits,
+    uint_t unit_align, const char *idname)
+{
+	ulong_t off = dt_decl_bf_align(dt_decl_bf_size(dsp), unit_align);
+
+	dt_decl_bf_anchor(dsp, base, off, idname);
+
+	dsp->ds_bf_active = 1;
+	dsp->ds_bf_base = base;
+	dsp->ds_bf_unit_off = off;
+	dsp->ds_bf_unit_bits = unit_bits;
+	dsp->ds_bf_unit_align = unit_align;
+	dsp->ds_bf_next = 0;
+}
+
+static void
+dt_decl_member_copy(dt_scope_t *dsp, dtrace_typeinfo_t *dtt,
+    const char *idname)
+{
+	/*
+	 * If the member type is not defined in the same CTF container as the
+	 * one associated with the current scope (i.e. the container for the
+	 * struct or union itself) or its parent, copy the member type into
+	 * this container and reset dtt to refer to the copied type.
+	 */
+	if (dtt->dtt_ctfp != dsp->ds_ctfp &&
+	    dtt->dtt_ctfp != ctf_parent_file(dsp->ds_ctfp)) {
+
+		dtt->dtt_type = ctf_add_type(dsp->ds_ctfp,
+		    dtt->dtt_ctfp, dtt->dtt_type);
+		dtt->dtt_ctfp = dsp->ds_ctfp;
+
+		if (dtt->dtt_type == CTF_ERR ||
+		    ctf_update(dtt->dtt_ctfp) == CTF_ERR) {
+			xyerror(D_UNKNOWN, "failed to copy type of '%s': %s\n",
+			    idname, ctf_errmsg(ctf_errno(dtt->dtt_ctfp)));
+		}
+	}
+}
+
 void
 dt_decl_member(dt_node_t *dnp)
 {
@@ -542,22 +628,28 @@ dt_decl_member(dt_node_t *dnp)
 		xyerror(D_DECL_VOIDOBJ, "cannot have void member: %s\n", ident);
 
 	/*
-	 * If a bit-field qualifier was part of the member declaration, create
-	 * a new integer type of the same name and attributes as the base type
-	 * and size equal to the specified number of bits.  We reset 'dtt' to
-	 * refer to this new bit-field type and continue on to add the member.
+	 * If a bit-field qualifier was part of the member declaration, add a
+	 * hidden storage-unit member to force the containing type to the C ABI
+	 * size, and add the named field as a CTF slice at the corresponding bit
+	 * offset within that unit.
 	 */
 	if (dnp != NULL) {
+		ctf_id_t bftype;
+		uint_t unit_bits, unit_align, width;
+		ulong_t bitoff, memberoff;
+		ssize_t align;
+		int is_union;
+
 		dnp = dt_node_cook(dnp, DT_IDFLG_REF);
 
 		/*
 		 * A bit-field member with no declarator is permitted to have
 		 * size zero and indicates that no more fields are to be packed
-		 * into the current storage unit.  We ignore these directives
-		 * as the underlying ctf code currently does so for all fields.
+		 * into the current storage unit.
 		 */
 		if (ident == NULL && dnp->dn_kind == DT_NODE_INT &&
 		    dnp->dn_value == 0) {
+			dt_decl_bf_reset(dsp);
 			dt_node_free(dnp);
 			goto done;
 		}
@@ -579,43 +671,77 @@ dt_decl_member(dt_node_t *dnp)
 			    "for type: %s\n", idname);
 		}
 
+		dt_decl_member_copy(dsp, &dtt, idname);
+
+		base = ctf_type_resolve(dtt.dtt_ctfp, dtt.dtt_type);
+		unit_bits = cte.cte_bits;
+		width = (uint_t)dnp->dn_value;
+		align = ctf_type_align(dtt.dtt_ctfp, base);
+		if (align < 0)
+			align = size;
+		unit_align = (uint_t)align * CHAR_BIT;
+		if (unit_align == 0)
+			unit_align = CHAR_BIT;
+		is_union = ctf_type_kind(dsp->ds_ctfp, dsp->ds_type) ==
+		    CTF_K_UNION;
+
+		if (is_union) {
+			dt_decl_bf_anchor(dsp, base, 0, idname);
+			bitoff = 0;
+		} else {
+			if (!dsp->ds_bf_active) {
+				dt_decl_bf_start(dsp, base, unit_bits,
+				    unit_align, idname);
+			} else if (unit_bits > dsp->ds_bf_unit_bits &&
+			    dsp->ds_bf_next + width <= unit_bits &&
+			    dsp->ds_bf_unit_off % unit_align == 0) {
+				dt_decl_bf_anchor(dsp, base,
+				    dsp->ds_bf_unit_off, idname);
+				dsp->ds_bf_base = base;
+				dsp->ds_bf_unit_bits = unit_bits;
+				dsp->ds_bf_unit_align = unit_align;
+			} else if (dsp->ds_bf_next + width >
+			    dsp->ds_bf_unit_bits) {
+				dt_decl_bf_start(dsp, base, unit_bits,
+				    unit_align, idname);
+			}
+
+			bitoff = dsp->ds_bf_next;
+			dsp->ds_bf_next += width;
+		}
+
+		if (ident == NULL) {
+			dt_node_free(dnp);
+			goto done;
+		}
+
 		cte.cte_offset = 0;
-		cte.cte_bits = (uint_t)dnp->dn_value;
+		cte.cte_bits = width;
+		memberoff = is_union ? 0 :
+		    dsp->ds_bf_unit_off + (bitoff / CHAR_BIT) * CHAR_BIT;
+		cte.cte_offset = bitoff % CHAR_BIT;
 
-		dtt.dtt_type = ctf_add_integer(dsp->ds_ctfp,
-		    CTF_ADD_NONROOT, ctf_type_name(dtt.dtt_ctfp,
-		    dtt.dtt_type, n, sizeof(n)), &cte);
+		bftype = ctf_add_slice(dsp->ds_ctfp, CTF_ADD_NONROOT,
+		    base, &cte);
 
-		if (dtt.dtt_type == CTF_ERR ||
-		    ctf_update(dsp->ds_ctfp) == CTF_ERR) {
+		if (bftype == CTF_ERR) {
 			xyerror(D_UNKNOWN, "failed to create type for "
 			    "member '%s': %s\n", idname,
 			    ctf_errmsg(ctf_errno(dsp->ds_ctfp)));
 		}
 
-		dtt.dtt_ctfp = dsp->ds_ctfp;
+		if (ctf_add_member_offset(dsp->ds_ctfp, dsp->ds_type,
+		    ident, bftype, memberoff) == CTF_ERR) {
+			xyerror(D_UNKNOWN, "failed to define member '%s': %s\n",
+			    idname, ctf_errmsg(ctf_errno(dsp->ds_ctfp)));
+		}
+
 		dt_node_free(dnp);
+		goto done;
 	}
 
-	/*
-	 * If the member type is not defined in the same CTF container as the
-	 * one associated with the current scope (i.e. the container for the
-	 * struct or union itself) or its parent, copy the member type into
-	 * this container and reset dtt to refer to the copied type.
-	 */
-	if (dtt.dtt_ctfp != dsp->ds_ctfp &&
-	    dtt.dtt_ctfp != ctf_parent_file(dsp->ds_ctfp)) {
-
-		dtt.dtt_type = ctf_add_type(dsp->ds_ctfp,
-		    dtt.dtt_ctfp, dtt.dtt_type);
-		dtt.dtt_ctfp = dsp->ds_ctfp;
-
-		if (dtt.dtt_type == CTF_ERR ||
-		    ctf_update(dtt.dtt_ctfp) == CTF_ERR) {
-			xyerror(D_UNKNOWN, "failed to copy type of '%s': %s\n",
-			    idname, ctf_errmsg(ctf_errno(dtt.dtt_ctfp)));
-		}
-	}
+	dt_decl_bf_reset(dsp);
+	dt_decl_member_copy(dsp, &dtt, idname);
 
 	if (ctf_add_member(dsp->ds_ctfp, dsp->ds_type,
 	    ident, dtt.dtt_type) == CTF_ERR) {
@@ -1040,6 +1166,7 @@ dt_scope_create(dt_scope_t *dsp)
 	dsp->ds_type = CTF_ERR;
 	dsp->ds_class = DT_DC_DEFAULT;
 	dsp->ds_enumval = -1;
+	dt_decl_bf_reset(dsp);
 }
 
 void
@@ -1072,6 +1199,7 @@ dt_scope_push(ctf_file_t *ctfp, ctf_id_t type)
 	dsp->ds_type = type;
 	dsp->ds_class = rsp->ds_class;
 	dsp->ds_enumval = rsp->ds_enumval;
+	dt_decl_bf_reset(dsp);
 
 	dt_scope_create(rsp);
 	rsp->ds_next = dsp;
@@ -1101,6 +1229,12 @@ dt_scope_pop(void)
 	rsp->ds_type = dsp->ds_type;
 	rsp->ds_class = dsp->ds_class;
 	rsp->ds_enumval = dsp->ds_enumval;
+	rsp->ds_bf_active = dsp->ds_bf_active;
+	rsp->ds_bf_base = dsp->ds_bf_base;
+	rsp->ds_bf_unit_off = dsp->ds_bf_unit_off;
+	rsp->ds_bf_unit_bits = dsp->ds_bf_unit_bits;
+	rsp->ds_bf_unit_align = dsp->ds_bf_unit_align;
+	rsp->ds_bf_next = dsp->ds_bf_next;
 
 	free(dsp);
 	return rsp->ds_decl;
diff --git a/libdtrace/dt_decl.h b/libdtrace/dt_decl.h
index 0bec183b..4be31158 100644
--- a/libdtrace/dt_decl.h
+++ b/libdtrace/dt_decl.h
@@ -58,6 +58,12 @@ typedef struct dt_scope {
 	ctf_id_t ds_type;		/* CTF id of enclosing type */
 	dt_dclass_t ds_class;		/* declaration class for this scope */
 	int ds_enumval;			/* most recent enumerator value */
+	int ds_bf_active;		/* bit-field allocation unit is active */
+	ctf_id_t ds_bf_base;		/* bit-field allocation unit base type */
+	ulong_t ds_bf_unit_off;		/* bit offset of bit-field unit */
+	uint_t ds_bf_unit_bits;		/* size in bits of bit-field unit */
+	uint_t ds_bf_unit_align;		/* alignment in bits of bit-field unit */
+	uint_t ds_bf_next;		/* next bit offset in bit-field unit */
 } dt_scope_t;
 
 extern dt_decl_t *dt_decl_alloc(ushort_t, char *);
diff --git a/test/unittest/bitfields/tst.SizeofBitField.d b/test/unittest/bitfields/tst.SizeofBitField.d
index 1eb4ab5d..2ad7df02 100644
--- a/test/unittest/bitfields/tst.SizeofBitField.d
+++ b/test/unittest/bitfields/tst.SizeofBitField.d
@@ -80,9 +80,9 @@ BEGIN
 }
 
 END
-/(1 != sizeof(var1)) || (2 != sizeof(var2)) || (3 != sizeof(var3)) ||
-    (4 != sizeof(var4)) || (5 != sizeof(var5)) || (6 != sizeof(var6))
-    || (7 != sizeof(var7)) || (8 != sizeof(var8)) || (12 != sizeof(var12))/
+/(4 != sizeof(var1)) || (4 != sizeof(var2)) || (4 != sizeof(var3)) ||
+    (4 != sizeof(var4)) || (4 != sizeof(var5)) || (8 != sizeof(var6))
+    || (8 != sizeof(var7)) || (8 != sizeof(var8)) || (12 != sizeof(var12))/
 {
 	exit(1);
 }
diff --git a/test/unittest/bitfields/tst.SizeofBitField.r b/test/unittest/bitfields/tst.SizeofBitField.r
index 9d97b2aa..a5e4007b 100644
--- a/test/unittest/bitfields/tst.SizeofBitField.r
+++ b/test/unittest/bitfields/tst.SizeofBitField.r
@@ -1,10 +1,10 @@
-sizeof(bitRecord1): 1
-sizeof(bitRecord2): 2
-sizeof(bitRecord3): 3
+sizeof(bitRecord1): 4
+sizeof(bitRecord2): 4
+sizeof(bitRecord3): 4
 sizeof(bitRecord4): 4
-sizeof(bitRecord5): 5
-sizeof(bitRecord6): 6
-sizeof(bitRecord7): 7
+sizeof(bitRecord5): 4
+sizeof(bitRecord6): 8
+sizeof(bitRecord7): 8
 sizeof(bitRecord8): 8
 sizeof(bitRecord12): 12
 
-- 
2.43.5


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH 2/6] libdtrace: do not taint scalar alloca member loads
  2026-06-05 22:12 [PATCH 0/6] dtrace: support user-space symbol resolution via uresolve() Alan Maguire
  2026-06-05 22:12 ` [PATCH 1/6] libdtrace: Pack declared bitfields into C storage units Alan Maguire
@ 2026-06-05 22:12 ` Alan Maguire
  2026-06-05 22:12 ` [PATCH 3/6] dtrace: add 'probeoff' variable to record relative offset of probes Alan Maguire
                   ` (3 subsequent siblings)
  5 siblings, 0 replies; 10+ messages in thread
From: Alan Maguire @ 2026-06-05 22:12 UTC (permalink / raw)
  To: dtrace; +Cc: dtrace-devel, Alan Maguire, OpenAI Codex

Member access on an alloca-backed struct propagated DT_NF_ALLOCA from the
base expression unconditionally.  This is correct when the result still
denotes  storage inside the alloca buffer, but wrong for scalar member
loads: after the load, the result is an ordinary scalar value.

This caused values such as copyin(...)->myint to be treated later as
scratch offsets, producing bogus pointer-like values and bad reads.

Only propagate alloca taint for member expressions that remain
by reference.  Preserve address-taking of alloca-backed members,
and update store codegen so writes to scalar memebers inside alloca
storage still bounds-check and convert the alloca offset correctly.

Add a regression test for assigning a scalar member loaded from
alloca-backed storage to a normal scalar variable.

Signed-off-by: Alan Maguire <alan.maguire@oracle.com>
Assisted-by: OpenAI Codex (GPT-5) <codex@openai.com>
---
 libdtrace/dt_cg.c                             |  9 ++---
 libdtrace/dt_parser.c                         | 15 ++++++--
 .../tst.alloca-struct-scalar-load-taint.d     | 35 +++++++++++++++++++
 3 files changed, 52 insertions(+), 7 deletions(-)
 create mode 100644 test/unittest/funcs/alloca/tst.alloca-struct-scalar-load-taint.d

diff --git a/libdtrace/dt_cg.c b/libdtrace/dt_cg.c
index 0859e9cb..e97e9abc 100644
--- a/libdtrace/dt_cg.c
+++ b/libdtrace/dt_cg.c
@@ -3788,13 +3788,15 @@ dt_cg_store(dt_node_t *src, dt_irlist_t *dlp, dt_regset_t *drp, dt_node_t *dst)
 
 	/*
 	 * If we're storing into a writable lvalue that is a dereference of an
-	 * alloca pointer or if we are storing into a writable alloca lvalue,
-	 * we need to do bounds-checking and turn the offset value into a real
-	 * pointer.
+	 * alloca pointer, a member of an alloca pointer, or if we are storing
+	 * into a writable alloca lvalue, we need to do bounds-checking and
+	 * turn the offset value into a real pointer.
 	 */
 	if (dst->dn_flags & DT_NF_WRITABLE && dst->dn_flags & DT_NF_LVALUE &&
 	    ((dst->dn_op == DT_TOK_DEREF &&
 	      dst->dn_child->dn_flags & DT_NF_ALLOCA) ||
+	     ((dst->dn_op == DT_TOK_DOT || dst->dn_op == DT_TOK_PTR) &&
+	      dst->dn_left->dn_flags & DT_NF_ALLOCA) ||
 	     dst->dn_flags & DT_NF_ALLOCA)) {
 		assert(!(dst->dn_flags & DT_NF_BITFIELD));
 
@@ -7496,7 +7498,6 @@ dt_cg_node(dt_node_t *dnp, dt_irlist_t *dlp, dt_regset_t *drp)
 			 * pointer, bounds-check the pointer reference.
 			 */
 			if (dnp->dn_left->dn_flags & DT_NF_ALLOCA) {
-				assert(dnp->dn_flags & DT_NF_ALLOCA);
 				dt_cg_alloca_access_check(dlp, drp, dnp->dn_reg,
 							  DT_ISIMM, size);
 				dt_cg_alloca_ptr(dlp, drp, dnp->dn_reg,
diff --git a/libdtrace/dt_parser.c b/libdtrace/dt_parser.c
index e71c0985..3e1351d2 100644
--- a/libdtrace/dt_parser.c
+++ b/libdtrace/dt_parser.c
@@ -3179,7 +3179,6 @@ dt_cook_op1(dt_node_t *dnp, uint_t idflags)
 		if ((cp->dn_flags & DT_NF_USERLAND) &&
 		    (kind == CTF_K_POINTER || (dnp->dn_flags & DT_NF_REF)))
 			dnp->dn_flags |= DT_NF_USERLAND;
-		break;
 
 		/* Dereferenced pointers cannot be alloca pointers any more.  */
 		dnp->dn_flags &= ~(DT_NF_ALLOCA | DT_NF_NONASSIGN);
@@ -3251,6 +3250,11 @@ dt_cook_op1(dt_node_t *dnp, uint_t idflags)
 			 cp->dn_op == DT_TOK_DEREF &&
 			 (cp->dn_child->dn_flags & DT_NF_ALLOCA))
 			dt_cook_taint_alloca(dnp, NULL, cp->dn_child);
+		else if (cp->dn_kind == DT_NODE_OP2 &&
+			 (cp->dn_op == DT_TOK_DOT ||
+			  cp->dn_op == DT_TOK_PTR) &&
+			 (cp->dn_left->dn_flags & DT_NF_ALLOCA))
+			dt_cook_taint_alloca(dnp, NULL, cp->dn_left);
 		break;
 
 	case DT_TOK_SIZEOF:
@@ -4049,8 +4053,13 @@ asgn_common:
 		if (lp->dn_flags & DT_NF_WRITABLE)
 			dnp->dn_flags |= DT_NF_WRITABLE;
 
-		/* Transfer alloca taint. */
-		if (lp->dn_flags & DT_NF_ALLOCA)
+		/*
+		 * Only propagate alloca taint when the member access still
+		 * denotes storage inside the alloca buffer.  Scalar members
+		 * are loaded from the alloca buffer and yield ordinary values.
+		 */
+		if ((lp->dn_flags & DT_NF_ALLOCA) &&
+		    (dnp->dn_flags & DT_NF_REF))
 			dt_cook_taint_alloca(dnp, NULL, lp);
 
 		if (xflags && (kind == CTF_K_POINTER ||
diff --git a/test/unittest/funcs/alloca/tst.alloca-struct-scalar-load-taint.d b/test/unittest/funcs/alloca/tst.alloca-struct-scalar-load-taint.d
new file mode 100644
index 00000000..8aa071be
--- /dev/null
+++ b/test/unittest/funcs/alloca/tst.alloca-struct-scalar-load-taint.d
@@ -0,0 +1,35 @@
+/*
+ * Oracle Linux DTrace.
+ * Copyright (c) 2026, 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.
+ */
+
+/*
+ * ASSERTION: Loading a scalar struct member from alloca()ed storage does not
+ *	      taint the scalar value as an alloca() pointer.
+ *
+ * SECTION: Actions and Subroutines/alloca()
+ */
+
+#pragma D option quiet
+
+struct foo {
+	int	c;
+} *ptr;
+
+BEGIN
+{
+	ptr = alloca(sizeof(struct foo));
+	ptr->c = 12;
+
+	depth = ptr->c;
+	depth = 13;
+
+	exit(depth == 13 ? 0 : 1);
+}
+
+ERROR
+{
+	exit(1);
+}
-- 
2.43.5


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH 3/6] dtrace: add 'probeoff' variable to record relative offset of probes
  2026-06-05 22:12 [PATCH 0/6] dtrace: support user-space symbol resolution via uresolve() Alan Maguire
  2026-06-05 22:12 ` [PATCH 1/6] libdtrace: Pack declared bitfields into C storage units Alan Maguire
  2026-06-05 22:12 ` [PATCH 2/6] libdtrace: do not taint scalar alloca member loads Alan Maguire
@ 2026-06-05 22:12 ` Alan Maguire
  2026-06-06  1:38   ` [DTrace-devel] " Kris Van Hees
  2026-06-05 22:12 ` [PATCH 4/6] dtrace: Add uresolve(symbol_name[, optional_fallback]) for symbol lookup Alan Maguire
                   ` (2 subsequent siblings)
  5 siblings, 1 reply; 10+ messages in thread
From: Alan Maguire @ 2026-06-05 22:12 UTC (permalink / raw)
  To: dtrace; +Cc: dtrace-devel, Alan Maguire

Record probe offset in machine state and make it accesible
via 'probeoff' variable.  Having this is valuable as it will
allow dynamic computation of runtime variable offsets by giving
us the base address of a library.  For example, we can determine
the base address of the python libpython3 library via:

python$target:::function-entry
{
	base_addr = uregs[R_PC] - probeoff;
...

This will handle ASLR and will work for specific targets and
system-wide probes.

Since uregs[R_PC] will give us the runtime address of the python
probe, subtracting probeoff will give us the relative address used
at attach time.  For example if 0x7ff925cd7f0d is the uregs
program counter value and 0xd7f0d is the probeoff value, we now
know the library base address is 0x7ff925c00000.

Once we have this, dynamic resolution of variables becomes easy
because we just need the symbol table offset for the variable
and we can add the library base address computed as above.

Signed-off-by: Alan Maguire <alan.maguire@oracle.com>
---
 bpf/get_bvar.c               | 7 +++++++
 include/dtrace/dif_defines.h | 1 +
 libdtrace/dt_cg.c            | 5 +++++
 libdtrace/dt_dctx.h          | 2 ++
 libdtrace/dt_open.c          | 2 ++
 libdtrace/dt_prov_uprobe.c   | 8 ++++++++
 6 files changed, 25 insertions(+)

diff --git a/bpf/get_bvar.c b/bpf/get_bvar.c
index 99a6503d..2b53b7d1 100644
--- a/bpf/get_bvar.c
+++ b/bpf/get_bvar.c
@@ -203,6 +203,13 @@ noinline uint64_t dt_bvar_probedesc(const dt_dctx_t *dctx, uint32_t idx)
 	}
 }
 
+noinline uint64_t dt_bvar_probeoff(const dt_dctx_t *dctx)
+{
+	dt_mstate_t	*mst = dctx->mst;
+
+	return mst->proff;
+}
+
 noinline uint64_t dt_bvar_stackdepth(const dt_dctx_t *dctx)
 {
 	uint32_t	bufsiz = (uint32_t) (uint64_t) (&STKSIZ);
diff --git a/include/dtrace/dif_defines.h b/include/dtrace/dif_defines.h
index a18614d2..edb98f06 100644
--- a/include/dtrace/dif_defines.h
+++ b/include/dtrace/dif_defines.h
@@ -163,6 +163,7 @@
 #define DIF_VAR_ERRNO		0x0120
 #define DIF_VAR_CURCPU		0x0121
 #define DIF_VAR_EXECARGS	0x0122
+#define DIF_VAR_PROBEOFF	0x0123
 
 #define DIF_SUBR_RAND			0
 #define DIF_SUBR_MUTEX_OWNED		1
diff --git a/libdtrace/dt_cg.c b/libdtrace/dt_cg.c
index e97e9abc..1bd73e11 100644
--- a/libdtrace/dt_cg.c
+++ b/libdtrace/dt_cg.c
@@ -288,6 +288,11 @@ dt_cg_tramp_prologue_act(dt_pcb_t *pcb, dt_activity_t act)
 	emit(dlp,  BPF_ALU64_IMM(BPF_ADD, BPF_REG_0, roundup(sizeof(dt_mstate_t), 8)));
 	emit(dlp,  BPF_STORE(BPF_DW, BPF_REG_9, DCTX_BUF, BPF_REG_0));
 
+	/*
+	 *	dctx.mst->proff = 0;
+	 */
+	emit(dlp,  BPF_STORE_IMM(BPF_DW, BPF_REG_7, DMST_PROFF, 0));
+
 	/*
 	 *	mem = buf + roundup(dtp->dt_maxreclen, 8);
 	 *				// add %r0, roundup(dtp->dt_maxreclen,
diff --git a/libdtrace/dt_dctx.h b/libdtrace/dt_dctx.h
index 5bf21345..797c8fd2 100644
--- a/libdtrace/dt_dctx.h
+++ b/libdtrace/dt_dctx.h
@@ -30,6 +30,7 @@ typedef struct dt_mstate {
 	uint32_t	tag;		/* Tag (for future use) */
 	uint32_t	scratch_top;	/* Current top of scratch space */
 	int32_t		syscall_errno;	/* syscall errno */
+	uint64_t	proff;		/* Probe offset */
 	uint64_t	specsize;	/* speculation size */
 	uint64_t	scalarizer;	/* used to scalarize pointers */
 	uint64_t	fault;		/* DTrace fault flags */
@@ -44,6 +45,7 @@ typedef struct dt_mstate {
 #define DMST_STID		offsetof(dt_mstate_t, stid)
 #define DMST_TAG		offsetof(dt_mstate_t, tag)
 #define DMST_SCRATCH_TOP	offsetof(dt_mstate_t, scratch_top)
+#define DMST_PROFF		offsetof(dt_mstate_t, proff)
 #define DMST_ERRNO		offsetof(dt_mstate_t, syscall_errno)
 #define DMST_SPECSIZE		offsetof(dt_mstate_t, specsize)
 #define DMST_SCALARIZER		offsetof(dt_mstate_t, scalarizer)
diff --git a/libdtrace/dt_open.c b/libdtrace/dt_open.c
index 30b8758d..ca00788a 100644
--- a/libdtrace/dt_open.c
+++ b/libdtrace/dt_open.c
@@ -240,6 +240,8 @@ static const dt_ident_t _dtrace_globals[] = {
 	DT_ATTR_STABCMN, DT_VERS_1_0, &dt_idops_type, "string" },
 { "probeprov", DT_IDENT_SCALAR, 0, DIF_VAR_PROBEPROV,
 	DT_ATTR_STABCMN, DT_VERS_1_0, &dt_idops_type, "string" },
+{ "probeoff", DT_IDENT_SCALAR, 0, DIF_VAR_PROBEOFF,
+	DT_ATTR_STABCMN, DT_VERS_2_0, &dt_idops_type, "uint64_t" },
 { "progenyof", DT_IDENT_FUNC, 0, DIF_SUBR_PROGENYOF,
 	DT_ATTR_STABCMN, DT_VERS_1_0,
 	&dt_idops_func, "int(pid_t)" },
diff --git a/libdtrace/dt_prov_uprobe.c b/libdtrace/dt_prov_uprobe.c
index b1b852ce..d53b1e43 100644
--- a/libdtrace/dt_prov_uprobe.c
+++ b/libdtrace/dt_prov_uprobe.c
@@ -1392,6 +1392,14 @@ static int trampoline(dt_pcb_t *pcb, uint_t exitlbl)
 	 */
 	dt_cg_tramp_copy_regs(pcb);
 
+	/*
+	 * Record the uprobe instrumentation offset in the probe context.
+	 * This is the same object-relative offset passed to perf_event_open()
+	 * as attr.probe_offset.
+	 */
+	dt_cg_xsetx(dlp, NULL, DT_LBL_NONE, BPF_REG_0, upp->off);
+	emit(dlp, BPF_STORE(BPF_DW, BPF_REG_7, DMST_PROFF, BPF_REG_0));
+
 	/*
 	 * pid probes.
 	 *
-- 
2.43.5


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH 4/6] dtrace: Add uresolve(symbol_name[, optional_fallback]) for symbol lookup
  2026-06-05 22:12 [PATCH 0/6] dtrace: support user-space symbol resolution via uresolve() Alan Maguire
                   ` (2 preceding siblings ...)
  2026-06-05 22:12 ` [PATCH 3/6] dtrace: add 'probeoff' variable to record relative offset of probes Alan Maguire
@ 2026-06-05 22:12 ` Alan Maguire
  2026-06-06  1:08   ` [DTrace-devel] " Kris Van Hees
  2026-06-05 22:12 ` [PATCH 5/6] test,funcs: add uresolve() tests Alan Maguire
  2026-06-05 22:12 ` [PATCH 6/6] docs: document uresolve() in the user guide Alan Maguire
  5 siblings, 1 reply; 10+ messages in thread
From: Alan Maguire @ 2026-06-05 22:12 UTC (permalink / raw)
  To: dtrace; +Cc: dtrace-devel, Alan Maguire, OpenAI Codex

Add uresolve(symbol[, fallback]) to resolve user-space symbols from D
scripts.  uresolve() resolves a symbol to an object-relative offset at
compile time, then computes the process-specific runtime address at probe
fire time as:

    current uprobe IP - probe offset + symbol offset

For pid$target probes, use the target process and its /proc/<pid>/maps state
to find the symbol and translate it to the same object-relative coordinate
used by uprobes.  For system-wide uprobes, resolve the offset from the probed
object file.  In both cases, address generation is shared and remains dynamic;
the presence of a target process does not bake in an absolute address.

Use bpf_get_func_ip() when available so system-wide uprobes use the actual
probed instruction address rather than a saved pt_regs IP that may contain
the resume address.  Retain the saved-IP path as a fallback.

This allows scripts to locate process-local data such as
_PyThreadState_Current across ASLR and across system-wide probes firing in
different processes.  The optional fallback argument is returned when the
symbol cannot be resolved; without it, resolution failure remains a
compile-time error.

Signed-off-by: Alan Maguire <alan.maguire@oracle.com>
Assisted-by: OpenAI Codex (GPT-5) <codex@openai.com>
---
 include/dtrace/dif_defines.h |   3 +-
 libdtrace/dt_bpf.c           |   1 +
 libdtrace/dt_cg.c            | 456 +++++++++++++++++++++++++++++++++++
 libdtrace/dt_open.c          |   2 +
 libdtrace/dt_probe.h         |   1 +
 libdtrace/dt_prov_uprobe.c   |  58 ++++-
 6 files changed, 516 insertions(+), 5 deletions(-)

diff --git a/include/dtrace/dif_defines.h b/include/dtrace/dif_defines.h
index edb98f06..63b8c293 100644
--- a/include/dtrace/dif_defines.h
+++ b/include/dtrace/dif_defines.h
@@ -213,8 +213,9 @@
 #define DIF_SUBR_LINK_NTOP		45
 #define DIF_SUBR_STACK			46
 #define DIF_SUBR_USTACK			47
+#define DIF_SUBR_URESOLVE		48
 
-#define DIF_SUBR_MAX			47
+#define DIF_SUBR_MAX			48
 
 typedef uint32_t	dif_instr_t;
 
diff --git a/libdtrace/dt_bpf.c b/libdtrace/dt_bpf.c
index 146a66e3..aecd71d9 100644
--- a/libdtrace/dt_bpf.c
+++ b/libdtrace/dt_bpf.c
@@ -470,6 +470,7 @@ dt_bpf_init_helpers(dtrace_hdl_t *dtp)
 	BPF_HELPER_MAP(probe_read_user_str, probe_read_str);
 	BPF_HELPER_MAP(probe_read_kernel, probe_read);
 	BPF_HELPER_MAP(probe_read_kernel_str, probe_read_str);
+	BPF_HELPER_MAP(get_func_ip, unspec);
 	BPF_HELPER_MAP(get_current_task_btf, unspec);
 	BPF_HELPER_MAP(task_pt_regs, unspec);
 #undef BPF_HELPER_MAP
diff --git a/libdtrace/dt_cg.c b/libdtrace/dt_cg.c
index 1bd73e11..16c58e35 100644
--- a/libdtrace/dt_cg.c
+++ b/libdtrace/dt_cg.c
@@ -11,6 +11,9 @@
 #include <setjmp.h>
 #include <assert.h>
 #include <errno.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <unistd.h>
 
 #include <sys/socket.h>			/* needed for if_arp.h on OL7/x86 */
 #include <linux/if_arp.h>		/* ARPHRD_ETHER ARPHRD_INFINIBAND */
@@ -6988,6 +6991,458 @@ dt_cg_subr_link_ntop(dt_node_t *dnp, dt_irlist_t *dlp, dt_regset_t *drp)
 	dt_regset_free(drp, type->dn_reg);
 }
 
+static const char *
+dt_cg_basename(const char *path)
+{
+	const char	*p;
+
+	p = strrchr(path, '/');
+	return p != NULL ? p + 1 : path;
+}
+
+static int
+dt_cg_parse_objname(const char *mod, Lmid_t *lmidp, const char **objp)
+{
+	char	*end;
+
+	if (mod == NULL || mod[0] == '\0' || strisglob(mod))
+		return -1;
+
+	if (strncmp(mod, "LM", 2) == 0 && isdigit((unsigned char)mod[2])) {
+		*lmidp = strtoul(mod + 2, &end, 16);
+		if (*end != '`' || end[1] == '\0' || strchr(end + 1, '`'))
+			return -1;
+
+		*objp = end + 1;
+		return 0;
+	}
+
+	*lmidp = PR_LMID_EVERY;
+	*objp = mod;
+	return 0;
+}
+
+static int
+dt_cg_probe_objname(const dt_probe_t *prp, Lmid_t *lmidp, const char **objp)
+{
+	if (prp == NULL || prp->desc == NULL)
+		return -1;
+
+	return dt_cg_parse_objname(prp->desc->mod, lmidp, objp);
+}
+
+static int
+dt_cg_obj_matches_path(const char *obj, const char *path)
+{
+	const char	*base;
+	Lmid_t		lmid;
+
+	if (obj == NULL || path == NULL)
+		return 0;
+
+	if (dt_cg_parse_objname(obj, &lmid, &obj) == -1)
+		return 0;
+
+	base = dt_cg_basename(path);
+	return strcmp(obj, path) == 0 || strcmp(obj, base) == 0;
+}
+
+static int
+dt_cg_elf_sym_to_offset(Elf *elf, const GElf_Sym *sym, uint64_t *offp)
+{
+	GElf_Ehdr	ehdr;
+	size_t		i, phnum;
+	uint64_t	base = 0;
+
+	if (sym->st_shndx == SHN_UNDEF ||
+	    gelf_getehdr(elf, &ehdr) == NULL ||
+	    elf_getphdrnum(elf, &phnum) != 0)
+		return -1;
+
+	/*
+	 * Match the object-relative coordinate used by uprobe offsets.  For
+	 * ET_DYN, libproc computes file_dyn_base from the first PT_LOAD
+	 * segment's page offset.  For ET_EXEC, the load bias is zero and the
+	 * first segment's vaddr remains part of the object-relative offset.
+	 */
+	for (i = 0; i < phnum; i++) {
+		GElf_Phdr	phdr;
+
+		if (gelf_getphdr(elf, i, &phdr) == NULL ||
+		    phdr.p_type != PT_LOAD)
+			continue;
+
+		if (ehdr.e_type == ET_EXEC)
+			base = phdr.p_vaddr;
+		else if (ehdr.e_type == ET_DYN && phdr.p_align != 0)
+			base = phdr.p_vaddr & (phdr.p_align - 1);
+
+		break;
+	}
+
+	if (i == phnum || sym->st_value < base)
+		return -1;
+
+	*offp = sym->st_value - base;
+	return 0;
+}
+
+static int
+dt_cg_elf_lookup_symtab(Elf *elf, int type, const char *name, GElf_Sym *symp)
+{
+	Elf_Scn		*scn = NULL;
+
+	while ((scn = elf_nextscn(elf, scn)) != NULL) {
+		GElf_Shdr	shdr;
+		Elf_Data	*data;
+		size_t		i, nsyms;
+
+		if (gelf_getshdr(scn, &shdr) == NULL ||
+		    shdr.sh_type != type ||
+		    shdr.sh_entsize == 0)
+			continue;
+
+		data = elf_getdata(scn, NULL);
+		if (data == NULL)
+			continue;
+
+		nsyms = shdr.sh_size / shdr.sh_entsize;
+		for (i = 0; i < nsyms; i++) {
+			GElf_Sym	sym;
+			const char	*s;
+			uchar_t		stype;
+
+			if (gelf_getsym(data, i, &sym) == NULL ||
+			    sym.st_name == 0 ||
+			    sym.st_shndx == SHN_UNDEF)
+				continue;
+
+			stype = GELF_ST_TYPE(sym.st_info);
+			if (stype == STT_FILE || stype == STT_SECTION)
+				continue;
+
+			s = elf_strptr(elf, shdr.sh_link, sym.st_name);
+			if (s != NULL && strcmp(s, name) == 0) {
+				*symp = sym;
+				return 0;
+			}
+		}
+	}
+
+	return -1;
+}
+
+static int
+dt_cg_elf_lookup_offset(const char *path, const char *name, uint64_t *offp)
+{
+	GElf_Sym	sym;
+	Elf		*elf = NULL;
+	int		fd, rc = -1;
+
+	fd = open(path, O_RDONLY);
+	if (fd < 0)
+		return -1;
+
+	elf = elf_begin(fd, ELF_C_READ_MMAP, NULL);
+	if (elf == NULL || elf_kind(elf) != ELF_K_ELF)
+		goto out;
+
+	if (dt_cg_elf_lookup_symtab(elf, SHT_SYMTAB, name, &sym) == -1 &&
+	    dt_cg_elf_lookup_symtab(elf, SHT_DYNSYM, name, &sym) == -1)
+		goto out;
+
+	rc = dt_cg_elf_sym_to_offset(elf, &sym, offp);
+
+out:
+	if (elf != NULL)
+		elf_end(elf);
+	close(fd);
+	return rc;
+}
+
+typedef struct dt_cg_uresolve_arg {
+	const char	*name;
+	const char	*obj;
+	int		explicit_obj;
+	int		nmatches;
+	int		no_path;
+	int		no_symbol;
+	int		bad_object;
+	int		different_offset;
+	uint64_t	off;
+} dt_cg_uresolve_arg_t;
+
+static int
+dt_cg_uresolve_probe(dtrace_hdl_t *dtp, dt_probe_t *prp, void *arg)
+{
+	dt_cg_uresolve_arg_t	*uap = arg;
+	const char		*path;
+	uint64_t		off;
+
+	path = dt_probe_uprobe_path(prp);
+	if (path == NULL) {
+		uap->no_path = 1;
+		return 1;
+	}
+
+	if (uap->explicit_obj && !dt_cg_obj_matches_path(uap->obj, path)) {
+		uap->bad_object = 1;
+		return 1;
+	}
+
+	if (dt_cg_elf_lookup_offset(path, uap->name, &off) != 0) {
+		uap->no_symbol = 1;
+		return 1;
+	}
+
+	if (uap->nmatches++ == 0) {
+		uap->off = off;
+		return 0;
+	}
+
+	if (uap->off != off) {
+		uap->different_offset = 1;
+		return 1;
+	}
+
+	return 0;
+}
+
+static int
+dt_cg_uresolve_from_probe_file(dt_node_t *dnp, dt_node_t *arg,
+			       const char *name, const char *obj,
+			       int explicit_obj, int allow_fallback,
+			       uint64_t *offp)
+{
+	dt_cg_uresolve_arg_t	uarg = { 0 };
+	dtrace_hdl_t		*dtp = yypcb->pcb_hdl;
+
+	uarg.name = name;
+	uarg.obj = obj;
+	uarg.explicit_obj = explicit_obj;
+
+	if (yypcb->pcb_pdesc != NULL)
+		dt_probe_iter(dtp, yypcb->pcb_pdesc, dt_cg_uresolve_probe,
+			      NULL, &uarg);
+	else if (yypcb->pcb_probe != NULL)
+		dt_cg_uresolve_probe(dtp, yypcb->pcb_probe, &uarg);
+
+	if (uarg.bad_object)
+		dnerror(arg, D_PROC_NAME, "uresolve( ) object %s does not "
+			"match the current uprobe object\n", obj);
+	if (uarg.no_path)
+		dnerror(dnp, D_PROC_NAME, "uresolve( ) requires a unique "
+			"uprobe object path for this probe\n");
+	if (uarg.no_symbol) {
+		if (allow_fallback)
+			return -1;
+
+		dnerror(arg, D_PROC_FUNC, "failed to resolve user symbol %s "
+			"in the current uprobe object\n", arg->dn_string);
+	}
+
+	if (uarg.nmatches == 0) {
+		dnerror(dnp, D_PROC_NAME, "uresolve( ) cannot determine the "
+			"current uprobe object\n");
+	}
+
+	if (uarg.different_offset)
+		dnerror(arg, D_PROC_FUNC, "uresolve( ) matched multiple "
+			"uprobe objects with different offsets for symbol %s\n",
+			arg->dn_string);
+
+	*offp = uarg.off;
+	return 0;
+}
+
+static int
+dt_cg_uresolve_from_target(dt_node_t *dnp, dt_node_t *arg, pid_t pid,
+			   Lmid_t lmid, const char *obj, const char *name,
+			   int allow_fallback, uint64_t *offp)
+{
+	dtrace_hdl_t	*dtp = yypcb->pcb_hdl;
+	const prmap_t	*mapp, *first;
+	GElf_Sym	sym;
+	pid_t		gpid;
+	int		err = 0;
+
+	gpid = dt_proc_grab_lock(dtp, pid,
+	    DTRACE_PROC_WAITING | DTRACE_PROC_SHORTLIVED);
+	if (gpid <= 0)
+		dnerror(dnp, D_PROC_GRAB, "failed to grab target process %d "
+			"for uresolve( )\n", pid);
+
+	if (dt_Pxlookup_by_name(dtp, gpid, lmid, obj, name,
+	    &sym, NULL) != 0) {
+		err = 1;
+	} else if ((mapp = dt_Paddr_to_map(dtp, gpid, sym.st_value)) == NULL ||
+	    mapp->pr_file == NULL ||
+	    (first = mapp->pr_file->first_segment) == NULL ||
+	    sym.st_value < first->pr_vaddr) {
+		err = 1;
+	} else {
+		*offp = sym.st_value - first->pr_vaddr;
+	}
+
+	dt_proc_release_unlock(dtp, gpid);
+
+	if (err == 0)
+		return 0;
+
+	if (allow_fallback)
+		return -1;
+
+	dnerror(arg, D_PROC_FUNC, "failed to resolve target user symbol %s\n",
+		arg->dn_string);
+}
+
+static void
+dt_cg_uresolve_emit_offset_addr(dt_node_t *dnp, dt_irlist_t *dlp,
+				dt_regset_t *drp, uint64_t symoff)
+{
+	dtrace_hdl_t	*dtp = yypcb->pcb_hdl;
+	int	treg;
+
+	/*
+	 * Compute the current object base from the saved user PC and the
+	 * uprobe offset, then add the compile-time-resolved symbol offset.
+	 * When available, use the kernel's current uprobe IP directly because
+	 * the saved register block may not reliably carry the breakpoint
+	 * address for system-wide uprobes across different processes.
+	 */
+	if (dtp->dt_bpfhelper[BPF_FUNC_get_func_ip] != BPF_FUNC_unspec) {
+		if (dt_regset_xalloc_args(drp) == -1)
+			longjmp(yypcb->pcb_jmpbuf, EDT_NOREG);
+		if (dt_regset_xalloc(drp, BPF_REG_0) == -1)
+			longjmp(yypcb->pcb_jmpbuf, EDT_NOREG);
+
+		dt_cg_access_dctx(BPF_REG_1, dlp, drp, DCTX_CTX);
+		emit(dlp, BPF_CALL_HELPER(dtp->dt_bpfhelper[BPF_FUNC_get_func_ip]));
+		dt_regset_free_args(drp);
+
+		if ((dnp->dn_reg = dt_regset_alloc(drp)) == -1)
+			longjmp(yypcb->pcb_jmpbuf, EDT_NOREG);
+
+		emit(dlp, BPF_MOV_REG(dnp->dn_reg, BPF_REG_0));
+		dt_regset_free(drp, BPF_REG_0);
+	} else {
+		if ((dnp->dn_reg = dt_regset_alloc(drp)) == -1)
+			longjmp(yypcb->pcb_jmpbuf, EDT_NOREG);
+
+		if ((treg = dt_regset_alloc(drp)) == -1)
+			longjmp(yypcb->pcb_jmpbuf, EDT_NOREG);
+
+		dt_cg_access_dctx(treg, dlp, drp, DCTX_MST);
+		emit(dlp, BPF_LOAD(BPF_DW, dnp->dn_reg, treg,
+				   DMST_REGS + PT_REGS_IP));
+		dt_regset_free(drp, treg);
+	}
+
+	if ((treg = dt_regset_alloc(drp)) == -1)
+		longjmp(yypcb->pcb_jmpbuf, EDT_NOREG);
+
+	dt_cg_access_dctx(treg, dlp, drp, DCTX_MST);
+	emit(dlp, BPF_LOAD(BPF_DW, treg, treg, DMST_PROFF));
+	emit(dlp, BPF_ALU64_REG(BPF_SUB, dnp->dn_reg, treg));
+	dt_cg_xsetx(dlp, NULL, DT_LBL_NONE, treg, symoff);
+	emit(dlp, BPF_ALU64_REG(BPF_ADD, dnp->dn_reg, treg));
+
+	dt_regset_free(drp, treg);
+}
+
+static void
+dt_cg_subr_uresolve(dt_node_t *dnp, dt_irlist_t *dlp, dt_regset_t *drp)
+{
+	dtrace_hdl_t	*dtp = yypcb->pcb_hdl;
+	dt_node_t	*arg = dnp->dn_args;
+	dt_node_t	*fallback;
+	dt_ident_t	*tidp;
+	pid_t		pid = 0;
+	char		*spec, *tick;
+	const char	*obj = NULL;
+	const char	*name;
+	Lmid_t		lmid = PR_LMID_EVERY;
+	uint64_t	off = 0;
+	int		err = 0, explicit_obj = 0;
+	int		use_fallback = 0;
+
+	if (arg == NULL || (arg->dn_list != NULL &&
+	    arg->dn_list->dn_list != NULL))
+		dnerror(dnp, D_PROTO_LEN, "uresolve( ) prototype mismatch: "
+			"expected 1 or 2 arguments\n");
+
+	if (arg->dn_kind != DT_NODE_STRING)
+		dnerror(arg, D_PROTO_ARG, "uresolve( ) argument #1 must be "
+			"a string constant\n");
+
+	fallback = arg->dn_list;
+	tidp = dt_idhash_lookup(dtp->dt_macros, "target");
+	if (tidp != NULL)
+		pid = tidp->di_id;
+
+	spec = strdup(arg->dn_string);
+	if (spec == NULL)
+		longjmp(yypcb->pcb_jmpbuf, EDT_NOMEM);
+
+	tick = strrchr(spec, '`');
+	if (tick != NULL) {
+		*tick = '\0';
+		obj = spec;
+		name = tick + 1;
+		explicit_obj = 1;
+
+		if (*obj == '\0' || *name == '\0')
+			err = 1;
+	} else {
+		name = spec;
+	}
+
+	if (err != 0) {
+		free(spec);
+		dnerror(arg, D_PROC_NAME, "uresolve( ) argument must be "
+			"\"symbol\" or \"object`symbol\"\n");
+	}
+
+	if (pid == 0) {
+		if (dt_cg_uresolve_from_probe_file(dnp, arg, name, obj,
+		    explicit_obj, fallback != NULL, &off) != 0)
+			use_fallback = 1;
+		goto done;
+	}
+
+	if (explicit_obj) {
+		if (dt_cg_parse_objname(obj, &lmid, &obj) != 0) {
+			free(spec);
+			dnerror(arg, D_PROC_NAME, "uresolve( ) object %s is "
+				"not a valid user object name\n", arg->dn_string);
+		}
+	} else if (dt_cg_probe_objname(yypcb->pcb_probe, &lmid, &obj) != 0) {
+		const char	*path = dt_probe_uprobe_path(yypcb->pcb_probe);
+
+		if (path != NULL) {
+			obj = path;
+			lmid = PR_LMID_EVERY;
+		} else {
+			free(spec);
+			dnerror(arg, D_PROC_NAME, "unqualified uresolve( ) "
+				"requires a concrete probe module\n");
+		}
+	}
+
+	if (dt_cg_uresolve_from_target(dnp, arg, pid, lmid, obj, name,
+	    fallback != NULL, &off) != 0)
+		use_fallback = 1;
+
+done:
+	free(spec);
+
+	if (use_fallback) {
+		dt_cg_node(fallback, dlp, drp);
+		dnp->dn_reg = fallback->dn_reg;
+	} else
+		dt_cg_uresolve_emit_offset_addr(dnp, dlp, drp, off);
+}
+
 typedef void dt_cg_subr_f(dt_node_t *, dt_irlist_t *, dt_regset_t *);
 
 static dt_cg_subr_f *_dt_cg_subr[DIF_SUBR_MAX + 1] = {
@@ -7039,6 +7494,7 @@ static dt_cg_subr_f *_dt_cg_subr[DIF_SUBR_MAX + 1] = {
 	[DIF_SUBR_LINK_NTOP]		= &dt_cg_subr_link_ntop,
 	[DIF_SUBR_STACK]		= &dt_cg_subr_stack,
 	[DIF_SUBR_USTACK]		= &dt_cg_subr_ustack,
+	[DIF_SUBR_URESOLVE]		= &dt_cg_subr_uresolve,
 };
 
 static void
diff --git a/libdtrace/dt_open.c b/libdtrace/dt_open.c
index ca00788a..0505e2ff 100644
--- a/libdtrace/dt_open.c
+++ b/libdtrace/dt_open.c
@@ -330,6 +330,8 @@ static const dt_ident_t _dtrace_globals[] = {
 	DT_VERS_1_2, &dt_idops_func, "_usymaddr(uintptr_t)" },
 { "uregs", DT_IDENT_ARRAY, 0, DIF_VAR_UREGS, DT_ATTR_STABCMN, DT_VERS_1_0,
 	&dt_idops_regs, NULL },
+{ "uresolve", DT_IDENT_FUNC, 0, DIF_SUBR_URESOLVE, DT_ATTR_STABCMN,
+	DT_VERS_2_0, &dt_idops_func, "uintptr_t(string, [uintptr_t])" },
 { "ustack", DT_IDENT_FUNC, DT_IDFLG_DPTR, DIF_SUBR_USTACK, DT_ATTR_STABCMN,
 	DT_VERS_1_0, &dt_idops_func, "dt_stack_t([uint32_t], [uint32_t])" },
 { "ustackdepth", DT_IDENT_SCALAR, 0, DIF_VAR_USTACKDEPTH,
diff --git a/libdtrace/dt_probe.h b/libdtrace/dt_probe.h
index 54053cd3..bff47055 100644
--- a/libdtrace/dt_probe.h
+++ b/libdtrace/dt_probe.h
@@ -102,6 +102,7 @@ typedef int dt_dependent_f(dtrace_hdl_t *dtp, dt_probe_t *prp, void *arg);
 extern int dt_probe_dependent_iter(dtrace_hdl_t *dtp, const dt_probe_t *prp,
 				   dt_dependent_f *func, void *arg);
 
+extern const char *dt_probe_uprobe_path(const dt_probe_t *prp);
 
 extern void dt_probe_init(dtrace_hdl_t *dtp);
 extern void dt_probe_detach_all(dtrace_hdl_t *dtp);
diff --git a/libdtrace/dt_prov_uprobe.c b/libdtrace/dt_prov_uprobe.c
index d53b1e43..9a481f59 100644
--- a/libdtrace/dt_prov_uprobe.c
+++ b/libdtrace/dt_prov_uprobe.c
@@ -307,6 +307,47 @@ typedef struct uprobe_data {
 	int	ref_shift;
 } uprobe_data_t;
 
+dt_provimpl_t	dt_pid;
+dt_provimpl_t	dt_usdt;
+dt_provimpl_t	dt_stapsdt;
+
+const char *
+dt_probe_uprobe_path(const dt_probe_t *prp)
+{
+	const dt_uprobe_t	*upp;
+	const list_probe_t	*pup;
+	const char		*path = NULL;
+
+	if (prp == NULL || prp->prov == NULL)
+		return NULL;
+
+	if (prp->prov->impl == &dt_uprobe) {
+		upp = prp->prv_data;
+		return upp != NULL ? upp->fn : NULL;
+	}
+
+	if (prp->prov->impl != &dt_pid &&
+	    prp->prov->impl != &dt_usdt &&
+	    prp->prov->impl != &dt_stapsdt)
+		return NULL;
+
+	for (pup = prp->prv_data; pup != NULL; pup = dt_list_next(pup)) {
+		if (pup->probe == NULL || pup->probe->prv_data == NULL)
+			return NULL;
+
+		upp = pup->probe->prv_data;
+		if (upp->fn == NULL)
+			return NULL;
+
+		if (path == NULL)
+			path = upp->fn;
+		else if (strcmp(path, upp->fn) != 0)
+			return NULL;
+	}
+
+	return path;
+}
+
 static const dtrace_pattr_t	pattr = {
 { DTRACE_STABILITY_EVOLVING, DTRACE_STABILITY_EVOLVING, DTRACE_CLASS_ISA },
 { DTRACE_STABILITY_PRIVATE, DTRACE_STABILITY_PRIVATE, DTRACE_CLASS_UNKNOWN },
@@ -315,10 +356,6 @@ static const dtrace_pattr_t	pattr = {
 { DTRACE_STABILITY_PRIVATE, DTRACE_STABILITY_PRIVATE, DTRACE_CLASS_UNKNOWN },
 };
 
-dt_provimpl_t	dt_pid;
-dt_provimpl_t	dt_usdt;
-dt_provimpl_t	dt_stapsdt;
-
 #define UPROBE_CONFIG	"/sys/bus/event_source/devices/uprobe/"
 #define PERF_TYPE_FILE	(UPROBE_CONFIG "type")
 #define RET_FLAG_FILE	(UPROBE_CONFIG "format/retprobe")
@@ -1392,6 +1429,19 @@ static int trampoline(dt_pcb_t *pcb, uint_t exitlbl)
 	 */
 	dt_cg_tramp_copy_regs(pcb);
 
+	/*
+	 * On some architectures the saved user IP in the uprobe pt_regs is the
+	 * resume address rather than the probed instruction address.  Normalize
+	 * the saved IP to the actual uprobe address when the kernel can provide
+	 * it so consumers such as uresolve() can derive the correct object base.
+	 */
+	if (dtp->dt_bpfhelper[BPF_FUNC_get_func_ip] != BPF_FUNC_unspec) {
+		emit(dlp, BPF_MOV_REG(BPF_REG_1, BPF_REG_8));
+		emit(dlp, BPF_CALL_HELPER(dtp->dt_bpfhelper[BPF_FUNC_get_func_ip]));
+		emit(dlp, BPF_STORE(BPF_DW, BPF_REG_7,
+				    DMST_REGS + PT_REGS_IP, BPF_REG_0));
+	}
+
 	/*
 	 * Record the uprobe instrumentation offset in the probe context.
 	 * This is the same object-relative offset passed to perf_event_open()
-- 
2.43.5


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH 5/6] test,funcs: add uresolve() tests
  2026-06-05 22:12 [PATCH 0/6] dtrace: support user-space symbol resolution via uresolve() Alan Maguire
                   ` (3 preceding siblings ...)
  2026-06-05 22:12 ` [PATCH 4/6] dtrace: Add uresolve(symbol_name[, optional_fallback]) for symbol lookup Alan Maguire
@ 2026-06-05 22:12 ` Alan Maguire
  2026-06-05 22:12 ` [PATCH 6/6] docs: document uresolve() in the user guide Alan Maguire
  5 siblings, 0 replies; 10+ messages in thread
From: Alan Maguire @ 2026-06-05 22:12 UTC (permalink / raw)
  To: dtrace; +Cc: dtrace-devel, Alan Maguire, OpenAI Codex

Add test for uresolve() symbol lookup failure with/without fallback.
Also add positive test which does per-target and systemwide context
uresolve() lookups, ensuring expected value is found.

Signed-off-by: Alan Maguire <alan.maguire@oracle.com>
Assisted-by: OpenAI Codex (GPT-5) <codex@openai.com>
---
 test/unittest/funcs/uresolve/err.nofallback.d |  21 +++
 test/unittest/funcs/uresolve/err.nofallback.r |   2 +
 test/unittest/funcs/uresolve/tst.fallback.d   |  25 +++
 test/unittest/funcs/uresolve/tst.fallback.r   |   2 +
 test/unittest/funcs/uresolve/tst.positive.sh  | 144 ++++++++++++++++++
 5 files changed, 194 insertions(+)
 create mode 100644 test/unittest/funcs/uresolve/err.nofallback.d
 create mode 100644 test/unittest/funcs/uresolve/err.nofallback.r
 create mode 100644 test/unittest/funcs/uresolve/tst.fallback.d
 create mode 100644 test/unittest/funcs/uresolve/tst.fallback.r
 create mode 100755 test/unittest/funcs/uresolve/tst.positive.sh

diff --git a/test/unittest/funcs/uresolve/err.nofallback.d b/test/unittest/funcs/uresolve/err.nofallback.d
new file mode 100644
index 00000000..5d6a1e87
--- /dev/null
+++ b/test/unittest/funcs/uresolve/err.nofallback.d
@@ -0,0 +1,21 @@
+/*
+ * Oracle Linux DTrace.
+ * Copyright (c) 2026, 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.
+ */
+
+/* @@trigger: longsleep */
+
+/*
+ * ASSERTION: uresolve() withot fallback for unknown symbol fails.
+ *
+ * SECTION: Functions and Variables/uresolve()
+ */
+
+#pragma D option quiet
+
+BEGIN
+{
+	x = uresolve("a.out`__dtrace_no_such_symbol__");
+}
diff --git a/test/unittest/funcs/uresolve/err.nofallback.r b/test/unittest/funcs/uresolve/err.nofallback.r
new file mode 100644
index 00000000..b93f9f78
--- /dev/null
+++ b/test/unittest/funcs/uresolve/err.nofallback.r
@@ -0,0 +1,2 @@
+-- @@stderr --
+dtrace: failed to compile script test/unittest/funcs/uresolve/err.nofallback.d: line 20: failed to resolve target user symbol a.out`__dtrace_no_such_symbol__
diff --git a/test/unittest/funcs/uresolve/tst.fallback.d b/test/unittest/funcs/uresolve/tst.fallback.d
new file mode 100644
index 00000000..a2d5e203
--- /dev/null
+++ b/test/unittest/funcs/uresolve/tst.fallback.d
@@ -0,0 +1,25 @@
+/*
+ * Oracle Linux DTrace.
+ * Copyright (c) 2026, 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.
+ */
+
+/* @@trigger: longsleep */
+
+/*
+ * ASSERTION: uresolve() returns its optional fallback value when a target
+ * user symbol cannot be resolved.
+ *
+ * SECTION: Functions and Variables/uresolve()
+ */
+
+#pragma D option quiet
+
+BEGIN
+{
+	x = uresolve("a.out`__dtrace_no_such_symbol__", 0);
+	y = uresolve("a.out`__dtrace_no_such_symbol2__", 0xdeadbeef);
+	printf("%lu %lx\n", (unsigned long)x, (unsigned long)y);
+	exit(0);
+}
diff --git a/test/unittest/funcs/uresolve/tst.fallback.r b/test/unittest/funcs/uresolve/tst.fallback.r
new file mode 100644
index 00000000..a224d874
--- /dev/null
+++ b/test/unittest/funcs/uresolve/tst.fallback.r
@@ -0,0 +1,2 @@
+0 deadbeef
+
diff --git a/test/unittest/funcs/uresolve/tst.positive.sh b/test/unittest/funcs/uresolve/tst.positive.sh
new file mode 100755
index 00000000..a552695b
--- /dev/null
+++ b/test/unittest/funcs/uresolve/tst.positive.sh
@@ -0,0 +1,144 @@
+#!/bin/bash
+#
+# Oracle Linux DTrace.
+# Copyright (c) 2026, 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.
+
+# @@timeout: 80
+
+if [ $# != 1 ]; then
+	echo expected one argument: '<'dtrace-path'>'
+	exit 2
+fi
+
+dtrace=$1
+DIRNAME="$tmpdir/uresolve.$$.$RANDOM"
+holdpid=
+firepid=
+
+cleanup()
+{
+	if [ -n "$holdpid" ]; then
+		kill "$holdpid" >/dev/null 2>&1
+		wait "$holdpid" >/dev/null 2>&1
+		holdpid=
+	fi
+	if [ -n "$firepid" ]; then
+		kill "$firepid" >/dev/null 2>&1
+		wait "$firepid" >/dev/null 2>&1
+		firepid=
+	fi
+}
+
+trap cleanup EXIT
+
+fail()
+{
+	echo "ERROR: $1"
+	if [ -f D.out ]; then
+		echo "==== DTrace output"
+		cat D.out
+	fi
+	cleanup
+	exit 1
+}
+
+mkdir -p "$DIRNAME" || exit 1
+cd "$DIRNAME" || exit 1
+
+cat > prov.d <<EOF
+provider uresolve_tst {
+	probe go();
+};
+EOF
+
+cat > main.c <<EOF
+#include <signal.h>
+#include <string.h>
+#include <unistd.h>
+#include "prov.h"
+
+volatile int uresolve_tst_value = 0x12345678;
+
+__attribute__((noinline)) void
+uresolve_tst_fire(void)
+{
+	URESOLVE_TST_GO();
+}
+
+int
+main(int argc, char **argv)
+{
+	int i;
+
+	if (argc > 1 && strcmp(argv[1], "hold") == 0) {
+		for (;;)
+			pause();
+	}
+
+	if (argc > 1 && strcmp(argv[1], "wait") == 0) {
+		for (i = 0; i < 400 && !URESOLVE_TST_GO_ENABLED(); i++)
+			usleep(50000);
+		if (!URESOLVE_TST_GO_ENABLED())
+			return 2;
+		sleep(2);
+	}
+
+	uresolve_tst_fire();
+	return 0;
+}
+EOF
+
+$dtrace $dt_flags -h -s prov.d || fail "failed to generate USDT header"
+$CC $test_cppflags -O0 -fPIE -fno-inline -c main.c ||
+	fail "failed to compile test object"
+$dtrace $dt_flags -G -s prov.d main.o || fail "failed to generate USDT DOF"
+$CC $test_ldflags -fPIE -pie -Wl,--export-dynamic -o main main.o prov.o ||
+	fail "failed to link test program"
+
+$dtrace $dt_flags -q -c ./main -n '
+pid$target:a.out:uresolve_tst_fire:entry
+{
+	this->val = (int *)copyin(uresolve("a.out`uresolve_tst_value"),
+	    sizeof(int));
+	exit(*this->val == 0x12345678 ? 0 : 1);
+}' > D.out 2>&1 || fail "pid target uresolve failed"
+
+$dtrace $dt_flags -q -c "./main wait" -n '
+uresolve_tst$target:::go
+{
+	this->val = (int *)copyin(uresolve("uresolve_tst_value"),
+	    sizeof(int));
+	exit(*this->val == 0x12345678 ? 0 : 1);
+}' > D.out 2>&1 || fail "target USDT uresolve failed"
+
+./main hold &
+holdpid=$!
+sleep 1
+
+$dtrace $dt_flags -Zq -p "$holdpid" -n '
+uresolve_tst*:::go
+/pid != $target/
+{
+	this->val = (int *)copyin(uresolve("a.out`uresolve_tst_value"),
+	    sizeof(int));
+	exit(*this->val == 0x12345678 ? 0 : 1);
+}
+
+tick-1s
+/i++ > 30/
+{
+	exit(1);
+}' > D.out 2>&1 &
+dtpid=$!
+
+sleep 1
+./main wait &
+firepid=$!
+wait "$dtpid" || fail "system-wide USDT uresolve with target failed"
+wait "$firepid" >/dev/null 2>&1
+firepid=
+
+cleanup
+exit 0
-- 
2.43.5


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH 6/6] docs: document uresolve() in the user guide
  2026-06-05 22:12 [PATCH 0/6] dtrace: support user-space symbol resolution via uresolve() Alan Maguire
                   ` (4 preceding siblings ...)
  2026-06-05 22:12 ` [PATCH 5/6] test,funcs: add uresolve() tests Alan Maguire
@ 2026-06-05 22:12 ` Alan Maguire
  5 siblings, 0 replies; 10+ messages in thread
From: Alan Maguire @ 2026-06-05 22:12 UTC (permalink / raw)
  To: dtrace; +Cc: dtrace-devel, Alan Maguire, OpenAI Codex

Signed-off-by: Alan Maguire <alan.maguire@oracle.com>
Assisted-by: OpenAI Codex (GPT-5) <codex@openai.com>
---
 doc/userguide/index.md                       |  1 +
 doc/userguide/reference/dtrace_functions.md  |  4 ++
 doc/userguide/reference/function_uresolve.md | 61 ++++++++++++++++++++
 3 files changed, 66 insertions(+)
 create mode 100644 doc/userguide/reference/function_uresolve.md

diff --git a/doc/userguide/index.md b/doc/userguide/index.md
index 3bf731c6..348ecc8c 100644
--- a/doc/userguide/index.md
+++ b/doc/userguide/index.md
@@ -181,6 +181,7 @@
     -   [tracemem](reference/function_tracemem.md)
     -   [trunc](reference/function_trunc.md)
     -   [uaddr](reference/function_uaddr.md)
+    -   [uresolve](reference/function_uresolve.md)
     -   [ufunc](reference/function_ufunc.md)
     -   [umod](reference/function_umod.md)
     -   [ustack](reference/function_ustack.md)
diff --git a/doc/userguide/reference/dtrace_functions.md b/doc/userguide/reference/dtrace_functions.md
index 7bf37794..021e599d 100644
--- a/doc/userguide/reference/dtrace_functions.md
+++ b/doc/userguide/reference/dtrace_functions.md
@@ -191,6 +191,8 @@ Functions can be grouped according to their general use case and might appear in
 
     -   [`uaddr`](function_uaddr.md): Prints the symbol for a specified address.
 
+    -   [`uresolve`](function_uresolve.md): Returns the process-specific address of a user-space symbol.
+
     -   [`ufunc`](function_ufunc.md): Prints the symbol for a specified user space address. An alias for `usym`.
 
     -   [`umod`](function_umod.md): Prints the module name that corresponds to a specified user space address.
@@ -398,6 +400,8 @@ Returns a string translation of a hardware address.
  Truncates keys from an aggregation.
 -   **[uaddr](../reference/function_uaddr.md)**  
  Prints the symbol for a specified address.
+-   **[uresolve](../reference/function_uresolve.md)**
+ Returns the process-specific address of a user-space symbol.
 -   **[ufunc](../reference/function_ufunc.md)**  
  Prints the symbol for a specified user space address. An alias for `usym`.
 -   **[umod](../reference/function_umod.md)**  
diff --git a/doc/userguide/reference/function_uresolve.md b/doc/userguide/reference/function_uresolve.md
new file mode 100644
index 00000000..3241ecfa
--- /dev/null
+++ b/doc/userguide/reference/function_uresolve.md
@@ -0,0 +1,61 @@
+
+# uresolve
+
+Returns the process-specific address of a user-space symbol.
+
+```
+uintptr_t uresolve(string symbol[, uintptr_t fallback])
+```
+
+The `uresolve` function resolves a symbol in the user-space object that's associated with the current user-space probe and returns the address of that symbol in the process that fired the probe. The result is a user-space address and is typically passed to functions such as [`copyin`](function_copyin.md) or [`copyinstr`](function_copyinstr.md).
+
+The *symbol* argument must be a string constant. It can use either of these forms:
+
+-   `symbol`: Resolve *symbol* in the object associated with the current probe.
+
+-   ``object`symbol``: Resolve *symbol* in the named object. The object name can be a full path or a module name such as `a.out` or `libc.so.6`.
+
+When a target process is specified with `-c` or `-p`, DTrace can use that process and its `/proc` mappings to find the symbol's object-relative offset. For system-wide user-space probes, DTrace can use the probed object file instead. In both cases, the address returned at probe firing is computed for the process that fired the current probe, so the result remains valid across ASLR and across different processes that map the same object at different addresses.
+
+If *fallback* is supplied and the symbol lookup fails, `uresolve` returns the fallback value. If *fallback* isn't supplied, failing to resolve the symbol is a compilation error.
+
+`uresolve` is intended for user-space probe contexts, such as `pid`, USDT, and `stapsdt` probes, where DTrace can identify the current probed user object. It doesn't read memory by itself and doesn't verify that the returned address is readable. Use `copyin` or `copyinstr` to read data from the returned address.
+
+## How to use uresolve to read a user-space variable
+
+This example resolves the global variable `my_counter` in the target program, reads its value, and prints it when `update_counter` is entered:
+
+```
+pid$target:a.out:update_counter:entry
+{
+        this->addr = uresolve("a.out`my_counter");
+        this->value = (int *)copyin(this->addr, sizeof(int));
+        printf("my_counter = %d\n", *this->value);
+}
+```
+
+## How to use uresolve with system-wide user-space probes
+
+This example resolves `_PyThreadState_Current` in each process that fires the Python function-entry probe. The address is computed for the process that fired the probe:
+
+```
+python*:libpython3.6m.so::function-entry
+{
+        this->addr = uresolve("_PyThreadState_Current", 0);
+        this->statep = (uintptr_t *)copyin(this->addr, sizeof(uintptr_t));
+        printf("%d %p\n", pid, *this->statep);
+}
+```
+
+The fallback value of `0` lets the script compile and run on systems or Python builds where the symbol isn't present. If the fallback is returned, the subsequent `copyin` would attempt to read from address 0, so real scripts should guard that case before reading:
+
+```
+python*:libpython3.6m.so::function-entry
+/(this->addr = uresolve("_PyThreadState_Current", 0)) != 0/
+{
+        this->statep = (uintptr_t *)copyin(this->addr, sizeof(uintptr_t));
+        printf("%d %p\n", pid, *this->statep);
+}
+```
+
+**Parent topic:** [DTrace Function Reference](../reference/dtrace_functions.md)
-- 
2.43.5


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* Re: [DTrace-devel] [PATCH 4/6] dtrace: Add uresolve(symbol_name[, optional_fallback]) for symbol lookup
  2026-06-05 22:12 ` [PATCH 4/6] dtrace: Add uresolve(symbol_name[, optional_fallback]) for symbol lookup Alan Maguire
@ 2026-06-06  1:08   ` Kris Van Hees
  0 siblings, 0 replies; 10+ messages in thread
From: Kris Van Hees @ 2026-06-06  1:08 UTC (permalink / raw)
  To: Alan Maguire; +Cc: dtrace, OpenAI Codex, dtrace-devel

A few initial comments:

- How is this different from what ``symbol or object``symbol would provide?
  (Yes, as far as I know it never got fully implemented but I think this might
   be similar to what you are doing, and perhaps it would be better to use the
   already documented syntax, and actually implement it more fully.)

- I think it is problematic that the cg code is making calls to a function in
  a particular provider.  THe architecture has been quite careful at avoiding
  breaking through that boundary.  I'm still digging a bit more through the
  code to determine exactly why that call is truly needed - it should not be
  necessary as far as I can see.

Resolution of a userspace symbol should essentially be a simple matter of:

	object-base-addr + symbol-offset

where the object is a particular mapping within the address space of the
current user process.

On Fri, Jun 05, 2026 at 11:12:15PM +0100, Alan Maguire via DTrace-devel wrote:
> Add uresolve(symbol[, fallback]) to resolve user-space symbols from D
> scripts.  uresolve() resolves a symbol to an object-relative offset at
> compile time, then computes the process-specific runtime address at probe
> fire time as:
> 
>     current uprobe IP - probe offset + symbol offset
> 
> For pid$target probes, use the target process and its /proc/<pid>/maps state
> to find the symbol and translate it to the same object-relative coordinate
> used by uprobes.  For system-wide uprobes, resolve the offset from the probed
> object file.  In both cases, address generation is shared and remains dynamic;
> the presence of a target process does not bake in an absolute address.
> 
> Use bpf_get_func_ip() when available so system-wide uprobes use the actual
> probed instruction address rather than a saved pt_regs IP that may contain
> the resume address.  Retain the saved-IP path as a fallback.
> 
> This allows scripts to locate process-local data such as
> _PyThreadState_Current across ASLR and across system-wide probes firing in
> different processes.  The optional fallback argument is returned when the
> symbol cannot be resolved; without it, resolution failure remains a
> compile-time error.
> 
> Signed-off-by: Alan Maguire <alan.maguire@oracle.com>
> Assisted-by: OpenAI Codex (GPT-5) <codex@openai.com>
> ---
>  include/dtrace/dif_defines.h |   3 +-
>  libdtrace/dt_bpf.c           |   1 +
>  libdtrace/dt_cg.c            | 456 +++++++++++++++++++++++++++++++++++
>  libdtrace/dt_open.c          |   2 +
>  libdtrace/dt_probe.h         |   1 +
>  libdtrace/dt_prov_uprobe.c   |  58 ++++-
>  6 files changed, 516 insertions(+), 5 deletions(-)
> 
> diff --git a/include/dtrace/dif_defines.h b/include/dtrace/dif_defines.h
> index edb98f06..63b8c293 100644
> --- a/include/dtrace/dif_defines.h
> +++ b/include/dtrace/dif_defines.h
> @@ -213,8 +213,9 @@
>  #define DIF_SUBR_LINK_NTOP		45
>  #define DIF_SUBR_STACK			46
>  #define DIF_SUBR_USTACK			47
> +#define DIF_SUBR_URESOLVE		48
>  
> -#define DIF_SUBR_MAX			47
> +#define DIF_SUBR_MAX			48
>  
>  typedef uint32_t	dif_instr_t;
>  
> diff --git a/libdtrace/dt_bpf.c b/libdtrace/dt_bpf.c
> index 146a66e3..aecd71d9 100644
> --- a/libdtrace/dt_bpf.c
> +++ b/libdtrace/dt_bpf.c
> @@ -470,6 +470,7 @@ dt_bpf_init_helpers(dtrace_hdl_t *dtp)
>  	BPF_HELPER_MAP(probe_read_user_str, probe_read_str);
>  	BPF_HELPER_MAP(probe_read_kernel, probe_read);
>  	BPF_HELPER_MAP(probe_read_kernel_str, probe_read_str);
> +	BPF_HELPER_MAP(get_func_ip, unspec);
>  	BPF_HELPER_MAP(get_current_task_btf, unspec);
>  	BPF_HELPER_MAP(task_pt_regs, unspec);
>  #undef BPF_HELPER_MAP
> diff --git a/libdtrace/dt_cg.c b/libdtrace/dt_cg.c
> index 1bd73e11..16c58e35 100644
> --- a/libdtrace/dt_cg.c
> +++ b/libdtrace/dt_cg.c
> @@ -11,6 +11,9 @@
>  #include <setjmp.h>
>  #include <assert.h>
>  #include <errno.h>
> +#include <ctype.h>
> +#include <fcntl.h>
> +#include <unistd.h>
>  
>  #include <sys/socket.h>			/* needed for if_arp.h on OL7/x86 */
>  #include <linux/if_arp.h>		/* ARPHRD_ETHER ARPHRD_INFINIBAND */
> @@ -6988,6 +6991,458 @@ dt_cg_subr_link_ntop(dt_node_t *dnp, dt_irlist_t *dlp, dt_regset_t *drp)
>  	dt_regset_free(drp, type->dn_reg);
>  }
>  
> +static const char *
> +dt_cg_basename(const char *path)
> +{
> +	const char	*p;
> +
> +	p = strrchr(path, '/');
> +	return p != NULL ? p + 1 : path;
> +}
> +
> +static int
> +dt_cg_parse_objname(const char *mod, Lmid_t *lmidp, const char **objp)
> +{
> +	char	*end;
> +
> +	if (mod == NULL || mod[0] == '\0' || strisglob(mod))
> +		return -1;
> +
> +	if (strncmp(mod, "LM", 2) == 0 && isdigit((unsigned char)mod[2])) {
> +		*lmidp = strtoul(mod + 2, &end, 16);
> +		if (*end != '`' || end[1] == '\0' || strchr(end + 1, '`'))
> +			return -1;
> +
> +		*objp = end + 1;
> +		return 0;
> +	}
> +
> +	*lmidp = PR_LMID_EVERY;
> +	*objp = mod;
> +	return 0;
> +}
> +
> +static int
> +dt_cg_probe_objname(const dt_probe_t *prp, Lmid_t *lmidp, const char **objp)
> +{
> +	if (prp == NULL || prp->desc == NULL)
> +		return -1;
> +
> +	return dt_cg_parse_objname(prp->desc->mod, lmidp, objp);
> +}
> +
> +static int
> +dt_cg_obj_matches_path(const char *obj, const char *path)
> +{
> +	const char	*base;
> +	Lmid_t		lmid;
> +
> +	if (obj == NULL || path == NULL)
> +		return 0;
> +
> +	if (dt_cg_parse_objname(obj, &lmid, &obj) == -1)
> +		return 0;
> +
> +	base = dt_cg_basename(path);
> +	return strcmp(obj, path) == 0 || strcmp(obj, base) == 0;
> +}
> +
> +static int
> +dt_cg_elf_sym_to_offset(Elf *elf, const GElf_Sym *sym, uint64_t *offp)
> +{
> +	GElf_Ehdr	ehdr;
> +	size_t		i, phnum;
> +	uint64_t	base = 0;
> +
> +	if (sym->st_shndx == SHN_UNDEF ||
> +	    gelf_getehdr(elf, &ehdr) == NULL ||
> +	    elf_getphdrnum(elf, &phnum) != 0)
> +		return -1;
> +
> +	/*
> +	 * Match the object-relative coordinate used by uprobe offsets.  For
> +	 * ET_DYN, libproc computes file_dyn_base from the first PT_LOAD
> +	 * segment's page offset.  For ET_EXEC, the load bias is zero and the
> +	 * first segment's vaddr remains part of the object-relative offset.
> +	 */
> +	for (i = 0; i < phnum; i++) {
> +		GElf_Phdr	phdr;
> +
> +		if (gelf_getphdr(elf, i, &phdr) == NULL ||
> +		    phdr.p_type != PT_LOAD)
> +			continue;
> +
> +		if (ehdr.e_type == ET_EXEC)
> +			base = phdr.p_vaddr;
> +		else if (ehdr.e_type == ET_DYN && phdr.p_align != 0)
> +			base = phdr.p_vaddr & (phdr.p_align - 1);
> +
> +		break;
> +	}
> +
> +	if (i == phnum || sym->st_value < base)
> +		return -1;
> +
> +	*offp = sym->st_value - base;
> +	return 0;
> +}
> +
> +static int
> +dt_cg_elf_lookup_symtab(Elf *elf, int type, const char *name, GElf_Sym *symp)
> +{
> +	Elf_Scn		*scn = NULL;
> +
> +	while ((scn = elf_nextscn(elf, scn)) != NULL) {
> +		GElf_Shdr	shdr;
> +		Elf_Data	*data;
> +		size_t		i, nsyms;
> +
> +		if (gelf_getshdr(scn, &shdr) == NULL ||
> +		    shdr.sh_type != type ||
> +		    shdr.sh_entsize == 0)
> +			continue;
> +
> +		data = elf_getdata(scn, NULL);
> +		if (data == NULL)
> +			continue;
> +
> +		nsyms = shdr.sh_size / shdr.sh_entsize;
> +		for (i = 0; i < nsyms; i++) {
> +			GElf_Sym	sym;
> +			const char	*s;
> +			uchar_t		stype;
> +
> +			if (gelf_getsym(data, i, &sym) == NULL ||
> +			    sym.st_name == 0 ||
> +			    sym.st_shndx == SHN_UNDEF)
> +				continue;
> +
> +			stype = GELF_ST_TYPE(sym.st_info);
> +			if (stype == STT_FILE || stype == STT_SECTION)
> +				continue;
> +
> +			s = elf_strptr(elf, shdr.sh_link, sym.st_name);
> +			if (s != NULL && strcmp(s, name) == 0) {
> +				*symp = sym;
> +				return 0;
> +			}
> +		}
> +	}
> +
> +	return -1;
> +}
> +
> +static int
> +dt_cg_elf_lookup_offset(const char *path, const char *name, uint64_t *offp)
> +{
> +	GElf_Sym	sym;
> +	Elf		*elf = NULL;
> +	int		fd, rc = -1;
> +
> +	fd = open(path, O_RDONLY);
> +	if (fd < 0)
> +		return -1;
> +
> +	elf = elf_begin(fd, ELF_C_READ_MMAP, NULL);
> +	if (elf == NULL || elf_kind(elf) != ELF_K_ELF)
> +		goto out;
> +
> +	if (dt_cg_elf_lookup_symtab(elf, SHT_SYMTAB, name, &sym) == -1 &&
> +	    dt_cg_elf_lookup_symtab(elf, SHT_DYNSYM, name, &sym) == -1)
> +		goto out;
> +
> +	rc = dt_cg_elf_sym_to_offset(elf, &sym, offp);
> +
> +out:
> +	if (elf != NULL)
> +		elf_end(elf);
> +	close(fd);
> +	return rc;
> +}
> +
> +typedef struct dt_cg_uresolve_arg {
> +	const char	*name;
> +	const char	*obj;
> +	int		explicit_obj;
> +	int		nmatches;
> +	int		no_path;
> +	int		no_symbol;
> +	int		bad_object;
> +	int		different_offset;
> +	uint64_t	off;
> +} dt_cg_uresolve_arg_t;
> +
> +static int
> +dt_cg_uresolve_probe(dtrace_hdl_t *dtp, dt_probe_t *prp, void *arg)
> +{
> +	dt_cg_uresolve_arg_t	*uap = arg;
> +	const char		*path;
> +	uint64_t		off;
> +
> +	path = dt_probe_uprobe_path(prp);
> +	if (path == NULL) {
> +		uap->no_path = 1;
> +		return 1;
> +	}
> +
> +	if (uap->explicit_obj && !dt_cg_obj_matches_path(uap->obj, path)) {
> +		uap->bad_object = 1;
> +		return 1;
> +	}
> +
> +	if (dt_cg_elf_lookup_offset(path, uap->name, &off) != 0) {
> +		uap->no_symbol = 1;
> +		return 1;
> +	}
> +
> +	if (uap->nmatches++ == 0) {
> +		uap->off = off;
> +		return 0;
> +	}
> +
> +	if (uap->off != off) {
> +		uap->different_offset = 1;
> +		return 1;
> +	}
> +
> +	return 0;
> +}
> +
> +static int
> +dt_cg_uresolve_from_probe_file(dt_node_t *dnp, dt_node_t *arg,
> +			       const char *name, const char *obj,
> +			       int explicit_obj, int allow_fallback,
> +			       uint64_t *offp)
> +{
> +	dt_cg_uresolve_arg_t	uarg = { 0 };
> +	dtrace_hdl_t		*dtp = yypcb->pcb_hdl;
> +
> +	uarg.name = name;
> +	uarg.obj = obj;
> +	uarg.explicit_obj = explicit_obj;
> +
> +	if (yypcb->pcb_pdesc != NULL)
> +		dt_probe_iter(dtp, yypcb->pcb_pdesc, dt_cg_uresolve_probe,
> +			      NULL, &uarg);
> +	else if (yypcb->pcb_probe != NULL)
> +		dt_cg_uresolve_probe(dtp, yypcb->pcb_probe, &uarg);
> +
> +	if (uarg.bad_object)
> +		dnerror(arg, D_PROC_NAME, "uresolve( ) object %s does not "
> +			"match the current uprobe object\n", obj);
> +	if (uarg.no_path)
> +		dnerror(dnp, D_PROC_NAME, "uresolve( ) requires a unique "
> +			"uprobe object path for this probe\n");
> +	if (uarg.no_symbol) {
> +		if (allow_fallback)
> +			return -1;
> +
> +		dnerror(arg, D_PROC_FUNC, "failed to resolve user symbol %s "
> +			"in the current uprobe object\n", arg->dn_string);
> +	}
> +
> +	if (uarg.nmatches == 0) {
> +		dnerror(dnp, D_PROC_NAME, "uresolve( ) cannot determine the "
> +			"current uprobe object\n");
> +	}
> +
> +	if (uarg.different_offset)
> +		dnerror(arg, D_PROC_FUNC, "uresolve( ) matched multiple "
> +			"uprobe objects with different offsets for symbol %s\n",
> +			arg->dn_string);
> +
> +	*offp = uarg.off;
> +	return 0;
> +}
> +
> +static int
> +dt_cg_uresolve_from_target(dt_node_t *dnp, dt_node_t *arg, pid_t pid,
> +			   Lmid_t lmid, const char *obj, const char *name,
> +			   int allow_fallback, uint64_t *offp)
> +{
> +	dtrace_hdl_t	*dtp = yypcb->pcb_hdl;
> +	const prmap_t	*mapp, *first;
> +	GElf_Sym	sym;
> +	pid_t		gpid;
> +	int		err = 0;
> +
> +	gpid = dt_proc_grab_lock(dtp, pid,
> +	    DTRACE_PROC_WAITING | DTRACE_PROC_SHORTLIVED);
> +	if (gpid <= 0)
> +		dnerror(dnp, D_PROC_GRAB, "failed to grab target process %d "
> +			"for uresolve( )\n", pid);
> +
> +	if (dt_Pxlookup_by_name(dtp, gpid, lmid, obj, name,
> +	    &sym, NULL) != 0) {
> +		err = 1;
> +	} else if ((mapp = dt_Paddr_to_map(dtp, gpid, sym.st_value)) == NULL ||
> +	    mapp->pr_file == NULL ||
> +	    (first = mapp->pr_file->first_segment) == NULL ||
> +	    sym.st_value < first->pr_vaddr) {
> +		err = 1;
> +	} else {
> +		*offp = sym.st_value - first->pr_vaddr;
> +	}
> +
> +	dt_proc_release_unlock(dtp, gpid);
> +
> +	if (err == 0)
> +		return 0;
> +
> +	if (allow_fallback)
> +		return -1;
> +
> +	dnerror(arg, D_PROC_FUNC, "failed to resolve target user symbol %s\n",
> +		arg->dn_string);
> +}
> +
> +static void
> +dt_cg_uresolve_emit_offset_addr(dt_node_t *dnp, dt_irlist_t *dlp,
> +				dt_regset_t *drp, uint64_t symoff)
> +{
> +	dtrace_hdl_t	*dtp = yypcb->pcb_hdl;
> +	int	treg;
> +
> +	/*
> +	 * Compute the current object base from the saved user PC and the
> +	 * uprobe offset, then add the compile-time-resolved symbol offset.
> +	 * When available, use the kernel's current uprobe IP directly because
> +	 * the saved register block may not reliably carry the breakpoint
> +	 * address for system-wide uprobes across different processes.
> +	 */
> +	if (dtp->dt_bpfhelper[BPF_FUNC_get_func_ip] != BPF_FUNC_unspec) {
> +		if (dt_regset_xalloc_args(drp) == -1)
> +			longjmp(yypcb->pcb_jmpbuf, EDT_NOREG);
> +		if (dt_regset_xalloc(drp, BPF_REG_0) == -1)
> +			longjmp(yypcb->pcb_jmpbuf, EDT_NOREG);
> +
> +		dt_cg_access_dctx(BPF_REG_1, dlp, drp, DCTX_CTX);
> +		emit(dlp, BPF_CALL_HELPER(dtp->dt_bpfhelper[BPF_FUNC_get_func_ip]));
> +		dt_regset_free_args(drp);
> +
> +		if ((dnp->dn_reg = dt_regset_alloc(drp)) == -1)
> +			longjmp(yypcb->pcb_jmpbuf, EDT_NOREG);
> +
> +		emit(dlp, BPF_MOV_REG(dnp->dn_reg, BPF_REG_0));
> +		dt_regset_free(drp, BPF_REG_0);
> +	} else {
> +		if ((dnp->dn_reg = dt_regset_alloc(drp)) == -1)
> +			longjmp(yypcb->pcb_jmpbuf, EDT_NOREG);
> +
> +		if ((treg = dt_regset_alloc(drp)) == -1)
> +			longjmp(yypcb->pcb_jmpbuf, EDT_NOREG);
> +
> +		dt_cg_access_dctx(treg, dlp, drp, DCTX_MST);
> +		emit(dlp, BPF_LOAD(BPF_DW, dnp->dn_reg, treg,
> +				   DMST_REGS + PT_REGS_IP));
> +		dt_regset_free(drp, treg);
> +	}
> +
> +	if ((treg = dt_regset_alloc(drp)) == -1)
> +		longjmp(yypcb->pcb_jmpbuf, EDT_NOREG);
> +
> +	dt_cg_access_dctx(treg, dlp, drp, DCTX_MST);
> +	emit(dlp, BPF_LOAD(BPF_DW, treg, treg, DMST_PROFF));
> +	emit(dlp, BPF_ALU64_REG(BPF_SUB, dnp->dn_reg, treg));
> +	dt_cg_xsetx(dlp, NULL, DT_LBL_NONE, treg, symoff);
> +	emit(dlp, BPF_ALU64_REG(BPF_ADD, dnp->dn_reg, treg));
> +
> +	dt_regset_free(drp, treg);
> +}
> +
> +static void
> +dt_cg_subr_uresolve(dt_node_t *dnp, dt_irlist_t *dlp, dt_regset_t *drp)
> +{
> +	dtrace_hdl_t	*dtp = yypcb->pcb_hdl;
> +	dt_node_t	*arg = dnp->dn_args;
> +	dt_node_t	*fallback;
> +	dt_ident_t	*tidp;
> +	pid_t		pid = 0;
> +	char		*spec, *tick;
> +	const char	*obj = NULL;
> +	const char	*name;
> +	Lmid_t		lmid = PR_LMID_EVERY;
> +	uint64_t	off = 0;
> +	int		err = 0, explicit_obj = 0;
> +	int		use_fallback = 0;
> +
> +	if (arg == NULL || (arg->dn_list != NULL &&
> +	    arg->dn_list->dn_list != NULL))
> +		dnerror(dnp, D_PROTO_LEN, "uresolve( ) prototype mismatch: "
> +			"expected 1 or 2 arguments\n");
> +
> +	if (arg->dn_kind != DT_NODE_STRING)
> +		dnerror(arg, D_PROTO_ARG, "uresolve( ) argument #1 must be "
> +			"a string constant\n");
> +
> +	fallback = arg->dn_list;
> +	tidp = dt_idhash_lookup(dtp->dt_macros, "target");
> +	if (tidp != NULL)
> +		pid = tidp->di_id;
> +
> +	spec = strdup(arg->dn_string);
> +	if (spec == NULL)
> +		longjmp(yypcb->pcb_jmpbuf, EDT_NOMEM);
> +
> +	tick = strrchr(spec, '`');
> +	if (tick != NULL) {
> +		*tick = '\0';
> +		obj = spec;
> +		name = tick + 1;
> +		explicit_obj = 1;
> +
> +		if (*obj == '\0' || *name == '\0')
> +			err = 1;
> +	} else {
> +		name = spec;
> +	}
> +
> +	if (err != 0) {
> +		free(spec);
> +		dnerror(arg, D_PROC_NAME, "uresolve( ) argument must be "
> +			"\"symbol\" or \"object`symbol\"\n");
> +	}
> +
> +	if (pid == 0) {
> +		if (dt_cg_uresolve_from_probe_file(dnp, arg, name, obj,
> +		    explicit_obj, fallback != NULL, &off) != 0)
> +			use_fallback = 1;
> +		goto done;
> +	}
> +
> +	if (explicit_obj) {
> +		if (dt_cg_parse_objname(obj, &lmid, &obj) != 0) {
> +			free(spec);
> +			dnerror(arg, D_PROC_NAME, "uresolve( ) object %s is "
> +				"not a valid user object name\n", arg->dn_string);
> +		}
> +	} else if (dt_cg_probe_objname(yypcb->pcb_probe, &lmid, &obj) != 0) {
> +		const char	*path = dt_probe_uprobe_path(yypcb->pcb_probe);
> +
> +		if (path != NULL) {
> +			obj = path;
> +			lmid = PR_LMID_EVERY;
> +		} else {
> +			free(spec);
> +			dnerror(arg, D_PROC_NAME, "unqualified uresolve( ) "
> +				"requires a concrete probe module\n");
> +		}
> +	}
> +
> +	if (dt_cg_uresolve_from_target(dnp, arg, pid, lmid, obj, name,
> +	    fallback != NULL, &off) != 0)
> +		use_fallback = 1;
> +
> +done:
> +	free(spec);
> +
> +	if (use_fallback) {
> +		dt_cg_node(fallback, dlp, drp);
> +		dnp->dn_reg = fallback->dn_reg;
> +	} else
> +		dt_cg_uresolve_emit_offset_addr(dnp, dlp, drp, off);
> +}
> +
>  typedef void dt_cg_subr_f(dt_node_t *, dt_irlist_t *, dt_regset_t *);
>  
>  static dt_cg_subr_f *_dt_cg_subr[DIF_SUBR_MAX + 1] = {
> @@ -7039,6 +7494,7 @@ static dt_cg_subr_f *_dt_cg_subr[DIF_SUBR_MAX + 1] = {
>  	[DIF_SUBR_LINK_NTOP]		= &dt_cg_subr_link_ntop,
>  	[DIF_SUBR_STACK]		= &dt_cg_subr_stack,
>  	[DIF_SUBR_USTACK]		= &dt_cg_subr_ustack,
> +	[DIF_SUBR_URESOLVE]		= &dt_cg_subr_uresolve,
>  };
>  
>  static void
> diff --git a/libdtrace/dt_open.c b/libdtrace/dt_open.c
> index ca00788a..0505e2ff 100644
> --- a/libdtrace/dt_open.c
> +++ b/libdtrace/dt_open.c
> @@ -330,6 +330,8 @@ static const dt_ident_t _dtrace_globals[] = {
>  	DT_VERS_1_2, &dt_idops_func, "_usymaddr(uintptr_t)" },
>  { "uregs", DT_IDENT_ARRAY, 0, DIF_VAR_UREGS, DT_ATTR_STABCMN, DT_VERS_1_0,
>  	&dt_idops_regs, NULL },
> +{ "uresolve", DT_IDENT_FUNC, 0, DIF_SUBR_URESOLVE, DT_ATTR_STABCMN,
> +	DT_VERS_2_0, &dt_idops_func, "uintptr_t(string, [uintptr_t])" },
>  { "ustack", DT_IDENT_FUNC, DT_IDFLG_DPTR, DIF_SUBR_USTACK, DT_ATTR_STABCMN,
>  	DT_VERS_1_0, &dt_idops_func, "dt_stack_t([uint32_t], [uint32_t])" },
>  { "ustackdepth", DT_IDENT_SCALAR, 0, DIF_VAR_USTACKDEPTH,
> diff --git a/libdtrace/dt_probe.h b/libdtrace/dt_probe.h
> index 54053cd3..bff47055 100644
> --- a/libdtrace/dt_probe.h
> +++ b/libdtrace/dt_probe.h
> @@ -102,6 +102,7 @@ typedef int dt_dependent_f(dtrace_hdl_t *dtp, dt_probe_t *prp, void *arg);
>  extern int dt_probe_dependent_iter(dtrace_hdl_t *dtp, const dt_probe_t *prp,
>  				   dt_dependent_f *func, void *arg);
>  
> +extern const char *dt_probe_uprobe_path(const dt_probe_t *prp);
>  
>  extern void dt_probe_init(dtrace_hdl_t *dtp);
>  extern void dt_probe_detach_all(dtrace_hdl_t *dtp);
> diff --git a/libdtrace/dt_prov_uprobe.c b/libdtrace/dt_prov_uprobe.c
> index d53b1e43..9a481f59 100644
> --- a/libdtrace/dt_prov_uprobe.c
> +++ b/libdtrace/dt_prov_uprobe.c
> @@ -307,6 +307,47 @@ typedef struct uprobe_data {
>  	int	ref_shift;
>  } uprobe_data_t;
>  
> +dt_provimpl_t	dt_pid;
> +dt_provimpl_t	dt_usdt;
> +dt_provimpl_t	dt_stapsdt;
> +
> +const char *
> +dt_probe_uprobe_path(const dt_probe_t *prp)
> +{
> +	const dt_uprobe_t	*upp;
> +	const list_probe_t	*pup;
> +	const char		*path = NULL;
> +
> +	if (prp == NULL || prp->prov == NULL)
> +		return NULL;
> +
> +	if (prp->prov->impl == &dt_uprobe) {
> +		upp = prp->prv_data;
> +		return upp != NULL ? upp->fn : NULL;
> +	}
> +
> +	if (prp->prov->impl != &dt_pid &&
> +	    prp->prov->impl != &dt_usdt &&
> +	    prp->prov->impl != &dt_stapsdt)
> +		return NULL;
> +
> +	for (pup = prp->prv_data; pup != NULL; pup = dt_list_next(pup)) {
> +		if (pup->probe == NULL || pup->probe->prv_data == NULL)
> +			return NULL;
> +
> +		upp = pup->probe->prv_data;
> +		if (upp->fn == NULL)
> +			return NULL;
> +
> +		if (path == NULL)
> +			path = upp->fn;
> +		else if (strcmp(path, upp->fn) != 0)
> +			return NULL;
> +	}
> +
> +	return path;
> +}
> +
>  static const dtrace_pattr_t	pattr = {
>  { DTRACE_STABILITY_EVOLVING, DTRACE_STABILITY_EVOLVING, DTRACE_CLASS_ISA },
>  { DTRACE_STABILITY_PRIVATE, DTRACE_STABILITY_PRIVATE, DTRACE_CLASS_UNKNOWN },
> @@ -315,10 +356,6 @@ static const dtrace_pattr_t	pattr = {
>  { DTRACE_STABILITY_PRIVATE, DTRACE_STABILITY_PRIVATE, DTRACE_CLASS_UNKNOWN },
>  };
>  
> -dt_provimpl_t	dt_pid;
> -dt_provimpl_t	dt_usdt;
> -dt_provimpl_t	dt_stapsdt;
> -
>  #define UPROBE_CONFIG	"/sys/bus/event_source/devices/uprobe/"
>  #define PERF_TYPE_FILE	(UPROBE_CONFIG "type")
>  #define RET_FLAG_FILE	(UPROBE_CONFIG "format/retprobe")
> @@ -1392,6 +1429,19 @@ static int trampoline(dt_pcb_t *pcb, uint_t exitlbl)
>  	 */
>  	dt_cg_tramp_copy_regs(pcb);
>  
> +	/*
> +	 * On some architectures the saved user IP in the uprobe pt_regs is the
> +	 * resume address rather than the probed instruction address.  Normalize
> +	 * the saved IP to the actual uprobe address when the kernel can provide
> +	 * it so consumers such as uresolve() can derive the correct object base.
> +	 */
> +	if (dtp->dt_bpfhelper[BPF_FUNC_get_func_ip] != BPF_FUNC_unspec) {
> +		emit(dlp, BPF_MOV_REG(BPF_REG_1, BPF_REG_8));
> +		emit(dlp, BPF_CALL_HELPER(dtp->dt_bpfhelper[BPF_FUNC_get_func_ip]));
> +		emit(dlp, BPF_STORE(BPF_DW, BPF_REG_7,
> +				    DMST_REGS + PT_REGS_IP, BPF_REG_0));
> +	}
> +
>  	/*
>  	 * Record the uprobe instrumentation offset in the probe context.
>  	 * This is the same object-relative offset passed to perf_event_open()
> -- 
> 2.43.5
> 
> 
> _______________________________________________
> DTrace-devel mailing list
> DTrace-devel@oss.oracle.com
> https://oss.oracle.com/mailman/listinfo/dtrace-devel

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [DTrace-devel] [PATCH 3/6] dtrace: add 'probeoff' variable to record relative offset of probes
  2026-06-05 22:12 ` [PATCH 3/6] dtrace: add 'probeoff' variable to record relative offset of probes Alan Maguire
@ 2026-06-06  1:38   ` Kris Van Hees
  0 siblings, 0 replies; 10+ messages in thread
From: Kris Van Hees @ 2026-06-06  1:38 UTC (permalink / raw)
  To: Alan Maguire; +Cc: dtrace, dtrace-devel

I think this is too specific to a particular problem.  We already know the
address of the probe location (the current PC value, unless it is a return
probe).  What you really seem to want is the base address of the object that
the probe fired in, and the proposed probeoff is merely a means to get to
that information.  It would seem more beneficial to add in functionality
that can give us that base address (base address of the mapping) directly,
and then it would possibly be useful to also have something that gives us
the address of the function the probe fired in.

Introducing built-in variables like modaddr, funcaddr, and perhaps even
probeaddr for consistency might be a way to go.  Or a (fake) array similar
to uregs[] and rags[] that can provide this information.

Anyway, I believe we need to think about it in this direction, because that
is more generic and certainly would support other uses quite nicely.

On Fri, Jun 05, 2026 at 11:12:14PM +0100, Alan Maguire via DTrace-devel wrote:
> Record probe offset in machine state and make it accesible
> via 'probeoff' variable.  Having this is valuable as it will
> allow dynamic computation of runtime variable offsets by giving
> us the base address of a library.  For example, we can determine
> the base address of the python libpython3 library via:
> 
> python$target:::function-entry
> {
> 	base_addr = uregs[R_PC] - probeoff;
> ...
> 
> This will handle ASLR and will work for specific targets and
> system-wide probes.
> 
> Since uregs[R_PC] will give us the runtime address of the python
> probe, subtracting probeoff will give us the relative address used
> at attach time.  For example if 0x7ff925cd7f0d is the uregs
> program counter value and 0xd7f0d is the probeoff value, we now
> know the library base address is 0x7ff925c00000.
> 
> Once we have this, dynamic resolution of variables becomes easy
> because we just need the symbol table offset for the variable
> and we can add the library base address computed as above.
> 
> Signed-off-by: Alan Maguire <alan.maguire@oracle.com>
> ---
>  bpf/get_bvar.c               | 7 +++++++
>  include/dtrace/dif_defines.h | 1 +
>  libdtrace/dt_cg.c            | 5 +++++
>  libdtrace/dt_dctx.h          | 2 ++
>  libdtrace/dt_open.c          | 2 ++
>  libdtrace/dt_prov_uprobe.c   | 8 ++++++++
>  6 files changed, 25 insertions(+)
> 
> diff --git a/bpf/get_bvar.c b/bpf/get_bvar.c
> index 99a6503d..2b53b7d1 100644
> --- a/bpf/get_bvar.c
> +++ b/bpf/get_bvar.c
> @@ -203,6 +203,13 @@ noinline uint64_t dt_bvar_probedesc(const dt_dctx_t *dctx, uint32_t idx)
>  	}
>  }
>  
> +noinline uint64_t dt_bvar_probeoff(const dt_dctx_t *dctx)
> +{
> +	dt_mstate_t	*mst = dctx->mst;
> +
> +	return mst->proff;
> +}
> +
>  noinline uint64_t dt_bvar_stackdepth(const dt_dctx_t *dctx)
>  {
>  	uint32_t	bufsiz = (uint32_t) (uint64_t) (&STKSIZ);
> diff --git a/include/dtrace/dif_defines.h b/include/dtrace/dif_defines.h
> index a18614d2..edb98f06 100644
> --- a/include/dtrace/dif_defines.h
> +++ b/include/dtrace/dif_defines.h
> @@ -163,6 +163,7 @@
>  #define DIF_VAR_ERRNO		0x0120
>  #define DIF_VAR_CURCPU		0x0121
>  #define DIF_VAR_EXECARGS	0x0122
> +#define DIF_VAR_PROBEOFF	0x0123
>  
>  #define DIF_SUBR_RAND			0
>  #define DIF_SUBR_MUTEX_OWNED		1
> diff --git a/libdtrace/dt_cg.c b/libdtrace/dt_cg.c
> index e97e9abc..1bd73e11 100644
> --- a/libdtrace/dt_cg.c
> +++ b/libdtrace/dt_cg.c
> @@ -288,6 +288,11 @@ dt_cg_tramp_prologue_act(dt_pcb_t *pcb, dt_activity_t act)
>  	emit(dlp,  BPF_ALU64_IMM(BPF_ADD, BPF_REG_0, roundup(sizeof(dt_mstate_t), 8)));
>  	emit(dlp,  BPF_STORE(BPF_DW, BPF_REG_9, DCTX_BUF, BPF_REG_0));
>  
> +	/*
> +	 *	dctx.mst->proff = 0;
> +	 */
> +	emit(dlp,  BPF_STORE_IMM(BPF_DW, BPF_REG_7, DMST_PROFF, 0));
> +
>  	/*
>  	 *	mem = buf + roundup(dtp->dt_maxreclen, 8);
>  	 *				// add %r0, roundup(dtp->dt_maxreclen,
> diff --git a/libdtrace/dt_dctx.h b/libdtrace/dt_dctx.h
> index 5bf21345..797c8fd2 100644
> --- a/libdtrace/dt_dctx.h
> +++ b/libdtrace/dt_dctx.h
> @@ -30,6 +30,7 @@ typedef struct dt_mstate {
>  	uint32_t	tag;		/* Tag (for future use) */
>  	uint32_t	scratch_top;	/* Current top of scratch space */
>  	int32_t		syscall_errno;	/* syscall errno */
> +	uint64_t	proff;		/* Probe offset */
>  	uint64_t	specsize;	/* speculation size */
>  	uint64_t	scalarizer;	/* used to scalarize pointers */
>  	uint64_t	fault;		/* DTrace fault flags */
> @@ -44,6 +45,7 @@ typedef struct dt_mstate {
>  #define DMST_STID		offsetof(dt_mstate_t, stid)
>  #define DMST_TAG		offsetof(dt_mstate_t, tag)
>  #define DMST_SCRATCH_TOP	offsetof(dt_mstate_t, scratch_top)
> +#define DMST_PROFF		offsetof(dt_mstate_t, proff)
>  #define DMST_ERRNO		offsetof(dt_mstate_t, syscall_errno)
>  #define DMST_SPECSIZE		offsetof(dt_mstate_t, specsize)
>  #define DMST_SCALARIZER		offsetof(dt_mstate_t, scalarizer)
> diff --git a/libdtrace/dt_open.c b/libdtrace/dt_open.c
> index 30b8758d..ca00788a 100644
> --- a/libdtrace/dt_open.c
> +++ b/libdtrace/dt_open.c
> @@ -240,6 +240,8 @@ static const dt_ident_t _dtrace_globals[] = {
>  	DT_ATTR_STABCMN, DT_VERS_1_0, &dt_idops_type, "string" },
>  { "probeprov", DT_IDENT_SCALAR, 0, DIF_VAR_PROBEPROV,
>  	DT_ATTR_STABCMN, DT_VERS_1_0, &dt_idops_type, "string" },
> +{ "probeoff", DT_IDENT_SCALAR, 0, DIF_VAR_PROBEOFF,
> +	DT_ATTR_STABCMN, DT_VERS_2_0, &dt_idops_type, "uint64_t" },
>  { "progenyof", DT_IDENT_FUNC, 0, DIF_SUBR_PROGENYOF,
>  	DT_ATTR_STABCMN, DT_VERS_1_0,
>  	&dt_idops_func, "int(pid_t)" },
> diff --git a/libdtrace/dt_prov_uprobe.c b/libdtrace/dt_prov_uprobe.c
> index b1b852ce..d53b1e43 100644
> --- a/libdtrace/dt_prov_uprobe.c
> +++ b/libdtrace/dt_prov_uprobe.c
> @@ -1392,6 +1392,14 @@ static int trampoline(dt_pcb_t *pcb, uint_t exitlbl)
>  	 */
>  	dt_cg_tramp_copy_regs(pcb);
>  
> +	/*
> +	 * Record the uprobe instrumentation offset in the probe context.
> +	 * This is the same object-relative offset passed to perf_event_open()
> +	 * as attr.probe_offset.
> +	 */
> +	dt_cg_xsetx(dlp, NULL, DT_LBL_NONE, BPF_REG_0, upp->off);
> +	emit(dlp, BPF_STORE(BPF_DW, BPF_REG_7, DMST_PROFF, BPF_REG_0));
> +
>  	/*
>  	 * pid probes.
>  	 *
> -- 
> 2.43.5
> 
> 
> _______________________________________________
> DTrace-devel mailing list
> DTrace-devel@oss.oracle.com
> https://oss.oracle.com/mailman/listinfo/dtrace-devel

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH 1/6] libdtrace: Pack declared bitfields into C storage units
  2026-06-05 22:12 ` [PATCH 1/6] libdtrace: Pack declared bitfields into C storage units Alan Maguire
@ 2026-06-06  1:54   ` Kris Van Hees
  0 siblings, 0 replies; 10+ messages in thread
From: Kris Van Hees @ 2026-06-06  1:54 UTC (permalink / raw)
  To: Alan Maguire; +Cc: dtrace, dtrace-devel, OpenAI Codex

I hope that Nick is able to comment on this (he is on the list) because I seem
to recall that he has mentioned in the past that the packing of bitfields is
not strictly defined and that we therefore cannot always know how the compiler
performs the layout.  In other words, yes, we could change this but it is not
at all guaranteed to be correct for anything other than the compiler we test it
with.

If my recollection on this is correct, then it becomes a matter of making an
informed decision on what the "best" representation is as opposed to the
"correct" one (where "best" is defined as "most commonly used").

Also, if it is indeed implementation specific, that should certainly be
mentioned in comments and the commit message, so that it is clear that this is
a matter of a choice having been made rather than implementing a particular
standard-defined feature.

On Fri, Jun 05, 2026 at 11:12:12PM +0100, Alan Maguire wrote:
> dt_decl_member() represented each D-declared bitfield as a standalone
> integer type sized to the field width, then let libctf auto-place the
> member.  That made adjacent bitfields consume whole bytes instead of
> sharing the declared C storage unit.  Unnamed padding fields such as
> `unsigned int :24` were therefore treated as additional storage, causing
> ABI-visible layout drift; for example CPython's PyASCIIObject was sized
> as 56 bytes instead of 48 for python 3.6 when associated python3.6
> headers were included.
> 
> The problem in that case was the "state" bitfield which had the form:
> 
>   struct {
>       unsigned int interned:2;
>       unsigned int kind:3;
>       unsigned int compact:1;
>       unsigned int ascii:1;
>       unsigned int ready:1;
>       unsigned int :24;
>   } state;
> 
> i.e. it should have been a packed unsigned int (4 bytes) but DTrace
> was using adding 8 bytes to the representation size, also throwing
> off field offsets that followed.
> 
> Track bitfield allocation state while declaring struct/union members.
> For each storage unit, add an empty-name CTF member using the original
> base type to force the containing type to the C ABI size, and add named
> bitfields as CTF slices at explicit bit offsets within that unit.  Reset
> the allocation state for zero-width unnamed fields and non-bitfield
> members.
> 
> Also make bitfield code generation choose load/store sizes from both the
> field width and its intra-byte offset, since packed fields can now cross
> byte boundaries.
> 
> Update the bitfield sizeof test expectations to C ABI sizes and add a
> regression test for unnamed padding matching the PyASCIIObject layout:
> state size 4, wstr offset 40, total size 48.
> 
> Signed-off-by: Alan Maguire <alan.maguire@oracle.com>
> Assisted-by: OpenAI Codex (GPT-5) <codex@openai.com>
> ---
>  libdtrace/dt_cg.c                            |  33 ++--
>  libdtrace/dt_decl.c                          | 198 ++++++++++++++++---
>  libdtrace/dt_decl.h                          |   6 +
>  test/unittest/bitfields/tst.SizeofBitField.d |   6 +-
>  test/unittest/bitfields/tst.SizeofBitField.r |  12 +-
>  5 files changed, 201 insertions(+), 54 deletions(-)
> 
> diff --git a/libdtrace/dt_cg.c b/libdtrace/dt_cg.c
> index a77c022c..0859e9cb 100644
> --- a/libdtrace/dt_cg.c
> +++ b/libdtrace/dt_cg.c
> @@ -92,6 +92,12 @@ clp2(size_t x)
>  	return x + 1;
>  }
>  
> +static size_t
> +dt_cg_bitfield_size(uint_t offset, uint_t bits)
> +{
> +	return clp2(P2ROUNDUP((offset % NBBY) + bits, NBBY) / NBBY);
> +}
> +
>  /*
>   * Determine the load size for the specified node and CTF type, and return the
>   * equivalent BPF size specifier.
> @@ -104,13 +110,13 @@ dt_cg_ldsize(dt_node_t *dnp, ctf_file_t *ctfp, ctf_id_t type, ssize_t *ret_size)
>  
>  	/*
>  	 * If we're loading a bit-field, the size of our load is found by
> -	 * rounding cte_bits up to a byte boundary and then finding the
> -	 * nearest power of two to this value (see clp2(), above).
> +	 * rounding the bit offset and size up to a byte boundary and then
> +	 * finding the nearest power of two to this value (see clp2(), above).
>  	 */
>  	if (dnp &&
>  	    (dnp->dn_flags & DT_NF_BITFIELD) &&
>  	    ctf_type_encoding(ctfp, type, &e) != CTF_ERR)
> -		size = clp2(P2ROUNDUP(e.cte_bits, NBBY) / NBBY);
> +		size = dt_cg_bitfield_size(e.cte_offset, e.cte_bits);
>  	else
>  		size = ctf_type_size(ctfp, type);
>  
> @@ -3586,7 +3592,8 @@ dt_cg_field_get(dt_node_t *dnp, dt_irlist_t *dlp, dt_regset_t *drp,
>  	if (dnp->dn_flags & DT_NF_REF)
>  		return;
>  
> -	op = dt_cg_ldsize(dnp, fp, mp->ctm_type, &size);
> +	size = dt_cg_bitfield_size(offset, e.cte_bits);
> +	op = bpf_ldst_size(size, 0);
>  	if (dnp->dn_left->dn_flags & (DT_NF_ALLOCA | DT_NF_DPTR))
>  		emit(dlp, BPF_LOAD(op, reg, reg, 0));
>  	else
> @@ -3653,8 +3660,7 @@ dt_cg_field_get(dt_node_t *dnp, dt_irlist_t *dlp, dt_regset_t *drp,
>  	 * nearest power of two to this value (see clp2(), above).
>  	 */
>  #ifdef _BIG_ENDIAN
> -	shift = clp2(P2ROUNDUP(e.cte_bits, NBBY) / NBBY) * NBBY -
> -		(offset + e.cte_bits);
> +	shift = size * NBBY - (offset + e.cte_bits);
>  #else
>  	shift = offset;
>  #endif
> @@ -3686,7 +3692,7 @@ dt_cg_field_set(dt_node_t *src, dt_irlist_t *dlp,
>  {
>  	uint64_t cmask, fmask, shift;
>  	int r1, r2;
> -	size_t offset;
> +	size_t offset, size;
>  
>  	ctf_membinfo_t m;
>  	ctf_encoding_t e;
> @@ -3731,9 +3737,10 @@ dt_cg_field_set(dt_node_t *src, dt_irlist_t *dlp,
>  	 * pass through the containing bits and zero the field bits.
>  	 */
>  #ifdef _BIG_ENDIAN
> -	shift = clp2(P2ROUNDUP(e.cte_bits, NBBY) / NBBY) * NBBY -
> -		(offset % NBBY + e.cte_bits);
> +	size = dt_cg_bitfield_size(offset, e.cte_bits);
> +	shift = size * NBBY - (offset % NBBY + e.cte_bits);
>  #else
> +	size = dt_cg_bitfield_size(offset, e.cte_bits);
>  	shift = offset % NBBY;
>  #endif
>  	fmask = (1ULL << e.cte_bits) - 1;
> @@ -3748,7 +3755,7 @@ dt_cg_field_set(dt_node_t *src, dt_irlist_t *dlp,
>  	 * r2 <<= shift
>  	 * r1 |= r2
>  	 */
> -	emit(dlp, BPF_LOAD(dt_cg_ldsize(dst, fp, m.ctm_type, NULL), r1, dst->dn_reg, 0));
> +	emit(dlp, BPF_LOAD(bpf_ldst_size(size, 0), r1, dst->dn_reg, 0));
>  	dt_cg_setx(dlp, r2, cmask);
>  	emit(dlp, BPF_ALU64_REG(BPF_AND, r1, r2));
>  	dt_cg_setx(dlp, r2, fmask);
> @@ -3770,12 +3777,12 @@ dt_cg_store(dt_node_t *src, dt_irlist_t *dlp, dt_regset_t *drp, dt_node_t *dst)
>  
>  	/*
>  	 * If we're storing into a bit-field, the size of our store is found by
> -	 * rounding dst's cte_bits up to a byte boundary and then finding the
> -	 * nearest power of two to this value (see clp2(), above).
> +	 * rounding dst's bit offset and size up to a byte boundary and then
> +	 * finding the nearest power of two to this value (see clp2(), above).
>  	 */
>  	if ((dst->dn_flags & DT_NF_BITFIELD) &&
>  	    ctf_type_encoding(dst->dn_ctfp, dst->dn_type, &e) != CTF_ERR)
> -		size = clp2(P2ROUNDUP(e.cte_bits, NBBY) / NBBY);
> +		size = dt_cg_bitfield_size(e.cte_offset, e.cte_bits);
>  	else
>  		size = dt_node_type_size(dst);
>  
> diff --git a/libdtrace/dt_decl.c b/libdtrace/dt_decl.c
> index 1f4936dd..29c58189 100644
> --- a/libdtrace/dt_decl.c
> +++ b/libdtrace/dt_decl.c
> @@ -16,6 +16,8 @@
>  #include <dt_module.h>
>  #include <dt_impl.h>
>  
> +static void dt_decl_bf_reset(dt_scope_t *);
> +
>  static dt_decl_t *
>  dt_decl_check(dt_decl_t *ddp)
>  {
> @@ -129,6 +131,7 @@ dt_decl_pop(void)
>  	dsp->ds_type = CTF_ERR;
>  	dsp->ds_class = DT_DC_DEFAULT;
>  	dsp->ds_enumval = -1;
> +	dt_decl_bf_reset(dsp);
>  
>  	return ddp;
>  }
> @@ -483,6 +486,89 @@ dt_decl_sou(uint_t kind, char *name)
>  	return ddp;
>  }
>  
> +static ulong_t
> +dt_decl_bf_align(ulong_t off, ulong_t align)
> +{
> +	return align == 0 ? off : ((off + align - 1) / align) * align;
> +}
> +
> +static void
> +dt_decl_bf_reset(dt_scope_t *dsp)
> +{
> +	dsp->ds_bf_active = 0;
> +	dsp->ds_bf_base = CTF_ERR;
> +	dsp->ds_bf_unit_off = 0;
> +	dsp->ds_bf_unit_bits = 0;
> +	dsp->ds_bf_unit_align = 0;
> +	dsp->ds_bf_next = 0;
> +}
> +
> +static ulong_t
> +dt_decl_bf_size(dt_scope_t *dsp)
> +{
> +	ssize_t size = ctf_type_size(dsp->ds_ctfp, dsp->ds_type);
> +
> +	if (size < 0) {
> +		xyerror(D_UNKNOWN, "failed to determine struct size: %s\n",
> +		    ctf_errmsg(ctf_errno(dsp->ds_ctfp)));
> +	}
> +
> +	return (ulong_t)size * CHAR_BIT;
> +}
> +
> +static void
> +dt_decl_bf_anchor(dt_scope_t *dsp, ctf_id_t base, ulong_t off,
> +    const char *idname)
> +{
> +	if (ctf_add_member_offset(dsp->ds_ctfp, dsp->ds_type, "",
> +	    base, off) == CTF_ERR) {
> +		xyerror(D_UNKNOWN, "failed to define storage for "
> +		    "bit-field '%s': %s\n", idname,
> +		    ctf_errmsg(ctf_errno(dsp->ds_ctfp)));
> +	}
> +}
> +
> +static void
> +dt_decl_bf_start(dt_scope_t *dsp, ctf_id_t base, uint_t unit_bits,
> +    uint_t unit_align, const char *idname)
> +{
> +	ulong_t off = dt_decl_bf_align(dt_decl_bf_size(dsp), unit_align);
> +
> +	dt_decl_bf_anchor(dsp, base, off, idname);
> +
> +	dsp->ds_bf_active = 1;
> +	dsp->ds_bf_base = base;
> +	dsp->ds_bf_unit_off = off;
> +	dsp->ds_bf_unit_bits = unit_bits;
> +	dsp->ds_bf_unit_align = unit_align;
> +	dsp->ds_bf_next = 0;
> +}
> +
> +static void
> +dt_decl_member_copy(dt_scope_t *dsp, dtrace_typeinfo_t *dtt,
> +    const char *idname)
> +{
> +	/*
> +	 * If the member type is not defined in the same CTF container as the
> +	 * one associated with the current scope (i.e. the container for the
> +	 * struct or union itself) or its parent, copy the member type into
> +	 * this container and reset dtt to refer to the copied type.
> +	 */
> +	if (dtt->dtt_ctfp != dsp->ds_ctfp &&
> +	    dtt->dtt_ctfp != ctf_parent_file(dsp->ds_ctfp)) {
> +
> +		dtt->dtt_type = ctf_add_type(dsp->ds_ctfp,
> +		    dtt->dtt_ctfp, dtt->dtt_type);
> +		dtt->dtt_ctfp = dsp->ds_ctfp;
> +
> +		if (dtt->dtt_type == CTF_ERR ||
> +		    ctf_update(dtt->dtt_ctfp) == CTF_ERR) {
> +			xyerror(D_UNKNOWN, "failed to copy type of '%s': %s\n",
> +			    idname, ctf_errmsg(ctf_errno(dtt->dtt_ctfp)));
> +		}
> +	}
> +}
> +
>  void
>  dt_decl_member(dt_node_t *dnp)
>  {
> @@ -542,22 +628,28 @@ dt_decl_member(dt_node_t *dnp)
>  		xyerror(D_DECL_VOIDOBJ, "cannot have void member: %s\n", ident);
>  
>  	/*
> -	 * If a bit-field qualifier was part of the member declaration, create
> -	 * a new integer type of the same name and attributes as the base type
> -	 * and size equal to the specified number of bits.  We reset 'dtt' to
> -	 * refer to this new bit-field type and continue on to add the member.
> +	 * If a bit-field qualifier was part of the member declaration, add a
> +	 * hidden storage-unit member to force the containing type to the C ABI
> +	 * size, and add the named field as a CTF slice at the corresponding bit
> +	 * offset within that unit.
>  	 */
>  	if (dnp != NULL) {
> +		ctf_id_t bftype;
> +		uint_t unit_bits, unit_align, width;
> +		ulong_t bitoff, memberoff;
> +		ssize_t align;
> +		int is_union;
> +
>  		dnp = dt_node_cook(dnp, DT_IDFLG_REF);
>  
>  		/*
>  		 * A bit-field member with no declarator is permitted to have
>  		 * size zero and indicates that no more fields are to be packed
> -		 * into the current storage unit.  We ignore these directives
> -		 * as the underlying ctf code currently does so for all fields.
> +		 * into the current storage unit.
>  		 */
>  		if (ident == NULL && dnp->dn_kind == DT_NODE_INT &&
>  		    dnp->dn_value == 0) {
> +			dt_decl_bf_reset(dsp);
>  			dt_node_free(dnp);
>  			goto done;
>  		}
> @@ -579,43 +671,77 @@ dt_decl_member(dt_node_t *dnp)
>  			    "for type: %s\n", idname);
>  		}
>  
> +		dt_decl_member_copy(dsp, &dtt, idname);
> +
> +		base = ctf_type_resolve(dtt.dtt_ctfp, dtt.dtt_type);
> +		unit_bits = cte.cte_bits;
> +		width = (uint_t)dnp->dn_value;
> +		align = ctf_type_align(dtt.dtt_ctfp, base);
> +		if (align < 0)
> +			align = size;
> +		unit_align = (uint_t)align * CHAR_BIT;
> +		if (unit_align == 0)
> +			unit_align = CHAR_BIT;
> +		is_union = ctf_type_kind(dsp->ds_ctfp, dsp->ds_type) ==
> +		    CTF_K_UNION;
> +
> +		if (is_union) {
> +			dt_decl_bf_anchor(dsp, base, 0, idname);
> +			bitoff = 0;
> +		} else {
> +			if (!dsp->ds_bf_active) {
> +				dt_decl_bf_start(dsp, base, unit_bits,
> +				    unit_align, idname);
> +			} else if (unit_bits > dsp->ds_bf_unit_bits &&
> +			    dsp->ds_bf_next + width <= unit_bits &&
> +			    dsp->ds_bf_unit_off % unit_align == 0) {
> +				dt_decl_bf_anchor(dsp, base,
> +				    dsp->ds_bf_unit_off, idname);
> +				dsp->ds_bf_base = base;
> +				dsp->ds_bf_unit_bits = unit_bits;
> +				dsp->ds_bf_unit_align = unit_align;
> +			} else if (dsp->ds_bf_next + width >
> +			    dsp->ds_bf_unit_bits) {
> +				dt_decl_bf_start(dsp, base, unit_bits,
> +				    unit_align, idname);
> +			}
> +
> +			bitoff = dsp->ds_bf_next;
> +			dsp->ds_bf_next += width;
> +		}
> +
> +		if (ident == NULL) {
> +			dt_node_free(dnp);
> +			goto done;
> +		}
> +
>  		cte.cte_offset = 0;
> -		cte.cte_bits = (uint_t)dnp->dn_value;
> +		cte.cte_bits = width;
> +		memberoff = is_union ? 0 :
> +		    dsp->ds_bf_unit_off + (bitoff / CHAR_BIT) * CHAR_BIT;
> +		cte.cte_offset = bitoff % CHAR_BIT;
>  
> -		dtt.dtt_type = ctf_add_integer(dsp->ds_ctfp,
> -		    CTF_ADD_NONROOT, ctf_type_name(dtt.dtt_ctfp,
> -		    dtt.dtt_type, n, sizeof(n)), &cte);
> +		bftype = ctf_add_slice(dsp->ds_ctfp, CTF_ADD_NONROOT,
> +		    base, &cte);
>  
> -		if (dtt.dtt_type == CTF_ERR ||
> -		    ctf_update(dsp->ds_ctfp) == CTF_ERR) {
> +		if (bftype == CTF_ERR) {
>  			xyerror(D_UNKNOWN, "failed to create type for "
>  			    "member '%s': %s\n", idname,
>  			    ctf_errmsg(ctf_errno(dsp->ds_ctfp)));
>  		}
>  
> -		dtt.dtt_ctfp = dsp->ds_ctfp;
> +		if (ctf_add_member_offset(dsp->ds_ctfp, dsp->ds_type,
> +		    ident, bftype, memberoff) == CTF_ERR) {
> +			xyerror(D_UNKNOWN, "failed to define member '%s': %s\n",
> +			    idname, ctf_errmsg(ctf_errno(dsp->ds_ctfp)));
> +		}
> +
>  		dt_node_free(dnp);
> +		goto done;
>  	}
>  
> -	/*
> -	 * If the member type is not defined in the same CTF container as the
> -	 * one associated with the current scope (i.e. the container for the
> -	 * struct or union itself) or its parent, copy the member type into
> -	 * this container and reset dtt to refer to the copied type.
> -	 */
> -	if (dtt.dtt_ctfp != dsp->ds_ctfp &&
> -	    dtt.dtt_ctfp != ctf_parent_file(dsp->ds_ctfp)) {
> -
> -		dtt.dtt_type = ctf_add_type(dsp->ds_ctfp,
> -		    dtt.dtt_ctfp, dtt.dtt_type);
> -		dtt.dtt_ctfp = dsp->ds_ctfp;
> -
> -		if (dtt.dtt_type == CTF_ERR ||
> -		    ctf_update(dtt.dtt_ctfp) == CTF_ERR) {
> -			xyerror(D_UNKNOWN, "failed to copy type of '%s': %s\n",
> -			    idname, ctf_errmsg(ctf_errno(dtt.dtt_ctfp)));
> -		}
> -	}
> +	dt_decl_bf_reset(dsp);
> +	dt_decl_member_copy(dsp, &dtt, idname);
>  
>  	if (ctf_add_member(dsp->ds_ctfp, dsp->ds_type,
>  	    ident, dtt.dtt_type) == CTF_ERR) {
> @@ -1040,6 +1166,7 @@ dt_scope_create(dt_scope_t *dsp)
>  	dsp->ds_type = CTF_ERR;
>  	dsp->ds_class = DT_DC_DEFAULT;
>  	dsp->ds_enumval = -1;
> +	dt_decl_bf_reset(dsp);
>  }
>  
>  void
> @@ -1072,6 +1199,7 @@ dt_scope_push(ctf_file_t *ctfp, ctf_id_t type)
>  	dsp->ds_type = type;
>  	dsp->ds_class = rsp->ds_class;
>  	dsp->ds_enumval = rsp->ds_enumval;
> +	dt_decl_bf_reset(dsp);
>  
>  	dt_scope_create(rsp);
>  	rsp->ds_next = dsp;
> @@ -1101,6 +1229,12 @@ dt_scope_pop(void)
>  	rsp->ds_type = dsp->ds_type;
>  	rsp->ds_class = dsp->ds_class;
>  	rsp->ds_enumval = dsp->ds_enumval;
> +	rsp->ds_bf_active = dsp->ds_bf_active;
> +	rsp->ds_bf_base = dsp->ds_bf_base;
> +	rsp->ds_bf_unit_off = dsp->ds_bf_unit_off;
> +	rsp->ds_bf_unit_bits = dsp->ds_bf_unit_bits;
> +	rsp->ds_bf_unit_align = dsp->ds_bf_unit_align;
> +	rsp->ds_bf_next = dsp->ds_bf_next;
>  
>  	free(dsp);
>  	return rsp->ds_decl;
> diff --git a/libdtrace/dt_decl.h b/libdtrace/dt_decl.h
> index 0bec183b..4be31158 100644
> --- a/libdtrace/dt_decl.h
> +++ b/libdtrace/dt_decl.h
> @@ -58,6 +58,12 @@ typedef struct dt_scope {
>  	ctf_id_t ds_type;		/* CTF id of enclosing type */
>  	dt_dclass_t ds_class;		/* declaration class for this scope */
>  	int ds_enumval;			/* most recent enumerator value */
> +	int ds_bf_active;		/* bit-field allocation unit is active */
> +	ctf_id_t ds_bf_base;		/* bit-field allocation unit base type */
> +	ulong_t ds_bf_unit_off;		/* bit offset of bit-field unit */
> +	uint_t ds_bf_unit_bits;		/* size in bits of bit-field unit */
> +	uint_t ds_bf_unit_align;		/* alignment in bits of bit-field unit */
> +	uint_t ds_bf_next;		/* next bit offset in bit-field unit */
>  } dt_scope_t;
>  
>  extern dt_decl_t *dt_decl_alloc(ushort_t, char *);
> diff --git a/test/unittest/bitfields/tst.SizeofBitField.d b/test/unittest/bitfields/tst.SizeofBitField.d
> index 1eb4ab5d..2ad7df02 100644
> --- a/test/unittest/bitfields/tst.SizeofBitField.d
> +++ b/test/unittest/bitfields/tst.SizeofBitField.d
> @@ -80,9 +80,9 @@ BEGIN
>  }
>  
>  END
> -/(1 != sizeof(var1)) || (2 != sizeof(var2)) || (3 != sizeof(var3)) ||
> -    (4 != sizeof(var4)) || (5 != sizeof(var5)) || (6 != sizeof(var6))
> -    || (7 != sizeof(var7)) || (8 != sizeof(var8)) || (12 != sizeof(var12))/
> +/(4 != sizeof(var1)) || (4 != sizeof(var2)) || (4 != sizeof(var3)) ||
> +    (4 != sizeof(var4)) || (4 != sizeof(var5)) || (8 != sizeof(var6))
> +    || (8 != sizeof(var7)) || (8 != sizeof(var8)) || (12 != sizeof(var12))/
>  {
>  	exit(1);
>  }
> diff --git a/test/unittest/bitfields/tst.SizeofBitField.r b/test/unittest/bitfields/tst.SizeofBitField.r
> index 9d97b2aa..a5e4007b 100644
> --- a/test/unittest/bitfields/tst.SizeofBitField.r
> +++ b/test/unittest/bitfields/tst.SizeofBitField.r
> @@ -1,10 +1,10 @@
> -sizeof(bitRecord1): 1
> -sizeof(bitRecord2): 2
> -sizeof(bitRecord3): 3
> +sizeof(bitRecord1): 4
> +sizeof(bitRecord2): 4
> +sizeof(bitRecord3): 4
>  sizeof(bitRecord4): 4
> -sizeof(bitRecord5): 5
> -sizeof(bitRecord6): 6
> -sizeof(bitRecord7): 7
> +sizeof(bitRecord5): 4
> +sizeof(bitRecord6): 8
> +sizeof(bitRecord7): 8
>  sizeof(bitRecord8): 8
>  sizeof(bitRecord12): 12
>  
> -- 
> 2.43.5
> 

^ permalink raw reply	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2026-06-06  1:55 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-05 22:12 [PATCH 0/6] dtrace: support user-space symbol resolution via uresolve() Alan Maguire
2026-06-05 22:12 ` [PATCH 1/6] libdtrace: Pack declared bitfields into C storage units Alan Maguire
2026-06-06  1:54   ` Kris Van Hees
2026-06-05 22:12 ` [PATCH 2/6] libdtrace: do not taint scalar alloca member loads Alan Maguire
2026-06-05 22:12 ` [PATCH 3/6] dtrace: add 'probeoff' variable to record relative offset of probes Alan Maguire
2026-06-06  1:38   ` [DTrace-devel] " Kris Van Hees
2026-06-05 22:12 ` [PATCH 4/6] dtrace: Add uresolve(symbol_name[, optional_fallback]) for symbol lookup Alan Maguire
2026-06-06  1:08   ` [DTrace-devel] " Kris Van Hees
2026-06-05 22:12 ` [PATCH 5/6] test,funcs: add uresolve() tests Alan Maguire
2026-06-05 22:12 ` [PATCH 6/6] docs: document uresolve() in the user guide Alan Maguire

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox