Skip to content

Commit 9a7ef9f

Browse files
author
Alexei Starovoitov
committed
Merge branch 'Add libbpf support for USDTs'
Andrii Nakryiko says: ==================== Add libbpf support for USDT (User Statically-Defined Tracing) probes. USDTs is important part of tracing, and BPF, ecosystem, widely used in mission-critical production applications for observability, performance analysis, and debugging. And while USDTs themselves are pretty complicated abstraction built on top of uprobes, for end-users USDT is as natural a primitive as uprobes themselves. And thus it's important for libbpf to provide best possible user experience when it comes to build tracing applications relying on USDTs. USDTs historically presented a lot of challenges for libbpf's no compilation-on-the-fly general approach to BPF tracing. BCC utilizes power of on-the-fly source code generation and compilation using its embedded Clang toolchain, which was impractical for more lightweight and thus more rigid libbpf-based approach. But still, with enough diligence and BPF cookies it's possible to implement USDT support that feels as natural as tracing any uprobe. This patch set is the culmination of such effort to add libbpf USDT support following the spirit and philosophy of BPF CO-RE (even though it's not inherently relying on BPF CO-RE much, see patch #1 for some notes regarding this). Each respective patch has enough details and explanations, so I won't go into details here. In the end, I think the overall usability of libbpf's USDT support *exceeds* the status quo set by BCC due to the elimination of awkward runtime USDT supporting code generation. It also exceeds BCC's capabilities due to the use of BPF cookie. This eliminates the need to determine a USDT call site (and thus specifics about how exactly to fetch arguments) based on its *absolute IP address*, which is impossible with shared libraries if no PID is specified (as we then just *can't* know absolute IP at which shared library is loaded, because it might be different for each process). With BPF cookie this is not a problem as we record "call site ID" directly in a BPF cookie value. This makes it possible to do a system-wide tracing of a USDT defined in a shared library. Think about tracing some USDT in libc across any process in the system, both running at the time of attachment and all the new processes started *afterwards*. This is a very powerful capability that allows more efficient observability and tracing tooling. Once this functionality lands, the plan is to extend libbpf-bootstrap ([0]) with an USDT example. It will also become possible to start converting BCC tools that rely on USDTs to their libbpf-based counterparts ([1]). It's worth noting that preliminary version of this code was currently used and tested in production code running fleet-wide observability toolkit. Libbpf functionality is broken down into 5 mostly logically independent parts, for ease of reviewing: - patch #1 adds BPF-side implementation; - patch #2 adds user-space APIs and wires bpf_link for USDTs; - patch #3 adds the most mundate pieces: handling ELF, parsing USDT notes, dealing with memory segments, relative vs absolute addresses, etc; - patch #4 adds internal ID allocation and setting up/tearing down of BPF-side state (spec and IP-to-ID mapping); - patch #5 implements x86/x86-64-specific logic of parsing USDT argument specifications; - patch #6 adds testing of various basic aspects of handling of USDT; - patch #7 extends the set of tests with more combinations of semaphore, executable vs shared library, and PID filter options. [0] https://github.com/libbpf/libbpf-bootstrap [1] https://github.com/iovisor/bcc/tree/master/libbpf-tools v2->v3: - fix typos, leave link to systemtap doc, acks, etc (Dave); - include sys/sdt.h to avoid extra system-wide package dependencies; v1->v2: - huge high-level comment describing how all the moving parts fit together (Alan, Alexei); - switched from `__hidden __weak` to `static inline __noinline` for now, as there is a bug in BPF linker breaking final BPF object file due to invalid .BTF.ext data; I want to fix it separately at which point I'll switch back to __hidden __weak again. The fix isn't trivial, so I don't want to block on that. Same for __weak variable lookup bug that Henqi reported. - various fixes and improvements, addressing other feedback (Alan, Hengqi); Cc: Alan Maguire <alan.maguire@oracle.com> Cc: Dave Marchevsky <davemarchevsky@fb.com> Cc: Hengqi Chen <hengqi.chen@gmail.com> ==================== Signed-off-by: Alexei Starovoitov <ast@kernel.org>
2 parents 5681893 + 00a0fa2 commit 9a7ef9f

19 files changed

+2938
-25
lines changed

tools/lib/bpf/Build

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
libbpf-y := libbpf.o bpf.o nlattr.o btf.o libbpf_errno.o str_error.o \
22
netlink.o bpf_prog_linfo.o libbpf_probes.o xsk.o hashmap.o \
3-
btf_dump.o ringbuf.o strset.o linker.o gen_loader.o relo_core.o
3+
btf_dump.o ringbuf.o strset.o linker.o gen_loader.o relo_core.o \
4+
usdt.o

