[DTrace-devel] [PATCH] cg, printf: allow storing [u]stack() into vars and printf using %k
Kris Van Hees
kris.van.hees at oracle.com
Fri Oct 3 16:05:46 UTC 2025
________________________________________
From: Eugene Loh <eugene.loh at oracle.com>
Sent: Friday, October 3, 2025 1:26 AM
To: Kris Van Hees; dtrace at lists.linux.dev; dtrace-devel at oss.oracle.com
Subject: Re: [DTrace-devel] [PATCH] cg, printf: allow storing [u]stack() into vars and printf using %k
Wow. Amazing stuff. I wouldn't swear to every detail, but it generally
looks good to me.
Picky stuff below.
On 10/2/25 15:47, 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 type; // type of stack (kernel/user)
The name is fine. I can live with "type". But in pfprint_stack() you
call it "is_user", which seems much more descriptive. I wouldn't mind a
change to "is_user", but again I can live with "type". Plus, changing
to "is_user" means renaming the ref_type test.
KVH: I'll rename it to is_user since there are only two types anyway.
> 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).
>
> diff --git a/libdtrace/dt_cg.c b/libdtrace/dt_cg.c
> @@ -2760,17 +2767,18 @@ dt_cg_act_stack_sub(dt_pcb_t *pcb, dt_node_t *dnp, int reg, int off,
> dt_regset_free_args(drp);
> /* mov32 %r0, %r0 effectively masks the lower 32 bits. */
> emit(dlp, BPF_MOV32_REG(BPF_REG_0, BPF_REG_0));
> - emit(dlp, BPF_STORE(BPF_DW, reg, off, BPF_REG_0));
> + emit(dlp, BPF_STORE(BPF_DW, reg, aloff + 12, BPF_REG_0));
> dt_regset_free(drp, BPF_REG_0);
> - }
> + } else
> + emit(dlp, BPF_STORE_IMM(BPF_W, reg, aloff + 12, 0));
Store BPF_DW at aloff+12? Shouldn't that be BPF_W? And then maybe
don't need BPF_MOV32_REG?
KVH: Yes, the store should be BPF_W. Whether the masking (done using the MOV32) is still needed is something I'll have to test. It seems reasonable but at the same time it is probably nicer to first make sure we are dealing with a 32-bit value rather than counting on the store implementation to correctly just use the lower 32 bits.
> 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 type
> + * uint32_t pid
> + * uint64_t addrs[depth & 0x7fffffff]
> + *
> + * If 'depth' member provides the maximum number of addresses in the stack
s/If/The/? Also, "maximum" in this context is a little confusing to me
(since maxframes is a different concept and we're just talking about how
many frames we're making space for), but it's okay.
KVH: Well, the depth *is* the maximum of frames that might be in the stack trace rather than the number of frame slots actually used because it represents how many values can be read safely. Therefore, depth != stackdepth.
> + * trace.
> + * The 'strsz' member provides the size of the optional string blob that is
> + * appended to the stack trace data.
> + * The 'type' member identifies the stack trace as kernel space (0) or
> + * userspace (1).
Again, the code goes on to call this "is_user", which seems so much more
descriptive. But not a big deal.
> + * 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;
Well, if we no longer use pfd->pfd_rec, then the caller dt_print_stack()
no longer needs to set it.
KVH: Good point. Removed.
> diff --git a/test/unittest/funcs/stack/tst.asgn_dvar.d b/test/unittest/funcs/stack/tst.asgn_dvar.d
> + assoc["a"] = stack(5);
> diff --git a/test/unittest/funcs/stack/tst.asgn_dvar.r.p b/test/unittest/funcs/stack/tst.asgn_dvar.r.p
> @@ -0,0 +1,35 @@
> +#!/usr/bin/gawk -f
> +
> +/ksys_write/ {
> + # check probe
> + if ( $1 != "ksys_write:entry" ) {
> + print "ERROR: expected fun:prb = ksys_write:entry";
> + exit 1;
> + }
> +
> + # check stack(3)
> + getline;
> + if (index($1, "`ksys_write+0x") == 0 &&
> + match($1, "`ksys_write$") == 0) {
> + print "ERROR: expected leaf frame to be ksys_write";
> + exit 1;
> + }
> + getline;
> + if (NF == 0) {
> + print "ERROR: missing second frame";
> + exit 1;
> + }
> + getline;
> + if (NF == 0) {
> + print "ERROR: missing third frame";
> + exit 1;
> + }
> + getline;
> + if (NF > 0) {
> + print "ERROR: expected stack(3) to have only three frames";
> + exit 1;
> + }
> +
> + print "success";
> + exit(0);
> +}
Hang on. The test uses stack(5) but the .r.p file twice tells me it
expects stack(3). What's going on? Well, I think the stack is short
and so we get stack(3) by accident, allowing the test to pass. So it
seems that we need to probe somewhere where the stack is deeper.
Also, fwiw, all that nice diagnostic output emitted by the .r.p file in
case of a failure will be lost. We won't see it in runtest.log or
runtest.sum. Maybe that's okay. Just fwiw.
KVH: My bad, the test should be using stack(3)... And the post-processor isn't doing its job. v2 will fix.
> diff --git a/test/unittest/funcs/stack/tst.ref_pid.d b/test/unittest/funcs/stack/tst.ref_pid.d
> + * ASSERTION: Test accessing the 'pid' field of s kernel stack yields 0.
> diff --git a/test/unittest/funcs/stack/tst.ref_strsz.d b/test/unittest/funcs/stack/tst.ref_strsz.d
> + * ASSERTION: Test accessing the 'strsz' field of s kernel stack yields 0.
"of s kernel" =>
"of a kernel"
(Ditto for the ustack variants of these tests.)
KVH: Thanks.
> diff --git a/test/unittest/funcs/stack/tst.store.d b/test/unittest/funcs/stack/tst.store.d
> + v.depth = 2;
> + printf("%k", v);
Wow, cool. But how about changing this to
v.depth = 1;
printf("%k", v);
v.depth = 2;
printf("%k", v);
and then checking that
the first stack is a single frame
the second stack looks like the first one plus one more frame
Not hugely different from other testing being done, but since we're
checking storing to a field of a stack var, could do some correctness
checking and not just "well, it doesn't complain."
KVH: Well, I am really only checking that it can be done. Whether it has sensible effects is less interesting because messing with what essentially is an internal structure is unlikely to be a good idea. Changing the depth is really the only potentially useful thing, but even that is doubtful.
But I'll look at perhaps checking it since it *could* be used i a sensible way.
> diff --git a/test/unittest/funcs/ustack/tst.asgn_dvar.d b/test/unittest/funcs/ustack/tst.asgn_dvar.d
> + assoc["a"] = ustack(5);
> diff --git a/test/unittest/funcs/ustack/tst.asgn_dvar.r.p b/test/unittest/funcs/ustack/tst.asgn_dvar.r.p
> +#!/usr/bin/gawk -f
> +
> +/ksys_write/ {
> + # check probe
> + if ( $1 != "ksys_write:entry" ) {
> + print "ERROR: expected fun:prb = ksys_write:entry";
> + exit 1;
> + }
> +
> + # check stack(3)
> + getline;
> + if (index($1, "`ksys_write+0x") == 0 &&
> + match($1, "`ksys_write$") == 0) {
> + print "ERROR: expected leaf frame to be ksys_write";
> + exit 1;
> + }
> + getline;
> + if (NF == 0) {
> + print "ERROR: missing second frame";
> + exit 1;
> + }
> + getline;
> + if (NF == 0) {
> + print "ERROR: missing third frame";
> + exit 1;
> + }
> + getline;
> + if (NF > 0) {
> + print "ERROR: expected stack(3) to have only three frames";
> + exit 1;
> + }
> +
> + print "success";
> + exit(0);
> +}
Okay, same problems as before (asgn_* tests, but stack that time and
ustack this time).
One "problem" is that the nice diagnostic output from the .r.p is lost
when there is an error. Oh well. I guess that's okay.
The big problem, though, is that the test emits ustack(5) while the .r.p
file is emphatic that it is expecting ustack(3). Well, it says
"stack(3)" but that typo can be fixed and wouldn't change anything. In
the stack() case, the problem was that the stack was coincidentally too
small. That is certainly not the problem here. Here, the problem is
that we claim we're looking for ksys_write (which is the wrong frame to
look for) and if we do not find it then no checks are applied! So, the
.r.p file should be changed to look for a ustack frame and to complain
if it is not found.
These problems appear in the other ustack asgn_* tests as well.
> diff --git a/test/unittest/funcs/ustack/tst.ref_addrs.d b/test/unittest/funcs/ustack/tst.ref_addrs.d
> + trace("Expecting vmlinux`ksys_write, got ");
> diff --git a/test/unittest/funcs/ustack/tst.ref_addrs.r b/test/unittest/funcs/ustack/tst.ref_addrs.r
> + myfunc_z:entry Expecting vmlinux`ksys_write, got ustack-tst-basic`myfunc_z
Expecting vmlinux`ksys_write on the ustack? No, as demonstrated by the
expected output.
> diff --git a/test/unittest/printf/tst.ustack25_pid.d b/test/unittest/printf/tst.ustack25_pid.d
This test fails for me, but I think the issue is simple: leading white
space / indentation. It might suffice to strip out the leading spaces,
both in the .r file AND in the output that is fabricated in the .r.p file.
More information about the DTrace-devel
mailing list