blob: e630e5f0f5fa50016f9b8e39a1704687d782ade2 [file] [edit]
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2026, Oracle and/or its affiliates. */
#include <test_progs.h>
#include <bpf/btf.h>
#include <bpf/libbpf.h>
#define BTF_SYSFS_DIR "/sys/kernel/btf"
#define BTF_INLINE_SUFFIX ".inline"
/*
* For a specific inline site, verify we have the right function,
* loc proto and loc param representation and that the offset is
* reasonable given the caller where it was inlined.
*/
void test_btf_inline(void)
{
struct btf *inline_btf = NULL, *btf = NULL, *vmlinux_btf = NULL;
const char *inline_caller = "bpf_testmod_test_write";
const char *inline_func = "testmod_inline_len";
unsigned long long caller_addr, base_addr;
bool skip = false, found_loc = false;
const struct btf_loc_param *lp;
int locsec_id, func_id, n, i;
const struct btf_type *t;
struct btf_loc *l;
const __u32 *p;
int err = 0;
if (!env.has_testmod) {
test__skip();
return;
}
/* LOCSEC offsets are relative to the ELF section base. */
base_addr = module_get_base_addr_sec("bpf_testmod", ".text");
if (!ASSERT_NEQ(base_addr, 0, "base_addr_nonzero"))
return;
if (!ASSERT_OK(kallsyms_find(inline_caller, &caller_addr),
"find_inline_caller"))
return;
if (!ASSERT_NEQ(caller_addr, 0, "caller_addr_nonzero"))
return;
if (!ASSERT_GE(caller_addr, base_addr, "caller_addr_ge_base_addr"))
return;
caller_addr -= base_addr;
vmlinux_btf = btf__load_vmlinux_btf();
if (!ASSERT_OK_PTR(vmlinux_btf, "vmlinux_btf"))
return;
btf = btf__parse_split(BTF_SYSFS_DIR "/bpf_testmod", vmlinux_btf);
if (!ASSERT_OK_PTR(btf, "bpf_testmod_btf"))
goto out;
inline_btf = btf__parse_split(BTF_SYSFS_DIR "/bpf_testmod" BTF_INLINE_SUFFIX,
btf);
err = libbpf_get_error(inline_btf);
/* pahole may not have inline BTF feature support. */
if (err == -ENOENT) {
skip = true;
goto out;
}
if (!ASSERT_OK(err, "parse_inline_btf"))
goto out;
locsec_id = btf__find_by_name_kind(inline_btf, "inline.text", BTF_KIND_LOCSEC);
if (locsec_id < 0) {
skip = true;
goto out;
}
func_id = btf__find_by_name_kind(inline_btf, inline_func, BTF_KIND_FUNC);
if (!ASSERT_GT(func_id, 0, "inline_caller_func"))
goto out;
t = btf__type_by_id(inline_btf, locsec_id);
n = btf_vlen(t);
for (i = 0, l = btf_locsec_locs(t); i < n; i++, l++) {
if (l->func == func_id) {
found_loc = true;
break;
}
}
if (!ASSERT_TRUE(found_loc, "found_loc"))
goto out;
if (!ASSERT_GT(l->loc_proto, 0, "loc_proto_id"))
goto out;
if (!ASSERT_GT(l->offset, 0, "loc_offset"))
goto out;
t = btf__type_by_id(inline_btf, l->loc_proto);
if (!ASSERT_OK_PTR(t, "loc_proto_ptr"))
goto out;
if (!ASSERT_EQ(btf_vlen(t), 1, "loc_proto_one_param"))
goto out;
p = btf_loc_proto_params(t);
if (!ASSERT_GT(*p, 0, "loc_proto_has_loc_param"))
goto out;
t = btf__type_by_id(inline_btf, *p);
if (!ASSERT_OK_PTR(t, "loc_param_ptr"))
goto out;
lp = btf_loc_param(t);
if (!ASSERT_TRUE(lp->flags & BTF_LOC_PARAM_REG, "param_has_reg"))
goto out;
if (!ASSERT_GT(l->offset, caller_addr, "inline_gt_caller"))
goto out;
/* simple sanity test to roughly ensure inline site still in function */
if (!ASSERT_LT(l->offset, caller_addr + 512, "inline_in_caller"))
goto out;
out:
btf__free(inline_btf);
btf__free(btf);
btf__free(vmlinux_btf);
if (skip)
test__skip();
}