[DTrace-devel] [RFC 1/2] dtrace: support print() action for DTrace v2
Alan Maguire
alan.maguire at oracle.com
Mon Nov 13 11:41:22 UTC 2023
print() prints the type of the value pointed to by the pointer
passed in. For example,
$ /sbin/dtrace -n 'fbt::ip_rcv_core:entry { print((struct sk_buff *)arg0); }'
dtrace: description 'fbt::ip_rcv_core:entry ' matched 1 probe
CPU ID FUNCTION:NAME
0 75596 ip_output:entry
(struct sk_buff) {
(struct) {
.sk = (struct sock *)0xffff9b7ad5df2f80,
.ip_defrag_offset = (int)-706793600,
},
(struct) {
.tstamp = (ktime_t)504907694897760,
.skb_mstamp_ns = (u64)504907694897760,
},
.cb = (char [48]) [
],
(struct) {
(struct) {
._skb_refdst = (long unsigned int)18446633550675199489,
.destructor = (void (*)(*) ())0xffffffffb66c0b80,
},
.tcp_tsorted_anchor = (struct list_head) {
.next = (struct list_head *)0xffff9b7ad9cc4601,
.prev = (struct list_head *)0xffffffffb66c0b80,
},
._sk_redir = (long unsigned int)18446633550675199489,
},
._nfct = (long unsigned int)18446633547046898499,
.len = (unsigned int)8788,
.data_len = (unsigned int)8736,
.hdr_len = (__u16)320,
.cloned = (__u8)1,
.ip_summed = (__u8)2,
...etc.
Zero-valued fields are skipped to improve compactness of the representation.
Since some data structures are large, the "printsize" option can be used
to restrict the amount of data displayed; in such cases a "..." connotes
the fact that additional data was present but not traced.
User-defined structures can also be printed:
CPU ID FUNCTION:NAME
0 75596 ip_output:entry
(struct foo) {
.a = (int)2,
}
Signed-off-by: Alan Maguire <alan.maguire at oracle.com>
---
include/dtrace/actions_defines.h | 1 +
include/dtrace/options_defines.h | 3 +-
libdtrace/dt_cg.c | 57 ++++++
libdtrace/dt_consume.c | 316 +++++++++++++++++++++++++++++++
libdtrace/dt_errtags.h | 4 +-
libdtrace/dt_impl.h | 6 +-
libdtrace/dt_open.c | 2 +
libdtrace/dt_options.c | 25 +++
8 files changed, 410 insertions(+), 4 deletions(-)
diff --git a/include/dtrace/actions_defines.h b/include/dtrace/actions_defines.h
index f161df7c..4ff4fb83 100644
--- a/include/dtrace/actions_defines.h
+++ b/include/dtrace/actions_defines.h
@@ -37,6 +37,7 @@
#define DTRACEACT_LIBACT 5 /* library-controlled action */
#define DTRACEACT_TRACEMEM 6 /* tracemem() action */
#define DTRACEACT_PCAP 7 /* pcap() action */
+#define DTRACEACT_PRINT 8 /* print() action */
#define DTRACEACT_PROC 0x0100
#define DTRACEACT_USTACK (DTRACEACT_PROC + 1)
diff --git a/include/dtrace/options_defines.h b/include/dtrace/options_defines.h
index 61a23f42..80246be8 100644
--- a/include/dtrace/options_defines.h
+++ b/include/dtrace/options_defines.h
@@ -62,7 +62,8 @@
#define DTRACEOPT_BPFLOG 32 /* always output BPF verifier log */
#define DTRACEOPT_SCRATCHSIZE 33 /* max scratch size permitted */
#define DTRACEOPT_LOCKMEM 34 /* max locked memory */
-#define DTRACEOPT_MAX 35 /* number of options */
+#define DTRACEOPT_PRINTSIZE 35 /* max # bytes printed by print() action */
+#define DTRACEOPT_MAX 36 /* number of options */
#define DTRACEOPT_UNSET (dtrace_optval_t)-2 /* unset option */
diff --git a/libdtrace/dt_cg.c b/libdtrace/dt_cg.c
index 581ada4e..01db774a 100644
--- a/libdtrace/dt_cg.c
+++ b/libdtrace/dt_cg.c
@@ -2560,6 +2560,62 @@ dt_cg_act_ustack(dt_pcb_t *pcb, dt_node_t *dnp, dtrace_actkind_t kind)
BPF_NOP());
}
+static void
+dt_cg_act_print(dt_pcb_t *pcb, dt_node_t *dnp, dtrace_actkind_t kind)
+{
+ uint_t addr_off, data_off;
+ dt_node_t *addr = dnp->dn_args;
+ dt_irlist_t *dlp = &pcb->pcb_ir;
+ dt_regset_t *drp = pcb->pcb_regs;
+ dtrace_hdl_t *dtp = pcb->pcb_hdl;
+ ctf_file_t *fp = addr->dn_ctfp;
+ ctf_id_t basetype, type = addr->dn_type;
+ char n[DT_TYPE_NAMELEN];
+ char t[DT_TYPE_NAMELEN];
+ size_t size;
+
+ if (dt_node_is_pointer(addr) == 0) {
+ dnerror(addr, D_PRINT_ADDR,
+ "print( ) argument #1 is incompatible with "
+ "prototype:\n\tprototype: pointer\n"
+ "\t argument: %s\n",
+ dt_node_type_name(addr, n, sizeof(n)));
+ }
+ basetype = ctf_type_reference(fp, type);
+ if (basetype == CTF_ERR)
+ longjmp(yypcb->pcb_jmpbuf, EDT_CTF);
+ size = ctf_type_size(fp, basetype);
+ if (size == 0)
+ dnerror(addr, D_PRINT_SIZE,
+ "print( ) argument #1 reference has type '%s' "
+ "with size 0; cannot print( ) it.\n",
+ ctf_type_name(fp, basetype, t, sizeof(t)));
+ if (size > dtp->dt_options[DTRACEOPT_PRINTSIZE])
+ size = dtp->dt_options[DTRACEOPT_PRINTSIZE];
+
+ /* reserve space for addr/type, data/size */
+ addr_off = dt_rec_add(dtp, dt_cg_fill_gap, DTRACEACT_PRINT,
+ sizeof(uint64_t), 8, NULL, basetype);
+ data_off = dt_rec_add(dtp, dt_cg_fill_gap, DTRACEACT_PRINT,
+ size, 8, NULL, size);
+
+ dt_cg_node(addr, &pcb->pcb_ir, drp);
+ dt_cg_check_ptr_arg(dlp, drp, addr, NULL);
+
+ if (dt_regset_xalloc_args(drp) == -1)
+ longjmp(yypcb->pcb_jmpbuf, EDT_NOREG);
+
+ /* store addr, size, type and bpf_probe_read(data_off, size, addr); */
+ emit(dlp, BPF_STORE(BPF_DW, BPF_REG_9, addr_off, addr->dn_reg));
+ emit(dlp, BPF_MOV_REG(BPF_REG_1, BPF_REG_9));
+ emit(dlp, BPF_ALU64_IMM(BPF_ADD, BPF_REG_1, data_off));
+ emit(dlp, BPF_MOV_IMM(BPF_REG_2, size));
+ emit(dlp, BPF_MOV_REG(BPF_REG_3, addr->dn_reg));
+ emit(dlp, BPF_CALL_HELPER(BPF_FUNC_probe_read));
+
+ dt_regset_free_args(drp);
+}
+
typedef void dt_cg_action_f(dt_pcb_t *, dt_node_t *, dtrace_actkind_t);
typedef struct dt_cg_actdesc {
@@ -2615,6 +2671,7 @@ static const dt_cg_actdesc_t _dt_cg_actions[DT_ACT_MAX] = {
DTRACEACT_UADDR },
[DT_ACT_IDX(DT_ACT_SETOPT)] = { &dt_cg_act_setopt, },
[DT_ACT_IDX(DT_ACT_PCAP)] = { &dt_cg_act_pcap, },
+ [DT_ACT_IDX(DT_ACT_PRINT)] = { &dt_cg_act_print, },
};
dt_irnode_t *
diff --git a/libdtrace/dt_consume.c b/libdtrace/dt_consume.c
index 7e2b1005..bd7cd85d 100644
--- a/libdtrace/dt_consume.c
+++ b/libdtrace/dt_consume.c
@@ -14,6 +14,7 @@
#include <ctype.h>
#include <alloca.h>
#include <dt_impl.h>
+#include <dt_module.h>
#include <dt_pcap.h>
#include <dt_peb.h>
#include <dt_state.h>
@@ -1971,6 +1972,315 @@ dt_print_trace(dtrace_hdl_t *dtp, FILE *fp, dtrace_recdesc_t *rec,
return dt_print_rawbytes(dtp, fp, data, rec->dtrd_size);
}
+#define DT_PRINT_DEPTH 10
+
+struct dt_visit_arg {
+ dtrace_hdl_t *dv_dtp;
+ FILE *dv_fp;
+ ctf_file_t *dv_ctfp;
+ uint64_t dv_size;
+ void *dv_data;
+ int dv_kinds[DT_PRINT_DEPTH];
+ int dv_last_depth;
+ int dv_startindent;
+};
+
+#define DT_NAME_LEN 32
+#define DT_PRINT_LEN 80
+
+#define DT_PRINT_STARTINDENT 44
+
+/* used to compare nested objects like structs/unions/arrays to see if they are 0. */
+static char zeros[65536] = {};
+
+static int
+dt_print_close_parens(struct dt_visit_arg *dva, int depth)
+{
+ int i;
+
+ switch (dva->dv_kinds[depth]) {
+ case CTF_K_STRUCT:
+ case CTF_K_UNION:
+ /* if struct/union contents were 0, we still opened the
+ * struct "{" at the top level, even if we did not
+ * display any members, so need to close it. An empty
+ * struct looks like this:
+ * (struct foo) {
+ * }
+ *
+ * ...at the top level, but at any other depth of nesting
+ * it is not displayed at all.
+ */
+ if (depth == 0 && dva->dv_last_depth == 0)
+ dva->dv_last_depth = 1;
+ /* add a "}" for each level needed between current depth
+ * and last depth we displayed data at.
+ */
+ for (i = dva->dv_last_depth - depth; i > 0; i--) {
+ if (dt_printf(dva->dv_dtp, dva->dv_fp, "%*s}%s\n",
+ dva->dv_startindent + depth + i - 1, "",
+ (depth > 0 || i > 1) ? "," : "") < 0)
+ return -1;
+ dva->dv_last_depth = depth;
+ }
+ break;
+ }
+ return 0;
+}
+
+static int
+dt_print_visit(const char *name, ctf_id_t type, unsigned long offset,
+ int depth, void *arg)
+{
+ struct dt_visit_arg *dva = arg;
+ const void *data = dva->dv_data + offset/NBBY;
+ size_t typesize, typelen, asize;
+ char typename[DT_NAME_LEN] = {};
+ char datastr[DT_PRINT_LEN] = {};
+ const char *membername = NULL;
+ int64_t intdata = 0;
+ const char *ename;
+ ctf_encoding_t e;
+ ctf_arinfo_t a;
+ ctf_id_t rtype;
+ int issigned;
+ int rkind;
+ int i;
+
+ /* run out of data to display, or hit depth limit? */
+ if ((offset/NBBY) > dva->dv_size || depth >= DT_PRINT_DEPTH) {
+ dt_printf(dva->dv_dtp, dva->dv_fp, "%*s...\n",
+ dva->dv_startindent + depth, "");
+ return -1;
+ }
+ rtype = ctf_type_resolve(dva->dv_ctfp, type);
+ if (rtype == CTF_ERR)
+ rtype = type;
+ rkind = ctf_type_kind(dva->dv_ctfp, rtype);
+
+ /* we may have just shown the last member in a struct/union
+ * at depth + 1 (or deeper than that). If so close the "{".
+ * The final parenthesis close (at depth 0) will be done
+ * by the caller.
+ */
+ if (dva->dv_last_depth > depth)
+ dt_print_close_parens(dva, depth);
+
+ /* zeroed data? if so, do not display it. */
+ if (depth > 0) {
+ typesize = ctf_type_size(dva->dv_ctfp, rtype);
+ if (typesize < sizeof(zeros) && memcmp(data, zeros, typesize) == 0)
+ return 0;
+ }
+
+ dva->dv_kinds[depth] = rkind;
+ dva->dv_last_depth = depth;
+
+ ctf_type_name(dva->dv_ctfp, type, typename, sizeof(typename));
+ /* anon struct/union/enum will be "struct "; remove the space. */
+ typelen = strlen(typename);
+ if (typename[typelen - 1] == ' ')
+ typename[typelen - 1] = '\0';
+
+ /* print .<member_name> where available */
+ if (depth > 0) {
+ if (name && strlen(name) > 0)
+ membername = name;
+ }
+
+ switch (rkind) {
+ case CTF_K_STRUCT:
+ case CTF_K_UNION:
+ snprintf(datastr, sizeof(datastr), "(%s) {", typename);
+ break;
+ case CTF_K_ARRAY:
+ ctf_array_info(dva->dv_ctfp, rtype, &a);
+ asize = ctf_type_size(dva->dv_ctfp, a.ctr_contents);
+ snprintf(datastr, sizeof(datastr), "(%s) [%s", typename,
+ a.ctr_nelems == 0 ? "]," : "");
+ /* array member processing will be handled below... */
+ break;
+ case CTF_K_INTEGER:
+ /* type resolution sometimes fails for __u8 bitfields for some reason */
+ case CTF_K_TYPEDEF:
+ ctf_type_encoding(dva->dv_ctfp, rtype, &e);
+ issigned = (e.cte_format & CTF_INT_SIGNED) != 0;
+ switch (e.cte_bits) {
+ case 8:
+ if (issigned)
+ intdata = (int64_t)*(char *)data;
+ else
+ intdata = (int64_t)*(unsigned char *)data;
+ break;
+ case 16:
+ if (issigned)
+ intdata = (int64_t)*(int16_t *)data;
+ else
+ intdata = (int64_t)*(uint16_t *)data;
+ break;
+ case 32:
+ if (issigned)
+ intdata = (int64_t)*(int32_t *)data;
+ else
+ intdata = (int64_t)*(uint32_t *)data;
+ break;
+ case 64:
+ if (issigned) {
+ intdata = (int64_t)*(int64_t *)data;
+ } else {
+ /* cannot use intdata (int64_t) to hold uin64_t;
+ * stringify the cast and data here instead.
+ */
+ snprintf(datastr, sizeof(datastr), "(%s)%lu,",
+ typename, *(uint64_t *)data);
+ goto doprint;
+ }
+ break;
+ default:
+ if (e.cte_bits <= 64) {
+ unsigned int lshift, rshift;
+ uint64_t bitdata = *(uint64_t *)(dva->dv_data +
+ (offset + e.cte_offset)/NBBY);
+ /* shift bitfield data left then right to
+ * eliminate unneeded bits.
+ */
+ lshift = 63 - ((offset + e.cte_offset) % 64);
+ rshift = 64 - e.cte_bits;
+ bitdata = bitdata << lshift;
+ bitdata = bitdata >> rshift;
+ intdata = (int64_t)bitdata;
+ /* zero-value bitfield; do not display */
+ if (intdata == 0)
+ return 0;
+ } else {
+ /* > 64 bit integer vals not supported. */
+ snprintf(datastr, sizeof(datastr),
+ "(%s)?(%d bit value),",
+ typename, e.cte_bits);
+ }
+ break;
+ }
+ if ((e.cte_format & CTF_INT_CHAR) != 0 &&
+ strcmp(typename, "char") == 0) {
+ snprintf(datastr, sizeof(datastr), "(%s)'%c',",
+ typename, (char)intdata);
+ } else {
+ snprintf(datastr, sizeof(datastr), "(%s)%ld,",
+ typename, intdata);
+ }
+ break;
+ case CTF_K_ENUM:
+ ename = ctf_enum_name(dva->dv_ctfp, rtype, *(int *)data);
+ if (ename != NULL) {
+ /* exception to printing 0-valued data here;
+ * enum values are often meaningful with 0
+ * value so display the enum value string.
+ */
+ snprintf(datastr, sizeof(datastr), "(%s)%s,",
+ typename, ename);
+ } else {
+ snprintf(datastr, sizeof(datastr), "(%s)%d,",
+ typename, *(int *)data);
+ }
+ break;
+ case CTF_K_POINTER:
+ snprintf(datastr, sizeof(datastr), "(%s)0x%lx,",
+ typename, (uintptr_t)*(void **)data);
+ break;
+ default:
+ return 0;
+ }
+doprint:
+ /* format is
+ * [.<membername> = ](<type>)value,
+ * For example
+ * .foo = (bar)1,
+ */
+ if (dt_printf(dva->dv_dtp, dva->dv_fp, "%*s%s%s%s%s\n",
+ dva->dv_startindent + depth, "",
+ membername != NULL ? "." : "",
+ membername != NULL ? membername : "",
+ membername != NULL ? " = " : "",
+ datastr) < 0)
+ return -1;
+
+ /* type visit does not recurse into array elements... */
+ if (rkind == CTF_K_ARRAY) {
+ struct dt_visit_arg dva2;
+ int ischar = 0;
+
+ dva2.dv_dtp = dva->dv_dtp;
+ dva2.dv_ctfp = dva->dv_ctfp;
+ dva2.dv_fp = dva->dv_fp;
+ dva2.dv_data = dva->dv_data;
+ dva2.dv_startindent = dva->dv_startindent;
+ ctf_type_encoding(dva->dv_ctfp, a.ctr_contents, &e);
+ /* is array all 0s? Then skip... */
+ if (memcmp(data, zeros, asize * a.ctr_nelems) == 0)
+ return 0;
+ /* we check for early '\0' in char arrays... */
+ if ((e.cte_format & CTF_INT_CHAR) != 0 && e.cte_bits == 8)
+ ischar = 1;
+
+ for (i = 0; i < a.ctr_nelems; i++) {
+ int aoffset = (asize * 8) * i;
+
+ if (ischar && *(char *)(data + i) == '\0')
+ break;
+ if (dt_print_visit(NULL, a.ctr_contents,
+ offset + aoffset,
+ depth + 1, &dva2) < 0)
+ return -1;
+ }
+ if (a.ctr_nelems > 0) {
+ if (dt_printf(dva->dv_dtp, dva->dv_fp, "%*s],\n",
+ dva->dv_startindent + depth, "") < 0)
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static int
+dt_print_print(dtrace_hdl_t *dtp, FILE *fp, dtrace_recdesc_t *rec,
+ const caddr_t buf)
+{
+ caddr_t addr = (caddr_t)buf + rec->dtrd_offset;
+ dtrace_recdesc_t *data_rec = rec + 1;
+ caddr_t data = (caddr_t)buf + data_rec->dtrd_offset;
+ ctf_id_t type = (ctf_id_t)rec->dtrd_arg;
+ uint64_t printaddr, size;
+ struct dt_visit_arg dva;
+ ctf_file_t *ctfp;
+
+ if (dt_read_scalar(addr, rec, &printaddr) < 0)
+ return dt_set_errno(dtp, EDT_PRINT);
+ size = (size_t)data_rec->dtrd_arg;
+
+ ctfp = dtp->dt_ddefs->dm_ctfp;
+
+ dva.dv_dtp = dtp;
+ dva.dv_fp = fp;
+ dva.dv_ctfp = ctfp;
+ dva.dv_data = data;
+ dva.dv_size = size;
+ dva.dv_last_depth = 0;
+ dva.dv_startindent = DT_PRINT_STARTINDENT;
+
+ /* start display on newline to keep indentation consistent */
+ if (dt_printf(dtp, fp, "\n") < 0)
+ return -1;
+
+ ctf_type_visit(ctfp, type, dt_print_visit, &dva);
+
+ /* may need to close top-level parenthesis for struct/union. */
+ if (dt_print_close_parens(&dva, 0) < 0)
+ return -1;
+
+ return 2;
+}
+
/*
* The lifecycle of speculation buffers is as follows:
*
@@ -2530,6 +2840,12 @@ dt_consume_one_probe(dtrace_hdl_t *dtp, FILE *fp, char *data, uint32_t size,
case DTRACEACT_FREOPEN:
func = dtrace_freopen;
break;
+ case DTRACEACT_PRINT:
+ n = dt_print_print(dtp, fp, rec, data);
+ if (n < 0)
+ return -1;
+ i += n - 1;
+ continue;
default:
break;
}
diff --git a/libdtrace/dt_errtags.h b/libdtrace/dt_errtags.h
index 86c9bd4e..93f93ede 100644
--- a/libdtrace/dt_errtags.h
+++ b/libdtrace/dt_errtags.h
@@ -230,7 +230,9 @@ typedef enum {
D_LLQUANT_MATCHSTEPS, /* llquantize() mismatch on steps */
D_PCAP_ADDR, /* pcap() address bad type */
D_PCAP_PROTO, /* pcap() prototype mismatch */
- D_PCAP_SIZE /* pcap() bad size */
+ D_PCAP_SIZE, /* pcap() bad size */
+ D_PRINT_ADDR, /* print() address bad type */
+ D_PRINT_SIZE, /* print() address target bad size */
} dt_errtag_t;
extern const char *dt_errtag(dt_errtag_t);
diff --git a/libdtrace/dt_impl.h b/libdtrace/dt_impl.h
index 98a74910..5c429946 100644
--- a/libdtrace/dt_impl.h
+++ b/libdtrace/dt_impl.h
@@ -585,8 +585,9 @@ struct dtrace_hdl {
#define DT_ACT_UADDR DT_ACT(27) /* uaddr() action */
#define DT_ACT_SETOPT DT_ACT(28) /* setopt() action */
#define DT_ACT_PCAP DT_ACT(29) /* pcap() action */
+#define DT_ACT_PRINT DT_ACT(30) /* print() action */
-#define DT_ACT_MAX 30
+#define DT_ACT_MAX 31
/*
* Aggregation functions.
@@ -703,7 +704,8 @@ enum {
EDT_OBJIO, /* cannot read object file or module name mapping */
EDT_READMAXSTACK, /* cannot read kernel param perf_event_max_stack */
EDT_TRACEMEM, /* missing or corrupt tracemem() record */
- EDT_PCAP /* missing or corrupt pcap() record */
+ EDT_PCAP, /* missing or corrupt pcap() record */
+ EDT_PRINT, /* missing or corrupt print() record */
};
/*
diff --git a/libdtrace/dt_open.c b/libdtrace/dt_open.c
index b521d887..1ec165fc 100644
--- a/libdtrace/dt_open.c
+++ b/libdtrace/dt_open.c
@@ -263,6 +263,8 @@ static const dt_ident_t _dtrace_globals[] = {
&dt_idops_func, "void(@, ...)" },
{ "printf", DT_IDENT_ACTFUNC, 0, DT_ACT_PRINTF, DT_ATTR_STABCMN, DT_VERS_1_0,
&dt_idops_func, "void(string, ...)" },
+{ "print", DT_IDENT_ACTFUNC, 0, DT_ACT_PRINT, DT_ATTR_STABCMN, DT_VERS_2_0,
+ &dt_idops_func, "void(void *)" },
{ "probefunc", DT_IDENT_SCALAR, 0, DIF_VAR_PROBEFUNC,
DT_ATTR_STABCMN, DT_VERS_1_0, &dt_idops_type, "string" },
{ "probemod", DT_IDENT_SCALAR, 0, DIF_VAR_PROBEMOD,
diff --git a/libdtrace/dt_options.c b/libdtrace/dt_options.c
index 33995a6f..3b8eae06 100644
--- a/libdtrace/dt_options.c
+++ b/libdtrace/dt_options.c
@@ -915,6 +915,30 @@ dt_opt_strsize(dtrace_hdl_t *dtp, const char *arg, uintptr_t option)
return 0;
}
+#define DT_PRINT_DEF_SIZE 8192
+
+static int
+dt_opt_printsize(dtrace_hdl_t *dtp, const char *arg, uintptr_t option)
+{
+ dtrace_optval_t val = DT_PRINT_DEF_SIZE;
+ int rval;
+
+ if (arg != NULL) {
+ rval = dt_opt_size(dtp, arg, option);
+
+ if (rval != 0)
+ return rval;
+
+ val = dtp->dt_options[option];
+
+ if (val <= 0 || val > 65536)
+ val = DT_PRINT_DEF_SIZE;
+ }
+ dtp->dt_options[option] = P2ROUNDUP(val, 8);
+
+ return 0;
+}
+
static const struct {
const char *dtbp_name;
int dtbp_policy;
@@ -1160,6 +1184,7 @@ static const dt_option_t _dtrace_rtoptions[] = {
{ "strsize", dt_opt_strsize, DTRACEOPT_STRSIZE },
{ "ustackframes", dt_opt_runtime, DTRACEOPT_USTACKFRAMES },
{ "noresolve", dt_opt_runtime, DTRACEOPT_NORESOLVE },
+ { "printsize", dt_opt_printsize, DTRACEOPT_PRINTSIZE },
{ NULL }
};
--
2.39.3
More information about the DTrace-devel
mailing list