// SPDX-License-Identifier: GPL-2.0
/*
* Fprobe-based tracing events
* Copyright (C) 2022 Google LLC.
*/
#define pr_fmt(fmt) "trace_fprobe: " fmt
#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"
#include "trace_probe_tmpl.h"
#define FPROBE_EVENT_SYSTEM "fprobes"
#define TRACEPOINT_EVENT_SYSTEM "tracepoints"
#define RETHOOK_MAXACTIVE_MAX 4096
static int trace_fprobe_create(const char *raw_command);
static int trace_fprobe_show(struct seq_file *m, struct dyn_event *ev);
static int trace_fprobe_release(struct dyn_event *ev);
static bool trace_fprobe_is_busy(struct dyn_event *ev);
static bool trace_fprobe_match(const char *system, const char *event,
int argc, const char **argv, struct dyn_event *ev);
static struct dyn_event_operations trace_fprobe_ops = {
.create = trace_fprobe_create,
.show = trace_fprobe_show,
.is_busy = trace_fprobe_is_busy,
.free = trace_fprobe_release,
.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;
bool tprobe;
struct tracepoint_user *tuser;
struct trace_probe tp;
};
static bool is_trace_fprobe(struct dyn_event *ev)
{
return ev->ops == &trace_fprobe_ops;
}
static struct trace_fprobe *to_trace_fprobe(struct dyn_event *ev)
{
return container_of(ev, struct trace_fprobe, devent);
}
/**
* for_each_trace_fprobe - iterate over the trace_fprobe list
* @pos: the struct trace_fprobe * for each entry
* @dpos: the struct dyn_event * to use as a loop cursor
*/
#define for_each_trace_fprobe(pos, dpos) \
for_each_dyn_event(dpos) \
if (is_trace_fprobe(dpos) && (pos = to_trace_fprobe(dpos)))
static bool trace_fprobe_is_return(struct trace_fprobe *tf)
{
return tf->fp.exit_handler != NULL;
}
static bool trace_fprobe_is_tracepoint(struct trace_fprobe *tf)
{
return tf->tprobe;
}
static const char *trace_fprobe_symbol(struct trace_fprobe *tf)
{
return tf->symbol ? tf->symbol : "unknown";
}
static bool trace_fprobe_is_busy(struct dyn_event *ev)
{
struct trace_fprobe *tf = to_trace_fprobe(ev);
return trace_prob
|