Probes updates for v6.17:

- Stack usage reduction for probe events:
    - Allocate string buffers from the heap for uprobe, eprobe, kprobe,
      and fprobe events to avoid stack overflow.
    - Allocate traceprobe_parse_context from the heap to prevent
      potential stack overflow.
    - Fix a typo in the above commit.
 
  - New features for eprobe and tprobe events:
    - Add support for arrays in eprobes.
    - Support multiple tprobes on the same tracepoint.
 
  - Improve efficiency:
    - Register fprobe-events only when it is enabled to reduce overhead.
    - Register tracepoints for tprobe events only when enabled to
      resolve a lock dependency.
 
  - Code Cleanup:
    - Add kerneldoc for traceprobe_parse_event_name() and __get_insn_slot().
    - Sort #include alphabetically in the probes code.
    - Remove the unused 'mod' field from the tprobe-event.
    - Clean up the entry-arg storing code in probe-events.
 
  - Selftest update
    - Enable fprobe events before checking enable_functions in selftests.
 -----BEGIN PGP SIGNATURE-----
 
 iQFPBAABCgA5FiEEh7BulGwFlgAOi5DV2/sHvwUrPxsFAmiJ2DQbHG1hc2FtaS5o
 aXJhbWF0c3VAZ21haWwuY29tAAoJENv7B78FKz8bSfkH/06Zn5I55rU85FKSBQll
 FN4hipmef/9Nd13skDwpEuFyzLPNS4P1up/UBUuyDQUTlO74+t2zSFO2dpcNrWmu
 sPTenQ+6h82H3K591WTIC23VzF54syIbFLXEj8iMBALT3wyU4Nn0bs4DCbnTo5HX
 R3NVo77rk6wxNJoKYOtT6ALf/lHonuNlGF+KTUGWP8UbWsIY3fIp0RWWy572M0bt
 +YBE8D8RIVrw+ZY+vNKn1LdZdWlR1ton518XDf1gV9isTCfKErcd/6HJKwuj5q2v
 qMgwiaKK+Gne/ylAKmWLEg2oNDo7kpyfW+612oiECitgZkqxOXhyYYfWgRt1lFNp
 Wb8=
 =E+Z6
 -----END PGP SIGNATURE-----

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

Pull probes updates from Masami Hiramatsu:
 "Stack usage reduction for probe events:
   - Allocate string buffers from the heap for uprobe, eprobe, kprobe,
     and fprobe events to avoid stack overflow
   - Allocate traceprobe_parse_context from the heap to prevent
     potential stack overflow
   - Fix a typo in the above commit

  New features for eprobe and tprobe events:
   - Add support for arrays in eprobes
   - Support multiple tprobes on the same tracepoint

  Improve efficiency:
   - Register fprobe-events only when it is enabled to reduce overhead
   - Register tracepoints for tprobe events only when enabled to resolve
     a lock dependency

  Code Cleanup:
   - Add kerneldoc for traceprobe_parse_event_name() and
     __get_insn_slot()
   - Sort #include alphabetically in the probes code
   - Remove the unused 'mod' field from the tprobe-event
   - Clean up the entry-arg storing code in probe-events

  Selftest update
   - Enable fprobe events before checking enable_functions in selftests"

* tag 'probes-v6.17' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace:
  tracing: trace_fprobe: Fix typo of the semicolon
  tracing: Have eprobes handle arrays
  tracing: probes: Add a kerneldoc for traceprobe_parse_event_name()
  tracing: uprobe-event: Allocate string buffers from heap
  tracing: eprobe-event: Allocate string buffers from heap
  tracing: kprobe-event: Allocate string buffers from heap
  tracing: fprobe-event: Allocate string buffers from heap
  tracing: probe: Allocate traceprobe_parse_context from heap
  tracing: probes: Sort #include alphabetically
  kprobes: Add missing kerneldoc for __get_insn_slot
  tracing: tprobe-events: Register tracepoint when enable tprobe event
  selftests: tracing: Enable fprobe events before checking enable_functions
  tracing: fprobe-events: Register fprobe-events only when it is enabled
  tracing: tprobe-events: Support multiple tprobes on the same tracepoint
  tracing: tprobe-events: Remove mod field from tprobe-event
  tracing: probe-events: Cleanup entry-arg storing code
This commit is contained in:
Linus Torvalds 2025-07-30 15:38:01 -07:00
commit b7dbc2e813
11 changed files with 676 additions and 353 deletions

View File

