btf: Extend UAPI to support BTF location (inline site) info

Add BTF_KIND_LOC_PARAM, BTF_KIND_LOC_PROTO and BTF_KIND_LOCSEC
to help represent location information for functions.

BTF_KIND_LOC_PARAM is used to represent how we retrieve data at a
location; either via register(s), or register+offset, a dereference
of a register+offset or a constant value.

BTF_KIND_LOC_PROTO represents location information about a location
with multiple BTF_KIND_LOC_PARAMs.

And finally BTF_KIND_LOCSEC is a set of location sites, each
of which has

- a BTF_KIND_FUNC function associated with the inline site
- a location prototype specifying where to find the function
  parameters
- an address offset relative to the kernel base address

This can be used to support representing

- a fully-inlined function at potentially multiple inline sites
  with potentially different parameter availability
- a partially-inlined function where some _LOC_PROTOs represent
  inlined sites as above and others have normal _FUNC representations

Also BTF_KIND_LOCSEC struct btf_loc will have two type id
references; one for the associated func, the other for the loc_proto.
Accordingly increase the number of m_offs references in btf_field_desc
to 2.

Signed-off-by: Alan Maguire <alan.maguire@oracle.com>
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Acked-by: Eduard Zingerman <eddyz87@gmail.com>
Link: https://patch.msgid.link/20260924111428.75957-2-alan.maguire@oracle.com
This commit is contained in:
Alan Maguire 2026-09-24 12:14:18 +01:00 committed by Alexei Starovoitov
parent 4f3a5eae89
commit 33c5a3278b
No known key found for this signature in database
4 changed files with 499 additions and 11 deletions

View File

