* [PATCH v3] cg, printf: allow storing [u]stack() into vars and printf using %k
@ 2025-10-06 19:55 Kris Van Hees
2025-10-07 3:05 ` Eugene Loh
0 siblings, 1 reply; 4+ messages in thread
From: Kris Van Hees @ 2025-10-06 19:55 UTC (permalink / raw)
To: dtrace, dtrace-devel
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 <kris.van.hees@oracle.com>
---
libdtrace/dt_cg.c | 81 +++++++++++--------
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 | 59 ++++++++++++++
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 | 25 ++++++
.../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 +++++++++
69 files changed, 1244 insertions(+), 74 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/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..967649ba 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 'nframes' and 'strsz', 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)
@@ -2760,17 +2772,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_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 +2799,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 +8748,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..ea444332 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 & 0x7fffffff]
+ *
+ * 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 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 },
@@ -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..9999094f
--- /dev/null
+++ b/test/unittest/funcs/ustack/tst.asgn_dvar.r.p
@@ -0,0 +1,59 @@
+#!/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;
+ }
+}
+
+/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..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/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..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
^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [PATCH v3] cg, printf: allow storing [u]stack() into vars and printf using %k
2025-10-06 19:55 [PATCH v3] cg, printf: allow storing [u]stack() into vars and printf using %k Kris Van Hees
@ 2025-10-07 3:05 ` Eugene Loh
2025-10-07 3:10 ` Kris Van Hees
0 siblings, 1 reply; 4+ messages in thread
From: Eugene Loh @ 2025-10-07 3:05 UTC (permalink / raw)
To: Kris Van Hees, dtrace, dtrace-devel
On 10/6/25 15:55, Kris Van Hees wrote:
> diff --git 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 'nframes' and 'strsz', and returning the storage size for
Or, in '*nframesp' and '*strszp'.
> + * the call stack data (not including the size of the optional string data).
> + */
> +static uint_t
Again, I would think that "size" should become uint in dt_cg_agg() as well.
> +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;
> @@ -2760,17 +2772,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));
The unnecessary MOV32 instruction is okay, but I'm curious what your
thoughts are... why you're in favor of keeping it.
> - emit(dlp, BPF_STORE(BPF_DW, reg, off, BPF_REG_0));
> + 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 {
> diff --git a/libdtrace/dt_printf.c b/libdtrace/dt_printf.c
> @@ -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 & 0x7fffffff]
> + *
> + * 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 kernel space (0) or
> + * userspace (1).
Or, instead of "1" it's ">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;
>
> 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..9999094f
> --- /dev/null
> +++ b/test/unittest/funcs/ustack/tst.asgn_dvar.r.p
> @@ -0,0 +1,59 @@
> +#!/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;
> + }
> +}
Cool. Nice solution to that problem. Unfortunately, it fails for me on
all ARM. I think the problem is that the "missing frame" problem is on
all ARM: even newer kernels don't fix it (just on x86). So, for arm,
missing_frame should be 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.store.d b/test/unittest/funcs/ustack/tst.store.d
Okay. Just curious again, though. Is the omission of .r and .r.p files
for this test intentional, given that the stack version of this test
does more checking.
> 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
Looks fine. Other stack tests seem to be migrating to hrtimer_nanosleep
-- and dumping the destructive BEGIN{system()} stuff -- but that does
not seem necessary here.
> 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.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
I still don't get this one. Just as in v1 and v2, I get consistent
FAILs here. Does this pass for you? So far as I can tell, there are
issues with leading whitespace. How about trimming the leading
whitespace in the .r file? And...
> 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}" }'
... trimming the leading space in both of those print statements.
> +fi
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH v3] cg, printf: allow storing [u]stack() into vars and printf using %k
2025-10-07 3:05 ` Eugene Loh
@ 2025-10-07 3:10 ` Kris Van Hees
2025-10-07 4:24 ` Eugene Loh
0 siblings, 1 reply; 4+ messages in thread
From: Kris Van Hees @ 2025-10-07 3:10 UTC (permalink / raw)
To: Eugene Loh; +Cc: Kris Van Hees, dtrace, dtrace-devel
On Mon, Oct 06, 2025 at 11:05:26PM -0400, Eugene Loh wrote:
> On 10/6/25 15:55, Kris Van Hees wrote:
>
> > diff --git 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 'nframes' and 'strsz', and returning the storage size for
>
> Or, in '*nframesp' and '*strszp'.
Fixed.
> > + * the call stack data (not including the size of the optional string data).
> > + */
> > +static uint_t
>
> Again, I would think that "size" should become uint in dt_cg_agg() as well.
I believe I amswered that in my previous email already.
> > +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;
> > @@ -2760,17 +2772,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));
>
> The unnecessary MOV32 instruction is okay, but I'm curious what your
> thoughts are... why you're in favor of keeping it.
Oops, as I mentioned in my email, I meant to drop that. Fixed.
> > - emit(dlp, BPF_STORE(BPF_DW, reg, off, BPF_REG_0));
> > + 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 {
> > diff --git a/libdtrace/dt_printf.c b/libdtrace/dt_printf.c
> > @@ -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 & 0x7fffffff]
> > + *
> > + * 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 kernel space (0) or
> > + * userspace (1).
>
> Or, instead of "1" it's ">0"?
Fixed.
> > + * 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;
> > 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..9999094f
> > --- /dev/null
> > +++ b/test/unittest/funcs/ustack/tst.asgn_dvar.r.p
> > @@ -0,0 +1,59 @@
> > +#!/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;
> > + }
> > +}
>
> Cool. Nice solution to that problem. Unfortunately, it fails for me on all
> ARM. I think the problem is that the "missing frame" problem is on all
> ARM: even newer kernels don't fix it (just on x86). So, for arm,
> missing_frame should be 1.
Fixed.
> > +/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.store.d b/test/unittest/funcs/ustack/tst.store.d
>
> Okay. Just curious again, though. Is the omission of .r and .r.p files for
> this test intentional, given that the stack version of this test does more
> checking.
Oops, forgot to 'git add'.
> > 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
>
> Looks fine. Other stack tests seem to be migrating to hrtimer_nanosleep --
> and dumping the destructive BEGIN{system()} stuff -- but that does not seem
> necessary here.
>
> > 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.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
>
> I still don't get this one. Just as in v1 and v2, I get consistent FAILs
> here. Does this pass for you? So far as I can tell, there are issues with
> leading whitespace. How about trimming the leading whitespace in the .r
> file? And...
Ah darn, got distracted and forgot to look at this. Checking...
> > 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}" }'
>
> ... trimming the leading space in both of those print statements.
>
> > +fi
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH v3] cg, printf: allow storing [u]stack() into vars and printf using %k
2025-10-07 3:10 ` Kris Van Hees
@ 2025-10-07 4:24 ` Eugene Loh
0 siblings, 0 replies; 4+ messages in thread
From: Eugene Loh @ 2025-10-07 4:24 UTC (permalink / raw)
To: Kris Van Hees; +Cc: dtrace, dtrace-devel
Sounds great, looking forward to v4, but one thing... you mention an
earlier email. I don't think I saw it, and I cannot find it right now
in the archive. Could you send it again, please. Mainly just for my
records. Or maybe just...
On 10/6/25 23:10, Kris Van Hees wrote:
> On Mon, Oct 06, 2025 at 11:05:26PM -0400, Eugene Loh wrote:
>> On 10/6/25 15:55, Kris Van Hees wrote:
>>> diff --git a/libdtrace/dt_cg.c b/libdtrace/dt_cg.c
>>> +static uint_t
>> Again, I would think that "size" should become uint in dt_cg_agg() as well.
> I believe I amswered that in my previous email already.
I'm mildly curious about this. Not a big deal.
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2025-10-07 4:24 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-10-06 19:55 [PATCH v3] cg, printf: allow storing [u]stack() into vars and printf using %k Kris Van Hees
2025-10-07 3:05 ` Eugene Loh
2025-10-07 3:10 ` Kris Van Hees
2025-10-07 4:24 ` Eugene Loh
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.