tools/lib/bpf/Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ install_lib: all_cmd
239239

240240
SRC_HDRS := bpf.h libbpf.h btf.h libbpf_common.h libbpf_legacy.h xsk.h \
241241
bpf_helpers.h bpf_tracing.h bpf_endian.h bpf_core_read.h \
242-
skel_internal.h libbpf_version.h
242+
skel_internal.h libbpf_version.h usdt.bpf.h
243243
GEN_HDRS := $(BPF_GENERATED)
244244

245245
INSTALL_PFX := $(DESTDIR)$(prefix)/include/bpf

tools/lib/bpf/libbpf.c

+105-10
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,8 @@ struct elf_state {
483483
int st_ops_shndx;
484484
};
485485

486+
struct usdt_manager;
487+
486488
struct bpf_object {
487489
char name[BPF_OBJ_NAME_LEN];
488490
char license[64];
@@ -545,6 +547,8 @@ struct bpf_object {
545547
size_t fd_array_cap;
546548
size_t fd_array_cnt;
547549

550+
struct usdt_manager *usdt_man;
551+
548552
char path[];
549553
};
550554

@@ -4678,6 +4682,18 @@ static int probe_perf_link(void)
46784682
return link_fd < 0 && err == -EBADF;
46794683
}
46804684

4685+
static int probe_kern_bpf_cookie(void)
4686+
{
4687+
struct bpf_insn insns[] = {
4688+
BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_get_attach_cookie),
4689+
BPF_EXIT_INSN(),
4690+
};
4691+
int ret, insn_cnt = ARRAY_SIZE(insns);
4692+
4693+
ret = bpf_prog_load(BPF_PROG_TYPE_KPROBE, NULL, "GPL", insns, insn_cnt, NULL);
4694+
return probe_fd(ret);
4695+
}
4696+
46814697
enum kern_feature_result {
46824698
FEAT_UNKNOWN = 0,
46834699
FEAT_SUPPORTED = 1,
@@ -4740,6 +4756,9 @@ static struct kern_feature_desc {
47404756
[FEAT_MEMCG_ACCOUNT] = {
47414757
"memcg-based memory accounting", probe_memcg_account,
47424758
},
4759+
[FEAT_BPF_COOKIE] = {
4760+
"BPF cookie support", probe_kern_bpf_cookie,
4761+
},
47434762
};
47444763

47454764
bool kernel_supports(const struct bpf_object *obj, enum kern_feature_id feat_id)
@@ -8200,6 +8219,9 @@ void bpf_object__close(struct bpf_object *obj)
82008219
if (obj->clear_priv)
82018220
obj->clear_priv(obj, obj->priv);
82028221

8222+
usdt_manager_free(obj->usdt_man);
8223+
obj->usdt_man = NULL;
8224+
82038225
bpf_gen__free(obj->gen_loader);
82048226
bpf_object__elf_finish(obj);
82058227
bpf_object_unload(obj);
@@ -8631,6 +8653,7 @@ int bpf_program__set_log_buf(struct bpf_program *prog, char *log_buf, size_t log
86318653

86328654
static int attach_kprobe(const struct bpf_program *prog, long cookie, struct bpf_link **link);
86338655
static int attach_uprobe(const struct bpf_program *prog, long cookie, struct bpf_link **link);
8656+
static int attach_usdt(const struct bpf_program *prog, long cookie, struct bpf_link **link);
86348657
static int attach_tp(const struct bpf_program *prog, long cookie, struct bpf_link **link);
86358658
static int attach_raw_tp(const struct bpf_program *prog, long cookie, struct bpf_link **link);
86368659
static int attach_trace(const struct bpf_program *prog, long cookie, struct bpf_link **link);
@@ -8648,6 +8671,7 @@ static const struct bpf_sec_def section_defs[] = {
86488671
SEC_DEF("uretprobe+", KPROBE, 0, SEC_NONE, attach_uprobe),
86498672
SEC_DEF("kprobe.multi/", KPROBE, BPF_TRACE_KPROBE_MULTI, SEC_NONE, attach_kprobe_multi),
86508673
SEC_DEF("kretprobe.multi/", KPROBE, BPF_TRACE_KPROBE_MULTI, SEC_NONE, attach_kprobe_multi),
8674+
SEC_DEF("usdt+", KPROBE, 0, SEC_NONE, attach_usdt),
86518675
SEC_DEF("tc", SCHED_CLS, 0, SEC_NONE),
86528676
SEC_DEF("classifier", SCHED_CLS, 0, SEC_NONE | SEC_SLOPPY_PFX | SEC_DEPRECATED),
86538677
SEC_DEF("action", SCHED_ACT, 0, SEC_NONE | SEC_SLOPPY_PFX),
@@ -9693,14 +9717,6 @@ int bpf_prog_load_deprecated(const char *file, enum bpf_prog_type type,
96939717
return bpf_prog_load_xattr2(&attr, pobj, prog_fd);
96949718
}
96959719

9696-
struct bpf_link {
9697-
int (*detach)(struct bpf_link *link);
9698-
void (*dealloc)(struct bpf_link *link);
9699-
char *pin_path; /* NULL, if not pinned */
9700-
int fd; /* hook FD, -1 if not applicable */
9701-
bool disconnected;
9702-
};
9703-
97049720
/* Replace link's underlying BPF program with the new one */
97059721
int bpf_link__update_program(struct bpf_link *link, struct bpf_program *prog)
97069722
{
@@ -10810,8 +10826,8 @@ bpf_program__attach_uprobe_opts(const struct bpf_program *prog, pid_t pid,
1081010826
err = resolve_full_path(binary_path, full_binary_path,
1081110827
sizeof(full_binary_path));
1081210828
if (err) {
10813-
pr_warn("prog '%s': failed to resolve full path for '%s'\n",
10814-
prog->name, binary_path);
10829+
pr_warn("prog '%s': failed to resolve full path for '%s': %d\n",
10830+
prog->name, binary_path, err);
1081510831
return libbpf_err_ptr(err);
1081610832
}
1081710833
binary_path = full_binary_path;
@@ -10963,6 +10979,85 @@ struct bpf_link *bpf_program__attach_uprobe(const struct bpf_program *prog,
1096310979
return bpf_program__attach_uprobe_opts(prog, pid, binary_path, func_offset, &opts);
1096410980
}
1096510981

10982+
struct bpf_link *bpf_program__attach_usdt(const struct bpf_program *prog,
10983+
pid_t pid, const char *binary_path,
10984+
const char *usdt_provider, const char *usdt_name,
10985+
const struct bpf_usdt_opts *opts)
10986+
{
10987+
char resolved_path[512];
10988+
struct bpf_object *obj = prog->obj;
10989+
struct bpf_link *link;
10990+
long usdt_cookie;
10991+
int err;
10992+
10993+
if (!OPTS_VALID(opts, bpf_uprobe_opts))
10994+
return libbpf_err_ptr(-EINVAL);
10995+
10996+
if (bpf_program__fd(prog) < 0) {
10997+
pr_warn("prog '%s': can't attach BPF program w/o FD (did you load it?)\n",
10998+
prog->name);
10999+
return libbpf_err_ptr(-EINVAL);
11000+
}
11001+
11002+
if (!strchr(binary_path, '/')) {
11003+
err = resolve_full_path(binary_path, resolved_path, sizeof(resolved_path));
11004+
if (err) {
11005+
pr_warn("prog '%s': failed to resolve full path for '%s': %d\n",
11006+
prog->name, binary_path, err);
11007+
return libbpf_err_ptr(err);
11008+
}
11009+
binary_path = resolved_path;
11010+
}
11011+
11012+
/* USDT manager is instantiated lazily on first USDT attach. It will
11013+
* be destroyed together with BPF object in bpf_object__close().
11014+
*/
11015+
if (IS_ERR(obj->usdt_man))
11016+
return libbpf_ptr(obj->usdt_man);
11017+
if (!obj->usdt_man) {
11018+
obj->usdt_man = usdt_manager_new(obj);
11019+
if (IS_ERR(obj->usdt_man))
11020+
return libbpf_ptr(obj->usdt_man);
11021+
}
11022+
11023+
usdt_cookie = OPTS_GET(opts, usdt_cookie, 0);
11024+
link = usdt_manager_attach_usdt(obj->usdt_man, prog, pid, binary_path,
11025+
usdt_provider, usdt_name, usdt_cookie);
11026+
err = libbpf_get_error(link);
11027+
if (err)
11028+
return libbpf_err_ptr(err);
11029+
return link;
11030+
}
11031+
11032+
static int attach_usdt(const struct bpf_program *prog, long cookie, struct bpf_link **link)
11033+
{
11034+
char *path = NULL, *provider = NULL, *name = NULL;
11035+
const char *sec_name;
11036+
int n, err;
11037+
11038+
sec_name = bpf_program__section_name(prog);
11039+
if (strcmp(sec_name, "usdt") == 0) {
11040+
/* no auto-attach for just SEC("usdt") */
11041+
*link = NULL;
11042+
return 0;
11043+
}
11044+
11045+
n = sscanf(sec_name, "usdt/%m[^:]:%m[^:]:%m[^:]", &path, &provider, &name);
11046+
if (n != 3) {
11047+
pr_warn("invalid section '%s', expected SEC(\"usdt/<path>:<provider>:<name>\")\n",
11048+
sec_name);
11049+
err = -EINVAL;
11050+
} else {
11051+
*link = bpf_program__attach_usdt(prog, -1 /* any process */, path,
11052+
provider, name, NULL);
11053+
err = libbpf_get_error(*link);
11054+
}
11055+
free(path);
11056+
free(provider);
11057+
free(name);
11058+
return err;
11059+
}
11060+
1096611061
static int determine_tracepoint_id(const char *tp_category,
1096711062
const char *tp_name)
1096811063
{

tools/lib/bpf/libbpf.h

+31
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,37 @@ bpf_program__attach_uprobe_opts(const struct bpf_program *prog, pid_t pid,
511511
const char *binary_path, size_t func_offset,
512512
const struct bpf_uprobe_opts *opts);
513513

514+
struct bpf_usdt_opts {
515+
/* size of this struct, for forward/backward compatibility */
516+
size_t sz;
517+
/* custom user-provided value accessible through usdt_cookie() */
518+
__u64 usdt_cookie;
519+
size_t :0;
520+
};
521+
#define bpf_usdt_opts__last_field usdt_cookie
522+
523+
/**
524+
* @brief **bpf_program__attach_usdt()** is just like
525+
* bpf_program__attach_uprobe_opts() except it covers USDT (User-space
526+
* Statically Defined Tracepoint) attachment, instead of attaching to
527+
* user-space function entry or exit.
528+
*
529+
* @param prog BPF program to attach
530+
* @param pid Process ID to attach the uprobe to, 0 for self (own process),
531+
* -1 for all processes
532+
* @param binary_path Path to binary that contains provided USDT probe
533+
* @param usdt_provider USDT provider name
534+
* @param usdt_name USDT probe name
535+
* @param opts Options for altering program attachment
536+
* @return Reference to the newly created BPF link; or NULL is returned on error,
537+
* error code is stored in errno
538+
*/
539+
LIBBPF_API struct bpf_link *
540+
bpf_program__attach_usdt(const struct bpf_program *prog,
541+
pid_t pid, const char *binary_path,
542+
const char *usdt_provider, const char *usdt_name,
543+
const struct bpf_usdt_opts *opts);
544+
514545
struct bpf_tracepoint_opts {
515546
/* size of this struct, for forward/backward compatiblity */
516547
size_t sz;

tools/lib/bpf/libbpf.map

+1
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,7 @@ LIBBPF_0.8.0 {
444444
global:
445445
bpf_object__destroy_subskeleton;
446446
bpf_object__open_subskeleton;
447+
bpf_program__attach_usdt;
447448
libbpf_register_prog_handler;
448449
libbpf_unregister_prog_handler;
449450
bpf_program__attach_kprobe_multi_opts;

tools/lib/bpf/libbpf_internal.h

+19
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,15 @@ do { \
148148
#ifndef __has_builtin
149149
#define __has_builtin(x) 0
150150
#endif
151+
152+
struct bpf_link {
153+
int (*detach)(struct bpf_link *link);
154+
void (*dealloc)(struct bpf_link *link);
155+
char *pin_path; /* NULL, if not pinned */
156+
int fd; /* hook FD, -1 if not applicable */
157+
bool disconnected;
158+
};
159+
151160
/*
152161
* Re-implement glibc's reallocarray() for libbpf internal-only use.
153162
* reallocarray(), unfortunately, is not available in all versions of glibc,
@@ -329,6 +338,8 @@ enum kern_feature_id {
329338
FEAT_BTF_TYPE_TAG,
330339
/* memcg-based accounting for BPF maps and progs */
331340
FEAT_MEMCG_ACCOUNT,
341+
/* BPF cookie (bpf_get_attach_cookie() BPF helper) support */
342+
FEAT_BPF_COOKIE,
332343
__FEAT_CNT,
333344
};
334345

@@ -543,4 +554,12 @@ int bpf_core_add_cands(struct bpf_core_cand *local_cand,
543554
struct bpf_core_cand_list *cands);
544555
void bpf_core_free_cands(struct bpf_core_cand_list *cands);
545556

557+
struct usdt_manager *usdt_manager_new(struct bpf_object *obj);
558+
void usdt_manager_free(struct usdt_manager *man);
559+
struct bpf_link * usdt_manager_attach_usdt(struct usdt_manager *man,
560+
const struct bpf_program *prog,
561+
pid_t pid, const char *path,
562+
const char *usdt_provider, const char *usdt_name,
563+
long usdt_cookie);
564+
546565
#endif /* __LIBBPF_LIBBPF_INTERNAL_H */

0 commit comments

Comments
 (0)