Commit 88d01a57 authored by Andrii Nakryiko's avatar Andrii Nakryiko

Merge branch 'libbpf: name-based u[ret]probe attach'

Alan Maguire says:

====================

This patch series focuses on supporting name-based attach - similar
to that supported for kprobes - for uprobe BPF programs.

Currently attach for such probes is done by determining the offset
manually, so the aim is to try and mimic the simplicity of kprobe
attach, making use of uprobe opts to specify a name string.
Patch 1 supports expansion of the binary_path argument used for
bpf_program__attach_uprobe_opts(), allowing it to determine paths
for programs and shared objects automatically, allowing for
specification of "libc.so.6" rather than the full path
"/usr/lib64/libc.so.6".

Patch 2 adds the "func_name" option to allow uprobe attach by
name; the mechanics are described there.

Having name-based support allows us to support auto-attach for
uprobes; patch 3 adds auto-attach support while attempting
to handle backwards-compatibility issues that arise.  The format
supported is

u[ret]probe/binary_path:[raw_offset|function[+offset]]

For example, to attach to libc malloc:

SEC("uprobe//usr/lib64/libc.so.6:malloc")

..or, making use of the path computation mechanisms introduced in patch 1

SEC("uprobe/libc.so.6:malloc")

Finally patch 4 add tests to the attach_probe selftests covering
attach by name, with patch 5 covering skeleton auto-attach.

Changes since v4 [1]:
- replaced strtok_r() usage with copying segments from static char *; avoids
  unneeded string allocation (Andrii, patch 1)
- switched to using access() instead of stat() when checking path-resolved
  binary (Andrii, patch 1)
- removed computation of .plt offset for instrumenting shared library calls
  within binaries.  Firstly it proved too brittle, and secondly it was somewhat
  unintuitive in that this form of instrumentation did not support function+offset
  as the "local function in binary" and "shared library function in shared library"
  cases did.  We can still instrument library calls, just need to do it in the
  library .so (patch 2)
