From: Kris Van Hees <kris.van.hees@oracle.com>
To: dtrace@lists.linux.dev, dtrace-devel@oss.oracle.com
Subject: [PATCH 04/20] btf: deprecate dt_shared_btf and introduce btf->parent instead
Date: Fri, 30 Oct 2026 19:36:33 +0000 [thread overview]
Message-ID: <031b190cb7679c63673baf89f0389b9a@oracle.com> (raw)
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
reply other threads:[~2026-10-30 19:38 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=031b190cb7679c63673baf89f0389b9a@oracle.com \
--to=kris.van.hees@oracle.com \
--cc=dtrace-devel@oss.oracle.com \
--cc=dtrace@lists.linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.