/* * btf_loader.c * * Copyright (C) 2018 Arnaldo Carvalho de Melo * * Based on ctf_loader.c that, in turn, was based on ctfdump.c: CTF dumper. * * Copyright (C) 2008 David S. Miller */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libbtf.h" #include "lib/bpf/include/uapi/linux/btf.h" #include "dutil.h" #include "dwarves.h" /* * FIXME: We should just get the table from the BTF ELF section * and use it directly */ extern struct strings *strings; static void *tag__alloc(const size_t size) { struct tag *tag = zalloc(size); if (tag != NULL) tag->top_level = 1; return tag; } static int btf_elf__load_ftype(struct btf_elf *btfe, struct ftype *proto, uint32_t tag, uint32_t type, uint16_t vlen, struct btf_param *args, uint32_t id) { int i; proto->tag.tag = tag; proto->tag.type = type; INIT_LIST_HEAD(&proto->parms); for (i = 0; i < vlen; ++i) { struct btf_param param = { .name_off = btf_elf__get32(btfe, &args[i].name_off), .type = btf_elf__get32(btfe, &args[i].type), }; if (param.type == 0) proto->unspec_parms = 1; else { struct parameter *p = tag__alloc(sizeof(*p)); if (p == NULL) goto out_free_parameters; p->tag.tag = DW_TAG_formal_parameter; p->tag.type = param.type; p->name = param.name_off; ftype__add_parameter(proto, p); } } vlen *= sizeof(*args); cu__add_tag_with_id(btfe->priv, &proto->tag, id); return vlen; out_free_parameters: ftype__delete(proto, btfe->priv); return -ENOMEM; } static struct base_type *base_type__new(strings_t name, uint32_t attrs, uint8_t float_type, size_t size) { struct base_type *bt = tag__alloc(sizeof(*bt)); if (bt != NULL) { bt->name = name; bt->bit_size = size; bt->is_signed = attrs & BTF_INT_SIGNED; bt->is_bool = attrs & BTF_INT_BOOL; bt->name_has_encoding = false; bt->float_type = float_type; } return bt; } static void type__init(struct type *type, uint32_t tag, strings_t name, size_t size) { INIT_LIST_HEAD(&type->node); INIT_LIST_HEAD(&type->namespace.tags); type->size = size; type->namespace.tag.tag = tag; type->namespace.name = name; type->namespace.sname = 0; } static struct type *type__new(uint16_t tag, strings_t name, size_t size) { struct type *type = tag__alloc(sizeof(*type)); if (type != NULL) type__init(type, tag, name, size); return type; } static struct class *class__new(strings_t name, size_t size) { struct class *class = tag__alloc(sizeof(*class)); if (class != NULL) { type__init(&class->type, DW_TAG_structure_type, name, size); INIT_LIST_HEAD(&class->vtable); } return class; } static int create_new_base_type(struct btf_elf *btfe, void *ptr, struct btf_type *tp, uint32_t id) { uint32_t *enc = ptr; uint32_t eval = btf_elf__get32(btfe, enc); uint32_t attrs = BTF_INT_ENCODING(eval); strings_t name = btf_elf__get32(btfe, &tp->name_off); struct base_type *base = base_type__new(name, attrs, 0, BTF_INT_BITS(eval)); if (base == NULL) return -ENOMEM; base->tag.tag = DW_TAG_base_type; cu__add_tag_with_id(btfe->priv, &base->tag, id); return sizeof(*enc); } static int create_new_array(struct btf_elf *btfe, void *ptr, uint32_t id) { struct btf_array *ap = ptr; struct array_type *array = tag__alloc(sizeof(*array)); if (array == NULL) return -ENOMEM; /* FIXME: where to get the number of dimensions? * it it flattened? */ array->dimensions = 1; array->nr_entries = malloc(sizeof(uint32_t)); if (array->nr_entries == NULL) { free(array); return -ENOMEM; } array->nr_entries[0] = btf_elf__get32(btfe, &ap->nelems); array->tag.tag = DW_TAG_array_type; array->tag.type = btf_elf__get32(btfe, &ap->type); cu__add_tag_with_id(btfe->priv, &array->tag, id); return sizeof(*ap); } static int create_members(struct btf_elf *btfe, void *ptr, int vlen, struct type *class, bool kflag) { struct btf_member *mp = ptr; int i; for (i = 0; i < vlen; i++) { struct class_member *member = zalloc(sizeof(*member)); uint32_t offset; if (member == NULL) return -ENOMEM; member->tag.tag = DW_TAG_member; member->tag.type = btf_elf__get32(btfe, &mp[i].type); member->name = btf_elf__get32(btfe, &mp[i].name_off); offset = btf_elf__get32(btfe, &mp[i].offset); if (kflag) { member->bit_offset = BTF_MEMBER_BIT_OFFSET(offset); member->bitfield_size = BTF_MEMBER_BITFIELD_SIZE(offset); } else { member->bit_offset = offset; member->bitfield_size = 0; } member->byte_offset = member->bit_offset / 8; /* sizes and offsets will be corrected at class__fixup_btf_bitfields */ type__add_member(class, member); } return sizeof(*mp); } static int create_new_class(struct btf_elf *btfe, void *ptr, int vlen, struct btf_type *tp, uint64_t size, uint32_t id, bool kflag) { strings_t name = btf_elf__get32(btfe, &tp->name_off); struct class *class = class__new(name, size); int member_size = create_members(btfe, ptr, vlen, &class->type, kflag); if (member_size < 0) goto out_free; cu__add_tag_with_id(btfe->priv, &class->type.namespace.tag, id); return (vlen * member_size); out_free: class__delete(class, btfe->priv); return -ENOMEM; } static int create_new_union(struct btf_elf *btfe, void *ptr, int vlen, struct btf_type *tp, uint64_t size, uint32_t id, bool kflag) { strings_t name = btf_elf__get32(btfe, &tp->name_off); struct type *un = type__new(DW_TAG_union_type, name, size); int member_size = create_members(btfe, ptr, vlen, un, kflag); if (member_size < 0) goto out_free; cu__add_tag_with_id(btfe->priv, &un->namespace.tag, id); return (vlen * member_size); out_free: type__delete(un, btfe->priv); return -ENOMEM; } static struct enumerator *enumerator__new(strings_t name, uint32_t value) { struct enumerator *en = tag__alloc(sizeof(*en)); if (en != NULL) { en->name = name; en->value = value; en->tag.tag = DW_TAG_enumerator; } return en; } static int create_new_enumeration(struct btf_elf *btfe, void *ptr, int vlen, struct btf_type *tp, uint16_t size, uint32_t id) { struct btf_enum *ep = ptr; uint16_t i; struct type *enumeration = type__new(DW_TAG_enumeration_type, btf_elf__get32(btfe, &tp->name_off), size ? size * 8 : (sizeof(int) * 8)); if (enumeration == NULL) return -ENOMEM; for (i = 0; i < vlen; i++) { strings_t name = btf_elf__get32(btfe, &ep[i].name_off); uint32_t value = btf_elf__get32(btfe, &ep[i].val); struct enumerator *enumerator = enumerator__new(name, value); if (enumerator == NULL) goto out_free; enumeration__add(enumeration, enumerator); } cu__add_tag_with_id(btfe->priv, &enumeration->namespace.tag, id); return (vlen * sizeof(*ep)); out_free: enumeration__delete(enumeration, btfe->priv); return -ENOMEM; } static int create_new_subroutine_type(struct btf_elf *btfe, void *ptr, int vlen, struct btf_type *tp, uint32_t id) { struct btf_param *args = ptr; unsigned int type = btf_elf__get32(btfe, &tp->type); struct ftype *proto = tag__alloc(sizeof(*proto)); if (proto == NULL) return -ENOMEM; vlen = btf_elf__load_ftype(btfe, proto, DW_TAG_subroutine_type, type, vlen, args, id); return vlen < 0 ? -ENOMEM : vlen; } static int create_new_forward_decl(struct btf_elf *btfe, struct btf_type *tp, uint64_t size, uint32_t id) { strings_t name = btf_elf__get32(btfe, &tp->name_off); struct class *fwd = class__new(name, size); if (fwd == NULL) return -ENOMEM; fwd->type.declaration = 1; cu__add_tag_with_id(btfe->priv, &fwd->type.namespace.tag, id); return 0; } static int create_new_typedef(struct btf_elf *btfe, struct btf_type *tp, uint64_t size, uint32_t id) { strings_t name = btf_elf__get32(btfe, &tp->name_off); unsigned int type_id = btf_elf__get32(btfe, &tp->type); struct type *type = type__new(DW_TAG_typedef, name, size); if (type == NULL) return -ENOMEM; type->namespace.tag.type = type_id; cu__add_tag_with_id(btfe->priv, &type->namespace.tag, id); return 0; } static int create_new_tag(struct btf_elf *btfe, int type, struct btf_type *tp, uint32_t id) { unsigned int type_id = btf_elf__get32(btfe, &tp->type); struct tag *tag = zalloc(sizeof(*tag)); if (tag == NULL) return -ENOMEM; switch (type) { case BTF_KIND_CONST: tag->tag = DW_TAG_const_type; break; case BTF_KIND_PTR: tag->tag = DW_TAG_pointer_type; break; case BTF_KIND_RESTRICT: tag->tag = DW_TAG_restrict_type; break; case BTF_KIND_VOLATILE: tag->tag = DW_TAG_volatile_type; break; default: printf("%s: FOO %d\n\n", __func__, type); return 0; } tag->type = type_id; cu__add_tag_with_id(btfe->priv, tag, id); return 0; } void *btf_elf__get_buffer(struct btf_elf *btfe) { return btfe->data; } size_t btf_elf__get_size(struct btf_elf *btfe) { return btfe->size; } static int btf_elf__load_types(struct btf_elf *btfe) { void *btf_buffer = btf_elf__get_buffer(btfe); struct btf_header *hp = btf_buffer; void *btf_contents = btf_buffer + sizeof(*hp), *type_section = (btf_contents + btf_elf__get32(btfe, &hp->type_off)), *strings_section = (btf_contents + btf_elf__get32(btfe, &hp->str_off)); struct btf_type *type_ptr = type_section, *end = strings_section; uint32_t type_index = 0x0001; while (type_ptr < end) { uint32_t val = btf_elf__get32(btfe, &type_ptr->info); uint32_t type = BTF_INFO_KIND(val); int vlen = BTF_INFO_VLEN(val); void *ptr = type_ptr; uint32_t size = btf_elf__get32(btfe, &type_ptr->size); bool kflag = BTF_INFO_KFLAG(val); ptr += sizeof(struct btf_type); if (type == BTF_KIND_INT) { vlen = create_new_base_type(btfe, ptr, type_ptr, type_index); } else if (type == BTF_KIND_ARRAY) { vlen = create_new_array(btfe, ptr, type_index); } else if (type == BTF_KIND_STRUCT) { vlen = create_new_class(btfe, ptr, vlen, type_ptr, size, type_index, kflag); } else if (type == BTF_KIND_UNION) { vlen = create_new_union(btfe, ptr, vlen, type_ptr, size, type_index, kflag); } else if (type == BTF_KIND_ENUM) { vlen = create_new_enumeration(btfe, ptr, vlen, type_ptr, size, type_index); } else if (type == BTF_KIND_FWD) { vlen = create_new_forward_decl(btfe, type_ptr, size, type_index); } else if (type == BTF_KIND_TYPEDEF) { vlen = create_new_typedef(btfe, type_ptr, size, type_index); } else if (type == BTF_KIND_VOLATILE || type == BTF_KIND_PTR || type == BTF_KIND_CONST || type == BTF_KIND_RESTRICT) { vlen = create_new_tag(btfe, type, type_ptr, type_index); } else if (type == BTF_KIND_UNKN) { cu__table_nullify_type_entry(btfe->priv, type_index); fprintf(stderr, "BTF: idx: %d, off: %zd, Unknown\n", type_index, ((void *)type_ptr) - type_section); fflush(stderr); vlen = 0; } else if (type == BTF_KIND_FUNC_PROTO) { vlen = create_new_subroutine_type(btfe, ptr, vlen, type_ptr, type_index); } else if (type == BTF_KIND_FUNC) { /* BTF_KIND_FUNC corresponding to a defined subprogram. * This is not really a type and it won't be referred by any other types * either. Since types cannot be skipped, let us replace it with * a nullify_type_entry. * * No warning here since BTF_KIND_FUNC is a legal entry in BTF. */ cu__table_nullify_type_entry(btfe->priv, type_index); vlen = 0; } else { fprintf(stderr, "BTF: idx: %d, off: %zd, Unknown\n", type_index, ((void *)type_ptr) - type_section); fflush(stderr); vlen = 0; } if (vlen < 0) return vlen; type_ptr = ptr + vlen; type_index++; } return 0; } static int btf_elf__load_sections(struct btf_elf *btfe) { return btf_elf__load_types(btfe); } static int class__fixup_btf_bitfields(struct tag *tag, struct cu *cu, struct btf_elf *btfe) { struct class_member *pos; struct type *tag_type = tag__type(tag); type__for_each_data_member(tag_type, pos) { struct tag *type = tag__strip_typedefs_and_modifiers(&pos->tag, cu); if (type == NULL) /* FIXME: C++ BTF... */ continue; pos->bitfield_offset = 0; pos->byte_size = tag__size(type, cu); pos->bit_size = pos->byte_size * 8; /* bitfield fixup is needed for enums and base types only */ if (type->tag != DW_TAG_base_type && type->tag != DW_TAG_enumeration_type) continue; /* if BTF data is incorrect and has size == 0, skip field, * instead of crashing */ if (pos->byte_size == 0) { continue; } if (pos->bitfield_size) { /* bitfields seem to be always aligned, no matter the packing */ pos->byte_offset = pos->bit_offset / pos->bit_size * pos->bit_size / 8; pos->bitfield_offset = pos->bit_offset - pos->byte_offset * 8; /* re-adjust bitfield offset if it is negative */ if (pos->bitfield_offset < 0) { pos->bitfield_offset += pos->bit_size; pos->byte_offset -= pos->byte_size; pos->bit_offset = pos->byte_offset * 8 + pos->bitfield_offset; } } else { pos->byte_offset = pos->bit_offset / 8; } } return 0; } static int cu__fixup_btf_bitfields(struct cu *cu, struct btf_elf *btfe) { int err = 0; struct tag *pos; list_for_each_entry(pos, &cu->tags, node) if (tag__is_struct(pos) || tag__is_union(pos)) { err = class__fixup_btf_bitfields(pos, cu, btfe); if (err) break; } return err; } static void btf_elf__cu_delete(struct cu *cu) { btf_elf__delete(cu->priv); cu->priv = NULL; } static const char *btf_elf__strings_ptr(const struct cu *cu, strings_t s) { return btf_elf__string(cu->priv, s); } struct debug_fmt_ops btf_elf__ops; int btf_elf__load_file(struct cus *cus, struct conf_load *conf, const char *filename) { int err; struct btf_elf *btfe = btf_elf__new(filename, NULL); if (btfe == NULL) return -1; struct cu *cu = cu__new(filename, btfe->wordsize, NULL, 0, filename); if (cu == NULL) return -1; cu->language = LANG_C; cu->uses_global_strings = false; cu->little_endian = !btfe->is_big_endian; cu->dfops = &btf_elf__ops; cu->priv = btfe; btfe->priv = cu; if (btf_elf__load(btfe) != 0) return -1; err = btf_elf__load_sections(btfe); if (err != 0) { cu__delete(cu); return err; } err = cu__fixup_btf_bitfields(cu, btfe); /* * The app stole this cu, possibly deleting it, * so forget about it */ if (conf && conf->steal && conf->steal(cu, conf)) return 0; cus__add(cus, cu); return err; } struct debug_fmt_ops btf_elf__ops = { .name = "btf", .load_file = btf_elf__load_file, .strings__ptr = btf_elf__strings_ptr, .cu__delete = btf_elf__cu_delete, };