- added binary path logging in cases where it was missing (Andrii, patch 2)
- avoid strlen() calcuation in checking name match (Andrii, patch 2)
- reword comments for func_name option (Andrii, patch 2)
- tightened SEC() name validation to support "u[ret]probe" and fail on other
  permutations that do not support auto-attach (i.e. have u[ret]probe/binary_path:func
  format (Andrii, patch 3)
- fixed selftests to fail independently rather than skip remainder on failure
  (Andrii, patches 4,5)
Changes since v3 [2]:
- reworked variable naming to fit better with libbpf conventions
  (Andrii, patch 2)
- use quoted binary path in log messages (Andrii, patch 2)
- added path determination mechanisms using LD_LIBRARY_PATH/PATH and
  standard locations (patch 1, Andrii)
- changed section lookup to be type+name (if name is specified) to
  simplify use cases (patch 2, Andrii)
- fixed .plt lookup scheme to match symbol table entries with .plt
  index via the .rela.plt table; also fix the incorrect assumption
  that the code in the .plt that does library linking is the same
  size as .plt entries (it just happens to be on x86_64)
- aligned with pluggable section support such that uprobe SEC() names
  that do not conform to auto-attach format do not cause skeleton load
  failure (patch 3, Andrii)
- no longer need to look up absolute path to libraries used by test_progs
  since we have mechanism to determine path automatically
- replaced CHECK()s with ASSERT*()s for attach_probe test (Andrii, patch 4)
- added auto-attach selftests also (Andrii, patch 5)
Changes since RFC [3]:
- used "long" for addresses instead of ssize_t (Andrii, patch 1).
- used gelf_ interfaces to avoid assumptions about 64-bit
  binaries (Andrii, patch 1)
- clarified string matching in symbol table lookups
  (Andrii, patch 1)
- added support for specification of shared object functions
  in a non-shared object binary.  This approach instruments
  the Procedure Linking Table (PLT) - malloc@PLT.
- changed logic in symbol search to check dynamic symbol table
  first, then fall back to symbol table (Andrii, patch 1).
- modified auto-attach string to require "/" separator prior
  to path prefix i.e. uprobe//path/to/binary (Andrii, patch 2)
- modified auto-attach string to use ':' separator (Andrii,
  patch 2)
- modified auto-attach to support raw offset (Andrii, patch 2)
- modified skeleton attach to interpret -ESRCH errors as
  a non-fatal "unable to auto-attach" (Andrii suggested
  -EOPNOTSUPP but my concern was it might collide with other
  instances where that value is returned and reflects a
  failure to attach a to-be-expected attachment rather than
  skip a program that does not present an auto-attachable
  section name. Admittedly -EOPNOTSUPP seems a more natural
  value here).
- moved library path retrieval code to trace_helpers (Andrii,
  patch 3)

[1] https://lore.kernel.org/bpf/1647000658-16149-1-git-send-email-alan.maguire@oracle.com/
[2] https://lore.kernel.org/bpf/1643645554-28723-1-git-send-email-alan.maguire@oracle.com/
[3] https://lore.kernel.org/bpf/1642678950-19584-1-git-send-email-alan.maguire@oracle.com/
====================
Signed-off-by: default avatarAndrii Nakryiko <andrii@kernel.org>
parents 85bf1f51 579c3196
This diff is collapsed.
...@@ -459,9 +459,17 @@ struct bpf_uprobe_opts { ...@@ -459,9 +459,17 @@ struct bpf_uprobe_opts {
__u64 bpf_cookie; __u64 bpf_cookie;
/* uprobe is return probe, invoked at function return time */ /* uprobe is return probe, invoked at function return time */
bool retprobe; bool retprobe;
/* Function name to attach to. Could be an unqualified ("abc") or library-qualified
* "abc@LIBXYZ" name. To specify function entry, func_name should be set while
* func_offset argument to bpf_prog__attach_uprobe_opts() should be 0. To trace an
* offset within a function, specify func_name and use func_offset argument to specify
* offset within the function. Shared library functions must specify the shared library
* binary_path.
*/
const char *func_name;
size_t :0; size_t :0;
}; };
#define bpf_uprobe_opts__last_field retprobe #define bpf_uprobe_opts__last_field func_name
/** /**
* @brief **bpf_program__attach_uprobe()** attaches a BPF program * @brief **bpf_program__attach_uprobe()** attaches a BPF program
......
...@@ -11,15 +11,22 @@ static void trigger_func(void) ...@@ -11,15 +11,22 @@ static void trigger_func(void)
asm volatile (""); asm volatile ("");
} }
/* attach point for byname uprobe */
static void trigger_func2(void)
{
asm volatile ("");
}
void test_attach_probe(void) void test_attach_probe(void)
{ {
DECLARE_LIBBPF_OPTS(bpf_uprobe_opts, uprobe_opts); DECLARE_LIBBPF_OPTS(bpf_uprobe_opts, uprobe_opts);
int duration = 0;
struct bpf_link *kprobe_link, *kretprobe_link; struct bpf_link *kprobe_link, *kretprobe_link;
struct bpf_link *uprobe_link, *uretprobe_link; struct bpf_link *uprobe_link, *uretprobe_link;
struct test_attach_probe* skel; struct test_attach_probe* skel;
ssize_t uprobe_offset, ref_ctr_offset; ssize_t uprobe_offset, ref_ctr_offset;
struct bpf_link *uprobe_err_link;
bool legacy; bool legacy;
char *mem;
/* Check if new-style kprobe/uprobe API is supported. /* Check if new-style kprobe/uprobe API is supported.
* Kernels that support new FD-based kprobe and uprobe BPF attachment * Kernels that support new FD-based kprobe and uprobe BPF attachment
...@@ -43,9 +50,9 @@ void test_attach_probe(void) ...@@ -43,9 +50,9 @@ void test_attach_probe(void)
return; return;
skel = test_attach_probe__open_and_load(); skel = test_attach_probe__open_and_load();
if (CHECK(!skel, "skel_open", "failed to open skeleton\n")) if (!ASSERT_OK_PTR(skel, "skel_open"))
return; return;
if (CHECK(!skel->bss, "check_bss", ".bss wasn't mmap()-ed\n")) if (!ASSERT_OK_PTR(skel->bss, "check_bss"))
goto cleanup; goto cleanup;
kprobe_link = bpf_program__attach_kprobe(skel->progs.handle_kprobe, kprobe_link = bpf_program__attach_kprobe(skel->progs.handle_kprobe,
...@@ -90,25 +97,73 @@ void test_attach_probe(void) ...@@ -90,25 +97,73 @@ void test_attach_probe(void)
goto cleanup; goto cleanup;
skel->links.handle_uretprobe = uretprobe_link; skel->links.handle_uretprobe = uretprobe_link;
/* trigger & validate kprobe && kretprobe */ /* verify auto-attach fails for old-style uprobe definition */
usleep(1); uprobe_err_link = bpf_program__attach(skel->progs.handle_uprobe_byname);
if (!ASSERT_EQ(libbpf_get_error(uprobe_err_link), -EOPNOTSUPP,
"auto-attach should fail for old-style name"))
goto cleanup;
uprobe_opts.func_name = "trigger_func2";
uprobe_opts.retprobe = false;
uprobe_opts.ref_ctr_offset = 0;
skel->links.handle_uprobe_byname =
bpf_program__attach_uprobe_opts(skel->progs.handle_uprobe_byname,
0 /* this pid */,
"/proc/self/exe",
0, &uprobe_opts);
if (!ASSERT_OK_PTR(skel->links.handle_uprobe_byname, "attach_uprobe_byname"))
goto cleanup;
/* verify auto-attach works */
skel->links.handle_uretprobe_byname =
bpf_program__attach(skel->progs.handle_uretprobe_byname);
if (!ASSERT_OK_PTR(skel->links.handle_uretprobe_byname, "attach_uretprobe_byname"))
goto cleanup;
if (CHECK(skel->bss->kprobe_res != 1, "check_kprobe_res", /* test attach by name for a library function, using the library
"wrong kprobe res: %d\n", skel->bss->kprobe_res)) * as the binary argument. libc.so.6 will be resolved via dlopen()/dlinfo().
*/
uprobe_opts.func_name = "malloc";
uprobe_opts.retprobe = false;
skel->links.handle_uprobe_byname2 =
bpf_program__attach_uprobe_opts(skel->progs.handle_uprobe_byname2,
0 /* this pid */,
"libc.so.6",
0, &uprobe_opts);
if (!ASSERT_OK_PTR(skel->links.handle_uprobe_byname2, "attach_uprobe_byname2"))
goto cleanup; goto cleanup;
if (CHECK(skel->bss->kretprobe_res != 2, "check_kretprobe_res",
"wrong kretprobe res: %d\n", skel->bss->kretprobe_res)) uprobe_opts.func_name = "free";
uprobe_opts.retprobe = true;
skel->links.handle_uretprobe_byname2 =
bpf_program__attach_uprobe_opts(skel->progs.handle_uretprobe_byname2,
-1 /* any pid */,
"libc.so.6",
0, &uprobe_opts);
if (!ASSERT_OK_PTR(skel->links.handle_uretprobe_byname2, "attach_uretprobe_byname2"))
goto cleanup; goto cleanup;
/* trigger & validate kprobe && kretprobe */
usleep(1);
/* trigger & validate shared library u[ret]probes attached by name */
mem = malloc(1);
free(mem);
/* trigger & validate uprobe & uretprobe */ /* trigger & validate uprobe & uretprobe */
trigger_func(); trigger_func();
if (CHECK(skel->bss->uprobe_res != 3, "check_uprobe_res", /* trigger & validate uprobe attached by name */
"wrong uprobe res: %d\n", skel->bss->uprobe_res)) trigger_func2();
goto cleanup;
if (CHECK(skel->bss->uretprobe_res != 4, "check_uretprobe_res", ASSERT_EQ(skel->bss->kprobe_res, 1, "check_kprobe_res");
"wrong uretprobe res: %d\n", skel->bss->uretprobe_res)) ASSERT_EQ(skel->bss->kretprobe_res, 2, "check_kretprobe_res");
goto cleanup; ASSERT_EQ(skel->bss->uprobe_res, 3, "check_uprobe_res");
ASSERT_EQ(skel->bss->uretprobe_res, 4, "check_uretprobe_res");
ASSERT_EQ(skel->bss->uprobe_byname_res, 5, "check_uprobe_byname_res");
ASSERT_EQ(skel->bss->uretprobe_byname_res, 6, "check_uretprobe_byname_res");
ASSERT_EQ(skel->bss->uprobe_byname2_res, 7, "check_uprobe_byname2_res");
ASSERT_EQ(skel->bss->uretprobe_byname2_res, 8, "check_uretprobe_byname2_res");
cleanup: cleanup:
test_attach_probe__destroy(skel); test_attach_probe__destroy(skel);
......
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2022, Oracle and/or its affiliates. */
#include <test_progs.h>
#include "test_uprobe_autoattach.skel.h"
/* uprobe attach point */
static void autoattach_trigger_func(void)
{
asm volatile ("");
}
void test_uprobe_autoattach(void)
{
struct test_uprobe_autoattach *skel;
char *mem;
skel = test_uprobe_autoattach__open_and_load();
if (!ASSERT_OK_PTR(skel, "skel_open"))
return;
if (!ASSERT_OK(test_uprobe_autoattach__attach(skel), "skel_attach"))
goto cleanup;
/* trigger & validate uprobe & uretprobe */
autoattach_trigger_func();
/* trigger & validate shared library u[ret]probes attached by name */
mem = malloc(1);
free(mem);
ASSERT_EQ(skel->bss->uprobe_byname_res, 1, "check_uprobe_byname_res");
ASSERT_EQ(skel->bss->uretprobe_byname_res, 2, "check_uretprobe_byname_res");
ASSERT_EQ(skel->bss->uprobe_byname2_res, 3, "check_uprobe_byname2_res");
ASSERT_EQ(skel->bss->uretprobe_byname2_res, 4, "check_uretprobe_byname2_res");
cleanup:
test_uprobe_autoattach__destroy(skel);
}
...@@ -10,6 +10,10 @@ int kprobe_res = 0; ...@@ -10,6 +10,10 @@ int kprobe_res = 0;
int kretprobe_res = 0; int kretprobe_res = 0;
int uprobe_res = 0; int uprobe_res = 0;
int uretprobe_res = 0; int uretprobe_res = 0;
int uprobe_byname_res = 0;
int uretprobe_byname_res = 0;
int uprobe_byname2_res = 0;
int uretprobe_byname2_res = 0;
SEC("kprobe/sys_nanosleep") SEC("kprobe/sys_nanosleep")
int handle_kprobe(struct pt_regs *ctx) int handle_kprobe(struct pt_regs *ctx)
...@@ -25,18 +29,51 @@ int BPF_KRETPROBE(handle_kretprobe) ...@@ -25,18 +29,51 @@ int BPF_KRETPROBE(handle_kretprobe)
return 0; return 0;
} }
SEC("uprobe/trigger_func") SEC("uprobe")
int handle_uprobe(struct pt_regs *ctx) int handle_uprobe(struct pt_regs *ctx)
{ {
uprobe_res = 3; uprobe_res = 3;
return 0; return 0;
} }
SEC("uretprobe/trigger_func") SEC("uretprobe")
int handle_uretprobe(struct pt_regs *ctx) int handle_uretprobe(struct pt_regs *ctx)
{ {
uretprobe_res = 4; uretprobe_res = 4;
return 0; return 0;
} }
SEC("uprobe")
int handle_uprobe_byname(struct pt_regs *ctx)
{
uprobe_byname_res = 5;
return 0;
}
/* use auto-attach format for section definition. */
SEC("uretprobe//proc/self/exe:trigger_func2")
int handle_uretprobe_byname(struct pt_regs *ctx)
{
uretprobe_byname_res = 6;
return 0;
}
SEC("uprobe")
int handle_uprobe_byname2(struct pt_regs *ctx)
{
unsigned int size = PT_REGS_PARM1(ctx);
/* verify malloc size */
if (size == 1)
uprobe_byname2_res = 7;
return 0;
}
SEC("uretprobe")
int handle_uretprobe_byname2(struct pt_regs *ctx)
{
uretprobe_byname2_res = 8;
return 0;
}
char _license[] SEC("license") = "GPL"; char _license[] SEC("license") = "GPL";
...@@ -37,14 +37,14 @@ int handle_kretprobe(struct pt_regs *ctx) ...@@ -37,14 +37,14 @@ int handle_kretprobe(struct pt_regs *ctx)
return 0; return 0;
} }
SEC("uprobe/trigger_func") SEC("uprobe")
int handle_uprobe(struct pt_regs *ctx) int handle_uprobe(struct pt_regs *ctx)
{ {
update(ctx, &uprobe_res); update(ctx, &uprobe_res);
return 0; return 0;
} }
SEC("uretprobe/trigger_func") SEC("uretprobe")
int handle_uretprobe(struct pt_regs *ctx) int handle_uretprobe(struct pt_regs *ctx)
{ {
update(ctx, &uretprobe_res); update(ctx, &uretprobe_res);
......
...@@ -14,7 +14,7 @@ char current_regs[PT_REGS_SIZE] = {}; ...@@ -14,7 +14,7 @@ char current_regs[PT_REGS_SIZE] = {};
char ctx_regs[PT_REGS_SIZE] = {}; char ctx_regs[PT_REGS_SIZE] = {};
int uprobe_res = 0; int uprobe_res = 0;
SEC("uprobe/trigger_func") SEC("uprobe")
int handle_uprobe(struct pt_regs *ctx) int handle_uprobe(struct pt_regs *ctx)
{ {
struct task_struct *current; struct task_struct *current;
......
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2022, Oracle and/or its affiliates. */
#include <linux/ptrace.h>
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
int uprobe_byname_res = 0;
int uretprobe_byname_res = 0;
int uprobe_byname2_res = 0;
int uretprobe_byname2_res = 0;
/* This program cannot auto-attach, but that should not stop other
* programs from attaching.
*/
SEC("uprobe")
int handle_uprobe_noautoattach(struct pt_regs *ctx)
{
return 0;
}
SEC("uprobe//proc/self/exe:autoattach_trigger_func")
int handle_uprobe_byname(struct pt_regs *ctx)
{
uprobe_byname_res = 1;
return 0;
}
SEC("uretprobe//proc/self/exe:autoattach_trigger_func")
int handle_uretprobe_byname(struct pt_regs *ctx)
{
uretprobe_byname_res = 2;
return 0;
}
SEC("uprobe/libc.so.6:malloc")
int handle_uprobe_byname2(struct pt_regs *ctx)
{
uprobe_byname2_res = 3;
return 0;
}
SEC("uretprobe/libc.so.6:free")
int handle_uretprobe_byname2(struct pt_regs *ctx)
{
uretprobe_byname2_res = 4;
return 0;
}
char _license[] SEC("license") = "GPL";
...@@ -54,7 +54,7 @@ int bench_trigger_fmodret(void *ctx) ...@@ -54,7 +54,7 @@ int bench_trigger_fmodret(void *ctx)
return -22; return -22;
} }
SEC("uprobe/self/uprobe_target") SEC("uprobe")
int bench_trigger_uprobe(void *ctx) int bench_trigger_uprobe(void *ctx)
{ {
__sync_add_and_fetch(&hits, 1); __sync_add_and_fetch(&hits, 1);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment