[DTrace-devel] [PATCH] cg, printf: allow storing [u]stack() into vars and printf using %k

Kris Van Hees kris.van.hees at oracle.com
Thu Oct 2 19:47:39 UTC 2025


To support storing [u]stack() values in variables and as arguments for
printf(), a type definition is introduced:

	typedef struct dt_stack {
		uint32_t	frames;
		uint32_t	strsz;	  // optional string blob size
		uint32_t	type;	  // type of stack (kernel/user)
		uint32_t	pid;	  // process id (or 0 for kernel)
		uint64_t	addrs[n]; // stack trace addresses
	} dt_stack_t;

where 'n' is the maximum number of frames that can be retrieved (set
with the 'maxframes' option).

Since dt_stack_t needs to be known early enough to define stack(),
jstack(), and ustack(), it is defined in two stages.  First dt_stack_t
is defined as a typedef for struct dt_stack, and once options have been
processed, struct dt_stack is finalized.

Once the value of a stack has been assigned to a variable, it can be
accessed as a regular struct (*not* a pointer to a struct).
Modification is also possible, and safety checks are performed to ensure
that such modifications do not cause crashes in the consumer.

Signed-off-by: Kris Van Hees <kris.van.hees at oracle.com>
---
 libdtrace/dt_cg.c                             | 43 ++++++-----
 libdtrace/dt_dctx.h                           |  3 +-
 libdtrace/dt_impl.h                           |  3 +
 libdtrace/dt_open.c                           | 62 ++++++++++++---
 libdtrace/dt_parser.c                         |  2 +-
 libdtrace/dt_printf.c                         | 40 ++++++++--
 .../unittest/funcs/stack/err.corrupted-data.d | 30 ++++++++
 .../unittest/funcs/stack/err.corrupted-data.r |  6 ++
 .../unittest/funcs/stack/err.store-to-stack.d | 28 +++++++
 .../unittest/funcs/stack/err.store-to-stack.r |  2 +
 test/unittest/funcs/stack/tst.asgn_dvar.d     | 30 ++++++++
 test/unittest/funcs/stack/tst.asgn_dvar.r.p   | 35 +++++++++
 test/unittest/funcs/stack/tst.asgn_gvar.d     | 30 ++++++++
 test/unittest/funcs/stack/tst.asgn_gvar.r.p   |  1 +
 test/unittest/funcs/stack/tst.asgn_lvar.d     | 30 ++++++++
 test/unittest/funcs/stack/tst.asgn_lvar.r.p   |  1 +
 test/unittest/funcs/stack/tst.asgn_tvar.d     | 30 ++++++++
 test/unittest/funcs/stack/tst.asgn_tvar.r.p   |  1 +
 test/unittest/funcs/stack/tst.ref_addrs.d     | 29 +++++++
 test/unittest/funcs/stack/tst.ref_addrs.r     |  7 ++
 test/unittest/funcs/stack/tst.ref_depth.d     | 40 ++++++++++
 test/unittest/funcs/stack/tst.ref_depth.r     |  8 ++
 test/unittest/funcs/stack/tst.ref_pid.d       | 40 ++++++++++
 test/unittest/funcs/stack/tst.ref_pid.r       |  8 ++
 test/unittest/funcs/stack/tst.ref_strsz.d     | 40 ++++++++++
 test/unittest/funcs/stack/tst.ref_strsz.r     |  8 ++
 test/unittest/funcs/stack/tst.ref_type.d      | 40 ++++++++++
 test/unittest/funcs/stack/tst.ref_type.r      |  8 ++
 test/unittest/funcs/stack/tst.store.d         | 30 ++++++++
 .../funcs/ustack/err.corrupted-data.d         | 25 +++++++
 .../funcs/ustack/err.corrupted-data.r         |  4 +
 .../funcs/ustack/err.store-to-stack.d         | 23 ++++++
 .../funcs/ustack/err.store-to-stack.r         |  2 +
 test/unittest/funcs/ustack/tst.asgn_dvar.d    | 25 +++++++
 test/unittest/funcs/ustack/tst.asgn_dvar.r.p  | 35 +++++++++
 test/unittest/funcs/ustack/tst.asgn_gvar.d    | 25 +++++++
 test/unittest/funcs/ustack/tst.asgn_gvar.r.p  |  1 +
 test/unittest/funcs/ustack/tst.asgn_lvar.d    | 25 +++++++
 test/unittest/funcs/ustack/tst.asgn_lvar.r.p  |  1 +
 test/unittest/funcs/ustack/tst.asgn_tvar.d    | 25 +++++++
 test/unittest/funcs/ustack/tst.asgn_tvar.r.p  |  1 +
 test/unittest/funcs/ustack/tst.ref_addrs.d    | 24 ++++++
 test/unittest/funcs/ustack/tst.ref_addrs.r    |  5 ++
 test/unittest/funcs/ustack/tst.ref_depth.d    | 35 +++++++++
 test/unittest/funcs/ustack/tst.ref_depth.r    |  6 ++
 test/unittest/funcs/ustack/tst.ref_pid.d      | 37 +++++++++
 test/unittest/funcs/ustack/tst.ref_strsz.d    | 35 +++++++++
 test/unittest/funcs/ustack/tst.ref_strsz.r    |  6 ++
 test/unittest/funcs/ustack/tst.ref_type.d     | 35 +++++++++
 test/unittest/funcs/ustack/tst.ref_type.r     |  6 ++
 test/unittest/funcs/ustack/tst.store.d        | 25 +++++++
 test/unittest/printf/tst.stack.d              | 33 ++++++++
 test/unittest/printf/tst.stack.r              |  1 +
 test/unittest/printf/tst.stack.r.p            | 75 +++++++++++++++++++
 test/unittest/printf/tst.ustack25_pid.d       | 21 ++++++
 test/unittest/printf/tst.ustack25_pid.r       | 28 +++++++
 test/unittest/printf/tst.ustack25_pid.r.p     | 37 +++++++++
 57 files changed, 1196 insertions(+), 40 deletions(-)
 create mode 100644 test/unittest/funcs/stack/err.corrupted-data.d
 create mode 100644 test/unittest/funcs/stack/err.corrupted-data.r
 create mode 100644 test/unittest/funcs/stack/err.store-to-stack.d
 create mode 100644 test/unittest/funcs/stack/err.store-to-stack.r
 create mode 100644 test/unittest/funcs/stack/tst.asgn_dvar.d
 create mode 100755 test/unittest/funcs/stack/tst.asgn_dvar.r.p
 create mode 100644 test/unittest/funcs/stack/tst.asgn_gvar.d
 create mode 120000 test/unittest/funcs/stack/tst.asgn_gvar.r.p
 create mode 100644 test/unittest/funcs/stack/tst.asgn_lvar.d
 create mode 120000 test/unittest/funcs/stack/tst.asgn_lvar.r.p
 create mode 100644 test/unittest/funcs/stack/tst.asgn_tvar.d
 create mode 120000 test/unittest/funcs/stack/tst.asgn_tvar.r.p
 create mode 100644 test/unittest/funcs/stack/tst.ref_addrs.d
 create mode 100644 test/unittest/funcs/stack/tst.ref_addrs.r
 create mode 100644 test/unittest/funcs/stack/tst.ref_depth.d
 create mode 100644 test/unittest/funcs/stack/tst.ref_depth.r
 create mode 100644 test/unittest/funcs/stack/tst.ref_pid.d
 create mode 100644 test/unittest/funcs/stack/tst.ref_pid.r
 create mode 100644 test/unittest/funcs/stack/tst.ref_strsz.d
 create mode 100644 test/unittest/funcs/stack/tst.ref_strsz.r
 create mode 100644 test/unittest/funcs/stack/tst.ref_type.d
 create mode 100644 test/unittest/funcs/stack/tst.ref_type.r
 create mode 100644 test/unittest/funcs/stack/tst.store.d
 create mode 100644 test/unittest/funcs/ustack/err.corrupted-data.d
 create mode 100644 test/unittest/funcs/ustack/err.corrupted-data.r
 create mode 100644 test/unittest/funcs/ustack/err.store-to-stack.d
 create mode 100644 test/unittest/funcs/ustack/err.store-to-stack.r
 create mode 100644 test/unittest/funcs/ustack/tst.asgn_dvar.d
 create mode 100755 test/unittest/funcs/ustack/tst.asgn_dvar.r.p
 create mode 100644 test/unittest/funcs/ustack/tst.asgn_gvar.d
 create mode 120000 test/unittest/funcs/ustack/tst.asgn_gvar.r.p
 create mode 100644 test/unittest/funcs/ustack/tst.asgn_lvar.d
 create mode 120000 test/unittest/funcs/ustack/tst.asgn_lvar.r.p
 create mode 100644 test/unittest/funcs/ustack/tst.asgn_tvar.d
 create mode 120000 test/unittest/funcs/ustack/tst.asgn_tvar.r.p
 create mode 100644 test/unittest/funcs/ustack/tst.ref_addrs.d
 create mode 100644 test/unittest/funcs/ustack/tst.ref_addrs.r
 create mode 100644 test/unittest/funcs/ustack/tst.ref_depth.d
 create mode 100644 test/unittest/funcs/ustack/tst.ref_depth.r
 create mode 100644 test/unittest/funcs/ustack/tst.ref_pid.d
 create mode 100644 test/unittest/funcs/ustack/tst.ref_strsz.d
 create mode 100644 test/unittest/funcs/ustack/tst.ref_strsz.r
 create mode 100644 test/unittest/funcs/ustack/tst.ref_type.d
 create mode 100644 test/unittest/funcs/ustack/tst.ref_type.r
 create mode 100644 test/unittest/funcs/ustack/tst.store.d
 create mode 100644 test/unittest/printf/tst.stack.d
 create mode 100644 test/unittest/printf/tst.stack.r
 create mode 100755 test/unittest/printf/tst.stack.r.p
 create mode 100644 test/unittest/printf/tst.ustack25_pid.d
 create mode 100644 test/unittest/printf/tst.ustack25_pid.r
 create mode 100755 test/unittest/printf/tst.ustack25_pid.r.p