@ -261,6 +261,11 @@ const char *btf_type_str(const struct btf_type *t);
i < btf_type_vlen(datasec_type); \
i++, member++)
#define for_each_loc(i, locsec_type, member) \
for (i = 0, member = btf_type_loc_secinfo(locsec_type); \
i < btf_type_vlen(locsec_type); \
i++, member++)
static inline bool btf_type_is_ptr(const struct btf_type *t)
{
return BTF_INFO_KIND(t->info) == BTF_KIND_PTR;
@ -329,6 +334,21 @@ static inline u64 btf_enum64_value(const struct btf_enum64 *e)
return ((u64)e->val_hi32 << 32) | e->val_lo32;
}
static inline struct btf_loc_param *btf_loc_param(const struct btf_type *t)
{
return (struct btf_loc_param *)(t + 1);
}
static inline __u32 *btf_loc_proto_params(const struct btf_type *t)
{
return (__u32 *)(t + 1);
}
static inline struct btf_loc *btf_type_loc_secinfo(const struct btf_type *t)
{
return (struct btf_loc *)(t + 1);
}
static inline bool btf_is_composite(const struct btf_type *t)
{
u16 kind = btf_kind(t);
@ -559,7 +579,7 @@ struct btf_field_desc {
/* member struct size, or zero, if no members */
int m_sz;
/* repeated per-member offsets */
int m_off_cnt, m_offs[1];
int m_off_cnt, m_offs[2];
};
struct btf_field_iter {

View File

@ -54,8 +54,8 @@ struct btf_type {
* decl_tag and type_tag
*/
__u32 info;
/* "size" is used by INT, ENUM, STRUCT, UNION, DATASEC and ENUM64.
* "size" tells the size of the type it is describing.
/* "size" is used by INT, ENUM, STRUCT, UNION, DATASEC, ENUM64
* and LOC_PARAM. "size" tells the size of the type it is describing.
*
* "type" is used by PTR, TYPEDEF, VOLATILE, CONST, RESTRICT,
* FUNC, FUNC_PROTO, VAR, DECL_TAG and TYPE_TAG.
@ -92,7 +92,9 @@ enum {
BTF_KIND_DECL_TAG = 17, /* Decl Tag */
BTF_KIND_TYPE_TAG = 18, /* Type Tag */
BTF_KIND_ENUM64 = 19, /* Enumeration up to 64-bit values */
BTF_KIND_LOC_PARAM = 20, /* Location parameter information */
BTF_KIND_LOC_PROTO = 21, /* Location prototype for site */
BTF_KIND_LOCSEC = 22, /* Location section */
NR_BTF_KINDS,
BTF_KIND_MAX = NR_BTF_KINDS - 1,
};
@ -212,4 +214,79 @@ struct btf_enum64 {
__u32 val_hi32;
};
/*
* BTF_KIND_LOC_PARAM is followed by a single "struct btf_loc_param"
* that contains flags specifying the contents of the vlen-specified
* number of 4-byte values that follow.
*/
struct btf_loc_param {
__u32 flags;
__u32 values[];
};
/*
* The combination of size, vlen and flags gives us the means to interpret
* the following vlen-specified set of 4-byte values:
*
* - a BTF_LOC_PARAM_CONST is a constant value; combination
* of size, vlen and _SIGNED flag determines it. If the value requires
* 64 bits it is stored in {lo,hi} order.
* - a BTF_LOC_PARAM_ADDR|BTF_LOC_PARAM_CONST is an address that should be
* normalized with respect to kernel/module base address.
* - a BTF_LOC_PARAM_REG with vlen 1 is a simple register number;
* with vlen 2 it is a multi-register parameter. Register numbers are
* numbers derived from DW_OP_reg values, i.e. 0 is DW_OP_reg0; since
* DW_OP_fbreg has its own special DW_OP_value and DW_OP_regx can refer
* to an arbitrary register number, we reserve BTF_LOC_PARAM_FBREG for
* the frame base register to avoid collisions.
* - a _REG | OFFSET describes an address without dereferencing it.
* - a _REG | DEREF with vlen 1 dereferences the value in the register
* number specified.
* - a REG | DEREF | OFFSET with vlen > 1 specifies the register number
* in the first 4-byte value and the offset in the remainder.
* In the case of REG and OFFSET combinations, the OFFSET has the width
* of the value words while the type size describes the represented parameter,
* so for example a REG | DEREF | OFFSET with size 8 and vlen 2 would be
* an 8-byte register dereference with signed 4-byte offset, since the vlen 2
* values consist of a register value and the signed value.
*/
enum btf_loc_param_flags {
BTF_LOC_PARAM_SIGNED = 0x1,
BTF_LOC_PARAM_CONST = 0x2,
BTF_LOC_PARAM_ADDR = 0x4,
BTF_LOC_PARAM_REG = 0x8,
BTF_LOC_PARAM_DEREF = 0x10,
BTF_LOC_PARAM_OFFSET = 0x20,
};
enum {
BTF_LOC_PARAM_FBREG = 0xffffffff
};
/*
* BTF_KIND_LOC_PROTO specifies location prototypes; i.e. how locations relate
* to parameters; a struct btf_type of BTF_KIND_LOC_PROTO is followed by a
* vlen-specified number of __u32 BTF type ids which specify the associated
* BTF_KIND_LOC_PARAM for each function parameter associated with the
* location. The type should either be 0 (no location info) or point at
* a BTF_KIND_LOC_PARAM.
*/
/*
* BTF_KIND_LOCSEC consists of vlen-specified number of "struct btf_loc"
* containing location site-specific information for a specific ELF section;
* for example locations in ".text" are in a LOCSEC named "inline.text".
*
* - function (func)
* - location prototype type id (loc_proto)
* - address offset (offset) relative to the runtime base address of the
* ELF section associated with the LOCSEC
*/
struct btf_loc {
__u32 func;
__u32 loc_proto;
__u32 offset;
};
#endif /* _UAPI__LINUX_BTF_H__ */

View File

@ -346,6 +346,9 @@ static const char * const btf_kind_str[NR_BTF_KINDS] = {
[BTF_KIND_DECL_TAG] = "DECL_TAG",
[BTF_KIND_TYPE_TAG] = "TYPE_TAG",
[BTF_KIND_ENUM64] = "ENUM64",
[BTF_KIND_LOC_PARAM] = "LOC_PARAM",
[BTF_KIND_LOC_PROTO] = "LOC_PROTO",
[BTF_KIND_LOCSEC] = "LOCSEC",
};
const char *btf_type_str(const struct btf_type *t)
@ -518,11 +521,27 @@ static bool btf_type_is_decl_tag(const struct btf_type *t)
return BTF_INFO_KIND(t->info) == BTF_KIND_DECL_TAG;
}
static bool btf_type_is_loc_param(const struct btf_type *t)
{
return BTF_INFO_KIND(t->info) == BTF_KIND_LOC_PARAM;
}
static bool btf_type_is_loc_proto(const struct btf_type *t)
{
return BTF_INFO_KIND(t->info) == BTF_KIND_LOC_PROTO;
}
static bool btf_type_is_locsec(const struct btf_type *t)
{
return BTF_INFO_KIND(t->info) == BTF_KIND_LOCSEC;
}
static bool btf_type_nosize(const struct btf_type *t)
{
return btf_type_is_void(t) || btf_type_is_fwd(t) ||
btf_type_is_func(t) || btf_type_is_func_proto(t) ||
btf_type_is_decl_tag(t);
btf_type_is_decl_tag(t) || btf_type_is_loc_param(t) ||
btf_type_is_loc_proto(t) || btf_type_is_locsec(t);
}
static bool btf_type_nosize_or_null(const struct btf_type *t)
@ -769,7 +788,9 @@ static bool btf_type_is_resolve_source_only(const struct btf_type *t)
{
return btf_type_is_var(t) ||
btf_type_is_decl_tag(t) ||
btf_type_is_datasec(t);
btf_type_is_datasec(t) ||
btf_type_is_loc_proto(t) ||
btf_type_is_locsec(t);
}
/* What types need to be resolved?
@ -797,7 +818,9 @@ static bool btf_type_needs_resolve(const struct btf_type *t)
btf_type_is_var(t) ||
btf_type_is_func(t) ||
btf_type_is_decl_tag(t) ||
btf_type_is_datasec(t);
btf_type_is_datasec(t) ||
btf_type_is_loc_proto(t) ||
btf_type_is_locsec(t);
}
/* t->size can be used */
@ -4737,6 +4760,293 @@ static const struct btf_kind_operations enum64_ops = {
.show = btf_enum64_show,
};
static s32 btf_loc_param_check_meta(struct btf_verifier_env *env,
const struct btf_type *t,
u32 meta_left)
{
const struct btf_loc_param *p = btf_loc_param(t);
u32 size, meta_needed, vlen = btf_vlen(t);
meta_needed = sizeof(*p) + sizeof(__u32) * vlen;
if (meta_left < meta_needed) {
btf_verifier_log_basic(env, t,
"meta_left:%u meta_needed:%u",
meta_left, meta_needed);
return -EINVAL;
}
if (t->name_off) {
btf_verifier_log_type(env, t, "Invalid name");
return -EINVAL;
}
size = t->size;
if (!size || size > 16) {
btf_verifier_log_type(env, t, "Unexpected size");
return -EINVAL;
}
if (btf_type_kflag(t)) {
btf_verifier_log_type(env, t, "Invalid btf_info kind_flag");
return -EINVAL;
}
btf_verifier_log_type(env, t, NULL);
return meta_needed;
}
static void btf_loc_param_log(struct btf_verifier_env *env,
const struct btf_type *t)
{
const struct btf_loc_param *p = btf_loc_param(t);
u32 i, vlen = btf_vlen(t);
btf_verifier_log(env, "size=%u vlen=%u flags=0x%x", t->size, vlen, p->flags);
for (i = 0; i < vlen; i++)
btf_verifier_log(env, ", %u", p->values[i]);
}
static const struct btf_kind_operations loc_param_ops = {
.check_meta = btf_loc_param_check_meta,
.resolve = btf_df_resolve,
.check_member = btf_df_check_member,
.check_kflag_member = btf_df_check_kflag_member,
.log_details = btf_loc_param_log,
.show = btf_df_show,
};
static s32 btf_loc_proto_check_meta(struct btf_verifier_env *env,
const struct btf_type *t,
u32 meta_left)
{
u32 meta_needed;
meta_needed = sizeof(__u32) * btf_type_vlen(t);
if (meta_left < meta_needed) {
btf_verifier_log_basic(env, t,
"meta_left:%u meta_needed:%u",
meta_left, meta_needed);
return -EINVAL;
}
if (t->name_off) {
btf_verifier_log_type(env, t, "Invalid name");
return -EINVAL;
}
if (btf_type_kflag(t)) {
btf_verifier_log_type(env, t, "Invalid btf_info kind_flag");
return -EINVAL;
}
btf_verifier_log_type(env, t, NULL);
return meta_needed;
}
static void btf_loc_proto_log(struct btf_verifier_env *env,
const struct btf_type *t)
{
const __u32 *params = btf_loc_proto_params(t);
u32 i, nr_params = btf_type_vlen(t);
btf_verifier_log(env, "vlen=%u", nr_params);
for (i = 0; i < nr_params; i++, params++)
btf_verifier_log(env, ", %u", *params);
}
static int btf_loc_proto_resolve(struct btf_verifier_env *env,
const struct resolve_vertex *v)
{
const struct btf_type *t = v->t;
const __u32 *params = btf_loc_proto_params(t);
u32 i, nr_params = btf_type_vlen(t);
struct btf *btf = env->btf;
if (t->type) {
btf_verifier_log_type(env, t, "Invalid loc_proto type");
return -EINVAL;
}
for (i = 0; i < nr_params; i++) {
const struct btf_type *param_type;
u32 param_type_id = params[i];
if (!param_type_id)
continue;
param_type = btf_type_by_id(btf, param_type_id);
if (!param_type || !btf_type_is_loc_param(param_type)) {
btf_verifier_log_type(env, t,
"Invalid loc_param#%u", i + 1);
return -EINVAL;
}
}
env_stack_pop_resolved(env, 0, 0);
return 0;
}
static const struct btf_kind_operations loc_proto_ops = {
.check_meta = btf_loc_proto_check_meta,
.resolve = btf_loc_proto_resolve,
.check_member = btf_df_check_member,
.check_kflag_member = btf_df_check_kflag_member,
.log_details = btf_loc_proto_log,
.show = btf_df_show,
};
__printf(4, 5)
static void btf_verifier_log_loc(struct btf_verifier_env *env,
const struct btf_type *locsec_type,
const struct btf_loc *loc,
const char *fmt, ...)
{
struct bpf_verifier_log *log = &env->log;
va_list args;
if (!bpf_verifier_log_needed(log))
return;
if (log->level == BPF_LOG_KERNEL && !fmt)
return;
if (env->phase != CHECK_META)
btf_verifier_log_type(env, locsec_type, NULL);
__btf_verifier_log(log, "\t func=%u loc_proto=%u offset=%u",
loc->func, loc->loc_proto, loc->offset);
if (fmt && *fmt) {
__btf_verifier_log(log, " ");
va_start(args, fmt);
bpf_verifier_vlog(log, fmt, args);
va_end(args);
}
__btf_verifier_log(log, "\n");
}
static s32 btf_locsec_check_meta(struct btf_verifier_env *env,
const struct btf_type *t,
u32 meta_left)
{
u32 i, meta_needed, vlen = btf_type_vlen(t);
const struct btf_loc *loc;
meta_needed = sizeof(struct btf_loc) * vlen;
if (meta_left < meta_needed) {
btf_verifier_log_basic(env, t,
"meta_left:%u meta_needed:%u",
meta_left, meta_needed);
return -EINVAL;
}
if (btf_type_kflag(t)) {
btf_verifier_log_type(env, t, "Invalid btf_info kind_flag");
return -EINVAL;
}
if (!t->name_off ||
!btf_name_valid_section(env->btf, t->name_off)) {
btf_verifier_log_type(env, t, "Invalid name");
return -EINVAL;
}
for_each_loc(i, t, loc) {
/* A loc func, loc proto cannot be in type void */
if (!loc->func || !BTF_TYPE_ID_VALID(loc->func)) {
btf_verifier_log_loc(env, t, loc, "Invalid func");
return -EINVAL;
}
if (!loc->loc_proto || !BTF_TYPE_ID_VALID(loc->loc_proto)) {
btf_verifier_log_loc(env, t, loc, "Invalid loc_proto");
return -EINVAL;
}
btf_verifier_log_loc(env, t, loc, NULL);
}
return meta_needed;
}
static void btf_locsec_log(struct btf_verifier_env *env,
const struct btf_type *t)
{
btf_verifier_log(env, "vlen=%u", btf_type_vlen(t));
}
static int btf_locsec_resolve(struct btf_verifier_env *env,
const struct resolve_vertex *v)
{
const struct btf_type *t = v->t;
const struct btf_loc *loc;
struct btf *btf = env->btf;
u32 i;
if (t->type) {
btf_verifier_log_type(env, t, "Invalid locsec type");
return -EINVAL;
}
env->resolve_mode = RESOLVE_TBD;
for (i = v->next_member, loc = btf_type_loc_secinfo(t) + i;
i < btf_type_vlen(t); i++, loc++) {
const struct btf_type *func_type, *func_proto_type;
const struct btf_type *loc_proto_type;
u32 func_type_id = loc->func;
u32 loc_proto_type_id = loc->loc_proto;
u32 proto_vlen;
func_type = btf_type_by_id(btf, func_type_id);
if (!func_type || !btf_type_is_func(func_type)) {
btf_verifier_log_type(env, t,
"Invalid func#%u", i + 1);
return -EINVAL;
}
func_proto_type = btf_type_by_id(btf, func_type->type);
if (!func_proto_type || !btf_type_is_func_proto(func_proto_type)) {
btf_verifier_log_type(env, t,
"Invalid func#%u", i + 1);
return -EINVAL;
}
proto_vlen = btf_vlen(func_proto_type);
if (!env_type_is_resolved(env, func_type_id)) {
env_stack_set_next_member(env, i);
return env_stack_push(env, func_type, func_type_id);
}
loc_proto_type = btf_type_by_id(btf, loc_proto_type_id);
if (!loc_proto_type || !btf_type_is_loc_proto(loc_proto_type)) {
btf_verifier_log_type(env, t,
"Invalid loc_proto#%u", i + 1);
return -EINVAL;
}
if (proto_vlen != btf_vlen(loc_proto_type)) {
btf_verifier_log_type(env, t,
"Mismatched vlen for loc_proto#%u", i + 1);
return -EINVAL;
}
if (!env_type_is_resolved(env, loc_proto_type_id)) {
env_stack_set_next_member(env, i + 1);
return env_stack_push(env, loc_proto_type,
loc_proto_type_id);
}
}
env_stack_pop_resolved(env, 0, 0);
return 0;
}
static const struct btf_kind_operations locsec_ops = {
.check_meta = btf_locsec_check_meta,
.resolve = btf_locsec_resolve,
.check_member = btf_df_check_member,
.check_kflag_member = btf_df_check_kflag_member,
.log_details = btf_locsec_log,
.show = btf_df_show,
};
static s32 btf_func_proto_check_meta(struct btf_verifier_env *env,
const struct btf_type *t,
u32 meta_left)
@ -5406,6 +5716,9 @@ static const struct btf_kind_operations * const kind_ops[NR_BTF_KINDS] = {
[BTF_KIND_DECL_TAG] = &decl_tag_ops,
[BTF_KIND_TYPE_TAG] = &modifier_ops,
[BTF_KIND_ENUM64] = &enum64_ops,
[BTF_KIND_LOC_PARAM] = &loc_param_ops,
[BTF_KIND_LOC_PROTO] = &loc_proto_ops,
[BTF_KIND_LOCSEC] = &locsec_ops,
};
static s32 btf_check_meta(struct btf_verifier_env *env,
@ -5480,7 +5793,8 @@ static bool btf_resolve_valid(struct btf_verifier_env *env,
if (!env_type_is_resolved(env, type_id))
return false;
if (btf_type_is_struct(t) || btf_type_is_datasec(t))
if (btf_type_is_struct(t) || btf_type_is_datasec(t) ||
btf_type_is_loc_proto(t) || btf_type_is_locsec(t))
return !btf_resolved_type_id(btf, type_id) &&
!btf_resolved_type_size(btf, type_id);

View File

@ -54,8 +54,8 @@ struct btf_type {
* decl_tag and type_tag
*/
__u32 info;
/* "size" is used by INT, ENUM, STRUCT, UNION, DATASEC and ENUM64.
* "size" tells the size of the type it is describing.
/* "size" is used by INT, ENUM, STRUCT, UNION, DATASEC, ENUM64
* and LOC_PARAM. "size" tells the size of the type it is describing.
*
* "type" is used by PTR, TYPEDEF, VOLATILE, CONST, RESTRICT,
* FUNC, FUNC_PROTO, VAR, DECL_TAG and TYPE_TAG.
@ -92,7 +92,9 @@ enum {
BTF_KIND_DECL_TAG = 17, /* Decl Tag */
BTF_KIND_TYPE_TAG = 18, /* Type Tag */
BTF_KIND_ENUM64 = 19, /* Enumeration up to 64-bit values */
BTF_KIND_LOC_PARAM = 20, /* Location parameter information */
BTF_KIND_LOC_PROTO = 21, /* Location prototype for site */
BTF_KIND_LOCSEC = 22, /* Location section */
NR_BTF_KINDS,
BTF_KIND_MAX = NR_BTF_KINDS - 1,
};
@ -212,4 +214,79 @@ struct btf_enum64 {
__u32 val_hi32;
};
/*
* BTF_KIND_LOC_PARAM is followed by a single "struct btf_loc_param"
* that contains flags specifying the contents of the vlen-specified
* number of 4-byte values that follow.
*/
struct btf_loc_param {
__u32 flags;
__u32 values[];
};
/*
* The combination of size, vlen and flags gives us the means to interpret
* the following vlen-specified set of 4-byte values:
*
* - a BTF_LOC_PARAM_CONST is a constant value; combination
* of size, vlen and _SIGNED flag determines it. If the value requires
* 64 bits it is stored in {lo,hi} order.
* - a BTF_LOC_PARAM_ADDR|BTF_LOC_PARAM_CONST is an address that should be
* normalized with respect to kernel/module base address.
* - a BTF_LOC_PARAM_REG with vlen 1 is a simple register number;
* with vlen 2 it is a multi-register parameter. Register numbers are
* numbers derived from DW_OP_reg values, i.e. 0 is DW_OP_reg0; since
* DW_OP_fbreg has its own special DW_OP_value and DW_OP_regx can refer
* to an arbitrary register number, we reserve BTF_LOC_PARAM_FBREG for
* the frame base register to avoid collisions.
* - a _REG | OFFSET describes an address without dereferencing it.
* - a _REG | DEREF with vlen 1 dereferences the value in the register
* number specified.
* - a REG | DEREF | OFFSET with vlen > 1 specifies the register number
* in the first 4-byte value and the offset in the remainder.
* In the case of REG and OFFSET combinations, the OFFSET has the width
* of the value words while the type size describes the represented parameter,
* so for example a REG | DEREF | OFFSET with size 8 and vlen 2 would be
* an 8-byte register dereference with signed 4-byte offset, since the vlen 2
* values consist of a register value and the signed value.
*/
enum btf_loc_param_flags {
BTF_LOC_PARAM_SIGNED = 0x1,
BTF_LOC_PARAM_CONST = 0x2,
BTF_LOC_PARAM_ADDR = 0x4,
BTF_LOC_PARAM_REG = 0x8,
BTF_LOC_PARAM_DEREF = 0x10,
BTF_LOC_PARAM_OFFSET = 0x20,
};
enum {
BTF_LOC_PARAM_FBREG = 0xffffffff
};
/*
* BTF_KIND_LOC_PROTO specifies location prototypes; i.e. how locations relate
* to parameters; a struct btf_type of BTF_KIND_LOC_PROTO is followed by a
* vlen-specified number of __u32 BTF type ids which specify the associated
* BTF_KIND_LOC_PARAM for each function parameter associated with the
* location. The type should either be 0 (no location info) or point at
* a BTF_KIND_LOC_PARAM.
*/
/*
* BTF_KIND_LOCSEC consists of vlen-specified number of "struct btf_loc"
* containing location site-specific information for a specific ELF section;
* for example locations in ".text" are in a LOCSEC named "inline.text".
*
* - function (func)
* - location prototype type id (loc_proto)
* - address offset (offset) relative to the runtime base address of the
* ELF section associated with the LOCSEC
*/
struct btf_loc {
__u32 func;
__u32 loc_proto;
__u32 offset;
};
#endif /* _UAPI__LINUX_BTF_H__ */