Commit b70100f2 authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'probes-v6.6' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace

Pull probes updates from Masami Hiramatsu:

 - kprobes: use struct_size() for variable size kretprobe_instance data
   structure.

 - eprobe: Simplify trace_eprobe list iteration.

 - probe events: Data structure field access support on BTF argument.

     - Update BTF argument support on the functions in the kernel
       loadable modules (only loaded modules are supported).

     - Move generic BTF access function (search function prototype and
       get function parameters) to a separated file.

     - Add a function to search a member of data structure in BTF.

     - Support accessing BTF data structure member from probe args by
       C-like arrow('->') and dot('.') operators. e.g.
          't sched_switch next=next->pid vruntime=next->se.vruntime'

     - Support accessing BTF data structure member from $retval. e.g.
          'f getname_flags%return +0($retval->name):string'

     - Add string type checking if BTF type info is available. This will
       reject if user specify ":string" type for non "char pointer"
       type.

     - Automatically assume the fprobe event as a function return event
       if $retval is used.

 - selftests/ftrace: Add BTF data field access test cases.

 - Documentation: Update fprobe event example with BTF data field.

* tag 'probes-v6.6' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace:
  Documentation: tracing: Update fprobe event example with BTF field
  selftests/ftrace: Add BTF fields access testcases
  tracing/fprobe-event: Assume fprobe is a return event by $retval
  tracing/probes: Add string type check with BTF
  tracing/probes: Support BTF field access from $retval
  tracing/probes: Support BTF based data structure field access
  tracing/probes: Add a function to search a member of a struct/union
  tracing/probes: Move finding func-proto API and getting func-param API to trace_btf
  tracing/probes: Support BTF argument on module functions
  tracing/eprobe: Iterate trace_eprobe directly
  kernel: kprobes: Use struct_size()