diff --git a/libdtrace/dt_cg.c b/libdtrace/dt_cg.c
index 3ce13c74..ec47bffa 100644
--- a/libdtrace/dt_cg.c
+++ b/libdtrace/dt_cg.c
@@ -2730,7 +2730,7 @@ dt_cg_act_stack_sub(dt_pcb_t *pcb, dt_node_t *dnp, int reg, int off,
 	dt_irlist_t	*dlp = &pcb->pcb_ir;
 	dt_regset_t	*drp = pcb->pcb_regs;
 	uint64_t	arg;
-	int		nframes, stacksize, prefsz, align = sizeof(uint64_t);
+	int		nframes, allocsz, aloff;
 	uint_t		lbl_valid = dt_irlist_label(dlp);
 	dt_ident_t	*skip = dt_dlib_get_var(dtp, "STACK_SKIP");
 
@@ -2738,19 +2738,26 @@ dt_cg_act_stack_sub(dt_pcb_t *pcb, dt_node_t *dnp, int reg, int off,
 
 	/* Get sizing information from dnp->dn_arg. */
 	arg = dt_cg_stack_arg(dtp, dnp, kind);
-	prefsz = kind == DTRACEACT_USTACK ? sizeof(uint64_t) : 0;
 	nframes = DTRACE_STACK_NFRAMES(arg);
-	stacksize = nframes * sizeof(uint64_t);
+	allocsz = DTRACE_STACK_ALLOCSZ(arg);
 
 	/* Handle alignment and reserve space in the output buffer. */
 	if (reg >= 0) {
-		off = ALIGN(off, align);
+		aloff = ALIGN(off, 8);
 	} else {
 		reg = BPF_REG_9;
-		off = dt_rec_add(dtp, dt_cg_fill_gap, kind,
-				 prefsz + stacksize, align, NULL, arg);
+		aloff = off = dt_rec_add(dtp, dt_cg_fill_gap, kind, allocsz, 8,
+					 NULL, 0);
 	}
 
+	/* Store the stack trace meta-data (type, depth, strsize). */
+	emit(dlp,  BPF_STORE_IMM(BPF_W, reg, aloff,
+				 DTRACE_STACK_NFRAMES(arg)));
+	emit(dlp,  BPF_STORE_IMM(BPF_W, reg, aloff + 4,
+				 DTRACE_STACK_STRSIZE(arg)));
+	emit(dlp,  BPF_STORE_IMM(BPF_W, reg, aloff + 8,
+				 DTRACE_STACK_IS_USER(arg) ? 1 : 0));
+
 	/* Write the tgid. */
 	if (kind == DTRACEACT_USTACK) {
 		if (dt_regset_xalloc_args(drp) == -1)
@@ -2760,17 +2767,18 @@ dt_cg_act_stack_sub(dt_pcb_t *pcb, dt_node_t *dnp, int reg, int off,
 		dt_regset_free_args(drp);
 		/* mov32 %r0, %r0 effectively masks the lower 32 bits. */
 		emit(dlp,  BPF_MOV32_REG(BPF_REG_0, BPF_REG_0));
-		emit(dlp,  BPF_STORE(BPF_DW, reg, off, BPF_REG_0));
+		emit(dlp,  BPF_STORE(BPF_DW, reg, aloff + 12, BPF_REG_0));
 		dt_regset_free(drp, BPF_REG_0);
-	}
+	} else
+		emit(dlp,  BPF_STORE_IMM(BPF_W, reg, aloff + 12, 0));
 
 	/* Call bpf_get_stack(ctx, buf, size, flags). */
 	if (dt_regset_xalloc_args(drp) == -1)
 		longjmp(yypcb->pcb_jmpbuf, EDT_NOREG);
 	dt_cg_access_dctx(BPF_REG_1, dlp, drp, DCTX_CTX);
 	emit(dlp,  BPF_MOV_REG(BPF_REG_2, reg));
-	emit(dlp,  BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, off + prefsz));
-	emit(dlp,  BPF_MOV_IMM(BPF_REG_3, stacksize));
+	emit(dlp,  BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, aloff + 16));
+	emit(dlp,  BPF_MOV_IMM(BPF_REG_3, nframes * sizeof(uint64_t)));
 	if (kind == DTRACEACT_USTACK)
 		emit(dlp,  BPF_MOV_IMM(BPF_REG_4, BPF_F_USER_STACK));
 	else {
@@ -2786,7 +2794,7 @@ dt_cg_act_stack_sub(dt_pcb_t *pcb, dt_node_t *dnp, int reg, int off,
 	emitl(dlp, lbl_valid,
 		   BPF_NOP());
 
-	return prefsz + stacksize;
+	return (aloff - off) + allocsz;
 }
 
 static void
@@ -8735,15 +8743,14 @@ dt_cg_agg(dt_pcb_t *pcb, dt_node_t *dnp, dt_irlist_t *dlp, dt_regset_t *drp)
 
 				if (idp->di_kind == DT_IDENT_FUNC) {
 					switch (idp->di_id) {
-					case DIF_SUBR_USTACK:
-						arg = dt_cg_stack_arg(dtp, knp, DTRACEACT_USTACK);
-						kind = DTRACEACT_USTACK;
-						size = 8 + 8 * DTRACE_STACK_NFRAMES(arg);
-						goto add_rec;
 					case DIF_SUBR_STACK:
-						arg = dt_cg_stack_arg(dtp, knp, DTRACEACT_STACK);
 						kind = DTRACEACT_STACK;
-						size = 8 * arg;
+						goto is_stack;
+					case DIF_SUBR_USTACK:
+						kind = DTRACEACT_USTACK;
+is_stack:
+						arg = dt_cg_stack_arg(dtp, knp, kind);
+						size = DTRACE_STACK_ALLOCSZ(arg);
 						goto add_rec;
 					}
 				} else if (idp->di_kind == DT_IDENT_ACTFUNC) {
diff --git a/libdtrace/dt_dctx.h b/libdtrace/dt_dctx.h
index b50a1395..5bf21345 100644
--- a/libdtrace/dt_dctx.h
+++ b/libdtrace/dt_dctx.h
@@ -128,7 +128,8 @@ typedef struct dt_dctx {
  *	    completed.
  */
 #define DMEM_STACK_SZ(dtp) \
-		(sizeof(uint64_t) * (dtp)->dt_options[DTRACEOPT_MAXFRAMES] + 1)
+		(4 * sizeof(uint32_t) + \
+		 (dtp)->dt_options[DTRACEOPT_MAXFRAMES] * sizeof(uint64_t))
 #define DMEM_TSTR_SZ(dtp) \
 		(DT_TSTRING_SLOTS * DT_TSTRING_SIZE(dtp))
 #define DMEM_STRTOK_SZ(dtp) \
diff --git a/libdtrace/dt_impl.h b/libdtrace/dt_impl.h
index 723aac8b..97ad5e37 100644
--- a/libdtrace/dt_impl.h
+++ b/libdtrace/dt_impl.h
@@ -248,6 +248,9 @@ typedef struct dt_tstring {
 #define DTRACE_STACK_ARG(t, x, y)	((((uint64_t)(y)) << 32) | \
 					 ((t) ? (1UL << 31) : 0) | \
 					 ((x) & INT32_MAX))
+#define DTRACE_STACK_ALLOCSZ(x)		(4 * sizeof(uint32_t) + \
+					 DTRACE_STACK_NFRAMES(x) * \
+						sizeof(uint64_t))
 
 typedef struct dt_dirpath {
 	dt_list_t dir_list;		/* linked-list forward/back pointers */
diff --git a/libdtrace/dt_open.c b/libdtrace/dt_open.c
index ba5463a0..c43cf06d 100644
--- a/libdtrace/dt_open.c
+++ b/libdtrace/dt_open.c
@@ -175,7 +175,7 @@ static const dt_ident_t _dtrace_globals[] = {
 { "ipl", DT_IDENT_SCALAR, 0, DIF_VAR_IPL, DT_ATTR_STABCMN, DT_VERS_1_0,
 	&dt_idops_type, "uint_t" },
 { "jstack", DT_IDENT_ACTFUNC, 0, DT_ACT_JSTACK, DT_ATTR_STABCMN, DT_VERS_1_0,
-	&dt_idops_func, "dt_stack([uint32_t], [uint32_t])" },
+	&dt_idops_func, "dt_stack_t([uint32_t], [uint32_t])" },
 { "link_ntop", DT_IDENT_FUNC, 0, DIF_SUBR_LINK_NTOP, DT_ATTR_STABCMN,
 	DT_VERS_1_5, &dt_idops_func, "string(int, void *)" },
 { "llquantize", DT_IDENT_AGGFUNC, 0, DT_AGG_LLQUANTIZE,
@@ -274,7 +274,7 @@ static const dt_ident_t _dtrace_globals[] = {
 	DT_ATTR_STABCMN, DT_VERS_1_0,
 	&dt_idops_func, "int()" },
 { "stack", DT_IDENT_FUNC, DT_IDFLG_DPTR, DIF_SUBR_STACK, DT_ATTR_STABCMN,
-	DT_VERS_1_0, &dt_idops_func, "dt_stack([uint32_t])" },
+	DT_VERS_1_0, &dt_idops_func, "dt_stack_t([uint32_t])" },
 { "stackdepth", DT_IDENT_SCALAR, 0, DIF_VAR_STACKDEPTH,
 	DT_ATTR_STABCMN, DT_VERS_1_0,
 	&dt_idops_type, "uint32_t" },
@@ -329,7 +329,7 @@ static const dt_ident_t _dtrace_globals[] = {
 { "uregs", DT_IDENT_ARRAY, 0, DIF_VAR_UREGS, DT_ATTR_STABCMN, DT_VERS_1_0,
 	&dt_idops_regs, NULL },
 { "ustack", DT_IDENT_FUNC, DT_IDFLG_DPTR, DIF_SUBR_USTACK, DT_ATTR_STABCMN,
-	DT_VERS_1_0, &dt_idops_func, "dt_stack([uint32_t], [uint32_t])" },
+	DT_VERS_1_0, &dt_idops_func, "dt_stack_t([uint32_t], [uint32_t])" },
 { "ustackdepth", DT_IDENT_SCALAR, 0, DIF_VAR_USTACKDEPTH,
 	DT_ATTR_STABCMN, DT_VERS_1_2,
 	&dt_idops_type, "uint32_t" },
@@ -1058,16 +1058,33 @@ dt_vopen(int version, int flags, int *errp,
 	    "<DYN>", ctf_lookup_by_name(dmp->dm_ctfp, "void"));
 
 	/*
-	 * The stack type is added as a typedef of uint64_t[MAXFRAMES].  The
-	 * final value of MAXFRAMES may be adjusted with the "stackframes"
-	 * option.
+	 * Define:
+	 *   typedef struct dt_stack {
+	 *   	uint32_t	frames;
+	 *	uint32_t	strsz;		// optional string blob size
+	 *	uint32_t	type;		// type of stack (kernel/user)
+	 *	uint32_t	pid;		// process id (or 0 for kernel)
+	 *   	uint64_t	addrs[n];	// stack trace addresses
+	 *   } dt_stack_t;
+	 *
+	 * It is done in two stages because we won't know the size of the addrs
+	 * array until runtime options have been processed.  We add all members
+	 * (except for addrs) here, and then append the addrs array in
+	 * dtrace_init().
 	 */
-	ctr.ctr_contents = ctf_lookup_by_name(dmp->dm_ctfp, "uint64_t");
-	ctr.ctr_index = ctf_lookup_by_name(dmp->dm_ctfp, "long");
-	ctr.ctr_nelems = _dtrace_stackframes;
-
-	dtp->dt_type_stack = ctf_add_typedef(dmp->dm_ctfp, CTF_ADD_ROOT,
-		"dt_stack", ctf_add_array(dmp->dm_ctfp, CTF_ADD_ROOT, &ctr));
+	{
+		ctf_id_t	stid, mbid;
+
+		stid = ctf_add_struct(dmp->dm_ctfp, CTF_ADD_ROOT, "dt_stack");
+		dtp->dt_type_stack = ctf_add_typedef(dmp->dm_ctfp, CTF_ADD_ROOT,
+						     "dt_stack_t", stid);
+
+		mbid = ctf_lookup_by_name(dmp->dm_ctfp, "uint32_t");
+		ctf_add_member(dmp->dm_ctfp, stid, "depth", mbid);
+		ctf_add_member(dmp->dm_ctfp, stid, "strsz", mbid);
+		ctf_add_member(dmp->dm_ctfp, stid, "type", mbid);
+		ctf_add_member(dmp->dm_ctfp, stid, "pid", mbid);
+	}
 
 	dtp->dt_type_symaddr = ctf_add_typedef(dmp->dm_ctfp, CTF_ADD_ROOT,
 	    "_symaddr", ctf_lookup_by_name(dmp->dm_ctfp, "void"));
@@ -1174,6 +1191,27 @@ dtrace_init(dtrace_hdl_t *dtp)
 	int		i;
 	dtrace_optval_t	lockmem = dtp->dt_options[DTRACEOPT_LOCKMEM];
 	struct rlimit	rl;
+	dt_module_t	*dmp = dtp->dt_ddefs;
+	ctf_id_t	stid;
+	ctf_arinfo_t	ctr;
+
+	/*
+	 * Finalize 'struct dt_stack' now that we know the maxframes value.
+	 */
+	stid = ctf_lookup_by_name(dmp->dm_ctfp, "struct dt_stack");
+
+	ctr.ctr_contents = ctf_lookup_by_name(dmp->dm_ctfp, "uint64_t");
+	ctr.ctr_index = ctf_lookup_by_name(dmp->dm_ctfp, "long");
+	ctr.ctr_nelems = (uint_t)dtp->dt_options[DTRACEOPT_MAXFRAMES];
+
+	ctf_add_member(dmp->dm_ctfp, stid, "addrs",
+		       ctf_add_array(dmp->dm_ctfp, CTF_ADD_ROOT, &ctr));
+
+	if (ctf_update(dmp->dm_ctfp) != 0) {
+		dt_dprintf("failed update D container: %s\n",
+		    ctf_errmsg(ctf_errno(dmp->dm_ctfp)));
+		return dt_set_errno(dtp, EDT_CTF);
+	}
 
 	/*
 	 * Initialize the BPF library handling.
diff --git a/libdtrace/dt_parser.c b/libdtrace/dt_parser.c
index 006b4b6e..e71c0985 100644
--- a/libdtrace/dt_parser.c
+++ b/libdtrace/dt_parser.c
@@ -3794,7 +3794,7 @@ dt_cook_op2(dt_node_t *dnp, uint_t idflags)
 		 * described in the ANSI-C spec (see K&R[A7.17]).  We share
 		 * most of this code with the argument list checking code.
 		 */
-		if (!dt_node_is_string(lp)) {
+		if (!dt_node_is_string(lp) && !dt_node_is_stack(lp)) {
 			kind = ctf_type_kind(lp->dn_ctfp,
 			    ctf_type_resolve(lp->dn_ctfp, lp->dn_type));
 
diff --git a/libdtrace/dt_printf.c b/libdtrace/dt_printf.c
index a3e15397..9c5ed4d3 100644
--- a/libdtrace/dt_printf.c
+++ b/libdtrace/dt_printf.c
@@ -449,7 +449,8 @@ dt_print_stack_user(dtrace_hdl_t *dtp, FILE *fp, const char *format,
 	if (depth == 0)
 		return 0;
 
-	tgid = (pid_t)*pc++;
+	tgid = ((uint32_t *)pc)[1];
+	pc++;
 
 	if (format == NULL)
 		format = "%s";
@@ -569,18 +570,41 @@ dt_print_stack_user(dtrace_hdl_t *dtp, FILE *fp, const char *format,
 	return err;
 }
 
-/*ARGSUSED*/
+/*
+ * The data at vaddr is structured as follows:
+ *	uint32_t	depth
+ *	uint32_t	strsz
+ *	uint32_t	type
+ *	uint32_t	pid
+ *	uint64_t	addrs[depth & 0x7fffffff]
+ *
+ * If 'depth' member provides the maximum number of addresses in the stack
+ * trace.
+ * The 'strsz' member provides the size of the optional string blob that is
+ * appended to the stack trace data.
+ * The 'type' member identifies the stack trace as kernel space (0) or
+ * userspace (1).
+ * The 'pid' member provides the userspace process id for userspace stack
+ * traces and otherwise will be 0.
+ * The 'addrs' array provides the stack traces addresses.  If the stack trace
+ * is shorter than 'depth', remaining addresses will be 0 and can be ignored.
+ */
 static int
 pfprint_stack(dtrace_hdl_t *dtp, FILE *fp, const char *format,
 	      const dt_pfargd_t *pfd, const void *vaddr, size_t size,
 	      uint64_t normal, uint64_t sig)
 {
 	int width;
-	const dtrace_recdesc_t *rec = pfd->pfd_rec;
-	caddr_t addr = (caddr_t)vaddr;
-	uint32_t depth = DTRACE_STACK_NFRAMES(rec->dtrd_arg);
+	uint32_t *vals = (uint32_t *)vaddr;
+	uint32_t depth = vals[0];
+	uint32_t strsz = vals[1];
+	uint32_t is_user = vals[2];
+	caddr_t addr = (caddr_t)(vals + (is_user ? 2 : 4));
 	int err = 0;
 
+	if (depth >= dtp->dt_options[DTRACEOPT_MAXFRAMES])
+		return dt_set_errno(dtp, EDT_DSIZE);
+
 	if (depth == 0)
 		return 0;
 
@@ -603,9 +627,9 @@ pfprint_stack(dtrace_hdl_t *dtp, FILE *fp, const char *format,
 	if (dt_printf(dtp, fp, "\n") < 0)
 		return -1;
 
-	if (DTRACE_STACK_IS_USER(rec->dtrd_arg))
+	if (is_user)
 		err = dt_print_stack_user(dtp, fp, format, addr, width, depth,
-					  DTRACE_STACK_STRSIZE(rec->dtrd_arg));
+					  strsz);
 	else
 		err = dt_print_stack_kernel(dtp, fp, format, addr, width, depth);
 
@@ -777,7 +801,7 @@ static const dt_pfconv_t _dtrace_conversions[] = {
 { "hx", "x", "short", pfcheck_xshort, pfprint_uint },
 { "hX", "X", "short", pfcheck_xshort, pfprint_uint },
 { "i", "i", pfproto_xint, pfcheck_dint, pfprint_dint },
-{ "k", "s", "stack", pfcheck_stack, pfprint_stack },
+{ "k", "s", "dt_stack_t", pfcheck_stack, pfprint_stack },
 { "lc", "lc", "int", pfcheck_type, pfprint_sint }, /* a.k.a. wint_t */
 { "ld",	"d", "long", pfcheck_type, pfprint_sint },
 { "li",	"i", "long", pfcheck_type, pfprint_sint },
diff --git a/test/unittest/funcs/stack/err.corrupted-data.d b/test/unittest/funcs/stack/err.corrupted-data.d
new file mode 100644
index 00000000..bf312346
--- /dev/null
+++ b/test/unittest/funcs/stack/err.corrupted-data.d
@@ -0,0 +1,30 @@
+/*
+ * Oracle Linux DTrace.
+ * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
+ * Licensed under the Universal Permissive License v 1.0 as shown at
+ * http://oss.oracle.com/licenses/upl.
+ */
+
+/*
+ * ASSERTION: Corrupted stack data does not cause a crash.
+ */
+
+#pragma D option destructive
+
+BEGIN
+{
+	system("echo write something > /dev/null");
+}
+
+fbt::ksys_write:entry
+{
+	v = stack(5);
+	v.depth = 0x7fffffff;
+	printf("%k", v);
+	exit(0);
+}
+
+ERROR
+{
+	exit(1);
+}
diff --git a/test/unittest/funcs/stack/err.corrupted-data.r b/test/unittest/funcs/stack/err.corrupted-data.r
new file mode 100644
index 00000000..d447a360
--- /dev/null
+++ b/test/unittest/funcs/stack/err.corrupted-data.r
@@ -0,0 +1,6 @@
+                   FUNCTION:NAME
+                          :BEGIN 
+                ksys_write:entry -- @@stderr --
+dtrace: script 'test/unittest/funcs/stack/err.corrupted-data.d' matched 3 probes
+dtrace: allowing destructive actions
+dtrace: processing aborted: Data record has incorrect size
diff --git a/test/unittest/funcs/stack/err.store-to-stack.d b/test/unittest/funcs/stack/err.store-to-stack.d
new file mode 100644
index 00000000..9ce76163
--- /dev/null
+++ b/test/unittest/funcs/stack/err.store-to-stack.d
@@ -0,0 +1,28 @@
+/*
+ * Oracle Linux DTrace.
+ * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
+ * Licensed under the Universal Permissive License v 1.0 as shown at
+ * http://oss.oracle.com/licenses/upl.
+ */
+
+/*
+ * ASSERTION: Assigning a member in stack() is not allowed.
+ */
+
+#pragma D option destructive
+
+BEGIN
+{
+	system("echo write something > /dev/null");
+}
+
+fbt::ksys_write:entry
+{
+	stack(5).depth = 2;
+	exit(0);
+}
+
+ERROR
+{
+	exit(1);
+}
diff --git a/test/unittest/funcs/stack/err.store-to-stack.r b/test/unittest/funcs/stack/err.store-to-stack.r
new file mode 100644
index 00000000..51c14758
--- /dev/null
+++ b/test/unittest/funcs/stack/err.store-to-stack.r
@@ -0,0 +1,2 @@
+-- @@stderr --
+dtrace: failed to compile script test/unittest/funcs/stack/err.store-to-stack.d: line 21: operator = requires modifiable lvalue as an operand
diff --git a/test/unittest/funcs/stack/tst.asgn_dvar.d b/test/unittest/funcs/stack/tst.asgn_dvar.d
new file mode 100644
index 00000000..e347915e
--- /dev/null
+++ b/test/unittest/funcs/stack/tst.asgn_dvar.d
@@ -0,0 +1,30 @@
+/*
+ * Oracle Linux DTrace.
+ * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
+ * Licensed under the Universal Permissive License v 1.0 as shown at
+ * http://oss.oracle.com/licenses/upl.
+ */
+
+/*
+ * ASSERTION: Test assignment of stack() to a dynamic variable (assoc element).
+ */
+
+#pragma D option destructive
+
+BEGIN
+{
+	system("echo write something > /dev/null");
+}
+
+fbt::ksys_write:entry
+{
+	assoc["a"] = stack(5);
+	printf("%k", assoc["a"]);
+
+	exit(0);
+}
+
+ERROR
+{
+	exit(1);
+}
diff --git a/test/unittest/funcs/stack/tst.asgn_dvar.r.p b/test/unittest/funcs/stack/tst.asgn_dvar.r.p
new file mode 100755
index 00000000..f2b99b67
--- /dev/null
+++ b/test/unittest/funcs/stack/tst.asgn_dvar.r.p
@@ -0,0 +1,35 @@
+#!/usr/bin/gawk -f
+
+/ksys_write/ {
+    # check probe
+    if ( $1 != "ksys_write:entry" ) {
+        print "ERROR: expected fun:prb = ksys_write:entry";
+        exit 1;
+    }
+
+    # check stack(3)
+    getline;
+    if (index($1, "`ksys_write+0x") == 0 &&
+        match($1, "`ksys_write$") == 0) {
+        print "ERROR: expected leaf frame to be ksys_write";
+        exit 1;
+    }
+    getline;
+    if (NF == 0) {
+        print "ERROR: missing second frame";
+        exit 1;
+    }
+    getline;
+    if (NF == 0) {
+        print "ERROR: missing third frame";
+        exit 1;
+    }
+    getline;
+    if (NF > 0) {
+        print "ERROR: expected stack(3) to have only three frames";
+        exit 1;
+    }
+
+    print "success";
+    exit(0);
+}
diff --git a/test/unittest/funcs/stack/tst.asgn_gvar.d b/test/unittest/funcs/stack/tst.asgn_gvar.d
new file mode 100644
index 00000000..af925a7d
--- /dev/null
+++ b/test/unittest/funcs/stack/tst.asgn_gvar.d
@@ -0,0 +1,30 @@
+/*
+ * Oracle Linux DTrace.
+ * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
+ * Licensed under the Universal Permissive License v 1.0 as shown at
+ * http://oss.oracle.com/licenses/upl.
+ */
+
+/*
+ * ASSERTION: Test assignment of stack() to a global variable.
+ */
+
+#pragma D option destructive
+
+BEGIN
+{
+	system("echo write something > /dev/null");
+}
+
+fbt::ksys_write:entry
+{
+	v = stack(5);
+	printf("%k", v);
+
+	exit(0);
+}
+
+ERROR
+{
+	exit(1);
+}
diff --git a/test/unittest/funcs/stack/tst.asgn_gvar.r.p b/test/unittest/funcs/stack/tst.asgn_gvar.r.p
new file mode 120000
index 00000000..2ca41971
--- /dev/null
+++ b/test/unittest/funcs/stack/tst.asgn_gvar.r.p
@@ -0,0 +1 @@
+tst.asgn_dvar.r.p
\ No newline at end of file
diff --git a/test/unittest/funcs/stack/tst.asgn_lvar.d b/test/unittest/funcs/stack/tst.asgn_lvar.d
new file mode 100644
index 00000000..02b8cb53
--- /dev/null
+++ b/test/unittest/funcs/stack/tst.asgn_lvar.d
@@ -0,0 +1,30 @@
+/*
+ * Oracle Linux DTrace.
+ * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
+ * Licensed under the Universal Permissive License v 1.0 as shown at
+ * http://oss.oracle.com/licenses/upl.
+ */
+
+/*
+ * ASSERTION: Test assignment of stack() to a local variable.
+ */
+
+#pragma D option destructive
+
+BEGIN
+{
+	system("echo write something > /dev/null");
+}
+
+fbt::ksys_write:entry
+{
+	this->v = stack(5);
+	printf("%k", this->v);
+
+	exit(0);
+}
+
+ERROR
+{
+	exit(1);
+}
diff --git a/test/unittest/funcs/stack/tst.asgn_lvar.r.p b/test/unittest/funcs/stack/tst.asgn_lvar.r.p
new file mode 120000
index 00000000..2ca41971
--- /dev/null
+++ b/test/unittest/funcs/stack/tst.asgn_lvar.r.p
@@ -0,0 +1 @@
+tst.asgn_dvar.r.p
\ No newline at end of file
diff --git a/test/unittest/funcs/stack/tst.asgn_tvar.d b/test/unittest/funcs/stack/tst.asgn_tvar.d
new file mode 100644
index 00000000..ead7282f
--- /dev/null
+++ b/test/unittest/funcs/stack/tst.asgn_tvar.d
@@ -0,0 +1,30 @@
+/*
+ * Oracle Linux DTrace.
+ * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
+ * Licensed under the Universal Permissive License v 1.0 as shown at
+ * http://oss.oracle.com/licenses/upl.
+ */
+
+/*
+ * ASSERTION: Test assignment of stack() to a TLS variable.
+ */
+
+#pragma D option destructive
+
+BEGIN
+{
+	system("echo write something > /dev/null");
+}
+
+fbt::ksys_write:entry
+{
+	self->v = stack(5);
+	printf("%k", self->v);
+
+	exit(0);
+}
+
+ERROR
+{
+	exit(1);
+}
diff --git a/test/unittest/funcs/stack/tst.asgn_tvar.r.p b/test/unittest/funcs/stack/tst.asgn_tvar.r.p
new file mode 120000
index 00000000..2ca41971
--- /dev/null
+++ b/test/unittest/funcs/stack/tst.asgn_tvar.r.p
@@ -0,0 +1 @@
+tst.asgn_dvar.r.p
\ No newline at end of file
diff --git a/test/unittest/funcs/stack/tst.ref_addrs.d b/test/unittest/funcs/stack/tst.ref_addrs.d
new file mode 100644
index 00000000..b096460b
--- /dev/null
+++ b/test/unittest/funcs/stack/tst.ref_addrs.d
@@ -0,0 +1,29 @@
+/*
+ * Oracle Linux DTrace.
+ * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
+ * Licensed under the Universal Permissive License v 1.0 as shown at
+ * http://oss.oracle.com/licenses/upl.
+ */
+
+/*
+ * ASSERTION: Test accessing the 'addrs' array of a kernel stack.
+ */
+
+#pragma D option destructive
+
+BEGIN
+{
+	system("echo write something > /dev/null");
+}
+
+fbt::ksys_write:entry
+{
+	trace("Expecting vmlinux`ksys_write, got ");
+	func(stack(7).addrs[0]);
+	exit(0);
+}
+
+ERROR
+{
+	exit(1);
+}
diff --git a/test/unittest/funcs/stack/tst.ref_addrs.r b/test/unittest/funcs/stack/tst.ref_addrs.r
new file mode 100644
index 00000000..83117e4c
--- /dev/null
+++ b/test/unittest/funcs/stack/tst.ref_addrs.r
@@ -0,0 +1,7 @@
+                   FUNCTION:NAME
+                          :BEGIN 
+                ksys_write:entry   Expecting vmlinux`ksys_write, got   vmlinux`ksys_write                                
+
+-- @@stderr --
+dtrace: script 'test/unittest/funcs/stack/tst.ref_addrs.d' matched 3 probes
+dtrace: allowing destructive actions
diff --git a/test/unittest/funcs/stack/tst.ref_depth.d b/test/unittest/funcs/stack/tst.ref_depth.d
new file mode 100644
index 00000000..37f07d8e
--- /dev/null
+++ b/test/unittest/funcs/stack/tst.ref_depth.d
@@ -0,0 +1,40 @@
+/*
+ * Oracle Linux DTrace.
+ * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
+ * Licensed under the Universal Permissive License v 1.0 as shown at
+ * http://oss.oracle.com/licenses/upl.
+ */
+
+/*
+ * ASSERTION: Test accessing the 'depth' field of a kernel stack.
+ */
+
+#pragma D option destructive
+
+BEGIN
+{
+	system("echo write something > /dev/null");
+}
+
+fbt::ksys_write:entry
+{
+	trace("Expecting 7, got ");
+	trace(stack(7).depth);
+}
+
+fbt::ksys_write:entry
+/stack(7).depth == 7/
+{
+	exit(0);
+}
+
+fbt::ksys_write:entry
+/stack(7).depth != 7/
+{
+	exit(1);
+}
+
+ERROR
+{
+	exit(1);
+}
diff --git a/test/unittest/funcs/stack/tst.ref_depth.r b/test/unittest/funcs/stack/tst.ref_depth.r
new file mode 100644
index 00000000..04eb7382
--- /dev/null
+++ b/test/unittest/funcs/stack/tst.ref_depth.r
@@ -0,0 +1,8 @@
+                   FUNCTION:NAME
+                          :BEGIN 
+                ksys_write:entry   Expecting 7, got                           7
+                ksys_write:entry 
+
+-- @@stderr --
+dtrace: script 'test/unittest/funcs/stack/tst.ref_depth.d' matched 5 probes
+dtrace: allowing destructive actions
diff --git a/test/unittest/funcs/stack/tst.ref_pid.d b/test/unittest/funcs/stack/tst.ref_pid.d
new file mode 100644
index 00000000..c674bcd3
--- /dev/null
+++ b/test/unittest/funcs/stack/tst.ref_pid.d
@@ -0,0 +1,40 @@
+/*
+ * Oracle Linux DTrace.
+ * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
+ * Licensed under the Universal Permissive License v 1.0 as shown at
+ * http://oss.oracle.com/licenses/upl.
+ */
+
+/*
+ * ASSERTION: Test accessing the 'pid' field of s kernel stack yields 0.
+ */
+
+#pragma D option destructive
+
+BEGIN
+{
+	system("echo write something > /dev/null");
+}
+
+fbt::ksys_write:entry
+{
+	trace("Expecting 0, got ");
+	trace(stack(7).pid);
+}
+
+fbt::ksys_write:entry
+/stack(7).pid == 0/
+{
+	exit(0);
+}
+
+fbt::ksys_write:entry
+/stack(7).pid != 0/
+{
+	exit(1);
+}
+
+ERROR
+{
+	exit(1);
+}
diff --git a/test/unittest/funcs/stack/tst.ref_pid.r b/test/unittest/funcs/stack/tst.ref_pid.r
new file mode 100644
index 00000000..223f5ac2
--- /dev/null
+++ b/test/unittest/funcs/stack/tst.ref_pid.r
@@ -0,0 +1,8 @@
+                   FUNCTION:NAME
+                          :BEGIN 
+                ksys_write:entry   Expecting 0, got                           0
+                ksys_write:entry 
+
+-- @@stderr --
+dtrace: script 'test/unittest/funcs/stack/tst.ref_pid.d' matched 5 probes
+dtrace: allowing destructive actions
diff --git a/test/unittest/funcs/stack/tst.ref_strsz.d b/test/unittest/funcs/stack/tst.ref_strsz.d
new file mode 100644
index 00000000..19aae30d
--- /dev/null
+++ b/test/unittest/funcs/stack/tst.ref_strsz.d
@@ -0,0 +1,40 @@
+/*
+ * Oracle Linux DTrace.
+ * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
+ * Licensed under the Universal Permissive License v 1.0 as shown at
+ * http://oss.oracle.com/licenses/upl.
+ */
+
+/*
+ * ASSERTION: Test accessing the 'strsz' field of s kernel stack yields 0.
+ */
+
+#pragma D option destructive
+
+BEGIN
+{
+	system("echo write something > /dev/null");
+}
+
+fbt::ksys_write:entry
+{
+	trace("Expecting 0, got ");
+	trace(stack(7).strsz);
+}
+
+fbt::ksys_write:entry
+/stack(7).strsz == 0/
+{
+	exit(0);
+}
+
+fbt::ksys_write:entry
+/stack(7).strsz != 0/
+{
+	exit(1);
+}
+
+ERROR
+{
+	exit(1);
+}
diff --git a/test/unittest/funcs/stack/tst.ref_strsz.r b/test/unittest/funcs/stack/tst.ref_strsz.r
new file mode 100644
index 00000000..5811faf4
--- /dev/null
+++ b/test/unittest/funcs/stack/tst.ref_strsz.r
@@ -0,0 +1,8 @@
+                   FUNCTION:NAME
+                          :BEGIN 
+                ksys_write:entry   Expecting 0, got                           0
+                ksys_write:entry 
+
+-- @@stderr --
+dtrace: script 'test/unittest/funcs/stack/tst.ref_strsz.d' matched 5 probes
+dtrace: allowing destructive actions
diff --git a/test/unittest/funcs/stack/tst.ref_type.d b/test/unittest/funcs/stack/tst.ref_type.d
new file mode 100644
index 00000000..38a6db03
--- /dev/null
+++ b/test/unittest/funcs/stack/tst.ref_type.d
@@ -0,0 +1,40 @@
+/*
+ * Oracle Linux DTrace.
+ * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
+ * Licensed under the Universal Permissive License v 1.0 as shown at
+ * http://oss.oracle.com/licenses/upl.
+ */
+
+/*
+ * ASSERTION: Test accessing the 'type' field of a kernel stack.
+ */
+
+#pragma D option destructive
+
+BEGIN
+{
+	system("echo write something > /dev/null");
+}
+
+fbt::ksys_write:entry
+{
+	trace("Expecting 0, got ");
+	trace(stack(7).type);
+}
+
+fbt::ksys_write:entry
+/stack(7).type == 0/
+{
+	exit(0);
+}
+
+fbt::ksys_write:entry
+/stack(7).type != 0/
+{
+	exit(1);
+}
+
+ERROR
+{
+	exit(1);
+}
diff --git a/test/unittest/funcs/stack/tst.ref_type.r b/test/unittest/funcs/stack/tst.ref_type.r
new file mode 100644
index 00000000..f68b6ba5
--- /dev/null
+++ b/test/unittest/funcs/stack/tst.ref_type.r
@@ -0,0 +1,8 @@
+                   FUNCTION:NAME
+                          :BEGIN 
+                ksys_write:entry   Expecting 0, got                           0
+                ksys_write:entry 
+
+-- @@stderr --
+dtrace: script 'test/unittest/funcs/stack/tst.ref_type.d' matched 5 probes
+dtrace: allowing destructive actions
diff --git a/test/unittest/funcs/stack/tst.store.d b/test/unittest/funcs/stack/tst.store.d
new file mode 100644
index 00000000..cf31fc68
--- /dev/null
+++ b/test/unittest/funcs/stack/tst.store.d
@@ -0,0 +1,30 @@
+/*
+ * Oracle Linux DTrace.
+ * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
+ * Licensed under the Universal Permissive License v 1.0 as shown at
+ * http://oss.oracle.com/licenses/upl.
+ */
+
+/*
+ * ASSERTION: It is possible to store to members of dt_stack_t.
+ */
+
+#pragma D option destructive
+
+BEGIN
+{
+	system("echo write something > /dev/null");
+}
+
+fbt::ksys_write:entry
+{
+	v = stack(5);
+	v.depth = 2;
+	printf("%k", v);
+	exit(0);
+}
+
+ERROR
+{
+	exit(1);
+}
diff --git a/test/unittest/funcs/ustack/err.corrupted-data.d b/test/unittest/funcs/ustack/err.corrupted-data.d
new file mode 100644
index 00000000..c4525aaf
--- /dev/null
+++ b/test/unittest/funcs/ustack/err.corrupted-data.d
@@ -0,0 +1,25 @@
+/*
+ * Oracle Linux DTrace.
+ * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
+ * Licensed under the Universal Permissive License v 1.0 as shown at
+ * http://oss.oracle.com/licenses/upl.
+ */
+
+/*
+ * ASSERTION: Corrupted stack data does not cause a crash.
+ */
+
+/* @@trigger: ustack-tst-basic */
+
+pid$target:a.out:myfunc_z:entry
+{
+	v = ustack(5);
+	v.depth = 0x7fffffff;
+	printf("%k", v);
+	exit(0);
+}
+
+ERROR
+{
+	exit(1);
+}
diff --git a/test/unittest/funcs/ustack/err.corrupted-data.r b/test/unittest/funcs/ustack/err.corrupted-data.r
new file mode 100644
index 00000000..8ad0244f
--- /dev/null
+++ b/test/unittest/funcs/ustack/err.corrupted-data.r
@@ -0,0 +1,4 @@
+                   FUNCTION:NAME
+                  myfunc_z:entry -- @@stderr --
+dtrace: script 'test/unittest/funcs/ustack/err.corrupted-data.d' matched 2 probes
+dtrace: processing aborted: Data record has incorrect size
diff --git a/test/unittest/funcs/ustack/err.store-to-stack.d b/test/unittest/funcs/ustack/err.store-to-stack.d
new file mode 100644
index 00000000..106c0cd9
--- /dev/null
+++ b/test/unittest/funcs/ustack/err.store-to-stack.d
@@ -0,0 +1,23 @@
+/*
+ * Oracle Linux DTrace.
+ * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
+ * Licensed under the Universal Permissive License v 1.0 as shown at
+ * http://oss.oracle.com/licenses/upl.
+ */
+
+/*
+ * ASSERTION: Assigning a member in ustack() is not allowed.
+ */
+
+/* @@trigger: ustack-tst-basic */
+
+pid$target:a.out:myfunc_z:entry
+{
+	ustack(5).depth = 2;
+	exit(0);
+}
+
+ERROR
+{
+	exit(1);
+}
diff --git a/test/unittest/funcs/ustack/err.store-to-stack.r b/test/unittest/funcs/ustack/err.store-to-stack.r
new file mode 100644
index 00000000..3a0aeba5
--- /dev/null
+++ b/test/unittest/funcs/ustack/err.store-to-stack.r
@@ -0,0 +1,2 @@
+-- @@stderr --
+dtrace: failed to compile script test/unittest/funcs/ustack/err.store-to-stack.d: line 16: operator = requires modifiable lvalue as an operand
diff --git a/test/unittest/funcs/ustack/tst.asgn_dvar.d b/test/unittest/funcs/ustack/tst.asgn_dvar.d
new file mode 100644
index 00000000..da9e61ff
--- /dev/null
+++ b/test/unittest/funcs/ustack/tst.asgn_dvar.d
@@ -0,0 +1,25 @@
+/*
+ * Oracle Linux DTrace.
+ * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
+ * Licensed under the Universal Permissive License v 1.0 as shown at
+ * http://oss.oracle.com/licenses/upl.
+ */
+
+/*
+ * ASSERTION: Test assignment of ustack() to a dynamic variable (assoc element).
+ */
+
+/* @@trigger: ustack-tst-basic */
+
+pid$target:a.out:myfunc_z:entry
+{
+	assoc["a"] = ustack(5);
+	printf("%k", assoc["a"]);
+
+	exit(0);
+}
+
+ERROR
+{
+	exit(1);
+}
diff --git a/test/unittest/funcs/ustack/tst.asgn_dvar.r.p b/test/unittest/funcs/ustack/tst.asgn_dvar.r.p
new file mode 100755
index 00000000..f2b99b67
--- /dev/null
+++ b/test/unittest/funcs/ustack/tst.asgn_dvar.r.p
@@ -0,0 +1,35 @@
+#!/usr/bin/gawk -f
+
+/ksys_write/ {
+    # check probe
+    if ( $1 != "ksys_write:entry" ) {
+        print "ERROR: expected fun:prb = ksys_write:entry";
+        exit 1;
+    }
+
+    # check stack(3)
+    getline;
+    if (index($1, "`ksys_write+0x") == 0 &&
+        match($1, "`ksys_write$") == 0) {
+        print "ERROR: expected leaf frame to be ksys_write";
+        exit 1;
+    }
+    getline;
+    if (NF == 0) {
+        print "ERROR: missing second frame";
+        exit 1;
+    }
+    getline;
+    if (NF == 0) {
+        print "ERROR: missing third frame";
+        exit 1;
+    }
+    getline;
+    if (NF > 0) {
+        print "ERROR: expected stack(3) to have only three frames";
+        exit 1;
+    }
+
+    print "success";
+    exit(0);
+}
diff --git a/test/unittest/funcs/ustack/tst.asgn_gvar.d b/test/unittest/funcs/ustack/tst.asgn_gvar.d
new file mode 100644
index 00000000..02f53dfb
--- /dev/null
+++ b/test/unittest/funcs/ustack/tst.asgn_gvar.d
@@ -0,0 +1,25 @@
+/*
+ * Oracle Linux DTrace.
+ * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
+ * Licensed under the Universal Permissive License v 1.0 as shown at
+ * http://oss.oracle.com/licenses/upl.
+ */
+
+/*
+ * ASSERTION: Test assignment of ustack() to a global variable.
+ */
+
+/* @@trigger: ustack-tst-basic */
+
+pid$target:a.out:myfunc_z:entry
+{
+	v = ustack(5);
+	printf("%k", v);
+
+	exit(0);
+}
+
+ERROR
+{
+	exit(1);
+}
diff --git a/test/unittest/funcs/ustack/tst.asgn_gvar.r.p b/test/unittest/funcs/ustack/tst.asgn_gvar.r.p
new file mode 120000
index 00000000..2ca41971
--- /dev/null
+++ b/test/unittest/funcs/ustack/tst.asgn_gvar.r.p
@@ -0,0 +1 @@
+tst.asgn_dvar.r.p
\ No newline at end of file
diff --git a/test/unittest/funcs/ustack/tst.asgn_lvar.d b/test/unittest/funcs/ustack/tst.asgn_lvar.d
new file mode 100644
index 00000000..24acde52
--- /dev/null
+++ b/test/unittest/funcs/ustack/tst.asgn_lvar.d
@@ -0,0 +1,25 @@
+/*
+ * Oracle Linux DTrace.
+ * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
+ * Licensed under the Universal Permissive License v 1.0 as shown at
+ * http://oss.oracle.com/licenses/upl.
+ */
+
+/*
+ * ASSERTION: Test assignment of ustack() to a local variable.
+ */
+
+/* @@trigger: ustack-tst-basic */
+
+pid$target:a.out:myfunc_z:entry
+{
+	this->v = ustack(5);
+	printf("%k", this->v);
+
+	exit(0);
+}
+
+ERROR
+{
+	exit(1);
+}
diff --git a/test/unittest/funcs/ustack/tst.asgn_lvar.r.p b/test/unittest/funcs/ustack/tst.asgn_lvar.r.p
new file mode 120000
index 00000000..2ca41971
--- /dev/null
+++ b/test/unittest/funcs/ustack/tst.asgn_lvar.r.p
@@ -0,0 +1 @@
+tst.asgn_dvar.r.p
\ No newline at end of file
diff --git a/test/unittest/funcs/ustack/tst.asgn_tvar.d b/test/unittest/funcs/ustack/tst.asgn_tvar.d
new file mode 100644
index 00000000..7a5034b5
--- /dev/null
+++ b/test/unittest/funcs/ustack/tst.asgn_tvar.d
@@ -0,0 +1,25 @@
+/*
+ * Oracle Linux DTrace.
+ * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
+ * Licensed under the Universal Permissive License v 1.0 as shown at
+ * http://oss.oracle.com/licenses/upl.
+ */
+
+/*
+ * ASSERTION: Test assignment of ustack() to a TLS variable.
+ */
+
+/* @@trigger: ustack-tst-basic */
+
+pid$target:a.out:myfunc_z:entry
+{
+	self->v = ustack(5);
+	printf("%k", self->v);
+
+	exit(0);
+}
+
+ERROR
+{
+	exit(1);
+}
diff --git a/test/unittest/funcs/ustack/tst.asgn_tvar.r.p b/test/unittest/funcs/ustack/tst.asgn_tvar.r.p
new file mode 120000
index 00000000..2ca41971
--- /dev/null
+++ b/test/unittest/funcs/ustack/tst.asgn_tvar.r.p
@@ -0,0 +1 @@
+tst.asgn_dvar.r.p
\ No newline at end of file
diff --git a/test/unittest/funcs/ustack/tst.ref_addrs.d b/test/unittest/funcs/ustack/tst.ref_addrs.d
new file mode 100644
index 00000000..57b99efd
--- /dev/null
+++ b/test/unittest/funcs/ustack/tst.ref_addrs.d
@@ -0,0 +1,24 @@
+/*
+ * Oracle Linux DTrace.
+ * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
+ * Licensed under the Universal Permissive License v 1.0 as shown at
+ * http://oss.oracle.com/licenses/upl.
+ */
+
+/*
+ * ASSERTION: Test accessing the 'addrs' array of a userspace stack.
+ */
+
+/* @@trigger: ustack-tst-basic */
+
+pid$target:a.out:myfunc_z:entry
+{
+	trace("Expecting vmlinux`ksys_write, got ");
+	ufunc(ustack(7).addrs[0]);
+	exit(0);
+}
+
+ERROR
+{
+	exit(1);
+}
diff --git a/test/unittest/funcs/ustack/tst.ref_addrs.r b/test/unittest/funcs/ustack/tst.ref_addrs.r
new file mode 100644
index 00000000..c9876067
--- /dev/null
+++ b/test/unittest/funcs/ustack/tst.ref_addrs.r
@@ -0,0 +1,5 @@
+                   FUNCTION:NAME
+                  myfunc_z:entry   Expecting vmlinux`ksys_write, got   ustack-tst-basic`myfunc_z                         
+
+-- @@stderr --
+dtrace: script 'test/unittest/funcs/ustack/tst.ref_addrs.d' matched 2 probes
diff --git a/test/unittest/funcs/ustack/tst.ref_depth.d b/test/unittest/funcs/ustack/tst.ref_depth.d
new file mode 100644
index 00000000..d847736a
--- /dev/null
+++ b/test/unittest/funcs/ustack/tst.ref_depth.d
@@ -0,0 +1,35 @@
+/*
+ * Oracle Linux DTrace.
+ * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
+ * Licensed under the Universal Permissive License v 1.0 as shown at
+ * http://oss.oracle.com/licenses/upl.
+ */
+
+/*
+ * ASSERTION: Test accessing the 'depth' field of a userspace stack.
+ */
+
+/* @@trigger: ustack-tst-basic */
+
+pid$target:a.out:myfunc_z:entry
+{
+	trace("Expecting 7, got ");
+	trace(ustack(7).depth);
+}
+
+pid$target:a.out:myfunc_z:entry
+/ustack(7).depth == 7/
+{
+	exit(0);
+}
+
+pid$target:a.out:myfunc_z:entry
+/ustack(7).depth != 7/
+{
+	exit(1);
+}
+
+ERROR
+{
+	exit(1);
+}
diff --git a/test/unittest/funcs/ustack/tst.ref_depth.r b/test/unittest/funcs/ustack/tst.ref_depth.r
new file mode 100644
index 00000000..de6bf067
--- /dev/null
+++ b/test/unittest/funcs/ustack/tst.ref_depth.r
@@ -0,0 +1,6 @@
+                   FUNCTION:NAME
+                  myfunc_z:entry   Expecting 7, got                           7
+                  myfunc_z:entry 
+
+-- @@stderr --
+dtrace: script 'test/unittest/funcs/ustack/tst.ref_depth.d' matched 4 probes
diff --git a/test/unittest/funcs/ustack/tst.ref_pid.d b/test/unittest/funcs/ustack/tst.ref_pid.d
new file mode 100644
index 00000000..6ed62f12
--- /dev/null
+++ b/test/unittest/funcs/ustack/tst.ref_pid.d
@@ -0,0 +1,37 @@
+/*
+ * Oracle Linux DTrace.
+ * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
+ * Licensed under the Universal Permissive License v 1.0 as shown at
+ * http://oss.oracle.com/licenses/upl.
+ */
+
+/*
+ * ASSERTION: Test accessing the 'pid' field of s userspace stack.
+ */
+
+/* @@trigger: ustack-tst-basic */
+
+pid$target:a.out:myfunc_z:entry
+{
+	trace("Expecting ");
+	trace($target);
+	trace(", got ");
+	trace(ustack(7).pid);
+}
+
+pid$target:a.out:myfunc_z:entry
+/ustack(7).pid == $target/
+{
+	exit(0);
+}
+
+pid$target:a.out:myfunc_z:entry
+/ustack(7).pid != $target/
+{
+	exit(1);
+}
+
+ERROR
+{
+	exit(1);
+}
diff --git a/test/unittest/funcs/ustack/tst.ref_strsz.d b/test/unittest/funcs/ustack/tst.ref_strsz.d
new file mode 100644
index 00000000..b78a00c0
--- /dev/null
+++ b/test/unittest/funcs/ustack/tst.ref_strsz.d
@@ -0,0 +1,35 @@
+/*
+ * Oracle Linux DTrace.
+ * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
+ * Licensed under the Universal Permissive License v 1.0 as shown at
+ * http://oss.oracle.com/licenses/upl.
+ */
+
+/*
+ * ASSERTION: Test accessing the 'strsz' field of s userspace stack.
+ */
+
+/* @@trigger: ustack-tst-basic */
+
+pid$target:a.out:myfunc_z:entry
+{
+	trace("Expecting 42, got ");
+	trace(ustack(7, 42).strsz);
+}
+
+pid$target:a.out:myfunc_z:entry
+/ustack(7, 42).strsz == 42/
+{
+	exit(0);
+}
+
+pid$target:a.out:myfunc_z:entry
+/ustack(7, 42).strsz != 42/
+{
+	exit(1);
+}
+
+ERROR
+{
+	exit(1);
+}
diff --git a/test/unittest/funcs/ustack/tst.ref_strsz.r b/test/unittest/funcs/ustack/tst.ref_strsz.r
new file mode 100644
index 00000000..840f7e21
--- /dev/null
+++ b/test/unittest/funcs/ustack/tst.ref_strsz.r
@@ -0,0 +1,6 @@
+                   FUNCTION:NAME
+                  myfunc_z:entry   Expecting 42, got                         42
+                  myfunc_z:entry 
+
+-- @@stderr --
+dtrace: script 'test/unittest/funcs/ustack/tst.ref_strsz.d' matched 4 probes
diff --git a/test/unittest/funcs/ustack/tst.ref_type.d b/test/unittest/funcs/ustack/tst.ref_type.d
new file mode 100644
index 00000000..77f815a4
--- /dev/null
+++ b/test/unittest/funcs/ustack/tst.ref_type.d
@@ -0,0 +1,35 @@
+/*
+ * Oracle Linux DTrace.
+ * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
+ * Licensed under the Universal Permissive License v 1.0 as shown at
+ * http://oss.oracle.com/licenses/upl.
+ */
+
+/*
+ * ASSERTION: Test accessing the 'type' field of a userspace stack.
+ */
+
+/* @@trigger: ustack-tst-basic */
+
+pid$target:a.out:myfunc_z:entry
+{
+	trace("Expecting 1, got ");
+	trace(ustack(7).type);
+}
+
+pid$target:a.out:myfunc_z:entry
+/ustack(7).type == 1/
+{
+	exit(0);
+}
+
+pid$target:a.out:myfunc_z:entry
+/ustack(7).type != 0/
+{
+	exit(1);
+}
+
+ERROR
+{
+	exit(1);
+}
diff --git a/test/unittest/funcs/ustack/tst.ref_type.r b/test/unittest/funcs/ustack/tst.ref_type.r
new file mode 100644
index 00000000..61075e25
--- /dev/null
+++ b/test/unittest/funcs/ustack/tst.ref_type.r
@@ -0,0 +1,6 @@
+                   FUNCTION:NAME
+                  myfunc_z:entry   Expecting 1, got                           1
+                  myfunc_z:entry 
+
+-- @@stderr --
+dtrace: script 'test/unittest/funcs/ustack/tst.ref_type.d' matched 4 probes
diff --git a/test/unittest/funcs/ustack/tst.store.d b/test/unittest/funcs/ustack/tst.store.d
new file mode 100644
index 00000000..6a9f7c94
--- /dev/null
+++ b/test/unittest/funcs/ustack/tst.store.d
@@ -0,0 +1,25 @@
+/*
+ * Oracle Linux DTrace.
+ * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
+ * Licensed under the Universal Permissive License v 1.0 as shown at
+ * http://oss.oracle.com/licenses/upl.
+ */
+
+/*
+ * ASSERTION: It is possible to store to members of dt_stack_t.
+ */
+
+/* @@trigger: ustack-tst-basic */
+
+pid$target:a.out:myfunc_z:entry
+{
+	v = ustack(5);
+	v.depth = 2;
+	printf("%k", v);
+	exit(0);
+}
+
+ERROR
+{
+	exit(1);
+}
diff --git a/test/unittest/printf/tst.stack.d b/test/unittest/printf/tst.stack.d
new file mode 100644
index 00000000..db943c29
--- /dev/null
+++ b/test/unittest/printf/tst.stack.d
@@ -0,0 +1,33 @@
+/*
+ * Oracle Linux DTrace.
+ * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
+ * Licensed under the Universal Permissive License v 1.0 as shown at
+ * http://oss.oracle.com/licenses/upl.
+ */
+
+/*
+ * ASSERTION: Test printf with %k and a stack argument.
+ *
+ * SECTION: Output Formatting/printf()
+ */
+
+#pragma D option destructive
+
+BEGIN
+{
+	system("echo write something > /dev/null");
+}
+
+fbt::ksys_write:entry
+{
+	printf("%k", stack(1));
+	printf("%k", stack(2));
+	printf("%k", stack(3));
+	printf("%k", stack());
+	exit(0);
+}
+
+ERROR
+{
+	exit(1);
+}
diff --git a/test/unittest/printf/tst.stack.r b/test/unittest/printf/tst.stack.r
new file mode 100644
index 00000000..2e9ba477
--- /dev/null
+++ b/test/unittest/printf/tst.stack.r
@@ -0,0 +1 @@
+success
diff --git a/test/unittest/printf/tst.stack.r.p b/test/unittest/printf/tst.stack.r.p
new file mode 100755
index 00000000..895f96f5
--- /dev/null
+++ b/test/unittest/printf/tst.stack.r.p
@@ -0,0 +1,75 @@
+#!/usr/bin/gawk -f
+
+/ksys_write/ {
+    # check probe
+    if ( $1 != "ksys_write:entry" ) {
+        print "ERROR: expected fun:prb = ksys_write:entry";
+        exit 1;
+    }
+
+    # check stack(1)
+    getline;
+    if (index($1, "`ksys_write+0x") == 0 &&
+        match($1, "`ksys_write$") == 0) {
+        print "ERROR: expected leaf frame to be ksys_write";
+        exit 1;
+    }
+    FRAME1 = $1;
+    getline;
+    if (NF > 0) {
+        print "ERROR: expected stack(1) to have only one frame";
+        exit 1;
+    }
+
+    # check stack(2)
+    getline;
+    if ($1 != FRAME1) {
+        print "ERROR: stack(2) leaf frame looks wrong";
+        exit 1;
+    }
+    getline;
+    FRAME2 = $1;
+    getline;
+    if (NF > 0) {
+        print "ERROR: expected stack(2) to have only two frames";
+        exit 1;
+    }
+
+    # check stack(3)
+    getline;
+    if ($1 != FRAME1) {
+        print "ERROR: stack(3) leaf frame looks wrong";
+        exit 1;
+    }
+    getline;
+    if ($1 != FRAME2) {
+        print "ERROR: stack(3) frame2 looks wrong";
+        exit 1;
+    }
+    getline;
+    FRAME3 = $1;
+    getline;
+    if (NF > 0) {
+        print "ERROR: expected stack(3) to have only three frames";
+        exit 1;
+    }
+
+    # check stack()
+    getline;
+    if ($1 != FRAME1) {
+        print "ERROR: stack() leaf frame looks wrong";
+        exit 1;
+    }
+    getline;
+    if ($1 != FRAME2) {
+        print "ERROR: stack() frame2 looks wrong";
+        exit 1;
+    }
+    getline;
+    if ($1 != FRAME3) {
+        print "ERROR: stack() frame3 looks wrong";
+        exit 1;
+    }
+    print "success";
+    exit(0);
+}
diff --git a/test/unittest/printf/tst.ustack25_pid.d b/test/unittest/printf/tst.ustack25_pid.d
new file mode 100644
index 00000000..9ae3a7b8
--- /dev/null
+++ b/test/unittest/printf/tst.ustack25_pid.d
@@ -0,0 +1,21 @@
+/*
+ * Oracle Linux DTrace.
+ * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
+ * Licensed under the Universal Permissive License v 1.0 as shown at
+ * http://oss.oracle.com/licenses/upl.
+ */
+
+/* @@trigger: ustack-tst-basic */
+
+#pragma D option quiet
+
+pid$target:a.out:myfunc_z:entry
+{
+	printf("%k", ustack(25));
+	exit(0);
+}
+
+ERROR
+{
+	exit(1);
+}
diff --git a/test/unittest/printf/tst.ustack25_pid.r b/test/unittest/printf/tst.ustack25_pid.r
new file mode 100644
index 00000000..e7732fb8
--- /dev/null
+++ b/test/unittest/printf/tst.ustack25_pid.r
@@ -0,0 +1,28 @@
+
+              ustack-tst-basic`myfunc_z
+              ustack-tst-basic`myfunc_y+{ptr}
+              ustack-tst-basic`myfunc_x+{ptr}
+              ustack-tst-basic`myfunc_w+{ptr}
+              ustack-tst-basic`myfunc_v+{ptr}
+              ustack-tst-basic`myfunc_u+{ptr}
+              ustack-tst-basic`myfunc_t+{ptr}
+              ustack-tst-basic`myfunc_s+{ptr}
+              ustack-tst-basic`myfunc_r+{ptr}
+              ustack-tst-basic`myfunc_q+{ptr}
+              ustack-tst-basic`myfunc_p+{ptr}
+              ustack-tst-basic`myfunc_o+{ptr}
+              ustack-tst-basic`myfunc_n+{ptr}
+              ustack-tst-basic`myfunc_m+{ptr}
+              ustack-tst-basic`myfunc_l+{ptr}
+              ustack-tst-basic`myfunc_k+{ptr}
+              ustack-tst-basic`myfunc_j+{ptr}
+              ustack-tst-basic`myfunc_i+{ptr}
+              ustack-tst-basic`myfunc_h+{ptr}
+              ustack-tst-basic`myfunc_g+{ptr}
+              ustack-tst-basic`myfunc_f+{ptr}
+              ustack-tst-basic`myfunc_e+{ptr}
+              ustack-tst-basic`myfunc_d+{ptr}
+              ustack-tst-basic`myfunc_c+{ptr}
+              ustack-tst-basic`myfunc_b+{ptr}
+              ustack-tst-basic`myfunc_a+{ptr}
+
diff --git a/test/unittest/printf/tst.ustack25_pid.r.p b/test/unittest/printf/tst.ustack25_pid.r.p
new file mode 100755
index 00000000..c0110e00
--- /dev/null
+++ b/test/unittest/printf/tst.ustack25_pid.r.p
@@ -0,0 +1,37 @@
+#!/bin/bash
+
+# A pid entry probe places a uprobe on the first instruction of a function.
+# Unfortunately, this is so early in the function preamble that the function
+# frame pointer has not yet been established and the actual caller of the
+# traced function is missed.
+#
+# In Linux 6.11, x86-specific heuristics are introduced to fix this problem.
+# See commit cfa7f3d
+# ("perf,x86: avoid missing caller address in stack traces captured in uprobe")
+# for both a description of the problem and an explanation of the heuristics.
+#
+# Add post processing to these test results to allow for both cases:
+# caller frame is missing or not missing.
+
+missing_caller=1
+if [ $(uname -m) == "x86_64" ]; then
+        read MAJOR MINOR <<< `uname -r | grep -Eo '^[0-9]+\.[0-9]+' | tr '.' ' '`
+
+        if [ $MAJOR -ge 6 ]; then
+                if [ $MAJOR -gt 6 -o $MINOR -ge 11 ]; then
+                        missing_caller=0
+                fi
+        fi
+fi
+
+if [ $missing_caller -eq 1 ]; then
+        # Add the missing caller function after the current function.
+        awk '{ print }
+             /myfunc_z/ { print "              ustack-tst-basic`myfunc_y+{ptr}" }'
+else
+        # The .r results file has an extra frame at the end in case
+        # the caller frame is missing and the 25-frame limit goes
+        # "too far."  If the caller is not missing, fake that extra frame.
+        awk '{ print }
+             /myfunc_b/ { print "              ustack-tst-basic`myfunc_a+{ptr}" }'
+fi
-- 
2.43.5




More information about the DTrace-devel mailing list