mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git
synced 2024-12-29 17:22:07 +00:00
Probes updates for v6.6:
- 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. -----BEGIN PGP SIGNATURE----- iQFPBAABCgA5FiEEh7BulGwFlgAOi5DV2/sHvwUrPxsFAmTycQkbHG1hc2FtaS5o aXJhbWF0c3VAZ21haWwuY29tAAoJENv7B78FKz8bqS8H/jeR1JhOzIXOvTw7XCFm MrSY/SKi8tQfV6lau2UmoYdbYvYjpqL34XLOQPNf2/lrcL2M9aNYXk9fbhlW8enx vkMyKQ0E5anixkF4vsTbEl9DaprxbpsPVACmZ/7VjQk2JuXIdyaNk8hno9LgIcEq udztb0o2HmDFqAXfRi0LvlSTAIwvXZ+usmEvYpaq1g2WwrCe7NHEYl42vMpj+h4H 9l4t5rA9JyPPX4yQUjtKGW5eRVTwDTm/Gn6DRzYfYzkkiBZv27qfovzBOt672LgG hyot+u7XeKvZx3jjnF7+mRWoH/m0dqyhyi/nPhpIE09VhgwclrbGAcDuR1x6sp01 PHY= =hBDN -----END PGP SIGNATURE----- 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()
This commit is contained in:
commit
b70100f2e6
@ -79,9 +79,9 @@ automatically set by the given name. ::
|
||||
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 ::
|
||||
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
|
||||
@ -105,9 +105,47 @@ is expanded to all function arguments of the function or the tracepoint. ::
|
||||
# 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.
|
||||
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.
|
||||
|
||||
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
|
||||
--------------
|
||||
@ -161,10 +199,10 @@ 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.
|
||||
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
|
||||
# TASK-PID CPU# ||||| TIMESTAMP FUNCTION
|
||||
# | | | ||||| | |
|
||||
@ -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
|
||||
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``.
|
||||
|
@ -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);
|
||||
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 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,
|
||||
u32 id, u32 *res_id);
|
||||
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)
|
||||
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;
|
||||
s32 ret;
|
||||
|
@ -2232,8 +2232,7 @@ int register_kretprobe(struct kretprobe *rp)
|
||||
return -ENOMEM;
|
||||
|
||||
for (i = 0; i < rp->maxactive; i++) {
|
||||
inst = kzalloc(sizeof(struct kretprobe_instance) +
|
||||
rp->data_size, GFP_KERNEL);
|
||||
inst = kzalloc(struct_size(inst, data, rp->data_size), GFP_KERNEL);
|
||||
if (inst == NULL) {
|
||||
rethook_free(rp->rh);
|
||||
rp->rh = NULL;
|
||||
@ -2256,8 +2255,7 @@ int register_kretprobe(struct kretprobe *rp)
|
||||
|
||||
rp->rph->rp = rp;
|
||||
for (i = 0; i < rp->maxactive; i++) {
|
||||
inst = kzalloc(sizeof(struct kretprobe_instance) +
|
||||
rp->data_size, GFP_KERNEL);
|
||||
inst = kzalloc(struct_size(inst, data, rp->data_size), GFP_KERNEL);
|
||||
if (inst == NULL) {
|
||||
refcount_set(&rp->rph->ref, i);
|
||||
free_rp_inst(rp);
|
||||
|
@ -99,6 +99,7 @@ obj-$(CONFIG_KGDB_KDB) += trace_kdb.o
|
||||
endif
|
||||
obj-$(CONFIG_DYNAMIC_EVENTS) += trace_dynevent.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_BOOTTIME_TRACING) += trace_boot.o
|
||||
obj-$(CONFIG_FTRACE_RECORD_RECURSION) += trace_recursion_record.o
|
||||
|
@ -5711,7 +5711,8 @@ static const char readme_msg[] =
|
||||
"\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"
|
||||
"\t $stack<index>, $stack, $retval, $comm, $arg<N>,\n"
|
||||
"\t <argname>[->field[->field|.field...]],\n"
|
||||
#else
|
||||
"\t $stack<index>, $stack, $retval, $comm, $arg<N>,\n"
|
||||
#endif
|
||||
|
122
kernel/trace/trace_btf.c
Normal file
122
kernel/trace/trace_btf.c
Normal file
@ -0,0 +1,122 @@
|
||||
// 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;
|
||||
}
|
||||
|
11
kernel/trace/trace_btf.h
Normal file
11
kernel/trace/trace_btf.h
Normal file
@ -0,0 +1,11 @@
|
||||
/* 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 {
|
||||
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 void trace_event_probe_cleanup(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,
|
||||
struct trace_event_file *file)
|
||||
{
|
||||
struct trace_probe *pos, *tp;
|
||||
struct trace_probe *tp;
|
||||
struct trace_eprobe *ep;
|
||||
bool enabled;
|
||||
int ret = 0;
|
||||
@ -662,8 +666,7 @@ static int enable_trace_eprobe(struct trace_event_call *call,
|
||||
if (enabled)
|
||||
return 0;
|
||||
|
||||
list_for_each_entry(pos, trace_probe_probe_list(tp), list) {
|
||||
ep = container_of(pos, struct trace_eprobe, tp);
|
||||
for_each_trace_eprobe_tp(ep, tp) {
|
||||
ret = enable_eprobe(ep, file);
|
||||
if (ret)
|
||||
break;
|
||||
@ -680,8 +683,7 @@ static int enable_trace_eprobe(struct trace_event_call *call,
|
||||
*/
|
||||
WARN_ON_ONCE(ret != -ENOMEM);
|
||||
|
||||
list_for_each_entry(pos, trace_probe_probe_list(tp), list) {
|
||||
ep = container_of(pos, struct trace_eprobe, tp);
|
||||
for_each_trace_eprobe_tp(ep, tp) {
|
||||
disable_eprobe(ep, file->tr);
|
||||
if (!--cnt)
|
||||
break;
|
||||
@ -699,7 +701,7 @@ static int enable_trace_eprobe(struct trace_event_call *call,
|
||||
static int disable_trace_eprobe(struct trace_event_call *call,
|
||||
struct trace_event_file *file)
|
||||
{
|
||||
struct trace_probe *pos, *tp;
|
||||
struct trace_probe *tp;
|
||||
struct trace_eprobe *ep;
|
||||
|
||||
tp = trace_probe_primary_from_call(call);
|
||||
@ -716,10 +718,8 @@ static int disable_trace_eprobe(struct trace_event_call *call,
|
||||
trace_probe_clear_flag(tp, TP_FLAG_PROFILE);
|
||||
|
||||
if (!trace_probe_is_enabled(tp)) {
|
||||
list_for_each_entry(pos, trace_probe_probe_list(tp), list) {
|
||||
ep = container_of(pos, struct trace_eprobe, tp);
|
||||
for_each_trace_eprobe_tp(ep, tp)
|
||||
disable_eprobe(ep, file->tr);
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
@ -807,13 +807,11 @@ static int trace_eprobe_tp_update_arg(struct trace_eprobe *ep, const char *argv[
|
||||
int ret;
|
||||
|
||||
ret = traceprobe_parse_probe_arg(&ep->tp, i, argv[i], &ctx);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Handle symbols "@" */
|
||||
if (!ret)
|
||||
ret = traceprobe_update_arg(&ep->tp.args[i]);
|
||||
|
||||
traceprobe_finish_parse(&ctx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -898,6 +898,46 @@ static struct tracepoint *find_tracepoint(const char *tp_name)
|
||||
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[])
|
||||
{
|
||||
/*
|
||||
@ -927,7 +967,7 @@ static int __trace_fprobe_create(int argc, const char *argv[])
|
||||
struct trace_fprobe *tf = NULL;
|
||||
int i, len, new_argc = 0, ret = 0;
|
||||
bool is_return = false;
|
||||
char *symbol = NULL, *tmp = NULL;
|
||||
char *symbol = NULL;
|
||||
const char *event = NULL, *group = FPROBE_EVENT_SYSTEM;
|
||||
const char **new_argv = NULL;
|
||||
int maxactive = 0;
|
||||
@ -983,20 +1023,10 @@ static int __trace_fprobe_create(int argc, const char *argv[])
|
||||
trace_probe_log_set_index(1);
|
||||
|
||||
/* a symbol(or tracepoint) must be specified */
|
||||
symbol = kstrdup(argv[1], GFP_KERNEL);
|
||||
if (!symbol)
|
||||
return -ENOMEM;
|
||||
ret = parse_symbol_and_return(argc, argv, &symbol, &is_return, is_tracepoint);
|
||||
if (ret < 0)
|
||||
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) {
|
||||
trace_probe_log_set_index(0);
|
||||
trace_probe_log_err(1, BAD_MAXACT_TYPE);
|
||||
@ -1096,6 +1126,7 @@ static int __trace_fprobe_create(int argc, const char *argv[])
|
||||
}
|
||||
|
||||
out:
|
||||
traceprobe_finish_parse(&ctx);
|
||||
trace_probe_log_clear();
|
||||
kfree(new_argv);
|
||||
kfree(symbol);
|
||||
|
@ -907,6 +907,7 @@ static int __trace_kprobe_create(int argc, const char *argv[])
|
||||
}
|
||||
|
||||
out:
|
||||
traceprobe_finish_parse(&ctx);
|
||||
trace_probe_log_clear();
|
||||
kfree(new_argv);
|
||||
kfree(symbol);
|
||||
|
@ -12,6 +12,7 @@
|
||||
#define pr_fmt(fmt) "trace_probe: " fmt
|
||||
|
||||
#include <linux/bpf.h>
|
||||
#include "trace_btf.h"
|
||||
|
||||
#include "trace_probe.h"
|
||||
|
||||
@ -304,31 +305,90 @@ static int parse_trace_event_arg(char *arg, struct fetch_insn *code,
|
||||
|
||||
#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)
|
||||
static bool btf_type_is_char_ptr(struct btf *btf, const struct btf_type *type)
|
||||
{
|
||||
const struct btf_type *t;
|
||||
const struct btf_type *real_type;
|
||||
u32 intdata;
|
||||
s32 tid;
|
||||
|
||||
/* TODO: const char * could be converted as a string */
|
||||
t = btf_type_skip_modifiers(btf, id, &tid);
|
||||
real_type = btf_type_skip_modifiers(btf, type->type, &tid);
|
||||
if (!real_type)
|
||||
return false;
|
||||
|
||||
switch (BTF_INFO_KIND(t->info)) {
|
||||
if (BTF_INFO_KIND(real_type->info) != BTF_KIND_INT)
|
||||
return false;
|
||||
|
||||
intdata = btf_type_int(real_type);
|
||||
return !(BTF_INT_ENCODING(intdata) & BTF_INT_SIGNED)
|
||||
&& BTF_INT_BITS(intdata) == 8;
|
||||
}
|
||||
|
||||
static bool btf_type_is_char_array(struct btf *btf, const struct btf_type *type)
|
||||
{
|
||||
const struct btf_type *real_type;
|
||||
const struct btf_array *array;
|
||||
u32 intdata;
|
||||
s32 tid;
|
||||
|
||||
if (BTF_INFO_KIND(type->info) != BTF_KIND_ARRAY)
|
||||
return false;
|
||||
|
||||
array = (const struct btf_array *)(type + 1);
|
||||
|
||||
real_type = btf_type_skip_modifiers(btf, array->type, &tid);
|
||||
|
||||
intdata = btf_type_int(real_type);
|
||||
return !(BTF_INT_ENCODING(intdata) & BTF_INT_SIGNED)
|
||||
&& BTF_INT_BITS(intdata) == 8;
|
||||
}
|
||||
|
||||
static int check_prepare_btf_string_fetch(char *typename,
|
||||
struct fetch_insn **pcode,
|
||||
struct traceprobe_parse_context *ctx)
|
||||
{
|
||||
struct btf *btf = ctx->btf;
|
||||
|
||||
if (!btf || !ctx->last_type)
|
||||
return 0;
|
||||
|
||||
/* char [] does not need any change. */
|
||||
if (btf_type_is_char_array(btf, ctx->last_type))
|
||||
return 0;
|
||||
|
||||
/* char * requires dereference the pointer. */
|
||||
if (btf_type_is_char_ptr(btf, ctx->last_type)) {
|
||||
struct fetch_insn *code = *pcode + 1;
|
||||
|
||||
if (code->op == FETCH_OP_END) {
|
||||
trace_probe_log_err(ctx->offset, TOO_MANY_OPS);
|
||||
return -E2BIG;
|
||||
}
|
||||
if (typename[0] == 'u')
|
||||
code->op = FETCH_OP_UDEREF;
|
||||
else
|
||||
code->op = FETCH_OP_DEREF;
|
||||
code->offset = 0;
|
||||
*pcode = code;
|
||||
return 0;
|
||||
}
|
||||
/* Other types are not available for string */
|
||||
trace_probe_log_err(ctx->offset, BAD_TYPE4STR);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static const char *fetch_type_from_btf_type(struct btf *btf,
|
||||
const struct btf_type *type,
|
||||
struct traceprobe_parse_context *ctx)
|
||||
{
|
||||
u32 intdata;
|
||||
|
||||
/* TODO: const char * could be converted as a string */
|
||||
switch (BTF_INFO_KIND(type->info)) {
|
||||
case BTF_KIND_ENUM:
|
||||
/* enum is "int", so convert to "s32" */
|
||||
return "s32";
|
||||
@ -341,7 +401,7 @@ static const char *type_from_btf_id(struct btf *btf, s32 id)
|
||||
else
|
||||
return "x32";
|
||||
case BTF_KIND_INT:
|
||||
intdata = btf_type_int(t);
|
||||
intdata = btf_type_int(type);
|
||||
if (BTF_INT_ENCODING(intdata) & BTF_INT_SIGNED) {
|
||||
switch (BTF_INT_BITS(intdata)) {
|
||||
case 8:
|
||||
@ -364,6 +424,10 @@ static const char *type_from_btf_id(struct btf *btf, s32 id)
|
||||
case 64:
|
||||
return "u64";
|
||||
}
|
||||
/* bitfield, size is encoded in the type */
|
||||
ctx->last_bitsize = BTF_INT_BITS(intdata);
|
||||
ctx->last_bitoffs += BTF_INT_OFFSET(intdata);
|
||||
return "u64";
|
||||
}
|
||||
}
|
||||
/* TODO: support other types */
|
||||
@ -371,88 +435,223 @@ static const char *type_from_btf_id(struct btf *btf, s32 id)
|
||||
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 (!t || !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 (!t || !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)
|
||||
static int query_btf_context(struct traceprobe_parse_context *ctx)
|
||||
{
|
||||
const struct btf_param *param;
|
||||
const struct btf_type *t;
|
||||
const struct btf_type *type;
|
||||
struct btf *btf;
|
||||
s32 nr;
|
||||
|
||||
if (!funcname || !nr)
|
||||
return ERR_PTR(-EINVAL);
|
||||
if (ctx->btf)
|
||||
return 0;
|
||||
|
||||
t = find_btf_func_proto(funcname);
|
||||
if (IS_ERR(t))
|
||||
return (const struct btf_param *)t;
|
||||
if (!ctx->funcname)
|
||||
return -EINVAL;
|
||||
|
||||
*nr = btf_type_vlen(t);
|
||||
param = (const struct btf_param *)(t + 1);
|
||||
type = btf_find_func_proto(ctx->funcname, &btf);
|
||||
if (!type)
|
||||
return -ENOENT;
|
||||
|
||||
/* Hide the first 'data' argument of tracepoint */
|
||||
if (tracepoint) {
|
||||
(*nr)--;
|
||||
param++;
|
||||
ctx->btf = btf;
|
||||
ctx->proto = type;
|
||||
|
||||
/* ctx->params is optional, since func(void) will not have params. */
|
||||
nr = 0;
|
||||
param = btf_get_func_param(type, &nr);
|
||||
if (!IS_ERR_OR_NULL(param)) {
|
||||
/* Hide the first 'data' argument of tracepoint */
|
||||
if (ctx->flags & TPARG_FL_TPOINT) {
|
||||
nr--;
|
||||
param++;
|
||||
}
|
||||
}
|
||||
|
||||
if (*nr > 0)
|
||||
return param;
|
||||
else
|
||||
return NULL;
|
||||
if (nr > 0) {
|
||||
ctx->nr_params = nr;
|
||||
ctx->params = param;
|
||||
} else {
|
||||
ctx->nr_params = 0;
|
||||
ctx->params = NULL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_btf_arg(const char *varname, struct fetch_insn *code,
|
||||
static void clear_btf_context(struct traceprobe_parse_context *ctx)
|
||||
{
|
||||
if (ctx->btf) {
|
||||
btf_put(ctx->btf);
|
||||
ctx->btf = NULL;
|
||||
ctx->proto = NULL;
|
||||
ctx->params = NULL;
|
||||
ctx->nr_params = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Return 1 if the field separater is arrow operator ('->') */
|
||||
static int split_next_field(char *varname, char **next_field,
|
||||
struct traceprobe_parse_context *ctx)
|
||||
{
|
||||
char *field;
|
||||
int ret = 0;
|
||||
|
||||
field = strpbrk(varname, ".-");
|
||||
if (field) {
|
||||
if (field[0] == '-' && field[1] == '>') {
|
||||
field[0] = '\0';
|
||||
field += 2;
|
||||
ret = 1;
|
||||
} else if (field[0] == '.') {
|
||||
field[0] = '\0';
|
||||
field += 1;
|
||||
} else {
|
||||
trace_probe_log_err(ctx->offset + field - varname, BAD_HYPHEN);
|
||||
return -EINVAL;
|
||||
}
|
||||
*next_field = field;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse the field of data structure. The @type must be a pointer type
|
||||
* pointing the target data structure type.
|
||||
*/
|
||||
static int parse_btf_field(char *fieldname, const struct btf_type *type,
|
||||
struct fetch_insn **pcode, struct fetch_insn *end,
|
||||
struct traceprobe_parse_context *ctx)
|
||||
{
|
||||
struct fetch_insn *code = *pcode;
|
||||
const struct btf_member *field;
|
||||
u32 bitoffs, anon_offs;
|
||||
char *next;
|
||||
int is_ptr;
|
||||
s32 tid;
|
||||
|
||||
do {
|
||||
/* Outer loop for solving arrow operator ('->') */
|
||||
if (BTF_INFO_KIND(type->info) != BTF_KIND_PTR) {
|
||||
trace_probe_log_err(ctx->offset, NO_PTR_STRCT);
|
||||
return -EINVAL;
|
||||
}
|
||||
/* Convert a struct pointer type to a struct type */
|
||||
type = btf_type_skip_modifiers(ctx->btf, type->type, &tid);
|
||||
if (!type) {
|
||||
trace_probe_log_err(ctx->offset, BAD_BTF_TID);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
bitoffs = 0;
|
||||
do {
|
||||
/* Inner loop for solving dot operator ('.') */
|
||||
next = NULL;
|
||||
is_ptr = split_next_field(fieldname, &next, ctx);
|
||||
if (is_ptr < 0)
|
||||
return is_ptr;
|
||||
|
||||
anon_offs = 0;
|
||||
field = btf_find_struct_member(ctx->btf, type, fieldname,
|
||||
&anon_offs);
|
||||
if (!field) {
|
||||
trace_probe_log_err(ctx->offset, NO_BTF_FIELD);
|
||||
return -ENOENT;
|
||||
}
|
||||
/* Add anonymous structure/union offset */
|
||||
bitoffs += anon_offs;
|
||||
|
||||
/* Accumulate the bit-offsets of the dot-connected fields */
|
||||
if (btf_type_kflag(type)) {
|
||||
bitoffs += BTF_MEMBER_BIT_OFFSET(field->offset);
|
||||
ctx->last_bitsize = BTF_MEMBER_BITFIELD_SIZE(field->offset);
|
||||
} else {
|
||||
bitoffs += field->offset;
|
||||
ctx->last_bitsize = 0;
|
||||
}
|
||||
|
||||
type = btf_type_skip_modifiers(ctx->btf, field->type, &tid);
|
||||
if (!type) {
|
||||
trace_probe_log_err(ctx->offset, BAD_BTF_TID);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ctx->offset += next - fieldname;
|
||||
fieldname = next;
|
||||
} while (!is_ptr && fieldname);
|
||||
|
||||
if (++code == end) {
|
||||
trace_probe_log_err(ctx->offset, TOO_MANY_OPS);
|
||||
return -EINVAL;
|
||||
}
|
||||
code->op = FETCH_OP_DEREF; /* TODO: user deref support */
|
||||
code->offset = bitoffs / 8;
|
||||
*pcode = code;
|
||||
|
||||
ctx->last_bitoffs = bitoffs % 8;
|
||||
ctx->last_type = type;
|
||||
} while (fieldname);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_btf_arg(char *varname,
|
||||
struct fetch_insn **pcode, struct fetch_insn *end,
|
||||
struct traceprobe_parse_context *ctx)
|
||||
{
|
||||
struct btf *btf = traceprobe_get_btf();
|
||||
struct fetch_insn *code = *pcode;
|
||||
const struct btf_param *params;
|
||||
int i;
|
||||
|
||||
if (!btf) {
|
||||
trace_probe_log_err(ctx->offset, NOSUP_BTFARG);
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
const struct btf_type *type;
|
||||
char *field = NULL;
|
||||
int i, is_ptr, ret;
|
||||
u32 tid;
|
||||
|
||||
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_OR_NULL(params)) {
|
||||
is_ptr = split_next_field(varname, &field, ctx);
|
||||
if (is_ptr < 0)
|
||||
return is_ptr;
|
||||
if (!is_ptr && field) {
|
||||
/* dot-connected field on an argument is not supported. */
|
||||
trace_probe_log_err(ctx->offset + field - varname,
|
||||
NOSUP_DAT_ARG);
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
if (ctx->flags & TPARG_FL_RETURN) {
|
||||
if (strcmp(varname, "$retval") != 0) {
|
||||
trace_probe_log_err(ctx->offset, NO_BTFARG);
|
||||
return -ENOENT;
|
||||
}
|
||||
code->op = FETCH_OP_RETVAL;
|
||||
/* Check whether the function return type is not void */
|
||||
if (query_btf_context(ctx) == 0) {
|
||||
if (ctx->proto->type == 0) {
|
||||
trace_probe_log_err(ctx->offset, NO_RETVAL);
|
||||
return -ENOENT;
|
||||
}
|
||||
tid = ctx->proto->type;
|
||||
goto found;
|
||||
}
|
||||
if (field) {
|
||||
trace_probe_log_err(ctx->offset + field - varname,
|
||||
NO_BTF_ENTRY);
|
||||
return -ENOENT;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!ctx->btf) {
|
||||
ret = query_btf_context(ctx);
|
||||
if (ret < 0 || ctx->nr_params == 0) {
|
||||
trace_probe_log_err(ctx->offset, NO_BTF_ENTRY);
|
||||
return PTR_ERR(params);
|
||||
}
|
||||
ctx->params = params;
|
||||
} else
|
||||
params = ctx->params;
|
||||
}
|
||||
params = ctx->params;
|
||||
|
||||
for (i = 0; i < ctx->nr_params; i++) {
|
||||
const char *name = btf_name_by_offset(btf, params[i].name_off);
|
||||
const char *name = btf_name_by_offset(ctx->btf, params[i].name_off);
|
||||
|
||||
if (name && !strcmp(name, varname)) {
|
||||
code->op = FETCH_OP_ARG;
|
||||
@ -460,91 +659,114 @@ static int parse_btf_arg(const char *varname, struct fetch_insn *code,
|
||||
code->param = i + 1;
|
||||
else
|
||||
code->param = i;
|
||||
return 0;
|
||||
tid = params[i].type;
|
||||
goto found;
|
||||
}
|
||||
}
|
||||
trace_probe_log_err(ctx->offset, NO_BTFARG);
|
||||
return -ENOENT;
|
||||
|
||||
found:
|
||||
type = btf_type_skip_modifiers(ctx->btf, tid, &tid);
|
||||
if (!type) {
|
||||
trace_probe_log_err(ctx->offset, BAD_BTF_TID);
|
||||
return -EINVAL;
|
||||
}
|
||||
/* Initialize the last type information */
|
||||
ctx->last_type = type;
|
||||
ctx->last_bitoffs = 0;
|
||||
ctx->last_bitsize = 0;
|
||||
if (field) {
|
||||
ctx->offset += field - varname;
|
||||
return parse_btf_field(field, type, pcode, end, ctx);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct fetch_type *parse_btf_arg_type(int arg_idx,
|
||||
static const struct fetch_type *find_fetch_type_from_btf_type(
|
||||
struct traceprobe_parse_context *ctx)
|
||||
{
|
||||
struct btf *btf = traceprobe_get_btf();
|
||||
struct btf *btf = ctx->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);
|
||||
}
|
||||
if (btf && ctx->last_type)
|
||||
typestr = fetch_type_from_btf_type(btf, ctx->last_type, ctx);
|
||||
|
||||
return find_fetch_type(typestr, ctx->flags);
|
||||
}
|
||||
|
||||
static const struct fetch_type *parse_btf_retval_type(
|
||||
struct traceprobe_parse_context *ctx)
|
||||
static int parse_btf_bitfield(struct fetch_insn **pcode,
|
||||
struct traceprobe_parse_context *ctx)
|
||||
{
|
||||
struct btf *btf = traceprobe_get_btf();
|
||||
const char *typestr = NULL;
|
||||
const struct btf_type *t;
|
||||
struct fetch_insn *code = *pcode;
|
||||
|
||||
if (btf && ctx->funcname) {
|
||||
t = find_btf_func_proto(ctx->funcname);
|
||||
if (!IS_ERR(t))
|
||||
typestr = type_from_btf_id(btf, t->type);
|
||||
if ((ctx->last_bitsize % 8 == 0) && ctx->last_bitoffs == 0)
|
||||
return 0;
|
||||
|
||||
code++;
|
||||
if (code->op != FETCH_OP_NOP) {
|
||||
trace_probe_log_err(ctx->offset, TOO_MANY_OPS);
|
||||
return -EINVAL;
|
||||
}
|
||||
*pcode = code;
|
||||
|
||||
return find_fetch_type(typestr, ctx->flags);
|
||||
code->op = FETCH_OP_MOD_BF;
|
||||
code->lshift = 64 - (ctx->last_bitsize + ctx->last_bitoffs);
|
||||
code->rshift = 64 - ctx->last_bitsize;
|
||||
code->basesize = 64 / 8;
|
||||
return 0;
|
||||
}
|
||||
|
||||
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)
|
||||
static void clear_btf_context(struct traceprobe_parse_context *ctx)
|
||||
{
|
||||
return NULL;
|
||||
ctx->btf = NULL;
|
||||
}
|
||||
|
||||
static const struct btf_param *find_btf_func_param(const char *funcname, s32 *nr,
|
||||
bool tracepoint)
|
||||
static int query_btf_context(struct traceprobe_parse_context *ctx)
|
||||
{
|
||||
return ERR_PTR(-EOPNOTSUPP);
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
static int parse_btf_arg(const char *varname, struct fetch_insn *code,
|
||||
static int parse_btf_arg(char *varname,
|
||||
struct fetch_insn **pcode, struct fetch_insn *end,
|
||||
struct traceprobe_parse_context *ctx)
|
||||
{
|
||||
trace_probe_log_err(ctx->offset, NOSUP_BTFARG);
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
#define parse_btf_arg_type(idx, ctx) \
|
||||
static int parse_btf_bitfield(struct fetch_insn **pcode,
|
||||
struct traceprobe_parse_context *ctx)
|
||||
{
|
||||
trace_probe_log_err(ctx->offset, NOSUP_BTFARG);
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
#define find_fetch_type_from_btf_type(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)
|
||||
static int check_prepare_btf_string_fetch(char *typename,
|
||||
struct fetch_insn **pcode,
|
||||
struct traceprobe_parse_context *ctx)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
#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,
|
||||
/* Parse $vars. @orig_arg points '$', which syncs to @ctx->offset */
|
||||
static int parse_probe_vars(char *orig_arg, const struct fetch_type *t,
|
||||
struct fetch_insn **pcode,
|
||||
struct fetch_insn *end,
|
||||
struct traceprobe_parse_context *ctx)
|
||||
{
|
||||
unsigned long param;
|
||||
struct fetch_insn *code = *pcode;
|
||||
int err = TP_ERR_BAD_VAR;
|
||||
char *arg = orig_arg + 1;
|
||||
unsigned long param;
|
||||
int ret = 0;
|
||||
int len;
|
||||
|
||||
@ -563,18 +785,17 @@ static int parse_probe_vars(char *arg, const struct fetch_type *t,
|
||||
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;
|
||||
}
|
||||
if (str_has_prefix(arg, "retval")) {
|
||||
if (!(ctx->flags & TPARG_FL_RETURN)) {
|
||||
err = TP_ERR_RETVAL_ON_PROBE;
|
||||
goto inval;
|
||||
}
|
||||
if (!(ctx->flags & TPARG_FL_KERNEL) ||
|
||||
!IS_ENABLED(CONFIG_PROBE_EVENTS_BTF_ARGS)) {
|
||||
code->op = FETCH_OP_RETVAL;
|
||||
return 0;
|
||||
}
|
||||
err = TP_ERR_RETVAL_ON_PROBE;
|
||||
goto inval;
|
||||
return parse_btf_arg(orig_arg, pcode, end, ctx);
|
||||
}
|
||||
|
||||
len = str_has_prefix(arg, "stack");
|
||||
@ -676,7 +897,7 @@ parse_probe_arg(char *arg, const struct fetch_type *type,
|
||||
|
||||
switch (arg[0]) {
|
||||
case '$':
|
||||
ret = parse_probe_vars(arg + 1, type, code, ctx);
|
||||
ret = parse_probe_vars(arg, type, pcode, end, ctx);
|
||||
break;
|
||||
|
||||
case '%': /* named register */
|
||||
@ -795,6 +1016,8 @@ parse_probe_arg(char *arg, const struct fetch_type *type,
|
||||
|
||||
code->op = deref;
|
||||
code->offset = offset;
|
||||
/* Reset the last type if used */
|
||||
ctx->last_type = NULL;
|
||||
}
|
||||
break;
|
||||
case '\\': /* Immediate value */
|
||||
@ -818,7 +1041,7 @@ parse_probe_arg(char *arg, const struct fetch_type *type,
|
||||
trace_probe_log_err(ctx->offset, NOSUP_BTFARG);
|
||||
return -EINVAL;
|
||||
}
|
||||
ret = parse_btf_arg(arg, code, ctx);
|
||||
ret = parse_btf_arg(arg, pcode, end, ctx);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -964,17 +1187,22 @@ static int traceprobe_parse_probe_arg_body(const char *argv, ssize_t *size,
|
||||
goto out;
|
||||
code[FETCH_INSN_MAX - 1].op = FETCH_OP_END;
|
||||
|
||||
ctx->last_type = NULL;
|
||||
ret = parse_probe_arg(arg, parg->type, &code, &code[FETCH_INSN_MAX - 1],
|
||||
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);
|
||||
if (IS_ENABLED(CONFIG_PROBE_EVENTS_BTF_ARGS) &&
|
||||
ctx->last_type) {
|
||||
if (!t) {
|
||||
parg->type = find_fetch_type_from_btf_type(ctx);
|
||||
} else if (strstr(t, "string")) {
|
||||
ret = check_prepare_btf_string_fetch(t, &code, ctx);
|
||||
if (ret)
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
ret = -EINVAL;
|
||||
@ -1048,6 +1276,11 @@ static int traceprobe_parse_probe_arg_body(const char *argv, ssize_t *size,
|
||||
trace_probe_log_err(ctx->offset + t - arg, BAD_BITFIELD);
|
||||
goto fail;
|
||||
}
|
||||
} else if (IS_ENABLED(CONFIG_PROBE_EVENTS_BTF_ARGS) &&
|
||||
ctx->last_type) {
|
||||
ret = parse_btf_bitfield(&code, ctx);
|
||||
if (ret)
|
||||
goto fail;
|
||||
}
|
||||
ret = -EINVAL;
|
||||
/* Loop(Array) operation */
|
||||
@ -1231,7 +1464,6 @@ 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;
|
||||
|
||||
@ -1239,7 +1471,7 @@ static int sprint_nth_btf_arg(int idx, const char *type,
|
||||
trace_probe_log_err(0, NO_BTFARG);
|
||||
return -ENOENT;
|
||||
}
|
||||
name = btf_name_by_offset(btf, ctx->params[idx].name_off);
|
||||
name = btf_name_by_offset(ctx->btf, ctx->params[idx].name_off);
|
||||
if (!name) {
|
||||
trace_probe_log_err(0, NO_BTF_ENTRY);
|
||||
return -ENOENT;
|
||||
@ -1260,7 +1492,6 @@ const char **traceprobe_expand_meta_args(int argc, const char *argv[],
|
||||
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)
|
||||
@ -1271,9 +1502,8 @@ const char **traceprobe_expand_meta_args(int argc, const char *argv[],
|
||||
return NULL;
|
||||
}
|
||||
|
||||
params = find_btf_func_param(ctx->funcname, &nr_params,
|
||||
ctx->flags & TPARG_FL_TPOINT);
|
||||
if (IS_ERR_OR_NULL(params)) {
|
||||
ret = query_btf_context(ctx);
|
||||
if (ret < 0 || ctx->nr_params == 0) {
|
||||
if (args_idx != -1) {
|
||||
/* $arg* requires BTF info */
|
||||
trace_probe_log_err(0, NOSUP_BTFARG);
|
||||
@ -1282,8 +1512,6 @@ const char **traceprobe_expand_meta_args(int argc, const char *argv[],
|
||||
*new_argc = argc;
|
||||
return NULL;
|
||||
}
|
||||
ctx->params = params;
|
||||
ctx->nr_params = nr_params;
|
||||
|
||||
if (args_idx >= 0)
|
||||
*new_argc = argc + ctx->nr_params - 1;
|
||||
@ -1298,7 +1526,7 @@ const char **traceprobe_expand_meta_args(int argc, const char *argv[],
|
||||
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++) {
|
||||
for (n = 0; n < ctx->nr_params; n++) {
|
||||
ret = sprint_nth_btf_arg(n, "", buf + used,
|
||||
bufsize - used, ctx);
|
||||
if (ret < 0)
|
||||
@ -1337,6 +1565,11 @@ const char **traceprobe_expand_meta_args(int argc, const char *argv[],
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
void traceprobe_finish_parse(struct traceprobe_parse_context *ctx)
|
||||
{
|
||||
clear_btf_context(ctx);
|
||||
}
|
||||
|
||||
int traceprobe_update_arg(struct probe_arg *arg)
|
||||
{
|
||||
struct fetch_insn *code = arg->code;
|
||||
|
@ -383,9 +383,15 @@ static inline bool tparg_is_function_entry(unsigned int flags)
|
||||
|
||||
struct traceprobe_parse_context {
|
||||
struct trace_event_call *event;
|
||||
const struct btf_param *params;
|
||||
s32 nr_params;
|
||||
const char *funcname;
|
||||
/* BTF related parameters */
|
||||
const char *funcname; /* Function name in BTF */
|
||||
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;
|
||||
int offset;
|
||||
};
|
||||
@ -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 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);
|
||||
int traceprobe_parse_event_name(const char **pevent, const char **pgroup,
|
||||
char *buf, int offset);
|
||||
@ -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(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"),
|
||||
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
|
||||
#define C(a, b) TP_ERR_##a
|
||||
|
@ -688,6 +688,7 @@ static int __trace_uprobe_create(int argc, const char **argv)
|
||||
|
||||
trace_probe_log_set_index(i + 2);
|
||||
ret = traceprobe_parse_probe_arg(&tu->tp, i, argv[i], &ctx);
|
||||
traceprobe_finish_parse(&ctx);
|
||||
if (ret)
|
||||
goto error;
|
||||
}
|
||||
|
@ -5,6 +5,7 @@
|
||||
|
||||
KPROBES=
|
||||
FPROBES=
|
||||
FIELDS=
|
||||
|
||||
if grep -qF "p[:[<group>/][<event>]] <place> [<args>]" README ; then
|
||||
KPROBES=yes
|
||||
@ -12,6 +13,9 @@ fi
|
||||
if grep -qF "f[:[<group>/][<event>]] <func-name>[%return] [<args>]" README ; then
|
||||
FPROBES=yes
|
||||
fi
|
||||
if grep -qF "<argname>[->field[->field|.field...]]" README ; then
|
||||
FIELDS=yes
|
||||
fi
|
||||
|
||||
if [ -z "$KPROBES" -a -z "$FPROBES" ] ; then
|
||||
exit_unsupported
|
||||
@ -21,6 +25,9 @@ echo 0 > events/enable
|
||||
echo > dynamic_events
|
||||
|
||||
TP=kfree
|
||||
TP2=kmem_cache_alloc
|
||||
TP3=getname_flags
|
||||
TP4=sched_wakeup
|
||||
|
||||
if [ "$FPROBES" ] ; then
|
||||
echo "f:fpevent $TP object" >> dynamic_events
|
||||
@ -33,6 +40,7 @@ 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
|
||||
@ -45,6 +53,18 @@ fi
|
||||
|
||||
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
|
||||
echo "p:kpevent $TP object" >> dynamic_events
|
||||
grep -q "kpevent.*object=object" dynamic_events
|
||||
|
@ -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/^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 $retval ^$arg1' # BAD_VAR
|
||||
check_error 'f vfs_read ^$none_var' # BAD_VAR
|
||||
check_error 'f vfs_read ^'$REG # BAD_VAR
|
||||
|
||||
@ -103,6 +103,14 @@ 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
|
||||
|
||||
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
|
||||
check_error 'f vfs_read ^$arg*' # NOSUP_BTFARG
|
||||
check_error 't kfree ^$arg*' # NOSUP_BTFARG
|
||||
|
Loading…
Reference in New Issue
Block a user