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:
Linus Torvalds 2023-06-30 10:44:53 -07:00
commit d2a6fd45c5
31 changed files with 2482 additions and 170 deletions

View 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``.

View File

@ -13,6 +13,7 @@ Linux Tracing Technologies
kprobes
kprobetrace
uprobetracer
fprobetrace
tracepoints
events
events-kmem

View File

@ -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

View File

@ -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
/**

View File

@ -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.

View File

@ -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),
};

View File

@ -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;

View File

@ -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) \

View File

@ -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;

View File

@ -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

View File

@ -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/

View File

@ -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;

View File

@ -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;
/*

View File

@ -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;

View File

@ -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

View File

@ -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;

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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;

View File

@ -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, &param);
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, &param);
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, &param);
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, &param);
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, &param);
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;

View File

@ -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

View File

@ -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;
}

View File

@ -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);

View File

@ -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)
/*

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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