Commit 9a7ef9f8 authored by Alexei Starovoitov's avatar Alexei Starovoitov

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: default avatarAlexei Starovoitov <ast@kernel.org>
parents 56818931 00a0fa2d
libbpf-y := libbpf.o bpf.o nlattr.o btf.o libbpf_errno.o str_error.o \
netlink.o bpf_prog_linfo.o libbpf_probes.o xsk.o hashmap.o \
btf_dump.o ringbuf.o strset.o linker.o gen_loader.o relo_core.o
btf_dump.o ringbuf.o strset.o linker.o gen_loader.o relo_core.o \
usdt.o
......@@ -239,7 +239,7 @@ install_lib: all_cmd
SRC_HDRS := bpf.h libbpf.h btf.h libbpf_common.h libbpf_legacy.h xsk.h \
bpf_helpers.h bpf_tracing.h bpf_endian.h bpf_core_read.h \
skel_internal.h libbpf_version.h
skel_internal.h libbpf_version.h usdt.bpf.h
GEN_HDRS := $(BPF_GENERATED)
INSTALL_PFX := $(DESTDIR)$(prefix)/include/bpf
......
......@@ -483,6 +483,8 @@ struct elf_state {
int st_ops_shndx;
};
struct usdt_manager;
struct bpf_object {
char name[BPF_OBJ_NAME_LEN];
char license[64];
......@@ -545,6 +547,8 @@ struct bpf_object {
size_t fd_array_cap;
size_t fd_array_cnt;
struct usdt_manager *usdt_man;
char path[];
};
......@@ -4678,6 +4682,18 @@ static int probe_perf_link(void)
return link_fd < 0 && err == -EBADF;
}
static int probe_kern_bpf_cookie(void)
{
struct bpf_insn insns[] = {
BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_get_attach_cookie),
BPF_EXIT_INSN(),
};
int ret, insn_cnt = ARRAY_SIZE(insns);
ret = bpf_prog_load(BPF_PROG_TYPE_KPROBE, NULL, "GPL", insns, insn_cnt, NULL);
return probe_fd(ret);
}
enum kern_feature_result {
FEAT_UNKNOWN = 0,
FEAT_SUPPORTED = 1,
......@@ -4740,6 +4756,9 @@ static struct kern_feature_desc {
[FEAT_MEMCG_ACCOUNT] = {
"memcg-based memory accounting", probe_memcg_account,
},
[FEAT_BPF_COOKIE] = {
"BPF cookie support", probe_kern_bpf_cookie,
},
};
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)
if (obj->clear_priv)
obj->clear_priv(obj, obj->priv);
usdt_manager_free(obj->usdt_man);
obj->usdt_man = NULL;
bpf_gen__free(obj->gen_loader);
bpf_object__elf_finish(obj);
bpf_object_unload(obj);
......@@ -8631,6 +8653,7 @@ int bpf_program__set_log_buf(struct bpf_program *prog, char *log_buf, size_t log
static int attach_kprobe(const struct bpf_program *prog, long cookie, struct bpf_link **link);
static int attach_uprobe(const struct bpf_program *prog, long cookie, struct bpf_link **link);
static int attach_usdt(const struct bpf_program *prog, long cookie, struct bpf_link **link);
static int attach_tp(const struct bpf_program *prog, long cookie, struct bpf_link **link);
static int attach_raw_tp(const struct bpf_program *prog, long cookie, struct bpf_link **link);
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[] = {
SEC_DEF("uretprobe+", KPROBE, 0, SEC_NONE, attach_uprobe),
SEC_DEF("kprobe.multi/", KPROBE, BPF_TRACE_KPROBE_MULTI, SEC_NONE, attach_kprobe_multi),
SEC_DEF("kretprobe.multi/", KPROBE, BPF_TRACE_KPROBE_MULTI, SEC_NONE, attach_kprobe_multi),
SEC_DEF("usdt+", KPROBE, 0, SEC_NONE, attach_usdt),
SEC_DEF("tc", SCHED_CLS, 0, SEC_NONE),
SEC_DEF("classifier", SCHED_CLS, 0, SEC_NONE | SEC_SLOPPY_PFX | SEC_DEPRECATED),
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,
return bpf_prog_load_xattr2(&attr, pobj, prog_fd);
}
struct bpf_link {
int (*detach)(struct bpf_link *link);
void (*dealloc)(struct bpf_link *link);
char *pin_path; /* NULL, if not pinned */
int fd; /* hook FD, -1 if not applicable */
bool disconnected;
};
/* Replace link's underlying BPF program with the new one */
int bpf_link__update_program(struct bpf_link *link, struct bpf_program *prog)
{
......@@ -10810,8 +10826,8 @@ bpf_program__attach_uprobe_opts(const struct bpf_program *prog, pid_t pid,
err = resolve_full_path(binary_path, full_binary_path,
sizeof(full_binary_path));
if (err) {
pr_warn("prog '%s': failed to resolve full path for '%s'\n",
prog->name, binary_path);
pr_warn("prog '%s': failed to resolve full path for '%s': %d\n",
prog->name, binary_path, err);
return libbpf_err_ptr(err);
}
binary_path = full_binary_path;
......@@ -10963,6 +10979,85 @@ struct bpf_link *bpf_program__attach_uprobe(const struct bpf_program *prog,
return bpf_program__attach_uprobe_opts(prog, pid, binary_path, func_offset, &opts);
}
struct bpf_link *bpf_program__attach_usdt(const struct bpf_program *prog,
pid_t pid, const char *binary_path,
const char *usdt_provider, const char *usdt_name,
const struct bpf_usdt_opts *opts)
{
char resolved_path[512];
struct bpf_object *obj = prog->obj;
struct bpf_link *link;
long usdt_cookie;
int err;
if (!OPTS_VALID(opts, bpf_uprobe_opts))
return libbpf_err_ptr(-EINVAL);
if (bpf_program__fd(prog) < 0) {
pr_warn("prog '%s': can't attach BPF program w/o FD (did you load it?)\n",
prog->name);
return libbpf_err_ptr(-EINVAL);
}
if (!strchr(binary_path, '/')) {
err = resolve_full_path(binary_path, resolved_path, sizeof(resolved_path));
if (err) {
pr_warn("prog '%s': failed to resolve full path for '%s': %d\n",
prog->name, binary_path, err);
return libbpf_err_ptr(err);
}
binary_path = resolved_path;
}
/* USDT manager is instantiated lazily on first USDT attach. It will
* be destroyed together with BPF object in bpf_object__close().
*/
if (IS_ERR(obj->usdt_man))
return libbpf_ptr(obj->usdt_man);
if (!obj->usdt_man) {
obj->usdt_man = usdt_manager_new(obj);
if (IS_ERR(obj->usdt_man))
return libbpf_ptr(obj->usdt_man);
}
usdt_cookie = OPTS_GET(opts, usdt_cookie, 0);
link = usdt_manager_attach_usdt(obj->usdt_man, prog, pid, binary_path,
usdt_provider, usdt_name, usdt_cookie);
err = libbpf_get_error(link);
if (err)
return libbpf_err_ptr(err);
return link;
}
static int attach_usdt(const struct bpf_program *prog, long cookie, struct bpf_link **link)
{
char *path = NULL, *provider = NULL, *name = NULL;
const char *sec_name;
int n, err;
sec_name = bpf_program__section_name(prog);
if (strcmp(sec_name, "usdt") == 0) {
/* no auto-attach for just SEC("usdt") */
*link = NULL;
return 0;
}
n = sscanf(sec_name, "usdt/%m[^:]:%m[^:]:%m[^:]", &path, &provider, &name);
if (n != 3) {
pr_warn("invalid section '%s', expected SEC(\"usdt/<path>:<provider>:<name>\")\n",
sec_name);
err = -EINVAL;
} else {
*link = bpf_program__attach_usdt(prog, -1 /* any process */, path,
provider, name, NULL);
err = libbpf_get_error(*link);
}
free(path);
free(provider);
free(name);
return err;
}
static int determine_tracepoint_id(const char *tp_category,
const char *tp_name)
{
......
......@@ -511,6 +511,37 @@ bpf_program__attach_uprobe_opts(const struct bpf_program *prog, pid_t pid,
const char *binary_path, size_t func_offset,
const struct bpf_uprobe_opts *opts);
struct bpf_usdt_opts {
/* size of this struct, for forward/backward compatibility */
size_t sz;
/* custom user-provided value accessible through usdt_cookie() */
__u64 usdt_cookie;
size_t :0;
};
#define bpf_usdt_opts__last_field usdt_cookie
/**
* @brief **bpf_program__attach_usdt()** is just like
* bpf_program__attach_uprobe_opts() except it covers USDT (User-space
* Statically Defined Tracepoint) attachment, instead of attaching to
* user-space function entry or exit.
*
* @param prog BPF program to attach
* @param pid Process ID to attach the uprobe to, 0 for self (own process),
* -1 for all processes
* @param binary_path Path to binary that contains provided USDT probe
* @param usdt_provider USDT provider name
* @param usdt_name USDT probe name
* @param opts Options for altering program attachment
* @return Reference to the newly created BPF link; or NULL is returned on error,
* error code is stored in errno
*/
LIBBPF_API struct bpf_link *
bpf_program__attach_usdt(const struct bpf_program *prog,
pid_t pid, const char *binary_path,
const char *usdt_provider, const char *usdt_name,
const struct bpf_usdt_opts *opts);
struct bpf_tracepoint_opts {
/* size of this struct, for forward/backward compatiblity */
size_t sz;
......
......@@ -444,6 +444,7 @@ LIBBPF_0.8.0 {
global:
bpf_object__destroy_subskeleton;
bpf_object__open_subskeleton;
bpf_program__attach_usdt;
libbpf_register_prog_handler;
libbpf_unregister_prog_handler;
bpf_program__attach_kprobe_multi_opts;
......
......@@ -148,6 +148,15 @@ do { \
#ifndef __has_builtin
#define __has_builtin(x) 0
#endif
struct bpf_link {
int (*detach)(struct bpf_link *link);
void (*dealloc)(struct bpf_link *link);
char *pin_path; /* NULL, if not pinned */
int fd; /* hook FD, -1 if not applicable */
bool disconnected;
};
/*
* Re-implement glibc's reallocarray() for libbpf internal-only use.
* reallocarray(), unfortunately, is not available in all versions of glibc,
......@@ -329,6 +338,8 @@ enum kern_feature_id {
FEAT_BTF_TYPE_TAG,
/* memcg-based accounting for BPF maps and progs */
FEAT_MEMCG_ACCOUNT,
/* BPF cookie (bpf_get_attach_cookie() BPF helper) support */
FEAT_BPF_COOKIE,
__FEAT_CNT,
};
......@@ -543,4 +554,12 @@ int bpf_core_add_cands(struct bpf_core_cand *local_cand,
struct bpf_core_cand_list *cands);
void bpf_core_free_cands(struct bpf_core_cand_list *cands);
struct usdt_manager *usdt_manager_new(struct bpf_object *obj);
void usdt_manager_free(struct usdt_manager *man);
struct bpf_link * usdt_manager_attach_usdt(struct usdt_manager *man,
const struct bpf_program *prog,
pid_t pid, const char *path,
const char *usdt_provider, const char *usdt_name,
long usdt_cookie);
#endif /* __LIBBPF_LIBBPF_INTERNAL_H */
/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
/* Copyright (c) 2022 Meta Platforms, Inc. and affiliates. */
#ifndef __USDT_BPF_H__
#define __USDT_BPF_H__
#include <linux/errno.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_core_read.h>
/* Below types and maps are internal implementation details of libbpf's USDT
* support and are subjects to change. Also, bpf_usdt_xxx() API helpers should
* be considered an unstable API as well and might be adjusted based on user
* feedback from using libbpf's USDT support in production.
*/
/* User can override BPF_USDT_MAX_SPEC_CNT to change default size of internal
* map that keeps track of USDT argument specifications. This might be
* necessary if there are a lot of USDT attachments.
*/
#ifndef BPF_USDT_MAX_SPEC_CNT
#define BPF_USDT_MAX_SPEC_CNT 256
#endif
/* User can override BPF_USDT_MAX_IP_CNT to change default size of internal
* map that keeps track of IP (memory address) mapping to USDT argument
* specification.
* Note, if kernel supports BPF cookies, this map is not used and could be
* resized all the way to 1 to save a bit of memory.
*/
#ifndef BPF_USDT_MAX_IP_CNT
#define BPF_USDT_MAX_IP_CNT (4 * BPF_USDT_MAX_SPEC_CNT)
#endif
/* We use BPF CO-RE to detect support for BPF cookie from BPF side. This is
* the only dependency on CO-RE, so if it's undesirable, user can override
* BPF_USDT_HAS_BPF_COOKIE to specify whether to BPF cookie is supported or not.
*/
#ifndef BPF_USDT_HAS_BPF_COOKIE
#define BPF_USDT_HAS_BPF_COOKIE \
bpf_core_enum_value_exists(enum bpf_func_id___usdt, BPF_FUNC_get_attach_cookie___usdt)
#endif
enum __bpf_usdt_arg_type {
BPF_USDT_ARG_CONST,
BPF_USDT_ARG_REG,
BPF_USDT_ARG_REG_DEREF,
};
struct __bpf_usdt_arg_spec {
/* u64 scalar interpreted depending on arg_type, see below */
__u64 val_off;
/* arg location case, see bpf_udst_arg() for details */
enum __bpf_usdt_arg_type arg_type;
/* offset of referenced register within struct pt_regs */
short reg_off;
/* whether arg should be interpreted as signed value */
bool arg_signed;
/* number of bits that need to be cleared and, optionally,
* sign-extended to cast arguments that are 1, 2, or 4 bytes
* long into final 8-byte u64/s64 value returned to user
*/
char arg_bitshift;
};
/* should match USDT_MAX_ARG_CNT in usdt.c exactly */
#define BPF_USDT_MAX_ARG_CNT 12
struct __bpf_usdt_spec {
struct __bpf_usdt_arg_spec args[BPF_USDT_MAX_ARG_CNT];
__u64 usdt_cookie;
short arg_cnt;
};
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__uint(max_entries, BPF_USDT_MAX_SPEC_CNT);
__type(key, int);
__type(value, struct __bpf_usdt_spec);
} __bpf_usdt_specs SEC(".maps") __weak;
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, BPF_USDT_MAX_IP_CNT);
__type(key, long);
__type(value, __u32);
} __bpf_usdt_ip_to_spec_id SEC(".maps") __weak;
/* don't rely on user's BPF code to have latest definition of bpf_func_id */
enum bpf_func_id___usdt {
BPF_FUNC_get_attach_cookie___usdt = 0xBAD, /* value doesn't matter */
};
static __always_inline
int __bpf_usdt_spec_id(struct pt_regs *ctx)
{
if (!BPF_USDT_HAS_BPF_COOKIE) {
long ip = PT_REGS_IP(ctx);
int *spec_id_ptr;
spec_id_ptr = bpf_map_lookup_elem(&__bpf_usdt_ip_to_spec_id, &ip);
return spec_id_ptr ? *spec_id_ptr : -ESRCH;
}
return bpf_get_attach_cookie(ctx);
}
/* Return number of USDT arguments defined for currently traced USDT. */
static inline __noinline
int bpf_usdt_arg_cnt(struct pt_regs *ctx)
{
struct __bpf_usdt_spec *spec;
int spec_id;
spec_id = __bpf_usdt_spec_id(ctx);
if (spec_id < 0)
return -ESRCH;
spec = bpf_map_lookup_elem(&__bpf_usdt_specs, &spec_id);
if (!spec)
return -ESRCH;
return spec->arg_cnt;
}
/* Fetch USDT argument #*arg_num* (zero-indexed) and put its value into *res.
* Returns 0 on success; negative error, otherwise.
* On error *res is guaranteed to be set to zero.
*/
static inline __noinline
int bpf_usdt_arg(struct pt_regs *ctx, __u64 arg_num, long *res)
{
struct __bpf_usdt_spec *spec;
struct __bpf_usdt_arg_spec *arg_spec;
unsigned long val;
int err, spec_id;
*res = 0;
spec_id = __bpf_usdt_spec_id(ctx);
if (spec_id < 0)
return -ESRCH;
spec = bpf_map_lookup_elem(&__bpf_usdt_specs, &spec_id);
if (!spec)
return -ESRCH;
if (arg_num >= BPF_USDT_MAX_ARG_CNT || arg_num >= spec->arg_cnt)
return -ENOENT;
arg_spec = &spec->args[arg_num];
switch (arg_spec->arg_type) {
case BPF_USDT_ARG_CONST:
/* Arg is just a constant ("-4@$-9" in USDT arg spec).
* value is recorded in arg_spec->val_off directly.
*/
val = arg_spec->val_off;
break;
case BPF_USDT_ARG_REG:
/* Arg is in a register (e.g, "8@%rax" in USDT arg spec),
* so we read the contents of that register directly from
* struct pt_regs. To keep things simple user-space parts
* record offsetof(struct pt_regs, <regname>) in arg_spec->reg_off.
*/
err = bpf_probe_read_kernel(&val, sizeof(val), (void *)ctx + arg_spec->reg_off);
if (err)
return err;
break;
case BPF_USDT_ARG_REG_DEREF:
/* Arg is in memory addressed by register, plus some offset
* (e.g., "-4@-1204(%rbp)" in USDT arg spec). Register is
* identified lik with BPF_USDT_ARG_REG case, and the offset
* is in arg_spec->val_off. We first fetch register contents
* from pt_regs, then do another user-space probe read to
* fetch argument value itself.
*/
err = bpf_probe_read_kernel(&val, sizeof(val), (void *)ctx + arg_spec->reg_off);
if (err)
return err;
err = bpf_probe_read_user(&val, sizeof(val), (void *)val + arg_spec->val_off);
if (err)
return err;
break;
default:
return -EINVAL;
}
/* cast arg from 1, 2, or 4 bytes to final 8 byte size clearing
* necessary upper arg_bitshift bits, with sign extension if argument
* is signed
*/
val <<= arg_spec->arg_bitshift;
if (arg_spec->arg_signed)
val = ((long)val) >> arg_spec->arg_bitshift;
else
val = val >> arg_spec->arg_bitshift;
*res = val;
return 0;
}
/* Retrieve user-specified cookie value provided during attach as
* bpf_usdt_opts.usdt_cookie. This serves the same purpose as BPF cookie
* returned by bpf_get_attach_cookie(). Libbpf's support for USDT is itself
* utilizaing BPF cookies internally, so user can't use BPF cookie directly
* for USDT programs and has to use bpf_usdt_cookie() API instead.
*/
static inline __noinline
long bpf_usdt_cookie(struct pt_regs *ctx)
{
struct __bpf_usdt_spec *spec;
int spec_id;
spec_id = __bpf_usdt_spec_id(ctx);
if (spec_id < 0)
return 0;
spec = bpf_map_lookup_elem(&__bpf_usdt_specs, &spec_id);
if (!spec)
return 0;
return spec->usdt_cookie;
}
/* we rely on ___bpf_apply() and ___bpf_narg() macros already defined in bpf_tracing.h */
#define ___bpf_usdt_args0() ctx
#define ___bpf_usdt_args1(x) ___bpf_usdt_args0(), ({ long _x; bpf_usdt_arg(ctx, 0, &_x); (void *)_x; })
#define ___bpf_usdt_args2(x, args...) ___bpf_usdt_args1(args), ({ long _x; bpf_usdt_arg(ctx, 1, &_x); (void *)_x; })
#define ___bpf_usdt_args3(x, args...) ___bpf_usdt_args2(args), ({ long _x; bpf_usdt_arg(ctx, 2, &_x); (void *)_x; })
#define ___bpf_usdt_args4(x, args...) ___bpf_usdt_args3(args), ({ long _x; bpf_usdt_arg(ctx, 3, &_x); (void *)_x; })
#define ___bpf_usdt_args5(x, args...) ___bpf_usdt_args4(args), ({ long _x; bpf_usdt_arg(ctx, 4, &_x); (void *)_x; })
#define ___bpf_usdt_args6(x, args...) ___bpf_usdt_args5(args), ({ long _x; bpf_usdt_arg(ctx, 5, &_x); (void *)_x; })
#define ___bpf_usdt_args7(x, args...) ___bpf_usdt_args6(args), ({ long _x; bpf_usdt_arg(ctx, 6, &_x); (void *)_x; })
#define ___bpf_usdt_args8(x, args...) ___bpf_usdt_args7(args), ({ long _x; bpf_usdt_arg(ctx, 7, &_x); (void *)_x; })
#define ___bpf_usdt_args9(x, args...) ___bpf_usdt_args8(args), ({ long _x; bpf_usdt_arg(ctx, 8, &_x); (void *)_x; })
#define ___bpf_usdt_args10(x, args...) ___bpf_usdt_args9(args), ({ long _x; bpf_usdt_arg(ctx, 9, &_x); (void *)_x; })
#define ___bpf_usdt_args11(x, args...) ___bpf_usdt_args10(args), ({ long _x; bpf_usdt_arg(ctx, 10, &_x); (void *)_x; })
#define ___bpf_usdt_args12(x, args...) ___bpf_usdt_args11(args), ({ long _x; bpf_usdt_arg(ctx, 11, &_x); (void *)_x; })
#define ___bpf_usdt_args(args...) ___bpf_apply(___bpf_usdt_args, ___bpf_narg(args))(args)
/*
* BPF_USDT serves the same purpose for USDT handlers as BPF_PROG for
* tp_btf/fentry/fexit BPF programs and BPF_KPROBE for kprobes.
* Original struct pt_regs * context is preserved as 'ctx' argument.
*/
#define BPF_USDT(name, args...) \
name(struct pt_regs *ctx); \
static __attribute__((always_inline)) typeof(name(0)) \
____##name(struct pt_regs *ctx, ##args); \
typeof(name(0)) name(struct pt_regs *ctx) \
{ \
_Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Wint-conversion\"") \
return ____##name(___bpf_usdt_args(args)); \
_Pragma("GCC diagnostic pop") \
} \
static __attribute__((always_inline)) typeof(name(0)) \
____##name(struct pt_regs *ctx, ##args)
#endif /* __USDT_BPF_H__ */
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
/* Copyright (c) 2022 Meta Platforms, Inc. and affiliates. */
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libelf.h>
#include <gelf.h>
#include <unistd.h>
#include <linux/ptrace.h>
#include <linux/kernel.h>
#include "bpf.h"
#include "libbpf.h"
#include "libbpf_common.h"
#include "libbpf_internal.h"
#include "hashmap.h"
/* libbpf's USDT support consists of BPF-side state/code and user-space
* state/code working together in concert. BPF-side parts are defined in
* usdt.bpf.h header library. User-space state is encapsulated by struct
* usdt_manager and all the supporting code centered around usdt_manager.
*
* usdt.bpf.h defines two BPF maps that usdt_manager expects: USDT spec map
* and IP-to-spec-ID map, which is auxiliary map necessary for kernels that
* don't support BPF cookie (see below). These two maps are implicitly
* embedded into user's end BPF object file when user's code included
* usdt.bpf.h. This means that libbpf doesn't do anything special to create
* these USDT support maps. They are created by normal libbpf logic of
* instantiating BPF maps when opening and loading BPF object.
*
* As such, libbpf is basically unaware of the need to do anything
* USDT-related until the very first call to bpf_program__attach_usdt(), which
* can be called by user explicitly or happen automatically during skeleton
* attach (or, equivalently, through generic bpf_program__attach() call). At
* this point, libbpf will instantiate and initialize struct usdt_manager and
* store it in bpf_object. USDT manager is per-BPF object construct, as each
* independent BPF object might or might not have USDT programs, and thus all
* the expected USDT-related state. There is no coordination between two
* bpf_object in parts of USDT attachment, they are oblivious of each other's
* existence and libbpf is just oblivious, dealing with bpf_object-specific
* USDT state.
*
* Quick crash course on USDTs.
*
* From user-space application's point of view, USDT is essentially just
* a slightly special function call that normally has zero overhead, unless it
* is being traced by some external entity (e.g, BPF-based tool). Here's how
* a typical application can trigger USDT probe:
*
* #include <sys/sdt.h> // provided by systemtap-sdt-devel package
* // folly also provide similar functionality in folly/tracing/StaticTracepoint.h
*
* STAP_PROBE3(my_usdt_provider, my_usdt_probe_name, 123, x, &y);
*
* USDT is identified by it's <provider-name>:<probe-name> pair of names. Each
* individual USDT has a fixed number of arguments (3 in the above example)
* and specifies values of each argument as if it was a function call.
*
* USDT call is actually not a function call, but is instead replaced by
* a single NOP instruction (thus zero overhead, effectively). But in addition
* to that, those USDT macros generate special SHT_NOTE ELF records in
* .note.stapsdt ELF section. Here's an example USDT definition as emitted by
* `readelf -n <binary>`:
*
* stapsdt 0x00000089 NT_STAPSDT (SystemTap probe descriptors)
* Provider: test
* Name: usdt12
* Location: 0x0000000000549df3, Base: 0x00000000008effa4, Semaphore: 0x0000000000a4606e
* Arguments: -4@-1204(%rbp) -4@%edi -8@-1216(%rbp) -8@%r8 -4@$5 -8@%r9 8@%rdx 8@%r10 -4@$-9 -2@%cx -2@%ax -1@%sil
*
* In this case we have USDT test:usdt12 with 12 arguments.
*
* Location and base are offsets used to calculate absolute IP address of that
* NOP instruction that kernel can replace with an interrupt instruction to
* trigger instrumentation code (BPF program for all that we care about).
*
* Semaphore above is and optional feature. It records an address of a 2-byte
* refcount variable (normally in '.probes' ELF section) used for signaling if
* there is anything that is attached to USDT. This is useful for user
* applications if, for example, they need to prepare some arguments that are
* passed only to USDTs and preparation is expensive. By checking if USDT is
* "activated", an application can avoid paying those costs unnecessarily.
* Recent enough kernel has built-in support for automatically managing this
* refcount, which libbpf expects and relies on. If USDT is defined without
* associated semaphore, this value will be zero. See selftests for semaphore
* examples.
*
* Arguments is the most interesting part. This USDT specification string is
* providing information about all the USDT arguments and their locations. The
* part before @ sign defined byte size of the argument (1, 2, 4, or 8) and
* whether the argument is signed or unsigned (negative size means signed).
* The part after @ sign is assembly-like definition of argument location
* (see [0] for more details). Technically, assembler can provide some pretty
* advanced definitions, but libbpf is currently supporting three most common
* cases:
* 1) immediate constant, see 5th and 9th args above (-4@$5 and -4@-9);
* 2) register value, e.g., 8@%rdx, which means "unsigned 8-byte integer
* whose value is in register %rdx";
* 3) memory dereference addressed by register, e.g., -4@-1204(%rbp), which
* specifies signed 32-bit integer stored at offset -1204 bytes from
* memory address stored in %rbp.
*
* [0] https://sourceware.org/systemtap/wiki/UserSpaceProbeImplementation
*
* During attachment, libbpf parses all the relevant USDT specifications and
* prepares `struct usdt_spec` (USDT spec), which is then provided to BPF-side
* code through spec map. This allows BPF applications to quickly fetch the
* actual value at runtime using a simple BPF-side code.
*
* With basics out of the way, let's go over less immeditately obvious aspects
* of supporting USDTs.
*
* First, there is no special USDT BPF program type. It is actually just
* a uprobe BPF program (which for kernel, at least currently, is just a kprobe
* program, so BPF_PROG_TYPE_KPROBE program type). With the only difference
* that uprobe is usually attached at the function entry, while USDT will
* normally will be somewhere inside the function. But it should always be
* pointing to NOP instruction, which makes such uprobes the fastest uprobe
* kind.
*
* Second, it's important to realize that such STAP_PROBEn(provider, name, ...)
* macro invocations can end up being inlined many-many times, depending on
* specifics of each individual user application. So single conceptual USDT
* (identified by provider:name pair of identifiers) is, generally speaking,
* multiple uprobe locations (USDT call sites) in different places in user
* application. Further, again due to inlining, each USDT call site might end
* up having the same argument #N be located in a different place. In one call
* site it could be a constant, in another will end up in a register, and in
* yet another could be some other register or even somewhere on the stack.
*
* As such, "attaching to USDT" means (in general case) attaching the same
* uprobe BPF program to multiple target locations in user application, each
* potentially having a completely different USDT spec associated with it.
* To wire all this up together libbpf allocates a unique integer spec ID for
* each unique USDT spec. Spec IDs are allocated as sequential small integers
* so that they can be used as keys in array BPF map (for performance reasons).
* Spec ID allocation and accounting is big part of what usdt_manager is
* about. This state has to be maintained per-BPF object and coordinate
* between different USDT attachments within the same BPF object.
*
* Spec ID is the key in spec BPF map, value is the actual USDT spec layed out
* as struct usdt_spec. Each invocation of BPF program at runtime needs to
* know its associated spec ID. It gets it either through BPF cookie, which
* libbpf sets to spec ID during attach time, or, if kernel is too old to
* support BPF cookie, through IP-to-spec-ID map that libbpf maintains in such
* case. The latter means that some modes of operation can't be supported
* without BPF cookie. Such mode is attaching to shared library "generically",
* without specifying target process. In such case, it's impossible to
* calculate absolute IP addresses for IP-to-spec-ID map, and thus such mode
* is not supported without BPF cookie support.
*
* Note that libbpf is using BPF cookie functionality for its own internal
* needs, so user itself can't rely on BPF cookie feature. To that end, libbpf
* provides conceptually equivalent USDT cookie support. It's still u64
* user-provided value that can be associated with USDT attachment. Note that
* this will be the same value for all USDT call sites within the same single
* *logical* USDT attachment. This makes sense because to user attaching to
* USDT is a single BPF program triggered for singular USDT probe. The fact
* that this is done at multiple actual locations is a mostly hidden
* implementation details. This USDT cookie value can be fetched with
* bpf_usdt_cookie(ctx) API provided by usdt.bpf.h
*
* Lastly, while single USDT can have tons of USDT call sites, it doesn't
* necessarily have that many different USDT specs. It very well might be
* that 1000 USDT call sites only need 5 different USDT specs, because all the
* arguments are typically contained in a small set of registers or stack
* locations. As such, it's wasteful to allocate as many USDT spec IDs as
* there are USDT call sites. So libbpf tries to be frugal and performs
* on-the-fly deduplication during a single USDT attachment to only allocate
* the minimal required amount of unique USDT specs (and thus spec IDs). This
* is trivially achieved by using USDT spec string (Arguments string from USDT
* note) as a lookup key in a hashmap. USDT spec string uniquely defines
* everything about how to fetch USDT arguments, so two USDT call sites
* sharing USDT spec string can safely share the same USDT spec and spec ID.
* Note, this spec string deduplication is happening only during the same USDT
* attachment, so each USDT spec shares the same USDT cookie value. This is
* not generally true for other USDT attachments within the same BPF object,
* as even if USDT spec string is the same, USDT cookie value can be
* different. It was deemed excessive to try to deduplicate across independent
* USDT attachments by taking into account USDT spec string *and* USDT cookie
* value, which would complicated spec ID accounting significantly for little
* gain.
*/
#define USDT_BASE_SEC ".stapsdt.base"
#define USDT_SEMA_SEC ".probes"
#define USDT_NOTE_SEC ".note.stapsdt"
#define USDT_NOTE_TYPE 3
#define USDT_NOTE_NAME "stapsdt"
/* should match exactly enum __bpf_usdt_arg_type from bpf_usdt.bpf.h */
enum usdt_arg_type {
USDT_ARG_CONST,
USDT_ARG_REG,
USDT_ARG_REG_DEREF,
};
/* should match exactly struct __bpf_usdt_arg_spec from bpf_usdt.bpf.h */
struct usdt_arg_spec {
__u64 val_off;
enum usdt_arg_type arg_type;
short reg_off;
bool arg_signed;
char arg_bitshift;
};
/* should match BPF_USDT_MAX_ARG_CNT in usdt.bpf.h */
#define USDT_MAX_ARG_CNT 12
/* should match struct __bpf_usdt_spec from usdt.bpf.h */
struct usdt_spec {
struct usdt_arg_spec args[USDT_MAX_ARG_CNT];
__u64 usdt_cookie;
short arg_cnt;
};
struct usdt_note {
const char *provider;
const char *name;
/* USDT args specification string, e.g.:
* "-4@%esi -4@-24(%rbp) -4@%ecx 2@%ax 8@%rdx"
*/
const char *args;
long loc_addr;
long base_addr;
long sema_addr;
};
struct usdt_target {
long abs_ip;
long rel_ip;
long sema_off;
struct usdt_spec spec;
const char *spec_str;
};
struct usdt_manager {
struct bpf_map *specs_map;
struct bpf_map *ip_to_spec_id_map;
int *free_spec_ids;
size_t free_spec_cnt;
size_t next_free_spec_id;
bool has_bpf_cookie;
bool has_sema_refcnt;
};
struct usdt_manager *usdt_manager_new(struct bpf_object *obj)
{
static const char *ref_ctr_sysfs_path = "/sys/bus/event_source/devices/uprobe/format/ref_ctr_offset";
struct usdt_manager *man;
struct bpf_map *specs_map, *ip_to_spec_id_map;
specs_map = bpf_object__find_map_by_name(obj, "__bpf_usdt_specs");
ip_to_spec_id_map = bpf_object__find_map_by_name(obj, "__bpf_usdt_ip_to_spec_id");
if (!specs_map || !ip_to_spec_id_map) {
pr_warn("usdt: failed to find USDT support BPF maps, did you forget to include bpf/usdt.bpf.h?\n");
return ERR_PTR(-ESRCH);
}
man = calloc(1, sizeof(*man));
if (!man)
return ERR_PTR(-ENOMEM);
man->specs_map = specs_map;
man->ip_to_spec_id_map = ip_to_spec_id_map;
/* Detect if BPF cookie is supported for kprobes.
* We don't need IP-to-ID mapping if we can use BPF cookies.
* Added in: 7adfc6c9b315 ("bpf: Add bpf_get_attach_cookie() BPF helper to access bpf_cookie value")
*/
man->has_bpf_cookie = kernel_supports(obj, FEAT_BPF_COOKIE);
/* Detect kernel support for automatic refcounting of USDT semaphore.
* If this is not supported, USDTs with semaphores will not be supported.
* Added in: a6ca88b241d5 ("trace_uprobe: support reference counter in fd-based uprobe")
*/
man->has_sema_refcnt = access(ref_ctr_sysfs_path, F_OK) == 0;
return man;
}
void usdt_manager_free(struct usdt_manager *man)
{
if (IS_ERR_OR_NULL(man))
return;
free(man->free_spec_ids);
free(man);
}
static int sanity_check_usdt_elf(Elf *elf, const char *path)
{
GElf_Ehdr ehdr;
int endianness;
if (elf_kind(elf) != ELF_K_ELF) {
pr_warn("usdt: unrecognized ELF kind %d for '%s'\n", elf_kind(elf), path);
return -EBADF;
}
switch (gelf_getclass(elf)) {
case ELFCLASS64:
if (sizeof(void *) != 8) {
pr_warn("usdt: attaching to 64-bit ELF binary '%s' is not supported\n", path);
return -EBADF;
}
break;
case ELFCLASS32:
if (sizeof(void *) != 4) {
pr_warn("usdt: attaching to 32-bit ELF binary '%s' is not supported\n", path);
return -EBADF;
}
break;
default:
pr_warn("usdt: unsupported ELF class for '%s'\n", path);
return -EBADF;
}
if (!gelf_getehdr(elf, &ehdr))
return -EINVAL;
if (ehdr.e_type != ET_EXEC && ehdr.e_type != ET_DYN) {
pr_warn("usdt: unsupported type of ELF binary '%s' (%d), only ET_EXEC and ET_DYN are supported\n",
path, ehdr.e_type);
return -EBADF;
}
#if __BYTE_ORDER == __LITTLE_ENDIAN
endianness = ELFDATA2LSB;
#elif __BYTE_ORDER == __BIG_ENDIAN
endianness = ELFDATA2MSB;
#else
# error "Unrecognized __BYTE_ORDER__"
#endif
if (endianness != ehdr.e_ident[EI_DATA]) {
pr_warn("usdt: ELF endianness mismatch for '%s'\n", path);
return -EBADF;
}
return 0;
}
static int find_elf_sec_by_name(Elf *elf, const char *sec_name, GElf_Shdr *shdr, Elf_Scn **scn)
{
Elf_Scn *sec = NULL;
size_t shstrndx;
if (elf_getshdrstrndx(elf, &shstrndx))
return -EINVAL;
/* check if ELF is corrupted and avoid calling elf_strptr if yes */
if (!elf_rawdata(elf_getscn(elf, shstrndx), NULL))
return -EINVAL;
while ((sec = elf_nextscn(elf, sec)) != NULL) {
char *name;
if (!gelf_getshdr(sec, shdr))
return -EINVAL;
name = elf_strptr(elf, shstrndx, shdr->sh_name);
if (name && strcmp(sec_name, name) == 0) {
*scn = sec;
return 0;
}
}
return -ENOENT;
}
struct elf_seg {
long start;
long end;
long offset;
bool is_exec;
};
static int cmp_elf_segs(const void *_a, const void *_b)
{
const struct elf_seg *a = _a;
const struct elf_seg *b = _b;
return a->start < b->start ? -1 : 1;
}
static int parse_elf_segs(Elf *elf, const char *path, struct elf_seg **segs, size_t *seg_cnt)
{
GElf_Phdr phdr;
size_t n;
int i, err;
struct elf_seg *seg;
void *tmp;
*seg_cnt = 0;
if (elf_getphdrnum(elf, &n)) {
err = -errno;
return err;
}
for (i = 0; i < n; i++) {
if (!gelf_getphdr(elf, i, &phdr)) {
err = -errno;
return err;
}
pr_debug("usdt: discovered PHDR #%d in '%s': vaddr 0x%lx memsz 0x%lx offset 0x%lx type 0x%lx flags 0x%lx\n",
i, path, (long)phdr.p_vaddr, (long)phdr.p_memsz, (long)phdr.p_offset,
(long)phdr.p_type, (long)phdr.p_flags);
if (phdr.p_type != PT_LOAD)
continue;
tmp = libbpf_reallocarray(*segs, *seg_cnt + 1, sizeof(**segs));
if (!tmp)
return -ENOMEM;
*segs = tmp;
seg = *segs + *seg_cnt;
(*seg_cnt)++;
seg->start = phdr.p_vaddr;
seg->end = phdr.p_vaddr + phdr.p_memsz;
seg->offset = phdr.p_offset;
seg->is_exec = phdr.p_flags & PF_X;
}
if (*seg_cnt == 0) {
pr_warn("usdt: failed to find PT_LOAD program headers in '%s'\n", path);
return -ESRCH;
}
qsort(*segs, *seg_cnt, sizeof(**segs), cmp_elf_segs);
return 0;
}
static int parse_lib_segs(int pid, const char *lib_path, struct elf_seg **segs, size_t *seg_cnt)
{
char path[PATH_MAX], line[PATH_MAX], mode[16];
size_t seg_start, seg_end, seg_off;
struct elf_seg *seg;
int tmp_pid, i, err;
FILE *f;
*seg_cnt = 0;
/* Handle containerized binaries only accessible from
* /proc/<pid>/root/<path>. They will be reported as just /<path> in
* /proc/<pid>/maps.
*/
if (sscanf(lib_path, "/proc/%d/root%s", &tmp_pid, path) == 2 && pid == tmp_pid)
goto proceed;
if (!realpath(lib_path, path)) {
pr_warn("usdt: failed to get absolute path of '%s' (err %d), using path as is...\n",
lib_path, -errno);
strcpy(path, lib_path);
}
proceed:
sprintf(line, "/proc/%d/maps", pid);
f = fopen(line, "r");
if (!f) {
err = -errno;
pr_warn("usdt: failed to open '%s' to get base addr of '%s': %d\n",
line, lib_path, err);
return err;
}
/* We need to handle lines with no path at the end:
*
* 7f5c6f5d1000-7f5c6f5d3000 rw-p 001c7000 08:04 21238613 /usr/lib64/libc-2.17.so
* 7f5c6f5d3000-7f5c6f5d8000 rw-p 00000000 00:00 0
* 7f5c6f5d8000-7f5c6f5d9000 r-xp 00000000 103:01 362990598 /data/users/andriin/linux/tools/bpf/usdt/libhello_usdt.so
*/
while (fscanf(f, "%zx-%zx %s %zx %*s %*d%[^\n]\n",
&seg_start, &seg_end, mode, &seg_off, line) == 5) {
void *tmp;
/* to handle no path case (see above) we need to capture line
* without skipping any whitespaces. So we need to strip
* leading whitespaces manually here
*/
i = 0;
while (isblank(line[i]))
i++;
if (strcmp(line + i, path) != 0)
continue;
pr_debug("usdt: discovered segment for lib '%s': addrs %zx-%zx mode %s offset %zx\n",
path, seg_start, seg_end, mode, seg_off);
/* ignore non-executable sections for shared libs */
if (mode[2] != 'x')
continue;
tmp = libbpf_reallocarray(*segs, *seg_cnt + 1, sizeof(**segs));
if (!tmp) {
err = -ENOMEM;
goto err_out;
}
*segs = tmp;
seg = *segs + *seg_cnt;
*seg_cnt += 1;
seg->start = seg_start;
seg->end = seg_end;
seg->offset = seg_off;
seg->is_exec = true;
}
if (*seg_cnt == 0) {
pr_warn("usdt: failed to find '%s' (resolved to '%s') within PID %d memory mappings\n",
lib_path, path, pid);
err = -ESRCH;
goto err_out;
}
qsort(*segs, *seg_cnt, sizeof(**segs), cmp_elf_segs);
err = 0;
err_out:
fclose(f);
return err;
}
static struct elf_seg *find_elf_seg(struct elf_seg *segs, size_t seg_cnt, long addr, bool relative)
{
struct elf_seg *seg;
int i;
if (relative) {
/* for shared libraries, address is relative offset and thus
* should be fall within logical offset-based range of
* [offset_start, offset_end)
*/
for (i = 0, seg = segs; i < seg_cnt; i++, seg++) {
if (seg->offset <= addr && addr < seg->offset + (seg->end - seg->start))
return seg;
}
} else {
/* for binaries, address is absolute and thus should be within
* absolute address range of [seg_start, seg_end)
*/
for (i = 0, seg = segs; i < seg_cnt; i++, seg++) {
if (seg->start <= addr && addr < seg->end)
return seg;
}
}
return NULL;
}
static int parse_usdt_note(Elf *elf, const char *path, long base_addr,
GElf_Nhdr *nhdr, const char *data, size_t name_off, size_t desc_off,
struct usdt_note *usdt_note);
static int parse_usdt_spec(struct usdt_spec *spec, const struct usdt_note *note, long usdt_cookie);
static int collect_usdt_targets(struct usdt_manager *man, Elf *elf, const char *path, pid_t pid,
const char *usdt_provider, const char *usdt_name, long usdt_cookie,
struct usdt_target **out_targets, size_t *out_target_cnt)
{
size_t off, name_off, desc_off, seg_cnt = 0, lib_seg_cnt = 0, target_cnt = 0;
struct elf_seg *segs = NULL, *lib_segs = NULL;
struct usdt_target *targets = NULL, *target;
long base_addr = 0;
Elf_Scn *notes_scn, *base_scn;
GElf_Shdr base_shdr, notes_shdr;
GElf_Ehdr ehdr;
GElf_Nhdr nhdr;
Elf_Data *data;
int err;
*out_targets = NULL;
*out_target_cnt = 0;
err = find_elf_sec_by_name(elf, USDT_NOTE_SEC, &notes_shdr, &notes_scn);
if (err) {
pr_warn("usdt: no USDT notes section (%s) found in '%s'\n", USDT_NOTE_SEC, path);
return err;
}
if (notes_shdr.sh_type != SHT_NOTE || !gelf_getehdr(elf, &ehdr)) {
pr_warn("usdt: invalid USDT notes section (%s) in '%s'\n", USDT_NOTE_SEC, path);
return -EINVAL;
}
err = parse_elf_segs(elf, path, &segs, &seg_cnt);
if (err) {
pr_warn("usdt: failed to process ELF program segments for '%s': %d\n", path, err);
goto err_out;
}
/* .stapsdt.base ELF section is optional, but is used for prelink
* offset compensation (see a big comment further below)
*/
if (find_elf_sec_by_name(elf, USDT_BASE_SEC, &base_shdr, &base_scn) == 0)
base_addr = base_shdr.sh_addr;
data = elf_getdata(notes_scn, 0);
off = 0;
while ((off = gelf_getnote(data, off, &nhdr, &name_off, &desc_off)) > 0) {
long usdt_abs_ip, usdt_rel_ip, usdt_sema_off = 0;
struct usdt_note note;
struct elf_seg *seg = NULL;
void *tmp;
err = parse_usdt_note(elf, path, base_addr, &nhdr,
data->d_buf, name_off, desc_off, &note);
if (err)
goto err_out;
if (strcmp(note.provider, usdt_provider) != 0 || strcmp(note.name, usdt_name) != 0)
continue;
/* We need to compensate "prelink effect". See [0] for details,
* relevant parts quoted here:
*
* Each SDT probe also expands into a non-allocated ELF note. You can
* find this by looking at SHT_NOTE sections and decoding the format;
* see below for details. Because the note is non-allocated, it means
* there is no runtime cost, and also preserved in both stripped files
* and .debug files.
*
* However, this means that prelink won't adjust the note's contents
* for address offsets. Instead, this is done via the .stapsdt.base
* section. This is a special section that is added to the text. We
* will only ever have one of these sections in a final link and it
* will only ever be one byte long. Nothing about this section itself
* matters, we just use it as a marker to detect prelink address
* adjustments.
*
* Each probe note records the link-time address of the .stapsdt.base
* section alongside the probe PC address. The decoder compares the
* base address stored in the note with the .stapsdt.base section's
* sh_addr. Initially these are the same, but the section header will
* be adjusted by prelink. So the decoder applies the difference to
* the probe PC address to get the correct prelinked PC address; the
* same adjustment is applied to the semaphore address, if any.
*
* [0] https://sourceware.org/systemtap/wiki/UserSpaceProbeImplementation
*/
usdt_rel_ip = usdt_abs_ip = note.loc_addr;
if (base_addr) {
usdt_abs_ip += base_addr - note.base_addr;
usdt_rel_ip += base_addr - note.base_addr;
}
if (ehdr.e_type == ET_EXEC) {
/* When attaching uprobes (which what USDTs basically
* are) kernel expects a relative IP to be specified,
* so if we are attaching to an executable ELF binary
* (i.e., not a shared library), we need to calculate
* proper relative IP based on ELF's load address
*/
seg = find_elf_seg(segs, seg_cnt, usdt_abs_ip, false /* relative */);
if (!seg) {
err = -ESRCH;
pr_warn("usdt: failed to find ELF program segment for '%s:%s' in '%s' at IP 0x%lx\n",
usdt_provider, usdt_name, path, usdt_abs_ip);
goto err_out;
}
if (!seg->is_exec) {
err = -ESRCH;
pr_warn("usdt: matched ELF binary '%s' segment [0x%lx, 0x%lx) for '%s:%s' at IP 0x%lx is not executable\n",
path, seg->start, seg->end, usdt_provider, usdt_name,
usdt_abs_ip);
goto err_out;
}
usdt_rel_ip = usdt_abs_ip - (seg->start - seg->offset);
} else if (!man->has_bpf_cookie) { /* ehdr.e_type == ET_DYN */
/* If we don't have BPF cookie support but need to
* attach to a shared library, we'll need to know and
* record absolute addresses of attach points due to
* the need to lookup USDT spec by absolute IP of
* triggered uprobe. Doing this resolution is only
* possible when we have a specific PID of the process
* that's using specified shared library. BPF cookie
* removes the absolute address limitation as we don't
* need to do this lookup (we just use BPF cookie as
* an index of USDT spec), so for newer kernels with
* BPF cookie support libbpf supports USDT attachment
* to shared libraries with no PID filter.
*/
if (pid < 0) {
pr_warn("usdt: attaching to shared libaries without specific PID is not supported on current kernel\n");
err = -ENOTSUP;
goto err_out;
}
/* lib_segs are lazily initialized only if necessary */
if (lib_seg_cnt == 0) {
err = parse_lib_segs(pid, path, &lib_segs, &lib_seg_cnt);
if (err) {
pr_warn("usdt: failed to get memory segments in PID %d for shared library '%s': %d\n",
pid, path, err);
goto err_out;
}
}
seg = find_elf_seg(lib_segs, lib_seg_cnt, usdt_rel_ip, true /* relative */);
if (!seg) {
err = -ESRCH;
pr_warn("usdt: failed to find shared lib memory segment for '%s:%s' in '%s' at relative IP 0x%lx\n",
usdt_provider, usdt_name, path, usdt_rel_ip);
goto err_out;
}
usdt_abs_ip = seg->start + (usdt_rel_ip - seg->offset);
}
pr_debug("usdt: probe for '%s:%s' in %s '%s': addr 0x%lx base 0x%lx (resolved abs_ip 0x%lx rel_ip 0x%lx) args '%s' in segment [0x%lx, 0x%lx) at offset 0x%lx\n",
usdt_provider, usdt_name, ehdr.e_type == ET_EXEC ? "exec" : "lib ", path,
note.loc_addr, note.base_addr, usdt_abs_ip, usdt_rel_ip, note.args,
seg ? seg->start : 0, seg ? seg->end : 0, seg ? seg->offset : 0);
/* Adjust semaphore address to be a relative offset */
if (note.sema_addr) {
if (!man->has_sema_refcnt) {
pr_warn("usdt: kernel doesn't support USDT semaphore refcounting for '%s:%s' in '%s'\n",
usdt_provider, usdt_name, path);
err = -ENOTSUP;
goto err_out;
}
seg = find_elf_seg(segs, seg_cnt, note.sema_addr, false /* relative */);
if (!seg) {
err = -ESRCH;
pr_warn("usdt: failed to find ELF loadable segment with semaphore of '%s:%s' in '%s' at 0x%lx\n",
usdt_provider, usdt_name, path, note.sema_addr);
goto err_out;
}
if (seg->is_exec) {
err = -ESRCH;
pr_warn("usdt: matched ELF binary '%s' segment [0x%lx, 0x%lx] for semaphore of '%s:%s' at 0x%lx is executable\n",
path, seg->start, seg->end, usdt_provider, usdt_name,
note.sema_addr);
goto err_out;
}
usdt_sema_off = note.sema_addr - (seg->start - seg->offset);
pr_debug("usdt: sema for '%s:%s' in %s '%s': addr 0x%lx base 0x%lx (resolved 0x%lx) in segment [0x%lx, 0x%lx] at offset 0x%lx\n",
usdt_provider, usdt_name, ehdr.e_type == ET_EXEC ? "exec" : "lib ",
path, note.sema_addr, note.base_addr, usdt_sema_off,
seg->start, seg->end, seg->offset);
}
/* Record adjusted addresses and offsets and parse USDT spec */
tmp = libbpf_reallocarray(targets, target_cnt + 1, sizeof(*targets));
if (!tmp) {
err = -ENOMEM;
goto err_out;
}
targets = tmp;
target = &targets[target_cnt];
memset(target, 0, sizeof(*target));
target->abs_ip = usdt_abs_ip;
target->rel_ip = usdt_rel_ip;
target->sema_off = usdt_sema_off;
/* notes->args references strings from Elf itself, so they can
* be referenced safely until elf_end() call
*/
target->spec_str = note.args;
err = parse_usdt_spec(&target->spec, &note, usdt_cookie);
if (err)
goto err_out;
target_cnt++;
}
*out_targets = targets;
*out_target_cnt = target_cnt;
err = target_cnt;
err_out:
free(segs);
free(lib_segs);
if (err < 0)
free(targets);
return err;
}
struct bpf_link_usdt {
struct bpf_link link;
struct usdt_manager *usdt_man;
size_t spec_cnt;
int *spec_ids;
size_t uprobe_cnt;
struct {
long abs_ip;
struct bpf_link *link;
} *uprobes;
};
static int bpf_link_usdt_detach(struct bpf_link *link)
{
struct bpf_link_usdt *usdt_link = container_of(link, struct bpf_link_usdt, link);
struct usdt_manager *man = usdt_link->usdt_man;
int i;
for (i = 0; i < usdt_link->uprobe_cnt; i++) {
/* detach underlying uprobe link */
bpf_link__destroy(usdt_link->uprobes[i].link);
/* there is no need to update specs map because it will be
* unconditionally overwritten on subsequent USDT attaches,
* but if BPF cookies are not used we need to remove entry
* from ip_to_spec_id map, otherwise we'll run into false
* conflicting IP errors
*/
if (!man->has_bpf_cookie) {
/* not much we can do about errors here */
(void)bpf_map_delete_elem(bpf_map__fd(man->ip_to_spec_id_map),
&usdt_link->uprobes[i].abs_ip);
}
}
/* try to return the list of previously used spec IDs to usdt_manager
* for future reuse for subsequent USDT attaches
*/
if (!man->free_spec_ids) {
/* if there were no free spec IDs yet, just transfer our IDs */
man->free_spec_ids = usdt_link->spec_ids;
man->free_spec_cnt = usdt_link->spec_cnt;
usdt_link->spec_ids = NULL;
} else {
/* otherwise concat IDs */
size_t new_cnt = man->free_spec_cnt + usdt_link->spec_cnt;
int *new_free_ids;
new_free_ids = libbpf_reallocarray(man->free_spec_ids, new_cnt,
sizeof(*new_free_ids));
/* If we couldn't resize free_spec_ids, we'll just leak
* a bunch of free IDs; this is very unlikely to happen and if
* system is so exausted on memory, it's the least of user's
* concerns, probably.
* So just do our best here to return those IDs to usdt_manager.
*/
if (new_free_ids) {
memcpy(new_free_ids + man->free_spec_cnt, usdt_link->spec_ids,
usdt_link->spec_cnt * sizeof(*usdt_link->spec_ids));
man->free_spec_ids = new_free_ids;
man->free_spec_cnt = new_cnt;
}
}
return 0;
}
static void bpf_link_usdt_dealloc(struct bpf_link *link)
{
struct bpf_link_usdt *usdt_link = container_of(link, struct bpf_link_usdt, link);
free(usdt_link->spec_ids);
free(usdt_link->uprobes);
free(usdt_link);
}
static size_t specs_hash_fn(const void *key, void *ctx)
{
const char *s = key;
return str_hash(s);
}
static bool specs_equal_fn(const void *key1, const void *key2, void *ctx)
{
const char *s1 = key1;
const char *s2 = key2;
return strcmp(s1, s2) == 0;
}
static int allocate_spec_id(struct usdt_manager *man, struct hashmap *specs_hash,
struct bpf_link_usdt *link, struct usdt_target *target,
int *spec_id, bool *is_new)
{
void *tmp;
int err;
/* check if we already allocated spec ID for this spec string */
if (hashmap__find(specs_hash, target->spec_str, &tmp)) {
*spec_id = (long)tmp;
*is_new = false;
return 0;
}
/* otherwise it's a new ID that needs to be set up in specs map and
* returned back to usdt_manager when USDT link is detached
*/
tmp = libbpf_reallocarray(link->spec_ids, link->spec_cnt + 1, sizeof(*link->spec_ids));
if (!tmp)
return -ENOMEM;
link->spec_ids = tmp;
/* get next free spec ID, giving preference to free list, if not empty */
if (man->free_spec_cnt) {
*spec_id = man->free_spec_ids[man->free_spec_cnt - 1];
/* cache spec ID for current spec string for future lookups */
err = hashmap__add(specs_hash, target->spec_str, (void *)(long)*spec_id);
if (err)
return err;
man->free_spec_cnt--;
} else {
/* don't allocate spec ID bigger than what fits in specs map */
if (man->next_free_spec_id >= bpf_map__max_entries(man->specs_map))
return -E2BIG;
*spec_id = man->next_free_spec_id;
/* cache spec ID for current spec string for future lookups */
err = hashmap__add(specs_hash, target->spec_str, (void *)(long)*spec_id);
if (err)
return err;
man->next_free_spec_id++;
}
/* remember new spec ID in the link for later return back to free list on detach */
link->spec_ids[link->spec_cnt] = *spec_id;
link->spec_cnt++;
*is_new = true;
return 0;
}
struct bpf_link *usdt_manager_attach_usdt(struct usdt_manager *man, const struct bpf_program *prog,
pid_t pid, const char *path,
const char *usdt_provider, const char *usdt_name,
long usdt_cookie)
{
int i, fd, err, spec_map_fd, ip_map_fd;
LIBBPF_OPTS(bpf_uprobe_opts, opts);
struct hashmap *specs_hash = NULL;
struct bpf_link_usdt *link = NULL;
struct usdt_target *targets = NULL;
size_t target_cnt;
Elf *elf;
spec_map_fd = bpf_map__fd(man->specs_map);
ip_map_fd = bpf_map__fd(man->ip_to_spec_id_map);
/* TODO: perform path resolution similar to uprobe's */
fd = open(path, O_RDONLY);
if (fd < 0) {
err = -errno;
pr_warn("usdt: failed to open ELF binary '%s': %d\n", path, err);
return libbpf_err_ptr(err);
}
elf = elf_begin(fd, ELF_C_READ_MMAP, NULL);
if (!elf) {
err = -EBADF;
pr_warn("usdt: failed to parse ELF binary '%s': %s\n", path, elf_errmsg(-1));
goto err_out;
}
err = sanity_check_usdt_elf(elf, path);
if (err)
goto err_out;
/* normalize PID filter */
if (pid < 0)
pid = -1;
else if (pid == 0)
pid = getpid();
/* discover USDT in given binary, optionally limiting
* activations to a given PID, if pid > 0
*/
err = collect_usdt_targets(man, elf, path, pid, usdt_provider, usdt_name,
usdt_cookie, &targets, &target_cnt);
if (err <= 0) {
err = (err == 0) ? -ENOENT : err;
goto err_out;
}
specs_hash = hashmap__new(specs_hash_fn, specs_equal_fn, NULL);
if (IS_ERR(specs_hash)) {
err = PTR_ERR(specs_hash);
goto err_out;
}
link = calloc(1, sizeof(*link));
if (!link) {
err = -ENOMEM;
goto err_out;
}
link->usdt_man = man;
link->link.detach = &bpf_link_usdt_detach;
link->link.dealloc = &bpf_link_usdt_dealloc;
link->uprobes = calloc(target_cnt, sizeof(*link->uprobes));
if (!link->uprobes) {
err = -ENOMEM;
goto err_out;
}
for (i = 0; i < target_cnt; i++) {
struct usdt_target *target = &targets[i];
struct bpf_link *uprobe_link;
bool is_new;
int spec_id;
/* Spec ID can be either reused or newly allocated. If it is
* newly allocated, we'll need to fill out spec map, otherwise
* entire spec should be valid and can be just used by a new
* uprobe. We reuse spec when USDT arg spec is identical. We
* also never share specs between two different USDT
* attachments ("links"), so all the reused specs already
* share USDT cookie value implicitly.
*/
err = allocate_spec_id(man, specs_hash, link, target, &spec_id, &is_new);
if (err)
goto err_out;
if (is_new && bpf_map_update_elem(spec_map_fd, &spec_id, &target->spec, BPF_ANY)) {
err = -errno;
pr_warn("usdt: failed to set USDT spec #%d for '%s:%s' in '%s': %d\n",
spec_id, usdt_provider, usdt_name, path, err);
goto err_out;
}
if (!man->has_bpf_cookie &&
bpf_map_update_elem(ip_map_fd, &target->abs_ip, &spec_id, BPF_NOEXIST)) {
err = -errno;
if (err == -EEXIST) {
pr_warn("usdt: IP collision detected for spec #%d for '%s:%s' in '%s'\n",
spec_id, usdt_provider, usdt_name, path);
} else {
pr_warn("usdt: failed to map IP 0x%lx to spec #%d for '%s:%s' in '%s': %d\n",
target->abs_ip, spec_id, usdt_provider, usdt_name,
path, err);
}
goto err_out;
}
opts.ref_ctr_offset = target->sema_off;
opts.bpf_cookie = man->has_bpf_cookie ? spec_id : 0;
uprobe_link = bpf_program__attach_uprobe_opts(prog, pid, path,
target->rel_ip, &opts);
err = libbpf_get_error(uprobe_link);
if (err) {
pr_warn("usdt: failed to attach uprobe #%d for '%s:%s' in '%s': %d\n",
i, usdt_provider, usdt_name, path, err);
goto err_out;
}
link->uprobes[i].link = uprobe_link;
link->uprobes[i].abs_ip = target->abs_ip;
link->uprobe_cnt++;
}
free(targets);
hashmap__free(specs_hash);
elf_end(elf);
close(fd);
return &link->link;
err_out:
bpf_link__destroy(&link->link);
free(targets);
hashmap__free(specs_hash);
if (elf)
elf_end(elf);
close(fd);
return libbpf_err_ptr(err);
}
/* Parse out USDT ELF note from '.note.stapsdt' section.
* Logic inspired by perf's code.
*/
static int parse_usdt_note(Elf *elf, const char *path, long base_addr,
GElf_Nhdr *nhdr, const char *data, size_t name_off, size_t desc_off,
struct usdt_note *note)
{
const char *provider, *name, *args;
long addrs[3];
size_t len;
/* sanity check USDT note name and type first */
if (strncmp(data + name_off, USDT_NOTE_NAME, nhdr->n_namesz) != 0)
return -EINVAL;
if (nhdr->n_type != USDT_NOTE_TYPE)
return -EINVAL;
/* sanity check USDT note contents ("description" in ELF terminology) */
len = nhdr->n_descsz;
data = data + desc_off;
/* +3 is the very minimum required to store three empty strings */
if (len < sizeof(addrs) + 3)
return -EINVAL;
/* get location, base, and semaphore addrs */
memcpy(&addrs, data, sizeof(addrs));
/* parse string fields: provider, name, args */
provider = data + sizeof(addrs);
name = (const char *)memchr(provider, '\0', data + len - provider);
if (!name) /* non-zero-terminated provider */
return -EINVAL;
name++;
if (name >= data + len || *name == '\0') /* missing or empty name */
return -EINVAL;
args = memchr(name, '\0', data + len - name);
if (!args) /* non-zero-terminated name */
return -EINVAL;
++args;
if (args >= data + len) /* missing arguments spec */
return -EINVAL;
note->provider = provider;
note->name = name;
if (*args == '\0' || *args == ':')
note->args = "";
else
note->args = args;
note->loc_addr = addrs[0];
note->base_addr = addrs[1];
note->sema_addr = addrs[2];
return 0;
}
static int parse_usdt_arg(const char *arg_str, int arg_num, struct usdt_arg_spec *arg);
static int parse_usdt_spec(struct usdt_spec *spec, const struct usdt_note *note, long usdt_cookie)
{
const char *s;
int len;
spec->usdt_cookie = usdt_cookie;
spec->arg_cnt = 0;
s = note->args;
while (s[0]) {
if (spec->arg_cnt >= USDT_MAX_ARG_CNT) {
pr_warn("usdt: too many USDT arguments (> %d) for '%s:%s' with args spec '%s'\n",
USDT_MAX_ARG_CNT, note->provider, note->name, note->args);
return -E2BIG;
}
len = parse_usdt_arg(s, spec->arg_cnt, &spec->args[spec->arg_cnt]);
if (len < 0)
return len;
s += len;
spec->arg_cnt++;
}
return 0;
}
/* Architecture-specific logic for parsing USDT argument location specs */
#if defined(__x86_64__) || defined(__i386__)
static int calc_pt_regs_off(const char *reg_name)
{
static struct {
const char *names[4];
size_t pt_regs_off;
} reg_map[] = {
#if __x86_64__
#define reg_off(reg64, reg32) offsetof(struct pt_regs, reg64)
#else
#define reg_off(reg64, reg32) offsetof(struct pt_regs, reg32)
#endif
{ {"rip", "eip", "", ""}, reg_off(rip, eip) },
{ {"rax", "eax", "ax", "al"}, reg_off(rax, eax) },
{ {"rbx", "ebx", "bx", "bl"}, reg_off(rbx, ebx) },
{ {"rcx", "ecx", "cx", "cl"}, reg_off(rcx, ecx) },
{ {"rdx", "edx", "dx", "dl"}, reg_off(rdx, edx) },
{ {"rsi", "esi", "si", "sil"}, reg_off(rsi, esi) },
{ {"rdi", "edi", "di", "dil"}, reg_off(rdi, edi) },
{ {"rbp", "ebp", "bp", "bpl"}, reg_off(rbp, ebp) },
{ {"rsp", "esp", "sp", "spl"}, reg_off(rsp, esp) },
#undef reg_off
#if __x86_64__
{ {"r8", "r8d", "r8w", "r8b"}, offsetof(struct pt_regs, r8) },
{ {"r9", "r9d", "r9w", "r9b"}, offsetof(struct pt_regs, r9) },
{ {"r10", "r10d", "r10w", "r10b"}, offsetof(struct pt_regs, r10) },
{ {"r11", "r11d", "r11w", "r11b"}, offsetof(struct pt_regs, r11) },
{ {"r12", "r12d", "r12w", "r12b"}, offsetof(struct pt_regs, r12) },
{ {"r13", "r13d", "r13w", "r13b"}, offsetof(struct pt_regs, r13) },
{ {"r14", "r14d", "r14w", "r14b"}, offsetof(struct pt_regs, r14) },
{ {"r15", "r15d", "r15w", "r15b"}, offsetof(struct pt_regs, r15) },
#endif
};
int i, j;
for (i = 0; i < ARRAY_SIZE(reg_map); i++) {
for (j = 0; j < ARRAY_SIZE(reg_map[i].names); j++) {
if (strcmp(reg_name, reg_map[i].names[j]) == 0)
return reg_map[i].pt_regs_off;
}
}
pr_warn("usdt: unrecognized register '%s'\n", reg_name);
return -ENOENT;
}
static int parse_usdt_arg(const char *arg_str, int arg_num, struct usdt_arg_spec *arg)
{
char *reg_name = NULL;
int arg_sz, len, reg_off;
long off;
if (sscanf(arg_str, " %d @ %ld ( %%%m[^)] ) %n", &arg_sz, &off, &reg_name, &len) == 3) {
/* Memory dereference case, e.g., -4@-20(%rbp) */
arg->arg_type = USDT_ARG_REG_DEREF;
arg->val_off = off;
reg_off = calc_pt_regs_off(reg_name);
free(reg_name);
if (reg_off < 0)
return reg_off;
arg->reg_off = reg_off;
} else if (sscanf(arg_str, " %d @ %%%ms %n", &arg_sz, &reg_name, &len) == 2) {
/* Register read case, e.g., -4@%eax */
arg->arg_type = USDT_ARG_REG;
arg->val_off = 0;
reg_off = calc_pt_regs_off(reg_name);
free(reg_name);
if (reg_off < 0)
return reg_off;
arg->reg_off = reg_off;
} else if (sscanf(arg_str, " %d @ $%ld %n", &arg_sz, &off, &len) == 2) {
/* Constant value case, e.g., 4@$71 */
arg->arg_type = USDT_ARG_CONST;
arg->val_off = off;
arg->reg_off = 0;
} else {
pr_warn("usdt: unrecognized arg #%d spec '%s'\n", arg_num, arg_str);
return -EINVAL;
}
arg->arg_signed = arg_sz < 0;
if (arg_sz < 0)
arg_sz = -arg_sz;
switch (arg_sz) {
case 1: case 2: case 4: case 8:
arg->arg_bitshift = 64 - arg_sz * 8;
break;
default:
pr_warn("usdt: unsupported arg #%d (spec '%s') size: %d\n",
arg_num, arg_str, arg_sz);
return -EINVAL;
}
return len;
}
#else
static int parse_usdt_arg(const char *arg_str, int arg_num, struct usdt_arg_spec *arg)
{
pr_warn("usdt: libbpf doesn't support USDTs on current architecture\n");
return -ENOTSUP;
}
#endif
......@@ -168,9 +168,15 @@ $(OUTPUT)/%:%.c
$(call msg,BINARY,,$@)
$(Q)$(LINK.c) $^ $(LDLIBS) -o $@
$(OUTPUT)/urandom_read: urandom_read.c
$(OUTPUT)/liburandom_read.so: urandom_read_lib1.c urandom_read_lib2.c
$(call msg,LIB,,$@)
$(Q)$(CC) $(CFLAGS) -fPIC $(LDFLAGS) $^ $(LDLIBS) --shared -o $@
$(OUTPUT)/urandom_read: urandom_read.c urandom_read_aux.c $(OUTPUT)/liburandom_read.so
$(call msg,BINARY,,$@)
$(Q)$(CC) $(CFLAGS) $(LDFLAGS) $< $(LDLIBS) -Wl,--build-id=sha1 -o $@
$(Q)$(CC) $(CFLAGS) $(LDFLAGS) $(filter %.c,$^) \
liburandom_read.so $(LDLIBS) \
-Wl,-rpath=. -Wl,--build-id=sha1 -o $@
$(OUTPUT)/bpf_testmod.ko: $(VMLINUX_BTF) $(wildcard bpf_testmod/Makefile bpf_testmod/*.[ch])
$(call msg,MOD,,$@)
......@@ -328,12 +334,8 @@ SKEL_BLACKLIST := btf__% test_pinning_invalid.c test_sk_assign.c
LINKED_SKELS := test_static_linked.skel.h linked_funcs.skel.h \
linked_vars.skel.h linked_maps.skel.h \
test_subskeleton.skel.h test_subskeleton_lib.skel.h
# In the subskeleton case, we want the test_subskeleton_lib.subskel.h file
# but that's created as a side-effect of the skel.h generation.
test_subskeleton.skel.h-deps := test_subskeleton_lib2.o test_subskeleton_lib.o test_subskeleton.o
test_subskeleton_lib.skel.h-deps := test_subskeleton_lib2.o test_subskeleton_lib.o
test_subskeleton.skel.h test_subskeleton_lib.skel.h \
test_usdt.skel.h
LSKELS := kfunc_call_test.c fentry_test.c fexit_test.c fexit_sleep.c \
test_ringbuf.c atomics.c trace_printk.c trace_vprintk.c \
......@@ -346,6 +348,11 @@ test_static_linked.skel.h-deps := test_static_linked1.o test_static_linked2.o
linked_funcs.skel.h-deps := linked_funcs1.o linked_funcs2.o
linked_vars.skel.h-deps := linked_vars1.o linked_vars2.o
linked_maps.skel.h-deps := linked_maps1.o linked_maps2.o
# In the subskeleton case, we want the test_subskeleton_lib.subskel.h file
# but that's created as a side-effect of the skel.h generation.
test_subskeleton.skel.h-deps := test_subskeleton_lib2.o test_subskeleton_lib.o test_subskeleton.o
test_subskeleton_lib.skel.h-deps := test_subskeleton_lib2.o test_subskeleton_lib.o
test_usdt.skel.h-deps := test_usdt.o test_usdt_multispec.o
LINKED_BPF_SRCS := $(patsubst %.o,%.c,$(foreach skel,$(LINKED_SKELS),$($(skel)-deps)))
......@@ -400,6 +407,7 @@ $(TRUNNER_BPF_OBJS): $(TRUNNER_OUTPUT)/%.o: \
$(TRUNNER_BPF_PROGS_DIR)/*.h \
$$(INCLUDE_DIR)/vmlinux.h \
$(wildcard $(BPFDIR)/bpf_*.h) \
$(wildcard $(BPFDIR)/*.bpf.h) \
| $(TRUNNER_OUTPUT) $$(BPFOBJ)
$$(call $(TRUNNER_BPF_BUILD_RULE),$$<,$$@, \
$(TRUNNER_BPF_CFLAGS))
......@@ -491,6 +499,7 @@ TRUNNER_EXTRA_SOURCES := test_progs.c cgroup_helpers.c trace_helpers.c \
btf_helpers.c flow_dissector_load.h \
cap_helpers.c
TRUNNER_EXTRA_FILES := $(OUTPUT)/urandom_read $(OUTPUT)/bpf_testmod.ko \
$(OUTPUT)/liburandom_read.so \
ima_setup.sh \
$(wildcard progs/btf_dump_test_case_*.c)
TRUNNER_BPF_BUILD_RULE := CLANG_BPF_BUILD_RULE
......
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2022 Meta Platforms, Inc. and affiliates. */
#include <test_progs.h>
#define _SDT_HAS_SEMAPHORES 1
#include "../sdt.h"
#include "test_usdt.skel.h"
#include "test_urandom_usdt.skel.h"
int lets_test_this(int);
static volatile int idx = 2;
static volatile __u64 bla = 0xFEDCBA9876543210ULL;
static volatile short nums[] = {-1, -2, -3, };
static volatile struct {
int x;
signed char y;
} t1 = { 1, -127 };
#define SEC(name) __attribute__((section(name), used))
unsigned short test_usdt0_semaphore SEC(".probes");
unsigned short test_usdt3_semaphore SEC(".probes");
unsigned short test_usdt12_semaphore SEC(".probes");
static void __always_inline trigger_func(int x) {
long y = 42;
if (test_usdt0_semaphore)
STAP_PROBE(test, usdt0);
if (test_usdt3_semaphore)
STAP_PROBE3(test, usdt3, x, y, &bla);
if (test_usdt12_semaphore) {
STAP_PROBE12(test, usdt12,
x, x + 1, y, x + y, 5,
y / 7, bla, &bla, -9, nums[x],
nums[idx], t1.y);
}
}
static void subtest_basic_usdt(void)
{
LIBBPF_OPTS(bpf_usdt_opts, opts);
struct test_usdt *skel;
struct test_usdt__bss *bss;
int err;
skel = test_usdt__open_and_load();
if (!ASSERT_OK_PTR(skel, "skel_open"))
return;
bss = skel->bss;
bss->my_pid = getpid();
err = test_usdt__attach(skel);
if (!ASSERT_OK(err, "skel_attach"))
goto cleanup;
/* usdt0 won't be auto-attached */
opts.usdt_cookie = 0xcafedeadbeeffeed;
skel->links.usdt0 = bpf_program__attach_usdt(skel->progs.usdt0,
0 /*self*/, "/proc/self/exe",
"test", "usdt0", &opts);
if (!ASSERT_OK_PTR(skel->links.usdt0, "usdt0_link"))
goto cleanup;
trigger_func(1);
ASSERT_EQ(bss->usdt0_called, 1, "usdt0_called");
ASSERT_EQ(bss->usdt3_called, 1, "usdt3_called");
ASSERT_EQ(bss->usdt12_called, 1, "usdt12_called");
ASSERT_EQ(bss->usdt0_cookie, 0xcafedeadbeeffeed, "usdt0_cookie");
ASSERT_EQ(bss->usdt0_arg_cnt, 0, "usdt0_arg_cnt");
ASSERT_EQ(bss->usdt0_arg_ret, -ENOENT, "usdt0_arg_ret");
/* auto-attached usdt3 gets default zero cookie value */
ASSERT_EQ(bss->usdt3_cookie, 0, "usdt3_cookie");
ASSERT_EQ(bss->usdt3_arg_cnt, 3, "usdt3_arg_cnt");
ASSERT_EQ(bss->usdt3_arg_rets[0], 0, "usdt3_arg1_ret");
ASSERT_EQ(bss->usdt3_arg_rets[1], 0, "usdt3_arg2_ret");
ASSERT_EQ(bss->usdt3_arg_rets[2], 0, "usdt3_arg3_ret");
ASSERT_EQ(bss->usdt3_args[0], 1, "usdt3_arg1");
ASSERT_EQ(bss->usdt3_args[1], 42, "usdt3_arg2");
ASSERT_EQ(bss->usdt3_args[2], (uintptr_t)&bla, "usdt3_arg3");
/* auto-attached usdt12 gets default zero cookie value */
ASSERT_EQ(bss->usdt12_cookie, 0, "usdt12_cookie");
ASSERT_EQ(bss->usdt12_arg_cnt, 12, "usdt12_arg_cnt");
ASSERT_EQ(bss->usdt12_args[0], 1, "usdt12_arg1");
ASSERT_EQ(bss->usdt12_args[1], 1 + 1, "usdt12_arg2");
ASSERT_EQ(bss->usdt12_args[2], 42, "usdt12_arg3");
ASSERT_EQ(bss->usdt12_args[3], 42 + 1, "usdt12_arg4");
ASSERT_EQ(bss->usdt12_args[4], 5, "usdt12_arg5");
ASSERT_EQ(bss->usdt12_args[5], 42 / 7, "usdt12_arg6");
ASSERT_EQ(bss->usdt12_args[6], bla, "usdt12_arg7");
ASSERT_EQ(bss->usdt12_args[7], (uintptr_t)&bla, "usdt12_arg8");
ASSERT_EQ(bss->usdt12_args[8], -9, "usdt12_arg9");
ASSERT_EQ(bss->usdt12_args[9], nums[1], "usdt12_arg10");
ASSERT_EQ(bss->usdt12_args[10], nums[idx], "usdt12_arg11");
ASSERT_EQ(bss->usdt12_args[11], t1.y, "usdt12_arg12");
/* trigger_func() is marked __always_inline, so USDT invocations will be
* inlined in two different places, meaning that each USDT will have
* at least 2 different places to be attached to. This verifies that
* bpf_program__attach_usdt() handles this properly and attaches to
* all possible places of USDT invocation.
*/
trigger_func(2);
ASSERT_EQ(bss->usdt0_called, 2, "usdt0_called");
ASSERT_EQ(bss->usdt3_called, 2, "usdt3_called");
ASSERT_EQ(bss->usdt12_called, 2, "usdt12_called");
/* only check values that depend on trigger_func()'s input value */
ASSERT_EQ(bss->usdt3_args[0], 2, "usdt3_arg1");
ASSERT_EQ(bss->usdt12_args[0], 2, "usdt12_arg1");
ASSERT_EQ(bss->usdt12_args[1], 2 + 1, "usdt12_arg2");
ASSERT_EQ(bss->usdt12_args[3], 42 + 2, "usdt12_arg4");
ASSERT_EQ(bss->usdt12_args[9], nums[2], "usdt12_arg10");
/* detach and re-attach usdt3 */
bpf_link__destroy(skel->links.usdt3);
opts.usdt_cookie = 0xBADC00C51E;
skel->links.usdt3 = bpf_program__attach_usdt(skel->progs.usdt3, -1 /* any pid */,
"/proc/self/exe", "test", "usdt3", &opts);
if (!ASSERT_OK_PTR(skel->links.usdt3, "usdt3_reattach"))
goto cleanup;
trigger_func(3);
ASSERT_EQ(bss->usdt3_called, 3, "usdt3_called");
/* this time usdt3 has custom cookie */
ASSERT_EQ(bss->usdt3_cookie, 0xBADC00C51E, "usdt3_cookie");
ASSERT_EQ(bss->usdt3_arg_cnt, 3, "usdt3_arg_cnt");
ASSERT_EQ(bss->usdt3_arg_rets[0], 0, "usdt3_arg1_ret");
ASSERT_EQ(bss->usdt3_arg_rets[1], 0, "usdt3_arg2_ret");
ASSERT_EQ(bss->usdt3_arg_rets[2], 0, "usdt3_arg3_ret");
ASSERT_EQ(bss->usdt3_args[0], 3, "usdt3_arg1");
ASSERT_EQ(bss->usdt3_args[1], 42, "usdt3_arg2");
ASSERT_EQ(bss->usdt3_args[2], (uintptr_t)&bla, "usdt3_arg3");
cleanup:
test_usdt__destroy(skel);
}
unsigned short test_usdt_100_semaphore SEC(".probes");
unsigned short test_usdt_300_semaphore SEC(".probes");
unsigned short test_usdt_400_semaphore SEC(".probes");
#define R10(F, X) F(X+0); F(X+1);F(X+2); F(X+3); F(X+4); \
F(X+5); F(X+6); F(X+7); F(X+8); F(X+9);
#define R100(F, X) R10(F,X+ 0);R10(F,X+10);R10(F,X+20);R10(F,X+30);R10(F,X+40); \
R10(F,X+50);R10(F,X+60);R10(F,X+70);R10(F,X+80);R10(F,X+90);
/* carefully control that we get exactly 100 inlines by preventing inlining */
static void __always_inline f100(int x)
{
STAP_PROBE1(test, usdt_100, x);
}
__weak void trigger_100_usdts(void)
{
R100(f100, 0);
}
/* we shouldn't be able to attach to test:usdt2_300 USDT as we don't have as
* many slots for specs. It's important that each STAP_PROBE2() invocation
* (after untolling) gets different arg spec due to compiler inlining i as
* a constant
*/
static void __always_inline f300(int x)
{
STAP_PROBE1(test, usdt_300, x);
}
__weak void trigger_300_usdts(void)
{
R100(f300, 0);
R100(f300, 100);
R100(f300, 200);
}
static void __always_inline f400(int x __attribute__((unused)))
{
static int y;
STAP_PROBE1(test, usdt_400, y++);
}
/* this time we have 400 different USDT call sites, but they have uniform
* argument location, so libbpf's spec string deduplication logic should keep
* spec count use very small and so we should be able to attach to all 400
* call sites
*/
__weak void trigger_400_usdts(void)
{
R100(f400, 0);
R100(f400, 100);
R100(f400, 200);
R100(f400, 300);
}
static void subtest_multispec_usdt(void)
{
LIBBPF_OPTS(bpf_usdt_opts, opts);
struct test_usdt *skel;
struct test_usdt__bss *bss;
int err, i;
skel = test_usdt__open_and_load();
if (!ASSERT_OK_PTR(skel, "skel_open"))
return;
bss = skel->bss;
bss->my_pid = getpid();
err = test_usdt__attach(skel);
if (!ASSERT_OK(err, "skel_attach"))
goto cleanup;
/* usdt_100 is auto-attached and there are 100 inlined call sites,
* let's validate that all of them are properly attached to and
* handled from BPF side
*/
trigger_100_usdts();
ASSERT_EQ(bss->usdt_100_called, 100, "usdt_100_called");
ASSERT_EQ(bss->usdt_100_sum, 99 * 100 / 2, "usdt_100_sum");
/* Stress test free spec ID tracking. By default libbpf allows up to
* 256 specs to be used, so if we don't return free spec IDs back
* after few detachments and re-attachments we should run out of
* available spec IDs.
*/
for (i = 0; i < 2; i++) {
bpf_link__destroy(skel->links.usdt_100);
skel->links.usdt_100 = bpf_program__attach_usdt(skel->progs.usdt_100, -1,
"/proc/self/exe",
"test", "usdt_100", NULL);
if (!ASSERT_OK_PTR(skel->links.usdt_100, "usdt_100_reattach"))
goto cleanup;
bss->usdt_100_sum = 0;
trigger_100_usdts();
ASSERT_EQ(bss->usdt_100_called, (i + 1) * 100 + 100, "usdt_100_called");
ASSERT_EQ(bss->usdt_100_sum, 99 * 100 / 2, "usdt_100_sum");
}
/* Now let's step it up and try to attach USDT that requires more than
* 256 attach points with different specs for each.
* Note that we need trigger_300_usdts() only to actually have 300
* USDT call sites, we are not going to actually trace them.
*/
trigger_300_usdts();
/* we'll reuse usdt_100 BPF program for usdt_300 test */
bpf_link__destroy(skel->links.usdt_100);
skel->links.usdt_100 = bpf_program__attach_usdt(skel->progs.usdt_100, -1, "/proc/self/exe",
"test", "usdt_300", NULL);
err = -errno;
if (!ASSERT_ERR_PTR(skel->links.usdt_100, "usdt_300_bad_attach"))
goto cleanup;
ASSERT_EQ(err, -E2BIG, "usdt_300_attach_err");
/* let's check that there are no "dangling" BPF programs attached due
* to partial success of the above test:usdt_300 attachment
*/
bss->usdt_100_called = 0;
bss->usdt_100_sum = 0;
f300(777); /* this is 301st instance of usdt_300 */
ASSERT_EQ(bss->usdt_100_called, 0, "usdt_301_called");
ASSERT_EQ(bss->usdt_100_sum, 0, "usdt_301_sum");
/* This time we have USDT with 400 inlined invocations, but arg specs
* should be the same across all sites, so libbpf will only need to
* use one spec and thus we'll be able to attach 400 uprobes
* successfully.
*
* Again, we are reusing usdt_100 BPF program.
*/
skel->links.usdt_100 = bpf_program__attach_usdt(skel->progs.usdt_100, -1,
"/proc/self/exe",
"test", "usdt_400", NULL);
if (!ASSERT_OK_PTR(skel->links.usdt_100, "usdt_400_attach"))
goto cleanup;
trigger_400_usdts();
ASSERT_EQ(bss->usdt_100_called, 400, "usdt_400_called");
ASSERT_EQ(bss->usdt_100_sum, 399 * 400 / 2, "usdt_400_sum");
cleanup:
test_usdt__destroy(skel);
}
static FILE *urand_spawn(int *pid)
{
FILE *f;
/* urandom_read's stdout is wired into f */
f = popen("./urandom_read 1 report-pid", "r");
if (!f)
return NULL;
if (fscanf(f, "%d", pid) != 1) {
pclose(f);
return NULL;
}
return f;
}
static int urand_trigger(FILE **urand_pipe)
{
int exit_code;
/* pclose() waits for child process to exit and returns their exit code */
exit_code = pclose(*urand_pipe);
*urand_pipe = NULL;
return exit_code;
}
static void subtest_urandom_usdt(bool auto_attach)
{
struct test_urandom_usdt *skel;
struct test_urandom_usdt__bss *bss;
struct bpf_link *l;
FILE *urand_pipe = NULL;
int err, urand_pid = 0;
skel = test_urandom_usdt__open_and_load();
if (!ASSERT_OK_PTR(skel, "skel_open"))
return;
urand_pipe = urand_spawn(&urand_pid);
if (!ASSERT_OK_PTR(urand_pipe, "urand_spawn"))
goto cleanup;
bss = skel->bss;
bss->urand_pid = urand_pid;
if (auto_attach) {
err = test_urandom_usdt__attach(skel);
if (!ASSERT_OK(err, "skel_auto_attach"))
goto cleanup;
} else {
l = bpf_program__attach_usdt(skel->progs.urand_read_without_sema,
urand_pid, "./urandom_read",
"urand", "read_without_sema", NULL);
if (!ASSERT_OK_PTR(l, "urand_without_sema_attach"))
goto cleanup;
skel->links.urand_read_without_sema = l;
l = bpf_program__attach_usdt(skel->progs.urand_read_with_sema,
urand_pid, "./urandom_read",
"urand", "read_with_sema", NULL);
if (!ASSERT_OK_PTR(l, "urand_with_sema_attach"))
goto cleanup;
skel->links.urand_read_with_sema = l;
l = bpf_program__attach_usdt(skel->progs.urandlib_read_without_sema,
urand_pid, "./liburandom_read.so",
"urandlib", "read_without_sema", NULL);
if (!ASSERT_OK_PTR(l, "urandlib_without_sema_attach"))
goto cleanup;
skel->links.urandlib_read_without_sema = l;
l = bpf_program__attach_usdt(skel->progs.urandlib_read_with_sema,
urand_pid, "./liburandom_read.so",
"urandlib", "read_with_sema", NULL);
if (!ASSERT_OK_PTR(l, "urandlib_with_sema_attach"))
goto cleanup;
skel->links.urandlib_read_with_sema = l;
}
/* trigger urandom_read USDTs */
ASSERT_OK(urand_trigger(&urand_pipe), "urand_exit_code");
ASSERT_EQ(bss->urand_read_without_sema_call_cnt, 1, "urand_wo_sema_cnt");
ASSERT_EQ(bss->urand_read_without_sema_buf_sz_sum, 256, "urand_wo_sema_sum");
ASSERT_EQ(bss->urand_read_with_sema_call_cnt, 1, "urand_w_sema_cnt");
ASSERT_EQ(bss->urand_read_with_sema_buf_sz_sum, 256, "urand_w_sema_sum");
ASSERT_EQ(bss->urandlib_read_without_sema_call_cnt, 1, "urandlib_wo_sema_cnt");
ASSERT_EQ(bss->urandlib_read_without_sema_buf_sz_sum, 256, "urandlib_wo_sema_sum");
ASSERT_EQ(bss->urandlib_read_with_sema_call_cnt, 1, "urandlib_w_sema_cnt");
ASSERT_EQ(bss->urandlib_read_with_sema_buf_sz_sum, 256, "urandlib_w_sema_sum");
cleanup:
if (urand_pipe)
pclose(urand_pipe);
test_urandom_usdt__destroy(skel);
}
void test_usdt(void)
{
if (test__start_subtest("basic"))
subtest_basic_usdt();
if (test__start_subtest("multispec"))
subtest_multispec_usdt();
if (test__start_subtest("urand_auto_attach"))
subtest_urandom_usdt(true /* auto_attach */);
if (test__start_subtest("urand_pid_attach"))
subtest_urandom_usdt(false /* auto_attach */);
}
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2022 Meta Platforms, Inc. and affiliates. */
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/usdt.bpf.h>
int urand_pid;
int urand_read_without_sema_call_cnt;
int urand_read_without_sema_buf_sz_sum;
SEC("usdt/./urandom_read:urand:read_without_sema")
int BPF_USDT(urand_read_without_sema, int iter_num, int iter_cnt, int buf_sz)
{
if (urand_pid != (bpf_get_current_pid_tgid() >> 32))
return 0;
__sync_fetch_and_add(&urand_read_without_sema_call_cnt, 1);
__sync_fetch_and_add(&urand_read_without_sema_buf_sz_sum, buf_sz);
return 0;
}
int urand_read_with_sema_call_cnt;
int urand_read_with_sema_buf_sz_sum;
SEC("usdt/./urandom_read:urand:read_with_sema")
int BPF_USDT(urand_read_with_sema, int iter_num, int iter_cnt, int buf_sz)
{
if (urand_pid != (bpf_get_current_pid_tgid() >> 32))
return 0;
__sync_fetch_and_add(&urand_read_with_sema_call_cnt, 1);
__sync_fetch_and_add(&urand_read_with_sema_buf_sz_sum, buf_sz);
return 0;
}
int urandlib_read_without_sema_call_cnt;
int urandlib_read_without_sema_buf_sz_sum;
SEC("usdt/./liburandom_read.so:urandlib:read_without_sema")
int BPF_USDT(urandlib_read_without_sema, int iter_num, int iter_cnt, int buf_sz)
{
if (urand_pid != (bpf_get_current_pid_tgid() >> 32))
return 0;
__sync_fetch_and_add(&urandlib_read_without_sema_call_cnt, 1);
__sync_fetch_and_add(&urandlib_read_without_sema_buf_sz_sum, buf_sz);
return 0;
}
int urandlib_read_with_sema_call_cnt;
int urandlib_read_with_sema_buf_sz_sum;
SEC("usdt/./liburandom_read.so:urandlib:read_with_sema")
int BPF_USDT(urandlib_read_with_sema, int iter_num, int iter_cnt, int buf_sz)
{
if (urand_pid != (bpf_get_current_pid_tgid() >> 32))
return 0;
__sync_fetch_and_add(&urandlib_read_with_sema_call_cnt, 1);
__sync_fetch_and_add(&urandlib_read_with_sema_buf_sz_sum, buf_sz);
return 0;
}
char _license[] SEC("license") = "GPL";
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2022 Meta Platforms, Inc. and affiliates. */
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/usdt.bpf.h>
int my_pid;
int usdt0_called;
u64 usdt0_cookie;
int usdt0_arg_cnt;
int usdt0_arg_ret;
SEC("usdt")
int usdt0(struct pt_regs *ctx)
{
long tmp;
if (my_pid != (bpf_get_current_pid_tgid() >> 32))
return 0;
__sync_fetch_and_add(&usdt0_called, 1);
usdt0_cookie = bpf_usdt_cookie(ctx);
usdt0_arg_cnt = bpf_usdt_arg_cnt(ctx);
/* should return -ENOENT for any arg_num */
usdt0_arg_ret = bpf_usdt_arg(ctx, bpf_get_prandom_u32(), &tmp);
return 0;
}
int usdt3_called;
u64 usdt3_cookie;
int usdt3_arg_cnt;
int usdt3_arg_rets[3];
u64 usdt3_args[3];
SEC("usdt//proc/self/exe:test:usdt3")
int usdt3(struct pt_regs *ctx)
{
long tmp;
if (my_pid != (bpf_get_current_pid_tgid() >> 32))
return 0;
__sync_fetch_and_add(&usdt3_called, 1);
usdt3_cookie = bpf_usdt_cookie(ctx);
usdt3_arg_cnt = bpf_usdt_arg_cnt(ctx);
usdt3_arg_rets[0] = bpf_usdt_arg(ctx, 0, &tmp);
usdt3_args[0] = (int)tmp;
usdt3_arg_rets[1] = bpf_usdt_arg(ctx, 1, &tmp);
usdt3_args[1] = (long)tmp;
usdt3_arg_rets[2] = bpf_usdt_arg(ctx, 2, &tmp);
usdt3_args[2] = (uintptr_t)tmp;
return 0;
}
int usdt12_called;
u64 usdt12_cookie;
int usdt12_arg_cnt;
u64 usdt12_args[12];
SEC("usdt//proc/self/exe:test:usdt12")
int BPF_USDT(usdt12, int a1, int a2, long a3, long a4, unsigned a5,
long a6, __u64 a7, uintptr_t a8, int a9, short a10,
short a11, signed char a12)
{
if (my_pid != (bpf_get_current_pid_tgid() >> 32))
return 0;
__sync_fetch_and_add(&usdt12_called, 1);
usdt12_cookie = bpf_usdt_cookie(ctx);
usdt12_arg_cnt = bpf_usdt_arg_cnt(ctx);
usdt12_args[0] = a1;
usdt12_args[1] = a2;
usdt12_args[2] = a3;
usdt12_args[3] = a4;
usdt12_args[4] = a5;
usdt12_args[5] = a6;
usdt12_args[6] = a7;
usdt12_args[7] = a8;
usdt12_args[8] = a9;
usdt12_args[9] = a10;
usdt12_args[10] = a11;
usdt12_args[11] = a12;
return 0;
}
char _license[] SEC("license") = "GPL";
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2022 Meta Platforms, Inc. and affiliates. */
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/usdt.bpf.h>
/* this file is linked together with test_usdt.c to validate that usdt.bpf.h
* can be included in multiple .bpf.c files forming single final BPF object
* file
*/
extern int my_pid;
int usdt_100_called;
int usdt_100_sum;
SEC("usdt//proc/self/exe:test:usdt_100")
int BPF_USDT(usdt_100, int x)
{
long tmp;
if (my_pid != (bpf_get_current_pid_tgid() >> 32))
return 0;
__sync_fetch_and_add(&usdt_100_called, 1);
__sync_fetch_and_add(&usdt_100_sum, x);
return 0;
}
char _license[] SEC("license") = "GPL";
/* includes/sys/sdt-config.h. Generated from sdt-config.h.in by configure.
This file just defines _SDT_ASM_SECTION_AUTOGROUP_SUPPORT to 0 or 1 to
indicate whether the assembler supports "?" in .pushsection directives. */
#define _SDT_ASM_SECTION_AUTOGROUP_SUPPORT 1
/* <sys/sdt.h> - Systemtap static probe definition macros.
This file is dedicated to the public domain, pursuant to CC0
(https://creativecommons.org/publicdomain/zero/1.0/)
*/
#ifndef _SYS_SDT_H
#define _SYS_SDT_H 1
/*
This file defines a family of macros
STAP_PROBEn(op1, ..., opn)
that emit a nop into the instruction stream, and some data into an auxiliary
note section. The data in the note section describes the operands, in terms
of size and location. Each location is encoded as assembler operand string.
Consumer tools such as gdb or systemtap insert breakpoints on top of
the nop, and decode the location operand-strings, like an assembler,
to find the values being passed.
The operand strings are selected by the compiler for each operand.
They are constrained by gcc inline-assembler codes. The default is:
#define STAP_SDT_ARG_CONSTRAINT nor
This is a good default if the operands tend to be integral and
moderate in number (smaller than number of registers). In other
cases, the compiler may report "'asm' requires impossible reload" or
similar. In this case, consider simplifying the macro call (fewer
and simpler operands), reduce optimization, or override the default
constraints string via:
#define STAP_SDT_ARG_CONSTRAINT g
#include <sys/sdt.h>
See also:
https://sourceware.org/systemtap/wiki/UserSpaceProbeImplementation
https://gcc.gnu.org/onlinedocs/gcc/Constraints.html
*/
#ifdef __ASSEMBLER__
# define _SDT_PROBE(provider, name, n, arglist) \
_SDT_ASM_BODY(provider, name, _SDT_ASM_SUBSTR_1, (_SDT_DEPAREN_##n arglist)) \
_SDT_ASM_BASE
# define _SDT_ASM_1(x) x;
# define _SDT_ASM_2(a, b) a,b;
# define _SDT_ASM_3(a, b, c) a,b,c;
# define _SDT_ASM_5(a, b, c, d, e) a,b,c,d,e;
# define _SDT_ASM_STRING_1(x) .asciz #x;
# define _SDT_ASM_SUBSTR_1(x) .ascii #x;
# define _SDT_DEPAREN_0() /* empty */
# define _SDT_DEPAREN_1(a) a
# define _SDT_DEPAREN_2(a,b) a b
# define _SDT_DEPAREN_3(a,b,c) a b c
# define _SDT_DEPAREN_4(a,b,c,d) a b c d
# define _SDT_DEPAREN_5(a,b,c,d,e) a b c d e
# define _SDT_DEPAREN_6(a,b,c,d,e,f) a b c d e f
# define _SDT_DEPAREN_7(a,b,c,d,e,f,g) a b c d e f g
# define _SDT_DEPAREN_8(a,b,c,d,e,f,g,h) a b c d e f g h
# define _SDT_DEPAREN_9(a,b,c,d,e,f,g,h,i) a b c d e f g h i
# define _SDT_DEPAREN_10(a,b,c,d,e,f,g,h,i,j) a b c d e f g h i j
# define _SDT_DEPAREN_11(a,b,c,d,e,f,g,h,i,j,k) a b c d e f g h i j k
# define _SDT_DEPAREN_12(a,b,c,d,e,f,g,h,i,j,k,l) a b c d e f g h i j k l
#else
#if defined _SDT_HAS_SEMAPHORES
#define _SDT_NOTE_SEMAPHORE_USE(provider, name) \
__asm__ __volatile__ ("" :: "m" (provider##_##name##_semaphore));
#else
#define _SDT_NOTE_SEMAPHORE_USE(provider, name)
#endif
# define _SDT_PROBE(provider, name, n, arglist) \
do { \
_SDT_NOTE_SEMAPHORE_USE(provider, name); \
__asm__ __volatile__ (_SDT_ASM_BODY(provider, name, _SDT_ASM_ARGS, (n)) \
:: _SDT_ASM_OPERANDS_##n arglist); \
__asm__ __volatile__ (_SDT_ASM_BASE); \
} while (0)
# define _SDT_S(x) #x
# define _SDT_ASM_1(x) _SDT_S(x) "\n"
# define _SDT_ASM_2(a, b) _SDT_S(a) "," _SDT_S(b) "\n"
# define _SDT_ASM_3(a, b, c) _SDT_S(a) "," _SDT_S(b) "," \
_SDT_S(c) "\n"
# define _SDT_ASM_5(a, b, c, d, e) _SDT_S(a) "," _SDT_S(b) "," \
_SDT_S(c) "," _SDT_S(d) "," \
_SDT_S(e) "\n"
# define _SDT_ASM_ARGS(n) _SDT_ASM_TEMPLATE_##n
# define _SDT_ASM_STRING_1(x) _SDT_ASM_1(.asciz #x)
# define _SDT_ASM_SUBSTR_1(x) _SDT_ASM_1(.ascii #x)
# define _SDT_ARGFMT(no) _SDT_ASM_1(_SDT_SIGN %n[_SDT_S##no]) \
_SDT_ASM_1(_SDT_SIZE %n[_SDT_S##no]) \
_SDT_ASM_1(_SDT_TYPE %n[_SDT_S##no]) \
_SDT_ASM_SUBSTR(_SDT_ARGTMPL(_SDT_A##no))
# ifndef STAP_SDT_ARG_CONSTRAINT
# if defined __powerpc__
# define STAP_SDT_ARG_CONSTRAINT nZr
# elif defined __arm__
# define STAP_SDT_ARG_CONSTRAINT g
# else
# define STAP_SDT_ARG_CONSTRAINT nor
# endif
# endif
# define _SDT_STRINGIFY(x) #x
# define _SDT_ARG_CONSTRAINT_STRING(x) _SDT_STRINGIFY(x)
/* _SDT_S encodes the size and type as 0xSSTT which is decoded by the assembler
macros _SDT_SIZE and _SDT_TYPE */
# define _SDT_ARG(n, x) \
[_SDT_S##n] "n" ((_SDT_ARGSIGNED (x) ? (int)-1 : 1) * (-(((int) _SDT_ARGSIZE (x)) << 8) + (-(0x7f & __builtin_classify_type (x))))), \
[_SDT_A##n] _SDT_ARG_CONSTRAINT_STRING (STAP_SDT_ARG_CONSTRAINT) (_SDT_ARGVAL (x))
#endif
#define _SDT_ASM_STRING(x) _SDT_ASM_STRING_1(x)
#define _SDT_ASM_SUBSTR(x) _SDT_ASM_SUBSTR_1(x)
#define _SDT_ARGARRAY(x) (__builtin_classify_type (x) == 14 \
|| __builtin_classify_type (x) == 5)
#ifdef __cplusplus
# define _SDT_ARGSIGNED(x) (!_SDT_ARGARRAY (x) \
&& __sdt_type<__typeof (x)>::__sdt_signed)
# define _SDT_ARGSIZE(x) (_SDT_ARGARRAY (x) \
? sizeof (void *) : sizeof (x))
# define _SDT_ARGVAL(x) (x)
# include <cstddef>
template<typename __sdt_T>
struct __sdt_type
{
static const bool __sdt_signed = false;
};
#define __SDT_ALWAYS_SIGNED(T) \
template<> struct __sdt_type<T> { static const bool __sdt_signed = true; };
#define __SDT_COND_SIGNED(T,CT) \
template<> struct __sdt_type<T> { static const bool __sdt_signed = ((CT)(-1) < 1); };
__SDT_ALWAYS_SIGNED(signed char)
__SDT_ALWAYS_SIGNED(short)
__SDT_ALWAYS_SIGNED(int)
__SDT_ALWAYS_SIGNED(long)
__SDT_ALWAYS_SIGNED(long long)
__SDT_ALWAYS_SIGNED(volatile signed char)
__SDT_ALWAYS_SIGNED(volatile short)
__SDT_ALWAYS_SIGNED(volatile int)
__SDT_ALWAYS_SIGNED(volatile long)
__SDT_ALWAYS_SIGNED(volatile long long)
__SDT_ALWAYS_SIGNED(const signed char)
__SDT_ALWAYS_SIGNED(const short)
__SDT_ALWAYS_SIGNED(const int)
__SDT_ALWAYS_SIGNED(const long)
__SDT_ALWAYS_SIGNED(const long long)
__SDT_ALWAYS_SIGNED(const volatile signed char)
__SDT_ALWAYS_SIGNED(const volatile short)
__SDT_ALWAYS_SIGNED(const volatile int)
__SDT_ALWAYS_SIGNED(const volatile long)
__SDT_ALWAYS_SIGNED(const volatile long long)
__SDT_COND_SIGNED(char, char)
__SDT_COND_SIGNED(wchar_t, wchar_t)
__SDT_COND_SIGNED(volatile char, char)
__SDT_COND_SIGNED(volatile wchar_t, wchar_t)
__SDT_COND_SIGNED(const char, char)
__SDT_COND_SIGNED(const wchar_t, wchar_t)
__SDT_COND_SIGNED(const volatile char, char)
__SDT_COND_SIGNED(const volatile wchar_t, wchar_t)
#if defined (__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 4))
/* __SDT_COND_SIGNED(char16_t) */
/* __SDT_COND_SIGNED(char32_t) */
#endif
template<typename __sdt_E>
struct __sdt_type<__sdt_E[]> : public __sdt_type<__sdt_E *> {};
template<typename __sdt_E, size_t __sdt_N>
struct __sdt_type<__sdt_E[__sdt_N]> : public __sdt_type<__sdt_E *> {};
#elif !defined(__ASSEMBLER__)
__extension__ extern unsigned long long __sdt_unsp;
# define _SDT_ARGINTTYPE(x) \
__typeof (__builtin_choose_expr (((__builtin_classify_type (x) \
+ 3) & -4) == 4, (x), 0U))
# define _SDT_ARGSIGNED(x) \
(!__extension__ \
(__builtin_constant_p ((((unsigned long long) \
(_SDT_ARGINTTYPE (x)) __sdt_unsp) \
& ((unsigned long long)1 << (sizeof (unsigned long long) \
* __CHAR_BIT__ - 1))) == 0) \
|| (_SDT_ARGINTTYPE (x)) -1 > (_SDT_ARGINTTYPE (x)) 0))
# define _SDT_ARGSIZE(x) \
(_SDT_ARGARRAY (x) ? sizeof (void *) : sizeof (x))
# define _SDT_ARGVAL(x) (x)
#endif
#if defined __powerpc__ || defined __powerpc64__
# define _SDT_ARGTMPL(id) %I[id]%[id]
#elif defined __i386__
# define _SDT_ARGTMPL(id) %k[id] /* gcc.gnu.org/PR80115 sourceware.org/PR24541 */
#else
# define _SDT_ARGTMPL(id) %[id]
#endif
/* NB: gdb PR24541 highlighted an unspecified corner of the sdt.h
operand note format.
The named register may be a longer or shorter (!) alias for the
storage where the value in question is found. For example, on
i386, 64-bit value may be put in register pairs, and the register
name stored would identify just one of them. Previously, gcc was
asked to emit the %w[id] (16-bit alias of some registers holding
operands), even when a wider 32-bit value was used.
Bottom line: the byte-width given before the @ sign governs. If
there is a mismatch between that width and that of the named
register, then a sys/sdt.h note consumer may need to employ
architecture-specific heuristics to figure out where the compiler
has actually put the complete value.
*/
#ifdef __LP64__
# define _SDT_ASM_ADDR .8byte
#else
# define _SDT_ASM_ADDR .4byte
#endif
/* The ia64 and s390 nop instructions take an argument. */
#if defined(__ia64__) || defined(__s390__) || defined(__s390x__)
#define _SDT_NOP nop 0
#else
#define _SDT_NOP nop
#endif
#define _SDT_NOTE_NAME "stapsdt"
#define _SDT_NOTE_TYPE 3
/* If the assembler supports the necessary feature, then we can play
nice with code in COMDAT sections, which comes up in C++ code.
Without that assembler support, some combinations of probe placements
in certain kinds of C++ code may produce link-time errors. */
#include "sdt-config.h"
#if _SDT_ASM_SECTION_AUTOGROUP_SUPPORT
# define _SDT_ASM_AUTOGROUP "?"
#else
# define _SDT_ASM_AUTOGROUP ""
#endif
#define _SDT_DEF_MACROS \
_SDT_ASM_1(.altmacro) \
_SDT_ASM_1(.macro _SDT_SIGN x) \
_SDT_ASM_3(.pushsection .note.stapsdt,"","note") \
_SDT_ASM_1(.iflt \\x) \
_SDT_ASM_1(.ascii "-") \
_SDT_ASM_1(.endif) \
_SDT_ASM_1(.popsection) \
_SDT_ASM_1(.endm) \
_SDT_ASM_1(.macro _SDT_SIZE_ x) \
_SDT_ASM_3(.pushsection .note.stapsdt,"","note") \
_SDT_ASM_1(.ascii "\x") \
_SDT_ASM_1(.popsection) \
_SDT_ASM_1(.endm) \
_SDT_ASM_1(.macro _SDT_SIZE x) \
_SDT_ASM_1(_SDT_SIZE_ %%((-(-\\x*((-\\x>0)-(-\\x<0))))>>8)) \
_SDT_ASM_1(.endm) \
_SDT_ASM_1(.macro _SDT_TYPE_ x) \
_SDT_ASM_3(.pushsection .note.stapsdt,"","note") \
_SDT_ASM_2(.ifc 8,\\x) \
_SDT_ASM_1(.ascii "f") \
_SDT_ASM_1(.endif) \
_SDT_ASM_1(.ascii "@") \
_SDT_ASM_1(.popsection) \
_SDT_ASM_1(.endm) \
_SDT_ASM_1(.macro _SDT_TYPE x) \
_SDT_ASM_1(_SDT_TYPE_ %%((\\x)&(0xff))) \
_SDT_ASM_1(.endm)
#define _SDT_UNDEF_MACROS \
_SDT_ASM_1(.purgem _SDT_SIGN) \
_SDT_ASM_1(.purgem _SDT_SIZE_) \
_SDT_ASM_1(.purgem _SDT_SIZE) \
_SDT_ASM_1(.purgem _SDT_TYPE_) \
_SDT_ASM_1(.purgem _SDT_TYPE)
#define _SDT_ASM_BODY(provider, name, pack_args, args, ...) \
_SDT_DEF_MACROS \
_SDT_ASM_1(990: _SDT_NOP) \
_SDT_ASM_3( .pushsection .note.stapsdt,_SDT_ASM_AUTOGROUP,"note") \
_SDT_ASM_1( .balign 4) \
_SDT_ASM_3( .4byte 992f-991f, 994f-993f, _SDT_NOTE_TYPE) \
_SDT_ASM_1(991: .asciz _SDT_NOTE_NAME) \
_SDT_ASM_1(992: .balign 4) \
_SDT_ASM_1(993: _SDT_ASM_ADDR 990b) \
_SDT_ASM_1( _SDT_ASM_ADDR _.stapsdt.base) \
_SDT_SEMAPHORE(provider,name) \
_SDT_ASM_STRING(provider) \
_SDT_ASM_STRING(name) \
pack_args args \
_SDT_ASM_SUBSTR(\x00) \
_SDT_UNDEF_MACROS \
_SDT_ASM_1(994: .balign 4) \
_SDT_ASM_1( .popsection)
#define _SDT_ASM_BASE \
_SDT_ASM_1(.ifndef _.stapsdt.base) \
_SDT_ASM_5( .pushsection .stapsdt.base,"aG","progbits", \
.stapsdt.base,comdat) \
_SDT_ASM_1( .weak _.stapsdt.base) \
_SDT_ASM_1( .hidden _.stapsdt.base) \
_SDT_ASM_1( _.stapsdt.base: .space 1) \
_SDT_ASM_2( .size _.stapsdt.base, 1) \
_SDT_ASM_1( .popsection) \
_SDT_ASM_1(.endif)
#if defined _SDT_HAS_SEMAPHORES
#define _SDT_SEMAPHORE(p,n) \
_SDT_ASM_1( _SDT_ASM_ADDR p##_##n##_semaphore)
#else
#define _SDT_SEMAPHORE(p,n) _SDT_ASM_1( _SDT_ASM_ADDR 0)
#endif
#define _SDT_ASM_BLANK _SDT_ASM_SUBSTR(\x20)
#define _SDT_ASM_TEMPLATE_0 /* no arguments */
#define _SDT_ASM_TEMPLATE_1 _SDT_ARGFMT(1)
#define _SDT_ASM_TEMPLATE_2 _SDT_ASM_TEMPLATE_1 _SDT_ASM_BLANK _SDT_ARGFMT(2)
#define _SDT_ASM_TEMPLATE_3 _SDT_ASM_TEMPLATE_2 _SDT_ASM_BLANK _SDT_ARGFMT(3)
#define _SDT_ASM_TEMPLATE_4 _SDT_ASM_TEMPLATE_3 _SDT_ASM_BLANK _SDT_ARGFMT(4)
#define _SDT_ASM_TEMPLATE_5 _SDT_ASM_TEMPLATE_4 _SDT_ASM_BLANK _SDT_ARGFMT(5)
#define _SDT_ASM_TEMPLATE_6 _SDT_ASM_TEMPLATE_5 _SDT_ASM_BLANK _SDT_ARGFMT(6)
#define _SDT_ASM_TEMPLATE_7 _SDT_ASM_TEMPLATE_6 _SDT_ASM_BLANK _SDT_ARGFMT(7)
#define _SDT_ASM_TEMPLATE_8 _SDT_ASM_TEMPLATE_7 _SDT_ASM_BLANK _SDT_ARGFMT(8)
#define _SDT_ASM_TEMPLATE_9 _SDT_ASM_TEMPLATE_8 _SDT_ASM_BLANK _SDT_ARGFMT(9)
#define _SDT_ASM_TEMPLATE_10 _SDT_ASM_TEMPLATE_9 _SDT_ASM_BLANK _SDT_ARGFMT(10)
#define _SDT_ASM_TEMPLATE_11 _SDT_ASM_TEMPLATE_10 _SDT_ASM_BLANK _SDT_ARGFMT(11)
#define _SDT_ASM_TEMPLATE_12 _SDT_ASM_TEMPLATE_11 _SDT_ASM_BLANK _SDT_ARGFMT(12)
#define _SDT_ASM_OPERANDS_0() [__sdt_dummy] "g" (0)
#define _SDT_ASM_OPERANDS_1(arg1) _SDT_ARG(1, arg1)
#define _SDT_ASM_OPERANDS_2(arg1, arg2) \
_SDT_ASM_OPERANDS_1(arg1), _SDT_ARG(2, arg2)
#define _SDT_ASM_OPERANDS_3(arg1, arg2, arg3) \
_SDT_ASM_OPERANDS_2(arg1, arg2), _SDT_ARG(3, arg3)
#define _SDT_ASM_OPERANDS_4(arg1, arg2, arg3, arg4) \
_SDT_ASM_OPERANDS_3(arg1, arg2, arg3), _SDT_ARG(4, arg4)
#define _SDT_ASM_OPERANDS_5(arg1, arg2, arg3, arg4, arg5) \
_SDT_ASM_OPERANDS_4(arg1, arg2, arg3, arg4), _SDT_ARG(5, arg5)
#define _SDT_ASM_OPERANDS_6(arg1, arg2, arg3, arg4, arg5, arg6) \
_SDT_ASM_OPERANDS_5(arg1, arg2, arg3, arg4, arg5), _SDT_ARG(6, arg6)
#define _SDT_ASM_OPERANDS_7(arg1, arg2, arg3, arg4, arg5, arg6, arg7) \
_SDT_ASM_OPERANDS_6(arg1, arg2, arg3, arg4, arg5, arg6), _SDT_ARG(7, arg7)
#define _SDT_ASM_OPERANDS_8(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8) \
_SDT_ASM_OPERANDS_7(arg1, arg2, arg3, arg4, arg5, arg6, arg7), \
_SDT_ARG(8, arg8)
#define _SDT_ASM_OPERANDS_9(arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9) \
_SDT_ASM_OPERANDS_8(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8), \
_SDT_ARG(9, arg9)
#define _SDT_ASM_OPERANDS_10(arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9,arg10) \
_SDT_ASM_OPERANDS_9(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9), \
_SDT_ARG(10, arg10)
#define _SDT_ASM_OPERANDS_11(arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9,arg10,arg11) \
_SDT_ASM_OPERANDS_10(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10), \
_SDT_ARG(11, arg11)
#define _SDT_ASM_OPERANDS_12(arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9,arg10,arg11,arg12) \
_SDT_ASM_OPERANDS_11(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11), \
_SDT_ARG(12, arg12)
/* These macros can be used in C, C++, or assembly code.
In assembly code the arguments should use normal assembly operand syntax. */
#define STAP_PROBE(provider, name) \
_SDT_PROBE(provider, name, 0, ())
#define STAP_PROBE1(provider, name, arg1) \
_SDT_PROBE(provider, name, 1, (arg1))
#define STAP_PROBE2(provider, name, arg1, arg2) \
_SDT_PROBE(provider, name, 2, (arg1, arg2))
#define STAP_PROBE3(provider, name, arg1, arg2, arg3) \
_SDT_PROBE(provider, name, 3, (arg1, arg2, arg3))
#define STAP_PROBE4(provider, name, arg1, arg2, arg3, arg4) \
_SDT_PROBE(provider, name, 4, (arg1, arg2, arg3, arg4))
#define STAP_PROBE5(provider, name, arg1, arg2, arg3, arg4, arg5) \
_SDT_PROBE(provider, name, 5, (arg1, arg2, arg3, arg4, arg5))
#define STAP_PROBE6(provider, name, arg1, arg2, arg3, arg4, arg5, arg6) \
_SDT_PROBE(provider, name, 6, (arg1, arg2, arg3, arg4, arg5, arg6))
#define STAP_PROBE7(provider, name, arg1, arg2, arg3, arg4, arg5, arg6, arg7) \
_SDT_PROBE(provider, name, 7, (arg1, arg2, arg3, arg4, arg5, arg6, arg7))
#define STAP_PROBE8(provider,name,arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8) \
_SDT_PROBE(provider, name, 8, (arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8))
#define STAP_PROBE9(provider,name,arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9)\
_SDT_PROBE(provider, name, 9, (arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9))
#define STAP_PROBE10(provider,name,arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9,arg10) \
_SDT_PROBE(provider, name, 10, \
(arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9,arg10))
#define STAP_PROBE11(provider,name,arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9,arg10,arg11) \
_SDT_PROBE(provider, name, 11, \
(arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9,arg10,arg11))
#define STAP_PROBE12(provider,name,arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9,arg10,arg11,arg12) \
_SDT_PROBE(provider, name, 12, \
(arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9,arg10,arg11,arg12))
/* This STAP_PROBEV macro can be used in variadic scenarios, where the
number of probe arguments is not known until compile time. Since
variadic macro support may vary with compiler options, you must
pre-#define SDT_USE_VARIADIC to enable this type of probe.
The trick to count __VA_ARGS__ was inspired by this post by
Laurent Deniau <laurent.deniau@cern.ch>:
http://groups.google.com/group/comp.std.c/msg/346fc464319b1ee5
Note that our _SDT_NARG is called with an extra 0 arg that's not
counted, so we don't have to worry about the behavior of macros
called without any arguments. */
#define _SDT_NARG(...) __SDT_NARG(__VA_ARGS__, 12,11,10,9,8,7,6,5,4,3,2,1,0)
#define __SDT_NARG(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12, N, ...) N
#ifdef SDT_USE_VARIADIC
#define _SDT_PROBE_N(provider, name, N, ...) \
_SDT_PROBE(provider, name, N, (__VA_ARGS__))
#define STAP_PROBEV(provider, name, ...) \
_SDT_PROBE_N(provider, name, _SDT_NARG(0, ##__VA_ARGS__), ##__VA_ARGS__)
#endif
/* These macros are for use in asm statements. You must compile
with -std=gnu99 or -std=c99 to use the STAP_PROBE_ASM macro.
The STAP_PROBE_ASM macro generates a quoted string to be used in the
template portion of the asm statement, concatenated with strings that
contain the actual assembly code around the probe site.
For example:
asm ("before\n"
STAP_PROBE_ASM(provider, fooprobe, %eax 4(%esi))
"after");
emits the assembly code for "before\nafter", with a probe in between.
The probe arguments are the %eax register, and the value of the memory
word located 4 bytes past the address in the %esi register. Note that
because this is a simple asm, not a GNU C extended asm statement, these
% characters do not need to be doubled to generate literal %reg names.
In a GNU C extended asm statement, the probe arguments can be specified
using the macro STAP_PROBE_ASM_TEMPLATE(n) for n arguments. The paired
macro STAP_PROBE_ASM_OPERANDS gives the C values of these probe arguments,
and appears in the input operand list of the asm statement. For example:
asm ("someinsn %0,%1\n" // %0 is output operand, %1 is input operand
STAP_PROBE_ASM(provider, fooprobe, STAP_PROBE_ASM_TEMPLATE(3))
"otherinsn %[namedarg]"
: "r" (outvar)
: "g" (some_value), [namedarg] "i" (1234),
STAP_PROBE_ASM_OPERANDS(3, some_value, some_ptr->field, 1234));
This is just like writing:
STAP_PROBE3(provider, fooprobe, some_value, some_ptr->field, 1234));
but the probe site is right between "someinsn" and "otherinsn".
The probe arguments in STAP_PROBE_ASM can be given as assembly
operands instead, even inside a GNU C extended asm statement.
Note that these can use operand templates like %0 or %[name],
and likewise they must write %%reg for a literal operand of %reg. */
#define _SDT_ASM_BODY_1(p,n,...) _SDT_ASM_BODY(p,n,_SDT_ASM_SUBSTR,(__VA_ARGS__))
#define _SDT_ASM_BODY_2(p,n,...) _SDT_ASM_BODY(p,n,/*_SDT_ASM_STRING */,__VA_ARGS__)
#define _SDT_ASM_BODY_N2(p,n,no,...) _SDT_ASM_BODY_ ## no(p,n,__VA_ARGS__)
#define _SDT_ASM_BODY_N1(p,n,no,...) _SDT_ASM_BODY_N2(p,n,no,__VA_ARGS__)
#define _SDT_ASM_BODY_N(p,n,...) _SDT_ASM_BODY_N1(p,n,_SDT_NARG(0, __VA_ARGS__),__VA_ARGS__)
#if __STDC_VERSION__ >= 199901L
# define STAP_PROBE_ASM(provider, name, ...) \
_SDT_ASM_BODY_N(provider, name, __VA_ARGS__) \
_SDT_ASM_BASE
# define STAP_PROBE_ASM_OPERANDS(n, ...) _SDT_ASM_OPERANDS_##n(__VA_ARGS__)
#else
# define STAP_PROBE_ASM(provider, name, args) \
_SDT_ASM_BODY(provider, name, /* _SDT_ASM_STRING */, (args)) \
_SDT_ASM_BASE
#endif
#define STAP_PROBE_ASM_TEMPLATE(n) _SDT_ASM_TEMPLATE_##n,"use _SDT_ASM_TEMPLATE_"
/* DTrace compatible macro names. */
#define DTRACE_PROBE(provider,probe) \
STAP_PROBE(provider,probe)
#define DTRACE_PROBE1(provider,probe,parm1) \
STAP_PROBE1(provider,probe,parm1)
#define DTRACE_PROBE2(provider,probe,parm1,parm2) \
STAP_PROBE2(provider,probe,parm1,parm2)
#define DTRACE_PROBE3(provider,probe,parm1,parm2,parm3) \
STAP_PROBE3(provider,probe,parm1,parm2,parm3)
#define DTRACE_PROBE4(provider,probe,parm1,parm2,parm3,parm4) \
STAP_PROBE4(provider,probe,parm1,parm2,parm3,parm4)
#define DTRACE_PROBE5(provider,probe,parm1,parm2,parm3,parm4,parm5) \
STAP_PROBE5(provider,probe,parm1,parm2,parm3,parm4,parm5)
#define DTRACE_PROBE6(provider,probe,parm1,parm2,parm3,parm4,parm5,parm6) \
STAP_PROBE6(provider,probe,parm1,parm2,parm3,parm4,parm5,parm6)
#define DTRACE_PROBE7(provider,probe,parm1,parm2,parm3,parm4,parm5,parm6,parm7) \
STAP_PROBE7(provider,probe,parm1,parm2,parm3,parm4,parm5,parm6,parm7)
#define DTRACE_PROBE8(provider,probe,parm1,parm2,parm3,parm4,parm5,parm6,parm7,parm8) \
STAP_PROBE8(provider,probe,parm1,parm2,parm3,parm4,parm5,parm6,parm7,parm8)
#define DTRACE_PROBE9(provider,probe,parm1,parm2,parm3,parm4,parm5,parm6,parm7,parm8,parm9) \
STAP_PROBE9(provider,probe,parm1,parm2,parm3,parm4,parm5,parm6,parm7,parm8,parm9)
#define DTRACE_PROBE10(provider,probe,parm1,parm2,parm3,parm4,parm5,parm6,parm7,parm8,parm9,parm10) \
STAP_PROBE10(provider,probe,parm1,parm2,parm3,parm4,parm5,parm6,parm7,parm8,parm9,parm10)
#define DTRACE_PROBE11(provider,probe,parm1,parm2,parm3,parm4,parm5,parm6,parm7,parm8,parm9,parm10,parm11) \
STAP_PROBE11(provider,probe,parm1,parm2,parm3,parm4,parm5,parm6,parm7,parm8,parm9,parm10,parm11)
#define DTRACE_PROBE12(provider,probe,parm1,parm2,parm3,parm4,parm5,parm6,parm7,parm8,parm9,parm10,parm11,parm12) \
STAP_PROBE12(provider,probe,parm1,parm2,parm3,parm4,parm5,parm6,parm7,parm8,parm9,parm10,parm11,parm12)
#endif /* sys/sdt.h */
#include <stdbool.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <signal.h>
#define _SDT_HAS_SEMAPHORES 1
#include "sdt.h"
#define SEC(name) __attribute__((section(name), used))
#define BUF_SIZE 256
/* defined in urandom_read_aux.c */
void urand_read_without_sema(int iter_num, int iter_cnt, int read_sz);
/* these are coming from urandom_read_lib{1,2}.c */
void urandlib_read_with_sema(int iter_num, int iter_cnt, int read_sz);
void urandlib_read_without_sema(int iter_num, int iter_cnt, int read_sz);
unsigned short urand_read_with_sema_semaphore SEC(".probes");
static __attribute__((noinline))
void urandom_read(int fd, int count)
{
char buf[BUF_SIZE];
int i;
for (i = 0; i < count; ++i)
for (i = 0; i < count; ++i) {
read(fd, buf, BUF_SIZE);
/* trigger USDTs defined in executable itself */
urand_read_without_sema(i, count, BUF_SIZE);
STAP_PROBE3(urand, read_with_sema, i, count, BUF_SIZE);
/* trigger USDTs defined in shared lib */
urandlib_read_without_sema(i, count, BUF_SIZE);
urandlib_read_with_sema(i, count, BUF_SIZE);
}
}
static volatile bool parent_ready;
static void handle_sigpipe(int sig)
{
parent_ready = true;
}
int main(int argc, char *argv[])
{
int fd = open("/dev/urandom", O_RDONLY);
int count = 4;
bool report_pid = false;
if (fd < 0)
return 1;
if (argc == 2)
if (argc >= 2)
count = atoi(argv[1]);
if (argc >= 3) {
report_pid = true;
/* install SIGPIPE handler to catch when parent closes their
* end of the pipe (on the other side of our stdout)
*/
signal(SIGPIPE, handle_sigpipe);
}
/* report PID and wait for parent process to send us "signal" by
* closing stdout
*/
if (report_pid) {
while (!parent_ready) {
fprintf(stdout, "%d\n", getpid());
fflush(stdout);
}
/* at this point stdout is closed, parent process knows our
* PID and is ready to trace us
*/
}
urandom_read(fd, count);
......
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2022 Meta Platforms, Inc. and affiliates. */
#include "sdt.h"
void urand_read_without_sema(int iter_num, int iter_cnt, int read_sz)
{
/* semaphore-less USDT */
STAP_PROBE3(urand, read_without_sema, iter_num, iter_cnt, read_sz);
}
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2022 Meta Platforms, Inc. and affiliates. */
#define _SDT_HAS_SEMAPHORES 1
#include "sdt.h"
#define SEC(name) __attribute__((section(name), used))
unsigned short urandlib_read_with_sema_semaphore SEC(".probes");
void urandlib_read_with_sema(int iter_num, int iter_cnt, int read_sz)
{
STAP_PROBE3(urandlib, read_with_sema, iter_num, iter_cnt, read_sz);
}
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2022 Meta Platforms, Inc. and affiliates. */
#include "sdt.h"
void urandlib_read_without_sema(int iter_num, int iter_cnt, int read_sz)
{
STAP_PROBE3(urandlib, read_without_sema, iter_num, iter_cnt, read_sz);
}
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