Skip to content

Commit 2f38fe6

Browse files
anakryikoAlexei Starovoitov
authored andcommitted
libbpf: implement __arg_ctx fallback logic
Out of all special global func arg tag annotations, __arg_ctx is practically is the most immediately useful and most critical to have working across multitude kernel version, if possible. This would allow end users to write much simpler code if __arg_ctx semantics worked for older kernels that don't natively understand btf_decl_tag("arg:ctx") in verifier logic. Luckily, it is possible to ensure __arg_ctx works on old kernels through a bit of extra work done by libbpf, at least in a lot of common cases. To explain the overall idea, we need to go back at how context argument was supported in global funcs before __arg_ctx support was added. This was done based on special struct name checks in kernel. E.g., for BPF_PROG_TYPE_PERF_EVENT the expectation is that argument type `struct bpf_perf_event_data *` mark that argument as PTR_TO_CTX. This is all good as long as global function is used from the same BPF program types only, which is often not the case. If the same subprog has to be called from, say, kprobe and perf_event program types, there is no single definition that would satisfy BPF verifier. Subprog will have context argument either for kprobe (if using bpf_user_pt_regs_t struct name) or perf_event (with bpf_perf_event_data struct name), but not both. This limitation was the reason to add btf_decl_tag("arg:ctx"), making the actual argument type not important, so that user can just define "generic" signature: __noinline int global_subprog(void *ctx __arg_ctx) { ... } I won't belabor how libbpf is implementing subprograms, see a huge comment next to bpf_object_relocate_calls() function. The idea is that each main/entry BPF program gets its own copy of global_subprog's code appended. This per-program copy of global subprog code *and* associated func_info .BTF.ext information, pointing to FUNC -> FUNC_PROTO BTF type chain allows libbpf to simulate __arg_ctx behavior transparently, even if the kernel doesn't yet support __arg_ctx annotation natively. The idea is straightforward: each time we append global subprog's code and func_info information, we adjust its FUNC -> FUNC_PROTO type information, if necessary (that is, libbpf can detect the presence of btf_decl_tag("arg:ctx") just like BPF verifier would do it). The rest is just mechanical and somewhat painful BTF manipulation code. It's painful because we need to clone FUNC -> FUNC_PROTO, instead of reusing it, as same FUNC -> FUNC_PROTO chain might be used by another main BPF program within the same BPF object, so we can't just modify it in-place (and cloning BTF types within the same struct btf object is painful due to constant memory invalidation, see comments in code). Uploaded BPF object's BTF information has to work for all BPF programs at the same time. Once we have FUNC -> FUNC_PROTO clones, we make sure that instead of using some `void *ctx` parameter definition, we have an expected `struct bpf_perf_event_data *ctx` definition (as far as BPF verifier and kernel is concerned), which will mark it as context for BPF verifier. Same global subprog relocated and copied into another main BPF program will get different type information according to main program's type. It all works out in the end in a completely transparent way for end user. Libbpf maintains internal program type -> expected context struct name mapping internally. Note, not all BPF program types have named context struct, so this approach won't work for such programs (just like it didn't before __arg_ctx). So native __arg_ctx is still important to have in kernel to have generic context support across all BPF program types. Acked-by: Jiri Olsa <[email protected]> Signed-off-by: Andrii Nakryiko <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Alexei Starovoitov <[email protected]>
1 parent 1004742 commit 2f38fe6

File tree

1 file changed

+252
-4
lines changed

1 file changed

+252
-4
lines changed

tools/lib/bpf/libbpf.c

Lines changed: 252 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6181,7 +6181,7 @@ reloc_prog_func_and_line_info(const struct bpf_object *obj,
61816181
int err;
61826182

61836183
/* no .BTF.ext relocation if .BTF.ext is missing or kernel doesn't
6184-
* supprot func/line info
6184+
* support func/line info
61856185
*/
61866186
if (!obj->btf_ext || !kernel_supports(obj, FEAT_BTF_FUNC))
61876187
return 0;
@@ -6663,8 +6663,247 @@ static int bpf_prog_assign_exc_cb(struct bpf_object *obj, struct bpf_program *pr
66636663
return 0;
66646664
}
66656665

6666-
static int
6667-
bpf_object__relocate(struct bpf_object *obj, const char *targ_btf_path)
6666+
static struct {
6667+
enum bpf_prog_type prog_type;
6668+
const char *ctx_name;
6669+
} global_ctx_map[] = {
6670+
{ BPF_PROG_TYPE_CGROUP_DEVICE, "bpf_cgroup_dev_ctx" },
6671+
{ BPF_PROG_TYPE_CGROUP_SKB, "__sk_buff" },
6672+
{ BPF_PROG_TYPE_CGROUP_SOCK, "bpf_sock" },
6673+
{ BPF_PROG_TYPE_CGROUP_SOCK_ADDR, "bpf_sock_addr" },
6674+
{ BPF_PROG_TYPE_CGROUP_SOCKOPT, "bpf_sockopt" },
6675+
{ BPF_PROG_TYPE_CGROUP_SYSCTL, "bpf_sysctl" },
6676+
{ BPF_PROG_TYPE_FLOW_DISSECTOR, "__sk_buff" },
6677+
{ BPF_PROG_TYPE_KPROBE, "bpf_user_pt_regs_t" },
6678+
{ BPF_PROG_TYPE_LWT_IN, "__sk_buff" },
6679+
{ BPF_PROG_TYPE_LWT_OUT, "__sk_buff" },
6680+
{ BPF_PROG_TYPE_LWT_SEG6LOCAL, "__sk_buff" },
6681+
{ BPF_PROG_TYPE_LWT_XMIT, "__sk_buff" },
6682+
{ BPF_PROG_TYPE_NETFILTER, "bpf_nf_ctx" },
6683+
{ BPF_PROG_TYPE_PERF_EVENT, "bpf_perf_event_data" },
6684+
{ BPF_PROG_TYPE_RAW_TRACEPOINT, "bpf_raw_tracepoint_args" },
6685+
{ BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE, "bpf_raw_tracepoint_args" },
6686+
{ BPF_PROG_TYPE_SCHED_ACT, "__sk_buff" },
6687+
{ BPF_PROG_TYPE_SCHED_CLS, "__sk_buff" },
6688+
{ BPF_PROG_TYPE_SK_LOOKUP, "bpf_sk_lookup" },
6689+
{ BPF_PROG_TYPE_SK_MSG, "sk_msg_md" },
6690+
{ BPF_PROG_TYPE_SK_REUSEPORT, "sk_reuseport_md" },
6691+
{ BPF_PROG_TYPE_SK_SKB, "__sk_buff" },
6692+
{ BPF_PROG_TYPE_SOCK_OPS, "bpf_sock_ops" },
6693+
{ BPF_PROG_TYPE_SOCKET_FILTER, "__sk_buff" },
6694+
{ BPF_PROG_TYPE_XDP, "xdp_md" },
6695+
/* all other program types don't have "named" context structs */
6696+
};
6697+
6698+
static int clone_func_btf_info(struct btf *btf, int orig_fn_id, struct bpf_program *prog)
6699+
{
6700+
int fn_id, fn_proto_id, ret_type_id, orig_proto_id;
6701+
int i, err, arg_cnt, fn_name_off, linkage;
6702+
struct btf_type *fn_t, *fn_proto_t, *t;
6703+
struct btf_param *p;
6704+
6705+
/* caller already validated FUNC -> FUNC_PROTO validity */
6706+
fn_t = btf_type_by_id(btf, orig_fn_id);
6707+
fn_proto_t = btf_type_by_id(btf, fn_t->type);
6708+
6709+
/* Note that each btf__add_xxx() operation invalidates
6710+
* all btf_type and string pointers, so we need to be
6711+
* very careful when cloning BTF types. BTF type
6712+
* pointers have to be always refetched. And to avoid
6713+
* problems with invalidated string pointers, we
6714+
* add empty strings initially, then just fix up
6715+
* name_off offsets in place. Offsets are stable for
6716+
* existing strings, so that works out.
6717+
*/
6718+
fn_name_off = fn_t->name_off; /* we are about to invalidate fn_t */
6719+
linkage = btf_func_linkage(fn_t);
6720+
orig_proto_id = fn_t->type; /* original FUNC_PROTO ID */
6721+
ret_type_id = fn_proto_t->type; /* fn_proto_t will be invalidated */
6722+
arg_cnt = btf_vlen(fn_proto_t);
6723+
6724+
/* clone FUNC_PROTO and its params */
6725+
fn_proto_id = btf__add_func_proto(btf, ret_type_id);
6726+
if (fn_proto_id < 0)
6727+
return -EINVAL;
6728+
6729+
for (i = 0; i < arg_cnt; i++) {
6730+
int name_off;
6731+
6732+
/* copy original parameter data */
6733+
t = btf_type_by_id(btf, orig_proto_id);
6734+
p = &btf_params(t)[i];
6735+
name_off = p->name_off;
6736+
6737+
err = btf__add_func_param(btf, "", p->type);
6738+
if (err)
6739+
return err;
6740+
6741+
fn_proto_t = btf_type_by_id(btf, fn_proto_id);
6742+
p = &btf_params(fn_proto_t)[i];
6743+
p->name_off = name_off; /* use remembered str offset */
6744+
}
6745+
6746+
/* clone FUNC now, btf__add_func() enforces non-empty name, so use
6747+
* entry program's name as a placeholder, which we replace immediately
6748+
* with original name_off
6749+
*/
6750+
fn_id = btf__add_func(btf, prog->name, linkage, fn_proto_id);
6751+
if (fn_id < 0)
6752+
return -EINVAL;
6753+
6754+
fn_t = btf_type_by_id(btf, fn_id);
6755+
fn_t->name_off = fn_name_off; /* reuse original string */
6756+
6757+
return fn_id;
6758+
}
6759+
6760+
/* Check if main program or global subprog's function prototype has `arg:ctx`
6761+
* argument tags, and, if necessary, substitute correct type to match what BPF
6762+
* verifier would expect, taking into account specific program type. This
6763+
* allows to support __arg_ctx tag transparently on old kernels that don't yet
6764+
* have a native support for it in the verifier, making user's life much
6765+
* easier.
6766+
*/
6767+
static int bpf_program_fixup_func_info(struct bpf_object *obj, struct bpf_program *prog)
6768+
{
6769+
const char *ctx_name = NULL, *ctx_tag = "arg:ctx";
6770+
struct bpf_func_info_min *func_rec;
6771+
struct btf_type *fn_t, *fn_proto_t;
6772+
struct btf *btf = obj->btf;
6773+
const struct btf_type *t;
6774+
struct btf_param *p;
6775+
int ptr_id = 0, struct_id, tag_id, orig_fn_id;
6776+
int i, n, arg_idx, arg_cnt, err, rec_idx;
6777+
int *orig_ids;
6778+
6779+
/* no .BTF.ext, no problem */
6780+
if (!obj->btf_ext || !prog->func_info)
6781+
return 0;
6782+
6783+
/* some BPF program types just don't have named context structs, so
6784+
* this fallback mechanism doesn't work for them
6785+
*/
6786+
for (i = 0; i < ARRAY_SIZE(global_ctx_map); i++) {
6787+
if (global_ctx_map[i].prog_type != prog->type)
6788+
continue;
6789+
ctx_name = global_ctx_map[i].ctx_name;
6790+
break;
6791+
}
6792+
if (!ctx_name)
6793+
return 0;
6794+
6795+
/* remember original func BTF IDs to detect if we already cloned them */
6796+
orig_ids = calloc(prog->func_info_cnt, sizeof(*orig_ids));
6797+
if (!orig_ids)
6798+
return -ENOMEM;
6799+
for (i = 0; i < prog->func_info_cnt; i++) {
6800+
func_rec = prog->func_info + prog->func_info_rec_size * i;
6801+
orig_ids[i] = func_rec->type_id;
6802+
}
6803+
6804+
/* go through each DECL_TAG with "arg:ctx" and see if it points to one
6805+
* of our subprogs; if yes and subprog is global and needs adjustment,
6806+
* clone and adjust FUNC -> FUNC_PROTO combo
6807+
*/
6808+
for (i = 1, n = btf__type_cnt(btf); i < n; i++) {
6809+
/* only DECL_TAG with "arg:ctx" value are interesting */
6810+
t = btf__type_by_id(btf, i);
6811+
if (!btf_is_decl_tag(t))
6812+
continue;
6813+
if (strcmp(btf__str_by_offset(btf, t->name_off), ctx_tag) != 0)
6814+
continue;
6815+
6816+
/* only global funcs need adjustment, if at all */
6817+
orig_fn_id = t->type;
6818+
fn_t = btf_type_by_id(btf, orig_fn_id);
6819+
if (!btf_is_func(fn_t) || btf_func_linkage(fn_t) != BTF_FUNC_GLOBAL)
6820+
continue;
6821+
6822+
/* sanity check FUNC -> FUNC_PROTO chain, just in case */
6823+
fn_proto_t = btf_type_by_id(btf, fn_t->type);
6824+
if (!fn_proto_t || !btf_is_func_proto(fn_proto_t))
6825+
continue;
6826+
6827+
/* find corresponding func_info record */
6828+
func_rec = NULL;
6829+
for (rec_idx = 0; rec_idx < prog->func_info_cnt; rec_idx++) {
6830+
if (orig_ids[rec_idx] == t->type) {
6831+
func_rec = prog->func_info + prog->func_info_rec_size * rec_idx;
6832+
break;
6833+
}
6834+
}
6835+
/* current main program doesn't call into this subprog */
6836+
if (!func_rec)
6837+
continue;
6838+
6839+
/* some more sanity checking of DECL_TAG */
6840+
arg_cnt = btf_vlen(fn_proto_t);
6841+
arg_idx = btf_decl_tag(t)->component_idx;
6842+
if (arg_idx < 0 || arg_idx >= arg_cnt)
6843+
continue;
6844+
6845+
/* check if existing parameter already matches verifier expectations */
6846+
p = &btf_params(fn_proto_t)[arg_idx];
6847+
t = skip_mods_and_typedefs(btf, p->type, NULL);
6848+
if (btf_is_ptr(t) &&
6849+
(t = skip_mods_and_typedefs(btf, t->type, NULL)) &&
6850+
btf_is_struct(t) &&
6851+
strcmp(btf__str_by_offset(btf, t->name_off), ctx_name) == 0) {
6852+
continue; /* no need for fix up */
6853+
}
6854+
6855+
/* clone fn/fn_proto, unless we already did it for another arg */
6856+
if (func_rec->type_id == orig_fn_id) {
6857+
int fn_id;
6858+
6859+
fn_id = clone_func_btf_info(btf, orig_fn_id, prog);
6860+
if (fn_id < 0) {
6861+
err = fn_id;
6862+
goto err_out;
6863+
}
6864+
6865+
/* point func_info record to a cloned FUNC type */
6866+
func_rec->type_id = fn_id;
6867+
}
6868+
6869+
/* create PTR -> STRUCT type chain to mark PTR_TO_CTX argument;
6870+
* we do it just once per main BPF program, as all global
6871+
* funcs share the same program type, so need only PTR ->
6872+
* STRUCT type chain
6873+
*/
6874+
if (ptr_id == 0) {
6875+
struct_id = btf__add_struct(btf, ctx_name, 0);
6876+
ptr_id = btf__add_ptr(btf, struct_id);
6877+
if (ptr_id < 0 || struct_id < 0) {
6878+
err = -EINVAL;
6879+
goto err_out;
6880+
}
6881+
}
6882+
6883+
/* for completeness, clone DECL_TAG and point it to cloned param */
6884+
tag_id = btf__add_decl_tag(btf, ctx_tag, func_rec->type_id, arg_idx);
6885+
if (tag_id < 0) {
6886+
err = -EINVAL;
6887+
goto err_out;
6888+
}
6889+
6890+
/* all the BTF manipulations invalidated pointers, refetch them */
6891+
fn_t = btf_type_by_id(btf, func_rec->type_id);
6892+
fn_proto_t = btf_type_by_id(btf, fn_t->type);
6893+
6894+
/* fix up type ID pointed to by param */
6895+
p = &btf_params(fn_proto_t)[arg_idx];
6896+
p->type = ptr_id;
6897+
}
6898+
6899+
free(orig_ids);
6900+
return 0;
6901+
err_out:
6902+
free(orig_ids);
6903+
return err;
6904+
}
6905+
6906+
static int bpf_object__relocate(struct bpf_object *obj, const char *targ_btf_path)
66686907
{
66696908
struct bpf_program *prog;
66706909
size_t i, j;
@@ -6745,19 +6984,28 @@ bpf_object__relocate(struct bpf_object *obj, const char *targ_btf_path)
67456984
}
67466985
}
67476986
}
6748-
/* Process data relos for main programs */
67496987
for (i = 0; i < obj->nr_programs; i++) {
67506988
prog = &obj->programs[i];
67516989
if (prog_is_subprog(obj, prog))
67526990
continue;
67536991
if (!prog->autoload)
67546992
continue;
6993+
6994+
/* Process data relos for main programs */
67556995
err = bpf_object__relocate_data(obj, prog);
67566996
if (err) {
67576997
pr_warn("prog '%s': failed to relocate data references: %d\n",
67586998
prog->name, err);
67596999
return err;
67607000
}
7001+
7002+
/* Fix up .BTF.ext information, if necessary */
7003+
err = bpf_program_fixup_func_info(obj, prog);
7004+
if (err) {
7005+
pr_warn("prog '%s': failed to perform .BTF.ext fix ups: %d\n",
7006+
prog->name, err);
7007+
return err;
7008+
}
67617009
}
67627010

67637011
return 0;

0 commit comments

Comments
 (0)