mirror of
https://github.com/torvalds/linux.git
synced 2024-11-21 19:41:42 +00:00
Probes updates for v6.5:
- fprobe: Pass return address to the fprobe entry/exit callbacks so that the callbacks don't need to analyze pt_regs/stack to find the function return address. - kprobe events: cleanup usage of TPARG_FL_FENTRY and TPARG_FL_RETURN flags so that those are not set at once. - fprobe events: . Add a new fprobe events for tracing arbitrary function entry and exit as a trace event. . Add a new tracepoint events for tracing raw tracepoint as a trace event. This allows user to trace non user-exposed tracepoints. . Move eprobe's event parser code into probe event common file. . Introduce BTF (BPF type format) support to kernel probe (kprobe, fprobe and tracepoint probe) events so that user can specify traced function arguments by name. This also applies the type of argument when fetching the argument. . Introduce '$arg*' wildcard support if BTF is available. This expands the '$arg*' meta argument to all function argument automatically. . Check the return value types by BTF. If the function returns 'void', '$retval' is rejected. . Add some selftest script for fprobe events, tracepoint events and BTF support. . Update documentation about the fprobe events. . Some fixes for above features, document and selftests. - selftests for ftrace (except for new fprobe events): . Add a test case for multiple consecutive probes in a function which checks if ftrace based kprobe, optimized kprobe and normal kprobe can be defined in the same target function. . Add a test case for optimized probe, which checks whether kprobe can be optimized or not. -----BEGIN PGP SIGNATURE----- iQEzBAABCgAdFiEEh7BulGwFlgAOi5DV2/sHvwUrPxsFAmSa+9MACgkQ2/sHvwUr PxsmOAgAmUOIWtvH5py7AZpIRhCj8B18F6KnT7w2hByCsRxf7SaCqMhpBCk9VnYv 9fJFBHpvYRJEmpHoH3o2ET5AGfKVNac9z96AGI2qJ4ECWITd6I5+WfTdZ5ueVn2d f6DQ10mHXDHSMFbuqfYWSHtkeivJpWpUNHhwzPb4doNOe06bZNfVuSgnksFg1at5 kq16HbvGnhPzdO4YHmvqwjmRHr5/nCI1KDE9xIBcqNtWFbiRigC11zaZEUkLX+vT F63ShyfCK718AiwDfnjXpGkXAiVOZuAIR8RELaSqQ92YHCFKq5k9K4++WllPR5f9 AxjVultFDiCd4oSPgYpQkjuZdFq9NA== =IhmY -----END PGP SIGNATURE----- Merge tag 'probes-v6.5' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace Pull probes updates from Masami Hiramatsu: - fprobe: Pass return address to the fprobe entry/exit callbacks so that the callbacks don't need to analyze pt_regs/stack to find the function return address. - kprobe events: cleanup usage of TPARG_FL_FENTRY and TPARG_FL_RETURN flags so that those are not set at once. - fprobe events: - Add a new fprobe events for tracing arbitrary function entry and exit as a trace event. - Add a new tracepoint events for tracing raw tracepoint as a trace event. This allows user to trace non user-exposed tracepoints. - Move eprobe's event parser code into probe event common file. - Introduce BTF (BPF type format) support to kernel probe (kprobe, fprobe and tracepoint probe) events so that user can specify traced function arguments by name. This also applies the type of argument when fetching the argument. - Introduce '$arg*' wildcard support if BTF is available. This expands the '$arg*' meta argument to all function argument automatically. - Check the return value types by BTF. If the function returns 'void', '$retval' is rejected. - Add some selftest script for fprobe events, tracepoint events and BTF support. - Update documentation about the fprobe events. - Some fixes for above features, document and selftests. - selftests for ftrace (in addition to the new fprobe events): - Add a test case for multiple consecutive probes in a function which checks if ftrace based kprobe, optimized kprobe and normal kprobe can be defined in the same target function. - Add a test case for optimized probe, which checks whether kprobe can be optimized or not. * tag 'probes-v6.5' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace: tracing/probes: Fix tracepoint event with $arg* to fetch correct argument Documentation: Fix typo of reference file name tracing/probes: Fix to return NULL and keep using current argc selftests/ftrace: Add new test case which checks for optimized probes selftests/ftrace: Add new test case which adds multiple consecutive probes in a function Documentation: tracing/probes: Add fprobe event tracing document selftests/ftrace: Add BTF arguments test cases selftests/ftrace: Add tracepoint probe test case tracing/probes: Add BTF retval type support tracing/probes: Add $arg* meta argument for all function args tracing/probes: Support function parameters if BTF is available tracing/probes: Move event parameter fetching code to common parser tracing/probes: Add tracepoint support on fprobe_events selftests/ftrace: Add fprobe related testcases tracing/probes: Add fprobe events for tracing function entry and exit. tracing/probes: Avoid setting TPARG_FL_FENTRY and TPARG_FL_RETURN fprobe: Pass return address to the handlers
This commit is contained in:
commit
d2a6fd45c5
188
Documentation/trace/fprobetrace.rst
Normal file
188
Documentation/trace/fprobetrace.rst
Normal file
@ -0,0 +1,188 @@
|
||||
.. SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
==========================
|
||||
Fprobe-based Event Tracing
|
||||
==========================
|
||||
|
||||
.. Author: Masami Hiramatsu <mhiramat@kernel.org>
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
Fprobe event is similar to the kprobe event, but limited to probe on
|
||||
the function entry and exit only. It is good enough for many use cases
|
||||
which only traces some specific functions.
|
||||
|
||||
This document also covers tracepoint probe events (tprobe) since this
|
||||
is also works only on the tracepoint entry. User can trace a part of
|
||||
tracepoint argument, or the tracepoint without trace-event, which is
|
||||
not exposed on tracefs.
|
||||
|
||||
As same as other dynamic events, fprobe events and tracepoint probe
|
||||
events are defined via `dynamic_events` interface file on tracefs.
|
||||
|
||||
Synopsis of fprobe-events
|
||||
-------------------------
|
||||
::
|
||||
|
||||
f[:[GRP1/][EVENT1]] SYM [FETCHARGS] : Probe on function entry
|
||||
f[MAXACTIVE][:[GRP1/][EVENT1]] SYM%return [FETCHARGS] : Probe on function exit
|
||||
t[:[GRP2/][EVENT2]] TRACEPOINT [FETCHARGS] : Probe on tracepoint
|
||||
|
||||
GRP1 : Group name for fprobe. If omitted, use "fprobes" for it.
|
||||
GRP2 : Group name for tprobe. If omitted, use "tracepoints" for it.
|
||||
EVENT1 : Event name for fprobe. If omitted, the event name is
|
||||
"SYM__entry" or "SYM__exit".
|
||||
EVENT2 : Event name for tprobe. If omitted, the event name is
|
||||
the same as "TRACEPOINT", but if the "TRACEPOINT" starts
|
||||
with a digit character, "_TRACEPOINT" is used.
|
||||
MAXACTIVE : Maximum number of instances of the specified function that
|
||||
can be probed simultaneously, or 0 for the default value
|
||||
as defined in Documentation/trace/fprobe.rst
|
||||
|
||||
FETCHARGS : Arguments. Each probe can have up to 128 args.
|
||||
ARG : Fetch "ARG" function argument using BTF (only for function
|
||||
entry or tracepoint.) (\*1)
|
||||
@ADDR : Fetch memory at ADDR (ADDR should be in kernel)
|
||||
@SYM[+|-offs] : Fetch memory at SYM +|- offs (SYM should be a data symbol)
|
||||
$stackN : Fetch Nth entry of stack (N >= 0)
|
||||
$stack : Fetch stack address.
|
||||
$argN : Fetch the Nth function argument. (N >= 1) (\*2)
|
||||
$retval : Fetch return value.(\*3)
|
||||
$comm : Fetch current task comm.
|
||||
+|-[u]OFFS(FETCHARG) : Fetch memory at FETCHARG +|- OFFS address.(\*4)(\*5)
|
||||
\IMM : Store an immediate value to the argument.
|
||||
NAME=FETCHARG : Set NAME as the argument name of FETCHARG.
|
||||
FETCHARG:TYPE : Set TYPE as the type of FETCHARG. Currently, basic types
|
||||
(u8/u16/u32/u64/s8/s16/s32/s64), hexadecimal types
|
||||
(x8/x16/x32/x64), "char", "string", "ustring", "symbol", "symstr"
|
||||
and bitfield are supported.
|
||||
|
||||
(\*1) This is available only when BTF is enabled.
|
||||
(\*2) only for the probe on function entry (offs == 0).
|
||||
(\*3) only for return probe.
|
||||
(\*4) this is useful for fetching a field of data structures.
|
||||
(\*5) "u" means user-space dereference.
|
||||
|
||||
For the details of TYPE, see :ref:`kprobetrace documentation <kprobetrace_types>`.
|
||||
|
||||
BTF arguments
|
||||
-------------
|
||||
BTF (BPF Type Format) argument allows user to trace function and tracepoint
|
||||
parameters by its name instead of ``$argN``. This feature is available if the
|
||||
kernel is configured with CONFIG_BPF_SYSCALL and CONFIG_DEBUG_INFO_BTF.
|
||||
If user only specify the BTF argument, the event's argument name is also
|
||||
automatically set by the given name. ::
|
||||
|
||||
# echo 'f:myprobe vfs_read count pos' >> dynamic_events
|
||||
# cat dynamic_events
|
||||
f:fprobes/myprobe vfs_read count=count pos=pos
|
||||
|
||||
It also chooses the fetch type from BTF information. For example, in the above
|
||||
example, the ``count`` is unsigned long, and the ``pos`` is a pointer. Thus, both
|
||||
are converted to 64bit unsigned long, but only ``pos`` has "%Lx" print-format as
|
||||
below ::
|
||||
|
||||
# cat events/fprobes/myprobe/format
|
||||
name: myprobe
|
||||
ID: 1313
|
||||
format:
|
||||
field:unsigned short common_type; offset:0; size:2; signed:0;
|
||||
field:unsigned char common_flags; offset:2; size:1; signed:0;
|
||||
field:unsigned char common_preempt_count; offset:3; size:1; signed:0;
|
||||
field:int common_pid; offset:4; size:4; signed:1;
|
||||
|
||||
field:unsigned long __probe_ip; offset:8; size:8; signed:0;
|
||||
field:u64 count; offset:16; size:8; signed:0;
|
||||
field:u64 pos; offset:24; size:8; signed:0;
|
||||
|
||||
print fmt: "(%lx) count=%Lu pos=0x%Lx", REC->__probe_ip, REC->count, REC->pos
|
||||
|
||||
If user unsures the name of arguments, ``$arg*`` will be helpful. The ``$arg*``
|
||||
is expanded to all function arguments of the function or the tracepoint. ::
|
||||
|
||||
# echo 'f:myprobe vfs_read $arg*' >> dynamic_events
|
||||
# cat dynamic_events
|
||||
f:fprobes/myprobe vfs_read file=file buf=buf count=count pos=pos
|
||||
|
||||
BTF also affects the ``$retval``. If user doesn't set any type, the retval type is
|
||||
automatically picked from the BTF. If the function returns ``void``, ``$retval``
|
||||
is rejected.
|
||||
|
||||
Usage examples
|
||||
--------------
|
||||
Here is an example to add fprobe events on ``vfs_read()`` function entry
|
||||
and exit, with BTF arguments.
|
||||
::
|
||||
|
||||
# echo 'f vfs_read $arg*' >> dynamic_events
|
||||
# echo 'f vfs_read%return $retval' >> dynamic_events
|
||||
# cat dynamic_events
|
||||
f:fprobes/vfs_read__entry vfs_read file=file buf=buf count=count pos=pos
|
||||
f:fprobes/vfs_read__exit vfs_read%return arg1=$retval
|
||||
# echo 1 > events/fprobes/enable
|
||||
# head -n 20 trace | tail
|
||||
# TASK-PID CPU# ||||| TIMESTAMP FUNCTION
|
||||
# | | | ||||| | |
|
||||
sh-70 [000] ...1. 335.883195: vfs_read__entry: (vfs_read+0x4/0x340) file=0xffff888005cf9a80 buf=0x7ffef36c6879 count=1 pos=0xffffc900005aff08
|
||||
sh-70 [000] ..... 335.883208: vfs_read__exit: (ksys_read+0x75/0x100 <- vfs_read) arg1=1
|
||||
sh-70 [000] ...1. 335.883220: vfs_read__entry: (vfs_read+0x4/0x340) file=0xffff888005cf9a80 buf=0x7ffef36c6879 count=1 pos=0xffffc900005aff08
|
||||
sh-70 [000] ..... 335.883224: vfs_read__exit: (ksys_read+0x75/0x100 <- vfs_read) arg1=1
|
||||
sh-70 [000] ...1. 335.883232: vfs_read__entry: (vfs_read+0x4/0x340) file=0xffff888005cf9a80 buf=0x7ffef36c687a count=1 pos=0xffffc900005aff08
|
||||
sh-70 [000] ..... 335.883237: vfs_read__exit: (ksys_read+0x75/0x100 <- vfs_read) arg1=1
|
||||
sh-70 [000] ...1. 336.050329: vfs_read__entry: (vfs_read+0x4/0x340) file=0xffff888005cf9a80 buf=0x7ffef36c6879 count=1 pos=0xffffc900005aff08
|
||||
sh-70 [000] ..... 336.050343: vfs_read__exit: (ksys_read+0x75/0x100 <- vfs_read) arg1=1
|
||||
|
||||
You can see all function arguments and return values are recorded as signed int.
|
||||
|
||||
Also, here is an example of tracepoint events on ``sched_switch`` tracepoint.
|
||||
To compare the result, this also enables the ``sched_switch`` traceevent too.
|
||||
::
|
||||
|
||||
# echo 't sched_switch $arg*' >> dynamic_events
|
||||
# echo 1 > events/sched/sched_switch/enable
|
||||
# echo 1 > events/tracepoints/sched_switch/enable
|
||||
# echo > trace
|
||||
# head -n 20 trace | tail
|
||||
# TASK-PID CPU# ||||| TIMESTAMP FUNCTION
|
||||
# | | | ||||| | |
|
||||
sh-70 [000] d..2. 3912.083993: sched_switch: prev_comm=sh prev_pid=70 prev_prio=120 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120
|
||||
sh-70 [000] d..3. 3912.083995: sched_switch: (__probestub_sched_switch+0x4/0x10) preempt=0 prev=0xffff88800664e100 next=0xffffffff828229c0 prev_state=1
|
||||
<idle>-0 [000] d..2. 3912.084183: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=rcu_preempt next_pid=16 next_prio=120
|
||||
<idle>-0 [000] d..3. 3912.084184: sched_switch: (__probestub_sched_switch+0x4/0x10) preempt=0 prev=0xffffffff828229c0 next=0xffff888004208000 prev_state=0
|
||||
rcu_preempt-16 [000] d..2. 3912.084196: sched_switch: prev_comm=rcu_preempt prev_pid=16 prev_prio=120 prev_state=I ==> next_comm=swapper/0 next_pid=0 next_prio=120
|
||||
rcu_preempt-16 [000] d..3. 3912.084196: sched_switch: (__probestub_sched_switch+0x4/0x10) preempt=0 prev=0xffff888004208000 next=0xffffffff828229c0 prev_state=1026
|
||||
<idle>-0 [000] d..2. 3912.085191: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=rcu_preempt next_pid=16 next_prio=120
|
||||
<idle>-0 [000] d..3. 3912.085191: sched_switch: (__probestub_sched_switch+0x4/0x10) preempt=0 prev=0xffffffff828229c0 next=0xffff888004208000 prev_state=0
|
||||
|
||||
As you can see, the ``sched_switch`` trace-event shows *cooked* parameters, on
|
||||
the other hand, the ``sched_switch`` tracepoint probe event shows *raw*
|
||||
parameters. This means you can access any field values in the task
|
||||
structure pointed by the ``prev`` and ``next`` arguments.
|
||||
|
||||
For example, usually ``task_struct::start_time`` is not traced, but with this
|
||||
traceprobe event, you can trace it as below.
|
||||
::
|
||||
|
||||
# echo 't sched_switch comm=+1896(next):string start_time=+1728(next):u64' > dynamic_events
|
||||
# head -n 20 trace | tail
|
||||
# TASK-PID CPU# ||||| TIMESTAMP FUNCTION
|
||||
# | | | ||||| | |
|
||||
sh-70 [000] d..3. 5606.686577: sched_switch: (__probestub_sched_switch+0x4/0x10) comm="rcu_preempt" usage=1 start_time=245000000
|
||||
rcu_preempt-16 [000] d..3. 5606.686602: sched_switch: (__probestub_sched_switch+0x4/0x10) comm="sh" usage=1 start_time=1596095526
|
||||
sh-70 [000] d..3. 5606.686637: sched_switch: (__probestub_sched_switch+0x4/0x10) comm="swapper/0" usage=2 start_time=0
|
||||
<idle>-0 [000] d..3. 5606.687190: sched_switch: (__probestub_sched_switch+0x4/0x10) comm="rcu_preempt" usage=1 start_time=245000000
|
||||
rcu_preempt-16 [000] d..3. 5606.687202: sched_switch: (__probestub_sched_switch+0x4/0x10) comm="swapper/0" usage=2 start_time=0
|
||||
<idle>-0 [000] d..3. 5606.690317: sched_switch: (__probestub_sched_switch+0x4/0x10) comm="kworker/0:1" usage=1 start_time=137000000
|
||||
kworker/0:1-14 [000] d..3. 5606.690339: sched_switch: (__probestub_sched_switch+0x4/0x10) comm="swapper/0" usage=2 start_time=0
|
||||
<idle>-0 [000] d..3. 5606.692368: sched_switch: (__probestub_sched_switch+0x4/0x10) comm="kworker/0:1" usage=1 start_time=137000000
|
||||
|
||||
Currently, to find the offset of a specific field in the data structure,
|
||||
you need to build kernel with debuginfo and run `perf probe` command with
|
||||
`-D` option. e.g.
|
||||
::
|
||||
|
||||
# perf probe -D "__probestub_sched_switch next->comm:string next->start_time"
|
||||
p:probe/__probestub_sched_switch __probestub_sched_switch+0 comm=+1896(%cx):string start_time=+1728(%cx):u64
|
||||
|
||||
And replace the ``%cx`` with the ``next``.
|
@ -13,6 +13,7 @@ Linux Tracing Technologies
|
||||
kprobes
|
||||
kprobetrace
|
||||
uprobetracer
|
||||
fprobetrace
|
||||
tracepoints
|
||||
events
|
||||
events-kmem
|
||||
|
@ -66,6 +66,8 @@ Synopsis of kprobe_events
|
||||
(\*3) this is useful for fetching a field of data structures.
|
||||
(\*4) "u" means user-space dereference. See :ref:`user_mem_access`.
|
||||
|
||||
.. _kprobetrace_types:
|
||||
|
||||
Types
|
||||
-----
|
||||
Several types are supported for fetchargs. Kprobe tracer will access memory
|
||||
|
@ -35,9 +35,11 @@ struct fprobe {
|
||||
int nr_maxactive;
|
||||
|
||||
int (*entry_handler)(struct fprobe *fp, unsigned long entry_ip,
|
||||
struct pt_regs *regs, void *entry_data);
|
||||
unsigned long ret_ip, struct pt_regs *regs,
|
||||
void *entry_data);
|
||||
void (*exit_handler)(struct fprobe *fp, unsigned long entry_ip,
|
||||
struct pt_regs *regs, void *entry_data);
|
||||
unsigned long ret_ip, struct pt_regs *regs,
|
||||
void *entry_data);
|
||||
};
|
||||
|
||||
/* This fprobe is soft-disabled. */
|
||||
@ -64,6 +66,7 @@ int register_fprobe(struct fprobe *fp, const char *filter, const char *notfilter
|
||||
int register_fprobe_ips(struct fprobe *fp, unsigned long *addrs, int num);
|
||||
int register_fprobe_syms(struct fprobe *fp, const char **syms, int num);
|
||||
int unregister_fprobe(struct fprobe *fp);
|
||||
bool fprobe_is_registered(struct fprobe *fp);
|
||||
#else
|
||||
static inline int register_fprobe(struct fprobe *fp, const char *filter, const char *notfilter)
|
||||
{
|
||||
@ -81,6 +84,10 @@ static inline int unregister_fprobe(struct fprobe *fp)
|
||||
{
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
static inline bool fprobe_is_registered(struct fprobe *fp)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
|
@ -14,7 +14,7 @@
|
||||
|
||||
struct rethook_node;
|
||||
|
||||
typedef void (*rethook_handler_t) (struct rethook_node *, void *, struct pt_regs *);
|
||||
typedef void (*rethook_handler_t) (struct rethook_node *, void *, unsigned long, struct pt_regs *);
|
||||
|
||||
/**
|
||||
* struct rethook - The rethook management data structure.
|
||||
|
@ -318,6 +318,7 @@ enum {
|
||||
TRACE_EVENT_FL_KPROBE_BIT,
|
||||
TRACE_EVENT_FL_UPROBE_BIT,
|
||||
TRACE_EVENT_FL_EPROBE_BIT,
|
||||
TRACE_EVENT_FL_FPROBE_BIT,
|
||||
TRACE_EVENT_FL_CUSTOM_BIT,
|
||||
};
|
||||
|
||||
@ -332,6 +333,7 @@ enum {
|
||||
* KPROBE - Event is a kprobe
|
||||
* UPROBE - Event is a uprobe
|
||||
* EPROBE - Event is an event probe
|
||||
* FPROBE - Event is an function probe
|
||||
* CUSTOM - Event is a custom event (to be attached to an exsiting tracepoint)
|
||||
* This is set when the custom event has not been attached
|
||||
* to a tracepoint yet, then it is cleared when it is.
|
||||
@ -346,6 +348,7 @@ enum {
|
||||
TRACE_EVENT_FL_KPROBE = (1 << TRACE_EVENT_FL_KPROBE_BIT),
|
||||
TRACE_EVENT_FL_UPROBE = (1 << TRACE_EVENT_FL_UPROBE_BIT),
|
||||
TRACE_EVENT_FL_EPROBE = (1 << TRACE_EVENT_FL_EPROBE_BIT),
|
||||
TRACE_EVENT_FL_FPROBE = (1 << TRACE_EVENT_FL_FPROBE_BIT),
|
||||
TRACE_EVENT_FL_CUSTOM = (1 << TRACE_EVENT_FL_CUSTOM_BIT),
|
||||
};
|
||||
|
||||
|
@ -35,6 +35,7 @@ struct tracepoint {
|
||||
struct static_call_key *static_call_key;
|
||||
void *static_call_tramp;
|
||||
void *iterator;
|
||||
void *probestub;
|
||||
int (*regfunc)(void);
|
||||
void (*unregfunc)(void);
|
||||
struct tracepoint_func __rcu *funcs;
|
||||
|
@ -303,6 +303,7 @@ static inline struct tracepoint *tracepoint_ptr_deref(tracepoint_ptr_t *p)
|
||||
__section("__tracepoints_strings") = #_name; \
|
||||
extern struct static_call_key STATIC_CALL_KEY(tp_func_##_name); \
|
||||
int __traceiter_##_name(void *__data, proto); \
|
||||
void __probestub_##_name(void *__data, proto); \
|
||||
struct tracepoint __tracepoint_##_name __used \
|
||||
__section("__tracepoints") = { \
|
||||
.name = __tpstrtab_##_name, \
|
||||
@ -310,6 +311,7 @@ static inline struct tracepoint *tracepoint_ptr_deref(tracepoint_ptr_t *p)
|
||||
.static_call_key = &STATIC_CALL_KEY(tp_func_##_name), \
|
||||
.static_call_tramp = STATIC_CALL_TRAMP_ADDR(tp_func_##_name), \
|
||||
.iterator = &__traceiter_##_name, \
|
||||
.probestub = &__probestub_##_name, \
|
||||
.regfunc = _reg, \
|
||||
.unregfunc = _unreg, \
|
||||
.funcs = NULL }; \
|
||||
@ -330,6 +332,9 @@ static inline struct tracepoint *tracepoint_ptr_deref(tracepoint_ptr_t *p)
|
||||
} \
|
||||
return 0; \
|
||||
} \
|
||||
void __probestub_##_name(void *__data, proto) \
|
||||
{ \
|
||||
} \
|
||||
DEFINE_STATIC_CALL(tp_func_##_name, __traceiter_##_name);
|
||||
|
||||
#define DEFINE_TRACE(name, proto, args) \
|
||||
|
@ -2127,6 +2127,7 @@ static int pre_handler_kretprobe(struct kprobe *p, struct pt_regs *regs)
|
||||
NOKPROBE_SYMBOL(pre_handler_kretprobe);
|
||||
|
||||
static void kretprobe_rethook_handler(struct rethook_node *rh, void *data,
|
||||
unsigned long ret_addr,
|
||||
struct pt_regs *regs)
|
||||
{
|
||||
struct kretprobe *rp = (struct kretprobe *)data;
|
||||
|
@ -665,6 +665,32 @@ config BLK_DEV_IO_TRACE
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
config FPROBE_EVENTS
|
||||
depends on FPROBE
|
||||
depends on HAVE_REGS_AND_STACK_ACCESS_API
|
||||
bool "Enable fprobe-based dynamic events"
|
||||
select TRACING
|
||||
select PROBE_EVENTS
|
||||
select DYNAMIC_EVENTS
|
||||
default y
|
||||
help
|
||||
This allows user to add tracing events on the function entry and
|
||||
exit via ftrace interface. The syntax is same as the kprobe events
|
||||
and the kprobe events on function entry and exit will be
|
||||
transparently converted to this fprobe events.
|
||||
|
||||
config PROBE_EVENTS_BTF_ARGS
|
||||
depends on HAVE_FUNCTION_ARG_ACCESS_API
|
||||
depends on FPROBE_EVENTS || KPROBE_EVENTS
|
||||
depends on DEBUG_INFO_BTF && BPF_SYSCALL
|
||||
bool "Support BTF function arguments for probe events"
|
||||
default y
|
||||
help
|
||||
The user can specify the arguments of the probe event using the names
|
||||
of the arguments of the probed function, when the probe location is a
|
||||
kernel function entry or a tracepoint.
|
||||
This is available only if BTF (BPF Type Format) support is enabled.
|
||||
|
||||
config KPROBE_EVENTS
|
||||
depends on KPROBES
|
||||
depends on HAVE_REGS_AND_STACK_ACCESS_API
|
||||
|
@ -104,6 +104,7 @@ obj-$(CONFIG_BOOTTIME_TRACING) += trace_boot.o
|
||||
obj-$(CONFIG_FTRACE_RECORD_RECURSION) += trace_recursion_record.o
|
||||
obj-$(CONFIG_FPROBE) += fprobe.o
|
||||
obj-$(CONFIG_RETHOOK) += rethook.o
|
||||
obj-$(CONFIG_FPROBE_EVENTS) += trace_fprobe.o
|
||||
|
||||
obj-$(CONFIG_TRACEPOINT_BENCHMARK) += trace_benchmark.o
|
||||
obj-$(CONFIG_RV) += rv/
|
||||
|
@ -2652,7 +2652,8 @@ kprobe_multi_link_prog_run(struct bpf_kprobe_multi_link *link,
|
||||
|
||||
static int
|
||||
kprobe_multi_link_handler(struct fprobe *fp, unsigned long fentry_ip,
|
||||
struct pt_regs *regs, void *data)
|
||||
unsigned long ret_ip, struct pt_regs *regs,
|
||||
void *data)
|
||||
{
|
||||
struct bpf_kprobe_multi_link *link;
|
||||
|
||||
@ -2663,7 +2664,8 @@ kprobe_multi_link_handler(struct fprobe *fp, unsigned long fentry_ip,
|
||||
|
||||
static void
|
||||
kprobe_multi_link_exit_handler(struct fprobe *fp, unsigned long fentry_ip,
|
||||
struct pt_regs *regs, void *data)
|
||||
unsigned long ret_ip, struct pt_regs *regs,
|
||||
void *data)
|
||||
{
|
||||
struct bpf_kprobe_multi_link *link;
|
||||
|
||||
|
@ -46,7 +46,7 @@ static inline void __fprobe_handler(unsigned long ip, unsigned long parent_ip,
|
||||
}
|
||||
|
||||
if (fp->entry_handler)
|
||||
ret = fp->entry_handler(fp, ip, ftrace_get_regs(fregs), entry_data);
|
||||
ret = fp->entry_handler(fp, ip, parent_ip, ftrace_get_regs(fregs), entry_data);
|
||||
|
||||
/* If entry_handler returns !0, nmissed is not counted. */
|
||||
if (rh) {
|
||||
@ -112,7 +112,7 @@ static void fprobe_kprobe_handler(unsigned long ip, unsigned long parent_ip,
|
||||
}
|
||||
|
||||
static void fprobe_exit_handler(struct rethook_node *rh, void *data,
|
||||
struct pt_regs *regs)
|
||||
unsigned long ret_ip, struct pt_regs *regs)
|
||||
{
|
||||
struct fprobe *fp = (struct fprobe *)data;
|
||||
struct fprobe_rethook_node *fpr;
|
||||
@ -133,7 +133,7 @@ static void fprobe_exit_handler(struct rethook_node *rh, void *data,
|
||||
return;
|
||||
}
|
||||
|
||||
fp->exit_handler(fp, fpr->entry_ip, regs,
|
||||
fp->exit_handler(fp, fpr->entry_ip, ret_ip, regs,
|
||||
fp->entry_data_size ? (void *)fpr->data : NULL);
|
||||
ftrace_test_recursion_unlock(bit);
|
||||
}
|
||||
@ -348,6 +348,14 @@ int register_fprobe_syms(struct fprobe *fp, const char **syms, int num)
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(register_fprobe_syms);
|
||||
|
||||
bool fprobe_is_registered(struct fprobe *fp)
|
||||
{
|
||||
if (!fp || (fp->ops.saved_func != fprobe_handler &&
|
||||
fp->ops.saved_func != fprobe_kprobe_handler))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* unregister_fprobe() - Unregister fprobe from ftrace
|
||||
* @fp: A fprobe data structure to be unregistered.
|
||||
@ -360,8 +368,7 @@ int unregister_fprobe(struct fprobe *fp)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!fp || (fp->ops.saved_func != fprobe_handler &&
|
||||
fp->ops.saved_func != fprobe_kprobe_handler))
|
||||
if (!fprobe_is_registered(fp))
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
|
@ -301,7 +301,8 @@ unsigned long rethook_trampoline_handler(struct pt_regs *regs,
|
||||
break;
|
||||
handler = READ_ONCE(rhn->rethook->handler);
|
||||
if (handler)
|
||||
handler(rhn, rhn->rethook->data, regs);
|
||||
handler(rhn, rhn->rethook->data,
|
||||
correct_ret_addr, regs);
|
||||
|
||||
if (first == node)
|
||||
break;
|
||||
|
@ -5672,10 +5672,17 @@ static const char readme_msg[] =
|
||||
" uprobe_events\t\t- Create/append/remove/show the userspace dynamic events\n"
|
||||
"\t\t\t Write into this file to define/undefine new trace events.\n"
|
||||
#endif
|
||||
#if defined(CONFIG_KPROBE_EVENTS) || defined(CONFIG_UPROBE_EVENTS)
|
||||
#if defined(CONFIG_KPROBE_EVENTS) || defined(CONFIG_UPROBE_EVENTS) || \
|
||||
defined(CONFIG_FPROBE_EVENTS)
|
||||
"\t accepts: event-definitions (one definition per line)\n"
|
||||
#if defined(CONFIG_KPROBE_EVENTS) || defined(CONFIG_UPROBE_EVENTS)
|
||||
"\t Format: p[:[<group>/][<event>]] <place> [<args>]\n"
|
||||
"\t r[maxactive][:[<group>/][<event>]] <place> [<args>]\n"
|
||||
#endif
|
||||
#ifdef CONFIG_FPROBE_EVENTS
|
||||
"\t f[:[<group>/][<event>]] <func-name>[%return] [<args>]\n"
|
||||
"\t t[:[<group>/][<event>]] <tracepoint> [<args>]\n"
|
||||
#endif
|
||||
#ifdef CONFIG_HIST_TRIGGERS
|
||||
"\t s:[synthetic/]<event> <field> [<field>]\n"
|
||||
#endif
|
||||
@ -5691,7 +5698,11 @@ static const char readme_msg[] =
|
||||
"\t args: <name>=fetcharg[:type]\n"
|
||||
"\t fetcharg: (%<register>|$<efield>), @<address>, @<symbol>[+|-<offset>],\n"
|
||||
#ifdef CONFIG_HAVE_FUNCTION_ARG_ACCESS_API
|
||||
#ifdef CONFIG_PROBE_EVENTS_BTF_ARGS
|
||||
"\t $stack<index>, $stack, $retval, $comm, $arg<N>, <argname>\n"
|
||||
#else
|
||||
"\t $stack<index>, $stack, $retval, $comm, $arg<N>,\n"
|
||||
#endif
|
||||
#else
|
||||
"\t $stack<index>, $stack, $retval, $comm,\n"
|
||||
#endif
|
||||
|
@ -148,6 +148,17 @@ struct kretprobe_trace_entry_head {
|
||||
unsigned long ret_ip;
|
||||
};
|
||||
|
||||
struct fentry_trace_entry_head {
|
||||
struct trace_entry ent;
|
||||
unsigned long ip;
|
||||
};
|
||||
|
||||
struct fexit_trace_entry_head {
|
||||
struct trace_entry ent;
|
||||
unsigned long func;
|
||||
unsigned long ret_ip;
|
||||
};
|
||||
|
||||
#define TRACE_BUF_SIZE 1024
|
||||
|
||||
struct trace_array;
|
||||
|
@ -227,37 +227,6 @@ error:
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
static int trace_eprobe_tp_arg_update(struct trace_eprobe *ep, int i)
|
||||
{
|
||||
struct probe_arg *parg = &ep->tp.args[i];
|
||||
struct ftrace_event_field *field;
|
||||
struct list_head *head;
|
||||
int ret = -ENOENT;
|
||||
|
||||
head = trace_get_fields(ep->event);
|
||||
list_for_each_entry(field, head, link) {
|
||||
if (!strcmp(parg->code->data, field->name)) {
|
||||
kfree(parg->code->data);
|
||||
parg->code->data = field;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Argument not found on event. But allow for comm and COMM
|
||||
* to be used to get the current->comm.
|
||||
*/
|
||||
if (strcmp(parg->code->data, "COMM") == 0 ||
|
||||
strcmp(parg->code->data, "comm") == 0) {
|
||||
parg->code->op = FETCH_OP_COMM;
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
kfree(parg->code->data);
|
||||
parg->code->data = NULL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int eprobe_event_define_fields(struct trace_event_call *event_call)
|
||||
{
|
||||
struct eprobe_trace_entry_head field;
|
||||
@ -817,19 +786,16 @@ find_and_get_event(const char *system, const char *event_name)
|
||||
|
||||
static int trace_eprobe_tp_update_arg(struct trace_eprobe *ep, const char *argv[], int i)
|
||||
{
|
||||
unsigned int flags = TPARG_FL_KERNEL | TPARG_FL_TPOINT;
|
||||
struct traceprobe_parse_context ctx = {
|
||||
.event = ep->event,
|
||||
.flags = TPARG_FL_KERNEL | TPARG_FL_TEVENT,
|
||||
};
|
||||
int ret;
|
||||
|
||||
ret = traceprobe_parse_probe_arg(&ep->tp, i, argv[i], flags);
|
||||
ret = traceprobe_parse_probe_arg(&ep->tp, i, argv[i], &ctx);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (ep->tp.args[i].code->op == FETCH_OP_TP_ARG) {
|
||||
ret = trace_eprobe_tp_arg_update(ep, i);
|
||||
if (ret)
|
||||
trace_probe_log_err(0, BAD_ATTACH_ARG);
|
||||
}
|
||||
|
||||
/* Handle symbols "@" */
|
||||
if (!ret)
|
||||
ret = traceprobe_update_arg(&ep->tp.args[i]);
|
||||
|
1199
kernel/trace/trace_fprobe.c
Normal file
1199
kernel/trace/trace_fprobe.c
Normal file
File diff suppressed because it is too large
Load Diff
@ -732,9 +732,10 @@ static int __trace_kprobe_create(int argc, const char *argv[])
|
||||
* FETCHARG:TYPE : use TYPE instead of unsigned long.
|
||||
*/
|
||||
struct trace_kprobe *tk = NULL;
|
||||
int i, len, ret = 0;
|
||||
int i, len, new_argc = 0, ret = 0;
|
||||
bool is_return = false;
|
||||
char *symbol = NULL, *tmp = NULL;
|
||||
const char **new_argv = NULL;
|
||||
const char *event = NULL, *group = KPROBE_EVENT_SYSTEM;
|
||||
enum probe_print_type ptype;
|
||||
int maxactive = 0;
|
||||
@ -742,7 +743,8 @@ static int __trace_kprobe_create(int argc, const char *argv[])
|
||||
void *addr = NULL;
|
||||
char buf[MAX_EVENT_NAME_LEN];
|
||||
char gbuf[MAX_EVENT_NAME_LEN];
|
||||
unsigned int flags = TPARG_FL_KERNEL;
|
||||
char abuf[MAX_BTF_ARGS_LEN];
|
||||
struct traceprobe_parse_context ctx = { .flags = TPARG_FL_KERNEL };
|
||||
|
||||
switch (argv[0][0]) {
|
||||
case 'r':
|
||||
@ -764,7 +766,7 @@ static int __trace_kprobe_create(int argc, const char *argv[])
|
||||
|
||||
if (isdigit(argv[0][1])) {
|
||||
if (!is_return) {
|
||||
trace_probe_log_err(1, MAXACT_NO_KPROBE);
|
||||
trace_probe_log_err(1, BAD_MAXACT_TYPE);
|
||||
goto parse_error;
|
||||
}
|
||||
if (event)
|
||||
@ -823,10 +825,10 @@ static int __trace_kprobe_create(int argc, const char *argv[])
|
||||
goto parse_error;
|
||||
}
|
||||
if (is_return)
|
||||
flags |= TPARG_FL_RETURN;
|
||||
ctx.flags |= TPARG_FL_RETURN;
|
||||
ret = kprobe_on_func_entry(NULL, symbol, offset);
|
||||
if (ret == 0)
|
||||
flags |= TPARG_FL_FENTRY;
|
||||
if (ret == 0 && !is_return)
|
||||
ctx.flags |= TPARG_FL_FENTRY;
|
||||
/* Defer the ENOENT case until register kprobe */
|
||||
if (ret == -EINVAL && is_return) {
|
||||
trace_probe_log_err(0, BAD_RETPROBE);
|
||||
@ -854,21 +856,35 @@ static int __trace_kprobe_create(int argc, const char *argv[])
|
||||
event = buf;
|
||||
}
|
||||
|
||||
argc -= 2; argv += 2;
|
||||
ctx.funcname = symbol;
|
||||
new_argv = traceprobe_expand_meta_args(argc, argv, &new_argc,
|
||||
abuf, MAX_BTF_ARGS_LEN, &ctx);
|
||||
if (IS_ERR(new_argv)) {
|
||||
ret = PTR_ERR(new_argv);
|
||||
new_argv = NULL;
|
||||
goto out;
|
||||
}
|
||||
if (new_argv) {
|
||||
argc = new_argc;
|
||||
argv = new_argv;
|
||||
}
|
||||
|
||||
/* setup a probe */
|
||||
tk = alloc_trace_kprobe(group, event, addr, symbol, offset, maxactive,
|
||||
argc - 2, is_return);
|
||||
argc, is_return);
|
||||
if (IS_ERR(tk)) {
|
||||
ret = PTR_ERR(tk);
|
||||
/* This must return -ENOMEM, else there is a bug */
|
||||
WARN_ON_ONCE(ret != -ENOMEM);
|
||||
goto out; /* We know tk is not allocated */
|
||||
}
|
||||
argc -= 2; argv += 2;
|
||||
|
||||
/* parse arguments */
|
||||
for (i = 0; i < argc && i < MAX_TRACE_ARGS; i++) {
|
||||
trace_probe_log_set_index(i + 2);
|
||||
ret = traceprobe_parse_probe_arg(&tk->tp, i, argv[i], flags);
|
||||
ctx.offset = 0;
|
||||
ret = traceprobe_parse_probe_arg(&tk->tp, i, argv[i], &ctx);
|
||||
if (ret)
|
||||
goto error; /* This can be -ENOMEM */
|
||||
}
|
||||
@ -892,6 +908,7 @@ static int __trace_kprobe_create(int argc, const char *argv[])
|
||||
|
||||
out:
|
||||
trace_probe_log_clear();
|
||||
kfree(new_argv);
|
||||
kfree(symbol);
|
||||
return ret;
|
||||
|
||||
|
@ -11,6 +11,8 @@
|
||||
*/
|
||||
#define pr_fmt(fmt) "trace_probe: " fmt
|
||||
|
||||
#include <linux/bpf.h>
|
||||
|
||||
#include "trace_probe.h"
|
||||
|
||||
#undef C
|
||||
@ -283,69 +285,354 @@ int traceprobe_parse_event_name(const char **pevent, const char **pgroup,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_trace_event_arg(char *arg, struct fetch_insn *code,
|
||||
struct traceprobe_parse_context *ctx)
|
||||
{
|
||||
struct ftrace_event_field *field;
|
||||
struct list_head *head;
|
||||
|
||||
head = trace_get_fields(ctx->event);
|
||||
list_for_each_entry(field, head, link) {
|
||||
if (!strcmp(arg, field->name)) {
|
||||
code->op = FETCH_OP_TP_ARG;
|
||||
code->data = field;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PROBE_EVENTS_BTF_ARGS
|
||||
|
||||
static struct btf *traceprobe_get_btf(void)
|
||||
{
|
||||
struct btf *btf = bpf_get_btf_vmlinux();
|
||||
|
||||
if (IS_ERR_OR_NULL(btf))
|
||||
return NULL;
|
||||
|
||||
return btf;
|
||||
}
|
||||
|
||||
static u32 btf_type_int(const struct btf_type *t)
|
||||
{
|
||||
return *(u32 *)(t + 1);
|
||||
}
|
||||
|
||||
static const char *type_from_btf_id(struct btf *btf, s32 id)
|
||||
{
|
||||
const struct btf_type *t;
|
||||
u32 intdata;
|
||||
s32 tid;
|
||||
|
||||
/* TODO: const char * could be converted as a string */
|
||||
t = btf_type_skip_modifiers(btf, id, &tid);
|
||||
|
||||
switch (BTF_INFO_KIND(t->info)) {
|
||||
case BTF_KIND_ENUM:
|
||||
/* enum is "int", so convert to "s32" */
|
||||
return "s32";
|
||||
case BTF_KIND_ENUM64:
|
||||
return "s64";
|
||||
case BTF_KIND_PTR:
|
||||
/* pointer will be converted to "x??" */
|
||||
if (IS_ENABLED(CONFIG_64BIT))
|
||||
return "x64";
|
||||
else
|
||||
return "x32";
|
||||
case BTF_KIND_INT:
|
||||
intdata = btf_type_int(t);
|
||||
if (BTF_INT_ENCODING(intdata) & BTF_INT_SIGNED) {
|
||||
switch (BTF_INT_BITS(intdata)) {
|
||||
case 8:
|
||||
return "s8";
|
||||
case 16:
|
||||
return "s16";
|
||||
case 32:
|
||||
return "s32";
|
||||
case 64:
|
||||
return "s64";
|
||||
}
|
||||
} else { /* unsigned */
|
||||
switch (BTF_INT_BITS(intdata)) {
|
||||
case 8:
|
||||
return "u8";
|
||||
case 16:
|
||||
return "u16";
|
||||
case 32:
|
||||
return "u32";
|
||||
case 64:
|
||||
return "u64";
|
||||
}
|
||||
}
|
||||
}
|
||||
/* TODO: support other types */
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const struct btf_type *find_btf_func_proto(const char *funcname)
|
||||
{
|
||||
struct btf *btf = traceprobe_get_btf();
|
||||
const struct btf_type *t;
|
||||
s32 id;
|
||||
|
||||
if (!btf || !funcname)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
id = btf_find_by_name_kind(btf, funcname, BTF_KIND_FUNC);
|
||||
if (id <= 0)
|
||||
return ERR_PTR(-ENOENT);
|
||||
|
||||
/* Get BTF_KIND_FUNC type */
|
||||
t = btf_type_by_id(btf, id);
|
||||
if (!btf_type_is_func(t))
|
||||
return ERR_PTR(-ENOENT);
|
||||
|
||||
/* The type of BTF_KIND_FUNC is BTF_KIND_FUNC_PROTO */
|
||||
t = btf_type_by_id(btf, t->type);
|
||||
if (!btf_type_is_func_proto(t))
|
||||
return ERR_PTR(-ENOENT);
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
static const struct btf_param *find_btf_func_param(const char *funcname, s32 *nr,
|
||||
bool tracepoint)
|
||||
{
|
||||
const struct btf_param *param;
|
||||
const struct btf_type *t;
|
||||
|
||||
if (!funcname || !nr)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
t = find_btf_func_proto(funcname);
|
||||
if (IS_ERR(t))
|
||||
return (const struct btf_param *)t;
|
||||
|
||||
*nr = btf_type_vlen(t);
|
||||
param = (const struct btf_param *)(t + 1);
|
||||
|
||||
/* Hide the first 'data' argument of tracepoint */
|
||||
if (tracepoint) {
|
||||
(*nr)--;
|
||||
param++;
|
||||
}
|
||||
|
||||
if (*nr > 0)
|
||||
return param;
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int parse_btf_arg(const char *varname, struct fetch_insn *code,
|
||||
struct traceprobe_parse_context *ctx)
|
||||
{
|
||||
struct btf *btf = traceprobe_get_btf();
|
||||
const struct btf_param *params;
|
||||
int i;
|
||||
|
||||
if (!btf) {
|
||||
trace_probe_log_err(ctx->offset, NOSUP_BTFARG);
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
if (WARN_ON_ONCE(!ctx->funcname))
|
||||
return -EINVAL;
|
||||
|
||||
if (!ctx->params) {
|
||||
params = find_btf_func_param(ctx->funcname, &ctx->nr_params,
|
||||
ctx->flags & TPARG_FL_TPOINT);
|
||||
if (IS_ERR(params)) {
|
||||
trace_probe_log_err(ctx->offset, NO_BTF_ENTRY);
|
||||
return PTR_ERR(params);
|
||||
}
|
||||
ctx->params = params;
|
||||
} else
|
||||
params = ctx->params;
|
||||
|
||||
for (i = 0; i < ctx->nr_params; i++) {
|
||||
const char *name = btf_name_by_offset(btf, params[i].name_off);
|
||||
|
||||
if (name && !strcmp(name, varname)) {
|
||||
code->op = FETCH_OP_ARG;
|
||||
if (ctx->flags & TPARG_FL_TPOINT)
|
||||
code->param = i + 1;
|
||||
else
|
||||
code->param = i;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
trace_probe_log_err(ctx->offset, NO_BTFARG);
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
static const struct fetch_type *parse_btf_arg_type(int arg_idx,
|
||||
struct traceprobe_parse_context *ctx)
|
||||
{
|
||||
struct btf *btf = traceprobe_get_btf();
|
||||
const char *typestr = NULL;
|
||||
|
||||
if (btf && ctx->params) {
|
||||
if (ctx->flags & TPARG_FL_TPOINT)
|
||||
arg_idx--;
|
||||
typestr = type_from_btf_id(btf, ctx->params[arg_idx].type);
|
||||
}
|
||||
|
||||
return find_fetch_type(typestr, ctx->flags);
|
||||
}
|
||||
|
||||
static const struct fetch_type *parse_btf_retval_type(
|
||||
struct traceprobe_parse_context *ctx)
|
||||
{
|
||||
struct btf *btf = traceprobe_get_btf();
|
||||
const char *typestr = NULL;
|
||||
const struct btf_type *t;
|
||||
|
||||
if (btf && ctx->funcname) {
|
||||
t = find_btf_func_proto(ctx->funcname);
|
||||
if (!IS_ERR(t))
|
||||
typestr = type_from_btf_id(btf, t->type);
|
||||
}
|
||||
|
||||
return find_fetch_type(typestr, ctx->flags);
|
||||
}
|
||||
|
||||
static bool is_btf_retval_void(const char *funcname)
|
||||
{
|
||||
const struct btf_type *t;
|
||||
|
||||
t = find_btf_func_proto(funcname);
|
||||
if (IS_ERR(t))
|
||||
return false;
|
||||
|
||||
return t->type == 0;
|
||||
}
|
||||
#else
|
||||
static struct btf *traceprobe_get_btf(void)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const struct btf_param *find_btf_func_param(const char *funcname, s32 *nr,
|
||||
bool tracepoint)
|
||||
{
|
||||
return ERR_PTR(-EOPNOTSUPP);
|
||||
}
|
||||
|
||||
static int parse_btf_arg(const char *varname, struct fetch_insn *code,
|
||||
struct traceprobe_parse_context *ctx)
|
||||
{
|
||||
trace_probe_log_err(ctx->offset, NOSUP_BTFARG);
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
#define parse_btf_arg_type(idx, ctx) \
|
||||
find_fetch_type(NULL, ctx->flags)
|
||||
|
||||
#define parse_btf_retval_type(ctx) \
|
||||
find_fetch_type(NULL, ctx->flags)
|
||||
|
||||
#define is_btf_retval_void(funcname) (false)
|
||||
|
||||
#endif
|
||||
|
||||
#define PARAM_MAX_STACK (THREAD_SIZE / sizeof(unsigned long))
|
||||
|
||||
static int parse_probe_vars(char *arg, const struct fetch_type *t,
|
||||
struct fetch_insn *code, unsigned int flags, int offs)
|
||||
struct fetch_insn *code,
|
||||
struct traceprobe_parse_context *ctx)
|
||||
{
|
||||
unsigned long param;
|
||||
int err = TP_ERR_BAD_VAR;
|
||||
int ret = 0;
|
||||
int len;
|
||||
|
||||
if (flags & TPARG_FL_TPOINT) {
|
||||
if (ctx->flags & TPARG_FL_TEVENT) {
|
||||
if (code->data)
|
||||
return -EFAULT;
|
||||
code->data = kstrdup(arg, GFP_KERNEL);
|
||||
if (!code->data)
|
||||
return -ENOMEM;
|
||||
code->op = FETCH_OP_TP_ARG;
|
||||
} else if (strcmp(arg, "retval") == 0) {
|
||||
if (flags & TPARG_FL_RETURN) {
|
||||
code->op = FETCH_OP_RETVAL;
|
||||
} else {
|
||||
trace_probe_log_err(offs, RETVAL_ON_PROBE);
|
||||
ret = -EINVAL;
|
||||
ret = parse_trace_event_arg(arg, code, ctx);
|
||||
if (!ret)
|
||||
return 0;
|
||||
if (strcmp(arg, "comm") == 0 || strcmp(arg, "COMM") == 0) {
|
||||
code->op = FETCH_OP_COMM;
|
||||
return 0;
|
||||
}
|
||||
} else if ((len = str_has_prefix(arg, "stack"))) {
|
||||
/* backward compatibility */
|
||||
ctx->offset = 0;
|
||||
goto inval;
|
||||
}
|
||||
|
||||
if (strcmp(arg, "retval") == 0) {
|
||||
if (ctx->flags & TPARG_FL_RETURN) {
|
||||
if ((ctx->flags & TPARG_FL_KERNEL) &&
|
||||
is_btf_retval_void(ctx->funcname)) {
|
||||
err = TP_ERR_NO_RETVAL;
|
||||
goto inval;
|
||||
}
|
||||
code->op = FETCH_OP_RETVAL;
|
||||
return 0;
|
||||
}
|
||||
err = TP_ERR_RETVAL_ON_PROBE;
|
||||
goto inval;
|
||||
}
|
||||
|
||||
len = str_has_prefix(arg, "stack");
|
||||
if (len) {
|
||||
|
||||
if (arg[len] == '\0') {
|
||||
code->op = FETCH_OP_STACKP;
|
||||
} else if (isdigit(arg[len])) {
|
||||
ret = kstrtoul(arg + len, 10, ¶m);
|
||||
if (ret) {
|
||||
goto inval_var;
|
||||
} else if ((flags & TPARG_FL_KERNEL) &&
|
||||
param > PARAM_MAX_STACK) {
|
||||
trace_probe_log_err(offs, BAD_STACK_NUM);
|
||||
ret = -EINVAL;
|
||||
} else {
|
||||
code->op = FETCH_OP_STACK;
|
||||
code->param = (unsigned int)param;
|
||||
}
|
||||
} else
|
||||
goto inval_var;
|
||||
} else if (strcmp(arg, "comm") == 0 || strcmp(arg, "COMM") == 0) {
|
||||
code->op = FETCH_OP_COMM;
|
||||
#ifdef CONFIG_HAVE_FUNCTION_ARG_ACCESS_API
|
||||
} else if (((flags & TPARG_FL_MASK) ==
|
||||
(TPARG_FL_KERNEL | TPARG_FL_FENTRY)) &&
|
||||
(len = str_has_prefix(arg, "arg"))) {
|
||||
ret = kstrtoul(arg + len, 10, ¶m);
|
||||
if (ret) {
|
||||
goto inval_var;
|
||||
} else if (!param || param > PARAM_MAX_STACK) {
|
||||
trace_probe_log_err(offs, BAD_ARG_NUM);
|
||||
return -EINVAL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (isdigit(arg[len])) {
|
||||
ret = kstrtoul(arg + len, 10, ¶m);
|
||||
if (ret)
|
||||
goto inval;
|
||||
|
||||
if ((ctx->flags & TPARG_FL_KERNEL) &&
|
||||
param > PARAM_MAX_STACK) {
|
||||
err = TP_ERR_BAD_STACK_NUM;
|
||||
goto inval;
|
||||
}
|
||||
code->op = FETCH_OP_STACK;
|
||||
code->param = (unsigned int)param;
|
||||
return 0;
|
||||
}
|
||||
goto inval;
|
||||
}
|
||||
|
||||
if (strcmp(arg, "comm") == 0 || strcmp(arg, "COMM") == 0) {
|
||||
code->op = FETCH_OP_COMM;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_HAVE_FUNCTION_ARG_ACCESS_API
|
||||
len = str_has_prefix(arg, "arg");
|
||||
if (len && tparg_is_function_entry(ctx->flags)) {
|
||||
ret = kstrtoul(arg + len, 10, ¶m);
|
||||
if (ret)
|
||||
goto inval;
|
||||
|
||||
if (!param || param > PARAM_MAX_STACK) {
|
||||
err = TP_ERR_BAD_ARG_NUM;
|
||||
goto inval;
|
||||
}
|
||||
|
||||
code->op = FETCH_OP_ARG;
|
||||
code->param = (unsigned int)param - 1;
|
||||
/*
|
||||
* The tracepoint probe will probe a stub function, and the
|
||||
* first parameter of the stub is a dummy and should be ignored.
|
||||
*/
|
||||
if (ctx->flags & TPARG_FL_TPOINT)
|
||||
code->param++;
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
} else
|
||||
goto inval_var;
|
||||
|
||||
return ret;
|
||||
|
||||
inval_var:
|
||||
trace_probe_log_err(offs, BAD_VAR);
|
||||
inval:
|
||||
__trace_probe_log_err(ctx->offset, err);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
@ -378,7 +665,7 @@ static int __parse_imm_string(char *str, char **pbuf, int offs)
|
||||
static int
|
||||
parse_probe_arg(char *arg, const struct fetch_type *type,
|
||||
struct fetch_insn **pcode, struct fetch_insn *end,
|
||||
unsigned int flags, int offs)
|
||||
struct traceprobe_parse_context *ctx)
|
||||
{
|
||||
struct fetch_insn *code = *pcode;
|
||||
unsigned long param;
|
||||
@ -389,13 +676,13 @@ parse_probe_arg(char *arg, const struct fetch_type *type,
|
||||
|
||||
switch (arg[0]) {
|
||||
case '$':
|
||||
ret = parse_probe_vars(arg + 1, type, code, flags, offs);
|
||||
ret = parse_probe_vars(arg + 1, type, code, ctx);
|
||||
break;
|
||||
|
||||
case '%': /* named register */
|
||||
if (flags & TPARG_FL_TPOINT) {
|
||||
/* eprobes do not handle registers */
|
||||
trace_probe_log_err(offs, BAD_VAR);
|
||||
if (ctx->flags & (TPARG_FL_TEVENT | TPARG_FL_FPROBE)) {
|
||||
/* eprobe and fprobe do not handle registers */
|
||||
trace_probe_log_err(ctx->offset, BAD_VAR);
|
||||
break;
|
||||
}
|
||||
ret = regs_query_register_offset(arg + 1);
|
||||
@ -404,14 +691,14 @@ parse_probe_arg(char *arg, const struct fetch_type *type,
|
||||
code->param = (unsigned int)ret;
|
||||
ret = 0;
|
||||
} else
|
||||
trace_probe_log_err(offs, BAD_REG_NAME);
|
||||
trace_probe_log_err(ctx->offset, BAD_REG_NAME);
|
||||
break;
|
||||
|
||||
case '@': /* memory, file-offset or symbol */
|
||||
if (isdigit(arg[1])) {
|
||||
ret = kstrtoul(arg + 1, 0, ¶m);
|
||||
if (ret) {
|
||||
trace_probe_log_err(offs, BAD_MEM_ADDR);
|
||||
trace_probe_log_err(ctx->offset, BAD_MEM_ADDR);
|
||||
break;
|
||||
}
|
||||
/* load address */
|
||||
@ -419,13 +706,13 @@ parse_probe_arg(char *arg, const struct fetch_type *type,
|
||||
code->immediate = param;
|
||||
} else if (arg[1] == '+') {
|
||||
/* kprobes don't support file offsets */
|
||||
if (flags & TPARG_FL_KERNEL) {
|
||||
trace_probe_log_err(offs, FILE_ON_KPROBE);
|
||||
if (ctx->flags & TPARG_FL_KERNEL) {
|
||||
trace_probe_log_err(ctx->offset, FILE_ON_KPROBE);
|
||||
return -EINVAL;
|
||||
}
|
||||
ret = kstrtol(arg + 2, 0, &offset);
|
||||
if (ret) {
|
||||
trace_probe_log_err(offs, BAD_FILE_OFFS);
|
||||
trace_probe_log_err(ctx->offset, BAD_FILE_OFFS);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -433,8 +720,8 @@ parse_probe_arg(char *arg, const struct fetch_type *type,
|
||||
code->immediate = (unsigned long)offset; // imm64?
|
||||
} else {
|
||||
/* uprobes don't support symbols */
|
||||
if (!(flags & TPARG_FL_KERNEL)) {
|
||||
trace_probe_log_err(offs, SYM_ON_UPROBE);
|
||||
if (!(ctx->flags & TPARG_FL_KERNEL)) {
|
||||
trace_probe_log_err(ctx->offset, SYM_ON_UPROBE);
|
||||
return -EINVAL;
|
||||
}
|
||||
/* Preserve symbol for updating */
|
||||
@ -443,7 +730,7 @@ parse_probe_arg(char *arg, const struct fetch_type *type,
|
||||
if (!code->data)
|
||||
return -ENOMEM;
|
||||
if (++code == end) {
|
||||
trace_probe_log_err(offs, TOO_MANY_OPS);
|
||||
trace_probe_log_err(ctx->offset, TOO_MANY_OPS);
|
||||
return -EINVAL;
|
||||
}
|
||||
code->op = FETCH_OP_IMM;
|
||||
@ -451,7 +738,7 @@ parse_probe_arg(char *arg, const struct fetch_type *type,
|
||||
}
|
||||
/* These are fetching from memory */
|
||||
if (++code == end) {
|
||||
trace_probe_log_err(offs, TOO_MANY_OPS);
|
||||
trace_probe_log_err(ctx->offset, TOO_MANY_OPS);
|
||||
return -EINVAL;
|
||||
}
|
||||
*pcode = code;
|
||||
@ -470,36 +757,38 @@ parse_probe_arg(char *arg, const struct fetch_type *type,
|
||||
arg++; /* Skip '+', because kstrtol() rejects it. */
|
||||
tmp = strchr(arg, '(');
|
||||
if (!tmp) {
|
||||
trace_probe_log_err(offs, DEREF_NEED_BRACE);
|
||||
trace_probe_log_err(ctx->offset, DEREF_NEED_BRACE);
|
||||
return -EINVAL;
|
||||
}
|
||||
*tmp = '\0';
|
||||
ret = kstrtol(arg, 0, &offset);
|
||||
if (ret) {
|
||||
trace_probe_log_err(offs, BAD_DEREF_OFFS);
|
||||
trace_probe_log_err(ctx->offset, BAD_DEREF_OFFS);
|
||||
break;
|
||||
}
|
||||
offs += (tmp + 1 - arg) + (arg[0] != '-' ? 1 : 0);
|
||||
ctx->offset += (tmp + 1 - arg) + (arg[0] != '-' ? 1 : 0);
|
||||
arg = tmp + 1;
|
||||
tmp = strrchr(arg, ')');
|
||||
if (!tmp) {
|
||||
trace_probe_log_err(offs + strlen(arg),
|
||||
trace_probe_log_err(ctx->offset + strlen(arg),
|
||||
DEREF_OPEN_BRACE);
|
||||
return -EINVAL;
|
||||
} else {
|
||||
const struct fetch_type *t2 = find_fetch_type(NULL, flags);
|
||||
const struct fetch_type *t2 = find_fetch_type(NULL, ctx->flags);
|
||||
int cur_offs = ctx->offset;
|
||||
|
||||
*tmp = '\0';
|
||||
ret = parse_probe_arg(arg, t2, &code, end, flags, offs);
|
||||
ret = parse_probe_arg(arg, t2, &code, end, ctx);
|
||||
if (ret)
|
||||
break;
|
||||
ctx->offset = cur_offs;
|
||||
if (code->op == FETCH_OP_COMM ||
|
||||
code->op == FETCH_OP_DATA) {
|
||||
trace_probe_log_err(offs, COMM_CANT_DEREF);
|
||||
trace_probe_log_err(ctx->offset, COMM_CANT_DEREF);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (++code == end) {
|
||||
trace_probe_log_err(offs, TOO_MANY_OPS);
|
||||
trace_probe_log_err(ctx->offset, TOO_MANY_OPS);
|
||||
return -EINVAL;
|
||||
}
|
||||
*pcode = code;
|
||||
@ -510,7 +799,7 @@ parse_probe_arg(char *arg, const struct fetch_type *type,
|
||||
break;
|
||||
case '\\': /* Immediate value */
|
||||
if (arg[1] == '"') { /* Immediate string */
|
||||
ret = __parse_imm_string(arg + 2, &tmp, offs + 2);
|
||||
ret = __parse_imm_string(arg + 2, &tmp, ctx->offset + 2);
|
||||
if (ret)
|
||||
break;
|
||||
code->op = FETCH_OP_DATA;
|
||||
@ -518,15 +807,24 @@ parse_probe_arg(char *arg, const struct fetch_type *type,
|
||||
} else {
|
||||
ret = str_to_immediate(arg + 1, &code->immediate);
|
||||
if (ret)
|
||||
trace_probe_log_err(offs + 1, BAD_IMM);
|
||||
trace_probe_log_err(ctx->offset + 1, BAD_IMM);
|
||||
else
|
||||
code->op = FETCH_OP_IMM;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (isalpha(arg[0]) || arg[0] == '_') { /* BTF variable */
|
||||
if (!tparg_is_function_entry(ctx->flags)) {
|
||||
trace_probe_log_err(ctx->offset, NOSUP_BTFARG);
|
||||
return -EINVAL;
|
||||
}
|
||||
ret = parse_btf_arg(arg, code, ctx);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!ret && code->op == FETCH_OP_NOP) {
|
||||
/* Parsed, but do not find fetch method */
|
||||
trace_probe_log_err(offs, BAD_FETCH_ARG);
|
||||
trace_probe_log_err(ctx->offset, BAD_FETCH_ARG);
|
||||
ret = -EINVAL;
|
||||
}
|
||||
return ret;
|
||||
@ -571,12 +869,13 @@ static int __parse_bitfield_probe_arg(const char *bf,
|
||||
|
||||
/* String length checking wrapper */
|
||||
static int traceprobe_parse_probe_arg_body(const char *argv, ssize_t *size,
|
||||
struct probe_arg *parg, unsigned int flags, int offset)
|
||||
struct probe_arg *parg,
|
||||
struct traceprobe_parse_context *ctx)
|
||||
{
|
||||
struct fetch_insn *code, *scode, *tmp = NULL;
|
||||
char *t, *t2, *t3;
|
||||
char *arg;
|
||||
int ret, len;
|
||||
char *arg;
|
||||
|
||||
arg = kstrdup(argv, GFP_KERNEL);
|
||||
if (!arg)
|
||||
@ -585,10 +884,10 @@ static int traceprobe_parse_probe_arg_body(const char *argv, ssize_t *size,
|
||||
ret = -EINVAL;
|
||||
len = strlen(arg);
|
||||
if (len > MAX_ARGSTR_LEN) {
|
||||
trace_probe_log_err(offset, ARG_TOO_LONG);
|
||||
trace_probe_log_err(ctx->offset, ARG_TOO_LONG);
|
||||
goto out;
|
||||
} else if (len == 0) {
|
||||
trace_probe_log_err(offset, NO_ARG_BODY);
|
||||
trace_probe_log_err(ctx->offset, NO_ARG_BODY);
|
||||
goto out;
|
||||
}
|
||||
|
||||
@ -606,23 +905,24 @@ static int traceprobe_parse_probe_arg_body(const char *argv, ssize_t *size,
|
||||
*t2++ = '\0';
|
||||
t3 = strchr(t2, ']');
|
||||
if (!t3) {
|
||||
offset += t2 + strlen(t2) - arg;
|
||||
trace_probe_log_err(offset,
|
||||
int offs = t2 + strlen(t2) - arg;
|
||||
|
||||
trace_probe_log_err(ctx->offset + offs,
|
||||
ARRAY_NO_CLOSE);
|
||||
goto out;
|
||||
} else if (t3[1] != '\0') {
|
||||
trace_probe_log_err(offset + t3 + 1 - arg,
|
||||
trace_probe_log_err(ctx->offset + t3 + 1 - arg,
|
||||
BAD_ARRAY_SUFFIX);
|
||||
goto out;
|
||||
}
|
||||
*t3 = '\0';
|
||||
if (kstrtouint(t2, 0, &parg->count) || !parg->count) {
|
||||
trace_probe_log_err(offset + t2 - arg,
|
||||
trace_probe_log_err(ctx->offset + t2 - arg,
|
||||
BAD_ARRAY_NUM);
|
||||
goto out;
|
||||
}
|
||||
if (parg->count > MAX_ARRAY_LEN) {
|
||||
trace_probe_log_err(offset + t2 - arg,
|
||||
trace_probe_log_err(ctx->offset + t2 - arg,
|
||||
ARRAY_TOO_BIG);
|
||||
goto out;
|
||||
}
|
||||
@ -633,17 +933,17 @@ static int traceprobe_parse_probe_arg_body(const char *argv, ssize_t *size,
|
||||
* Since $comm and immediate string can not be dereferenced,
|
||||
* we can find those by strcmp. But ignore for eprobes.
|
||||
*/
|
||||
if (!(flags & TPARG_FL_TPOINT) &&
|
||||
if (!(ctx->flags & TPARG_FL_TEVENT) &&
|
||||
(strcmp(arg, "$comm") == 0 || strcmp(arg, "$COMM") == 0 ||
|
||||
strncmp(arg, "\\\"", 2) == 0)) {
|
||||
/* The type of $comm must be "string", and not an array. */
|
||||
if (parg->count || (t && strcmp(t, "string")))
|
||||
goto out;
|
||||
parg->type = find_fetch_type("string", flags);
|
||||
parg->type = find_fetch_type("string", ctx->flags);
|
||||
} else
|
||||
parg->type = find_fetch_type(t, flags);
|
||||
parg->type = find_fetch_type(t, ctx->flags);
|
||||
if (!parg->type) {
|
||||
trace_probe_log_err(offset + (t ? (t - arg) : 0), BAD_TYPE);
|
||||
trace_probe_log_err(ctx->offset + (t ? (t - arg) : 0), BAD_TYPE);
|
||||
goto out;
|
||||
}
|
||||
parg->offset = *size;
|
||||
@ -665,10 +965,18 @@ static int traceprobe_parse_probe_arg_body(const char *argv, ssize_t *size,
|
||||
code[FETCH_INSN_MAX - 1].op = FETCH_OP_END;
|
||||
|
||||
ret = parse_probe_arg(arg, parg->type, &code, &code[FETCH_INSN_MAX - 1],
|
||||
flags, offset);
|
||||
ctx);
|
||||
if (ret)
|
||||
goto fail;
|
||||
|
||||
/* Update storing type if BTF is available */
|
||||
if (IS_ENABLED(CONFIG_PROBE_EVENTS_BTF_ARGS) && !t) {
|
||||
if (code->op == FETCH_OP_ARG)
|
||||
parg->type = parse_btf_arg_type(code->param, ctx);
|
||||
else if (code->op == FETCH_OP_RETVAL)
|
||||
parg->type = parse_btf_retval_type(ctx);
|
||||
}
|
||||
|
||||
ret = -EINVAL;
|
||||
/* Store operation */
|
||||
if (parg->type->is_string) {
|
||||
@ -676,7 +984,7 @@ static int traceprobe_parse_probe_arg_body(const char *argv, ssize_t *size,
|
||||
if (code->op != FETCH_OP_REG && code->op != FETCH_OP_STACK &&
|
||||
code->op != FETCH_OP_RETVAL && code->op != FETCH_OP_ARG &&
|
||||
code->op != FETCH_OP_DEREF && code->op != FETCH_OP_TP_ARG) {
|
||||
trace_probe_log_err(offset + (t ? (t - arg) : 0),
|
||||
trace_probe_log_err(ctx->offset + (t ? (t - arg) : 0),
|
||||
BAD_SYMSTRING);
|
||||
goto fail;
|
||||
}
|
||||
@ -684,7 +992,7 @@ static int traceprobe_parse_probe_arg_body(const char *argv, ssize_t *size,
|
||||
if (code->op != FETCH_OP_DEREF && code->op != FETCH_OP_UDEREF &&
|
||||
code->op != FETCH_OP_IMM && code->op != FETCH_OP_COMM &&
|
||||
code->op != FETCH_OP_DATA && code->op != FETCH_OP_TP_ARG) {
|
||||
trace_probe_log_err(offset + (t ? (t - arg) : 0),
|
||||
trace_probe_log_err(ctx->offset + (t ? (t - arg) : 0),
|
||||
BAD_STRING);
|
||||
goto fail;
|
||||
}
|
||||
@ -703,7 +1011,7 @@ static int traceprobe_parse_probe_arg_body(const char *argv, ssize_t *size,
|
||||
*/
|
||||
code++;
|
||||
if (code->op != FETCH_OP_NOP) {
|
||||
trace_probe_log_err(offset, TOO_MANY_OPS);
|
||||
trace_probe_log_err(ctx->offset, TOO_MANY_OPS);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
@ -726,7 +1034,7 @@ static int traceprobe_parse_probe_arg_body(const char *argv, ssize_t *size,
|
||||
} else {
|
||||
code++;
|
||||
if (code->op != FETCH_OP_NOP) {
|
||||
trace_probe_log_err(offset, TOO_MANY_OPS);
|
||||
trace_probe_log_err(ctx->offset, TOO_MANY_OPS);
|
||||
goto fail;
|
||||
}
|
||||
code->op = FETCH_OP_ST_RAW;
|
||||
@ -737,7 +1045,7 @@ static int traceprobe_parse_probe_arg_body(const char *argv, ssize_t *size,
|
||||
if (t != NULL) {
|
||||
ret = __parse_bitfield_probe_arg(t, parg->type, &code);
|
||||
if (ret) {
|
||||
trace_probe_log_err(offset + t - arg, BAD_BITFIELD);
|
||||
trace_probe_log_err(ctx->offset + t - arg, BAD_BITFIELD);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
@ -747,13 +1055,13 @@ static int traceprobe_parse_probe_arg_body(const char *argv, ssize_t *size,
|
||||
if (scode->op != FETCH_OP_ST_MEM &&
|
||||
scode->op != FETCH_OP_ST_STRING &&
|
||||
scode->op != FETCH_OP_ST_USTRING) {
|
||||
trace_probe_log_err(offset + (t ? (t - arg) : 0),
|
||||
trace_probe_log_err(ctx->offset + (t ? (t - arg) : 0),
|
||||
BAD_STRING);
|
||||
goto fail;
|
||||
}
|
||||
code++;
|
||||
if (code->op != FETCH_OP_NOP) {
|
||||
trace_probe_log_err(offset, TOO_MANY_OPS);
|
||||
trace_probe_log_err(ctx->offset, TOO_MANY_OPS);
|
||||
goto fail;
|
||||
}
|
||||
code->op = FETCH_OP_LP_ARRAY;
|
||||
@ -801,8 +1109,35 @@ static int traceprobe_conflict_field_name(const char *name,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static char *generate_probe_arg_name(const char *arg, int idx)
|
||||
{
|
||||
char *name = NULL;
|
||||
const char *end;
|
||||
|
||||
/*
|
||||
* If argument name is omitted, try arg as a name (BTF variable)
|
||||
* or "argN".
|
||||
*/
|
||||
if (IS_ENABLED(CONFIG_PROBE_EVENTS_BTF_ARGS)) {
|
||||
end = strchr(arg, ':');
|
||||
if (!end)
|
||||
end = arg + strlen(arg);
|
||||
|
||||
name = kmemdup_nul(arg, end - arg, GFP_KERNEL);
|
||||
if (!name || !is_good_name(name)) {
|
||||
kfree(name);
|
||||
name = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (!name)
|
||||
name = kasprintf(GFP_KERNEL, "arg%d", idx + 1);
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
int traceprobe_parse_probe_arg(struct trace_probe *tp, int i, const char *arg,
|
||||
unsigned int flags)
|
||||
struct traceprobe_parse_context *ctx)
|
||||
{
|
||||
struct probe_arg *parg = &tp->args[i];
|
||||
const char *body;
|
||||
@ -822,8 +1157,7 @@ int traceprobe_parse_probe_arg(struct trace_probe *tp, int i, const char *arg,
|
||||
parg->name = kmemdup_nul(arg, body - arg, GFP_KERNEL);
|
||||
body++;
|
||||
} else {
|
||||
/* If argument name is omitted, set "argN" */
|
||||
parg->name = kasprintf(GFP_KERNEL, "arg%d", i + 1);
|
||||
parg->name = generate_probe_arg_name(arg, i);
|
||||
body = arg;
|
||||
}
|
||||
if (!parg->name)
|
||||
@ -837,9 +1171,9 @@ int traceprobe_parse_probe_arg(struct trace_probe *tp, int i, const char *arg,
|
||||
trace_probe_log_err(0, USED_ARG_NAME);
|
||||
return -EINVAL;
|
||||
}
|
||||
ctx->offset = body - arg;
|
||||
/* Parse fetch argument */
|
||||
return traceprobe_parse_probe_arg_body(body, &tp->size, parg, flags,
|
||||
body - arg);
|
||||
return traceprobe_parse_probe_arg_body(body, &tp->size, parg, ctx);
|
||||
}
|
||||
|
||||
void traceprobe_free_probe_arg(struct probe_arg *arg)
|
||||
@ -858,6 +1192,151 @@ void traceprobe_free_probe_arg(struct probe_arg *arg)
|
||||
kfree(arg->fmt);
|
||||
}
|
||||
|
||||
static int argv_has_var_arg(int argc, const char *argv[], int *args_idx,
|
||||
struct traceprobe_parse_context *ctx)
|
||||
{
|
||||
int i, found = 0;
|
||||
|
||||
for (i = 0; i < argc; i++)
|
||||
if (str_has_prefix(argv[i], "$arg")) {
|
||||
trace_probe_log_set_index(i + 2);
|
||||
|
||||
if (!tparg_is_function_entry(ctx->flags)) {
|
||||
trace_probe_log_err(0, NOFENTRY_ARGS);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (isdigit(argv[i][4])) {
|
||||
found = 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argv[i][4] != '*') {
|
||||
trace_probe_log_err(0, BAD_VAR);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (*args_idx >= 0 && *args_idx < argc) {
|
||||
trace_probe_log_err(0, DOUBLE_ARGS);
|
||||
return -EINVAL;
|
||||
}
|
||||
found = 1;
|
||||
*args_idx = i;
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
static int sprint_nth_btf_arg(int idx, const char *type,
|
||||
char *buf, int bufsize,
|
||||
struct traceprobe_parse_context *ctx)
|
||||
{
|
||||
struct btf *btf = traceprobe_get_btf();
|
||||
const char *name;
|
||||
int ret;
|
||||
|
||||
if (idx >= ctx->nr_params) {
|
||||
trace_probe_log_err(0, NO_BTFARG);
|
||||
return -ENOENT;
|
||||
}
|
||||
name = btf_name_by_offset(btf, ctx->params[idx].name_off);
|
||||
if (!name) {
|
||||
trace_probe_log_err(0, NO_BTF_ENTRY);
|
||||
return -ENOENT;
|
||||
}
|
||||
ret = snprintf(buf, bufsize, "%s%s", name, type);
|
||||
if (ret >= bufsize) {
|
||||
trace_probe_log_err(0, ARGS_2LONG);
|
||||
return -E2BIG;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Return new_argv which must be freed after use */
|
||||
const char **traceprobe_expand_meta_args(int argc, const char *argv[],
|
||||
int *new_argc, char *buf, int bufsize,
|
||||
struct traceprobe_parse_context *ctx)
|
||||
{
|
||||
const struct btf_param *params = NULL;
|
||||
int i, j, n, used, ret, args_idx = -1;
|
||||
const char **new_argv = NULL;
|
||||
int nr_params;
|
||||
|
||||
ret = argv_has_var_arg(argc, argv, &args_idx, ctx);
|
||||
if (ret < 0)
|
||||
return ERR_PTR(ret);
|
||||
|
||||
if (!ret) {
|
||||
*new_argc = argc;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
params = find_btf_func_param(ctx->funcname, &nr_params,
|
||||
ctx->flags & TPARG_FL_TPOINT);
|
||||
if (IS_ERR(params)) {
|
||||
if (args_idx != -1) {
|
||||
/* $arg* requires BTF info */
|
||||
trace_probe_log_err(0, NOSUP_BTFARG);
|
||||
return (const char **)params;
|
||||
}
|
||||
*new_argc = argc;
|
||||
return NULL;
|
||||
}
|
||||
ctx->params = params;
|
||||
ctx->nr_params = nr_params;
|
||||
|
||||
if (args_idx >= 0)
|
||||
*new_argc = argc + ctx->nr_params - 1;
|
||||
else
|
||||
*new_argc = argc;
|
||||
|
||||
new_argv = kcalloc(*new_argc, sizeof(char *), GFP_KERNEL);
|
||||
if (!new_argv)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
used = 0;
|
||||
for (i = 0, j = 0; i < argc; i++) {
|
||||
trace_probe_log_set_index(i + 2);
|
||||
if (i == args_idx) {
|
||||
for (n = 0; n < nr_params; n++) {
|
||||
ret = sprint_nth_btf_arg(n, "", buf + used,
|
||||
bufsize - used, ctx);
|
||||
if (ret < 0)
|
||||
goto error;
|
||||
|
||||
new_argv[j++] = buf + used;
|
||||
used += ret + 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (str_has_prefix(argv[i], "$arg")) {
|
||||
char *type = NULL;
|
||||
|
||||
n = simple_strtoul(argv[i] + 4, &type, 10);
|
||||
if (type && !(*type == ':' || *type == '\0')) {
|
||||
trace_probe_log_err(0, BAD_VAR);
|
||||
ret = -ENOENT;
|
||||
goto error;
|
||||
}
|
||||
/* Note: $argN starts from $arg1 */
|
||||
ret = sprint_nth_btf_arg(n - 1, type, buf + used,
|
||||
bufsize - used, ctx);
|
||||
if (ret < 0)
|
||||
goto error;
|
||||
new_argv[j++] = buf + used;
|
||||
used += ret + 1;
|
||||
} else
|
||||
new_argv[j++] = argv[i];
|
||||
}
|
||||
|
||||
return new_argv;
|
||||
|
||||
error:
|
||||
kfree(new_argv);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
int traceprobe_update_arg(struct probe_arg *arg)
|
||||
{
|
||||
struct fetch_insn *code = arg->code;
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include <linux/limits.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/btf.h>
|
||||
#include <asm/bitsperlong.h>
|
||||
|
||||
#include "trace.h"
|
||||
@ -32,7 +33,9 @@
|
||||
#define MAX_ARGSTR_LEN 63
|
||||
#define MAX_ARRAY_LEN 64
|
||||
#define MAX_ARG_NAME_LEN 32
|
||||
#define MAX_BTF_ARGS_LEN 128
|
||||
#define MAX_STRING_SIZE PATH_MAX
|
||||
#define MAX_ARG_BUF_LEN (MAX_TRACE_ARGS * MAX_ARG_NAME_LEN)
|
||||
|
||||
/* Reserved field names */
|
||||
#define FIELD_STRING_IP "__probe_ip"
|
||||
@ -357,15 +360,42 @@ int trace_probe_print_args(struct trace_seq *s, struct probe_arg *args, int nr_a
|
||||
#define trace_probe_for_each_link_rcu(pos, tp) \
|
||||
list_for_each_entry_rcu(pos, &(tp)->event->files, list)
|
||||
|
||||
/*
|
||||
* The flags used for parsing trace_probe arguments.
|
||||
* TPARG_FL_RETURN, TPARG_FL_FENTRY and TPARG_FL_TEVENT are mutually exclusive.
|
||||
* TPARG_FL_KERNEL and TPARG_FL_USER are also mutually exclusive.
|
||||
* TPARG_FL_FPROBE and TPARG_FL_TPOINT are optional but it should be with
|
||||
* TPARG_FL_KERNEL.
|
||||
*/
|
||||
#define TPARG_FL_RETURN BIT(0)
|
||||
#define TPARG_FL_KERNEL BIT(1)
|
||||
#define TPARG_FL_FENTRY BIT(2)
|
||||
#define TPARG_FL_TPOINT BIT(3)
|
||||
#define TPARG_FL_TEVENT BIT(3)
|
||||
#define TPARG_FL_USER BIT(4)
|
||||
#define TPARG_FL_MASK GENMASK(4, 0)
|
||||
#define TPARG_FL_FPROBE BIT(5)
|
||||
#define TPARG_FL_TPOINT BIT(6)
|
||||
#define TPARG_FL_LOC_MASK GENMASK(4, 0)
|
||||
|
||||
static inline bool tparg_is_function_entry(unsigned int flags)
|
||||
{
|
||||
return (flags & TPARG_FL_LOC_MASK) == (TPARG_FL_KERNEL | TPARG_FL_FENTRY);
|
||||
}
|
||||
|
||||
struct traceprobe_parse_context {
|
||||
struct trace_event_call *event;
|
||||
const struct btf_param *params;
|
||||
s32 nr_params;
|
||||
const char *funcname;
|
||||
unsigned int flags;
|
||||
int offset;
|
||||
};
|
||||
|
||||
extern int traceprobe_parse_probe_arg(struct trace_probe *tp, int i,
|
||||
const char *argv, unsigned int flags);
|
||||
const char *argv,
|
||||
struct traceprobe_parse_context *ctx);
|
||||
const char **traceprobe_expand_meta_args(int argc, const char *argv[],
|
||||
int *new_argc, char *buf, int bufsize,
|
||||
struct traceprobe_parse_context *ctx);
|
||||
|
||||
extern int traceprobe_update_arg(struct probe_arg *arg);
|
||||
extern void traceprobe_free_probe_arg(struct probe_arg *arg);
|
||||
@ -404,11 +434,12 @@ extern int traceprobe_define_arg_fields(struct trace_event_call *event_call,
|
||||
C(REFCNT_OPEN_BRACE, "Reference counter brace is not closed"), \
|
||||
C(BAD_REFCNT_SUFFIX, "Reference counter has wrong suffix"), \
|
||||
C(BAD_UPROBE_OFFS, "Invalid uprobe offset"), \
|
||||
C(MAXACT_NO_KPROBE, "Maxactive is not for kprobe"), \
|
||||
C(BAD_MAXACT_TYPE, "Maxactive is only for function exit"), \
|
||||
C(BAD_MAXACT, "Invalid maxactive number"), \
|
||||
C(MAXACT_TOO_BIG, "Maxactive is too big"), \
|
||||
C(BAD_PROBE_ADDR, "Invalid probed address or symbol"), \
|
||||
C(BAD_RETPROBE, "Retprobe address must be an function entry"), \
|
||||
C(NO_TRACEPOINT, "Tracepoint is not found"), \
|
||||
C(BAD_ADDR_SUFFIX, "Invalid probed address suffix"), \
|
||||
C(NO_GROUP_NAME, "Group name is not specified"), \
|
||||
C(GROUP_TOO_LONG, "Group name is too long"), \
|
||||
@ -418,6 +449,7 @@ extern int traceprobe_define_arg_fields(struct trace_event_call *event_call,
|
||||
C(BAD_EVENT_NAME, "Event name must follow the same rules as C identifiers"), \
|
||||
C(EVENT_EXIST, "Given group/event name is already used by another event"), \
|
||||
C(RETVAL_ON_PROBE, "$retval is not available on probe"), \
|
||||
C(NO_RETVAL, "This function returns 'void' type"), \
|
||||
C(BAD_STACK_NUM, "Invalid stack number"), \
|
||||
C(BAD_ARG_NUM, "Invalid argument number"), \
|
||||
C(BAD_VAR, "Invalid $-valiable specified"), \
|
||||
@ -456,7 +488,14 @@ extern int traceprobe_define_arg_fields(struct trace_event_call *event_call,
|
||||
C(NO_EVENT_INFO, "This requires both group and event name to attach"),\
|
||||
C(BAD_ATTACH_EVENT, "Attached event does not exist"),\
|
||||
C(BAD_ATTACH_ARG, "Attached event does not have this field"),\
|
||||
C(NO_EP_FILTER, "No filter rule after 'if'"),
|
||||
C(NO_EP_FILTER, "No filter rule after 'if'"), \
|
||||
C(NOSUP_BTFARG, "BTF is not available or not supported"), \
|
||||
C(NO_BTFARG, "This variable is not found at this probe point"),\
|
||||
C(NO_BTF_ENTRY, "No BTF entry for this probe point"), \
|
||||
C(BAD_VAR_ARGS, "$arg* must be an independent parameter without name etc."),\
|
||||
C(NOFENTRY_ARGS, "$arg* can be used only on function entry"), \
|
||||
C(DOUBLE_ARGS, "$arg* can be used only once in the parameters"), \
|
||||
C(ARGS_2LONG, "$arg* failed because the argument list is too long"),
|
||||
|
||||
#undef C
|
||||
#define C(a, b) TP_ERR_##a
|
||||
|
@ -686,10 +686,12 @@ static int __trace_uprobe_create(int argc, const char **argv)
|
||||
|
||||
/* parse arguments */
|
||||
for (i = 0; i < argc && i < MAX_TRACE_ARGS; i++) {
|
||||
struct traceprobe_parse_context ctx = {
|
||||
.flags = (is_return ? TPARG_FL_RETURN : 0) | TPARG_FL_USER,
|
||||
};
|
||||
|
||||
trace_probe_log_set_index(i + 2);
|
||||
ret = traceprobe_parse_probe_arg(&tu->tp, i, argv[i],
|
||||
(is_return ? TPARG_FL_RETURN : 0) |
|
||||
TPARG_FL_USER);
|
||||
ret = traceprobe_parse_probe_arg(&tu->tp, i, argv[i], &ctx);
|
||||
if (ret)
|
||||
goto error;
|
||||
}
|
||||
|
@ -39,7 +39,8 @@ static noinline u32 fprobe_selftest_nest_target(u32 value, u32 (*nest)(u32))
|
||||
}
|
||||
|
||||
static notrace int fp_entry_handler(struct fprobe *fp, unsigned long ip,
|
||||
struct pt_regs *regs, void *data)
|
||||
unsigned long ret_ip,
|
||||
struct pt_regs *regs, void *data)
|
||||
{
|
||||
KUNIT_EXPECT_FALSE(current_test, preemptible());
|
||||
/* This can be called on the fprobe_selftest_target and the fprobe_selftest_target2 */
|
||||
@ -57,6 +58,7 @@ static notrace int fp_entry_handler(struct fprobe *fp, unsigned long ip,
|
||||
}
|
||||
|
||||
static notrace void fp_exit_handler(struct fprobe *fp, unsigned long ip,
|
||||
unsigned long ret_ip,
|
||||
struct pt_regs *regs, void *data)
|
||||
{
|
||||
unsigned long ret = regs_return_value(regs);
|
||||
@ -78,14 +80,16 @@ static notrace void fp_exit_handler(struct fprobe *fp, unsigned long ip,
|
||||
}
|
||||
|
||||
static notrace int nest_entry_handler(struct fprobe *fp, unsigned long ip,
|
||||
struct pt_regs *regs, void *data)
|
||||
unsigned long ret_ip,
|
||||
struct pt_regs *regs, void *data)
|
||||
{
|
||||
KUNIT_EXPECT_FALSE(current_test, preemptible());
|
||||
return 0;
|
||||
}
|
||||
|
||||
static notrace void nest_exit_handler(struct fprobe *fp, unsigned long ip,
|
||||
struct pt_regs *regs, void *data)
|
||||
unsigned long ret_ip,
|
||||
struct pt_regs *regs, void *data)
|
||||
{
|
||||
KUNIT_EXPECT_FALSE(current_test, preemptible());
|
||||
KUNIT_EXPECT_EQ(current_test, ip, target_nest_ip);
|
||||
|
@ -49,6 +49,7 @@ static void show_backtrace(void)
|
||||
}
|
||||
|
||||
static int sample_entry_handler(struct fprobe *fp, unsigned long ip,
|
||||
unsigned long ret_ip,
|
||||
struct pt_regs *regs, void *data)
|
||||
{
|
||||
if (use_trace)
|
||||
@ -65,10 +66,11 @@ static int sample_entry_handler(struct fprobe *fp, unsigned long ip,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void sample_exit_handler(struct fprobe *fp, unsigned long ip, struct pt_regs *regs,
|
||||
static void sample_exit_handler(struct fprobe *fp, unsigned long ip,
|
||||
unsigned long ret_ip, struct pt_regs *regs,
|
||||
void *data)
|
||||
{
|
||||
unsigned long rip = instruction_pointer(regs);
|
||||
unsigned long rip = ret_ip;
|
||||
|
||||
if (use_trace)
|
||||
/*
|
||||
|
@ -0,0 +1,58 @@
|
||||
#!/bin/sh
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# description: Generic dynamic event - add/remove probes with BTF arguments
|
||||
# requires: dynamic_events "<argname>":README
|
||||
|
||||
KPROBES=
|
||||
FPROBES=
|
||||
|
||||
if grep -qF "p[:[<group>/][<event>]] <place> [<args>]" README ; then
|
||||
KPROBES=yes
|
||||
fi
|
||||
if grep -qF "f[:[<group>/][<event>]] <func-name>[%return] [<args>]" README ; then
|
||||
FPROBES=yes
|
||||
fi
|
||||
|
||||
if [ -z "$KPROBES" -a "$FPROBES" ] ; then
|
||||
exit_unsupported
|
||||
fi
|
||||
|
||||
echo 0 > events/enable
|
||||
echo > dynamic_events
|
||||
|
||||
TP=kfree
|
||||
|
||||
if [ "$FPROBES" ] ; then
|
||||
echo "f:fpevent $TP object" >> dynamic_events
|
||||
echo "t:tpevent $TP ptr" >> dynamic_events
|
||||
|
||||
grep -q "fpevent.*object=object" dynamic_events
|
||||
grep -q "tpevent.*ptr=ptr" dynamic_events
|
||||
|
||||
echo > dynamic_events
|
||||
|
||||
echo "f:fpevent $TP "'$arg1' >> dynamic_events
|
||||
grep -q "fpevent.*object=object" dynamic_events
|
||||
echo > dynamic_events
|
||||
|
||||
echo "f:fpevent $TP "'$arg*' >> dynamic_events
|
||||
echo "t:tpevent $TP "'$arg*' >> dynamic_events
|
||||
|
||||
grep -q "fpevent.*object=object" dynamic_events
|
||||
grep -q "tpevent.*ptr=ptr" dynamic_events
|
||||
! grep -q "tpevent.*_data" dynamic_events
|
||||
fi
|
||||
|
||||
echo > dynamic_events
|
||||
|
||||
if [ "$KPROBES" ] ; then
|
||||
echo "p:kpevent $TP object" >> dynamic_events
|
||||
grep -q "kpevent.*object=object" dynamic_events
|
||||
|
||||
echo > dynamic_events
|
||||
|
||||
echo "p:kpevent $TP "'$arg*' >> dynamic_events
|
||||
grep -q "kpevent.*object=object" dynamic_events
|
||||
fi
|
||||
|
||||
clear_trace
|
@ -0,0 +1,26 @@
|
||||
#!/bin/sh
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# description: Generic dynamic event - add/remove fprobe events
|
||||
# requires: dynamic_events "f[:[<group>/][<event>]] <func-name>[%return] [<args>]":README
|
||||
|
||||
echo 0 > events/enable
|
||||
echo > dynamic_events
|
||||
|
||||
PLACE=$FUNCTION_FORK
|
||||
|
||||
echo "f:myevent1 $PLACE" >> dynamic_events
|
||||
echo "f:myevent2 $PLACE%return" >> dynamic_events
|
||||
|
||||
grep -q myevent1 dynamic_events
|
||||
grep -q myevent2 dynamic_events
|
||||
test -d events/fprobes/myevent1
|
||||
test -d events/fprobes/myevent2
|
||||
|
||||
echo "-:myevent2" >> dynamic_events
|
||||
|
||||
grep -q myevent1 dynamic_events
|
||||
! grep -q myevent2 dynamic_events
|
||||
|
||||
echo > dynamic_events
|
||||
|
||||
clear_trace
|
@ -0,0 +1,27 @@
|
||||
#!/bin/sh
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# description: Generic dynamic event - add/remove tracepoint probe events
|
||||
# requires: dynamic_events "t[:[<group>/][<event>]] <tracepoint> [<args>]":README
|
||||
|
||||
echo 0 > events/enable
|
||||
echo > dynamic_events
|
||||
|
||||
TRACEPOINT1=kmem_cache_alloc
|
||||
TRACEPOINT2=kmem_cache_free
|
||||
|
||||
echo "t:myevent1 $TRACEPOINT1" >> dynamic_events
|
||||
echo "t:myevent2 $TRACEPOINT2" >> dynamic_events
|
||||
|
||||
grep -q myevent1 dynamic_events
|
||||
grep -q myevent2 dynamic_events
|
||||
test -d events/tracepoints/myevent1
|
||||
test -d events/tracepoints/myevent2
|
||||
|
||||
echo "-:myevent2" >> dynamic_events
|
||||
|
||||
grep -q myevent1 dynamic_events
|
||||
! grep -q myevent2 dynamic_events
|
||||
|
||||
echo > dynamic_events
|
||||
|
||||
clear_trace
|
@ -0,0 +1,111 @@
|
||||
#!/bin/sh
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# description: Fprobe event parser error log check
|
||||
# requires: dynamic_events "f[:[<group>/][<event>]] <func-name>[%return] [<args>]":README
|
||||
|
||||
check_error() { # command-with-error-pos-by-^
|
||||
ftrace_errlog_check 'trace_fprobe' "$1" 'dynamic_events'
|
||||
}
|
||||
|
||||
case `uname -m` in
|
||||
x86_64|i[3456]86)
|
||||
REG=%ax ;;
|
||||
aarch64)
|
||||
REG=%x0 ;;
|
||||
*)
|
||||
REG=%r0 ;;
|
||||
esac
|
||||
|
||||
check_error 'f^100 vfs_read' # MAXACT_NO_KPROBE
|
||||
check_error 'f^1a111 vfs_read' # BAD_MAXACT
|
||||
check_error 'f^100000 vfs_read' # MAXACT_TOO_BIG
|
||||
|
||||
check_error 'f ^non_exist_func' # BAD_PROBE_ADDR (enoent)
|
||||
check_error 'f ^vfs_read+10' # BAD_PROBE_ADDR
|
||||
check_error 'f:^/bar vfs_read' # NO_GROUP_NAME
|
||||
check_error 'f:^12345678901234567890123456789012345678901234567890123456789012345/bar vfs_read' # GROUP_TOO_LONG
|
||||
|
||||
check_error 'f:^foo.1/bar vfs_read' # BAD_GROUP_NAME
|
||||
check_error 'f:^ vfs_read' # NO_EVENT_NAME
|
||||
check_error 'f:foo/^12345678901234567890123456789012345678901234567890123456789012345 vfs_read' # EVENT_TOO_LONG
|
||||
check_error 'f:foo/^bar.1 vfs_read' # BAD_EVENT_NAME
|
||||
|
||||
check_error 'f vfs_read ^$retval' # RETVAL_ON_PROBE
|
||||
check_error 'f vfs_read ^$stack10000' # BAD_STACK_NUM
|
||||
|
||||
check_error 'f vfs_read ^$arg10000' # BAD_ARG_NUM
|
||||
|
||||
check_error 'f vfs_read ^$none_var' # BAD_VAR
|
||||
check_error 'f vfs_read ^'$REG # BAD_VAR
|
||||
|
||||
check_error 'f vfs_read ^@12345678abcde' # BAD_MEM_ADDR
|
||||
check_error 'f vfs_read ^@+10' # FILE_ON_KPROBE
|
||||
|
||||
grep -q "imm-value" README && \
|
||||
check_error 'f vfs_read arg1=\^x' # BAD_IMM
|
||||
grep -q "imm-string" README && \
|
||||
check_error 'f vfs_read arg1=\"abcd^' # IMMSTR_NO_CLOSE
|
||||
|
||||
check_error 'f vfs_read ^+0@0)' # DEREF_NEED_BRACE
|
||||
check_error 'f vfs_read ^+0ab1(@0)' # BAD_DEREF_OFFS
|
||||
check_error 'f vfs_read +0(+0(@0^)' # DEREF_OPEN_BRACE
|
||||
|
||||
if grep -A1 "fetcharg:" README | grep -q '\$comm' ; then
|
||||
check_error 'f vfs_read +0(^$comm)' # COMM_CANT_DEREF
|
||||
fi
|
||||
|
||||
check_error 'f vfs_read ^&1' # BAD_FETCH_ARG
|
||||
|
||||
|
||||
# We've introduced this limitation with array support
|
||||
if grep -q ' <type>\\\[<array-size>\\\]' README; then
|
||||
check_error 'f vfs_read +0(^+0(+0(+0(+0(+0(+0(+0(+0(+0(+0(+0(+0(+0(@0))))))))))))))' # TOO_MANY_OPS?
|
||||
check_error 'f vfs_read +0(@11):u8[10^' # ARRAY_NO_CLOSE
|
||||
check_error 'f vfs_read +0(@11):u8[10]^a' # BAD_ARRAY_SUFFIX
|
||||
check_error 'f vfs_read +0(@11):u8[^10a]' # BAD_ARRAY_NUM
|
||||
check_error 'f vfs_read +0(@11):u8[^256]' # ARRAY_TOO_BIG
|
||||
fi
|
||||
|
||||
check_error 'f vfs_read @11:^unknown_type' # BAD_TYPE
|
||||
check_error 'f vfs_read $stack0:^string' # BAD_STRING
|
||||
check_error 'f vfs_read @11:^b10@a/16' # BAD_BITFIELD
|
||||
|
||||
check_error 'f vfs_read ^arg123456789012345678901234567890=@11' # ARG_NAME_TOO_LOG
|
||||
check_error 'f vfs_read ^=@11' # NO_ARG_NAME
|
||||
check_error 'f vfs_read ^var.1=@11' # BAD_ARG_NAME
|
||||
check_error 'f vfs_read var1=@11 ^var1=@12' # USED_ARG_NAME
|
||||
check_error 'f vfs_read ^+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(@1234))))))' # ARG_TOO_LONG
|
||||
check_error 'f vfs_read arg1=^' # NO_ARG_BODY
|
||||
|
||||
|
||||
# multiprobe errors
|
||||
if grep -q "Create/append/" README && grep -q "imm-value" README; then
|
||||
echo "f:fprobes/testevent $FUNCTION_FORK" > dynamic_events
|
||||
check_error '^f:fprobes/testevent do_exit%return' # DIFF_PROBE_TYPE
|
||||
|
||||
# Explicitly use printf "%s" to not interpret \1
|
||||
printf "%s" "f:fprobes/testevent $FUNCTION_FORK abcd=\\1" > dynamic_events
|
||||
check_error "f:fprobes/testevent $FUNCTION_FORK ^bcd=\\1" # DIFF_ARG_TYPE
|
||||
check_error "f:fprobes/testevent $FUNCTION_FORK ^abcd=\\1:u8" # DIFF_ARG_TYPE
|
||||
check_error "f:fprobes/testevent $FUNCTION_FORK ^abcd=\\\"foo\"" # DIFF_ARG_TYPE
|
||||
check_error "^f:fprobes/testevent $FUNCTION_FORK abcd=\\1" # SAME_PROBE
|
||||
fi
|
||||
|
||||
# %return suffix errors
|
||||
check_error 'f vfs_read^%hoge' # BAD_ADDR_SUFFIX
|
||||
|
||||
# BTF arguments errors
|
||||
if grep -q "<argname>" README; then
|
||||
check_error 'f vfs_read args=^$arg*' # BAD_VAR_ARGS
|
||||
check_error 'f vfs_read +0(^$arg*)' # BAD_VAR_ARGS
|
||||
check_error 'f vfs_read $arg* ^$arg*' # DOUBLE_ARGS
|
||||
check_error 'f vfs_read%return ^$arg*' # NOFENTRY_ARGS
|
||||
check_error 'f vfs_read ^hoge' # NO_BTFARG
|
||||
check_error 'f kfree ^$arg10' # NO_BTFARG (exceed the number of parameters)
|
||||
check_error 'f kfree%return ^$retval' # NO_RETVAL
|
||||
else
|
||||
check_error 'f vfs_read ^$arg*' # NOSUP_BTFARG
|
||||
check_error 't kfree ^$arg*' # NOSUP_BTFARG
|
||||
fi
|
||||
|
||||
exit 0
|
@ -0,0 +1,82 @@
|
||||
#!/bin/sh
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# description: Tracepoint probe event parser error log check
|
||||
# requires: dynamic_events "t[:[<group>/][<event>]] <tracepoint> [<args>]":README
|
||||
|
||||
check_error() { # command-with-error-pos-by-^
|
||||
ftrace_errlog_check 'trace_fprobe' "$1" 'dynamic_events'
|
||||
}
|
||||
|
||||
check_error 't^100 kfree' # BAD_MAXACT_TYPE
|
||||
|
||||
check_error 't ^non_exist_tracepoint' # NO_TRACEPOINT
|
||||
check_error 't:^/bar kfree' # NO_GROUP_NAME
|
||||
check_error 't:^12345678901234567890123456789012345678901234567890123456789012345/bar kfree' # GROUP_TOO_LONG
|
||||
|
||||
check_error 't:^foo.1/bar kfree' # BAD_GROUP_NAME
|
||||
check_error 't:^ kfree' # NO_EVENT_NAME
|
||||
check_error 't:foo/^12345678901234567890123456789012345678901234567890123456789012345 kfree' # EVENT_TOO_LONG
|
||||
check_error 't:foo/^bar.1 kfree' # BAD_EVENT_NAME
|
||||
|
||||
check_error 't kfree ^$retval' # RETVAL_ON_PROBE
|
||||
check_error 't kfree ^$stack10000' # BAD_STACK_NUM
|
||||
|
||||
check_error 't kfree ^$arg10000' # BAD_ARG_NUM
|
||||
|
||||
check_error 't kfree ^$none_var' # BAD_VAR
|
||||
check_error 't kfree ^%rax' # BAD_VAR
|
||||
|
||||
check_error 't kfree ^@12345678abcde' # BAD_MEM_ADDR
|
||||
check_error 't kfree ^@+10' # FILE_ON_KPROBE
|
||||
|
||||
grep -q "imm-value" README && \
|
||||
check_error 't kfree arg1=\^x' # BAD_IMM
|
||||
grep -q "imm-string" README && \
|
||||
check_error 't kfree arg1=\"abcd^' # IMMSTR_NO_CLOSE
|
||||
|
||||
check_error 't kfree ^+0@0)' # DEREF_NEED_BRACE
|
||||
check_error 't kfree ^+0ab1(@0)' # BAD_DEREF_OFFS
|
||||
check_error 't kfree +0(+0(@0^)' # DEREF_OPEN_BRACE
|
||||
|
||||
if grep -A1 "fetcharg:" README | grep -q '\$comm' ; then
|
||||
check_error 't kfree +0(^$comm)' # COMM_CANT_DEREF
|
||||
fi
|
||||
|
||||
check_error 't kfree ^&1' # BAD_FETCH_ARG
|
||||
|
||||
|
||||
# We've introduced this limitation with array support
|
||||
if grep -q ' <type>\\\[<array-size>\\\]' README; then
|
||||
check_error 't kfree +0(^+0(+0(+0(+0(+0(+0(+0(+0(+0(+0(+0(+0(+0(@0))))))))))))))' # TOO_MANY_OPS?
|
||||
check_error 't kfree +0(@11):u8[10^' # ARRAY_NO_CLOSE
|
||||
check_error 't kfree +0(@11):u8[10]^a' # BAD_ARRAY_SUFFIX
|
||||
check_error 't kfree +0(@11):u8[^10a]' # BAD_ARRAY_NUM
|
||||
check_error 't kfree +0(@11):u8[^256]' # ARRAY_TOO_BIG
|
||||
fi
|
||||
|
||||
check_error 't kfree @11:^unknown_type' # BAD_TYPE
|
||||
check_error 't kfree $stack0:^string' # BAD_STRING
|
||||
check_error 't kfree @11:^b10@a/16' # BAD_BITFIELD
|
||||
|
||||
check_error 't kfree ^arg123456789012345678901234567890=@11' # ARG_NAME_TOO_LOG
|
||||
check_error 't kfree ^=@11' # NO_ARG_NAME
|
||||
check_error 't kfree ^var.1=@11' # BAD_ARG_NAME
|
||||
check_error 't kfree var1=@11 ^var1=@12' # USED_ARG_NAME
|
||||
check_error 't kfree ^+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(@1234))))))' # ARG_TOO_LONG
|
||||
check_error 't kfree arg1=^' # NO_ARG_BODY
|
||||
|
||||
|
||||
# multiprobe errors
|
||||
if grep -q "Create/append/" README && grep -q "imm-value" README; then
|
||||
echo "t:tracepoint/testevent kfree" > dynamic_events
|
||||
check_error '^f:tracepoint/testevent kfree' # DIFF_PROBE_TYPE
|
||||
|
||||
# Explicitly use printf "%s" to not interpret \1
|
||||
printf "%s" "t:tracepoints/testevent kfree abcd=\\1" > dynamic_events
|
||||
check_error "t:tracepoints/testevent kfree ^bcd=\\1" # DIFF_ARG_TYPE
|
||||
check_error "t:tracepoints/testevent kfree ^abcd=\\1:u8" # DIFF_ARG_TYPE
|
||||
check_error "t:tracepoints/testevent kfree ^abcd=\\\"foo\"" # DIFF_ARG_TYPE
|
||||
check_error "^t:tracepoints/testevent kfree abcd=\\1" # SAME_PROBE
|
||||
fi
|
||||
|
||||
exit 0
|
@ -0,0 +1,19 @@
|
||||
#!/bin/sh
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
# Copyright (C) 2023 Akanksha J N, IBM corporation
|
||||
# description: Register multiple kprobe events in a function
|
||||
# requires: kprobe_events
|
||||
|
||||
for i in `seq 0 255`; do
|
||||
echo p $FUNCTION_FORK+${i} >> kprobe_events || continue
|
||||
done
|
||||
|
||||
cat kprobe_events >> $testlog
|
||||
|
||||
echo 1 > events/kprobes/enable
|
||||
( echo "forked" )
|
||||
echo 0 > events/kprobes/enable
|
||||
echo > kprobe_events
|
||||
echo "Waiting for unoptimizing & freeing"
|
||||
sleep 5
|
||||
echo "Done"
|
@ -8,7 +8,7 @@ check_error() { # command-with-error-pos-by-^
|
||||
}
|
||||
|
||||
if grep -q 'r\[maxactive\]' README; then
|
||||
check_error 'p^100 vfs_read' # MAXACT_NO_KPROBE
|
||||
check_error 'p^100 vfs_read' # BAD_MAXACT_TYPE
|
||||
check_error 'r^1a111 vfs_read' # BAD_MAXACT
|
||||
check_error 'r^100000 vfs_read' # MAXACT_TOO_BIG
|
||||
fi
|
||||
@ -103,4 +103,18 @@ check_error 'p vfs_read^%hoge' # BAD_ADDR_SUFFIX
|
||||
check_error 'p ^vfs_read+10%return' # BAD_RETPROBE
|
||||
fi
|
||||
|
||||
# BTF arguments errors
|
||||
if grep -q "<argname>" README; then
|
||||
check_error 'p vfs_read args=^$arg*' # BAD_VAR_ARGS
|
||||
check_error 'p vfs_read +0(^$arg*)' # BAD_VAR_ARGS
|
||||
check_error 'p vfs_read $arg* ^$arg*' # DOUBLE_ARGS
|
||||
check_error 'r vfs_read ^$arg*' # NOFENTRY_ARGS
|
||||
check_error 'p vfs_read+8 ^$arg*' # NOFENTRY_ARGS
|
||||
check_error 'p vfs_read ^hoge' # NO_BTFARG
|
||||
check_error 'p kfree ^$arg10' # NO_BTFARG (exceed the number of parameters)
|
||||
check_error 'r kfree ^$retval' # NO_RETVAL
|
||||
else
|
||||
check_error 'p vfs_read ^$arg*' # NOSUP_BTFARG
|
||||
fi
|
||||
|
||||
exit 0
|
||||
|
Loading…
Reference in New Issue
Block a user