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

Eugene Loh <[email protected]>
Newsgroups dev.linux.lists.dtrace
Message-ID <[email protected]>
Nice feature.  Tour de force implementation.

Reviewed-by: Eugene Loh <[email protected]>

On 10/7/25 01:35, Kris Van Hees via DTrace-devel wrote:
> 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	is_user	  // > 0 if userspace stack trace
> 		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 <[email protected]>
> ---
>   libdtrace/dt_cg.c                             | 84 +++++++++++--------
>   libdtrace/dt_dctx.h                           |  3 +-
>   libdtrace/dt_impl.h                           | 13 ---
>   libdtrace/dt_open.c                           | 62 +++++++++++---
>   libdtrace/dt_parser.c                         |  2 +-
>   libdtrace/dt_printf.c                         | 43 +++++++---
>   .../unittest/funcs/stack/err.corrupted-data.d | 25 ++++++
>   .../unittest/funcs/stack/err.corrupted-data.r |  4 +
>   .../unittest/funcs/stack/err.store-to-stack.d | 23 +++++
>   .../unittest/funcs/stack/err.store-to-stack.r |  2 +
>   test/unittest/funcs/stack/tst.asgn_dvar.d     | 25 ++++++
>   test/unittest/funcs/stack/tst.asgn_dvar.r     |  1 +
>   test/unittest/funcs/stack/tst.asgn_dvar.r.p   | 35 ++++++++
>   test/unittest/funcs/stack/tst.asgn_gvar.d     | 25 ++++++
>   test/unittest/funcs/stack/tst.asgn_gvar.r     |  1 +
>   test/unittest/funcs/stack/tst.asgn_gvar.r.p   |  1 +
>   test/unittest/funcs/stack/tst.asgn_lvar.d     | 25 ++++++
>   test/unittest/funcs/stack/tst.asgn_lvar.r     |  1 +
>   test/unittest/funcs/stack/tst.asgn_lvar.r.p   |  1 +
>   test/unittest/funcs/stack/tst.asgn_tvar.d     | 25 ++++++
>   test/unittest/funcs/stack/tst.asgn_tvar.r     |  1 +
>   test/unittest/funcs/stack/tst.asgn_tvar.r.p   |  1 +
>   test/unittest/funcs/stack/tst.ref_addrs.d     | 24 ++++++
>   test/unittest/funcs/stack/tst.ref_addrs.r     |  5 ++
>   test/unittest/funcs/stack/tst.ref_depth.d     | 35 ++++++++
>   test/unittest/funcs/stack/tst.ref_depth.r     |  6 ++
>   test/unittest/funcs/stack/tst.ref_is_user.d   | 35 ++++++++
>   test/unittest/funcs/stack/tst.ref_is_user.r   |  6 ++
>   test/unittest/funcs/stack/tst.ref_pid.d       | 35 ++++++++
>   test/unittest/funcs/stack/tst.ref_pid.r       |  6 ++
>   test/unittest/funcs/stack/tst.ref_strsz.d     | 35 ++++++++
>   test/unittest/funcs/stack/tst.ref_strsz.r     |  6 ++
>   test/unittest/funcs/stack/tst.store.d         | 28 +++++++
>   test/unittest/funcs/stack/tst.store.r         |  1 +
>   test/unittest/funcs/stack/tst.store.r.p       | 63 ++++++++++++++
>   .../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    |  1 +
>   test/unittest/funcs/ustack/tst.asgn_dvar.r.p  | 60 +++++++++++++
>   test/unittest/funcs/ustack/tst.asgn_gvar.d    | 25 ++++++
>   test/unittest/funcs/ustack/tst.asgn_gvar.r    |  1 +
>   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    |  1 +
>   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    |  1 +
>   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_is_user.d  | 35 ++++++++
>   test/unittest/funcs/ustack/tst.ref_is_user.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.store.d        | 28 +++++++
>   test/unittest/funcs/ustack/tst.store.r        |  1 +
>   test/unittest/funcs/ustack/tst.store.r.p      | 63 ++++++++++++++
>   .../printa/err.D_PRINTF_ARG_TYPE.stack.r      |  2 +-
>   .../printa/err.D_PRINTF_ARG_TYPE.ustack.r     |  2 +-
>   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 ++++++++
>   71 files changed, 1313 insertions(+), 76 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 100644 test/unittest/funcs/stack/tst.asgn_dvar.r
>   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
>   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
>   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
>   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_is_user.d
>   create mode 100644 test/unittest/funcs/stack/tst.ref_is_user.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.store.d
>   create mode 100644 test/unittest/funcs/stack/tst.store.r
>   create mode 100755 test/unittest/funcs/stack/tst.store.r.p
>   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 100644 test/unittest/funcs/ustack/tst.asgn_dvar.r
>   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
>   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
>   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
>   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_is_user.d
>   create mode 100644 test/unittest/funcs/ustack/tst.ref_is_user.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.store.d
>   create mode 100644 test/unittest/funcs/ustack/tst.store.r
>   create mode 100755 test/unittest/funcs/ustack/tst.store.r.p
>   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..ec4e3123 100644
> --- a/libdtrace/dt_cg.c
> +++ b/libdtrace/dt_cg.c
> @@ -2647,11 +2647,16 @@ dt_cg_act_speculate(dt_pcb_t *pcb, dt_node_t *dnp, dtrace_actkind_t kind)
>   	dt_regset_free(drp, dnp->dn_reg);
>   }
>   
> -static uint64_t
> -dt_cg_stack_arg(dtrace_hdl_t *dtp, dt_node_t *dnp, dtrace_actkind_t kind)
> +/*
> + * Extract [u]stack() argument data, storing number of frames and optional
> + * string data in '*nframesp' and '*strszp', and returning the storage size for
> + * the call stack data (not including the size of the optional string data).
> + */
> +static uint_t
> +dt_cg_stack_arg(dtrace_hdl_t *dtp, dt_node_t *dnp, dtrace_actkind_t kind,
> +		uint_t *nframesp, uint_t *strszp)
>   {
> -	int		nframes;
> -	int		strsize = 0;
> +	uint_t		nframes;
>   	dt_node_t	*arg0 = dnp->dn_args;
>   	dt_node_t	*arg1 = arg0 != NULL ? arg0->dn_list : NULL;
>   	int		indopt, def, inderr;
> @@ -2689,19 +2694,24 @@ dt_cg_stack_arg(dtrace_hdl_t *dtp, dt_node_t *dnp, dtrace_actkind_t kind)
>   	if (nframes > dtp->dt_options[DTRACEOPT_MAXFRAMES])
>   		nframes = dtp->dt_options[DTRACEOPT_MAXFRAMES];
>   
> -	/* For user stacks, process one more argument. */
> -	if (kind == DTRACEACT_USTACK && arg1 != NULL) {
> -		if (arg1->dn_kind != DT_NODE_INT ||
> -		    ((arg1->dn_flags & DT_NF_SIGNED) &&
> -		    (int64_t)arg1->dn_value < 0))
> -			dnerror(arg1, D_USTACK_STRSIZE,
> -				"ustack( ) argument #2 must be a positive integer constant\n");
> +	if (nframesp)
> +		*nframesp = nframes;
> +
> +	if (strszp) {
> +		/* For user stacks, process one more argument. */
> +		if (kind == DTRACEACT_USTACK && arg1 != NULL) {
> +			if (arg1->dn_kind != DT_NODE_INT ||
> +			    ((arg1->dn_flags & DT_NF_SIGNED) &&
> +			    (int64_t)arg1->dn_value < 0))
> +				dnerror(arg1, D_USTACK_STRSIZE,
> +					"ustack( ) argument #2 must be a positive integer constant\n");
>   
> -		/* FIXME: for now, accept non-zero strsize, but it does nothing */
> -		strsize = arg1->dn_value;
> +			*strszp = arg1->dn_value;
> +		} else
> +			*strszp = 0;
>   	}
>   
> -	return DTRACE_STACK_ARG(kind == DTRACEACT_USTACK, nframes, strsize);
> +	return 4 * sizeof(uint32_t) + nframes * sizeof(uint64_t);
>   }
>   
>   /*
> @@ -2729,28 +2739,30 @@ dt_cg_act_stack_sub(dt_pcb_t *pcb, dt_node_t *dnp, int reg, int off,
>   	dtrace_hdl_t	*dtp = pcb->pcb_hdl;
>   	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		allocsz, nframes = 0, strsz = 0, aloff;
>   	uint_t		lbl_valid = dt_irlist_label(dlp);
>   	dt_ident_t	*skip = dt_dlib_get_var(dtp, "STACK_SKIP");
>   
>   	assert(skip != NULL);
>   
>   	/* 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 = dt_cg_stack_arg(dtp, dnp, kind, &nframes, &strsz);
>   
>   	/* 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, nframes));
> +	emit(dlp,  BPF_STORE_IMM(BPF_W, reg, aloff + 4, strsz));
> +	emit(dlp,  BPF_STORE_IMM(BPF_W, reg, aloff + 8,
> +				 kind == DTRACEACT_USTACK ? 1 : 0));
> +
>   	/* Write the tgid. */
>   	if (kind == DTRACEACT_USTACK) {
>   		if (dt_regset_xalloc_args(drp) == -1)
> @@ -2758,19 +2770,19 @@ dt_cg_act_stack_sub(dt_pcb_t *pcb, dt_node_t *dnp, int reg, int off,
>   		dt_regset_xalloc(drp, BPF_REG_0);
>   		emit(dlp,  BPF_CALL_HELPER(BPF_FUNC_get_current_pid_tgid));
>   		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));
> +		/* A 32-bit store effectively masks the lower 32 bits. */
> +		emit(dlp,  BPF_STORE(BPF_W, 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 +2798,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 +8747,13 @@ 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:
> +						size = dt_cg_stack_arg(dtp, knp, kind, NULL, NULL);
>   						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 aa81906f..3b0b2358 100644
> --- a/libdtrace/dt_impl.h
> +++ b/libdtrace/dt_impl.h
> @@ -236,19 +236,6 @@ typedef struct dt_tstring {
>   	int		in_use;			/* In use (1) or not (0) */
>   } dt_tstring_t;
>   
> -/*
> - * The stack()/ustack() data record argument encodes:
> - *  - the stack type (kernel or userspace)
> - *  - the number of frames in the stack trace
> - *  - the size of the optional string area for ustack()
> - */
> -#define DTRACE_STACK_IS_USER(x)		((x) & (1 << 31))
> -#define DTRACE_STACK_NFRAMES(x)		(uint32_t)((x) & INT32_MAX)
> -#define DTRACE_STACK_STRSIZE(x)		(uint32_t)((x) >> 32)
> -#define DTRACE_STACK_ARG(t, x, y)	((((uint64_t)(y)) << 32) | \
> -					 ((t) ? (1UL << 31) : 0) | \
> -					 ((x) & INT32_MAX))
> -
>   typedef struct dt_dirpath {
>   	dt_list_t dir_list;		/* linked-list forward/back pointers */
>   	char *dir_path;			/* directory pathname */
> diff --git a/libdtrace/dt_open.c b/libdtrace/dt_open.c
> index ba5463a0..17dfbf9a 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	is_user;	// > 0 if userspace stack
> +	 *	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, "is_user", 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..4f814c4e 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	is_user
> + *	uint32_t	pid
> + *	uint64_t	addrs[depth]
> + *
> + * The '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 'is_user' member identifies the stack trace as a userspace stack trace
> + * if its value is > 0.
> + * 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 },
> @@ -2487,9 +2511,8 @@ dt_print_stack(dtrace_hdl_t *dtp, FILE *fp, void *fmtdata,
>   	else
>   		format = ((dt_pfargv_t *)fmtdata)->pfv_format;
>   
> -	/* pfprint_stack() uses pfd_rec, pfd_flags, and pfd_width only */
> +	/* pfprint_stack() uses pfd_flags and pfd_width only */
>   	memset(&pfd, 0, sizeof(pfd));
> -	pfd.pfd_rec = recs;
>   	pfd.pfd_flags = DT_PFCONV_LEFT;
>   
>   	if (dtp->dt_options[DTRACEOPT_STACKINDENT] != DTRACEOPT_UNSET)
> 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..d35361eb
> --- /dev/null
> +++ b/test/unittest/funcs/stack/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: periodic_output */
> +
> +fbt::hrtimer_nanosleep: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..1a2b8d2b
> --- /dev/null
> +++ b/test/unittest/funcs/stack/err.corrupted-data.r
> @@ -0,0 +1,4 @@
> +                   FUNCTION:NAME
> +         hrtimer_nanosleep:entry -- @@stderr --
> +dtrace: script 'test/unittest/funcs/stack/err.corrupted-data.d' matched 2 probes
> +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..2125c395
> --- /dev/null
> +++ b/test/unittest/funcs/stack/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 stack() is not allowed.
> + */
> +
> +/* @@trigger: periodic_output */
> +
> +fbt::hrtimer_nanosleep: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..1b0fb044
> --- /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 16: 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..fd32c7b0
> --- /dev/null
> +++ b/test/unittest/funcs/stack/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 stack() to a dynamic variable (assoc element).
> + */
> +
> +/* @@trigger: periodic_output */
> +
> +fbt::hrtimer_nanosleep:entry
> +{
> +	assoc["a"] = stack(3);
> +	printf("%k", assoc["a"]);
> +
> +	exit(0);
> +}
> +
> +ERROR
> +{
> +	exit(1);
> +}
> diff --git a/test/unittest/funcs/stack/tst.asgn_dvar.r b/test/unittest/funcs/stack/tst.asgn_dvar.r
> new file mode 100644
> index 00000000..2e9ba477
> --- /dev/null
> +++ b/test/unittest/funcs/stack/tst.asgn_dvar.r
> @@ -0,0 +1 @@
> +success
> 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..2bc2077d
> --- /dev/null
> +++ b/test/unittest/funcs/stack/tst.asgn_dvar.r.p
> @@ -0,0 +1,35 @@
> +#!/usr/bin/gawk -f
> +
> +/hrtimer_nanosleep/ {
> +    # check probe
> +    if ( $1 != "hrtimer_nanosleep:entry" ) {
> +        print "ERROR: expected fun:prb = hrtimer_nanosleep:entry";
> +        exit(0);
> +    }
> +
> +    # check stack(3)
> +    getline;
> +    if (index($1, "`hrtimer_nanosleep+0x") == 0 &&
> +        match($1, "`hrtimer_nanosleep$") == 0) {
> +        print "ERROR: expected leaf frame to be hrtimer_nanosleep";
> +        exit(0);
> +    }
> +    getline;
> +    if (NF == 0) {
> +        print "ERROR: missing second frame";
> +        exit(0);
> +    }
> +    getline;
> +    if (NF == 0) {
> +        print "ERROR: missing third frame";
> +        exit(0);
> +    }
> +    getline;
> +    if (NF > 0) {
> +        print "ERROR: expected stack(3) to have only three frames";
> +        exit(0);
> +    }
> +
> +    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..06e75430
> --- /dev/null
> +++ b/test/unittest/funcs/stack/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 stack() to a global variable.
> + */
> +
> +/* @@trigger: periodic_output */
> +
> +fbt::hrtimer_nanosleep:entry
> +{
> +	v = stack(3);
> +	printf("%k", v);
> +
> +	exit(0);
> +}
> +
> +ERROR
> +{
> +	exit(1);
> +}
> diff --git a/test/unittest/funcs/stack/tst.asgn_gvar.r b/test/unittest/funcs/stack/tst.asgn_gvar.r
> new file mode 120000
> index 00000000..a79e4237
> --- /dev/null
> +++ b/test/unittest/funcs/stack/tst.asgn_gvar.r
> @@ -0,0 +1 @@
> +tst.asgn_dvar.r
> \ No newline at end of file
> 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..1b63d20a
> --- /dev/null
> +++ b/test/unittest/funcs/stack/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 stack() to a local variable.
> + */
> +
> +/* @@trigger: periodic_output */
> +
> +fbt::hrtimer_nanosleep:entry
> +{
> +	this->v = stack(3);
> +	printf("%k", this->v);
> +
> +	exit(0);
> +}
> +
> +ERROR
> +{
> +	exit(1);
> +}
> diff --git a/test/unittest/funcs/stack/tst.asgn_lvar.r b/test/unittest/funcs/stack/tst.asgn_lvar.r
> new file mode 120000
> index 00000000..a79e4237
> --- /dev/null
> +++ b/test/unittest/funcs/stack/tst.asgn_lvar.r
> @@ -0,0 +1 @@
> +tst.asgn_dvar.r
> \ No newline at end of file
> 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..9722290b
> --- /dev/null
> +++ b/test/unittest/funcs/stack/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 stack() to a TLS variable.
> + */
> +
> +/* @@trigger: periodic_output */
> +
> +fbt::hrtimer_nanosleep:entry
> +{
> +	self->v = stack(3);
> +	printf("%k", self->v);
> +
> +	exit(0);
> +}
> +
> +ERROR
> +{
> +	exit(1);
> +}
> diff --git a/test/unittest/funcs/stack/tst.asgn_tvar.r b/test/unittest/funcs/stack/tst.asgn_tvar.r
> new file mode 120000
> index 00000000..a79e4237
> --- /dev/null
> +++ b/test/unittest/funcs/stack/tst.asgn_tvar.r
> @@ -0,0 +1 @@
> +tst.asgn_dvar.r
> \ No newline at end of file
> 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..8ea385f7
> --- /dev/null
> +++ b/test/unittest/funcs/stack/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 kernel stack.
> + */
> +
> +/* @@trigger: periodic_output */
> +
> +fbt::hrtimer_nanosleep:entry
> +{
> +	trace("Expecting vmlinux`hrtimer_nanosleep, 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..f209b8ed
> --- /dev/null
> +++ b/test/unittest/funcs/stack/tst.ref_addrs.r
> @@ -0,0 +1,5 @@
> +                   FUNCTION:NAME
> +         hrtimer_nanosleep:entry   Expecting vmlinux`hrtimer_nanosleep, got   vmlinux`hrtimer_nanosleep
> +
> +-- @@stderr --
> +dtrace: script 'test/unittest/funcs/stack/tst.ref_addrs.d' matched 2 probes
> 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..0a8ce390
> --- /dev/null
> +++ b/test/unittest/funcs/stack/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 kernel stack.
> + */
> +
> +/* @@trigger: periodic_output */
> +
> +fbt::hrtimer_nanosleep:entry
> +{
> +	trace("Expecting 7, got ");
> +	trace(stack(7).depth);
> +}
> +
> +fbt::hrtimer_nanosleep:entry
> +/stack(7).depth == 7/
> +{
> +	exit(0);
> +}
> +
> +fbt::hrtimer_nanosleep: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..10a70b58
> --- /dev/null
> +++ b/test/unittest/funcs/stack/tst.ref_depth.r
> @@ -0,0 +1,6 @@
> +                   FUNCTION:NAME
> +         hrtimer_nanosleep:entry   Expecting 7, got                           7
> +         hrtimer_nanosleep:entry
> +
> +-- @@stderr --
> +dtrace: script 'test/unittest/funcs/stack/tst.ref_depth.d' matched 4 probes
> diff --git a/test/unittest/funcs/stack/tst.ref_is_user.d b/test/unittest/funcs/stack/tst.ref_is_user.d
> new file mode 100644
> index 00000000..076fe289
> --- /dev/null
> +++ b/test/unittest/funcs/stack/tst.ref_is_user.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 'is_user' field of a kernel stack.
> + */
> +
> +/* @@trigger: periodic_output */
> +
> +fbt::hrtimer_nanosleep:entry
> +{
> +	trace("Expecting 0, got ");
> +	trace(stack(7).is_user);
> +}
> +
> +fbt::hrtimer_nanosleep:entry
> +/stack(7).is_user == 0/
> +{
> +	exit(0);
> +}
> +
> +fbt::hrtimer_nanosleep:entry
> +/stack(7).is_user != 0/
> +{
> +	exit(1);
> +}
> +
> +ERROR
> +{
> +	exit(1);
> +}
> diff --git a/test/unittest/funcs/stack/tst.ref_is_user.r b/test/unittest/funcs/stack/tst.ref_is_user.r
> new file mode 100644
> index 00000000..78259b48
> --- /dev/null
> +++ b/test/unittest/funcs/stack/tst.ref_is_user.r
> @@ -0,0 +1,6 @@
> +                   FUNCTION:NAME
> +         hrtimer_nanosleep:entry   Expecting 0, got                           0
> +         hrtimer_nanosleep:entry
> +
> +-- @@stderr --
> +dtrace: script 'test/unittest/funcs/stack/tst.ref_is_user.d' matched 4 probes
> 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..db64d5fb
> --- /dev/null
> +++ b/test/unittest/funcs/stack/tst.ref_pid.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 'pid' field of a kernel stack yields 0.
> + */
> +
> +/* @@trigger: periodic_output */
> +
> +fbt::hrtimer_nanosleep:entry
> +{
> +	trace("Expecting 0, got ");
> +	trace(stack(7).pid);
> +}
> +
> +fbt::hrtimer_nanosleep:entry
> +/stack(7).pid == 0/
> +{
> +	exit(0);
> +}
> +
> +fbt::hrtimer_nanosleep: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..30f4541d
> --- /dev/null
> +++ b/test/unittest/funcs/stack/tst.ref_pid.r
> @@ -0,0 +1,6 @@
> +                   FUNCTION:NAME
> +         hrtimer_nanosleep:entry   Expecting 0, got                           0
> +         hrtimer_nanosleep:entry
> +
> +-- @@stderr --
> +dtrace: script 'test/unittest/funcs/stack/tst.ref_pid.d' matched 4 probes
> 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..3ec5efcf
> --- /dev/null
> +++ b/test/unittest/funcs/stack/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 a kernel stack yields 0.
> + */
> +
> +/* @@trigger: periodic_output */
> +
> +fbt::hrtimer_nanosleep:entry
> +{
> +	trace("Expecting 0, got ");
> +	trace(stack(7).strsz);
> +}
> +
> +fbt::hrtimer_nanosleep:entry
> +/stack(7).strsz == 0/
> +{
> +	exit(0);
> +}
> +
> +fbt::hrtimer_nanosleep: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..3fff4fea
> --- /dev/null
> +++ b/test/unittest/funcs/stack/tst.ref_strsz.r
> @@ -0,0 +1,6 @@
> +                   FUNCTION:NAME
> +         hrtimer_nanosleep:entry   Expecting 0, got                           0
> +         hrtimer_nanosleep:entry
> +
> +-- @@stderr --
> +dtrace: script 'test/unittest/funcs/stack/tst.ref_strsz.d' matched 4 probes
> diff --git a/test/unittest/funcs/stack/tst.store.d b/test/unittest/funcs/stack/tst.store.d
> new file mode 100644
> index 00000000..6d113596
> --- /dev/null
> +++ b/test/unittest/funcs/stack/tst.store.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: It is possible to store to members of dt_stack_t.
> + */
> +
> +/* @@trigger: periodic_output */
> +
> +fbt::hrtimer_nanosleep:entry
> +{
> +	v = stack(3);
> +	printf("%k", v);
> +	v.depth = 2;
> +	printf("%k", v);
> +	v.depth = 3;
> +	printf("%k", v);
> +	exit(0);
> +}
> +
> +ERROR
> +{
> +	exit(1);
> +}
> diff --git a/test/unittest/funcs/stack/tst.store.r b/test/unittest/funcs/stack/tst.store.r
> new file mode 100644
> index 00000000..2e9ba477
> --- /dev/null
> +++ b/test/unittest/funcs/stack/tst.store.r
> @@ -0,0 +1 @@
> +success
> diff --git a/test/unittest/funcs/stack/tst.store.r.p b/test/unittest/funcs/stack/tst.store.r.p
> new file mode 100755
> index 00000000..cd1bc568
> --- /dev/null
> +++ b/test/unittest/funcs/stack/tst.store.r.p
> @@ -0,0 +1,63 @@
> +#!/usr/bin/gawk -f
> +
> +/hrtimer_nanosleep/ {
> +    # check probe
> +    if ( $1 != "hrtimer_nanosleep:entry" ) {
> +        print "ERROR: expected fun:prb = hrtimer_nanosleep:entry";
> +        exit(0);
> +    }
> +
> +    # collect 3 stack frames
> +    for (i = 0; i < 3; i++) {
> +        getline;
> +        if (NF == 0) {
> +            print "ERROR: missing stack(3) frame addrs["i"]";
> +            exit(0);
> +        }
> +        stack[i] = $0;
> +    }
> +    getline;
> +    if (NF != 0) {
> +        print "ERROR: too many stack frames (first stack(3))";
> +        exit(0);
> +    }
> +
> +    # expect 2 stack frames
> +    for (i = 0; i < 2; i++) {
> +        getline;
> +        if (NF == 0) {
> +            print "ERROR: missing stack(2) frame addrs["i"]";
> +            exit(0);
> +        }
> +        if (stack[i] != $0) {
> +            print "ERROR: wrong stack(2) frame addrs["i"]";
> +            exit(0);
> +        }
> +    }
> +    getline;
> +    if (NF != 0) {
> +        print "ERROR: too many stack frames (stack(2))";
> +        exit(0);
> +    }
> +
> +    # expect 3 stack frames
> +    for (i = 0; i < 3; i++) {
> +        getline;
> +        if (NF == 0) {
> +            print "ERROR: missing stack(3) frame addrs["i"]";
> +            exit(0);
> +        }
> +        if (stack[i] != $0) {
> +            print "ERROR: wrong stack(3) frame addrs["i"]";
> +            exit(0);
> +        }
> +    }
> +    getline;
> +    if (NF != 0) {
> +        print "ERROR: too many stack frames (second stack(3))";
> +        exit(0);
> +    }
> +
> +    print "success";
> +    exit(0);
> +}
> 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..142a56ce
> --- /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(3);
> +	printf("%k", assoc["a"]);
> +
> +	exit(0);
> +}
> +
> +ERROR
> +{
> +	exit(1);
> +}
> diff --git a/test/unittest/funcs/ustack/tst.asgn_dvar.r b/test/unittest/funcs/ustack/tst.asgn_dvar.r
> new file mode 100644
> index 00000000..2e9ba477
> --- /dev/null
> +++ b/test/unittest/funcs/ustack/tst.asgn_dvar.r
> @@ -0,0 +1 @@
> +success
> 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..0ffa73ba
> --- /dev/null
> +++ b/test/unittest/funcs/ustack/tst.asgn_dvar.r.p
> @@ -0,0 +1,60 @@
> +#!/usr/bin/gawk -f
> +
> +BEGIN {
> +    cmd = "uname -rm";
> +    cmd | getline;
> +    close(cmd);
> +
> +    if (/x86_64/) {
> +	gsub(/\./, " ");
> +	maj = int($1);
> +	min = int($2);
> +	if (maj < 6 || (maj == 6 && min < 11))
> +	    missing_frame = 1;
> +    } else
> +	missing_frame = 1;
> +}
> +
> +/myfunc_z/ {
> +    # check probe
> +    if ( $1 != "myfunc_z:entry" ) {
> +        print "ERROR: expected fun:prb = myfunc_z:entry";
> +        exit(0);
> +    }
> +
> +    # check stack(3)
> +    getline;
> +    if (index($1, "`myfunc_z+0x") == 0 &&
> +        match($1, "`myfunc_z$") == 0) {
> +        print "ERROR: expected leaf frame to be myfunc_z";
> +        exit(0);
> +    }
> +    getline;
> +    if (NF == 0) {
> +        print "ERROR: missing second frame";
> +        exit(0);
> +    }
> +    if (index($1, missing_frame ? "`myfunc_x+0x" : "`myfunc_y+0x") == 0 &&
> +        match($1, missing_frame ? "`myfunc_x$" : "`myfunc_y$") == 0) {
> +        printf("ERROR: expected leaf frame to be %s\n", missing_frame ? "myfunc_x" : "myfunc_y");
> +        exit(0);
> +    }
> +    getline;
> +    if (NF == 0) {
> +        print "ERROR: missing third frame";
> +        exit(0);
> +    }
> +    if (index($1, missing_frame ? "`myfunc_w+0x" : "`myfunc_x+0x") == 0 &&
> +        match($1, missing_frame ? "`myfunc_w$" : "`myfunc_x$") == 0) {
> +        printf("ERROR: expected leaf frame to be %s\n", missing_frame ? "myfunc_w" : "myfunc_x");
> +        exit(0);
> +    }
> +    getline;
> +    if (NF > 0) {
> +        print "ERROR: expected stack(3) to have only three frames";
> +        exit(0);
> +    }
> +
> +    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..089e145f
> --- /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(3);
> +	printf("%k", v);
> +
> +	exit(0);
> +}
> +
> +ERROR
> +{
> +	exit(1);
> +}
> diff --git a/test/unittest/funcs/ustack/tst.asgn_gvar.r b/test/unittest/funcs/ustack/tst.asgn_gvar.r
> new file mode 120000
> index 00000000..a79e4237
> --- /dev/null
> +++ b/test/unittest/funcs/ustack/tst.asgn_gvar.r
> @@ -0,0 +1 @@
> +tst.asgn_dvar.r
> \ No newline at end of file
> 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..132d4a89
> --- /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(3);
> +	printf("%k", this->v);
> +
> +	exit(0);
> +}
> +
> +ERROR
> +{
> +	exit(1);
> +}
> diff --git a/test/unittest/funcs/ustack/tst.asgn_lvar.r b/test/unittest/funcs/ustack/tst.asgn_lvar.r
> new file mode 120000
> index 00000000..a79e4237
> --- /dev/null
> +++ b/test/unittest/funcs/ustack/tst.asgn_lvar.r
> @@ -0,0 +1 @@
> +tst.asgn_dvar.r
> \ No newline at end of file
> 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..46fbe606
> --- /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(3);
> +	printf("%k", self->v);
> +
> +	exit(0);
> +}
> +
> +ERROR
> +{
> +	exit(1);
> +}
> diff --git a/test/unittest/funcs/ustack/tst.asgn_tvar.r b/test/unittest/funcs/ustack/tst.asgn_tvar.r
> new file mode 120000
> index 00000000..a79e4237
> --- /dev/null
> +++ b/test/unittest/funcs/ustack/tst.asgn_tvar.r
> @@ -0,0 +1 @@
> +tst.asgn_dvar.r
> \ No newline at end of file
> 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..97b0d684
> --- /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 ustack-tst-basic`myfunc_z, 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..20f36969
> --- /dev/null
> +++ b/test/unittest/funcs/ustack/tst.ref_addrs.r
> @@ -0,0 +1,5 @@
> +                   FUNCTION:NAME
> +                  myfunc_z:entry   Expecting ustack-tst-basic`myfunc_z, 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_is_user.d b/test/unittest/funcs/ustack/tst.ref_is_user.d
> new file mode 100644
> index 00000000..5465f090
> --- /dev/null
> +++ b/test/unittest/funcs/ustack/tst.ref_is_user.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 'is_user' field of a userspace stack.
> + */
> +
> +/* @@trigger: ustack-tst-basic */
> +
> +pid$target:a.out:myfunc_z:entry
> +{
> +	trace("Expecting 1, got ");
> +	trace(ustack(7).is_user);
> +}
> +
> +pid$target:a.out:myfunc_z:entry
> +/ustack(7).is_user == 1/
> +{
> +	exit(0);
> +}
> +
> +pid$target:a.out:myfunc_z:entry
> +/ustack(7).is_user != 0/
> +{
> +	exit(1);
> +}
> +
> +ERROR
> +{
> +	exit(1);
> +}
> diff --git a/test/unittest/funcs/ustack/tst.ref_is_user.r b/test/unittest/funcs/ustack/tst.ref_is_user.r
> new file mode 100644
> index 00000000..937fd8d3
> --- /dev/null
> +++ b/test/unittest/funcs/ustack/tst.ref_is_user.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_is_user.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..80c2473b
> --- /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 a 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..7d4740c3
> --- /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 a 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.store.d b/test/unittest/funcs/ustack/tst.store.d
> new file mode 100644
> index 00000000..066d5705
> --- /dev/null
> +++ b/test/unittest/funcs/ustack/tst.store.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: 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);
> +	printf("%k", v);
> +	v.depth = 2;
> +	printf("%k", v);
> +	v.depth = 5;
> +	printf("%k", v);
> +	exit(0);
> +}
> +
> +ERROR
> +{
> +	exit(1);
> +}
> diff --git a/test/unittest/funcs/ustack/tst.store.r b/test/unittest/funcs/ustack/tst.store.r
> new file mode 100644
> index 00000000..2e9ba477
> --- /dev/null
> +++ b/test/unittest/funcs/ustack/tst.store.r
> @@ -0,0 +1 @@
> +success
> diff --git a/test/unittest/funcs/ustack/tst.store.r.p b/test/unittest/funcs/ustack/tst.store.r.p
> new file mode 100755
> index 00000000..7ccffbb5
> --- /dev/null
> +++ b/test/unittest/funcs/ustack/tst.store.r.p
> @@ -0,0 +1,63 @@
> +#!/usr/bin/gawk -f
> +
> +/myfunc_z/ {
> +    # check probe
> +    if ( $1 != "myfunc_z:entry" ) {
> +        print "ERROR: expected fun:prb = myfunc_z:entry";
> +        exit(0);
> +    }
> +
> +    # collect 5 stack frames
> +    for (i = 0; i < 5; i++) {
> +        getline;
> +        if (NF == 0) {
> +            print "ERROR: missing stack(5) frame addrs["i"]";
> +            exit(0);
> +        }
> +        stack[i] = $0;
> +    }
> +    getline;
> +    if (NF != 0) {
> +        print "ERROR: too many stack frames (first stack(5))";
> +        exit(0);
> +    }
> +
> +    # expect 2 stack frames
> +    for (i = 0; i < 2; i++) {
> +        getline;
> +        if (NF == 0) {
> +            print "ERROR: missing stack(2) frame addrs["i"]";
> +            exit(0);
> +        }
> +        if (stack[i] != $0) {
> +            print "ERROR: wrong stack(2) frame addrs["i"]";
> +            exit(0);
> +        }
> +    }
> +    getline;
> +    if (NF != 0) {
> +        print "ERROR: too many stack frames (stack(2))";
> +        exit(0);
> +    }
> +
> +    # expect 5 stack frames
> +    for (i = 0; i < 5; i++) {
> +        getline;
> +        if (NF == 0) {
> +            print "ERROR: missing stack(5) frame addrs["i"]";
> +            exit(0);
> +        }
> +        if (stack[i] != $0) {
> +            print "ERROR: wrong stack(5) frame addrs["i"]";
> +            exit(0);
> +        }
> +    }
> +    getline;
> +    if (NF != 0) {
> +        print "ERROR: too many stack frames (second stack(5))";
> +        exit(0);
> +    }
> +
> +    print "success";
> +    exit(0);
> +}
> diff --git a/test/unittest/printa/err.D_PRINTF_ARG_TYPE.stack.r b/test/unittest/printa/err.D_PRINTF_ARG_TYPE.stack.r
> index 95ac80da..b1e55bc0 100644
> --- a/test/unittest/printa/err.D_PRINTF_ARG_TYPE.stack.r
> +++ b/test/unittest/printa/err.D_PRINTF_ARG_TYPE.stack.r
> @@ -2,4 +2,4 @@
>   dtrace: failed to compile script test/unittest/printa/err.D_PRINTF_ARG_TYPE.stack.d: [D_PRINTF_ARG_TYPE] line 12: printa( ) argument #2 is incompatible with conversion #1 prototype:
>   	conversion: %p
>   	 prototype: pointer or integer
> -	  argument: dt_stack
> +	  argument: dt_stack_t
> diff --git a/test/unittest/printa/err.D_PRINTF_ARG_TYPE.ustack.r b/test/unittest/printa/err.D_PRINTF_ARG_TYPE.ustack.r
> index 5df10376..d6687d7a 100644
> --- a/test/unittest/printa/err.D_PRINTF_ARG_TYPE.ustack.r
> +++ b/test/unittest/printa/err.D_PRINTF_ARG_TYPE.ustack.r
> @@ -2,4 +2,4 @@
>   dtrace: failed to compile script test/unittest/printa/err.D_PRINTF_ARG_TYPE.ustack.d: [D_PRINTF_ARG_TYPE] line 12: printa( ) argument #2 is incompatible with conversion #1 prototype:
>   	conversion: %p
>   	 prototype: pointer or integer
> -	  argument: dt_stack
> +	  argument: dt_stack_t
> 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..412df410
> --- /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..c87ef73f
> --- /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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.