btf.c (385 lines of code) (raw):
// SPDX-License-Identifier: Apache-2.0
/* Copyright (c) 2024 Elastic NV */
#include <err.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/utsname.h>
#include "quark.h"
#include <bpf/btf.h>
#include "libbpf/include/linux/err.h" /* IS_ERR :( */
struct quark_btf_target base_targets[] = {
{ "cred.cap_ambient", -1 },
{ "cred.cap_bset", -1 },
{ "cred.cap_effective", -1 },
{ "cred.cap_inheritable", -1 },
{ "cred.cap_permitted", -1 },
{ "cred.egid", -1 },
{ "cred.euid", -1 },
{ "cred.gid", -1 },
{ "cred.sgid", -1 },
{ "cred.suid", -1 },
{ "cred.uid", -1 },
{ "dentry.d_name.name", -1 },
{ "dentry.d_parent", -1 },
{ "fs_struct.pwd.dentry", -1 },
{ "fs_struct.pwd.mnt", -1 },
{ "fs_struct.root.dentry", -1 },
{ "ipc_namespace.proc_inum", -1 }, /* or ipc_namespace.ns.inum */
{ "mm_struct.(anon).start_stack",-1 }, /* or mm_struct.start_stack */
{ "mount.mnt", -1 },
{ "mount.mnt_mountpoint", -1 },
{ "mnt_namespace.proc_inum", -1 }, /* or mnt_namespace.ns.inum */
{ "net_namespace.proc_inum", -1 }, /* or net.ns.inum or net.proc_inum */
{ "nsproxy.ipc_ns", -1 },
{ "nsproxy.mnt_ns", -1 },
{ "nsproxy.net_ns", -1 },
{ "nsproxy.uts_ns", -1 },
{ "pid.numbers", -1 },
{ "pid_type.PIDTYPE_PGID", -1 },
{ "pid_type.PIDTYPE_SID", -1 },
{ "signal_struct.pids", -1 },
{ "signal_struct.tty", -1 },
{ "task_struct.comm", -1 },
{ "task_struct.cred", -1 },
{ "task_struct.exit_code", -1 },
{ "task_struct.fs", -1 },
{ "task_struct.group_leader", -1 },
{ "task_struct.mm", -1 },
{ "task_struct.nsproxy", -1 },
{ "task_struct.pid", -1 },
{ "task_struct.pids", -1 },
{ "task_struct.real_parent", -1 },
{ "task_struct.start_boottime", -1 }, /* or task_struct.real_start_time */
{ "task_struct.signal", -1 }, /* or task_struct.pids via KLUDGE */
{ "task_struct.tgid", -1 },
{ "tty_driver.major", -1 },
{ "tty_driver.minor_start", -1 },
{ "tty_struct.driver", -1 },
{ "tty_struct.index", -1 },
{ "upid.nr", -1 },
{ "uts_namespace.proc_inum", -1 }, /* or uts_namespace.ns.inum */
{ "vfsmount.mnt_root", -1 },
{ NULL, -1 },
};
struct btf_alternative {
const char *new;
const char *old;
} btf_alternatives[] = {
{ "task_struct.start_boottime", "task_struct.real_start_time" },
{ "mm_struct.(anon).start_stack", "mm_struct.start_stack" },
{ "uts_namespace.proc_inum", "uts_namespace.ns.inum" },
{ "ipc_namespace.proc_inum", "ipc_namespace.ns.inum" },
{ "mnt_namespace.proc_inum", "mnt_namespace.ns.inum" },
{ "net_namespace.proc_inum", "net.ns.inum" },
{ "net_namespace.proc_inum", "net.proc_inum" },
{ NULL, NULL },
};
static const struct btf_type *
btf_type_by_name_kind(struct btf *btf, s32 *off, const char *name, int kind)
{
const struct btf_type *t;
s32 off1;
off1 = btf__find_by_name_kind(btf, name, kind);
if (off1 < 0)
return (NULL);
t = btf__type_by_id(btf, off1);
/* libbpf doesn't respect its own convention :( */
if (IS_ERR_OR_NULL(t))
return (NULL);
if (off)
*off = off1;
return (t);
}
static const struct btf_member *
btf_offsetof(struct btf *btf, struct btf_type const *t, const char *mname)
{
int i, vlen;
const struct btf_member *m;
const char *mname1;
if (btf_kind(t) != BTF_KIND_STRUCT)
return (errno = EINVAL, NULL);
vlen = btf_vlen(t);
m = (const struct btf_member *)(t + 1);
if (!strcmp(mname, "(anon)"))
mname = "";
for (i = 0; i < vlen; i++, m++) {
mname1 = btf__name_by_offset(btf, m->name_off);
if (IS_ERR_OR_NULL(mname1)) {
qwarnx("btf__name_by_offset(%d)", m->name_off);
continue;
}
if (strcmp(mname, mname1))
continue;
return (m);
}
return (NULL);
}
static s32
btf_root_offset2(struct btf *btf, const char *dotname)
{
const struct btf_type *parent;
const char *root_name, *child_name;
const struct btf_member *m;
char *last, buf[1024];
s32 off;
if (strlcpy(buf, dotname, sizeof(buf)) >= sizeof(buf))
return (-1);
root_name = strtok_r(buf, ".", &last);
if (root_name == NULL)
return (-1);
/* root must be a struct */
parent = btf_type_by_name_kind(btf, NULL, root_name, BTF_KIND_STRUCT);
if (parent == NULL)
return (-1);
off = 0;
while ((child_name = strtok_r(NULL, ".", &last)) != NULL) {
m = btf_offsetof(btf, parent, child_name);
if (m == NULL)
return (-1);
if (btf_kflag(parent)) {
off += BTF_MEMBER_BIT_OFFSET(m->offset);
/* no bit_size things for now */
if (BTF_MEMBER_BITFIELD_SIZE(m->offset) != 0)
return (-1);
} else
off += m->offset;
parent = btf__type_by_id(btf, m->type);
if (IS_ERR_OR_NULL(parent))
return (-1);
}
if ((off % 8) != 0)
return (-1);
return (off / 8);
}
s32
btf_root_offset(struct btf *btf, const char *dotname, int alternatives)
{
s32 off;
struct btf_alternative *alt;
off = btf_root_offset2(btf, dotname);
if (off != -1)
return (off);
if (!alternatives)
return (off);
for (alt = btf_alternatives; alt->new != NULL; alt++) {
if (strcmp(alt->new, dotname))
continue;
off = btf_root_offset2(btf, alt->old);
if (off != -1) {
qwarnx("found alternative for %s as %s (%d)",
dotname, alt->old, off);
break;
}
}
return (off);
}
static int
btf_enum_value(struct btf *btf, const char *dotname, ssize_t *uv)
{
int i;
const struct btf_type *parent;
const struct btf_enum *v;
char enum_type[256], enum_member[256];
char *dot;
if (strlcpy(enum_type, dotname, sizeof(enum_type)) >=
sizeof(enum_type))
return (-1);
if ((dot = strchr(enum_type, '.')) == NULL)
return (-1);
*dot = 0;
if (strlcpy(enum_member, dot + 1, sizeof(enum_member)) >=
sizeof(enum_member))
return (-1);
parent = btf_type_by_name_kind(btf, NULL, enum_type, BTF_KIND_ENUM);
if (parent == NULL)
return (-1);
v = btf_enum(parent);
for (i = 0; i < btf_vlen(parent); i++, v++) {
if (strcmp(btf__name_by_offset(btf, v->name_off), enum_member))
continue;
*uv = v->val;
return (0);
}
return (-1);
}
int
btf_number_of_params(struct btf *btf, const char *func)
{
s32 off;
const struct btf_type *t;
off = btf__find_by_name_kind(btf, func, BTF_KIND_FUNC);
if (off < 0)
return (-1);
t = btf__type_by_id(btf, off);
if (t == NULL)
return (-1);
t = btf__type_by_id(btf, t->type);
if (t == NULL)
return (-1);
if (!btf_is_func_proto(t))
return (-1);
return (btf_vlen(t));
}
int
btf_index_of_param(struct btf *btf, const char *func, const char *param)
{
s32 off;
struct btf_param *bp;
const struct btf_type *t;
const char *cand;
int i;
off = btf__find_by_name_kind(btf, func, BTF_KIND_FUNC);
if (off < 0)
return (-1);
t = btf__type_by_id(btf, off);
if (t == NULL)
return (-1);
t = btf__type_by_id(btf, t->type);
if (t == NULL)
return (-1);
for (i = 0, bp = btf_params(t); i < btf_vlen(t); i++, bp++) {
cand = btf__name_by_offset(btf, bp->name_off);
if (cand == NULL) {
warnx("name for offset %d not found, "
"this is likely a bug", bp->name_off);
continue;
}
/* found it */
if (!strcmp(cand, param))
return (i);
}
return (-1);
}
static struct quark_btf *
quark_btf_new(const char *new_name)
{
struct quark_btf *qbtf;
if ((qbtf = malloc(sizeof(*qbtf) + sizeof(base_targets))) == NULL)
return (NULL);
qbtf->kname = strdup(new_name);
if (qbtf->kname == NULL) {
free(qbtf);
return (NULL);
}
memcpy(qbtf->targets, base_targets, sizeof(base_targets));
return (qbtf);
}
static struct quark_btf *
quark_btf_dup(struct quark_btf *src)
{
struct quark_btf *qbtf;
if (src == NULL)
return (NULL);
qbtf = quark_btf_new(src->kname);
if (qbtf == NULL)
return (NULL);
memcpy(qbtf->targets, src->targets, sizeof(base_targets));
return (qbtf);
}
struct quark_btf *
quark_btf_open_hub(const char *version)
{
extern struct quark_btf *all_btfs[];
struct quark_btf **pp, *best, *cand;
int best_score;
/* paranoia */
if (version == NULL || strlen(version) == 0)
return (NULL);
best = NULL;
best_score = 0;
for (pp = all_btfs; (cand = *pp) != NULL; pp++) {
const char *pv, *pc;
int score;
score = 0;
/* paranoia */
if (cand->kname == NULL || strlen(cand->kname) == 0)
return (NULL);
/*
* Match head, the beginning of version
*/
for (pc = cand->kname, pv = version, score = 0;
*pc != 0 && *pv != 0 && *pc == *pv;
pc++, pv++, score++)
; /* NADA */
/*
* If we didn't score yet, don't bother matching tail
*/
if (score == 0)
continue;
/*
* Match tail, the end of version
*/
for (pc = cand->kname + strlen(cand->kname) - 1,
pv = version + strlen(version) - 1;
pc != cand->kname && pv != version && *pc == *pv;
pc--, pv--, score++)
; /* NADA */
if (score > best_score) {
best = cand;
best_score = score;
}
}
return (quark_btf_dup(best));
}
struct quark_btf *
quark_btf_open2(const char *path, const char *kname)
{
struct btf *btf;
int failed;
struct quark_btf *qbtf;
struct quark_btf_target *ta;
failed = 0;
errno = 0;
if (path == NULL)
btf = btf__load_vmlinux_btf();
else
btf = btf__parse(path, NULL);
if (IS_ERR_OR_NULL(btf)) {
if (errno == 0)
errno = ENOTSUP;
return (NULL);
}
if (kname == NULL)
kname = "sys";
if ((qbtf = quark_btf_new(kname)) == NULL)
return (NULL);
for (ta = qbtf->targets; ta->dotname != NULL; ta++) {
ta->offset = btf_root_offset(btf, ta->dotname, 1);
/* Maybe this is an enum */
if (ta->offset == -1 &&
btf_enum_value(btf, ta->dotname, &ta->offset) == -1) {
/*
* Be stingy with printing things that always fail
*/
if (quark_verbose >= QUARK_VL_DEBUG ||
(strcmp(ta->dotname, "signal_struct.pids") &&
strcmp(ta->dotname, "task_struct.pids"))) {
qwarnx("dotname=%s failed", ta->dotname);
}
failed++;
}
}
btf__free(btf);
for (ta = qbtf->targets; ta->dotname != NULL; ta++)
qdebugx("dotname=%s off=%ld (bitoff=%ld)",
ta->dotname, ta->offset, ta->offset * 8);
/*
* task_struct.signal is only present in new kernels, while
* task_struct.pids is only present in old kernels. If only one of
* either failed, it's all fine.
*/
if (failed == 1 &&
(quark_btf_offset(qbtf, "signal_struct.pids") == -1 ||
quark_btf_offset(qbtf, "task_struct.pids") == -1)) {
failed = 0;
}
if (failed) {
quark_btf_close(qbtf);
return (errno = ENOTSUP, NULL);
}
return (qbtf);
}
struct quark_btf *
quark_btf_open(void)
{
struct quark_btf *qbtf;
struct utsname uts;
/* Try the system BTF */
qbtf = quark_btf_open2(NULL, NULL);
/* Try BTF hub */
if (qbtf == NULL && uname(&uts) == 0)
qbtf = quark_btf_open_hub(uts.release);
return (qbtf);
}
void
quark_btf_close(struct quark_btf *qbtf)
{
free(qbtf->kname);
free(qbtf);
}
ssize_t
quark_btf_offset(struct quark_btf *qbtf, const char *dotname)
{
struct quark_btf_target *ta;
for (ta = qbtf->targets; ta->dotname != NULL; ta++) {
if (!strcmp(ta->dotname, dotname)) {
if (ta->offset != -1)
return (ta->offset);
break;
}
}
return (-1);
}