@ -94,6 +94,7 @@ 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);
int fprobe_count_ips_from_filter(const char *filter, const char *notfilter);
#else
static inline int register_fprobe(struct fprobe *fp, const char *filter, const char *notfilter)
{
@ -115,6 +116,10 @@ static inline bool fprobe_is_registered(struct fprobe *fp)
{
return false;
}
static inline int fprobe_count_ips_from_filter(const char *filter, const char *notfilter)
{
return -EOPNOTSUPP;
}
#endif
/**

View File

@ -14,6 +14,7 @@
#include <linux/buildid.h>
#include <linux/compiler.h>
#include <linux/cache.h>
#include <linux/cleanup.h>
#include <linux/kmod.h>
#include <linux/init.h>
#include <linux/elf.h>
@ -1018,4 +1019,7 @@ static inline unsigned long find_kallsyms_symbol_value(struct module *mod,
#endif /* CONFIG_MODULES && CONFIG_KALLSYMS */
/* Define __free(module_put) macro for struct module *. */
DEFINE_FREE(module_put, struct module *, if (_T) module_put(_T))
#endif /* _LINUX_MODULE_H */

View File

@ -135,8 +135,12 @@ struct kprobe_insn_cache kprobe_insn_slots = {
static int collect_garbage_slots(struct kprobe_insn_cache *c);
/**
* __get_insn_slot() - Find a slot on an executable page for an instruction.
* We allocate an executable page if there's no room on existing ones.
* __get_insn_slot - Find a slot on an executable page for an instruction.
* @c: Pointer to kprobe instruction cache
*
* Description: Locates available slot on existing executable pages,
* allocates an executable page if there's no room on existing ones.
* Return: Pointer to instruction slot on success, NULL on failure.
*/
kprobe_opcode_t *__get_insn_slot(struct kprobe_insn_cache *c)
{

View File

@ -648,6 +648,11 @@ static int fprobe_init(struct fprobe *fp, unsigned long *addrs, int num)
#define FPROBE_IPS_MAX INT_MAX
int fprobe_count_ips_from_filter(const char *filter, const char *notfilter)
{
return get_ips_from_filter(filter, notfilter, NULL, NULL, FPROBE_IPS_MAX);
}
/**
* register_fprobe() - Register fprobe to ftrace by pattern.
* @fp: A fprobe data structure to be registered.

View File

@ -9,14 +9,15 @@
* Copyright (C) 2021, VMware Inc, Tzvetomir Stoyanov tz.stoyanov@gmail.com>
*
*/
#include <linux/cleanup.h>
#include <linux/ftrace.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/ftrace.h>
#include "trace_dynevent.h"
#include "trace_probe.h"
#include "trace_probe_tmpl.h"
#include "trace_probe_kernel.h"
#include "trace_probe_tmpl.h"
#define EPROBE_EVENT_SYSTEM "eprobes"
@ -343,10 +344,15 @@ get_event_field(struct fetch_insn *code, void *rec)
val = *(unsigned int *)addr;
break;
default:
if (field->is_signed)
val = *(long *)addr;
else
val = *(unsigned long *)addr;
if (field->size == sizeof(long)) {
if (field->is_signed)
val = *(long *)addr;
else
val = *(unsigned long *)addr;
break;
}
/* This is an array, point to the addr itself */
val = (unsigned long)addr;
break;
}
return val;
@ -797,18 +803,20 @@ 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)
{
struct traceprobe_parse_context ctx = {
.event = ep->event,
.flags = TPARG_FL_KERNEL | TPARG_FL_TEVENT,
};
struct traceprobe_parse_context *ctx __free(traceprobe_parse_context) = NULL;
int ret;
ret = traceprobe_parse_probe_arg(&ep->tp, i, argv[i], &ctx);
ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
if (!ctx)
return -ENOMEM;
ctx->event = ep->event;
ctx->flags = TPARG_FL_KERNEL | TPARG_FL_TEVENT;
ret = traceprobe_parse_probe_arg(&ep->tp, i, argv[i], ctx);
/* Handle symbols "@" */
if (!ret)
ret = traceprobe_update_arg(&ep->tp.args[i]);
traceprobe_finish_parse(&ctx);
return ret;
}
@ -869,10 +877,10 @@ static int __trace_eprobe_create(int argc, const char *argv[])
const char *event = NULL, *group = EPROBE_EVENT_SYSTEM;
const char *sys_event = NULL, *sys_name = NULL;
struct trace_event_call *event_call;
char *buf1 __free(kfree) = NULL;
char *buf2 __free(kfree) = NULL;
char *gbuf __free(kfree) = NULL;
struct trace_eprobe *ep = NULL;
char buf1[MAX_EVENT_NAME_LEN];
char buf2[MAX_EVENT_NAME_LEN];
char gbuf[MAX_EVENT_NAME_LEN];
int ret = 0, filter_idx = 0;
int i, filter_cnt;
@ -883,6 +891,9 @@ static int __trace_eprobe_create(int argc, const char *argv[])
event = strchr(&argv[0][1], ':');
if (event) {
gbuf = kmalloc(MAX_EVENT_NAME_LEN, GFP_KERNEL);
if (!gbuf)
goto mem_error;
event++;
ret = traceprobe_parse_event_name(&event, &group, gbuf,
event - argv[0]);
@ -892,6 +903,11 @@ static int __trace_eprobe_create(int argc, const char *argv[])
trace_probe_log_set_index(1);
sys_event = argv[1];
buf2 = kmalloc(MAX_EVENT_NAME_LEN, GFP_KERNEL);
if (!buf2)
goto mem_error;
ret = traceprobe_parse_event_name(&sys_event, &sys_name, buf2, 0);
if (ret || !sys_event || !sys_name) {
trace_probe_log_err(0, NO_EVENT_INFO);
@ -899,7 +915,9 @@ static int __trace_eprobe_create(int argc, const char *argv[])
}
if (!event) {
strscpy(buf1, sys_event, MAX_EVENT_NAME_LEN);
buf1 = kstrdup(sys_event, GFP_KERNEL);
if (!buf1)
goto mem_error;
event = buf1;
}
@ -972,6 +990,9 @@ static int __trace_eprobe_create(int argc, const char *argv[])
trace_probe_log_clear();
return ret;
mem_error:
ret = -ENOMEM;
goto error;
parse_error:
ret = -EINVAL;
error:

View File

@ -4,15 +4,18 @@
* Copyright (C) 2022 Google LLC.
*/
#define pr_fmt(fmt) "trace_fprobe: " fmt
#include <asm/ptrace.h>
#include <linux/fprobe.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/rculist.h>
#include <linux/security.h>
#include <linux/tracepoint.h>
#include <linux/uaccess.h>
#include <asm/ptrace.h>
#include "trace_dynevent.h"
#include "trace_probe.h"
#include "trace_probe_kernel.h"
@ -21,7 +24,6 @@
#define FPROBE_EVENT_SYSTEM "fprobes"
#define TRACEPOINT_EVENT_SYSTEM "tracepoints"
#define RETHOOK_MAXACTIVE_MAX 4096
#define TRACEPOINT_STUB ERR_PTR(-ENOENT)
static int trace_fprobe_create(const char *raw_command);
static int trace_fprobe_show(struct seq_file *m, struct dyn_event *ev);
@ -38,15 +40,156 @@ static struct dyn_event_operations trace_fprobe_ops = {
.match = trace_fprobe_match,
};
/* List of tracepoint_user */
static LIST_HEAD(tracepoint_user_list);
static DEFINE_MUTEX(tracepoint_user_mutex);
/* While living tracepoint_user, @tpoint can be NULL and @refcount != 0. */
struct tracepoint_user {
struct list_head list;
const char *name;
struct tracepoint *tpoint;
unsigned int refcount;
};
/* NOTE: you must lock tracepoint_user_mutex. */
#define for_each_tracepoint_user(tuser) \
list_for_each_entry(tuser, &tracepoint_user_list, list)
static int tracepoint_user_register(struct tracepoint_user *tuser)
{
struct tracepoint *tpoint = tuser->tpoint;
if (!tpoint)
return 0;
return tracepoint_probe_register_prio_may_exist(tpoint,
tpoint->probestub, NULL, 0);
}
static void tracepoint_user_unregister(struct tracepoint_user *tuser)
{
if (!tuser->tpoint)
return;
WARN_ON_ONCE(tracepoint_probe_unregister(tuser->tpoint, tuser->tpoint->probestub, NULL));
tuser->tpoint = NULL;
}
static unsigned long tracepoint_user_ip(struct tracepoint_user *tuser)
{
if (!tuser->tpoint)
return 0UL;
return (unsigned long)tuser->tpoint->probestub;
}
static void __tracepoint_user_free(struct tracepoint_user *tuser)
{
if (!tuser)
return;
kfree(tuser->name);
kfree(tuser);
}
DEFINE_FREE(tuser_free, struct tracepoint_user *, __tracepoint_user_free(_T))
static struct tracepoint_user *__tracepoint_user_init(const char *name, struct tracepoint *tpoint)
{
struct tracepoint_user *tuser __free(tuser_free) = NULL;
int ret;
tuser = kzalloc(sizeof(*tuser), GFP_KERNEL);
if (!tuser)
return NULL;
tuser->name = kstrdup(name, GFP_KERNEL);
if (!tuser->name)
return NULL;
if (tpoint) {
ret = tracepoint_user_register(tuser);
if (ret)
return ERR_PTR(ret);
}
tuser->tpoint = tpoint;
tuser->refcount = 1;
INIT_LIST_HEAD(&tuser->list);
list_add(&tuser->list, &tracepoint_user_list);
return_ptr(tuser);
}
static struct tracepoint *find_tracepoint(const char *tp_name,
struct module **tp_mod);
/*
* Get tracepoint_user if exist, or allocate new one and register it.
* If tracepoint is on a module, get its refcounter too.
* This returns errno or NULL (not loaded yet) or tracepoint_user.
*/
static struct tracepoint_user *tracepoint_user_find_get(const char *name, struct module **pmod)
{
struct module *mod __free(module_put) = NULL;
struct tracepoint_user *tuser;
struct tracepoint *tpoint;
if (!name || !pmod)
return ERR_PTR(-EINVAL);
/* Get and lock the module which has tracepoint. */
tpoint = find_tracepoint(name, &mod);
guard(mutex)(&tracepoint_user_mutex);
/* Search existing tracepoint_user */
for_each_tracepoint_user(tuser) {
if (!strcmp(tuser->name, name)) {
tuser->refcount++;
*pmod = no_free_ptr(mod);
return tuser;
}
}
/* The corresponding tracepoint_user is not found. */
tuser = __tracepoint_user_init(name, tpoint);
if (!IS_ERR_OR_NULL(tuser))
*pmod = no_free_ptr(mod);
return tuser;
}
static void tracepoint_user_put(struct tracepoint_user *tuser)
{
scoped_guard(mutex, &tracepoint_user_mutex) {
if (--tuser->refcount > 0)
return;
list_del(&tuser->list);
tracepoint_user_unregister(tuser);
}
__tracepoint_user_free(tuser);
}
DEFINE_FREE(tuser_put, struct tracepoint_user *,
if (!IS_ERR_OR_NULL(_T))
tracepoint_user_put(_T))
/*
* Fprobe event core functions
*/
/*
* @tprobe is true for tracepoint probe.
* @tuser can be NULL if the trace_fprobe is disabled or the tracepoint is not
* loaded with a module. If @tuser != NULL, this trace_fprobe is enabled.
*/
struct trace_fprobe {
struct dyn_event devent;
struct fprobe fp;
const char *symbol;
struct tracepoint *tpoint;
struct module *mod;
bool tprobe;
struct tracepoint_user *tuser;
struct trace_probe tp;
};
@ -76,7 +219,7 @@ static bool trace_fprobe_is_return(struct trace_fprobe *tf)
static bool trace_fprobe_is_tracepoint(struct trace_fprobe *tf)
{
return tf->tpoint != NULL;
return tf->tprobe;
}
static const char *trace_fprobe_symbol(struct trace_fprobe *tf)
@ -411,6 +554,8 @@ static void free_trace_fprobe(struct trace_fprobe *tf)
{
if (tf) {
trace_probe_cleanup(&tf->tp);
if (tf->tuser)
tracepoint_user_put(tf->tuser);
kfree(tf->symbol);
kfree(tf);
}
@ -425,9 +570,8 @@ DEFINE_FREE(free_trace_fprobe, struct trace_fprobe *, if (!IS_ERR_OR_NULL(_T)) f
static struct trace_fprobe *alloc_trace_fprobe(const char *group,
const char *event,
const char *symbol,
struct tracepoint *tpoint,
struct module *mod,
int nargs, bool is_return)
int nargs, bool is_return,
bool is_tracepoint)
{
struct trace_fprobe *tf __free(free_trace_fprobe) = NULL;
int ret = -ENOMEM;
@ -445,8 +589,7 @@ static struct trace_fprobe *alloc_trace_fprobe(const char *group,
else
tf->fp.entry_handler = fentry_dispatcher;
tf->tpoint = tpoint;
tf->mod = mod;
tf->tprobe = is_tracepoint;
ret = trace_probe_init(&tf->tp, event, group, false, nargs);
if (ret < 0)
@ -469,98 +612,6 @@ static struct trace_fprobe *find_trace_fprobe(const char *event,
return NULL;
}
static inline int __enable_trace_fprobe(struct trace_fprobe *tf)
{
if (trace_fprobe_is_registered(tf))
enable_fprobe(&tf->fp);
return 0;
}
static void __disable_trace_fprobe(struct trace_probe *tp)
{
struct trace_fprobe *tf;
list_for_each_entry(tf, trace_probe_probe_list(tp), tp.list) {
if (!trace_fprobe_is_registered(tf))
continue;
disable_fprobe(&tf->fp);
}
}
/*
* Enable trace_probe
* if the file is NULL, enable "perf" handler, or enable "trace" handler.
*/
static int enable_trace_fprobe(struct trace_event_call *call,
struct trace_event_file *file)
{
struct trace_probe *tp;
struct trace_fprobe *tf;
bool enabled;
int ret = 0;
tp = trace_probe_primary_from_call(call);
if (WARN_ON_ONCE(!tp))
return -ENODEV;
enabled = trace_probe_is_enabled(tp);
/* This also changes "enabled" state */
if (file) {
ret = trace_probe_add_file(tp, file);
if (ret)
return ret;
} else
trace_probe_set_flag(tp, TP_FLAG_PROFILE);
if (!enabled) {
list_for_each_entry(tf, trace_probe_probe_list(tp), tp.list) {
/* TODO: check the fprobe is gone */
__enable_trace_fprobe(tf);
}
}
return 0;
}
/*
* Disable trace_probe
* if the file is NULL, disable "perf" handler, or disable "trace" handler.
*/
static int disable_trace_fprobe(struct trace_event_call *call,
struct trace_event_file *file)
{
struct trace_probe *tp;
tp = trace_probe_primary_from_call(call);
if (WARN_ON_ONCE(!tp))
return -ENODEV;
if (file) {
if (!trace_probe_get_file_link(tp, file))
return -ENOENT;
if (!trace_probe_has_single_file(tp))
goto out;
trace_probe_clear_flag(tp, TP_FLAG_TRACE);
} else
trace_probe_clear_flag(tp, TP_FLAG_PROFILE);
if (!trace_probe_is_enabled(tp))
__disable_trace_fprobe(tp);
out:
if (file)
/*
* Synchronization is done in below function. For perf event,
* file == NULL and perf_trace_event_unreg() calls
* tracepoint_synchronize_unregister() to ensure synchronize
* event. We don't need to care about it.
*/
trace_probe_remove_file(tp, file);
return 0;
}
/* Event entry printers */
static enum print_line_t
print_fentry_event(struct trace_iterator *iter, int flags,
@ -712,20 +763,52 @@ static int unregister_fprobe_event(struct trace_fprobe *tf)
static int __regsiter_tracepoint_fprobe(struct trace_fprobe *tf)
{
struct tracepoint *tpoint = tf->tpoint;
unsigned long ip = (unsigned long)tpoint->probestub;
struct tracepoint_user *tuser __free(tuser_put) = NULL;
struct module *mod __free(module_put) = NULL;
unsigned long ip;
int ret;
if (WARN_ON_ONCE(tf->tuser))
return -EINVAL;
/* If the tracepoint is in a module, it must be locked in this function. */
tuser = tracepoint_user_find_get(tf->symbol, &mod);
/* This tracepoint is not loaded yet */
if (IS_ERR(tuser))
return PTR_ERR(tuser);
if (!tuser)
return -ENOMEM;
/* Register fprobe only if the tracepoint is loaded. */
if (tuser->tpoint) {
ip = tracepoint_user_ip(tuser);
if (WARN_ON_ONCE(!ip))
return -ENOENT;
ret = register_fprobe_ips(&tf->fp, &ip, 1);
if (ret < 0)
return ret;
}
tf->tuser = no_free_ptr(tuser);
return 0;
}
/* Returns an error if the target function is not available, or 0 */
static int trace_fprobe_verify_target(struct trace_fprobe *tf)
{
int ret;
/* Tracepoint should have a stub function. */
if (trace_fprobe_is_tracepoint(tf))
return 0;
/*
* Here, we do 2 steps to enable fprobe on a tracepoint.
* At first, put __probestub_##TP function on the tracepoint
* and put a fprobe on the stub function.
* Note: since we don't lock the module, even if this succeeded,
* register_fprobe() later can fail.
*/
ret = tracepoint_probe_register_prio_may_exist(tpoint,
tpoint->probestub, NULL, 0);
if (ret < 0)
return ret;
return register_fprobe_ips(&tf->fp, &ip, 1);
ret = fprobe_count_ips_from_filter(tf->symbol, NULL);
return (ret < 0) ? ret : 0;
}
/* Internal register function - just handle fprobe and flags */
@ -747,20 +830,10 @@ static int __register_trace_fprobe(struct trace_fprobe *tf)
return ret;
}
/* Set/clear disabled flag according to tp->flag */
if (trace_probe_is_enabled(&tf->tp))
tf->fp.flags &= ~FPROBE_FL_DISABLED;
else
tf->fp.flags |= FPROBE_FL_DISABLED;
if (trace_fprobe_is_tracepoint(tf)) {
/* This tracepoint is not loaded yet */
if (tf->tpoint == TRACEPOINT_STUB)
return 0;
tf->fp.flags &= ~FPROBE_FL_DISABLED;
if (trace_fprobe_is_tracepoint(tf))
return __regsiter_tracepoint_fprobe(tf);
}
/* TODO: handle filter, nofilter or symbol list */
return register_fprobe(&tf->fp, tf->symbol, NULL);
@ -769,15 +842,11 @@ static int __register_trace_fprobe(struct trace_fprobe *tf)
/* Internal unregister function - just handle fprobe and flags */
static void __unregister_trace_fprobe(struct trace_fprobe *tf)
{
if (trace_fprobe_is_registered(tf)) {
if (trace_fprobe_is_registered(tf))
unregister_fprobe(&tf->fp);
memset(&tf->fp, 0, sizeof(tf->fp));
if (trace_fprobe_is_tracepoint(tf)) {
tracepoint_probe_unregister(tf->tpoint,
tf->tpoint->probestub, NULL);
tf->tpoint = NULL;
tf->mod = NULL;
}
if (tf->tuser) {
tracepoint_user_put(tf->tuser);
tf->tuser = NULL;
}
}
@ -837,7 +906,7 @@ static bool trace_fprobe_has_same_fprobe(struct trace_fprobe *orig,
return false;
}
static int append_trace_fprobe(struct trace_fprobe *tf, struct trace_fprobe *to)
static int append_trace_fprobe_event(struct trace_fprobe *tf, struct trace_fprobe *to)
{
int ret;
@ -865,7 +934,7 @@ static int append_trace_fprobe(struct trace_fprobe *tf, struct trace_fprobe *to)
if (ret)
return ret;
ret = __register_trace_fprobe(tf);
ret = trace_fprobe_verify_target(tf);
if (ret)
trace_probe_unlink(&tf->tp);
else
@ -874,8 +943,8 @@ static int append_trace_fprobe(struct trace_fprobe *tf, struct trace_fprobe *to)
return ret;
}
/* Register a trace_probe and probe_event */
static int register_trace_fprobe(struct trace_fprobe *tf)
/* Register a trace_probe and probe_event, and check the fprobe is available. */
static int register_trace_fprobe_event(struct trace_fprobe *tf)
{
struct trace_fprobe *old_tf;
int ret;
@ -885,7 +954,7 @@ static int register_trace_fprobe(struct trace_fprobe *tf)
old_tf = find_trace_fprobe(trace_probe_name(&tf->tp),
trace_probe_group_name(&tf->tp));
if (old_tf)
return append_trace_fprobe(tf, old_tf);
return append_trace_fprobe_event(tf, old_tf);
/* Register new event */
ret = register_fprobe_event(tf);
@ -898,8 +967,8 @@ static int register_trace_fprobe(struct trace_fprobe *tf)
return ret;
}
/* Register fprobe */
ret = __register_trace_fprobe(tf);
/* Verify fprobe is sane. */
ret = trace_fprobe_verify_target(tf);
if (ret < 0)
unregister_fprobe_event(tf);
else
@ -963,15 +1032,6 @@ static struct tracepoint *find_tracepoint(const char *tp_name,
}
#ifdef CONFIG_MODULES
static void reenable_trace_fprobe(struct trace_fprobe *tf)
{
struct trace_probe *tp = &tf->tp;
list_for_each_entry(tf, trace_probe_probe_list(tp), tp.list) {
__enable_trace_fprobe(tf);
}
}
/*
* Find a tracepoint from specified module. In this case, this does not get the
* module's refcount. The caller must ensure the module is not freed.
@ -988,36 +1048,94 @@ static struct tracepoint *find_tracepoint_in_module(struct module *mod,
return data.tpoint;
}
/* These are CONFIG_MODULES=y specific functions. */
static bool tracepoint_user_within_module(struct tracepoint_user *tuser,
struct module *mod)
{
return within_module(tracepoint_user_ip(tuser), mod);
}
static int tracepoint_user_register_again(struct tracepoint_user *tuser,
struct tracepoint *tpoint)
{
tuser->tpoint = tpoint;
return tracepoint_user_register(tuser);
}
static void tracepoint_user_unregister_clear(struct tracepoint_user *tuser)
{
tracepoint_user_unregister(tuser);
tuser->tpoint = NULL;
}
/* module callback for tracepoint_user */
static int __tracepoint_probe_module_cb(struct notifier_block *self,
unsigned long val, void *data)
{
struct tp_module *tp_mod = data;
struct tracepoint_user *tuser;
struct tracepoint *tpoint;
if (val != MODULE_STATE_GOING && val != MODULE_STATE_COMING)
return NOTIFY_DONE;
mutex_lock(&tracepoint_user_mutex);
for_each_tracepoint_user(tuser) {
if (val == MODULE_STATE_COMING) {
/* This is not a tracepoint in this module. Skip it. */
tpoint = find_tracepoint_in_module(tp_mod->mod, tuser->name);
if (!tpoint)
continue;
WARN_ON_ONCE(tracepoint_user_register_again(tuser, tpoint));
} else if (val == MODULE_STATE_GOING &&
tracepoint_user_within_module(tuser, tp_mod->mod)) {
/* Unregister all tracepoint_user in this module. */
tracepoint_user_unregister_clear(tuser);
}
}
mutex_unlock(&tracepoint_user_mutex);
return NOTIFY_DONE;
}
static struct notifier_block tracepoint_module_nb = {
.notifier_call = __tracepoint_probe_module_cb,
};
/* module callback for tprobe events */
static int __tprobe_event_module_cb(struct notifier_block *self,
unsigned long val, void *data)
{
struct trace_fprobe *tf;
struct dyn_event *pos;
struct module *mod = data;
if (val != MODULE_STATE_GOING && val != MODULE_STATE_COMING)
return NOTIFY_DONE;
mutex_lock(&event_mutex);
for_each_trace_fprobe(tf, pos) {
if (val == MODULE_STATE_COMING && tf->tpoint == TRACEPOINT_STUB) {
tpoint = find_tracepoint_in_module(tp_mod->mod, tf->symbol);
if (tpoint) {
tf->tpoint = tpoint;
tf->mod = tp_mod->mod;
if (!WARN_ON_ONCE(__regsiter_tracepoint_fprobe(tf)) &&
trace_probe_is_enabled(&tf->tp))
reenable_trace_fprobe(tf);
}
} else if (val == MODULE_STATE_GOING && tp_mod->mod == tf->mod) {
/* Skip fprobe and disabled tprobe events. */
if (!trace_fprobe_is_tracepoint(tf) || !tf->tuser)
continue;
/* Before this notification, tracepoint notifier has already done. */
if (val == MODULE_STATE_COMING &&
tracepoint_user_within_module(tf->tuser, mod)) {
unsigned long ip = tracepoint_user_ip(tf->tuser);
WARN_ON_ONCE(register_fprobe_ips(&tf->fp, &ip, 1));
} else if (val == MODULE_STATE_GOING &&
/*
* tracepoint_user_within_module() does not work here because
* tracepoint_user is already unregistered and cleared tpoint.
* Instead, checking whether the fprobe is registered but
* tpoint is cleared(unregistered). Such unbalance probes
* must be adjusted anyway.
*/
trace_fprobe_is_registered(tf) &&
!tf->tuser->tpoint) {
unregister_fprobe(&tf->fp);
if (trace_fprobe_is_tracepoint(tf)) {
tracepoint_probe_unregister(tf->tpoint,
tf->tpoint->probestub, NULL);
tf->tpoint = TRACEPOINT_STUB;
tf->mod = NULL;
}
}
}
mutex_unlock(&event_mutex);
@ -1025,8 +1143,11 @@ static int __tracepoint_probe_module_cb(struct notifier_block *self,
return NOTIFY_DONE;
}
static struct notifier_block tracepoint_module_nb = {
.notifier_call = __tracepoint_probe_module_cb,
/* NOTE: this must be called after tracepoint callback */
static struct notifier_block tprobe_event_module_nb = {
.notifier_call = __tprobe_event_module_cb,
/* Make sure this is later than tracepoint module notifier. */
.priority = -10,
};
#endif /* CONFIG_MODULES */
@ -1086,8 +1207,6 @@ static int parse_symbol_and_return(int argc, const char *argv[],
return 0;
}
DEFINE_FREE(module_put, struct module *, if (_T) module_put(_T))
static int trace_fprobe_create_internal(int argc, const char *argv[],
struct traceprobe_parse_context *ctx)
{
@ -1116,19 +1235,18 @@ static int trace_fprobe_create_internal(int argc, const char *argv[],
* FETCHARG:TYPE : use TYPE instead of unsigned long.
*/
struct trace_fprobe *tf __free(free_trace_fprobe) = NULL;
int i, new_argc = 0, ret = 0;
bool is_return = false;
char *symbol __free(kfree) = NULL;
const char *event = NULL, *group = FPROBE_EVENT_SYSTEM;
struct module *mod __free(module_put) = NULL;
const char **new_argv __free(kfree) = NULL;
char buf[MAX_EVENT_NAME_LEN];
char gbuf[MAX_EVENT_NAME_LEN];
char sbuf[KSYM_NAME_LEN];
char abuf[MAX_BTF_ARGS_LEN];
char *symbol __free(kfree) = NULL;
char *ebuf __free(kfree) = NULL;
char *gbuf __free(kfree) = NULL;
char *sbuf __free(kfree) = NULL;
char *abuf __free(kfree) = NULL;
char *dbuf __free(kfree) = NULL;
int i, new_argc = 0, ret = 0;
bool is_tracepoint = false;
struct module *tp_mod __free(module_put) = NULL;
struct tracepoint *tpoint = NULL;
bool is_return = false;
if ((argv[0][0] != 'f' && argv[0][0] != 't') || argc < 2)
return -ECANCELED;
@ -1156,6 +1274,9 @@ static int trace_fprobe_create_internal(int argc, const char *argv[],
trace_probe_log_set_index(0);
if (event) {
gbuf = kmalloc(MAX_EVENT_NAME_LEN, GFP_KERNEL);
if (!gbuf)
return -ENOMEM;
ret = traceprobe_parse_event_name(&event, &group, gbuf,
event - argv[0]);
if (ret)
@ -1163,15 +1284,18 @@ static int trace_fprobe_create_internal(int argc, const char *argv[],
}
if (!event) {
ebuf = kmalloc(MAX_EVENT_NAME_LEN, GFP_KERNEL);
if (!ebuf)
return -ENOMEM;
/* Make a new event name */
if (is_tracepoint)
snprintf(buf, MAX_EVENT_NAME_LEN, "%s%s",
snprintf(ebuf, MAX_EVENT_NAME_LEN, "%s%s",
isdigit(*symbol) ? "_" : "", symbol);
else
snprintf(buf, MAX_EVENT_NAME_LEN, "%s__%s", symbol,
snprintf(ebuf, MAX_EVENT_NAME_LEN, "%s__%s", symbol,
is_return ? "exit" : "entry");
sanitize_event_name(buf);
event = buf;
sanitize_event_name(ebuf);
event = ebuf;
}
if (is_return)
@ -1179,25 +1303,28 @@ static int trace_fprobe_create_internal(int argc, const char *argv[],
else
ctx->flags |= TPARG_FL_FENTRY;
ctx->funcname = NULL;
if (is_tracepoint) {
/* Get tracepoint and lock its module until the end of the registration. */
struct tracepoint *tpoint;
ctx->flags |= TPARG_FL_TPOINT;
tpoint = find_tracepoint(symbol, &tp_mod);
mod = NULL;
tpoint = find_tracepoint(symbol, &mod);
if (tpoint) {
ctx->funcname = kallsyms_lookup(
(unsigned long)tpoint->probestub,
NULL, NULL, NULL, sbuf);
} else if (IS_ENABLED(CONFIG_MODULES)) {
/* This *may* be loaded afterwards */
tpoint = TRACEPOINT_STUB;
ctx->funcname = symbol;
} else {
trace_probe_log_set_index(1);
trace_probe_log_err(0, NO_TRACEPOINT);
return -EINVAL;
sbuf = kmalloc(KSYM_NAME_LEN, GFP_KERNEL);
if (!sbuf)
return -ENOMEM;
ctx->funcname = kallsyms_lookup((unsigned long)tpoint->probestub,
NULL, NULL, NULL, sbuf);
}
} else
}
if (!ctx->funcname)
ctx->funcname = symbol;
abuf = kmalloc(MAX_BTF_ARGS_LEN, GFP_KERNEL);
if (!abuf)
return -ENOMEM;
argc -= 2; argv += 2;
new_argv = traceprobe_expand_meta_args(argc, argv, &new_argc,
abuf, MAX_BTF_ARGS_LEN, ctx);
@ -1218,8 +1345,7 @@ static int trace_fprobe_create_internal(int argc, const char *argv[],
return ret;
/* setup a probe */
tf = alloc_trace_fprobe(group, event, symbol, tpoint, tp_mod,
argc, is_return);
tf = alloc_trace_fprobe(group, event, symbol, argc, is_return, is_tracepoint);
if (IS_ERR(tf)) {
ret = PTR_ERR(tf);
/* This must return -ENOMEM, else there is a bug */
@ -1251,7 +1377,7 @@ static int trace_fprobe_create_internal(int argc, const char *argv[],
if (ret < 0)
return ret;
ret = register_trace_fprobe(tf);
ret = register_trace_fprobe_event(tf);
if (ret) {
trace_probe_log_set_index(1);
if (ret == -EILSEQ)
@ -1271,14 +1397,17 @@ static int trace_fprobe_create_internal(int argc, const char *argv[],
static int trace_fprobe_create_cb(int argc, const char *argv[])
{
struct traceprobe_parse_context ctx = {
.flags = TPARG_FL_KERNEL | TPARG_FL_FPROBE,
};
struct traceprobe_parse_context *ctx __free(traceprobe_parse_context) = NULL;
int ret;
ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
if (!ctx)
return -ENOMEM;
ctx->flags = TPARG_FL_KERNEL | TPARG_FL_FPROBE;
trace_probe_log_init("trace_fprobe", argc, argv);
ret = trace_fprobe_create_internal(argc, argv, &ctx);
traceprobe_finish_parse(&ctx);
ret = trace_fprobe_create_internal(argc, argv, ctx);
trace_probe_log_clear();
return ret;
}
@ -1320,6 +1449,84 @@ static int trace_fprobe_show(struct seq_file *m, struct dyn_event *ev)
return 0;
}
/*
* Enable trace_probe
* if the file is NULL, enable "perf" handler, or enable "trace" handler.
*/
static int enable_trace_fprobe(struct trace_event_call *call,
struct trace_event_file *file)
{
struct trace_probe *tp;
struct trace_fprobe *tf;
bool enabled;
int ret = 0;
tp = trace_probe_primary_from_call(call);
if (WARN_ON_ONCE(!tp))
return -ENODEV;
enabled = trace_probe_is_enabled(tp);
/* This also changes "enabled" state */
if (file) {
ret = trace_probe_add_file(tp, file);
if (ret)
return ret;
} else
trace_probe_set_flag(tp, TP_FLAG_PROFILE);
if (!enabled) {
list_for_each_entry(tf, trace_probe_probe_list(tp), tp.list) {
ret = __register_trace_fprobe(tf);
if (ret < 0)
return ret;
}
}
return 0;
}
/*
* Disable trace_probe
* if the file is NULL, disable "perf" handler, or disable "trace" handler.
*/
static int disable_trace_fprobe(struct trace_event_call *call,
struct trace_event_file *file)
{
struct trace_fprobe *tf;
struct trace_probe *tp;
tp = trace_probe_primary_from_call(call);
if (WARN_ON_ONCE(!tp))
return -ENODEV;
if (file) {
if (!trace_probe_get_file_link(tp, file))
return -ENOENT;
if (!trace_probe_has_single_file(tp))
goto out;
trace_probe_clear_flag(tp, TP_FLAG_TRACE);
} else
trace_probe_clear_flag(tp, TP_FLAG_PROFILE);
if (!trace_probe_is_enabled(tp)) {
list_for_each_entry(tf, trace_probe_probe_list(tp), tp.list) {
unregister_fprobe(&tf->fp);
}
}
out:
if (file)
/*
* Synchronization is done in below function. For perf event,
* file == NULL and perf_trace_event_unreg() calls
* tracepoint_synchronize_unregister() to ensure synchronize
* event. We don't need to care about it.
*/
trace_probe_remove_file(tp, file);
return 0;
}
/*
* called by perf_trace_init() or __ftrace_set_clr_event() under event_mutex.
*/
@ -1365,6 +1572,9 @@ static __init int init_fprobe_trace_early(void)
ret = register_tracepoint_module_notifier(&tracepoint_module_nb);
if (ret)
return ret;
ret = register_module_notifier(&tprobe_event_module_nb);
if (ret)
return ret;
#endif
return 0;

View File

@ -9,19 +9,19 @@
#include <linux/bpf-cgroup.h>
#include <linux/cleanup.h>
#include <linux/security.h>
#include <linux/module.h>
#include <linux/uaccess.h>
#include <linux/rculist.h>
#include <linux/error-injection.h>
#include <linux/module.h>
#include <linux/rculist.h>
#include <linux/security.h>
#include <linux/uaccess.h>
#include <asm/setup.h> /* for COMMAND_LINE_SIZE */
#include "trace_dynevent.h"
#include "trace_kprobe_selftest.h"
#include "trace_probe.h"
#include "trace_probe_tmpl.h"
#include "trace_probe_kernel.h"
#include "trace_probe_tmpl.h"
#define KPROBE_EVENT_SYSTEM "kprobes"
#define KRETPROBE_MAXACTIVE_MAX 4096
@ -861,20 +861,20 @@ static int trace_kprobe_create_internal(int argc, const char *argv[],
* FETCHARG:TYPE : use TYPE instead of unsigned long.
*/
struct trace_kprobe *tk __free(free_trace_kprobe) = NULL;
int i, len, new_argc = 0, ret = 0;
bool is_return = false;
char *symbol __free(kfree) = NULL;
char *tmp = NULL;
const char **new_argv __free(kfree) = NULL;
const char *event = NULL, *group = KPROBE_EVENT_SYSTEM;
enum probe_print_type ptype;
int maxactive = 0;
long offset = 0;
void *addr = NULL;
char buf[MAX_EVENT_NAME_LEN];
char gbuf[MAX_EVENT_NAME_LEN];
char abuf[MAX_BTF_ARGS_LEN];
const char **new_argv __free(kfree) = NULL;
int i, len, new_argc = 0, ret = 0;
char *symbol __free(kfree) = NULL;
char *ebuf __free(kfree) = NULL;
char *gbuf __free(kfree) = NULL;
char *abuf __free(kfree) = NULL;
char *dbuf __free(kfree) = NULL;
enum probe_print_type ptype;
bool is_return = false;
int maxactive = 0;
void *addr = NULL;
char *tmp = NULL;
long offset = 0;
switch (argv[0][0]) {
case 'r':
@ -893,6 +893,8 @@ static int trace_kprobe_create_internal(int argc, const char *argv[],
event++;
if (isdigit(argv[0][1])) {
char *buf __free(kfree) = NULL;
if (!is_return) {
trace_probe_log_err(1, BAD_MAXACT_TYPE);
return -EINVAL;
@ -905,7 +907,7 @@ static int trace_kprobe_create_internal(int argc, const char *argv[],
trace_probe_log_err(1, BAD_MAXACT);
return -EINVAL;
}
memcpy(buf, &argv[0][1], len);
buf = kmemdup(&argv[0][1], len + 1, GFP_KERNEL);
buf[len] = '\0';
ret = kstrtouint(buf, 0, &maxactive);
if (ret || !maxactive) {
@ -973,6 +975,9 @@ static int trace_kprobe_create_internal(int argc, const char *argv[],
trace_probe_log_set_index(0);
if (event) {
gbuf = kmalloc(MAX_EVENT_NAME_LEN, GFP_KERNEL);
if (!gbuf)
return -ENOMEM;
ret = traceprobe_parse_event_name(&event, &group, gbuf,
event - argv[0]);
if (ret)
@ -981,16 +986,22 @@ static int trace_kprobe_create_internal(int argc, const char *argv[],
if (!event) {
/* Make a new event name */
ebuf = kmalloc(MAX_EVENT_NAME_LEN, GFP_KERNEL);
if (!ebuf)
return -ENOMEM;
if (symbol)
snprintf(buf, MAX_EVENT_NAME_LEN, "%c_%s_%ld",
snprintf(ebuf, MAX_EVENT_NAME_LEN, "%c_%s_%ld",
is_return ? 'r' : 'p', symbol, offset);
else
snprintf(buf, MAX_EVENT_NAME_LEN, "%c_0x%p",
snprintf(ebuf, MAX_EVENT_NAME_LEN, "%c_0x%p",
is_return ? 'r' : 'p', addr);
sanitize_event_name(buf);
event = buf;
sanitize_event_name(ebuf);
event = ebuf;
}
abuf = kmalloc(MAX_BTF_ARGS_LEN, GFP_KERNEL);
if (!abuf)
return -ENOMEM;
argc -= 2; argv += 2;
ctx->funcname = symbol;
new_argv = traceprobe_expand_meta_args(argc, argv, &new_argc,
@ -1065,14 +1076,18 @@ static int trace_kprobe_create_internal(int argc, const char *argv[],
static int trace_kprobe_create_cb(int argc, const char *argv[])
{
struct traceprobe_parse_context ctx = { .flags = TPARG_FL_KERNEL };
struct traceprobe_parse_context *ctx __free(traceprobe_parse_context) = NULL;
int ret;
ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
if (!ctx)
return -ENOMEM;
ctx->flags = TPARG_FL_KERNEL;
trace_probe_log_init("trace_kprobe", argc, argv);
ret = trace_kprobe_create_internal(argc, argv, &ctx);
ret = trace_kprobe_create_internal(argc, argv, ctx);
traceprobe_finish_parse(&ctx);
trace_probe_log_clear();
return ret;
}

View File

@ -13,8 +13,8 @@
#include <linux/bpf.h>
#include <linux/fs.h>
#include "trace_btf.h"
#include "trace_btf.h"
#include "trace_probe.h"
#undef C
@ -247,7 +247,25 @@ int traceprobe_split_symbol_offset(char *symbol, long *offset)
return 0;
}
/* @buf must has MAX_EVENT_NAME_LEN size */
/**
* traceprobe_parse_event_name() - Parse a string into group and event names
* @pevent: A pointer to the string to be parsed.
* @pgroup: A pointer to the group name.
* @buf: A buffer to store the parsed group name.
* @offset: The offset of the string in the original user command, for logging.
*
* This parses a string with the format `[GROUP/][EVENT]` or `[GROUP.][EVENT]`
* (either GROUP or EVENT or both must be specified).
* Since the parsed group name is stored in @buf, the caller must ensure @buf
* is at least MAX_EVENT_NAME_LEN bytes.
*
* Return: 0 on success, or -EINVAL on failure.
*
* If success, *@pevent is updated to point to the event name part of the
* original string, or NULL if there is no event name.
* Also, *@pgroup is updated to point to the parsed group which is stored
* in @buf, or NULL if there is no group name.
*/
int traceprobe_parse_event_name(const char **pevent, const char **pgroup,
char *buf, int offset)
{
@ -779,6 +797,36 @@ static int check_prepare_btf_string_fetch(char *typename,
#ifdef CONFIG_HAVE_FUNCTION_ARG_ACCESS_API
static void store_entry_arg_at(struct fetch_insn *code, int argnum, int offset)
{
code[0].op = FETCH_OP_ARG;
code[0].param = argnum;
code[1].op = FETCH_OP_ST_EDATA;
code[1].offset = offset;
}
static int get_entry_arg_max_offset(struct probe_entry_arg *earg)
{
int i, max_offset = 0;
/*
* earg->code[] array has an operation sequence which is run in
* the entry handler.
* The sequence stopped by FETCH_OP_END and each data stored in
* the entry data buffer by FETCH_OP_ST_EDATA. The FETCH_OP_ST_EDATA
* stores the data at the data buffer + its offset, and all data are
* "unsigned long" size. The offset must be increased when a data is
* stored. Thus we need to find the last FETCH_OP_ST_EDATA in the
* code array.
*/
for (i = 0; i < earg->size - 1 && earg->code[i].op != FETCH_OP_END; i++) {
if (earg->code[i].op == FETCH_OP_ST_EDATA)
if (earg->code[i].offset > max_offset)
max_offset = earg->code[i].offset;
}
return max_offset;
}
/*
* Add the entry code to store the 'argnum'th parameter and return the offset
* in the entry data buffer where the data will be stored.
@ -786,8 +834,7 @@ static int check_prepare_btf_string_fetch(char *typename,
static int __store_entry_arg(struct trace_probe *tp, int argnum)
{
struct probe_entry_arg *earg = tp->entry_arg;
bool match = false;
int i, offset;
int i, offset, last_offset = 0;
if (!earg) {
earg = kzalloc(sizeof(*tp->entry_arg), GFP_KERNEL);
@ -804,78 +851,59 @@ static int __store_entry_arg(struct trace_probe *tp, int argnum)
for (i = 0; i < earg->size; i++)
earg->code[i].op = FETCH_OP_END;
tp->entry_arg = earg;
store_entry_arg_at(earg->code, argnum, 0);
return 0;
}
/*
* The entry code array is repeating the pair of
* [FETCH_OP_ARG(argnum)][FETCH_OP_ST_EDATA(offset of entry data buffer)]
* and the rest of entries are filled with [FETCH_OP_END].
* NOTE: if anyone change the following rule, please rewrite this.
* The entry code array is filled with the pair of
*
* To reduce the redundant function parameter fetching, we scan the entry
* code array to find the FETCH_OP_ARG which already fetches the 'argnum'
* parameter. If it doesn't match, update 'offset' to find the last
* offset.
* If we find the FETCH_OP_END without matching FETCH_OP_ARG entry, we
* will save the entry with FETCH_OP_ARG and FETCH_OP_ST_EDATA, and
* return data offset so that caller can find the data offset in the entry
* data buffer.
* [FETCH_OP_ARG(argnum)]
* [FETCH_OP_ST_EDATA(offset of entry data buffer)]
*
* and the rest of entries are filled with [FETCH_OP_END].
* The offset should be incremented, thus the last pair should
* have the largest offset.
*/
offset = 0;
for (i = 0; i < earg->size - 1; i++) {
switch (earg->code[i].op) {
case FETCH_OP_END:
earg->code[i].op = FETCH_OP_ARG;
earg->code[i].param = argnum;
earg->code[i + 1].op = FETCH_OP_ST_EDATA;
earg->code[i + 1].offset = offset;
return offset;
case FETCH_OP_ARG:
match = (earg->code[i].param == argnum);
break;
case FETCH_OP_ST_EDATA:
offset = earg->code[i].offset;
if (match)
return offset;
offset += sizeof(unsigned long);
break;
default:
break;
}
/* Search the offset for the sprcified argnum. */
for (i = 0; i < earg->size - 1 && earg->code[i].op != FETCH_OP_END; i += 2) {
if (WARN_ON_ONCE(earg->code[i].op != FETCH_OP_ARG))
return -EINVAL;
if (earg->code[i].param != argnum)
continue;
if (WARN_ON_ONCE(earg->code[i + 1].op != FETCH_OP_ST_EDATA))
return -EINVAL;
return earg->code[i + 1].offset;
}
return -ENOSPC;
/* Not found, append new entry if possible. */
if (i >= earg->size - 1)
return -ENOSPC;
/* The last entry must have the largest offset. */
if (i != 0) {
if (WARN_ON_ONCE(earg->code[i - 1].op != FETCH_OP_ST_EDATA))
return -EINVAL;
last_offset = earg->code[i - 1].offset;
}
offset = last_offset + sizeof(unsigned long);
store_entry_arg_at(&earg->code[i], argnum, offset);
return offset;
}
int traceprobe_get_entry_data_size(struct trace_probe *tp)
{
struct probe_entry_arg *earg = tp->entry_arg;
int i, size = 0;
if (!earg)
return 0;
/*
* earg->code[] array has an operation sequence which is run in
* the entry handler.
* The sequence stopped by FETCH_OP_END and each data stored in
* the entry data buffer by FETCH_OP_ST_EDATA. The FETCH_OP_ST_EDATA
* stores the data at the data buffer + its offset, and all data are
* "unsigned long" size. The offset must be increased when a data is
* stored. Thus we need to find the last FETCH_OP_ST_EDATA in the
* code array.
*/
for (i = 0; i < earg->size; i++) {
switch (earg->code[i].op) {
case FETCH_OP_END:
goto out;
case FETCH_OP_ST_EDATA:
size = earg->code[i].offset + sizeof(unsigned long);
break;
default:
break;
}
}
out:
return size;
return get_entry_arg_max_offset(earg) + sizeof(unsigned long);
}
void store_trace_entry_data(void *edata, struct trace_probe *tp, struct pt_regs *regs)

View File

@ -10,20 +10,22 @@
* Author: Srikar Dronamraju
*/
#include <linux/bitops.h>
#include <linux/btf.h>
#include <linux/cleanup.h>
#include <linux/kprobes.h>
#include <linux/limits.h>
#include <linux/perf_event.h>
#include <linux/ptrace.h>
#include <linux/seq_file.h>
#include <linux/slab.h>
#include <linux/smp.h>
#include <linux/string.h>
#include <linux/stringify.h>
#include <linux/tracefs.h>
#include <linux/types.h>
#include <linux/string.h>
#include <linux/ptrace.h>
#include <linux/perf_event.h>
#include <linux/kprobes.h>
#include <linux/stringify.h>
#include <linux/limits.h>
#include <linux/uaccess.h>
#include <linux/bitops.h>
#include <linux/btf.h>
#include <asm/bitsperlong.h>
#include "trace.h"
@ -438,6 +440,14 @@ extern void traceprobe_free_probe_arg(struct probe_arg *arg);
* this MUST be called for clean up the context and return a resource.
*/
void traceprobe_finish_parse(struct traceprobe_parse_context *ctx);
static inline void traceprobe_free_parse_ctx(struct traceprobe_parse_context *ctx)
{
traceprobe_finish_parse(ctx);
kfree(ctx);
}
DEFINE_FREE(traceprobe_parse_context, struct traceprobe_parse_context *,
if (_T) traceprobe_free_parse_ctx(_T))
extern int traceprobe_split_symbol_offset(char *symbol, long *offset);
int traceprobe_parse_event_name(const char **pevent, const char **pgroup,

View File

@ -8,17 +8,19 @@
#define pr_fmt(fmt) "trace_uprobe: " fmt
#include <linux/bpf-cgroup.h>
#include <linux/security.h>
#include <linux/cleanup.h>
#include <linux/ctype.h>
#include <linux/filter.h>
#include <linux/module.h>
#include <linux/namei.h>
#include <linux/percpu.h>
#include <linux/rculist.h>
#include <linux/security.h>
#include <linux/string.h>
#include <linux/uaccess.h>
#include <linux/uprobes.h>
#include <linux/namei.h>
#include <linux/string.h>
#include <linux/rculist.h>
#include <linux/filter.h>
#include <linux/percpu.h>
#include "trace.h"
#include "trace_dynevent.h"
#include "trace_probe.h"
#include "trace_probe_tmpl.h"
@ -537,15 +539,15 @@ static int register_trace_uprobe(struct trace_uprobe *tu)
*/
static int __trace_uprobe_create(int argc, const char **argv)
{
struct trace_uprobe *tu;
const char *event = NULL, *group = UPROBE_EVENT_SYSTEM;
char *arg, *filename, *rctr, *rctr_end, *tmp;
char buf[MAX_EVENT_NAME_LEN];
char gbuf[MAX_EVENT_NAME_LEN];
enum probe_print_type ptype;
struct path path;
unsigned long offset, ref_ctr_offset;
char *gbuf __free(kfree) = NULL;
char *buf __free(kfree) = NULL;
enum probe_print_type ptype;
struct trace_uprobe *tu;
bool is_return = false;
struct path path;
int i, ret;
ref_ctr_offset = 0;
@ -653,6 +655,10 @@ static int __trace_uprobe_create(int argc, const char **argv)
/* setup a probe */
trace_probe_log_set_index(0);
if (event) {
gbuf = kmalloc(MAX_EVENT_NAME_LEN, GFP_KERNEL);
if (!gbuf)
goto fail_mem;
ret = traceprobe_parse_event_name(&event, &group, gbuf,
event - argv[0]);
if (ret)
@ -664,15 +670,16 @@ static int __trace_uprobe_create(int argc, const char **argv)
char *ptr;
tail = kstrdup(kbasename(filename), GFP_KERNEL);
if (!tail) {
ret = -ENOMEM;
goto fail_address_parse;
}
if (!tail)
goto fail_mem;
ptr = strpbrk(tail, ".-_");
if (ptr)
*ptr = '\0';
buf = kmalloc(MAX_EVENT_NAME_LEN, GFP_KERNEL);
if (!buf)
goto fail_mem;
snprintf(buf, MAX_EVENT_NAME_LEN, "%c_%s_0x%lx", 'p', tail, offset);
event = buf;
kfree(tail);
@ -695,13 +702,16 @@ static int __trace_uprobe_create(int argc, const char **argv)
/* parse arguments */
for (i = 0; i < argc; i++) {
struct traceprobe_parse_context ctx = {
.flags = (is_return ? TPARG_FL_RETURN : 0) | TPARG_FL_USER,
};
struct traceprobe_parse_context *ctx __free(traceprobe_parse_context)
= kzalloc(sizeof(*ctx), GFP_KERNEL);
if (!ctx) {
ret = -ENOMEM;
goto error;
}
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], &ctx);
traceprobe_finish_parse(&ctx);
ret = traceprobe_parse_probe_arg(&tu->tp, i, argv[i], ctx);
if (ret)
goto error;
}
@ -721,6 +731,9 @@ static int __trace_uprobe_create(int argc, const char **argv)
trace_probe_log_clear();
return ret;
fail_mem:
ret = -ENOMEM;
fail_address_parse:
trace_probe_log_clear();
path_put(&path);

View File

@ -16,36 +16,41 @@ ocnt=`cat enabled_functions | wc -l`
echo "f:myevent1 $PLACE" >> dynamic_events
# Make sure the event is attached and is the only one
grep -q $PLACE enabled_functions
cnt=`cat enabled_functions | wc -l`
if [ $cnt -ne $((ocnt + 1)) ]; then
exit_fail
fi
echo "f:myevent2 $PLACE%return" >> dynamic_events
# It should till be the only attached function
cnt=`cat enabled_functions | wc -l`
if [ $cnt -ne $((ocnt + 1)) ]; then
exit_fail
fi
# add another event
echo "f:myevent3 $PLACE2" >> dynamic_events
grep -q $PLACE2 enabled_functions
cnt=`cat enabled_functions | wc -l`
if [ $cnt -ne $((ocnt + 2)) ]; then
exit_fail
fi
grep -q myevent1 dynamic_events
grep -q myevent2 dynamic_events
grep -q myevent3 dynamic_events
test -d events/fprobes/myevent1
test -d events/fprobes/myevent2
echo 1 > events/fprobes/myevent1/enable
# Make sure the event is attached and is the only one
grep -q $PLACE enabled_functions
cnt=`cat enabled_functions | wc -l`
if [ $cnt -ne $((ocnt + 1)) ]; then
exit_fail
fi
echo 1 > events/fprobes/myevent2/enable
# It should till be the only attached function
cnt=`cat enabled_functions | wc -l`
if [ $cnt -ne $((ocnt + 1)) ]; then
exit_fail
fi
echo 1 > events/fprobes/myevent3/enable
# If the function is different, the attached function should be increased
grep -q $PLACE2 enabled_functions
cnt=`cat enabled_functions | wc -l`
if [ $cnt -ne $((ocnt + 2)) ]; then
exit_fail
fi
echo 0 > events/fprobes/myevent2/enable
echo "-:myevent2" >> dynamic_events
grep -q myevent1 dynamic_events
@ -57,6 +62,7 @@ if [ $cnt -ne $((ocnt + 2)) ]; then
exit_fail
fi
echo 0 > events/fprobes/enable
echo > dynamic_events
# Should have none left
@ -67,12 +73,14 @@ fi
echo "f:myevent4 $PLACE" >> dynamic_events
echo 1 > events/fprobes/myevent4/enable
# Should only have one enabled
cnt=`cat enabled_functions | wc -l`
if [ $cnt -ne $((ocnt + 1)) ]; then
exit_fail
fi
echo 0 > events/fprobes/enable
echo > dynamic_events
# Should have none left