mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-30 07:39:12 -04:00
tracing/probes: Support dumping fetcharg program for debugging dynamic events
For debugging probe events, it is helpful to verify the compiled fetch instructions for each probe argument. This introduces a new kernel config CONFIG_PROBE_EVENTS_DUMP_FETCHARG to decode the instruction sequence of each argument and display it under a commented line starting with '#' immediately following the dynamic event definition (such as in dynamic_events, kprobe_events, uprobe_events, etc.). For example: /sys/kernel/tracing # cat dynamic_events p:kprobes/p_vfs_read_0 vfs_read arg1=+0(file):ustring arg2=%ax:x16 # arg1: ARG(0) -> ST_USTRING(offset=0,size=4) -> END # arg2: REG(80) -> ST_RAW(size=2) -> END Link: https://lore.kernel.org/all/178271359508.1176915.11895926894608440183.stgit@devnote2/ Assisted-by: Antigravity:gemini-3.5-flash Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
This commit is contained in:
@@ -779,6 +779,18 @@ config PROBE_EVENTS_BTF_ARGS
|
||||
kernel function entry or a tracepoint.
|
||||
This is available only if BTF (BPF Type Format) support is enabled.
|
||||
|
||||
config PROBE_EVENTS_DUMP_FETCHARG
|
||||
bool "Dump of dynamic probe event fetch-arguments"
|
||||
depends on PROBE_EVENTS
|
||||
default n
|
||||
help
|
||||
This shows the dump of fetch-arguments of dynamic probe events
|
||||
alongside their event definitions in the dynamic_events file
|
||||
as comment lines. This is useful to debug the probe events.
|
||||
Since this exposes the raw values in the dynamic_events file,
|
||||
it might be a security risk. Only enable it if you need to debug
|
||||
probe events themselves.
|
||||
|
||||
config KPROBE_EVENTS
|
||||
depends on KPROBES
|
||||
depends on HAVE_REGS_AND_STACK_ACCESS_API
|
||||
|
||||
@@ -87,6 +87,8 @@ static int eprobe_dyn_event_show(struct seq_file *m, struct dyn_event *ev)
|
||||
seq_printf(m, " %s=%s", ep->tp.args[i].name, ep->tp.args[i].comm);
|
||||
seq_putc(m, '\n');
|
||||
|
||||
trace_probe_dump_args(m, &ep->tp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -1449,6 +1449,8 @@ static int trace_fprobe_show(struct seq_file *m, struct dyn_event *ev)
|
||||
seq_printf(m, " %s=%s", tf->tp.args[i].name, tf->tp.args[i].comm);
|
||||
seq_putc(m, '\n');
|
||||
|
||||
trace_probe_dump_args(m, &tf->tp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -1320,6 +1320,8 @@ static int trace_kprobe_show(struct seq_file *m, struct dyn_event *ev)
|
||||
seq_printf(m, " %s=%s", tk->tp.args[i].name, tk->tp.args[i].comm);
|
||||
seq_putc(m, '\n');
|
||||
|
||||
trace_probe_dump_args(m, &tk->tp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -2403,3 +2403,99 @@ int trace_probe_print_args(struct trace_seq *s, struct probe_arg *args, int nr_a
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PROBE_EVENTS_DUMP_FETCHARG
|
||||
|
||||
struct fetch_op_decode {
|
||||
const char *name;
|
||||
void (*decode)(struct seq_file *m, struct fetch_insn *insn);
|
||||
};
|
||||
|
||||
static const struct fetch_op_decode fetch_op_decode[];
|
||||
|
||||
static void fetcharg_decode_none(struct seq_file *m, struct fetch_insn *insn)
|
||||
{
|
||||
seq_puts(m, fetch_op_decode[insn->op].name);
|
||||
}
|
||||
|
||||
static void fetcharg_decode_param(struct seq_file *m, struct fetch_insn *insn)
|
||||
{
|
||||
seq_printf(m, "%s(%u)", fetch_op_decode[insn->op].name, insn->param);
|
||||
}
|
||||
|
||||
static void fetcharg_decode_imm(struct seq_file *m, struct fetch_insn *insn)
|
||||
{
|
||||
seq_printf(m, "%s(0x%lx)", fetch_op_decode[insn->op].name, insn->immediate);
|
||||
}
|
||||
|
||||
static void fetcharg_decode_string(struct seq_file *m, struct fetch_insn *insn)
|
||||
{
|
||||
seq_printf(m, "%s(%s)", fetch_op_decode[insn->op].name, (char *)insn->data);
|
||||
}
|
||||
|
||||
static void fetcharg_decode_symbol(struct seq_file *m, struct fetch_insn *insn)
|
||||
{
|
||||
seq_printf(m, "%s(%s)", fetch_op_decode[insn->op].name, (char *)insn->data);
|
||||
}
|
||||
|
||||
static void fetcharg_decode_offset(struct seq_file *m, struct fetch_insn *insn)
|
||||
{
|
||||
seq_printf(m, "%s(offset=%d)", fetch_op_decode[insn->op].name, insn->offset);
|
||||
}
|
||||
|
||||
static void fetcharg_decode_store(struct seq_file *m, struct fetch_insn *insn)
|
||||
{
|
||||
if (insn->op == FETCH_OP_ST_RAW)
|
||||
seq_printf(m, "%s(size=%u)", fetch_op_decode[insn->op].name, insn->size);
|
||||
else
|
||||
seq_printf(m, "%s(offset=%d,size=%u)", fetch_op_decode[insn->op].name,
|
||||
insn->offset, insn->size);
|
||||
}
|
||||
|
||||
static void fetcharg_decode_bf(struct seq_file *m, struct fetch_insn *insn)
|
||||
{
|
||||
seq_printf(m, "%s(basesize=%u,lshift=%u,rshift=%u)",
|
||||
fetch_op_decode[insn->op].name, insn->basesize, insn->lshift, insn->rshift);
|
||||
}
|
||||
|
||||
static void fetcharg_decode_tp_arg(struct seq_file *m, struct fetch_insn *insn)
|
||||
{
|
||||
struct ftrace_event_field *field = insn->data;
|
||||
|
||||
seq_printf(m, "%s(%s)", fetch_op_decode[insn->op].name, field->name);
|
||||
}
|
||||
|
||||
#define FETCH_OP(opname, decode_fn) \
|
||||
[FETCH_OP_##opname] = { .name = #opname, .decode = fetcharg_decode_##decode_fn }
|
||||
|
||||
static const struct fetch_op_decode fetch_op_decode[] = FETCH_OP_LIST;
|
||||
#undef FETCH_OP
|
||||
|
||||
static void trace_probe_dump_arg(struct seq_file *m, struct probe_arg *parg)
|
||||
{
|
||||
int i;
|
||||
|
||||
seq_printf(m, "# %s: ", parg->name);
|
||||
for (i = 0; i < FETCH_INSN_MAX; i++) {
|
||||
struct fetch_insn *insn = parg->code + i;
|
||||
|
||||
if (insn->op >= ARRAY_SIZE(fetch_op_decode) || !fetch_op_decode[insn->op].decode)
|
||||
seq_printf(m, "unknown(%d)", insn->op);
|
||||
else
|
||||
fetch_op_decode[insn->op].decode(m, insn);
|
||||
|
||||
if (insn->op == FETCH_OP_END)
|
||||
break;
|
||||
seq_puts(m, " -> ");
|
||||
}
|
||||
seq_putc(m, '\n');
|
||||
}
|
||||
|
||||
void trace_probe_dump_args(struct seq_file *m, struct trace_probe *tp)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < tp->nr_args; i++)
|
||||
trace_probe_dump_arg(m, &tp->args[i]);
|
||||
}
|
||||
#endif /* CONFIG_PROBE_EVENTS_DUMP_FETCHARG */
|
||||
|
||||
@@ -83,38 +83,46 @@ static nokprobe_inline u32 update_data_loc(u32 loc, int consumed)
|
||||
/* Printing function type */
|
||||
typedef int (*print_type_func_t)(struct trace_seq *, void *, void *);
|
||||
|
||||
enum fetch_op {
|
||||
FETCH_OP_NOP = 0,
|
||||
// Stage 1 (load) ops
|
||||
FETCH_OP_REG, /* Register : .param = offset */
|
||||
FETCH_OP_STACK, /* Stack : .param = index */
|
||||
FETCH_OP_STACKP, /* Stack pointer */
|
||||
FETCH_OP_RETVAL, /* Return value */
|
||||
FETCH_OP_IMM, /* Immediate : .immediate */
|
||||
FETCH_OP_COMM, /* Current comm */
|
||||
FETCH_OP_ARG, /* Function argument : .param */
|
||||
FETCH_OP_FOFFS, /* File offset: .immediate */
|
||||
FETCH_OP_IMMSTR, /* Allocated string: .data */
|
||||
FETCH_OP_EDATA, /* Entry data: .offset */
|
||||
// Stage 2 (dereference) op
|
||||
FETCH_OP_DEREF, /* Dereference: .offset */
|
||||
FETCH_OP_UDEREF, /* User-space Dereference: .offset */
|
||||
// Stage 3 (store) ops
|
||||
FETCH_OP_ST_RAW, /* Raw: .size */
|
||||
FETCH_OP_ST_MEM, /* Mem: .offset, .size */
|
||||
FETCH_OP_ST_UMEM, /* Mem: .offset, .size */
|
||||
FETCH_OP_ST_STRING, /* String: .offset, .size */
|
||||
FETCH_OP_ST_USTRING, /* User String: .offset, .size */
|
||||
FETCH_OP_ST_SYMSTR, /* Kernel Symbol String: .offset, .size */
|
||||
FETCH_OP_ST_EDATA, /* Store Entry Data: .offset */
|
||||
// Stage 4 (modify) op
|
||||
FETCH_OP_MOD_BF, /* Bitfield: .basesize, .lshift, .rshift */
|
||||
// Stage 5 (loop) op
|
||||
FETCH_OP_LP_ARRAY, /* Array: .param = loop count */
|
||||
FETCH_OP_TP_ARG, /* Trace Point argument */
|
||||
FETCH_OP_END,
|
||||
FETCH_NOP_SYMBOL, /* Unresolved Symbol holder */
|
||||
};
|
||||
#define FETCH_OP_LIST { \
|
||||
/* Stage 1 (load) ops */ \
|
||||
FETCH_OP(NOP, none), /* NOP */ \
|
||||
FETCH_OP(REG, param), /* Register: .param = offset */ \
|
||||
FETCH_OP(STACK, param), /* Stack: .param = index */ \
|
||||
FETCH_OP(STACKP, none), /* Stack pointer */ \
|
||||
FETCH_OP(RETVAL, none), /* Return value */ \
|
||||
FETCH_OP(IMM, imm), /* Immediate: .immediate */ \
|
||||
FETCH_OP(COMM, none), /* Current comm */ \
|
||||
FETCH_OP(ARG, param), /* Argument: .param = index */ \
|
||||
FETCH_OP(FOFFS, imm), /* File offset: .immediate */ \
|
||||
FETCH_OP(IMMSTR, string), /* Allocated string: .data */ \
|
||||
FETCH_OP(EDATA, offset), /* Entry data: .offset */ \
|
||||
FETCH_OP(TP_ARG, tp_arg), /* Tracepoint argument: .data */\
|
||||
/* Stage 2 (dereference) ops */ \
|
||||
FETCH_OP(DEREF, offset), /* Dereference: .offset */ \
|
||||
FETCH_OP(UDEREF, offset), /* User-space dereference: .offset */\
|
||||
/* Stage 3 (store) ops */ \
|
||||
FETCH_OP(ST_RAW, store), /* Raw value: .size */ \
|
||||
FETCH_OP(ST_MEM, store), /* Memory: .offset, .size */ \
|
||||
FETCH_OP(ST_UMEM, store), /* User memory: .offset, .size */\
|
||||
FETCH_OP(ST_STRING, store), /* String: .offset, .size */ \
|
||||
FETCH_OP(ST_USTRING, store), /* User string: .offset, .size */\
|
||||
FETCH_OP(ST_SYMSTR, store), /* Symbol name: .offset, .size */\
|
||||
FETCH_OP(ST_EDATA, offset), /* Entry data: .offset */ \
|
||||
/* Stage 4 (modify) op */ \
|
||||
FETCH_OP(MOD_BF, bf), /* Bitfield: .basesize, .lshift, .rshift*/\
|
||||
/* Stage 5 (loop) op */ \
|
||||
FETCH_OP(LP_ARRAY, param), /* Loop array: .param = count */\
|
||||
/* End */ \
|
||||
FETCH_OP(END, none), \
|
||||
/* Unresolved Symbol holder */ \
|
||||
FETCH_OP(NOP_SYMBOL, symbol), /* Non loaded symbol: .data = symbol name */\
|
||||
}
|
||||
|
||||
#define FETCH_OP(opname, decode_fn) FETCH_OP_##opname
|
||||
enum fetch_op FETCH_OP_LIST;
|
||||
#undef FETCH_OP
|
||||
|
||||
#define FETCH_NOP_SYMBOL FETCH_OP_NOP_SYMBOL
|
||||
|
||||
struct fetch_insn {
|
||||
enum fetch_op op;
|
||||
@@ -370,6 +378,13 @@ bool trace_probe_match_command_args(struct trace_probe *tp,
|
||||
int trace_probe_create(const char *raw_command, int (*createfn)(int, const char **));
|
||||
int trace_probe_print_args(struct trace_seq *s, struct probe_arg *args, int nr_args,
|
||||
u8 *data, void *field);
|
||||
#ifdef CONFIG_PROBE_EVENTS_DUMP_FETCHARG
|
||||
void trace_probe_dump_args(struct seq_file *m, struct trace_probe *tp);
|
||||
#else
|
||||
static inline void trace_probe_dump_args(struct seq_file *m, struct trace_probe *tp)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_HAVE_FUNCTION_ARG_ACCESS_API
|
||||
int traceprobe_get_entry_data_size(struct trace_probe *tp);
|
||||
|
||||
@@ -765,6 +765,9 @@ static int trace_uprobe_show(struct seq_file *m, struct dyn_event *ev)
|
||||
seq_printf(m, " %s=%s", tu->tp.args[i].name, tu->tp.args[i].comm);
|
||||
|
||||
seq_putc(m, '\n');
|
||||
|
||||
trace_probe_dump_args(m, &tu->tp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user