parents e021c5f1 a2439a4c
...@@ -79,9 +79,9 @@ automatically set by the given name. :: ...@@ -79,9 +79,9 @@ automatically set by the given name. ::
f:fprobes/myprobe vfs_read count=count pos=pos f:fprobes/myprobe vfs_read count=count pos=pos
It also chooses the fetch type from BTF information. For example, in the above 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 example, the ``count`` is unsigned long, and the ``pos`` is a pointer. Thus,
are converted to 64bit unsigned long, but only ``pos`` has "%Lx" print-format as both are converted to 64bit unsigned long, but only ``pos`` has "%Lx"
below :: print-format as below ::
# cat events/fprobes/myprobe/format # cat events/fprobes/myprobe/format
name: myprobe name: myprobe
...@@ -105,9 +105,47 @@ is expanded to all function arguments of the function or the tracepoint. :: ...@@ -105,9 +105,47 @@ is expanded to all function arguments of the function or the tracepoint. ::
# cat dynamic_events # cat dynamic_events
f:fprobes/myprobe vfs_read file=file buf=buf count=count pos=pos 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 BTF also affects the ``$retval``. If user doesn't set any type, the retval
automatically picked from the BTF. If the function returns ``void``, ``$retval`` type is automatically picked from the BTF. If the function returns ``void``,
is rejected. ``$retval`` is rejected.
You can access the data fields of a data structure using allow operator ``->``
(for pointer type) and dot operator ``.`` (for data structure type.)::
# echo 't sched_switch preempt prev_pid=prev->pid next_pid=next->pid' >> dynamic_events
The field access operators, ``->`` and ``.`` can be combined for accessing deeper
members and other structure members pointed by the member. e.g. ``foo->bar.baz->qux``
If there is non-name union member, you can directly access it as the C code does.
For example::
struct {
union {
int a;
int b;
};
} *foo;
To access ``a`` and ``b``, use ``foo->a`` and ``foo->b`` in this case.
This data field access is available for the return value via ``$retval``,
e.g. ``$retval->name``.
For these BTF arguments and fields, ``:string`` and ``:ustring`` change the
behavior. If these are used for BTF argument or field, it checks whether
the BTF type of the argument or the data field is ``char *`` or ``char []``,
or not. If not, it rejects applying the string types. Also, with the BTF
support, you don't need a memory dereference operator (``+0(PTR)``) for
accessing the string pointed by a ``PTR``. It automatically adds the memory
dereference operator according to the BTF type. e.g. ::
# echo 't sched_switch prev->comm:string' >> dynamic_events
# echo 'f getname_flags%return $retval->name:string' >> dynamic_events
The ``prev->comm`` is an embedded char array in the data structure, and
``$retval->name`` is a char pointer in the data structure. But in both
cases, you can use ``:string`` type to get the string.
Usage examples Usage examples
-------------- --------------
...@@ -161,10 +199,10 @@ parameters. This means you can access any field values in the task ...@@ -161,10 +199,10 @@ parameters. This means you can access any field values in the task
structure pointed by the ``prev`` and ``next`` arguments. structure pointed by the ``prev`` and ``next`` arguments.
For example, usually ``task_struct::start_time`` is not traced, but with this For example, usually ``task_struct::start_time`` is not traced, but with this
traceprobe event, you can trace it as below. traceprobe event, you can trace that field as below.
:: ::
# echo 't sched_switch comm=+1896(next):string start_time=+1728(next):u64' > dynamic_events # echo 't sched_switch comm=next->comm:string next->start_time' > dynamic_events
# head -n 20 trace | tail # head -n 20 trace | tail
# TASK-PID CPU# ||||| TIMESTAMP FUNCTION # TASK-PID CPU# ||||| TIMESTAMP FUNCTION
# | | | ||||| | | # | | | ||||| | |
...@@ -176,13 +214,3 @@ traceprobe event, you can trace it as below. ...@@ -176,13 +214,3 @@ traceprobe event, you can trace it as below.
<idle>-0 [000] d..3. 5606.690317: sched_switch: (__probestub_sched_switch+0x4/0x10) comm="kworker/0:1" usage=1 start_time=137000000 <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 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 <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``.
...@@ -209,6 +209,7 @@ struct btf_record *btf_parse_fields(const struct btf *btf, const struct btf_type ...@@ -209,6 +209,7 @@ struct btf_record *btf_parse_fields(const struct btf *btf, const struct btf_type
int btf_check_and_fixup_fields(const struct btf *btf, struct btf_record *rec); int btf_check_and_fixup_fields(const struct btf *btf, struct btf_record *rec);
bool btf_type_is_void(const struct btf_type *t); bool btf_type_is_void(const struct btf_type *t);
s32 btf_find_by_name_kind(const struct btf *btf, const char *name, u8 kind); s32 btf_find_by_name_kind(const struct btf *btf, const char *name, u8 kind);
s32 bpf_find_btf_id(const char *name, u32 kind, struct btf **btf_p);
const struct btf_type *btf_type_skip_modifiers(const struct btf *btf, const struct btf_type *btf_type_skip_modifiers(const struct btf *btf,
u32 id, u32 *res_id); u32 id, u32 *res_id);
const struct btf_type *btf_type_resolve_ptr(const struct btf *btf, const struct btf_type *btf_type_resolve_ptr(const struct btf *btf,
......
...@@ -553,7 +553,7 @@ s32 btf_find_by_name_kind(const struct btf *btf, const char *name, u8 kind) ...@@ -553,7 +553,7 @@ s32 btf_find_by_name_kind(const struct btf *btf, const char *name, u8 kind)
return -ENOENT; return -ENOENT;
} }
static s32 bpf_find_btf_id(const char *name, u32 kind, struct btf **btf_p) s32 bpf_find_btf_id(const char *name, u32 kind, struct btf **btf_p)
{ {
struct btf *btf; struct btf *btf;
s32 ret; s32 ret;
......
...@@ -2232,8 +2232,7 @@ int register_kretprobe(struct kretprobe *rp) ...@@ -2232,8 +2232,7 @@ int register_kretprobe(struct kretprobe *rp)
return -ENOMEM; return -ENOMEM;
for (i = 0; i < rp->maxactive; i++) { for (i = 0; i < rp->maxactive; i++) {
inst = kzalloc(sizeof(struct kretprobe_instance) + inst = kzalloc(struct_size(inst, data, rp->data_size), GFP_KERNEL);
rp->data_size, GFP_KERNEL);
if (inst == NULL) { if (inst == NULL) {
rethook_free(rp->rh); rethook_free(rp->rh);
rp->rh = NULL; rp->rh = NULL;
...@@ -2256,8 +2255,7 @@ int register_kretprobe(struct kretprobe *rp) ...@@ -2256,8 +2255,7 @@ int register_kretprobe(struct kretprobe *rp)
rp->rph->rp = rp; rp->rph->rp = rp;
for (i = 0; i < rp->maxactive; i++) { for (i = 0; i < rp->maxactive; i++) {
inst = kzalloc(sizeof(struct kretprobe_instance) + inst = kzalloc(struct_size(inst, data, rp->data_size), GFP_KERNEL);
rp->data_size, GFP_KERNEL);
if (inst == NULL) { if (inst == NULL) {
refcount_set(&rp->rph->ref, i); refcount_set(&rp->rph->ref, i);
free_rp_inst(rp); free_rp_inst(rp);
......
...@@ -99,6 +99,7 @@ obj-$(CONFIG_KGDB_KDB) += trace_kdb.o ...@@ -99,6 +99,7 @@ obj-$(CONFIG_KGDB_KDB) += trace_kdb.o
endif endif
obj-$(CONFIG_DYNAMIC_EVENTS) += trace_dynevent.o obj-$(CONFIG_DYNAMIC_EVENTS) += trace_dynevent.o
obj-$(CONFIG_PROBE_EVENTS) += trace_probe.o obj-$(CONFIG_PROBE_EVENTS) += trace_probe.o
obj-$(CONFIG_PROBE_EVENTS_BTF_ARGS) += trace_btf.o
obj-$(CONFIG_UPROBE_EVENTS) += trace_uprobe.o obj-$(CONFIG_UPROBE_EVENTS) += trace_uprobe.o
obj-$(CONFIG_BOOTTIME_TRACING) += trace_boot.o obj-$(CONFIG_BOOTTIME_TRACING) += trace_boot.o
obj-$(CONFIG_FTRACE_RECORD_RECURSION) += trace_recursion_record.o obj-$(CONFIG_FTRACE_RECORD_RECURSION) += trace_recursion_record.o
......
...@@ -5711,7 +5711,8 @@ static const char readme_msg[] = ...@@ -5711,7 +5711,8 @@ static const char readme_msg[] =
"\t fetcharg: (%<register>|$<efield>), @<address>, @<symbol>[+|-<offset>],\n" "\t fetcharg: (%<register>|$<efield>), @<address>, @<symbol>[+|-<offset>],\n"
#ifdef CONFIG_HAVE_FUNCTION_ARG_ACCESS_API #ifdef CONFIG_HAVE_FUNCTION_ARG_ACCESS_API
#ifdef CONFIG_PROBE_EVENTS_BTF_ARGS #ifdef CONFIG_PROBE_EVENTS_BTF_ARGS
"\t $stack<index>, $stack, $retval, $comm, $arg<N>, <argname>\n" "\t $stack<index>, $stack, $retval, $comm, $arg<N>,\n"
"\t <argname>[->field[->field|.field...]],\n"
#else #else
"\t $stack<index>, $stack, $retval, $comm, $arg<N>,\n" "\t $stack<index>, $stack, $retval, $comm, $arg<N>,\n"
#endif #endif
......
// SPDX-License-Identifier: GPL-2.0
#include <linux/btf.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include "trace_btf.h"
/*
* Find a function proto type by name, and return the btf_type with its btf
* in *@btf_p. Return NULL if not found.
* Note that caller has to call btf_put(*@btf_p) after using the btf_type.
*/
const struct btf_type *btf_find_func_proto(const char *func_name, struct btf **btf_p)
{
const struct btf_type *t;
s32 id;
id = bpf_find_btf_id(func_name, BTF_KIND_FUNC, btf_p);
if (id < 0)
return NULL;
/* Get BTF_KIND_FUNC type */
t = btf_type_by_id(*btf_p, id);
if (!t || !btf_type_is_func(t))
goto err;
/* The type of BTF_KIND_FUNC is BTF_KIND_FUNC_PROTO */
t = btf_type_by_id(*btf_p, t->type);
if (!t || !btf_type_is_func_proto(t))
goto err;
return t;
err:
btf_put(*btf_p);
return NULL;
}
/*
* Get function parameter with the number of parameters.
* This can return NULL if the function has no parameters.
* It can return -EINVAL if the @func_proto is not a function proto type.
*/
const struct btf_param *btf_get_func_param(const struct btf_type *func_proto, s32 *nr)
{
if (!btf_type_is_func_proto(func_proto))
return ERR_PTR(-EINVAL);
*nr = btf_type_vlen(func_proto);
if (*nr > 0)
return (const struct btf_param *)(func_proto + 1);
else
return NULL;
}
#define BTF_ANON_STACK_MAX 16
struct btf_anon_stack {
u32 tid;
u32 offset;
};
/*
* Find a member of data structure/union by name and return it.
* Return NULL if not found, or -EINVAL if parameter is invalid.
* If the member is an member of anonymous union/structure, the offset
* of that anonymous union/structure is stored into @anon_offset. Caller
* can calculate the correct offset from the root data structure by
* adding anon_offset to the member's offset.
*/
const struct btf_member *btf_find_struct_member(struct btf *btf,
const struct btf_type *type,
const char *member_name,
u32 *anon_offset)
{
struct btf_anon_stack *anon_stack;
const struct btf_member *member;
u32 tid, cur_offset = 0;
const char *name;
int i, top = 0;
anon_stack = kcalloc(BTF_ANON_STACK_MAX, sizeof(*anon_stack), GFP_KERNEL);
if (!anon_stack)
return ERR_PTR(-ENOMEM);
retry:
if (!btf_type_is_struct(type)) {
member = ERR_PTR(-EINVAL);
goto out;
}
for_each_member(i, type, member) {
if (!member->name_off) {
/* Anonymous union/struct: push it for later use */
type = btf_type_skip_modifiers(btf, member->type, &tid);
if (type && top < BTF_ANON_STACK_MAX) {
anon_stack[top].tid = tid;
anon_stack[top++].offset =
cur_offset + member->offset;
}
} else {
name = btf_name_by_offset(btf, member->name_off);
if (name && !strcmp(member_name, name)) {
if (anon_offset)
*anon_offset = cur_offset;
goto out;
}
}
}
if (top > 0) {
/* Pop from the anonymous stack and retry */
tid = anon_stack[--top].tid;
cur_offset = anon_stack[top].offset;
type = btf_type_by_id(btf, tid);
goto retry;
}
member = NULL;
out:
kfree(anon_stack);
return member;
}
/* SPDX-License-Identifier: GPL-2.0 */
#include <linux/btf.h>
const struct btf_type *btf_find_func_proto(const char *func_name,
struct btf **btf_p);
const struct btf_param *btf_get_func_param(const struct btf_type *func_proto,
s32 *nr);
const struct btf_member *btf_find_struct_member(struct btf *btf,
const struct btf_type *type,
const char *member_name,
u32 *anon_offset);
...@@ -41,6 +41,10 @@ struct eprobe_data { ...@@ -41,6 +41,10 @@ struct eprobe_data {
struct trace_eprobe *ep; struct trace_eprobe *ep;
}; };
#define for_each_trace_eprobe_tp(ep, _tp) \
list_for_each_entry(ep, trace_probe_probe_list(_tp), tp.list)
static int __trace_eprobe_create(int argc, const char *argv[]); static int __trace_eprobe_create(int argc, const char *argv[]);
static void trace_event_probe_cleanup(struct trace_eprobe *ep) static void trace_event_probe_cleanup(struct trace_eprobe *ep)
...@@ -640,7 +644,7 @@ static int disable_eprobe(struct trace_eprobe *ep, ...@@ -640,7 +644,7 @@ static int disable_eprobe(struct trace_eprobe *ep,
static int enable_trace_eprobe(struct trace_event_call *call, static int enable_trace_eprobe(struct trace_event_call *call,
struct trace_event_file *file) struct trace_event_file *file)
{ {
struct trace_probe *pos, *tp; struct trace_probe *tp;
struct trace_eprobe *ep; struct trace_eprobe *ep;
bool enabled; bool enabled;
int ret = 0; int ret = 0;
...@@ -662,8 +666,7 @@ static int enable_trace_eprobe(struct trace_event_call *call, ...@@ -662,8 +666,7 @@ static int enable_trace_eprobe(struct trace_event_call *call,
if (enabled) if (enabled)
return 0; return 0;
list_for_each_entry(pos, trace_probe_probe_list(tp), list) { for_each_trace_eprobe_tp(ep, tp) {
ep = container_of(pos, struct trace_eprobe, tp);
ret = enable_eprobe(ep, file); ret = enable_eprobe(ep, file);
if (ret) if (ret)
break; break;
...@@ -680,8 +683,7 @@ static int enable_trace_eprobe(struct trace_event_call *call, ...@@ -680,8 +683,7 @@ static int enable_trace_eprobe(struct trace_event_call *call,
*/ */
WARN_ON_ONCE(ret != -ENOMEM); WARN_ON_ONCE(ret != -ENOMEM);
list_for_each_entry(pos, trace_probe_probe_list(tp), list) { for_each_trace_eprobe_tp(ep, tp) {
ep = container_of(pos, struct trace_eprobe, tp);
disable_eprobe(ep, file->tr); disable_eprobe(ep, file->tr);
if (!--cnt) if (!--cnt)
break; break;
...@@ -699,7 +701,7 @@ static int enable_trace_eprobe(struct trace_event_call *call, ...@@ -699,7 +701,7 @@ static int enable_trace_eprobe(struct trace_event_call *call,
static int disable_trace_eprobe(struct trace_event_call *call, static int disable_trace_eprobe(struct trace_event_call *call,
struct trace_event_file *file) struct trace_event_file *file)
{ {
struct trace_probe *pos, *tp; struct trace_probe *tp;
struct trace_eprobe *ep; struct trace_eprobe *ep;
tp = trace_probe_primary_from_call(call); tp = trace_probe_primary_from_call(call);
...@@ -716,10 +718,8 @@ static int disable_trace_eprobe(struct trace_event_call *call, ...@@ -716,10 +718,8 @@ static int disable_trace_eprobe(struct trace_event_call *call,
trace_probe_clear_flag(tp, TP_FLAG_PROFILE); trace_probe_clear_flag(tp, TP_FLAG_PROFILE);
if (!trace_probe_is_enabled(tp)) { if (!trace_probe_is_enabled(tp)) {
list_for_each_entry(pos, trace_probe_probe_list(tp), list) { for_each_trace_eprobe_tp(ep, tp)
ep = container_of(pos, struct trace_eprobe, tp);
disable_eprobe(ep, file->tr); disable_eprobe(ep, file->tr);
}
} }
out: out:
...@@ -807,13 +807,11 @@ static int trace_eprobe_tp_update_arg(struct trace_eprobe *ep, const char *argv[ ...@@ -807,13 +807,11 @@ static int trace_eprobe_tp_update_arg(struct trace_eprobe *ep, const char *argv[
int ret; int ret;
ret = traceprobe_parse_probe_arg(&ep->tp, i, argv[i], &ctx); ret = traceprobe_parse_probe_arg(&ep->tp, i, argv[i], &ctx);
if (ret)
return ret;
/* Handle symbols "@" */ /* Handle symbols "@" */
if (!ret) if (!ret)
ret = traceprobe_update_arg(&ep->tp.args[i]); ret = traceprobe_update_arg(&ep->tp.args[i]);
traceprobe_finish_parse(&ctx);
return ret; return ret;
} }
......
...@@ -898,6 +898,46 @@ static struct tracepoint *find_tracepoint(const char *tp_name) ...@@ -898,6 +898,46 @@ static struct tracepoint *find_tracepoint(const char *tp_name)
return data.tpoint; return data.tpoint;
} }
static int parse_symbol_and_return(int argc, const char *argv[],
char **symbol, bool *is_return,
bool is_tracepoint)
{
char *tmp = strchr(argv[1], '%');
int i;
if (tmp) {
int len = tmp - argv[1];
if (!is_tracepoint && !strcmp(tmp, "%return")) {
*is_return = true;
} else {
trace_probe_log_err(len, BAD_ADDR_SUFFIX);
return -EINVAL;
}
*symbol = kmemdup_nul(argv[1], len, GFP_KERNEL);
} else
*symbol = kstrdup(argv[1], GFP_KERNEL);
if (!*symbol)
return -ENOMEM;
if (*is_return)
return 0;
/* If there is $retval, this should be a return fprobe. */
for (i = 2; i < argc; i++) {
tmp = strstr(argv[i], "$retval");
if (tmp && !isalnum(tmp[7]) && tmp[7] != '_') {
*is_return = true;
/*
* NOTE: Don't check is_tracepoint here, because it will
* be checked when the argument is parsed.
*/
break;
}
}
return 0;
}
static int __trace_fprobe_create(int argc, const char *argv[]) static int __trace_fprobe_create(int argc, const char *argv[])
{ {
/* /*
...@@ -927,7 +967,7 @@ static int __trace_fprobe_create(int argc, const char *argv[]) ...@@ -927,7 +967,7 @@ static int __trace_fprobe_create(int argc, const char *argv[])
struct trace_fprobe *tf = NULL; struct trace_fprobe *tf = NULL;
int i, len, new_argc = 0, ret = 0; int i, len, new_argc = 0, ret = 0;
bool is_return = false; bool is_return = false;
char *symbol = NULL, *tmp = NULL; char *symbol = NULL;
const char *event = NULL, *group = FPROBE_EVENT_SYSTEM; const char *event = NULL, *group = FPROBE_EVENT_SYSTEM;
const char **new_argv = NULL; const char **new_argv = NULL;
int maxactive = 0; int maxactive = 0;
...@@ -983,20 +1023,10 @@ static int __trace_fprobe_create(int argc, const char *argv[]) ...@@ -983,20 +1023,10 @@ static int __trace_fprobe_create(int argc, const char *argv[])
trace_probe_log_set_index(1); trace_probe_log_set_index(1);
/* a symbol(or tracepoint) must be specified */ /* a symbol(or tracepoint) must be specified */
symbol = kstrdup(argv[1], GFP_KERNEL); ret = parse_symbol_and_return(argc, argv, &symbol, &is_return, is_tracepoint);
if (!symbol) if (ret < 0)
return -ENOMEM; goto parse_error;
tmp = strchr(symbol, '%');
if (tmp) {
if (!is_tracepoint && !strcmp(tmp, "%return")) {
*tmp = '\0';
is_return = true;
} else {
trace_probe_log_err(tmp - symbol, BAD_ADDR_SUFFIX);
goto parse_error;
}
}
if (!is_return && maxactive) { if (!is_return && maxactive) {
trace_probe_log_set_index(0); trace_probe_log_set_index(0);
trace_probe_log_err(1, BAD_MAXACT_TYPE); trace_probe_log_err(1, BAD_MAXACT_TYPE);
...@@ -1096,6 +1126,7 @@ static int __trace_fprobe_create(int argc, const char *argv[]) ...@@ -1096,6 +1126,7 @@ static int __trace_fprobe_create(int argc, const char *argv[])
} }
out: out:
traceprobe_finish_parse(&ctx);
trace_probe_log_clear(); trace_probe_log_clear();
kfree(new_argv); kfree(new_argv);
kfree(symbol); kfree(symbol);
......
...@@ -907,6 +907,7 @@ static int __trace_kprobe_create(int argc, const char *argv[]) ...@@ -907,6 +907,7 @@ static int __trace_kprobe_create(int argc, const char *argv[])
} }
out: out:
traceprobe_finish_parse(&ctx);
trace_probe_log_clear(); trace_probe_log_clear();
kfree(new_argv); kfree(new_argv);
kfree(symbol); kfree(symbol);
......
This diff is collapsed.
...@@ -383,9 +383,15 @@ static inline bool tparg_is_function_entry(unsigned int flags) ...@@ -383,9 +383,15 @@ static inline bool tparg_is_function_entry(unsigned int flags)
struct traceprobe_parse_context { struct traceprobe_parse_context {
struct trace_event_call *event; struct trace_event_call *event;
const struct btf_param *params; /* BTF related parameters */
s32 nr_params; const char *funcname; /* Function name in BTF */
const char *funcname; const struct btf_type *proto; /* Prototype of the function */
const struct btf_param *params; /* Parameter of the function */
s32 nr_params; /* The number of the parameters */
struct btf *btf; /* The BTF to be used */
const struct btf_type *last_type; /* Saved type */
u32 last_bitoffs; /* Saved bitoffs */
u32 last_bitsize; /* Saved bitsize */
unsigned int flags; unsigned int flags;
int offset; int offset;
}; };
...@@ -400,6 +406,12 @@ const char **traceprobe_expand_meta_args(int argc, const char *argv[], ...@@ -400,6 +406,12 @@ const char **traceprobe_expand_meta_args(int argc, const char *argv[],
extern int traceprobe_update_arg(struct probe_arg *arg); extern int traceprobe_update_arg(struct probe_arg *arg);
extern void traceprobe_free_probe_arg(struct probe_arg *arg); extern void traceprobe_free_probe_arg(struct probe_arg *arg);
/*
* If either traceprobe_parse_probe_arg() or traceprobe_expand_meta_args() is called,
* this MUST be called for clean up the context and return a resource.
*/
void traceprobe_finish_parse(struct traceprobe_parse_context *ctx);
extern int traceprobe_split_symbol_offset(char *symbol, long *offset); extern int traceprobe_split_symbol_offset(char *symbol, long *offset);
int traceprobe_parse_event_name(const char **pevent, const char **pgroup, int traceprobe_parse_event_name(const char **pevent, const char **pgroup,
char *buf, int offset); char *buf, int offset);
...@@ -495,7 +507,14 @@ extern int traceprobe_define_arg_fields(struct trace_event_call *event_call, ...@@ -495,7 +507,14 @@ extern int traceprobe_define_arg_fields(struct trace_event_call *event_call,
C(BAD_VAR_ARGS, "$arg* must be an independent parameter without name etc."),\ 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(NOFENTRY_ARGS, "$arg* can be used only on function entry"), \
C(DOUBLE_ARGS, "$arg* can be used only once in the parameters"), \ C(DOUBLE_ARGS, "$arg* can be used only once in the parameters"), \
C(ARGS_2LONG, "$arg* failed because the argument list is too long"), C(ARGS_2LONG, "$arg* failed because the argument list is too long"), \
C(ARGIDX_2BIG, "$argN index is too big"), \
C(NO_PTR_STRCT, "This is not a pointer to union/structure."), \
C(NOSUP_DAT_ARG, "Non pointer structure/union argument is not supported."),\
C(BAD_HYPHEN, "Failed to parse single hyphen. Forgot '>'?"), \
C(NO_BTF_FIELD, "This field is not found."), \
C(BAD_BTF_TID, "Failed to get BTF type info."),\
C(BAD_TYPE4STR, "This type does not fit for string."),
#undef C #undef C
#define C(a, b) TP_ERR_##a #define C(a, b) TP_ERR_##a
......
...@@ -688,6 +688,7 @@ static int __trace_uprobe_create(int argc, const char **argv) ...@@ -688,6 +688,7 @@ static int __trace_uprobe_create(int argc, const char **argv)
trace_probe_log_set_index(i + 2); trace_probe_log_set_index(i + 2);
ret = traceprobe_parse_probe_arg(&tu->tp, i, argv[i], &ctx); ret = traceprobe_parse_probe_arg(&tu->tp, i, argv[i], &ctx);
traceprobe_finish_parse(&ctx);
if (ret) if (ret)
goto error; goto error;
} }
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
KPROBES= KPROBES=
FPROBES= FPROBES=
FIELDS=
if grep -qF "p[:[<group>/][<event>]] <place> [<args>]" README ; then if grep -qF "p[:[<group>/][<event>]] <place> [<args>]" README ; then
KPROBES=yes KPROBES=yes
...@@ -12,6 +13,9 @@ fi ...@@ -12,6 +13,9 @@ fi
if grep -qF "f[:[<group>/][<event>]] <func-name>[%return] [<args>]" README ; then if grep -qF "f[:[<group>/][<event>]] <func-name>[%return] [<args>]" README ; then
FPROBES=yes FPROBES=yes
fi fi
if grep -qF "<argname>[->field[->field|.field...]]" README ; then
FIELDS=yes
fi
if [ -z "$KPROBES" -a -z "$FPROBES" ] ; then if [ -z "$KPROBES" -a -z "$FPROBES" ] ; then
exit_unsupported exit_unsupported
...@@ -21,6 +25,9 @@ echo 0 > events/enable ...@@ -21,6 +25,9 @@ echo 0 > events/enable
echo > dynamic_events echo > dynamic_events
TP=kfree TP=kfree
TP2=kmem_cache_alloc
TP3=getname_flags
TP4=sched_wakeup
if [ "$FPROBES" ] ; then if [ "$FPROBES" ] ; then
echo "f:fpevent $TP object" >> dynamic_events echo "f:fpevent $TP object" >> dynamic_events
...@@ -33,6 +40,7 @@ echo > dynamic_events ...@@ -33,6 +40,7 @@ echo > dynamic_events
echo "f:fpevent $TP "'$arg1' >> dynamic_events echo "f:fpevent $TP "'$arg1' >> dynamic_events
grep -q "fpevent.*object=object" dynamic_events grep -q "fpevent.*object=object" dynamic_events
echo > dynamic_events echo > dynamic_events
echo "f:fpevent $TP "'$arg*' >> dynamic_events echo "f:fpevent $TP "'$arg*' >> dynamic_events
...@@ -45,6 +53,18 @@ fi ...@@ -45,6 +53,18 @@ fi
echo > dynamic_events echo > dynamic_events
if [ "$FIELDS" ] ; then
echo "t:tpevent ${TP2} obj_size=s->object_size" >> dynamic_events
echo "f:fpevent ${TP3}%return path=\$retval->name:string" >> dynamic_events
echo "t:tpevent2 ${TP4} p->se.group_node.next->prev" >> dynamic_events
grep -q "tpevent .*obj_size=s->object_size" dynamic_events
grep -q "fpevent.*path=\$retval->name:string" dynamic_events
grep -q 'tpevent2 .*p->se.group_node.next->prev' dynamic_events
echo > dynamic_events
fi
if [ "$KPROBES" ] ; then if [ "$KPROBES" ] ; then
echo "p:kpevent $TP object" >> dynamic_events echo "p:kpevent $TP object" >> dynamic_events
grep -q "kpevent.*object=object" dynamic_events grep -q "kpevent.*object=object" dynamic_events
......
...@@ -30,11 +30,11 @@ check_error 'f:^ vfs_read' # NO_EVENT_NAME ...@@ -30,11 +30,11 @@ check_error 'f:^ vfs_read' # NO_EVENT_NAME
check_error 'f:foo/^12345678901234567890123456789012345678901234567890123456789012345 vfs_read' # EVENT_TOO_LONG check_error 'f:foo/^12345678901234567890123456789012345678901234567890123456789012345 vfs_read' # EVENT_TOO_LONG
check_error 'f:foo/^bar.1 vfs_read' # BAD_EVENT_NAME 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 ^$stack10000' # BAD_STACK_NUM
check_error 'f vfs_read ^$arg10000' # BAD_ARG_NUM check_error 'f vfs_read ^$arg10000' # BAD_ARG_NUM
check_error 'f vfs_read $retval ^$arg1' # BAD_VAR
check_error 'f vfs_read ^$none_var' # BAD_VAR check_error 'f vfs_read ^$none_var' # BAD_VAR
check_error 'f vfs_read ^'$REG # BAD_VAR check_error 'f vfs_read ^'$REG # BAD_VAR
...@@ -103,6 +103,14 @@ check_error 'f vfs_read%return ^$arg*' # NOFENTRY_ARGS ...@@ -103,6 +103,14 @@ check_error 'f vfs_read%return ^$arg*' # NOFENTRY_ARGS
check_error 'f vfs_read ^hoge' # NO_BTFARG check_error 'f vfs_read ^hoge' # NO_BTFARG
check_error 'f kfree ^$arg10' # NO_BTFARG (exceed the number of parameters) check_error 'f kfree ^$arg10' # NO_BTFARG (exceed the number of parameters)
check_error 'f kfree%return ^$retval' # NO_RETVAL check_error 'f kfree%return ^$retval' # NO_RETVAL
if grep -qF "<argname>[->field[->field|.field...]]" README ; then
check_error 'f vfs_read%return $retval->^foo' # NO_PTR_STRCT
check_error 'f vfs_read file->^foo' # NO_BTF_FIELD
check_error 'f vfs_read file^-.foo' # BAD_HYPHEN
check_error 'f vfs_read ^file:string' # BAD_TYPE4STR
fi
else else
check_error 'f vfs_read ^$arg*' # NOSUP_BTFARG check_error 'f vfs_read ^$arg*' # NOSUP_BTFARG
check_error 't kfree ^$arg*' # NOSUP_BTFARG check_error 't kfree ^$arg*' # NOSUP_BTFARG
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment