* [PATCH 04/20] btf: deprecate dt_shared_btf and introduce btf->parent instead
@ 2026-10-30 19:36 Kris Van Hees
0 siblings, 0 replies; only message in thread
From: Kris Van Hees @ 2026-10-30 19:36 UTC (permalink / raw)
To: dtrace, dtrace-devel
All module BTF (except vmlinux) share a common ancestor (the BTF data of
the vmlinux module). Represent this with a btf->parent reference.
For vmlinux, btf->parent will be NULL.
Signed-off-by: Kris Van Hees <kris.van.hees@oracle.com>
---
libdtrace/dt_btf.c | 517 +++++++++++++---------
libdtrace/dt_btf.h | 19 +-
libdtrace/dt_impl.h | 2 +-
libdtrace/dt_module.c | 18 +-
libdtrace/dt_open.c | 2 -
libdtrace/dt_prov_fbt.c | 16 +-
test/unittest/btf/test.x | 8 +
test/unittest/btf/tst.elf-vmlinux.sh | 31 ++
test/unittest/btf/tst.elf-vmlinux.x | 8 +
test/unittest/btf/tst.invalid-fallback.sh | 31 ++
test/unittest/btf/tst.invalid-fallback.x | 8 +
test/unittest/btf/tst.module-btf.sh | 46 ++
test/unittest/btf/tst.module-btf.x | 13 +
test/unittest/btf/tst.module-local.sh | 63 +++
test/unittest/btf/tst.module-local.x | 18 +
test/unittest/btf/tst.module-parent.sh | 46 ++
test/unittest/btf/tst.module-parent.x | 13 +
test/unittest/btf/tst.raw-vmlinux.sh | 30 ++
test/unittest/options/tst.ctf-only.sh | 27 ++
test/unittest/options/tst.ctf-only.x | 8 +
20 files changed, 693 insertions(+), 231 deletions(-)
create mode 100755 test/unittest/btf/test.x
create mode 100755 test/unittest/btf/tst.elf-vmlinux.sh
create mode 100755 test/unittest/btf/tst.elf-vmlinux.x
create mode 100755 test/unittest/btf/tst.invalid-fallback.sh
create mode 100755 test/unittest/btf/tst.invalid-fallback.x
create mode 100755 test/unittest/btf/tst.module-btf.sh
create mode 100755 test/unittest/btf/tst.module-btf.x
create mode 100755 test/unittest/btf/tst.module-local.sh
create mode 100755 test/unittest/btf/tst.module-local.x
create mode 100755 test/unittest/btf/tst.module-parent.sh
create mode 100755 test/unittest/btf/tst.module-parent.x
create mode 100755 test/unittest/btf/tst.raw-vmlinux.sh
create mode 100755 test/unittest/options/tst.ctf-only.sh
create mode 100755 test/unittest/options/tst.ctf-only.x
diff --git a/libdtrace/dt_btf.c b/libdtrace/dt_btf.c
index dce44987..194a251c 100644
--- a/libdtrace/dt_btf.c
+++ b/libdtrace/dt_btf.c
@@ -32,8 +32,8 @@ struct dt_btf {
size_t size; /* raw BTF data size */
btf_header_t *hdr; /* BTF header */
char *sdata; /* string data */
- uint32_t ssize; /* string data size */
- int32_t type_cnt; /* number of types */
+ const dt_btf_t *parent; /* parent BTF */
+ uint32_t type_cnt; /* number of types */
btf_type_t **types; /* type table */
ctf_id_t *ctfids; /* matching CTF type ids */
};
@@ -71,12 +71,86 @@ dt_btf_error(dtrace_hdl_t *dtp, int eid, const char *format, ...)
va_end(ap);
}
+static btf_type_t *
+dt_btf_type_by_id(const dt_btf_t *btf, int32_t id)
+{
+ const dt_btf_t *shared_btf;
+ uint32_t shared_cnt;
+
+ if (id == 0)
+ return &btf_void;
+
+ shared_btf = btf->parent;
+ if (shared_btf != NULL) {
+ shared_cnt = shared_btf->type_cnt;
+ if (id < shared_cnt)
+ return shared_btf->types[id];
+
+ if (shared_cnt > 0)
+ id -= shared_cnt - 1;
+ }
+
+ if (id < btf->type_cnt && id > 0)
+ return btf->types[id];
+
+ return NULL;
+}
+
+static btf_type_t *
+dt_btf_real_type_by_id(const dt_btf_t *btf, int32_t id)
+{
+ btf_type_t *type = dt_btf_type_by_id(btf, id);
+
+ while (type != NULL) {
+ switch (BTF_INFO_KIND(type->info)) {
+ case BTF_KIND_CONST:
+ case BTF_KIND_FUNC:
+ case BTF_KIND_RESTRICT:
+ case BTF_KIND_TYPE_TAG:
+ case BTF_KIND_TYPEDEF:
+ case BTF_KIND_VOLATILE:
+ type = dt_btf_type_by_id(btf, type->type);
+ continue;
+ default:
+ return type;
+ }
+ }
+
+ return NULL;
+}
+
+static const char *
+dt_btf_get_string(const dt_btf_t *btf, uint32_t off)
+{
+ const dt_btf_t *shared_btf;
+ int32_t shared_len;
+
+ shared_btf = btf->parent;
+ if (shared_btf != NULL && shared_btf->hdr != NULL) {
+ shared_len = shared_btf->hdr->str_len;
+ if (off < shared_len)
+ return shared_btf->sdata + off;
+
+ off -= shared_len;
+ }
+
+ if (off < btf->hdr->str_len)
+ return btf->sdata + off;
+
+ return NULL;
+}
+
static int
dt_btf_validate_header(dtrace_hdl_t *dtp, dt_btf_t *btf)
{
btf_header_t *hdr = (btf_header_t *)btf->data;
- btf->hdr = hdr;
+ if (btf->size < sizeof(btf_header_t)) {
+ dt_dprintf("BTF truncated header (%ld)\n", btf->size);
+ return dt_btf_set_errno(dtp, EINVAL);
+ }
+
+ hdr = (btf_header_t *)btf->data;
/* Validate magic number. */
if (hdr->magic != BTF_MAGIC) {
@@ -97,24 +171,39 @@ dt_btf_validate_header(dtrace_hdl_t *dtp, dt_btf_t *btf)
dt_dprintf("BTF type offset misaligned (%d)\n", hdr->type_off);
return dt_btf_set_errno(dtp, EINVAL);
}
- if (hdr->type_off + hdr->type_len > hdr->str_off) {
+ if (hdr->type_off >= btf->size - hdr->hdr_len) {
+ dt_dprintf("BTF bad type offset (%d)\n", hdr->type_off);
+ return dt_btf_set_errno(dtp, EINVAL);
+ }
+ if (hdr->str_off <= hdr->type_off ||
+ hdr->str_off >= btf->size - hdr->hdr_len) {
+ dt_dprintf("BTF bad string offset (%d)\n", hdr->str_off);
+ return dt_btf_set_errno(dtp, EINVAL);
+ }
+ if (hdr->type_len > hdr->str_off - hdr->type_off) {
dt_dprintf("BTF bad type section length (%d)\n", hdr->type_len);
return dt_btf_set_errno(dtp, EINVAL);
}
- if (hdr->hdr_len + hdr->str_off + hdr->str_len > btf->size) {
+ if (hdr->str_len > btf->size - hdr->hdr_len - hdr->str_off) {
dt_dprintf("BTF bad string section length (%d)\n",
hdr->str_len);
return dt_btf_set_errno(dtp, EINVAL);
}
+ if (((char *)btf->data)[hdr->hdr_len + hdr->str_off + hdr->str_len - 1] != 0) {
+ dt_dprintf("BTF string section is not NUL-terminated\n");
+ return dt_btf_set_errno(dtp, EINVAL);
+ }
+
+ btf->hdr = hdr;
return 0;
}
-static size_t
+static ssize_t
dt_btf_type_size(const char *ptr)
{
btf_type_t *type = (btf_type_t *)ptr;
- size_t size = sizeof(btf_type_t);
+ ssize_t size = sizeof(btf_type_t);
int vlen = BTF_INFO_VLEN(type->info);
switch (BTF_INFO_KIND(type->info)) {
@@ -164,7 +253,7 @@ dt_btf_decode(dtrace_hdl_t *dtp, dt_btf_t *btf)
char *tdata;
if (dt_btf_validate_header(dtp, btf) == -1)
- return -1;
+ return dt_btf_set_errno(dtp, EINVAL);
hdr = btf->hdr;
btf->sdata = (char *)btf->data + hdr->hdr_len + hdr->str_off;
@@ -175,19 +264,18 @@ dt_btf_decode(dtrace_hdl_t *dtp, dt_btf_t *btf)
end = tdata + hdr->type_len;
idx = 1;
while (ptr + sizeof(btf_type_t) <= end) {
- int size = dt_btf_type_size(ptr);
+ ssize_t size = dt_btf_type_size(ptr);
btf_type_t *type = (btf_type_t *)ptr;
- if (size < 0)
- return dt_btf_set_errno(dtp, -size);
- if (ptr + size > end)
- return dt_btf_set_errno(dtp, EINVAL);
- if (dt_btf_get_string(dtp, btf, type->name_off) == NULL)
- return dt_btf_set_errno(dtp, EINVAL);
+ if (size < 0 || ptr + size > end ||
+ dt_btf_get_string(btf, type->name_off) == NULL)
+ return dt_btf_set_errno(dtp, EINVAL);
idx++;
ptr += size;
}
+ if (ptr != end)
+ return dt_btf_set_errno(dtp, EINVAL);
/* Next, populate the type offsets table. */
btf->type_cnt = idx;
@@ -198,11 +286,16 @@ dt_btf_decode(dtrace_hdl_t *dtp, dt_btf_t *btf)
ptr = tdata;
idx = 1;
while (ptr + sizeof(btf_type_t) <= end) {
- int size = dt_btf_type_size(ptr);
+ ssize_t size = dt_btf_type_size(ptr);
+
+ if (size < 0)
+ return dt_btf_set_errno(dtp, EINVAL);
btf->types[idx++] = (btf_type_t *)ptr;
ptr += size;
}
+ if (ptr != end)
+ return dt_btf_set_errno(dtp, EINVAL);
return 0;
}
@@ -216,11 +309,10 @@ dt_btf_decode(dtrace_hdl_t *dtp, dt_btf_t *btf)
static dt_btf_t *
dt_btf_load(dtrace_hdl_t *dtp, const char *fn)
{
- FILE *fp;
+ FILE *fp = NULL;
dt_btf_t *btf = NULL;
btf_header_t hdr;
- void *data = NULL;
- Elf *elf;
+ Elf *elf = NULL;
size_t shstrs;
Elf_Scn *sp = NULL;
int err = 0;
@@ -230,36 +322,45 @@ dt_btf_load(dtrace_hdl_t *dtp, const char *fn)
return dt_btf_set_load_errno(dtp, ENOMEM);
fp = fopen(fn, "rb");
- if (fp == NULL)
- goto err;
+ if (fp == NULL) {
+ dtp->dt_btferr = errno;
+ goto fail;
+ }
/* First see whether this might be a file with raw BTF data. */
- if (fread(&hdr, 1, sizeof(hdr), fp) < sizeof(hdr))
+ if (fread(&hdr, 1, sizeof(hdr), fp) < sizeof(hdr)) {
+ dtp->dt_btferr = ferror(fp) ? EIO : EINVAL;
goto fail;
+ }
rewind(fp);
if (hdr.magic == BTF_MAGIC) {
struct stat st;
- if (fstat(fileno(fp), &st) == -1)
+ if (fstat(fileno(fp), &st) == -1) {
+ dtp->dt_btferr = errno;
goto fail;
+ }
- data = dt_alloc(dtp, st.st_size);
- if (data == NULL)
+ btf->data = dt_alloc(dtp, st.st_size);
+ if (btf->data == NULL) {
+ dtp->dt_btferr = ENOMEM;
goto fail;
+ }
- if (fread(data, 1, st.st_size, fp) < st.st_size)
+ if (fread(btf->data, 1, st.st_size, fp) < st.st_size) {
+ dtp->dt_btferr = ferror(fp) ? EIO : EINVAL;
goto fail;
+ }
- fclose(fp);
-
- btf->data = data;
btf->size = st.st_size;
} else {
/* Next see whether this might be an ELF file with BTF data. */
elf = elf_begin(fileno(fp), ELF_C_READ_MMAP, NULL);
- if (elf == NULL)
- goto elf_fail_no_end;
+ if (elf == NULL) {
+ dtp->dt_btferr = EINVAL;
+ goto elf_fail;
+ }
if (elf_getshdrstrndx(elf, &shstrs) == -1)
goto elf_fail;
@@ -282,59 +383,78 @@ dt_btf_load(dtrace_hdl_t *dtp, const char *fn)
if (edata == NULL)
goto elf_fail;
- data = dt_alloc(dtp, edata->d_size);
- if (data == NULL)
- goto elf_fail;
+ btf->data = dt_alloc(dtp, edata->d_size);
+ if (btf->data == NULL) {
+ dtp->dt_btferr = ENOMEM;
+ goto fail;
+ }
- memcpy(data, edata->d_buf, edata->d_size);
+ memcpy(btf->data, edata->d_buf, edata->d_size);
- btf->data = data;
btf->size = edata->d_size;
-
- elf_end(elf);
- fclose(fp);
-
break;
}
+ if (btf->data == NULL) {
+ dtp->dt_btferr = ENOENT;
+ goto fail;
+ }
}
+ if (elf != NULL)
+ elf_end(elf);
+ fclose(fp);
return btf;
elf_fail:
- elf_end(elf);
-
-elf_fail_no_end:
+ dtp->dt_btferr = EINVAL;
err = elf_errno();
- dt_btf_error(dtp, 0, "BTF: %s", elf_errmsg(err));
+ dt_btf_error(dtp, 0, "ELF: %s", elf_errmsg(err));
fail:
- fclose(fp);
- dt_free(dtp, data);
-
-err:
- dt_free(dtp, btf);
- dt_btf_set_errno(dtp, err ? err : errno);
-
+ if (elf != NULL)
+ elf_end(elf);
+ if (fp != NULL)
+ fclose(fp);
+ dt_dprintf("Cannot open BTF file %s: %s\n", fn,
+ dt_btf_errmsg(dtp->dt_btferr));
+ dt_btf_destroy(dtp, btf);
return NULL;
}
dt_btf_t *
-dt_btf_load_file(dtrace_hdl_t *dtp, const char *fn)
+dt_btf_load_file(dtrace_hdl_t *dtp, const char *fn, const dt_btf_t *parent)
{
dt_btf_t *btf;
+ /*
+ * Missing BTF or allocation failure returns NULL; invalid data leaves
+ * an empty BTF.
+ */
btf = dt_btf_load(dtp, fn);
- if (btf == NULL) {
- dt_dprintf("Cannot open BTF file %s: %s\n", fn,
- dt_btf_errmsg(dtp->dt_btferr));
+ if (btf == NULL)
return NULL;
- }
- if (dt_btf_decode(dtp, btf) == -1) {
+ btf->parent = parent;
+
+ /* Decode the loaded BTF data. */
+ if (btf->data && dt_btf_decode(dtp, btf) == -1) {
+ if (dtp->dt_btferr == ENOMEM) {
+ dt_btf_destroy(dtp, btf);
+ return NULL;
+ }
+
+ /*
+ * Failure to decode the data leaves us with just one option:
+ * pretend the module has no type data beyond its parent's
+ * data.
+ */
dt_dprintf("Cannot decode BTF data %s: %s\n", fn,
dt_btf_errmsg(dtp->dt_btferr));
- dt_btf_destroy(dtp, btf);
- return NULL;
+
+ dt_free(dtp, btf->data);
+ dt_free(dtp, btf->types);
+ memset(btf, 0, sizeof(*btf));
+ btf->parent = parent;
}
dt_dprintf("BTF file %s: %d types\n", fn, btf->type_cnt);
@@ -342,6 +462,70 @@ dt_btf_load_file(dtrace_hdl_t *dtp, const char *fn)
return btf;
}
+dt_btf_t *
+dt_btf_load_module(dtrace_hdl_t *dtp, dt_module_t *dmp)
+{
+ char *fn = NULL;
+ int rc = 0;
+ const dt_btf_t *parent = NULL;
+
+ assert(dmp != NULL);
+
+ /* If already tried this for the module, return what we have. */
+ if (dmp->dm_flags & DT_DM_BTF_LOADED)
+ return dmp->dm_btf;
+
+ /*
+ * If the module is not vmlinux, we will use the vmlinux BTF data as the
+ * parent.
+ */
+ if (dmp != dtp->dt_exec) {
+ parent = dtp->dt_exec->dm_btf;
+ /* If we do not have it yet, try to load it. */
+ if (parent == NULL)
+ parent = dt_btf_load_module(dtp, dtp->dt_exec);
+ /* If loading failed, the module cannot have BTF data either. */
+ if (parent == NULL)
+ return NULL;
+ }
+
+ /*
+ * Default: /sys/kernel/btf/<module>
+ * If "none", disable BTF.
+ * Otherwise: <BTF path>/<module>
+ */
+ if (dtp->dt_btf_path == NULL)
+ rc = asprintf(&fn, "/sys/kernel/btf/%s", dmp->dm_name);
+ else if (strcmp(dtp->dt_btf_path, "none") == 0)
+ return NULL;
+ else
+ rc = asprintf(&fn, "%s/%s", dtp->dt_btf_path, dmp->dm_name);
+
+ if (rc == -1)
+ return dt_btf_set_load_errno(dtp, ENOMEM);
+
+ dmp->dm_btf = dt_btf_load_file(dtp, fn, parent);
+ free(fn);
+
+ if (dmp->dm_btf == NULL) {
+ if (dtp->dt_btferr != ENOENT)
+ return NULL;
+
+ /* A missing module BTF file still inherits its parent's types. */
+ if (parent != NULL) {
+ dmp->dm_btf = dt_zalloc(dtp, sizeof(dt_btf_t));
+ if (dmp->dm_btf == NULL)
+ return dt_btf_set_load_errno(dtp, ENOMEM);
+
+ dmp->dm_btf->parent = parent;
+ }
+ }
+
+ dmp->dm_flags |= DT_DM_BTF_LOADED;
+
+ return dmp->dm_btf;
+}
+
void
dt_btf_destroy(dtrace_hdl_t *dtp, dt_btf_t *btf)
{
@@ -371,8 +555,9 @@ dt_ctf_error(dtrace_hdl_t *dtp, ctf_dict_t *ctf)
static ctf_id_t
dt_btf_add_to_ctf(dtrace_hdl_t *dtp, dt_btf_t *btf, ctf_dict_t *ctf,
- int32_t type_id)
+ uint32_t type_id)
{
+ const dt_btf_t *shared_btf = btf->parent;
btf_type_t *type;
int kind, vlen;
const char *name;
@@ -383,11 +568,12 @@ dt_btf_add_to_ctf(dtrace_hdl_t *dtp, dt_btf_t *btf, ctf_dict_t *ctf,
* If we already have shared_ctf, we may be looking for a type in that
* shared_ctf.
*/
- if (dt_module_shared_ctf(dtp) && btf != dtp->dt_shared_btf) {
- if (type_id < dtp->dt_shared_btf->type_cnt)
- return dtp->dt_shared_btf->ctfids[type_id];
+ if (dt_module_shared_ctf(dtp) && shared_btf != NULL) {
+ if (type_id < shared_btf->type_cnt)
+ return shared_btf->ctfids[type_id];
- type_id -= dtp->dt_shared_btf->type_cnt - 1;
+ if (shared_btf->type_cnt > 0)
+ type_id -= shared_btf->type_cnt - 1;
}
assert(type_id < btf->type_cnt);
@@ -400,9 +586,11 @@ dt_btf_add_to_ctf(dtrace_hdl_t *dtp, dt_btf_t *btf, ctf_dict_t *ctf,
type = btf->types[type_id];
kind = BTF_INFO_KIND(type->info);
vlen = BTF_INFO_VLEN(type->info);
- name = dt_btf_get_string(dtp, btf, type->name_off);
+ name = dt_btf_get_string(btf, type->name_off);
+ if (name == NULL)
+ return dt_ctf_set_errno(dtp, ECTF_NAMELEN);
- if (name && name[0]) {
+ if (name[0]) {
char n[DT_TYPE_NAMELEN];
/* Do we already have this type? */
@@ -498,7 +686,9 @@ dt_btf_add_to_ctf(dtrace_hdl_t *dtp, dt_btf_t *btf, ctf_dict_t *ctf,
int err;
const char *iname;
- iname = dt_btf_get_string(dtp, btf, item->name_off);
+ iname = dt_btf_get_string(btf, item->name_off);
+ if (iname == NULL)
+ return dt_ctf_set_errno(dtp, ECTF_NAMELEN);
err = ctf_add_enumerator(ctf, ctfid, iname, item->val);
if (err == CTF_ERR)
return dt_ctf_error(dtp, ctf);
@@ -599,7 +789,9 @@ dt_btf_add_to_ctf(dtrace_hdl_t *dtp, dt_btf_t *btf, ctf_dict_t *ctf,
mbid = dt_btf_add_to_ctf(dtp, btf, ctf, item->type);
if (mbid == CTF_ERR)
return CTF_ERR;
- mname = dt_btf_get_string(dtp, btf, item->name_off);
+ mname = dt_btf_get_string(btf, item->name_off);
+ if (mname == NULL)
+ return dt_ctf_set_errno(dtp, ECTF_NAMELEN);
if (BTF_INFO_KFLAG(type->info)) {
enc.cte_format = 0;
@@ -741,6 +933,13 @@ dt_btf_to_ctf(dtrace_hdl_t *dtp, dt_module_t *dmp, dt_btf_t *btf)
if (!btf)
goto out;
+ /*
+ * If we were not able to read the BTF data, and we are constructing
+ * 'shared_ctf', we need space for the default 'void' type.
+ */
+ if (!dmp && !btf->type_cnt)
+ btf->type_cnt = 1;
+
btf->ctfids = dt_calloc(dtp, btf->type_cnt, sizeof(ctf_id_t));
for (i = 1; i < btf->type_cnt; i++)
btf->ctfids[i] = CTF_ERR;
@@ -758,14 +957,14 @@ dt_btf_to_ctf(dtrace_hdl_t *dtp, dt_module_t *dmp, dt_btf_t *btf)
/*
* Any module other than 'vmlinux' inherits the types from
* 'vmlinux'.
- * Shared types are 1 through dt_shared_btf->type_cnt - 1
+ * Shared types are 1 through btf->parent->type_cnt - 1
* (base).
* A module's types are base through base + btf->type_cnt - 1,
* but the types are stored in the BTF types array with indexes
* 1 through btf->type_cnt - 1.
*/
- if (btf != dtp->dt_shared_btf)
- base = dtp->dt_shared_btf->type_cnt - 1;
+ if (btf->parent != NULL && btf->parent->type_cnt > 0)
+ base = btf->parent->type_cnt - 1;
}
for (i = 1; i < btf->type_cnt; i++) {
@@ -789,40 +988,6 @@ out:
}
#endif
-dt_btf_t *
-dt_btf_load_module(dtrace_hdl_t *dtp, dt_module_t *dmp)
-{
- char *fn = NULL;
- int rc = 0;
- dt_btf_t *btf;
-
- if (dmp->dm_btf)
- return dmp->dm_btf;
-
- /*
- * Default: /sys/kernel/btf/<module>
- * If "none", disable BTF.
- * Otherwise: <BTF path>/<module>
- */
- if (dtp->dt_btf_path == NULL)
- rc = asprintf(&fn, "/sys/kernel/btf/%s", dmp->dm_name);
- else if (strcmp(dtp->dt_btf_path, "none") == 0)
- return NULL;
- else
- rc = asprintf(&fn, "%s/%s", dtp->dt_btf_path, dmp->dm_name);
-
- if (rc == -1)
- return dt_btf_set_load_errno(dtp, ENOMEM);
-
- btf = dt_btf_load_file(dtp, fn);
- free(fn);
-
- if (btf && !dtp->dt_shared_btf && strcmp(dmp->dm_name, "vmlinux") == 0)
- dtp->dt_shared_btf = btf;
-
- return dmp->dm_btf = btf;
-}
-
ctf_dict_t *
dt_btf_module_ctf(dtrace_hdl_t *dtp, dt_module_t *dmp)
{
@@ -854,8 +1019,7 @@ dt_btf_module_ctf(dtrace_hdl_t *dtp, dt_module_t *dmp)
shared->dm_flags |= DT_DM_LOADED;
}
- ctf = dt_btf_to_ctf(dtp, dmp,
- btf == dtp->dt_shared_btf ? NULL : btf);
+ ctf = dt_btf_to_ctf(dtp, dmp, btf);
dt_dprintf("Generated %s CTF from BTF (%d types).\n", dmp->dm_name,
btf->type_cnt);
@@ -864,112 +1028,65 @@ dt_btf_module_ctf(dtrace_hdl_t *dtp, dt_module_t *dmp)
return ctf;
}
-static btf_type_t *
-dt_btf_type_by_id(dtrace_hdl_t *dtp, const dt_btf_t *btf, int32_t id)
-{
- if (id == 0)
- return &btf_void;
- if (id < dtp->dt_shared_btf->type_cnt)
- return dtp->dt_shared_btf->types[id];
-
- return btf->types[id - (dtp->dt_shared_btf->type_cnt - 1)];
-}
-
-static btf_type_t *
-dt_btf_real_type_by_id(dtrace_hdl_t *dtp, const dt_btf_t *btf, int32_t id)
+static uint32_t
+dt_btf_lookup_name_kind2(const dt_btf_t *btf, int32_t base, const char *name,
+ uint32_t kind)
{
- btf_type_t *type = dt_btf_type_by_id(dtp, btf, id);
-
- do {
- switch (BTF_INFO_KIND(type->info)) {
- case BTF_KIND_CONST:
- case BTF_KIND_FUNC:
- case BTF_KIND_RESTRICT:
- case BTF_KIND_TYPE_TAG:
- case BTF_KIND_TYPEDEF:
- case BTF_KIND_VOLATILE:
- type = dt_btf_type_by_id(dtp, btf, type->type);
- default:
- return type;
- }
- } while (type != NULL);
-
- return NULL;
-}
+ uint32_t i;
-const char *
-dt_btf_get_string(dtrace_hdl_t *dtp, const dt_btf_t *btf, uint32_t off)
-{
- if (dtp->dt_shared_btf == NULL)
- goto ok;
+ /* Look through the type data for a match. */
+ for (i = 1; i < btf->type_cnt; i++) {
+ const btf_type_t *type = btf->types[i];
+ const char *str;
- /* Check if the offset is within the base BTF string area. */
- if (btf == dtp->dt_shared_btf || off < dtp->dt_shared_btf->hdr->str_len)
- return dtp->dt_shared_btf->sdata + off;
+ if (BTF_INFO_KIND(type->info) != kind)
+ continue;
- off -= dtp->dt_shared_btf->hdr->str_len;
-ok:
- if (off < btf->hdr->str_len)
- return btf->sdata + off;
+ str = dt_btf_get_string(btf, type->name_off);
+ if (str == NULL)
+ continue;
+ if (strcmp(name, str) == 0)
+ return i + base;
+ }
- return dt_btf_set_load_errno(dtp, EINVAL);
+ return BTF_TYPE_NONE;
}
-int32_t
+uint32_t
dt_btf_lookup_name_kind(dtrace_hdl_t *dtp, dt_module_t *dmp, const char *name,
uint32_t kind)
{
- dt_btf_t *btf = dmp->dm_btf;
- int32_t i, base = 0;
+ dt_btf_t *btf;
+ const dt_btf_t *parent;
+ int32_t base = 0, id;
if (kind == BTF_KIND_UNKN)
- return -ENOENT;
+ return BTF_TYPE_NONE;
if (strcmp(name, "void") == 0)
return 0;
- /* Ensure the shared BTF is loaded. */
- if (!dtp->dt_shared_btf)
- dt_btf_load_module(dtp, dtp->dt_exec);
-
- /* If the module does not have BTF data yet, try to load it. */
- if (!btf) {
- btf = dt_btf_load_module(dtp, dmp);
+ /* Get BTF data for the module. */
+ btf = dt_btf_load_module(dtp, dmp);
- /* If no BTF module data was found, use the shared BTF. */
- if (!btf)
- btf = dtp->dt_shared_btf;
-
- if (!btf)
- return -ENOENT;
- }
+ /* No type lookup without BTF data. */
+ if (!btf)
+ return BTF_TYPE_NONE;
/*
- * Any module other than 'vmlinux' inherits the types from 'vmlinux'.
- * The shared types are 1 through (base = dtp->dt_btf->type_cnt - 1).
- * A module's types are base through (base + btf->type_cnt - 1), but
- * the types are stored in the BTF types array with indexes 1 through
- * (btf->type_cnt - 1).
+ * BTF type data for a module is split between two BTF data source:
+ * shared [ 0 .. parent->type_cnt - 1 ]
+ * local base + [ 1 .. type_cnt ]
+ * with base = parent->type_cnt - 1
*/
- if (btf != dtp->dt_shared_btf)
- base = dtp->dt_shared_btf->type_cnt - 1;
-
-
- for (i = 1; i < btf->type_cnt; i++) {
- const btf_type_t *type = btf->types[i];
- const char *str;
-
- if (BTF_INFO_KIND(type->info) != kind)
- continue;
-
- str = dt_btf_get_string(dtp, btf, type->name_off);
- if (str && strcmp(name, str) == 0)
- return i + base;
- }
+ parent = btf->parent;
+ if (parent != NULL && parent->type_cnt > 0)
+ base = parent->type_cnt - 1;
- if (base > 0)
- return dt_btf_lookup_name_kind(dtp, dtp->dt_exec, name, kind);
+ id = dt_btf_lookup_name_kind2(btf, base, name, kind);
+ if (id == BTF_TYPE_NONE && parent != NULL)
+ id = dt_btf_lookup_name_kind2(parent, 0, name, kind);
- return -ENOENT;
+ return id;
}
int
@@ -1025,9 +1142,9 @@ dt_btf_module_fd(const dt_module_t *dmp)
}
int
-dt_btf_func_argc(dtrace_hdl_t *dtp, const dt_btf_t *btf, uint32_t id)
+dt_btf_func_argc(const dt_btf_t *btf, uint32_t id)
{
- btf_type_t *type = dt_btf_real_type_by_id(dtp, btf, id);
+ btf_type_t *type = dt_btf_real_type_by_id(btf, id);
if (type == NULL)
return -1;
@@ -1039,9 +1156,9 @@ dt_btf_func_argc(dtrace_hdl_t *dtp, const dt_btf_t *btf, uint32_t id)
}
int
-dt_btf_func_is_void(dtrace_hdl_t *dtp, const dt_btf_t *btf, uint32_t id)
+dt_btf_func_is_void(const dt_btf_t *btf, uint32_t id)
{
- btf_type_t *type = dt_btf_real_type_by_id(dtp, btf, id);
+ btf_type_t *type = dt_btf_real_type_by_id(btf, id);
if (type != NULL &&
BTF_INFO_KIND(type->info) == BTF_KIND_FUNC_PROTO && type->type == 0)
@@ -1058,9 +1175,9 @@ dt_btf_func_is_void(dtrace_hdl_t *dtp, const dt_btf_t *btf, uint32_t id)
*/
#define MAX_BPF_FUNC_REG_REGS 5
int
-dt_btf_func_is_traceable(dtrace_hdl_t *dtp, const dt_btf_t *btf, uint32_t id)
+dt_btf_func_is_traceable(const dt_btf_t *btf, uint32_t id)
{
- btf_type_t *type = dt_btf_real_type_by_id(dtp, btf, id);
+ btf_type_t *type = dt_btf_real_type_by_id(btf, id);
int argc;
/* If no prototype is found, BPF fprobes do not work. */
@@ -1069,7 +1186,7 @@ dt_btf_func_is_traceable(dtrace_hdl_t *dtp, const dt_btf_t *btf, uint32_t id)
/* If the return type is a struct or union, BPF fprobes do not work. */
if (type->type != 0) {
- btf_type_t *rtype = dt_btf_real_type_by_id(dtp, btf, type->type);
+ btf_type_t *rtype = dt_btf_real_type_by_id(btf, type->type);
if (rtype == NULL)
return 0;
@@ -1095,7 +1212,7 @@ dt_btf_func_is_traceable(dtrace_hdl_t *dtp, const dt_btf_t *btf, uint32_t id)
return 0;
for (i = 0; i < argc; i++) {
- type = dt_btf_real_type_by_id(dtp, btf, args[i].type);
+ type = dt_btf_real_type_by_id(btf, args[i].type);
if (type == NULL)
return 0;
diff --git a/libdtrace/dt_btf.h b/libdtrace/dt_btf.h
index 2c921a03..a581ae6d 100644
--- a/libdtrace/dt_btf.h
+++ b/libdtrace/dt_btf.h
@@ -1,6 +1,6 @@
/*
* Oracle Linux DTrace.
- * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2023, 2026, Oracle and/or its affiliates. All rights reserved.
* Licensed under the Universal Permissive License v 1.0 as shown at
* http://oss.oracle.com/licenses/upl.
*/
@@ -14,22 +14,19 @@
extern "C" {
#endif
+#define BTF_TYPE_NONE 0x7fffffff
+
typedef struct dt_btf dt_btf_t;
typedef struct bpf_btf_info btf_info_t;
extern void dt_btf_destroy(dtrace_hdl_t *, dt_btf_t *);
extern dt_btf_t *dt_btf_load_module(dtrace_hdl_t *, dt_module_t *);
extern ctf_dict_t *dt_btf_module_ctf(dtrace_hdl_t *, dt_module_t *);
-extern const char *dt_btf_get_string(dtrace_hdl_t *, const dt_btf_t *,
- uint32_t);
-extern int32_t dt_btf_lookup_name_kind(dtrace_hdl_t *, dt_module_t *,
- const char *, uint32_t);
-extern int dt_btf_func_argc(dtrace_hdl_t *dtp, const dt_btf_t *btf,
- uint32_t id);
-extern int dt_btf_func_is_void(dtrace_hdl_t *dtp, const dt_btf_t *btf,
- uint32_t id);
-extern int dt_btf_func_is_traceable(dtrace_hdl_t *dtp, const dt_btf_t *btf,
- uint32_t id);
+extern uint32_t dt_btf_lookup_name_kind(dtrace_hdl_t *, dt_module_t *,
+ const char *, uint32_t);
+extern int dt_btf_func_argc(const dt_btf_t *btf, uint32_t id);
+extern int dt_btf_func_is_void(const dt_btf_t *btf, uint32_t id);
+extern int dt_btf_func_is_traceable(const dt_btf_t *btf, uint32_t id);
extern int dt_btf_get_module_ids(dtrace_hdl_t *);
extern int dt_btf_module_fd(const dt_module_t *);
diff --git a/libdtrace/dt_impl.h b/libdtrace/dt_impl.h
index 682dda82..cf04d0f6 100644
--- a/libdtrace/dt_impl.h
+++ b/libdtrace/dt_impl.h
@@ -202,6 +202,7 @@ typedef struct dt_kern_path {
#define DT_DM_KERNEL 0x02 /* module is associated with a kernel object */
#define DT_DM_CTF_ARCHIVED 0x04 /* module found in a CTF archive */
#define DT_DM_KERN_UNLOADED 0x08 /* module not loaded into the kernel */
+#define DT_DM_BTF_LOADED 0x10 /* module BTF data loaded */
#define DT_DM_SHARED 0x20 /* module owns shared kernel CTF */
#define DT_DM_KERN_SYMS 0x40 /* kernel symbols discovered and finalized */
@@ -311,7 +312,6 @@ struct dtrace_hdl {
ctf_archive_t *dt_ctfa; /* ctf archive for the entire kernel tree */
void *dt_ctfa_data; /* backing data for dt_ctfa */
char *dt_btf_path; /* path to vmlinux.btf */
- struct dt_btf *dt_shared_btf; /* BTF data for the kernel (shared) */
dt_htab_t *dt_kernpaths; /* hash table of dt_kern_path_t's */
dt_module_t *dt_exec; /* pointer to executable module */
dt_module_t *dt_cdefs; /* pointer to C dynamic type module */
diff --git a/libdtrace/dt_module.c b/libdtrace/dt_module.c
index 10e54412..0b0023a3 100644
--- a/libdtrace/dt_module.c
+++ b/libdtrace/dt_module.c
@@ -1045,8 +1045,7 @@ dt_module_clear_elf(dt_module_t *dmp)
static void
dt_module_destroy(dtrace_hdl_t *dtp, dt_module_t *dmp)
{
- if (dmp->dm_btf != dtp->dt_shared_btf)
- dt_btf_destroy(dtp, dmp->dm_btf);
+ dt_btf_destroy(dtp, dmp->dm_btf);
ctf_close(dmp->dm_ctfp);
dt_module_clear_elf(dmp);
dt_symtab_destroy(dtp, dmp->dm_kernsyms);
@@ -1253,23 +1252,24 @@ dt_kern_module_ctf_from_btf(dtrace_hdl_t *dtp, dt_module_t *dmp)
static void
dt_kern_module_find_btf(dtrace_hdl_t *dtp, dt_module_t *dmp)
{
+ /* The vmlinux module nust already exist as dtp->dt_exec. */
+ assert(dtp->dt_exec != NULL);
+
/*
* If there is no shared BTF data, and we're trying to load BTF data
- * for any module other than "vmlinux", we can conclude no module has
+ * for any module other than vmlinux, we can conclude no module has
* valid BTF data.
*/
- if (dtp->dt_shared_btf == NULL && strcmp(dmp->dm_name, "vmlinux") != 0)
+ if (dtp->dt_exec->dm_btf == NULL && dmp != dtp->dt_exec)
return;
dt_dprintf("Loading BTF for module %s.\n", dmp->dm_name);
/*
- * If no module specific BTF is found, we assume it is a builtin
- * module and we assign the shared BTF to it. That is the fallback
- * anyway for type lookups in modules, so it is safe.
+ * If no module specific BTF is found, the kernel either does not
+ * provide any BTF data or something went wrong. Neither is fatal.
*/
- if (dt_btf_load_module(dtp, dmp) == NULL)
- dmp->dm_btf = dtp->dt_shared_btf;
+ dt_btf_load_module(dtp, dmp);
}
/*
diff --git a/libdtrace/dt_open.c b/libdtrace/dt_open.c
index 6182a5da..245ccb70 100644
--- a/libdtrace/dt_open.c
+++ b/libdtrace/dt_open.c
@@ -1314,8 +1314,6 @@ dtrace_close(dtrace_hdl_t *dtp)
dt_htab_destroy(dtp->dt_mods);
dt_htab_destroy(dtp->dt_kernpaths);
- if (dtp->dt_shared_btf != NULL)
- dt_btf_destroy(dtp, dtp->dt_shared_btf);
if (dtp->dt_ctfa != NULL)
ctf_arc_close(dtp->dt_ctfa);
free(dtp->dt_ctfa_data);
diff --git a/libdtrace/dt_prov_fbt.c b/libdtrace/dt_prov_fbt.c
index 3ae03581..8bdfc89f 100644
--- a/libdtrace/dt_prov_fbt.c
+++ b/libdtrace/dt_prov_fbt.c
@@ -103,11 +103,11 @@ static int provide_probe(dtrace_hdl_t *dtp, dt_module_t *dmp,
* details).
*/
if (prv->impl->prog_type == BPF_PROG_TYPE_TRACING) {
- int32_t btf_id;
+ uint32_t btf_id;
btf_id = dt_btf_lookup_name_kind(dtp, dmp, pdp->fun, BTF_KIND_FUNC);
- if (btf_id <= 0 ||
- !dt_btf_func_is_traceable(dtp, dmp->dm_btf, btf_id))
+ if (btf_id == BTF_TYPE_NONE ||
+ !dt_btf_func_is_traceable(dmp->dm_btf, btf_id))
return -1;
}
@@ -355,7 +355,7 @@ static int fprobe_probe_info(dtrace_hdl_t *dtp, const dt_probe_t *prp,
{
const dtrace_probedesc_t *desc = prp->desc;
dt_module_t *dmp;
- int32_t btf_id;
+ uint32_t btf_id;
int i, argc = 0;
dt_argdesc_t *argv = NULL;
@@ -364,14 +364,14 @@ static int fprobe_probe_info(dtrace_hdl_t *dtp, const dt_probe_t *prp,
goto done;
btf_id = dt_btf_lookup_name_kind(dtp, dmp, desc->fun, BTF_KIND_FUNC);
- if (btf_id <= 0)
+ if (btf_id == BTF_TYPE_NONE)
goto done;
dt_tp_probe_set_id(prp, btf_id);
if (strcmp(desc->prb, "return") == 0) {
/* Void function return probes only provide 1 argument. */
- argc = dt_btf_func_is_void(dtp, dmp->dm_btf, btf_id) ? 1 : 2;
+ argc = dt_btf_func_is_void(dmp->dm_btf, btf_id) ? 1 : 2;
argv = dt_calloc(dtp, argc, sizeof(dt_argdesc_t));
if (argv == NULL)
@@ -392,8 +392,8 @@ static int fprobe_probe_info(dtrace_hdl_t *dtp, const dt_probe_t *prp,
goto done;
}
- argc = dt_btf_func_argc(dtp, dmp->dm_btf, btf_id);
- if (argc == 0)
+ argc = dt_btf_func_argc(dmp->dm_btf, btf_id);
+ if (argc <= 0)
goto done;
argv = dt_calloc(dtp, argc, sizeof(dt_argdesc_t));
diff --git a/test/unittest/btf/test.x b/test/unittest/btf/test.x
new file mode 100755
index 00000000..3aad9b2a
--- /dev/null
+++ b/test/unittest/btf/test.x
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+if [ ! -r /sys/kernel/btf/vmlinux ]; then
+ echo "No vmlinux BTF data found."
+ exit 2
+fi
+
+exit 0
diff --git a/test/unittest/btf/tst.elf-vmlinux.sh b/test/unittest/btf/tst.elf-vmlinux.sh
new file mode 100755
index 00000000..f8132e74
--- /dev/null
+++ b/test/unittest/btf/tst.elf-vmlinux.sh
@@ -0,0 +1,31 @@
+#!/bin/bash
+#
+# ASSERTION: The BTF loader reads a .BTF section from an ELF object.
+#
+
+dtrace=$1
+dir=$tmpdir/btf-elf-vmlinux.$$
+mkdir -p "$dir" || exit 1
+
+"${OBJCOPY:-objcopy}" --add-section \
+ .BTF=/sys/kernel/btf/vmlinux /bin/true "$dir/vmlinux" || exit 1
+
+"$dtrace" $dt_flags -e -xdebug -xctfpath="$dir/missing.ctfa" \
+ -xbtfpath="$dir" -n '
+BEGIN
+{
+ this->size = sizeof(struct vmlinux`task_struct);
+}' >"$dir/output" 2>&1
+rc=$?
+if [ "$rc" -ne 0 ]; then
+ cat "$dir/output"
+ exit 1
+fi
+
+if ! grep -q "BTF file $dir/vmlinux: [1-9][0-9]* types" "$dir/output" ||
+ ! grep -q 'Generated vmlinux CTF from BTF' "$dir/output"; then
+ cat "$dir/output"
+ exit 1
+fi
+
+exit 0
diff --git a/test/unittest/btf/tst.elf-vmlinux.x b/test/unittest/btf/tst.elf-vmlinux.x
new file mode 100755
index 00000000..c444fe67
--- /dev/null
+++ b/test/unittest/btf/tst.elf-vmlinux.x
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+if ! command -v "${OBJCOPY:-objcopy}" >/dev/null 2>&1; then
+ echo "objcopy is needed to create an ELF BTF fixture."
+ exit 2
+fi
+
+exit 0
diff --git a/test/unittest/btf/tst.invalid-fallback.sh b/test/unittest/btf/tst.invalid-fallback.sh
new file mode 100755
index 00000000..58632306
--- /dev/null
+++ b/test/unittest/btf/tst.invalid-fallback.sh
@@ -0,0 +1,31 @@
+#!/bin/bash
+#
+# ASSERTION: Invalid raw BTF is rejected and a usable CTF archive is loaded.
+#
+
+dtrace=$1
+dir=$tmpdir/btf-invalid-fallback.$$
+mkdir -p "$dir" || exit 1
+
+# Preserve the BTF magic, but truncate the file at the end of its header.
+# This enters the raw BTF loader and fails header validation.
+head -c 24 /sys/kernel/btf/vmlinux >"$dir/vmlinux" || exit 1
+
+"$dtrace" $dt_flags -e -xdebug -xbtfpath="$dir" -n '
+BEGIN
+{
+ this->size = sizeof(struct vmlinux`task_struct);
+}' >"$dir/output" 2>&1
+rc=$?
+if [ "$rc" -ne 0 ]; then
+ cat "$dir/output"
+ exit 1
+fi
+
+if ! grep -q "Cannot decode BTF data $dir/vmlinux" "$dir/output" ||
+ ! grep -q 'Loaded shared CTF from archive' "$dir/output"; then
+ cat "$dir/output"
+ exit 1
+fi
+
+exit 0
diff --git a/test/unittest/btf/tst.invalid-fallback.x b/test/unittest/btf/tst.invalid-fallback.x
new file mode 100755
index 00000000..372dc5c0
--- /dev/null
+++ b/test/unittest/btf/tst.invalid-fallback.x
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+if [ ! -r /lib/modules/$(uname -r)/kernel/vmlinux.ctfa ]; then
+ echo "No vmlinux CTF archive found."
+ exit 2
+fi
+
+exit 0
diff --git a/test/unittest/btf/tst.module-btf.sh b/test/unittest/btf/tst.module-btf.sh
new file mode 100755
index 00000000..fe1f7b54
--- /dev/null
+++ b/test/unittest/btf/tst.module-btf.sh
@@ -0,0 +1,46 @@
+#!/bin/bash
+#
+# ASSERTION: Split module BTF loads with vmlinux as its parent and converts
+# into a CTF container usable for scoped parent type lookup.
+#
+
+dtrace=$1
+dir=$tmpdir/btf-module.$$
+mkdir -p "$dir" || exit 1
+ln -s /sys/kernel/btf/vmlinux "$dir/vmlinux" || exit 1
+
+module=
+for path in /sys/kernel/btf/*; do
+ [ "$path" = /sys/kernel/btf/vmlinux ] && continue
+ [ -r "$path" ] || continue
+ name=${path##*/}
+ [[ $name =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]] || continue
+ module=$name
+ ln -s "$path" "$dir/$module" || exit 1
+ break
+done
+
+if [ -z "$module" ]; then
+ echo "No suitable loaded kernel module with BTF data."
+ exit 1
+fi
+
+"$dtrace" $dt_flags -e -xdebug -xctfpath="$dir/missing.ctfa" \
+ -xbtfpath="$dir" -n "
+BEGIN
+{
+ this->size = sizeof(struct ${module}\`task_struct);
+}" >"$dir/output" 2>&1
+rc=$?
+if [ "$rc" -ne 0 ]; then
+ cat "$dir/output"
+ exit 1
+fi
+
+if ! grep -q "BTF file $dir/$module: [1-9][0-9]* types" "$dir/output" ||
+ ! grep -q "Generated $module CTF from BTF" "$dir/output"; then
+ cat "$dir/output"
+ exit 1
+fi
+
+exit 0
diff --git a/test/unittest/btf/tst.module-btf.x b/test/unittest/btf/tst.module-btf.x
new file mode 100755
index 00000000..d27b0499
--- /dev/null
+++ b/test/unittest/btf/tst.module-btf.x
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+for path in /sys/kernel/btf/*; do
+ [ "$path" = /sys/kernel/btf/vmlinux ] && continue
+ [ -r "$path" ] || continue
+ module=${path##*/}
+ if [[ $module =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]]; then
+ exit 0
+ fi
+done
+
+echo "No suitable loaded kernel module with BTF data."
+exit 2
diff --git a/test/unittest/btf/tst.module-local.sh b/test/unittest/btf/tst.module-local.sh
new file mode 100755
index 00000000..74a07370
--- /dev/null
+++ b/test/unittest/btf/tst.module-local.sh
@@ -0,0 +1,63 @@
+#!/bin/bash
+#
+# ASSERTION: A module-local BTF type converts to CTF and the module also
+# inherits types from its vmlinux BTF parent.
+#
+
+dtrace=$1
+dir=$tmpdir/btf-module-local.$$
+mkdir -p "$dir" || exit 1
+ln -s /sys/kernel/btf/vmlinux "$dir/vmlinux" || exit 1
+
+module=
+for path in /sys/kernel/btf/*; do
+ [ "$path" = /sys/kernel/btf/vmlinux ] && continue
+ [ -r "$path" ] || continue
+ name=${path##*/}
+ [[ $name =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]] || continue
+ module=$name
+ break
+done
+[ -n "$module" ] || exit 1
+
+# BTF name offsets in split data follow the parent string table. Build a
+# single local struct whose name is absent from the real kernel BTF.
+python3 - "$dir/$module" <<'PY' || exit 1
+import struct
+import sys
+
+with open('/sys/kernel/btf/vmlinux', 'rb') as parent:
+ header = parent.read(24)
+magic, version, flags, header_len, type_off, type_len, str_off, str_len = \
+ struct.unpack('<HBBIIIII', header)
+if magic != 0xeb9f or version != 1:
+ raise ValueError('unsupported parent BTF header')
+
+name = b'\0dtrace_btf_local_test\0'
+type_data = struct.pack('<III', str_len + 1, 4 << 24, 8)
+child_header = struct.pack('<HBBIIIII', 0xeb9f, 1, 0, 24,
+ 0, len(type_data), len(type_data), len(name))
+with open(sys.argv[1], 'wb') as child:
+ child.write(child_header + type_data + name)
+PY
+
+"$dtrace" $dt_flags -e -xdebug -xctfpath="$dir/missing.ctfa" \
+ -xbtfpath="$dir" -n "
+BEGIN
+{
+ this->local_size = sizeof(struct ${module}\`dtrace_btf_local_test);
+ this->parent_size = sizeof(struct ${module}\`task_struct);
+}" >"$dir/output" 2>&1
+rc=$?
+if [ "$rc" -ne 0 ]; then
+ cat "$dir/output"
+ exit 1
+fi
+
+if ! grep -q "BTF file $dir/$module: 2 types" "$dir/output" ||
+ ! grep -q "Generated $module CTF from BTF (2 types)" "$dir/output"; then
+ cat "$dir/output"
+ exit 1
+fi
+
+exit 0
diff --git a/test/unittest/btf/tst.module-local.x b/test/unittest/btf/tst.module-local.x
new file mode 100755
index 00000000..0d60fdcc
--- /dev/null
+++ b/test/unittest/btf/tst.module-local.x
@@ -0,0 +1,18 @@
+#!/bin/bash
+
+if ! command -v python3 >/dev/null 2>&1; then
+ echo "Python 3 is needed to generate split BTF data."
+ exit 2
+fi
+
+for path in /sys/kernel/btf/*; do
+ [ "$path" = /sys/kernel/btf/vmlinux ] && continue
+ [ -r "$path" ] || continue
+ module=${path##*/}
+ if [[ $module =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]]; then
+ exit 0
+ fi
+done
+
+echo "No suitable loaded kernel module with BTF data."
+exit 2
diff --git a/test/unittest/btf/tst.module-parent.sh b/test/unittest/btf/tst.module-parent.sh
new file mode 100755
index 00000000..5a521362
--- /dev/null
+++ b/test/unittest/btf/tst.module-parent.sh
@@ -0,0 +1,46 @@
+#!/bin/bash
+#
+# ASSERTION: A kernel module without its own BTF inherits vmlinux types.
+#
+
+dtrace=$1
+dir=$tmpdir/btf-module-parent.$$
+mkdir -p "$dir" || exit 1
+ln -s /sys/kernel/btf/vmlinux "$dir/vmlinux" || exit 1
+
+module=
+for path in /sys/kernel/btf/*; do
+ [ "$path" = /sys/kernel/btf/vmlinux ] && continue
+ [ -r "$path" ] || continue
+ name=${path##*/}
+ [[ $name =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]] || continue
+ module=$name
+ break
+done
+
+if [ -z "$module" ]; then
+ echo "No loaded kernel module with BTF data."
+ exit 1
+fi
+
+# Deliberately leave $dir/$module absent. This exercises the inherited
+# parent-only BTF container, including its CTF import for scoped lookup.
+"$dtrace" $dt_flags -e -xdebug -xctfpath="$dir/missing.ctfa" \
+ -xbtfpath="$dir" -n "
+BEGIN
+{
+ this->size = sizeof(struct ${module}\`task_struct);
+}" >"$dir/output" 2>&1
+rc=$?
+if [ "$rc" -ne 0 ]; then
+ cat "$dir/output"
+ exit 1
+fi
+
+if ! grep -q "Cannot open BTF file $dir/$module:" "$dir/output" ||
+ ! grep -q "Generated $module CTF from BTF (0 types)" "$dir/output"; then
+ cat "$dir/output"
+ exit 1
+fi
+
+exit 0
diff --git a/test/unittest/btf/tst.module-parent.x b/test/unittest/btf/tst.module-parent.x
new file mode 100755
index 00000000..d27b0499
--- /dev/null
+++ b/test/unittest/btf/tst.module-parent.x
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+for path in /sys/kernel/btf/*; do
+ [ "$path" = /sys/kernel/btf/vmlinux ] && continue
+ [ -r "$path" ] || continue
+ module=${path##*/}
+ if [[ $module =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]]; then
+ exit 0
+ fi
+done
+
+echo "No suitable loaded kernel module with BTF data."
+exit 2
diff --git a/test/unittest/btf/tst.raw-vmlinux.sh b/test/unittest/btf/tst.raw-vmlinux.sh
new file mode 100755
index 00000000..12280ba6
--- /dev/null
+++ b/test/unittest/btf/tst.raw-vmlinux.sh
@@ -0,0 +1,30 @@
+#!/bin/bash
+#
+# ASSERTION: Raw vmlinux BTF is loaded and converted to usable CTF when the
+# CTF archive is unavailable, even if the host has an archive.
+#
+
+dtrace=$1
+dir=$tmpdir/btf-raw-vmlinux.$$
+mkdir -p "$dir" || exit 1
+ln -s /sys/kernel/btf/vmlinux "$dir/vmlinux" || exit 1
+
+"$dtrace" $dt_flags -e -xdebug -xctfpath="$dir/missing.ctfa" \
+ -xbtfpath="$dir" -n '
+BEGIN
+{
+ this->size = sizeof(struct vmlinux`task_struct);
+}' >"$dir/output" 2>&1
+rc=$?
+if [ "$rc" -ne 0 ]; then
+ cat "$dir/output"
+ exit 1
+fi
+
+if ! grep -q "BTF file $dir/vmlinux: [1-9][0-9]* types" "$dir/output" ||
+ ! grep -q 'Generated vmlinux CTF from BTF' "$dir/output"; then
+ cat "$dir/output"
+ exit 1
+fi
+
+exit 0
diff --git a/test/unittest/options/tst.ctf-only.sh b/test/unittest/options/tst.ctf-only.sh
new file mode 100755
index 00000000..8ca8a0ac
--- /dev/null
+++ b/test/unittest/options/tst.ctf-only.sh
@@ -0,0 +1,27 @@
+#!/bin/bash
+#
+# ASSERTION: Kernel types still load from the CTF archive when BTF is disabled.
+#
+
+dtrace=$1
+dir=$tmpdir/ctf-only.$$
+mkdir -p "$dir" || exit 1
+
+"$dtrace" $dt_flags -e -xdebug -xbtfpath=none -n '
+BEGIN
+{
+ this->size = sizeof(struct vmlinux`task_struct);
+}' >"$dir/output" 2>&1
+rc=$?
+if [ "$rc" -ne 0 ]; then
+ cat "$dir/output"
+ exit 1
+fi
+
+if ! grep -q 'Loaded shared CTF from archive' "$dir/output" ||
+ grep -q 'Generated vmlinux CTF from BTF' "$dir/output"; then
+ cat "$dir/output"
+ exit 1
+fi
+
+exit 0
diff --git a/test/unittest/options/tst.ctf-only.x b/test/unittest/options/tst.ctf-only.x
new file mode 100755
index 00000000..372dc5c0
--- /dev/null
+++ b/test/unittest/options/tst.ctf-only.x
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+if [ ! -r /lib/modules/$(uname -r)/kernel/vmlinux.ctfa ]; then
+ echo "No vmlinux CTF archive found."
+ exit 2
+fi
+
+exit 0
--
2.52.0
^ permalink raw reply related [flat|nested] only message in thread
only message in thread, other threads:[~2026-10-30 19:38 UTC | newest]
Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-30 19:36 [PATCH 04/20] btf: deprecate dt_shared_btf and introduce btf->parent instead Kris Van Hees
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox