[DTrace-devel] [PATCH v3] ctf: add BTF-to-CTF convertor

Alan Maguire alan.maguire at oracle.com
Fri Nov 10 18:17:03 UTC 2023


On 08/11/2023 17:45, Kris Van Hees via DTrace-devel wrote:
> For kernels that do not provide CTF data, kernel type information can be
> obtained at runtime as BTF data.  We pull in that data and generate
> equivalent CTF data from it.
> 
> Signed-off-by: Kris Van Hees <kris.van.hees at oracle.com>
> Reviewed-by: Nick Alcock <nick.alcock at oracle.com>

just tested this on an upstream bpf-next kernel, works beautifully:

# /usr/sbin/dtrace -n 'fbt::ip_rcv_core:entry { printf("%s\n",
stringof(((struct sk_buff *)arg0)->dev)); }'
dtrace: description 'fbt::ip_rcv_core:entry ' matched 1 probe
CPU     ID                    FUNCTION:NAME
  0  80242                ip_rcv_core:entry wlo1

  3  80242                ip_rcv_core:entry cscotun0


Great stuff!

> ---
>  libdtrace/Build       |   1 +
>  libdtrace/dt_btf.c    | 811 ++++++++++++++++++++++++++++++++++++++++++
>  libdtrace/dt_btf.h    |  27 ++
>  libdtrace/dt_impl.h   |   4 +-
>  libdtrace/dt_module.c |  57 ++-
>  5 files changed, 890 insertions(+), 10 deletions(-)
>  create mode 100644 libdtrace/dt_btf.c
>  create mode 100644 libdtrace/dt_btf.h
> 
> diff --git a/libdtrace/Build b/libdtrace/Build
> index 35c0fd4a..76fd32e4 100644
> --- a/libdtrace/Build
> +++ b/libdtrace/Build
> @@ -13,6 +13,7 @@ libdtrace-build_DIR := $(current-dir)
>  libdtrace-build_SOURCES = dt_aggregate.c \
>  			  dt_as.c \
>  			  dt_bpf.c \
> +			  dt_btf.c \
>  			  dt_buf.c \
>  			  dt_cc.c \
>  			  dt_cg.c \
> diff --git a/libdtrace/dt_btf.c b/libdtrace/dt_btf.c
> new file mode 100644
> index 00000000..2cb26aa5
> --- /dev/null
> +++ b/libdtrace/dt_btf.c
> @@ -0,0 +1,811 @@
> +/*
> + * Oracle Linux DTrace.
> + * Copyright (c) 2023, 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.
> + */
> +
> +#include <assert.h>
> +#include <byteswap.h>
> +#include <ctf.h>
> +#include <errno.h>
> +#include <stdio.h>
> +#include <sys/mman.h>
> +#include <linux/btf.h>
> +
> +#include <dt_impl.h>
> +#include <dt_btf.h>
> +
> +typedef struct btf_header	btf_header_t;
> +typedef struct btf_type		btf_type_t;
> +typedef struct btf_enum		btf_enum_t;
> +typedef struct btf_array	btf_array_t;
> +typedef struct btf_member	btf_member_t;
> +typedef struct btf_param	btf_param_t;
> +typedef struct btf_var		btf_var_t;
> +typedef struct btf_var_secinfo	btf_var_secinfo_t;
> +typedef struct btf_decl_tag	btf_decl_tag_t;
> +typedef struct btf_enum64	btf_enum64_t;
> +
> +struct dt_btf {
> +	void		*data;			/* raw BTF data */
> +	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 */
> +	btf_type_t	**types;		/* type table */
> +	ctf_id_t	*ctfids;		/* matching CTF type ids */
> +};
> +
> +static const char *
> +dt_btf_errmsg(int err)
> +{
> +	return strerror(err);
> +}
> +
> +static int
> +dt_btf_set_errno(dtrace_hdl_t *dtp, int err)
> +{
> +	dtp->dt_btferr = err;
> +	return -1;
> +}
> +
> +static void *
> +dt_btf_set_load_errno(dtrace_hdl_t *dtp, int err)
> +{
> +	dtp->dt_btferr = err;
> +	return NULL;
> +}
> +
> +static void
> +dt_btf_error(dtrace_hdl_t *dtp, int eid, const char *format, ...)
> +{
> +	va_list	ap;
> +
> +	va_start(ap, format);
> +	dt_set_errmsg(dtp, dt_errtag(eid), NULL, NULL, 0, format, ap);
> +	dt_set_errno(dtp, EDT_BPF);
> +	va_end(ap);
> +}
> +
> +static ctf_id_t
> +dt_ctf_set_errno(dtrace_hdl_t *dtp, int err)
> +{
> +	dtp->dt_ctferr = err;
> +	dt_set_errno(dtp, EDT_CTF);
> +	return CTF_ERR;
> +}
> +
> +static ctf_id_t
> +dt_ctf_error(dtrace_hdl_t *dtp, ctf_dict_t *ctf)
> +{
> +	return dt_ctf_set_errno(dtp, ctf_errno(ctf));
> +}
> +
> +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;
> +
> +	/* Validate magic number. */
> +	if (hdr->magic != BTF_MAGIC) {
> +		dt_dprintf("BTF magic mismatch (%04x, expected %04x)\n",
> +			   hdr->magic, BTF_MAGIC);
> +		return dt_btf_set_errno(dtp, EINVAL);
> +	}
> +
> +	/* Validate header length. */
> +	if (hdr->hdr_len < sizeof(btf_header_t) ||
> +	    hdr->hdr_len >= btf->size) {
> +		dt_dprintf("BTF bad header length (%d)\n", hdr->hdr_len);
> +		return dt_btf_set_errno(dtp, EINVAL);
> +	}
> +
> +	/* Validate data offsets and lengths. */
> +	if (hdr->type_off % 4) {
> +		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) {
> +		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) {
> +		dt_dprintf("BTF bad string section length (%d)\n",
> +			   hdr->str_len);
> +		return dt_btf_set_errno(dtp, EINVAL);
> +	}
> +
> +	return 0;
> +}
> +
> +static size_t
> +dt_btf_type_size(const char *ptr)
> +{
> +	btf_type_t	*type = (btf_type_t *)ptr;
> +	size_t		size = sizeof(btf_type_t);
> +	int		vlen = BTF_INFO_VLEN(type->info);
> +
> +	switch (BTF_INFO_KIND(type->info)) {
> +	case BTF_KIND_INT:
> +		return size + sizeof(uint32_t);
> +	case BTF_KIND_ARRAY:
> +		return size + sizeof(btf_array_t);
> +	case BTF_KIND_STRUCT:
> +	case BTF_KIND_UNION:
> +		return size + vlen * sizeof(btf_member_t);
> +	case BTF_KIND_ENUM:
> +		return size + vlen * sizeof(btf_enum_t);
> +	case BTF_KIND_FUNC_PROTO:
> +		return size + vlen * sizeof(btf_param_t);
> +	case BTF_KIND_VAR:
> +		return size + sizeof(btf_var_t);
> +	case BTF_KIND_DATASEC:
> +		return size + vlen * sizeof(btf_var_secinfo_t);
> +	case BTF_KIND_DECL_TAG:
> +		return size + sizeof(btf_decl_tag_t);
> +	case BTF_KIND_ENUM64:
> +		return size + vlen * sizeof(btf_enum64_t);
> +	case BTF_KIND_PTR:
> +	case BTF_KIND_FWD:
> +	case BTF_KIND_TYPEDEF:
> +	case BTF_KIND_VOLATILE:
> +	case BTF_KIND_CONST:
> +	case BTF_KIND_RESTRICT:
> +	case BTF_KIND_FUNC:
> +	case BTF_KIND_FLOAT:
> +	case BTF_KIND_TYPE_TAG:
> +		return size;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	return -EINVAL;
> +}
> +
> +static int
> +dt_btf_decode(dtrace_hdl_t *dtp, dt_btf_t *btf)
> +{
> +	btf_header_t	*hdr;
> +	char		*ptr, *end;
> +	int32_t		idx;
> +	char		*tdata;
> +
> +	if (dt_btf_validate_header(dtp, btf) == -1)
> +		return -1;
> +
> +	hdr = btf->hdr;
> +	btf->sdata = (char *)btf->data + hdr->hdr_len + hdr->str_off;
> +	tdata = (char *)btf->data + hdr->hdr_len + hdr->type_off;
> +
> +	/* First count how many types there really are. */
> +	ptr = tdata;
> +	end = tdata + hdr->type_len;
> +	idx = 1;
> +	while (ptr + sizeof(btf_type_t) <= end) {
> +		int		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);
> +
> +		idx++;
> +		ptr += size;
> +	}
> +
> +	/* Next, populate the type offsets table. */
> +	btf->type_cnt = idx;
> +	btf->types = dt_calloc(dtp, btf->type_cnt, sizeof(btf_type_t *));
> +
> +	ptr = tdata;
> +	idx = 1;
> +	while (ptr + sizeof(btf_type_t) <= end) {
> +		int	size = dt_btf_type_size(ptr);
> +
> +		btf->types[idx++] = (btf_type_t *)ptr;
> +		ptr += size;
> +	}
> +
> +	return 0;
> +}
> +
> +/*
> + * Try to load BTF data from the given file.  We use FILE-based IO primitives
> + * instead of fd-based IO primitives because the BTF data is usually provided
> + * by sysfs pseudo-files and the fd-based IO primitives do not seem to work
> + * with that.
> + */
> +static dt_btf_t *
> +dt_btf_load(dtrace_hdl_t *dtp, const char *fn)
> +{
> +	FILE		*fp;
> +	dt_btf_t	*btf = NULL;
> +	btf_header_t	hdr;
> +	void		*data = NULL;
> +	Elf		*elf;
> +	size_t		shstrs;
> +	Elf_Scn		*sp = NULL;
> +	int		err = 0;
> +
> +	btf = dt_zalloc(dtp, sizeof(dt_btf_t));
> +	if (btf == NULL)
> +		return dt_btf_set_load_errno(dtp, ENOMEM);
> +
> +	fp = fopen(fn, "rb");
> +	if (fp == NULL)
> +		return dt_btf_set_load_errno(dtp,  errno);
> +
> +	/* First see whether this might be a file with raw BTF data. */
> +	if (fread(&hdr, 1, sizeof(hdr), fp) < sizeof(hdr))
> +		goto fail;
> +
> +	rewind(fp);
> +	if (hdr.magic == BTF_MAGIC) {
> +		struct stat	st;
> +
> +		if (fstat(fileno(fp), &st) == -1)
> +			goto fail;
> +
> +		data = dt_alloc(dtp, st.st_size);
> +		if (data == NULL)
> +			goto fail;
> +
> +		if (fread(data, 1, st.st_size, fp) < st.st_size)
> +			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_getshdrstrndx(elf, &shstrs) == -1)
> +			goto elf_fail;
> +
> +		while ((sp = elf_nextscn(elf, sp)) != NULL) {
> +			GElf_Shdr	sh;
> +			Elf_Data	*edata;
> +			const char	*name;
> +
> +			/* Skip malformed sections. */
> +			if (gelf_getshdr(sp, &sh) == NULL ||
> +			    sh.sh_type == SHT_NULL)
> +				continue;
> +
> +			name = elf_strptr(elf, shstrs, sh.sh_name);
> +			if (name == NULL || strcmp(name, ".BTF") != 0)
> +				continue;
> +
> +			/* Found the section we need. */
> +			edata = elf_getdata(sp, 0);
> +			if (edata == NULL)
> +				goto elf_fail;
> +
> +			data = dt_alloc(dtp, edata->d_size);
> +			if (data == NULL)
> +				goto elf_fail;
> +
> +			memcpy(data, edata->d_buf, edata->d_size);
> +
> +			btf->data = data;
> +			btf->size = edata->d_size;
> +
> +			elf_end(elf);
> +			fclose(fp);
> +
> +			break;
> +		}
> +	}
> +
> +	return btf;
> +
> +elf_fail:
> +	elf_end(elf);
> +
> +elf_fail_no_end:
> +	err = elf_errno();
> +	dt_btf_error(dtp, 0, "BTF: %s", elf_errmsg(err));
> +	fclose(fp);
> +
> +	return NULL;
> +
> +fail:
> +	dt_free(dtp, data);
> +	dt_btf_set_errno(dtp,  err ? err : errno);
> +	fclose(fp);
> +
> +	return NULL;
> +}
> +
> +dt_btf_t *
> +dt_btf_load_file(dtrace_hdl_t *dtp, const char *fn)
> +{
> +	dt_btf_t	*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));
> +		return NULL;
> +	}
> +
> +	if (dt_btf_decode(dtp, btf) == -1) {
> +		dt_dprintf("Cannot decode BTF data %s: %s\n", fn,
> +			   dt_btf_errmsg(dtp->dt_btferr));
> +		return NULL;
> +	}
> +
> +	return btf;
> +}
> +
> +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)
> +{
> +	btf_type_t	*type;
> +	int		kind, vlen;
> +	const char	*name;
> +	ctf_id_t	ctfid;
> +	ctf_encoding_t	enc;
> +
> +	/*
> +	 * If we are not constructing shared_ctf, we may be looking for a type
> +	 * in shared_ctf.
> +	 */
> +	if (dtp->dt_btf && btf != dtp->dt_btf) {
> +		if (type_id < dtp->dt_btf->type_cnt)
> +			return dtp->dt_btf->ctfids[type_id];
> +
> +		type_id -= dtp->dt_btf->type_cnt - 1;
> +	}
> +
> +	assert(type_id < btf->type_cnt);
> +
> +	if (btf->ctfids[type_id] != CTF_ERR)
> +		return btf->ctfids[type_id];
> +
> +	assert(type_id > 0);
> +
> +	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);
> +
> +	if (name && name[0]) {
> +		char	n[DT_TYPE_NAMELEN];
> +
> +		/* Do we already have this type? */
> +		switch (kind) {
> +		case BTF_KIND_ENUM:
> +		case BTF_KIND_ENUM64:
> +			if (snprintf(n, sizeof(n), "enum %s",
> +				     name == NULL ? "(anon)" : name) < 0)
> +				return dt_ctf_set_errno(dtp, ECTF_NAMELEN);
> +
> +			ctfid = ctf_lookup_by_name(ctf, n);
> +			break;
> +		case BTF_KIND_UNION:
> +			if (snprintf(n, sizeof(n), "union %s",
> +				     name == NULL ? "(anon)" : name) < 0)
> +				return dt_ctf_set_errno(dtp, ECTF_NAMELEN);
> +	
> +			ctfid = ctf_lookup_by_name(ctf, n);
> +			break;
> +		case BTF_KIND_STRUCT:
> +			if (snprintf(n, sizeof(n), "struct %s",
> +				     name == NULL ? "(anon)" : name) < 0)
> +				return dt_ctf_set_errno(dtp, ECTF_NAMELEN);
> +	
> +			ctfid = ctf_lookup_by_name(ctf, n);
> +			break;
> +		default:
> +			ctfid = ctf_lookup_by_name(ctf, name);
> +		}
> +
> +		if (ctfid != CTF_ERR) {
> +			btf->ctfids[type_id] = ctfid;
> +			return ctfid;
> +		}
> +	}
> +
> +	switch (kind) {
> +	case BTF_KIND_INT: {
> +		uint32_t	data = *(uint32_t *)(type + 1);
> +		uint32_t	benc = BTF_INT_ENCODING(data);
> +
> +		enc.cte_format = 0;
> +		enc.cte_offset = BTF_INT_OFFSET(data);
> +		enc.cte_bits = BTF_INT_BITS(data);
> +
> +		if (benc & BTF_INT_SIGNED)
> +			enc.cte_format |= CTF_INT_SIGNED;
> +		if (benc & BTF_INT_CHAR)
> +			enc.cte_format |= CTF_INT_CHAR;
> +		if (benc & BTF_INT_BOOL)
> +			enc.cte_format |= CTF_INT_BOOL;
> +
> +		/* Some special case handling. */
> +		if (enc.cte_bits == 8) {
> +			if (strcmp(name, "_Bool") == 0)
> +				enc.cte_format = CTF_INT_BOOL;
> +			else
> +				enc.cte_format = CTF_INT_CHAR;
> +		}
> +
> +		ctfid = ctf_add_integer(ctf, CTF_ADD_ROOT, name, &enc);
> +		btf->ctfids[type_id] = ctfid;
> +
> +		return ctfid == CTF_ERR ? dt_ctf_error(dtp, ctf) : ctfid;
> +	}
> +	case BTF_KIND_FLOAT: {
> +		switch (type->size) {
> +		case 4:
> +			enc.cte_format = CTF_FP_SINGLE;
> +			break;
> +		case 8:
> +			enc.cte_format = CTF_FP_DOUBLE;
> +			break;
> +		case 16:
> +			enc.cte_format = CTF_FP_LDOUBLE;
> +			break;
> +		}
> +
> +		enc.cte_offset = 0;
> +		enc.cte_bits = type->size * 8;
> +
> +		ctfid = ctf_add_float(ctf, CTF_ADD_ROOT, name, &enc);
> +		btf->ctfids[type_id] = ctfid;
> +
> +		return ctfid == CTF_ERR ? dt_ctf_error(dtp, ctf) : ctfid;
> +	}
> +	case BTF_KIND_ENUM: {
> +		int		i;
> +		btf_enum_t	*item = (btf_enum_t *)(type + 1);
> +
> +		ctfid = ctf_add_enum(ctf, CTF_ADD_ROOT, name);
> +		if (ctfid == CTF_ERR)
> +			return dt_ctf_error(dtp, ctf);
> +
> +		for (i = 0; i < vlen; i++, item++) {
> +			int	err;
> +			const char	*iname;
> +
> +			iname = dt_btf_get_string(dtp, btf, item->name_off);
> +			err = ctf_add_enumerator(ctf, ctfid, iname, item->val);
> +			if (err == CTF_ERR)
> +				return dt_ctf_error(dtp, ctf);
> +		}
> +
> +		btf->ctfids[type_id] = ctfid;
> +
> +		return ctfid == CTF_ERR ? dt_ctf_error(dtp, ctf) : ctfid;
> +	}
> +	case BTF_KIND_PTR: {
> +		ctfid = dt_btf_add_to_ctf(dtp, btf, ctf, type->type);
> +		if (ctfid == CTF_ERR)
> +			return CTF_ERR;		/* errno already set */
> +
> +		ctfid = ctf_add_pointer(ctf, CTF_ADD_ROOT, ctfid);
> +		btf->ctfids[type_id] = ctfid;
> +
> +		return ctfid == CTF_ERR ? dt_ctf_error(dtp, ctf) : ctfid;
> +	}
> +	case BTF_KIND_CONST: {
> +		ctfid = dt_btf_add_to_ctf(dtp, btf, ctf, type->type);
> +		if (ctfid == CTF_ERR)
> +			return CTF_ERR;		/* errno already set */
> +
> +		ctfid = ctf_add_const(ctf, CTF_ADD_ROOT, ctfid);
> +		btf->ctfids[type_id] = ctfid;
> +
> +		return ctfid == CTF_ERR ? dt_ctf_error(dtp, ctf) : ctfid;
> +	}
> +	case BTF_KIND_VOLATILE: {
> +		ctfid = dt_btf_add_to_ctf(dtp, btf, ctf, type->type);
> +		if (ctfid == CTF_ERR)
> +			return CTF_ERR;		/* errno already set */
> +
> +		ctfid = ctf_add_volatile(ctf, CTF_ADD_ROOT, ctfid);
> +		btf->ctfids[type_id] = ctfid;
> +
> +		return ctfid == CTF_ERR ? dt_ctf_error(dtp, ctf) : ctfid;
> +	}
> +	case BTF_KIND_RESTRICT: {
> +		ctfid = dt_btf_add_to_ctf(dtp, btf, ctf, type->type);
> +		if (ctfid == CTF_ERR)
> +			return CTF_ERR;		/* errno already set */
> +
> +		ctfid = ctf_add_restrict(ctf, CTF_ADD_ROOT, ctfid);
> +		btf->ctfids[type_id] = ctfid;
> +
> +		return ctfid == CTF_ERR ? dt_ctf_error(dtp, ctf) : ctfid;
> +	}
> +	case BTF_KIND_ARRAY: {
> +		btf_array_t	*arr = (btf_array_t *)(type + 1);
> +		ctf_arinfo_t	arp;
> +
> +		arp.ctr_contents = dt_btf_add_to_ctf(dtp, btf, ctf, arr->type);
> +		if (arp.ctr_contents == CTF_ERR)
> +			return CTF_ERR;
> +		arp.ctr_index = dt_btf_add_to_ctf(dtp, btf, ctf, arr->index_type);
> +		if (arp.ctr_contents == CTF_ERR)
> +			return CTF_ERR;
> +		arp.ctr_nelems = arr->nelems;
> +
> +		ctfid = ctf_add_array(ctf, CTF_ADD_ROOT, &arp);
> +		btf->ctfids[type_id] = ctfid;
> +
> +		return ctfid == CTF_ERR ? dt_ctf_error(dtp, ctf) : ctfid;
> +	}
> +	case BTF_KIND_TYPEDEF: {
> +		ctfid = dt_btf_add_to_ctf(dtp, btf, ctf, type->type);
> +		if (ctfid == CTF_ERR)
> +			return CTF_ERR;		/* errno already set */
> +
> +		ctfid = ctf_add_typedef(ctf, CTF_ADD_ROOT, name, ctfid);
> +		btf->ctfids[type_id] = ctfid;
> +
> +		return ctfid == CTF_ERR ? dt_ctf_error(dtp, ctf) : ctfid;
> +	}
> +	case BTF_KIND_STRUCT:
> +	case BTF_KIND_UNION: {
> +		int		i;
> +		btf_member_t	*item = (btf_member_t *)(type + 1);
> +
> +		if (kind == BTF_KIND_STRUCT)
> +			ctfid = ctf_add_struct_sized(ctf, CTF_ADD_ROOT, name,
> +						     type->size);
> +		else
> +			ctfid = ctf_add_union_sized(ctf, CTF_ADD_ROOT, name,
> +						    type->size);
> +
> +		btf->ctfids[type_id] = ctfid;
> +		if (ctfid == CTF_ERR)
> +			return dt_ctf_error(dtp, ctf);
> +
> +		for (i = 0; i < vlen; i++, item++) {
> +			int		err;
> +			ctf_id_t	mbid;
> +			const char	*mname;
> +
> +			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);
> +
> +			if (BTF_INFO_KFLAG(type->info)) {
> +				enc.cte_format = 0;
> +				enc.cte_offset = 0;
> +				enc.cte_bits = BTF_MEMBER_BITFIELD_SIZE(item->offset);
> +				item->offset = BTF_MEMBER_BIT_OFFSET(item->offset);
> +
> +				if (enc.cte_bits > 0) {
> +					int	kind;
> +
> +					/* Resolve member type for CTF. */
> +					mbid = ctf_type_resolve(ctf, mbid);
> +					kind = ctf_type_kind(ctf, mbid);
> +
> +					if (kind != CTF_K_INTEGER &&
> +					    kind != CTF_K_FLOAT &&
> +					    kind != CTF_K_ENUM)
> +						return dt_ctf_set_errno(
> +							dtp, ECTF_NOTINTFP);
> +
> +					mbid = ctf_add_slice(ctf,
> +							     CTF_ADD_NONROOT,
> +							     mbid, &enc);
> +					if (mbid == CTF_ERR)
> +						return dt_ctf_error(dtp, ctf);
> +
> +					/* Fall-through */
> +				}
> +
> +				/* Fall-through - add as regular member. */
> +			}
> +
> +			err = ctf_add_member_offset(ctf, ctfid, mname, mbid,
> +						    item->offset);
> +			if (err == CTF_ERR)
> +				return dt_ctf_error(dtp, ctf);
> +		}
> +
> +		return ctfid;
> +	}
> +	case BTF_KIND_FUNC_PROTO: {
> +		int		i;
> +		ctf_id_t	argv[vlen];
> +		btf_param_t	*item = (btf_param_t *)(type + 1);
> +		ctf_funcinfo_t	ctc;
> +
> +		ctc.ctc_flags = 0;
> +		ctc.ctc_argc = vlen;
> +		ctc.ctc_return = dt_btf_add_to_ctf(dtp, btf, ctf, type->type);
> +		if (ctc.ctc_return == CTF_ERR)
> +			return CTF_ERR;
> +
> +		for (i = 0; i < vlen; i++) {
> +			argv[i] = dt_btf_add_to_ctf(dtp, btf, ctf, item->type);
> +			if (argv[i] == CTF_ERR)
> +				return CTF_ERR;
> +		}
> +
> +		ctfid = ctf_add_function(ctf, CTF_ADD_ROOT, &ctc, argv);
> +		btf->ctfids[type_id] = ctfid;
> +
> +		return ctfid == CTF_ERR ? dt_ctf_error(dtp, ctf) : ctfid;
> +	}
> +	case BTF_KIND_FWD: {
> +		uint32_t	kind;
> +
> +		kind = BTF_INFO_KFLAG(type->info) ? CTF_K_UNION : CTF_K_STRUCT;
> +		ctfid = ctf_add_forward(ctf, CTF_ADD_ROOT, name, kind);
> +		btf->ctfids[type_id] = ctfid;
> +
> +		return ctfid == CTF_ERR ? dt_ctf_error(dtp, ctf) : ctfid;
> +	}
> +	case BTF_KIND_VAR:
> +	case BTF_KIND_DATASEC:
> +	case BTF_KIND_DECL_TAG:
> +	case BTF_KIND_TYPE_TAG:
> +	case BTF_KIND_FUNC:
> +	case BTF_KIND_ENUM64:
> +		return btf->ctfids[0];		/* Ignored for CTF */
> +	default:
> +		return dt_ctf_error(dtp, ctf);
> +	}
> +
> +	assert(0);
> +}
> +
> +ctf_dict_t *
> +dt_btf_to_ctf(dtrace_hdl_t *dtp, dt_module_t *dmp, dt_btf_t *btf)
> +{
> +	int		i, base = 0;
> +	ctf_dict_t	*ctf;
> +	ctf_encoding_t	enc = { CTF_INT_SIGNED, 0, 0 };
> +
> +	ctf = ctf_create(&dtp->dt_ctferr);
> +	if (ctf == NULL)
> +		return NULL;
> +
> +	ctf_setmodel(ctf, dtp->dt_conf.dtc_ctfmodel);
> +
> +	/*
> +	 * All modules import 'shared_ctf'.  The 'vmlinux' module is handled as
> +	 * a special case because it simply imports 'shared_ctf'.
> +	 */
> +	if (dmp) {
> +		int	err;
> +
> +		err = ctf_import(ctf, dtp->dt_shared_ctf);
> +		if (err == CTF_ERR)
> +			return NULL;
> +
> +		ctf_setspecific(ctf, dmp);
> +
> +		if (strcmp(dmp->dm_name, "vmlinux") == 0)
> +			return ctf;
> +	}
> +
> +	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;
> +
> +	/*
> +	 * If we are constructing 'shared_ctf' (no module), we need to add an
> +	 * entry for the implicit 'void' BTF type (id 0).
> +	 */
> +	if (!dmp) {
> +		btf->ctfids[0] = ctf_add_integer(ctf, CTF_ADD_ROOT, "void",
> +						 &enc);
> +		if (btf->ctfids[0] == CTF_ERR)
> +			dt_dprintf("Could not create 'void' CTF entry.\n");
> +	}
> +
> +	/*
> +	 * 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).
> +	 */
> +	if (dtp->dt_btf)
> +		base = dtp->dt_btf->type_cnt - 1;
> +
> +	for (i = 1; i < btf->type_cnt; i++) {
> +		int	type_id = i + base;
> +
> +		if (dt_btf_add_to_ctf(dtp, btf, ctf, type_id) == CTF_ERR) {
> +			dt_dprintf("BTF-to-CTF error: %s\n",
> +				   ctf_errmsg(dtp->dt_ctferr));
> +			break;
> +		}
> +	}
> +
> +	if (ctf_update(ctf) == CTF_ERR)
> +		return NULL;
> +
> +	return ctf;
> +}
> +
> +ctf_dict_t *
> +dt_btf_load_module(dtrace_hdl_t *dtp, dt_module_t *dmp)
> +{
> +	char		fn[PATH_MAX + 1];
> +	dt_btf_t	*btf;
> +
> +	snprintf(fn, sizeof(fn), "/sys/kernel/btf/%s", dmp->dm_name);
> +	btf = dt_btf_load_file(dtp, fn);
> +	if (btf == NULL)
> +		return NULL;
> +
> +	if (strcmp(dmp->dm_name, "vmlinux") == 0) {
> +		dtp->dt_shared_ctf = dt_btf_to_ctf(dtp, NULL, btf);
> +		dtp->dt_btf = btf;
> +
> +		dt_dprintf("Generated shared CTF from BTF (%d types).\n",
> +			   btf->type_cnt);
> +
> +		return dtp->dt_shared_ctf;
> +	}
> +
> +	dt_dprintf("Generated %s CTF from BTF (%d types).\n", dmp->dm_name,
> +		   btf->type_cnt);
> +
> +	return dt_btf_to_ctf(dtp, dmp, btf);
> +}
> +
> +const char *
> +dt_btf_get_string(dtrace_hdl_t *dtp, dt_btf_t *btf, uint32_t off)
> +{
> +	if (dtp->dt_btf == NULL)
> +		goto ok;
> +
> +	/* Check if the offset is within the base BTF string area. */
> +	if (btf == dtp->dt_btf || off < dtp->dt_btf->hdr->str_len)
> +		return dtp->dt_btf->sdata + off;
> +
> +	off -= dtp->dt_btf->hdr->str_len;
> +ok:
> +	if (off < btf->hdr->str_len)
> +		return btf->sdata + off;
> +
> +	return dt_btf_set_load_errno(dtp, EINVAL);
> +}
> +
> +int32_t
> +dt_btf_lookup_name_kind(dtrace_hdl_t *dtp, dt_btf_t *btf, const char *name,
> +			uint32_t kind)
> +{
> +	int32_t	i;
> +
> +	if (kind == BTF_KIND_UNKN)
> +		return 0;
> +	if (strcmp(name, "void") == 0)
> +		return 0;
> +
> +	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 (strcmp(name, str) == 0)
> +			return i;
> +	}
> +
> +	return -ENOENT;
> +}
> diff --git a/libdtrace/dt_btf.h b/libdtrace/dt_btf.h
> new file mode 100644
> index 00000000..3db4203a
> --- /dev/null
> +++ b/libdtrace/dt_btf.h
> @@ -0,0 +1,27 @@
> +/*
> + * Oracle Linux DTrace.
> + * Copyright (c) 2023, 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.
> + */
> +
> +#ifndef	_DT_BTF_H
> +#define	_DT_BTF_H
> +
> +#include <ctf.h>
> +#include <dt_impl.h>
> +
> +#ifdef	__cplusplus
> +extern "C" {
> +#endif
> +
> +typedef struct dt_btf	dt_btf_t;
> +
> +extern ctf_dict_t *dt_btf_load_module(dtrace_hdl_t *, dt_module_t *);
> +extern const char *dt_btf_get_string(dtrace_hdl_t *, dt_btf_t *, uint32_t);
> +
> +#ifdef	__cplusplus
> +}
> +#endif
> +
> +#endif	/* _DT_BTF_H */
> diff --git a/libdtrace/dt_impl.h b/libdtrace/dt_impl.h
> index f6025817..48b34ec9 100644
> --- a/libdtrace/dt_impl.h
> +++ b/libdtrace/dt_impl.h
> @@ -328,9 +328,10 @@ struct dtrace_hdl {
>  	dt_list_t dt_modlist;	/* linked list of dt_module_t's */
>  	dt_htab_t *dt_mods;	/* hash table of dt_module_t's */
>  	dt_htab_t *dt_kernsyms; /* htab of kernel symbol names */
> +	char *dt_ctfa_path;	/* path to vmlinux.ctfa */
>  	ctf_archive_t *dt_ctfa; /* ctf archive for the entire kernel tree */
> +	struct dt_btf *dt_btf;	/* BTF data for the kernel */
>  	ctf_file_t *dt_shared_ctf; /* Handle to the shared CTF */
> -	char *dt_ctfa_path;	/* path to vmlinux.ctfa */
>  	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 */
> @@ -417,6 +418,7 @@ struct dtrace_hdl {
>  	uint64_t dt_options[DTRACEOPT_MAX]; /* dtrace run-time options */
>  	uint32_t dt_bpfhelper[__BPF_FUNC_MAX_ID]; /* BPF helper mapping */
>  	int dt_version;		/* library version requested by client */
> +	int dt_btferr;		/* error resulting from last BTF failure */
>  	int dt_ctferr;		/* error resulting from last CTF failure */
>  	int dt_errno;		/* error resulting from last failed operation */
>  	int dt_cdefs_fd;	/* file descriptor for C CTF debugging cache */
> diff --git a/libdtrace/dt_module.c b/libdtrace/dt_module.c
> index 28fa176c..bb8c030d 100644
> --- a/libdtrace/dt_module.c
> +++ b/libdtrace/dt_module.c
> @@ -26,6 +26,7 @@
>  #include <dt_kernel_module.h>
>  #include <dt_module.h>
>  #include <dt_impl.h>
> +#include <dt_btf.h>
>  #include <dt_string.h>
>  
>  #define KSYM_NAME_MAX 128		    /* from kernel/scripts/kallsyms.c */
> @@ -806,6 +807,7 @@ sym_type_to_info(char info)
>  
>  	return GELF_ST_INFO(binding, type);
>  }
> +
>  /*
>   * Do all necessary post-creation initialization of a module of type
>   * DT_DM_KERNEL.
> @@ -825,6 +827,28 @@ dt_kern_module_init(dtrace_hdl_t *dtp, dt_module_t *dmp)
>  	return 0;
>  }
>  
> +static void
> +dt_kern_module_ctf_from_btf(dtrace_hdl_t *dtp, dt_module_t *dmp)
> +{
> +	/*
> +	 * The first module for which we need to collect data must be the
> +	 * 'vmlinux' module.  It will be used to initialize the shared_ctf
> +	 * data.
> +	 */
> +	if (dtp->dt_btf == NULL)
> +		assert(strcmp(dmp->dm_name, "vmlinux") == 0);
> +
> +	dt_dprintf("Generating CTF for module %s from BTF.\n", dmp->dm_name);
> +
> +	dmp->dm_ctfp = dt_btf_load_module(dtp, dmp);
> +	if (dmp->dm_ctfp == NULL)
> +		dt_dprintf("Cannot generate CTF for module %s from BTF: %s; "
> +			   "looking for out-of-tree module.\n",
> +			   dmp->dm_name, ctf_errmsg(dtp->dt_ctferr));
> +	else
> +		dmp->dm_flags |= DT_DM_CTF_ARCHIVED;
> +}
> +
>  /*
>   * Determine the location of a kernel module's CTF data.
>   *
> @@ -843,6 +867,16 @@ dt_kern_module_find_ctf(dtrace_hdl_t *dtp, dt_module_t *dmp)
>  		dt_kern_module_init(dtp, dmp);
>  	}
>  
> +	/*
> +	 * If we already know that our CTF data is derived from BTF data (i.e.
> +	 * there is no CTF archive for the runtime kernel), there is no point
> +	 * in trying to load CTF data.  Go directly to BTF-based processing.
> +	 */
> +	if (dtp->dt_btf) {
> +		dt_kern_module_ctf_from_btf(dtp, dmp);
> +		return;
> +	}
> +
>  	/*
>  	 * Kernel modules' CTF can be in one of two places: the CTF archive or
>  	 * linked into the module (for out-of-tree modules).  The corresponding
> @@ -868,17 +902,10 @@ dt_kern_module_find_ctf(dtrace_hdl_t *dtp, dt_module_t *dmp)
>  			    strlen("/kernel/vmlinux.ctfa") + 1);
>  			to = stpcpy(ctfa_name, dtp->dt_module_path);
>  			stpcpy(to, "/kernel/vmlinux.ctfa");
> -		} else {
> +		} else
>  			ctfa_name = dtp->dt_ctfa_path;
> -		}
> -
> -		if ((dtp->dt_ctfa = ctf_arc_open(ctfa_name,
> -			    &dtp->dt_ctferr)) == NULL) {
> -			dt_dprintf("Cannot open CTF archive %s: %s; looking "
> -			    "for in-module CTF instead.\n", ctfa_name,
> -			    ctf_errmsg(dtp->dt_ctferr));
> -		}
>  
> +		dtp->dt_ctfa = ctf_arc_open(ctfa_name, &dtp->dt_ctferr);
>  		if (dtp->dt_ctfa != NULL) {
>  			/*
>  			 * Load in the shared CTF immediately.
> @@ -895,6 +922,18 @@ dt_kern_module_find_ctf(dtrace_hdl_t *dtp, dt_module_t *dmp)
>  				dt_dprintf("Loaded shared CTF from archive %s.\n",
>  				    ctfa_name);
>  			}
> +		} else {
> +			dt_dprintf("Cannot open CTF archive %s: %s; "
> +				   "trying BTF.\n",
> +				   ctfa_name, ctf_errmsg(dtp->dt_ctferr));
> +
> +			/*
> +			 * Try to load the vmlinux BTF data to generate the CTF
> +			 * data for shared_ctf from.
> +			 */
> +			dt_kern_module_ctf_from_btf(dtp,
> +				dt_module_lookup_by_name(dtp, "vmlinux"));
> +			return;
>  		}
>  
>  		if (dtp->dt_ctfa_path == NULL)



More information about the DTrace-devel mailing list