mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-08-05 16:54:27 +00:00
libbpf: Add legacy uprobe attaching support
Similarly to recently added legacy kprobe attach interface support through tracefs, support attaching uprobes using the legacy interface if host kernel doesn't support newer FD-based interface. For uprobes event name consists of "libbpf_" prefix, PID, sanitized binary path and offset within that binary. Structuraly the code is aligned with kprobe logic refactoring in previous patch. struct bpf_link_perf is re-used and all the same legacy_probe_name and legacy_is_retprobe fields are used to ensure proper cleanup on bpf_link__destroy(). Users should be aware, though, that on old kernels which don't support FD-based interface for kprobe/uprobe attachment, if the application crashes before bpf_link__destroy() is called, uprobe legacy events will be left in tracefs. This is the same limitation as with legacy kprobe interfaces. Signed-off-by: Andrii Nakryiko <andrii@kernel.org> Signed-off-by: Alexei Starovoitov <ast@kernel.org> Link: https://lore.kernel.org/bpf/20210921210036.1545557-5-andrii@kernel.org
This commit is contained in:
parent
46ed5fc33d
commit
cc10623c68
1 changed files with 122 additions and 8 deletions
|
@ -9021,6 +9021,7 @@ struct bpf_link_perf {
|
||||||
};
|
};
|
||||||
|
|
||||||
static int remove_kprobe_event_legacy(const char *probe_name, bool retprobe);
|
static int remove_kprobe_event_legacy(const char *probe_name, bool retprobe);
|
||||||
|
static int remove_uprobe_event_legacy(const char *probe_name, bool retprobe);
|
||||||
|
|
||||||
static int bpf_link_perf_detach(struct bpf_link *link)
|
static int bpf_link_perf_detach(struct bpf_link *link)
|
||||||
{
|
{
|
||||||
|
@ -9034,11 +9035,14 @@ static int bpf_link_perf_detach(struct bpf_link *link)
|
||||||
close(perf_link->perf_event_fd);
|
close(perf_link->perf_event_fd);
|
||||||
close(link->fd);
|
close(link->fd);
|
||||||
|
|
||||||
/* legacy kprobe needs to be removed after perf event fd closure */
|
/* legacy uprobe/kprobe needs to be removed after perf event fd closure */
|
||||||
if (perf_link->legacy_probe_name) {
|
if (perf_link->legacy_probe_name) {
|
||||||
if (perf_link->legacy_is_kprobe) {
|
if (perf_link->legacy_is_kprobe) {
|
||||||
err = remove_kprobe_event_legacy(perf_link->legacy_probe_name,
|
err = remove_kprobe_event_legacy(perf_link->legacy_probe_name,
|
||||||
perf_link->legacy_is_retprobe);
|
perf_link->legacy_is_retprobe);
|
||||||
|
} else {
|
||||||
|
err = remove_uprobe_event_legacy(perf_link->legacy_probe_name,
|
||||||
|
perf_link->legacy_is_retprobe);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9450,17 +9454,96 @@ static struct bpf_link *attach_kprobe(const struct bpf_program *prog)
|
||||||
return link;
|
return link;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void gen_uprobe_legacy_event_name(char *buf, size_t buf_sz,
|
||||||
|
const char *binary_path, uint64_t offset)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
snprintf(buf, buf_sz, "libbpf_%u_%s_0x%zx", getpid(), binary_path, (size_t)offset);
|
||||||
|
|
||||||
|
/* sanitize binary_path in the probe name */
|
||||||
|
for (i = 0; buf[i]; i++) {
|
||||||
|
if (!isalnum(buf[i]))
|
||||||
|
buf[i] = '_';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int add_uprobe_event_legacy(const char *probe_name, bool retprobe,
|
||||||
|
const char *binary_path, size_t offset)
|
||||||
|
{
|
||||||
|
const char *file = "/sys/kernel/debug/tracing/uprobe_events";
|
||||||
|
|
||||||
|
return append_to_file(file, "%c:%s/%s %s:0x%zx",
|
||||||
|
retprobe ? 'r' : 'p',
|
||||||
|
retprobe ? "uretprobes" : "uprobes",
|
||||||
|
probe_name, binary_path, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int remove_uprobe_event_legacy(const char *probe_name, bool retprobe)
|
||||||
|
{
|
||||||
|
const char *file = "/sys/kernel/debug/tracing/uprobe_events";
|
||||||
|
|
||||||
|
return append_to_file(file, "-:%s/%s", retprobe ? "uretprobes" : "uprobes", probe_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int determine_uprobe_perf_type_legacy(const char *probe_name, bool retprobe)
|
||||||
|
{
|
||||||
|
char file[512];
|
||||||
|
|
||||||
|
snprintf(file, sizeof(file),
|
||||||
|
"/sys/kernel/debug/tracing/events/%s/%s/id",
|
||||||
|
retprobe ? "uretprobes" : "uprobes", probe_name);
|
||||||
|
|
||||||
|
return parse_uint_from_file(file, "%d\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static int perf_event_uprobe_open_legacy(const char *probe_name, bool retprobe,
|
||||||
|
const char *binary_path, size_t offset, int pid)
|
||||||
|
{
|
||||||
|
struct perf_event_attr attr;
|
||||||
|
int type, pfd, err;
|
||||||
|
|
||||||
|
err = add_uprobe_event_legacy(probe_name, retprobe, binary_path, offset);
|
||||||
|
if (err < 0) {
|
||||||
|
pr_warn("failed to add legacy uprobe event for %s:0x%zx: %d\n",
|
||||||
|
binary_path, (size_t)offset, err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
type = determine_uprobe_perf_type_legacy(probe_name, retprobe);
|
||||||
|
if (type < 0) {
|
||||||
|
pr_warn("failed to determine legacy uprobe event id for %s:0x%zx: %d\n",
|
||||||
|
binary_path, offset, err);
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(&attr, 0, sizeof(attr));
|
||||||
|
attr.size = sizeof(attr);
|
||||||
|
attr.config = type;
|
||||||
|
attr.type = PERF_TYPE_TRACEPOINT;
|
||||||
|
|
||||||
|
pfd = syscall(__NR_perf_event_open, &attr,
|
||||||
|
pid < 0 ? -1 : pid, /* pid */
|
||||||
|
pid == -1 ? 0 : -1, /* cpu */
|
||||||
|
-1 /* group_fd */, PERF_FLAG_FD_CLOEXEC);
|
||||||
|
if (pfd < 0) {
|
||||||
|
err = -errno;
|
||||||
|
pr_warn("legacy uprobe perf_event_open() failed: %d\n", err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
return pfd;
|
||||||
|
}
|
||||||
|
|
||||||
LIBBPF_API struct bpf_link *
|
LIBBPF_API struct bpf_link *
|
||||||
bpf_program__attach_uprobe_opts(const struct bpf_program *prog, pid_t pid,
|
bpf_program__attach_uprobe_opts(const struct bpf_program *prog, pid_t pid,
|
||||||
const char *binary_path, size_t func_offset,
|
const char *binary_path, size_t func_offset,
|
||||||
const struct bpf_uprobe_opts *opts)
|
const struct bpf_uprobe_opts *opts)
|
||||||
{
|
{
|
||||||
DECLARE_LIBBPF_OPTS(bpf_perf_event_opts, pe_opts);
|
DECLARE_LIBBPF_OPTS(bpf_perf_event_opts, pe_opts);
|
||||||
char errmsg[STRERR_BUFSIZE];
|
char errmsg[STRERR_BUFSIZE], *legacy_probe = NULL;
|
||||||
struct bpf_link *link;
|
struct bpf_link *link;
|
||||||
size_t ref_ctr_off;
|
size_t ref_ctr_off;
|
||||||
int pfd, err;
|
int pfd, err;
|
||||||
bool retprobe;
|
bool retprobe, legacy;
|
||||||
|
|
||||||
if (!OPTS_VALID(opts, bpf_uprobe_opts))
|
if (!OPTS_VALID(opts, bpf_uprobe_opts))
|
||||||
return libbpf_err_ptr(-EINVAL);
|
return libbpf_err_ptr(-EINVAL);
|
||||||
|
@ -9469,15 +9552,35 @@ bpf_program__attach_uprobe_opts(const struct bpf_program *prog, pid_t pid,
|
||||||
ref_ctr_off = OPTS_GET(opts, ref_ctr_offset, 0);
|
ref_ctr_off = OPTS_GET(opts, ref_ctr_offset, 0);
|
||||||
pe_opts.bpf_cookie = OPTS_GET(opts, bpf_cookie, 0);
|
pe_opts.bpf_cookie = OPTS_GET(opts, bpf_cookie, 0);
|
||||||
|
|
||||||
pfd = perf_event_open_probe(true /* uprobe */, retprobe, binary_path,
|
legacy = determine_uprobe_perf_type() < 0;
|
||||||
func_offset, pid, ref_ctr_off);
|
if (!legacy) {
|
||||||
|
pfd = perf_event_open_probe(true /* uprobe */, retprobe, binary_path,
|
||||||
|
func_offset, pid, ref_ctr_off);
|
||||||
|
} else {
|
||||||
|
char probe_name[512];
|
||||||
|
|
||||||
|
if (ref_ctr_off)
|
||||||
|
return libbpf_err_ptr(-EINVAL);
|
||||||
|
|
||||||
|
gen_uprobe_legacy_event_name(probe_name, sizeof(probe_name),
|
||||||
|
binary_path, func_offset);
|
||||||
|
|
||||||
|
legacy_probe = strdup(probe_name);
|
||||||
|
if (!legacy_probe)
|
||||||
|
return libbpf_err_ptr(-ENOMEM);
|
||||||
|
|
||||||
|
pfd = perf_event_uprobe_open_legacy(legacy_probe, retprobe,
|
||||||
|
binary_path, func_offset, pid);
|
||||||
|
}
|
||||||
if (pfd < 0) {
|
if (pfd < 0) {
|
||||||
|
err = -errno;
|
||||||
pr_warn("prog '%s': failed to create %s '%s:0x%zx' perf event: %s\n",
|
pr_warn("prog '%s': failed to create %s '%s:0x%zx' perf event: %s\n",
|
||||||
prog->name, retprobe ? "uretprobe" : "uprobe",
|
prog->name, retprobe ? "uretprobe" : "uprobe",
|
||||||
binary_path, func_offset,
|
binary_path, func_offset,
|
||||||
libbpf_strerror_r(pfd, errmsg, sizeof(errmsg)));
|
libbpf_strerror_r(err, errmsg, sizeof(errmsg)));
|
||||||
return libbpf_err_ptr(pfd);
|
goto err_out;
|
||||||
}
|
}
|
||||||
|
|
||||||
link = bpf_program__attach_perf_event_opts(prog, pfd, &pe_opts);
|
link = bpf_program__attach_perf_event_opts(prog, pfd, &pe_opts);
|
||||||
err = libbpf_get_error(link);
|
err = libbpf_get_error(link);
|
||||||
if (err) {
|
if (err) {
|
||||||
|
@ -9486,9 +9589,20 @@ bpf_program__attach_uprobe_opts(const struct bpf_program *prog, pid_t pid,
|
||||||
prog->name, retprobe ? "uretprobe" : "uprobe",
|
prog->name, retprobe ? "uretprobe" : "uprobe",
|
||||||
binary_path, func_offset,
|
binary_path, func_offset,
|
||||||
libbpf_strerror_r(err, errmsg, sizeof(errmsg)));
|
libbpf_strerror_r(err, errmsg, sizeof(errmsg)));
|
||||||
return libbpf_err_ptr(err);
|
goto err_out;
|
||||||
|
}
|
||||||
|
if (legacy) {
|
||||||
|
struct bpf_link_perf *perf_link = container_of(link, struct bpf_link_perf, link);
|
||||||
|
|
||||||
|
perf_link->legacy_probe_name = legacy_probe;
|
||||||
|
perf_link->legacy_is_kprobe = false;
|
||||||
|
perf_link->legacy_is_retprobe = retprobe;
|
||||||
}
|
}
|
||||||
return link;
|
return link;
|
||||||
|
err_out:
|
||||||
|
free(legacy_probe);
|
||||||
|
return libbpf_err_ptr(err);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct bpf_link *bpf_program__attach_uprobe(const struct bpf_program *prog,
|
struct bpf_link *bpf_program__attach_uprobe(const struct bpf_program *prog,
|
||||||
|
|
Loading…
Add table
Reference in a new issue