| /* |
| Copyright (C) 2006 Mandriva Conectiva S.A. |
| Copyright (C) 2006 Arnaldo Carvalho de Melo <acme@mandriva.com> |
| Copyright (C) 2007 Red Hat Inc. |
| Copyright (C) 2007 Arnaldo Carvalho de Melo <acme@redhat.com> |
| |
| This program is free software; you can redistribute it and/or modify it |
| under the terms of version 2 of the GNU General Public License as |
| published by the Free Software Foundation. |
| */ |
| |
| #include <assert.h> |
| #include <dirent.h> |
| #include <dwarf.h> |
| #include <argp.h> |
| #include <elfutils/libdwfl.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <fnmatch.h> |
| #include <libelf.h> |
| #include <search.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| |
| #include "list.h" |
| #include "dwarves.h" |
| #include "dutil.h" |
| |
| static const char *dwarf_tag_names[] = { |
| [DW_TAG_array_type] = "array_type", |
| [DW_TAG_class_type] = "class_type", |
| [DW_TAG_entry_point] = "entry_point", |
| [DW_TAG_enumeration_type] = "enumeration_type", |
| [DW_TAG_formal_parameter] = "formal_parameter", |
| [DW_TAG_imported_declaration] = "imported_declaration", |
| [DW_TAG_label] = "label", |
| [DW_TAG_lexical_block] = "lexical_block", |
| [DW_TAG_member] = "member", |
| [DW_TAG_pointer_type] = "pointer_type", |
| [DW_TAG_reference_type] = "reference_type", |
| [DW_TAG_compile_unit] = "compile_unit", |
| [DW_TAG_string_type] = "string_type", |
| [DW_TAG_structure_type] = "structure_type", |
| [DW_TAG_subroutine_type] = "subroutine_type", |
| [DW_TAG_typedef] = "typedef", |
| [DW_TAG_union_type] = "union_type", |
| [DW_TAG_unspecified_parameters] = "unspecified_parameters", |
| [DW_TAG_variant] = "variant", |
| [DW_TAG_common_block] = "common_block", |
| [DW_TAG_common_inclusion] = "common_inclusion", |
| [DW_TAG_inheritance] = "inheritance", |
| [DW_TAG_inlined_subroutine] = "inlined_subroutine", |
| [DW_TAG_module] = "module", |
| [DW_TAG_ptr_to_member_type] = "ptr_to_member_type", |
| [DW_TAG_set_type] = "set_type", |
| [DW_TAG_subrange_type] = "subrange_type", |
| [DW_TAG_with_stmt] = "with_stmt", |
| [DW_TAG_access_declaration] = "access_declaration", |
| [DW_TAG_base_type] = "base_type", |
| [DW_TAG_catch_block] = "catch_block", |
| [DW_TAG_const_type] = "const_type", |
| [DW_TAG_constant] = "constant", |
| [DW_TAG_enumerator] = "enumerator", |
| [DW_TAG_file_type] = "file_type", |
| [DW_TAG_friend] = "friend", |
| [DW_TAG_namelist] = "namelist", |
| [DW_TAG_namelist_item] = "namelist_item", |
| [DW_TAG_packed_type] = "packed_type", |
| [DW_TAG_subprogram] = "subprogram", |
| [DW_TAG_template_type_parameter] = "template_type_parameter", |
| [DW_TAG_template_value_parameter] = "template_value_parameter", |
| [DW_TAG_thrown_type] = "thrown_type", |
| [DW_TAG_try_block] = "try_block", |
| [DW_TAG_variant_part] = "variant_part", |
| [DW_TAG_variable] = "variable", |
| [DW_TAG_volatile_type] = "volatile_type", |
| [DW_TAG_dwarf_procedure] = "dwarf_procedure", |
| [DW_TAG_restrict_type] = "restrict_type", |
| [DW_TAG_interface_type] = "interface_type", |
| [DW_TAG_namespace] = "namespace", |
| [DW_TAG_imported_module] = "imported_module", |
| [DW_TAG_unspecified_type] = "unspecified_type", |
| [DW_TAG_partial_unit] = "partial_unit", |
| [DW_TAG_imported_unit] = "imported_unit", |
| [DW_TAG_mutable_type] = "mutable_type", |
| [DW_TAG_condition] = "condition", |
| [DW_TAG_shared_type] = "shared_type", |
| }; |
| |
| const char *dwarf_tag_name(const uint32_t tag) |
| { |
| if (tag >= DW_TAG_array_type && tag <= DW_TAG_shared_type) |
| return dwarf_tag_names[tag]; |
| return "INVALID"; |
| } |
| |
| static const struct conf_fprintf conf_fprintf__defaults = { |
| .name_spacing = 23, |
| .type_spacing = 26, |
| .emit_stats = 1, |
| }; |
| |
| static const char tabs[] = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t"; |
| |
| static size_t cacheline_size; |
| |
| static void *zalloc(const size_t size) |
| { |
| void *s = malloc(size); |
| if (s != NULL) |
| memset(s, 0, size); |
| return s; |
| } |
| |
| void *memdup(const void *src, size_t len) |
| { |
| void *s = malloc(len); |
| if (s != NULL) |
| memcpy(s, src, len); |
| return s; |
| } |
| |
| static void *strings; |
| |
| static int strings__compare(const void *a, const void *b) |
| { |
| return strcmp(a, b); |
| } |
| |
| static char *strings__add(const char *str) |
| { |
| char **s; |
| |
| if (str == NULL) |
| return NULL; |
| |
| s = tsearch(str, &strings, strings__compare); |
| if (s != NULL) { |
| if (*s == str) { |
| char *dup = strdup(str); |
| if (dup != NULL) |
| *s = dup; |
| else { |
| tdelete(str, &strings, strings__compare); |
| return NULL; |
| } |
| } |
| } else |
| return NULL; |
| |
| return *s; |
| } |
| |
| /* Number decoding macros. See 7.6 Variable Length Data. */ |
| |
| #define get_uleb128_step(var, addr, nth, break) \ |
| __b = *(addr)++; \ |
| var |= (uintmax_t) (__b & 0x7f) << (nth * 7); \ |
| if ((__b & 0x80) == 0) \ |
| break |
| |
| #define get_uleb128_rest_return(var, i, addrp) \ |
| do { \ |
| for (; i < 10; ++i) { \ |
| get_uleb128_step(var, *addrp, i, \ |
| return var); \ |
| } \ |
| /* Other implementations set VALUE to UINT_MAX in this \ |
| case. So we better do this as well. */ \ |
| return UINT64_MAX; \ |
| } while (0) |
| |
| static uint64_t __libdw_get_uleb128(uint64_t acc, uint32_t i, |
| const uint8_t **addrp) |
| { |
| uint8_t __b; |
| get_uleb128_rest_return (acc, i, addrp); |
| } |
| |
| #define get_uleb128(var, addr) \ |
| do { \ |
| uint8_t __b; \ |
| var = 0; \ |
| get_uleb128_step(var, addr, 0, break); \ |
| var = __libdw_get_uleb128 (var, 1, &(addr)); \ |
| } while (0) |
| |
| static uint64_t attr_numeric(Dwarf_Die *die, uint32_t name) |
| { |
| Dwarf_Attribute attr; |
| uint32_t form; |
| |
| if (dwarf_attr(die, name, &attr) == NULL) |
| return 0; |
| |
| form = dwarf_whatform(&attr); |
| |
| switch (form) { |
| case DW_FORM_addr: { |
| Dwarf_Addr addr; |
| if (dwarf_formaddr(&attr, &addr) == 0) |
| return addr; |
| } |
| break; |
| case DW_FORM_data1: |
| case DW_FORM_data2: |
| case DW_FORM_data4: |
| case DW_FORM_data8: |
| case DW_FORM_sdata: |
| case DW_FORM_udata: { |
| Dwarf_Word value; |
| if (dwarf_formudata(&attr, &value) == 0) |
| return value; |
| } |
| break; |
| case DW_FORM_flag: |
| return 1; |
| default: |
| fprintf(stderr, "DW_AT_<0x%x>=0x%x\n", name, form); |
| break; |
| } |
| |
| return 0; |
| } |
| |
| static uint64_t dwarf_expr(const uint8_t *expr, uint32_t len __unused) |
| { |
| /* Common case: offset from start of the class */ |
| if (expr[0] == DW_OP_plus_uconst || |
| expr[0] == DW_OP_constu) { |
| uint64_t result; |
| ++expr; |
| get_uleb128(result, expr); |
| return result; |
| } |
| |
| fprintf(stderr, "%s: unhandled %#x DW_OP_ operation\n", |
| __func__, *expr); |
| return UINT64_MAX; |
| } |
| |
| static Dwarf_Off attr_offset(Dwarf_Die *die, const uint32_t name) |
| { |
| Dwarf_Attribute attr; |
| Dwarf_Block block; |
| |
| if (dwarf_attr(die, name, &attr) != NULL && |
| dwarf_formblock(&attr, &block) == 0) |
| return dwarf_expr(block.data, block.length); |
| |
| return 0; |
| } |
| |
| static const char *attr_string(Dwarf_Die *die, uint32_t name) |
| { |
| Dwarf_Attribute attr; |
| if (dwarf_attr(die, name, &attr) != NULL) |
| return dwarf_formstring(&attr); |
| return NULL; |
| } |
| |
| static Dwarf_Off attr_type(Dwarf_Die *die, uint32_t attr_name) |
| { |
| Dwarf_Attribute attr; |
| if (dwarf_attr(die, attr_name, &attr) != NULL) { |
| Dwarf_Die type_die; |
| if (dwarf_formref_die(&attr, &type_die) != NULL) |
| return dwarf_dieoffset(&type_die); |
| |
| } |
| return 0; |
| } |
| |
| static int attr_location(Dwarf_Die *die, Dwarf_Op **expr, size_t *exprlen) |
| { |
| Dwarf_Attribute attr; |
| if (dwarf_attr(die, DW_AT_location, &attr) != NULL) { |
| if (dwarf_getlocation(&attr, expr, exprlen) == 0) |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| static void tag__init(struct tag *self, Dwarf_Die *die) |
| { |
| int32_t decl_line; |
| |
| self->tag = dwarf_tag(die); |
| self->id = dwarf_dieoffset(die); |
| |
| if (self->tag == DW_TAG_imported_module || |
| self->tag == DW_TAG_imported_declaration) |
| self->type = attr_type(die, DW_AT_import); |
| else |
| self->type = attr_type(die, DW_AT_type); |
| |
| self->decl_file = strings__add(dwarf_decl_file(die)); |
| dwarf_decl_line(die, &decl_line); |
| self->decl_line = decl_line; |
| self->recursivity_level = 0; |
| } |
| |
| static struct tag *tag__new(Dwarf_Die *die) |
| { |
| struct tag *self = malloc(sizeof(*self)); |
| |
| if (self != NULL) |
| tag__init(self, die); |
| |
| return self; |
| } |
| |
| static void tag__delete(struct tag *self) |
| { |
| assert(list_empty(&self->node)); |
| free(self); |
| } |
| |
| struct tag *tag__follow_typedef(struct tag *tag, const struct cu *cu) |
| { |
| struct tag *type = cu__find_tag_by_id(cu, tag->type); |
| |
| if (type->tag == DW_TAG_typedef) |
| return tag__follow_typedef(type, cu); |
| |
| return type; |
| } |
| |
| static const char *tag__accessibility(const struct tag *self) |
| { |
| int a; |
| |
| switch (self->tag) { |
| case DW_TAG_inheritance: |
| case DW_TAG_member: |
| a = tag__class_member(self)->accessibility; |
| break; |
| case DW_TAG_subprogram: |
| a = tag__function(self)->accessibility; |
| break; |
| default: |
| return NULL; |
| } |
| |
| switch (a) { |
| case DW_ACCESS_public: return "public"; |
| case DW_ACCESS_private: return "private"; |
| case DW_ACCESS_protected: return "protected"; |
| } |
| |
| return NULL; |
| } |
| |
| size_t tag__nr_cachelines(const struct tag *self, const struct cu *cu) |
| { |
| return (tag__size(self, cu) + cacheline_size - 1) / cacheline_size; |
| } |
| |
| static void __tag__id_not_found(const struct tag *self, |
| unsigned long long id, const char *fn) |
| { |
| fprintf(stderr, "%s: %#llx id not found for %s (id=%#llx)\n", |
| fn, (unsigned long long)id, dwarf_tag_name(self->tag), |
| (unsigned long long)self->id); |
| fflush(stderr); |
| } |
| |
| #define tag__id_not_found(self, id) __tag__id_not_found(self, id, __func__) |
| |
| #define tag__type_not_found(self) __tag__id_not_found(self, (self)->type, __func__) |
| |
| size_t tag__fprintf_decl_info(const struct tag *self, FILE *fp) |
| { |
| return fprintf(fp, "/* <%llx> %s:%u */\n", |
| (unsigned long long)self->id, |
| self->decl_file, self->decl_line); |
| } |
| |
| static struct base_type *base_type__new(Dwarf_Die *die) |
| { |
| struct base_type *self = zalloc(sizeof(*self)); |
| |
| if (self != NULL) { |
| tag__init(&self->tag, die); |
| self->name = strings__add(attr_string(die, DW_AT_name)); |
| self->size = attr_numeric(die, DW_AT_byte_size); |
| } |
| |
| return self; |
| } |
| |
| static struct array_type *array_type__new(Dwarf_Die *die) |
| { |
| struct array_type *self = zalloc(sizeof(*self)); |
| |
| if (self != NULL) |
| tag__init(&self->tag, die); |
| |
| return self; |
| } |
| |
| static size_t type__fprintf(struct tag *type, const struct cu *cu, |
| const char *name, const struct conf_fprintf *conf, |
| FILE *fp); |
| |
| static size_t array_type__fprintf(const struct tag *tag_self, |
| const struct cu *cu, const char *name, |
| const struct conf_fprintf *conf, |
| FILE *fp) |
| { |
| struct array_type *self = tag__array_type(tag_self); |
| struct tag *type = cu__find_tag_by_id(cu, tag_self->type); |
| size_t printed = type__fprintf(type, cu, name, conf, fp); |
| int i; |
| |
| for (i = 0; i < self->dimensions; ++i) |
| printed += fprintf(fp, "[%u]", self->nr_entries[i]); |
| return printed; |
| } |
| |
| static void namespace__init(struct namespace *self, Dwarf_Die *die) |
| { |
| tag__init(&self->tag, die); |
| INIT_LIST_HEAD(&self->tags); |
| self->name = strings__add(attr_string(die, DW_AT_name)); |
| self->nr_tags = 0; |
| } |
| |
| static struct namespace *namespace__new(Dwarf_Die *die) |
| { |
| struct namespace *self = malloc(sizeof(*self)); |
| |
| if (self != NULL) |
| namespace__init(self, die); |
| |
| return self; |
| } |
| |
| static void namespace__delete(struct namespace *self) |
| { |
| struct tag *pos, *n; |
| |
| namespace__for_each_tag_safe(self, pos, n) { |
| list_del_init(&pos->node); |
| |
| /* Look for nested namespaces */ |
| if (tag__is_struct(pos) || |
| tag__is_union(pos) || |
| tag__is_namespace(pos) || |
| tag__is_enumeration(pos)) |
| namespace__delete(tag__namespace(pos)); |
| tag__delete(pos); |
| } |
| |
| tag__delete(&self->tag); |
| } |
| |
| static void type__init(struct type *self, Dwarf_Die *die) |
| { |
| namespace__init(&self->namespace, die); |
| INIT_LIST_HEAD(&self->node); |
| self->size = attr_numeric(die, DW_AT_byte_size); |
| self->declaration = attr_numeric(die, DW_AT_declaration); |
| self->specification = attr_type(die, DW_AT_specification); |
| self->definition_emitted = 0; |
| self->fwd_decl_emitted = 0; |
| self->resized = 0; |
| self->nr_members = 0; |
| } |
| |
| static struct type *type__new(Dwarf_Die *die) |
| { |
| struct type *self = malloc(sizeof(*self)); |
| |
| if (self != NULL) |
| type__init(self, die); |
| |
| return self; |
| } |
| |
| const char *type__name(struct type *self, const struct cu *cu) |
| { |
| /* Check if the tag doesn't comes with a DW_AT_name attribute... */ |
| if (self->namespace.name == NULL && |
| /* No? So it can have a DW_TAG_specification... */ |
| self->specification != 0 && |
| cu != NULL) { |
| struct tag *tag = cu__find_tag_by_id(cu, self->specification); |
| if (tag == NULL) { |
| tag__id_not_found(&self->namespace.tag, self->specification); |
| return NULL; |
| } |
| /* ... and now we cache the result in this tag ->name field */ |
| self->namespace.name = tag__type(tag)->namespace.name; |
| } |
| |
| return self->namespace.name; |
| } |
| |
| struct class_member * |
| type__find_first_biggest_size_base_type_member(struct type *self, |
| const struct cu *cu) |
| { |
| struct class_member *pos, *result = NULL; |
| size_t result_size = 0; |
| |
| type__for_each_data_member(self, pos) { |
| struct tag *type = cu__find_tag_by_id(cu, pos->tag.type); |
| size_t member_size = 0, power2; |
| struct class_member *inner = NULL; |
| reevaluate: |
| switch (type->tag) { |
| case DW_TAG_base_type: |
| member_size = tag__base_type(type)->size; |
| break; |
| case DW_TAG_pointer_type: |
| case DW_TAG_reference_type: |
| member_size = cu->addr_size; |
| break; |
| case DW_TAG_union_type: |
| case DW_TAG_structure_type: |
| if (tag__type(type)->nr_members == 0) |
| continue; |
| inner = type__find_first_biggest_size_base_type_member(tag__type(type), cu); |
| member_size = class_member__size(inner, cu); |
| break; |
| case DW_TAG_array_type: |
| case DW_TAG_const_type: |
| case DW_TAG_typedef: |
| case DW_TAG_volatile_type: |
| type = cu__find_tag_by_id(cu, type->type); |
| goto reevaluate; |
| case DW_TAG_enumeration_type: |
| member_size = tag__type(type)->size; |
| break; |
| } |
| |
| /* long long */ |
| if (member_size > cu->addr_size) |
| return pos; |
| |
| for (power2 = cu->addr_size; power2 > result_size; power2 /= 2) |
| if (member_size >= power2) { |
| if (power2 == cu->addr_size) |
| return inner ?: pos; |
| result_size = power2; |
| result = inner ?: pos; |
| } |
| } |
| |
| return result; |
| } |
| |
| size_t typedef__fprintf(const struct tag *tag_self, const struct cu *cu, |
| const struct conf_fprintf *conf, FILE *fp) |
| { |
| struct type *self = tag__type(tag_self); |
| const struct conf_fprintf *pconf = conf ?: &conf_fprintf__defaults; |
| const struct tag *type; |
| const struct tag *ptr_type; |
| char bf[512]; |
| int is_pointer = 0; |
| size_t printed; |
| |
| /* |
| * Check for void (humm, perhaps we should have a fake void tag instance |
| * to avoid all these checks? |
| */ |
| if (tag_self->type == 0) |
| return fprintf(fp, "typedef void %s", type__name(self, cu)); |
| |
| type = cu__find_tag_by_id(cu, tag_self->type); |
| if (type == NULL) { |
| tag__type_not_found(tag_self); |
| return 0; |
| } |
| |
| switch (type->tag) { |
| case DW_TAG_array_type: |
| printed = fprintf(fp, "typedef "); |
| return printed + array_type__fprintf(type, cu, |
| type__name(self, cu), |
| pconf, fp); |
| case DW_TAG_pointer_type: |
| if (type->type == 0) /* void pointer */ |
| break; |
| ptr_type = cu__find_tag_by_id(cu, type->type); |
| if (ptr_type == NULL) { |
| tag__type_not_found(type); |
| return 0; |
| } |
| if (ptr_type->tag != DW_TAG_subroutine_type) |
| break; |
| type = ptr_type; |
| is_pointer = 1; |
| /* Fall thru */ |
| case DW_TAG_subroutine_type: |
| printed = fprintf(fp, "typedef "); |
| return printed + ftype__fprintf(tag__ftype(type), cu, |
| type__name(self, cu), |
| 0, is_pointer, 0, |
| fp); |
| case DW_TAG_structure_type: { |
| struct type *ctype = tag__type(type); |
| |
| if (type__name(ctype, cu) != NULL) |
| return fprintf(fp, "typedef struct %s %s", |
| type__name(ctype, cu), |
| type__name(self, cu)); |
| } |
| } |
| |
| return fprintf(fp, "typedef %s %s", |
| tag__name(type, cu, bf, sizeof(bf)), |
| type__name(self, cu)); |
| } |
| |
| static size_t imported_declaration__fprintf(const struct tag *self, |
| const struct cu *cu, FILE *fp) |
| { |
| char bf[512]; |
| const struct tag *decl = cu__find_tag_by_id(cu, self->type); |
| |
| return fprintf(fp, "using ::%s", tag__name(decl, cu, bf, sizeof(bf))); |
| } |
| |
| static size_t imported_module__fprintf(const struct tag *self, |
| const struct cu *cu, FILE *fp) |
| { |
| const struct tag *module = cu__find_tag_by_id(cu, self->type); |
| const char *name = "<IMPORTED MODULE ERROR!>"; |
| |
| if (tag__is_namespace(module)) |
| name = tag__namespace(module)->name; |
| |
| return fprintf(fp, "using namespace %s", name); |
| } |
| |
| size_t enumeration__fprintf(const struct tag *tag_self, const struct cu *cu, |
| const struct conf_fprintf *conf, FILE *fp) |
| { |
| struct type *self = tag__type(tag_self); |
| struct enumerator *pos; |
| size_t printed = fprintf(fp, "enum%s%s {\n", |
| type__name(self, cu) ? " " : "", |
| type__name(self, cu) ?: ""); |
| int indent = conf->indent; |
| |
| if (indent >= (int)sizeof(tabs)) |
| indent = sizeof(tabs) - 1; |
| |
| type__for_each_enumerator(self, pos) |
| printed += fprintf(fp, "%.*s\t%s = %u,\n", indent, tabs, |
| pos->name, pos->value); |
| |
| return printed + fprintf(fp, "%.*s}%s%s", indent, tabs, |
| conf->suffix ? " " : "", conf->suffix ?: ""); |
| } |
| |
| static struct enumerator *enumerator__new(Dwarf_Die *die) |
| { |
| struct enumerator *self = zalloc(sizeof(*self)); |
| |
| if (self != NULL) { |
| tag__init(&self->tag, die); |
| self->name = strings__add(attr_string(die, DW_AT_name)); |
| self->value = attr_numeric(die, DW_AT_const_value); |
| } |
| |
| return self; |
| } |
| |
| static enum vlocation dwarf__location(Dwarf_Die *die) |
| { |
| Dwarf_Op *expr; |
| size_t exprlen; |
| enum vlocation location = LOCATION_UNKNOWN; |
| |
| if (attr_location(die, &expr, &exprlen) != 0) |
| location = LOCATION_OPTIMIZED; |
| else if (exprlen != 0) |
| switch (expr->atom) { |
| case DW_OP_addr: |
| location = LOCATION_GLOBAL; break; |
| case DW_OP_reg1 ... DW_OP_reg31: |
| case DW_OP_breg0 ... DW_OP_breg31: |
| location = LOCATION_REGISTER; break; |
| case DW_OP_fbreg: |
| location = LOCATION_LOCAL; break; |
| } |
| |
| return location; |
| } |
| |
| static struct variable *variable__new(Dwarf_Die *die) |
| { |
| struct variable *self = malloc(sizeof(*self)); |
| |
| if (self != NULL) { |
| tag__init(&self->tag, die); |
| self->name = strings__add(attr_string(die, DW_AT_name)); |
| self->abstract_origin = attr_type(die, DW_AT_abstract_origin); |
| /* variable is visible outside of its enclosing cu */ |
| self->external = dwarf_hasattr(die, DW_AT_external); |
| /* non-defining declaration of an object */ |
| self->declaration = dwarf_hasattr(die, DW_AT_declaration); |
| self->location = LOCATION_UNKNOWN; |
| if (!self->declaration) |
| self->location = dwarf__location(die); |
| } |
| |
| return self; |
| } |
| |
| static void cus__add(struct cus *self, struct cu *cu) |
| { |
| list_add_tail(&cu->node, &self->cus); |
| } |
| |
| static struct cu *cu__new(const char *name, uint8_t addr_size, |
| const unsigned char *build_id, |
| int build_id_len) |
| { |
| struct cu *self = malloc(sizeof(*self) + build_id_len); |
| |
| if (self != NULL) { |
| INIT_LIST_HEAD(&self->tags); |
| INIT_LIST_HEAD(&self->tool_list); |
| |
| self->name = strings__add(name); |
| self->addr_size = addr_size; |
| |
| self->nr_inline_expansions = 0; |
| self->size_inline_expansions = 0; |
| self->nr_structures_changed = 0; |
| self->nr_functions_changed = 0; |
| self->max_len_changed_item = 0; |
| self->function_bytes_added = 0; |
| self->function_bytes_removed = 0; |
| self->build_id_len = build_id_len; |
| if (build_id_len > 0) |
| memcpy(self->build_id, build_id, build_id_len); |
| } |
| |
| return self; |
| } |
| |
| void cu__delete(struct cu *self) |
| { |
| struct tag *pos, *n; |
| |
| list_for_each_entry_safe(pos, n, &self->tags, node) { |
| list_del_init(&pos->node); |
| |
| /* Look for nested namespaces */ |
| if (tag__is_struct(pos) || |
| tag__is_union(pos) || |
| tag__is_namespace(pos) || |
| tag__is_enumeration(pos)) { |
| namespace__delete(tag__namespace(pos)); |
| } |
| } |
| free(self); |
| } |
| |
| static void cu__add_tag(struct cu *self, struct tag *tag) |
| { |
| list_add_tail(&tag->node, &self->tags); |
| } |
| |
| bool cu__same_build_id(const struct cu *self, const struct cu *other) |
| { |
| return self->build_id_len != 0 && |
| self->build_id_len == other->build_id_len && |
| memcmp(self->build_id, other->build_id, self->build_id_len) == 0; |
| } |
| |
| static const char *tag__prefix(const struct cu *cu, const uint32_t tag) |
| { |
| switch (tag) { |
| case DW_TAG_enumeration_type: return "enum "; |
| case DW_TAG_structure_type: |
| return cu->language == DW_LANG_C_plus_plus ? "class " : |
| "struct "; |
| case DW_TAG_union_type: return "union "; |
| case DW_TAG_pointer_type: return " *"; |
| case DW_TAG_reference_type: return " &"; |
| } |
| |
| return ""; |
| } |
| |
| static struct tag *namespace__find_tag_by_id(const struct namespace *self, |
| const Dwarf_Off id) |
| { |
| struct tag *pos; |
| |
| if (id == 0) |
| return NULL; |
| |
| namespace__for_each_tag(self, pos) { |
| if (pos->id == id) |
| return pos; |
| |
| /* Look for nested namespaces */ |
| if (tag__is_struct(pos) || tag__is_union(pos) || |
| tag__is_namespace(pos)) { |
| struct tag *tag = |
| namespace__find_tag_by_id(tag__namespace(pos), id); |
| if (tag != NULL) |
| return tag; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| struct tag *cu__find_tag_by_id(const struct cu *self, const Dwarf_Off id) |
| { |
| struct tag *pos; |
| |
| if (self == NULL || id == 0) |
| return NULL; |
| |
| list_for_each_entry(pos, &self->tags, node) { |
| if (pos->id == id) |
| return pos; |
| |
| /* Look for nested namespaces */ |
| if (tag__is_struct(pos) || tag__is_union(pos) || |
| tag__is_namespace(pos)) { |
| struct tag *tag = |
| namespace__find_tag_by_id(tag__namespace(pos), id); |
| if (tag != NULL) |
| return tag; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| struct tag *cu__find_first_typedef_of_type(const struct cu *self, |
| const Dwarf_Off type) |
| { |
| struct tag *pos; |
| |
| if (self == NULL || type == 0) |
| return NULL; |
| |
| list_for_each_entry(pos, &self->tags, node) |
| if (pos->tag == DW_TAG_typedef && pos->type == type) |
| return pos; |
| |
| return NULL; |
| } |
| |
| struct tag *cu__find_base_type_by_name(const struct cu *self, const char *name) |
| { |
| struct tag *pos; |
| |
| if (self == NULL || name == NULL) |
| return NULL; |
| |
| list_for_each_entry(pos, &self->tags, node) |
| if (pos->tag == DW_TAG_base_type) { |
| const struct base_type *bt = tag__base_type(pos); |
| |
| if (bt->name != NULL && strcmp(bt->name, name) == 0) |
| return pos; |
| } |
| |
| return NULL; |
| } |
| |
| struct tag *cu__find_struct_by_name(const struct cu *self, const char *name, |
| const int include_decls) |
| { |
| struct tag *pos; |
| |
| if (self == NULL || name == NULL) |
| return NULL; |
| |
| list_for_each_entry(pos, &self->tags, node) { |
| struct type *type; |
| |
| if (!tag__is_struct(pos)) |
| continue; |
| |
| type = tag__type(pos); |
| if (type__name(type, self) != NULL && |
| strcmp(type__name(type, self), name) == 0) { |
| if (type->declaration) { |
| if (include_decls) |
| return pos; |
| } else |
| return pos; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| struct tag *cus__find_struct_by_name(const struct cus *self, |
| struct cu **cu, const char *name, |
| const int include_decls) |
| { |
| struct cu *pos; |
| |
| list_for_each_entry(pos, &self->cus, node) { |
| struct tag *tag = cu__find_struct_by_name(pos, name, |
| include_decls); |
| if (tag != NULL) { |
| if (cu != NULL) |
| *cu = pos; |
| return tag; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| struct tag *cus__find_function_by_name(const struct cus *self, |
| struct cu **cu, const char *name) |
| { |
| struct cu *pos; |
| |
| list_for_each_entry(pos, &self->cus, node) { |
| struct tag *function = cu__find_function_by_name(pos, name); |
| |
| if (function != NULL) { |
| if (cu != NULL) |
| *cu = pos; |
| return function; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| struct tag *cus__find_tag_by_id(const struct cus *self, |
| struct cu **cu, const Dwarf_Off id) |
| { |
| struct cu *pos; |
| |
| list_for_each_entry(pos, &self->cus, node) { |
| struct tag *tag = cu__find_tag_by_id(pos, id); |
| |
| if (tag != NULL) { |
| if (cu != NULL) |
| *cu = pos; |
| return tag; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| struct cu *cus__find_cu_by_name(const struct cus *self, const char *name) |
| { |
| struct cu *pos; |
| |
| list_for_each_entry(pos, &self->cus, node) |
| if (strcmp(pos->name, name) == 0) |
| return pos; |
| |
| return NULL; |
| } |
| |
| struct tag *cu__find_function_by_name(const struct cu *self, const char *name) |
| { |
| struct tag *pos; |
| struct function *fpos; |
| |
| if (self == NULL || name == NULL) |
| return NULL; |
| |
| list_for_each_entry(pos, &self->tags, node) { |
| if (pos->tag != DW_TAG_subprogram) |
| continue; |
| fpos = tag__function(pos); |
| if (fpos->name != NULL && strcmp(fpos->name, name) == 0) |
| return pos; |
| } |
| |
| return NULL; |
| } |
| |
| static struct tag *lexblock__find_tag_by_id(const struct lexblock *self, |
| const Dwarf_Off id) |
| { |
| struct tag *pos; |
| |
| list_for_each_entry(pos, &self->tags, node) { |
| /* Allow find DW_TAG_lexical_block tags */ |
| if (pos->id == id) |
| return pos; |
| /* |
| * Oh, not looking for DW_TAG_lexical_block tags? So lets look |
| * inside this lexblock: |
| */ |
| if (pos->tag == DW_TAG_lexical_block) { |
| const struct lexblock *child = tag__lexblock(pos); |
| struct tag *tag = lexblock__find_tag_by_id(child, id); |
| |
| if (tag != NULL) |
| return tag; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| static struct variable *cu__find_variable_by_id(const struct cu *self, |
| const Dwarf_Off id) |
| { |
| struct tag *pos; |
| |
| if (self == NULL) |
| return NULL; |
| |
| list_for_each_entry(pos, &self->tags, node) { |
| /* Look at global variables first */ |
| if (pos->id == id) |
| return (struct variable *)(pos); |
| |
| /* Now look inside function lexical blocks */ |
| if (pos->tag == DW_TAG_subprogram) { |
| struct function *fn = tag__function(pos); |
| struct tag *tag = |
| lexblock__find_tag_by_id(&fn->lexblock, id); |
| |
| if (tag != NULL) |
| return (struct variable *)(tag); |
| } |
| } |
| |
| return NULL; |
| } |
| |
| static struct tag *list__find_tag_by_id(const struct list_head *self, |
| const Dwarf_Off id) |
| { |
| struct tag *pos, *tag; |
| |
| list_for_each_entry(pos, self, node) { |
| if (pos->id == id) |
| return pos; |
| |
| switch (pos->tag) { |
| case DW_TAG_namespace: |
| case DW_TAG_structure_type: |
| case DW_TAG_union_type: |
| tag = list__find_tag_by_id(&tag__namespace(pos)->tags, id); |
| break; |
| case DW_TAG_subprogram: |
| tag = list__find_tag_by_id(&tag__ftype(pos)->parms, id); |
| if (tag == NULL) |
| tag = list__find_tag_by_id(&tag__function(pos)->lexblock.tags, id); |
| break; |
| default: |
| continue; |
| } |
| |
| if (tag != NULL) |
| return tag; |
| } |
| |
| return NULL; |
| } |
| |
| static struct tag *cu__find_parameter_by_id(const struct cu *self, |
| const Dwarf_Off id) |
| { |
| if (self == NULL) |
| return NULL; |
| return list__find_tag_by_id(&self->tags, id); |
| } |
| |
| static size_t array_type__nr_entries(const struct array_type *self) |
| { |
| int i; |
| size_t nr_entries = 1; |
| |
| for (i = 0; i < self->dimensions; ++i) |
| nr_entries *= self->nr_entries[i]; |
| |
| return nr_entries; |
| } |
| |
| size_t tag__size(const struct tag *self, const struct cu *cu) |
| { |
| size_t size; |
| |
| switch (self->tag) { |
| case DW_TAG_pointer_type: |
| case DW_TAG_reference_type: return cu->addr_size; |
| case DW_TAG_base_type: return tag__base_type(self)->size; |
| case DW_TAG_enumeration_type: return tag__type(self)->size; |
| } |
| |
| if (self->type == 0) /* struct class: unions, structs */ |
| size = tag__type(self)->size; |
| else { |
| const struct tag *type = cu__find_tag_by_id(cu, self->type); |
| |
| if (type == NULL) { |
| tag__type_not_found(self); |
| return -1; |
| } |
| size = tag__size(type, cu); |
| } |
| |
| if (self->tag == DW_TAG_array_type) |
| return size * array_type__nr_entries(tag__array_type(self)); |
| |
| return size; |
| } |
| |
| static const char *tag__ptr_name(const struct tag *self, const struct cu *cu, |
| char *bf, size_t len, char ptr_char) |
| { |
| if (self->type == 0) /* No type == void */ |
| snprintf(bf, len, "void %c", ptr_char); |
| else { |
| const struct tag *type = cu__find_tag_by_id(cu, self->type); |
| |
| if (type == NULL) { |
| tag__type_not_found(self); |
| snprintf(bf, len, |
| "<ERROR: type not found!> %c", ptr_char); |
| } else { |
| char tmpbf[512]; |
| snprintf(bf, len, "%s %c", |
| tag__name(type, cu, |
| tmpbf, sizeof(tmpbf)), ptr_char); |
| } |
| } |
| |
| return bf; |
| } |
| |
| const char *tag__name(const struct tag *self, const struct cu *cu, |
| char *bf, size_t len) |
| { |
| struct tag *type; |
| |
| if (self == NULL) |
| strncpy(bf, "void", len); |
| else switch (self->tag) { |
| case DW_TAG_base_type: { |
| const struct base_type *bt = tag__base_type(self); |
| const char *s = "nameless base type!"; |
| |
| if (bt->name != NULL) |
| s = bt->name; |
| |
| strncpy(bf, s, len); |
| } |
| break; |
| case DW_TAG_subprogram: |
| strncpy(bf, function__name(tag__function(self), cu), len); |
| break; |
| case DW_TAG_pointer_type: |
| return tag__ptr_name(self, cu, bf, len, '*'); |
| case DW_TAG_reference_type: |
| return tag__ptr_name(self, cu, bf, len, '&'); |
| case DW_TAG_volatile_type: |
| case DW_TAG_const_type: |
| type = cu__find_tag_by_id(cu, self->type); |
| if (type == NULL && self->type != 0) { |
| tag__type_not_found(self); |
| strncpy(bf, "<ERROR>", len); |
| } else { |
| char tmpbf[128]; |
| snprintf(bf, len, "%s %s ", |
| self->tag == DW_TAG_volatile_type ? |
| "volatile" : "const", |
| tag__name(type, cu, tmpbf, sizeof(tmpbf))); |
| } |
| break; |
| case DW_TAG_array_type: |
| type = cu__find_tag_by_id(cu, self->type); |
| if (type == NULL) { |
| tag__type_not_found(self); |
| strncpy(bf, "<ERROR>", len); |
| } else |
| return tag__name(type, cu, bf, len); |
| break; |
| case DW_TAG_subroutine_type: { |
| FILE *bfp = fmemopen(bf, len, "w"); |
| if (bfp != NULL) { |
| ftype__fprintf(tag__ftype(self), cu, NULL, 0, 0, 0, bfp); |
| fclose(bfp); |
| } else |
| strncpy(bf, "<ERROR>", len); |
| } |
| break; |
| default: |
| snprintf(bf, len, "%s%s", tag__prefix(cu, self->tag), |
| type__name(tag__type(self), cu) ?: ""); |
| break; |
| } |
| |
| return bf; |
| } |
| |
| static struct tag *variable__type(const struct variable *self, |
| const struct cu *cu) |
| { |
| struct variable *var; |
| |
| if (self->tag.type != 0) |
| return cu__find_tag_by_id(cu, self->tag.type); |
| else if (self->abstract_origin != 0) { |
| var = cu__find_variable_by_id(cu, self->abstract_origin); |
| if (var) |
| return variable__type(var, cu); |
| } |
| |
| return NULL; |
| } |
| |
| const char *variable__type_name(const struct variable *self, |
| const struct cu *cu, |
| char *bf, size_t len) |
| { |
| const struct tag *tag = variable__type(self, cu); |
| return tag != NULL ? tag__name(tag, cu, bf, len) : NULL; |
| } |
| |
| const char *variable__name(const struct variable *self, const struct cu *cu) |
| { |
| if (self->name != NULL) |
| return self->name; |
| |
| if (self->abstract_origin != 0) { |
| struct variable *var = |
| cu__find_variable_by_id(cu, self->abstract_origin); |
| if (var != NULL) |
| return var->name; |
| } |
| return NULL; |
| } |
| |
| static const char *variable__prefix(const struct variable *var) |
| { |
| switch (var->location) { |
| case LOCATION_REGISTER: |
| return "register "; |
| case LOCATION_UNKNOWN: |
| if (var->external && var->declaration) |
| return "extern "; |
| break; |
| case LOCATION_GLOBAL: |
| if (!var->external) |
| return "static "; |
| break; |
| case LOCATION_LOCAL: |
| case LOCATION_OPTIMIZED: |
| break; |
| } |
| return NULL; |
| } |
| |
| static struct class_member *class_member__new(Dwarf_Die *die) |
| { |
| struct class_member *self = zalloc(sizeof(*self)); |
| |
| if (self != NULL) { |
| tag__init(&self->tag, die); |
| self->offset = attr_offset(die, DW_AT_data_member_location); |
| self->bit_size = attr_numeric(die, DW_AT_bit_size); |
| self->bit_offset = attr_numeric(die, DW_AT_bit_offset); |
| self->accessibility = attr_numeric(die, DW_AT_accessibility); |
| self->virtuality = attr_numeric(die, DW_AT_virtuality); |
| self->name = strings__add(attr_string(die, DW_AT_name)); |
| } |
| |
| return self; |
| } |
| |
| void class_member__delete(struct class_member *self) |
| { |
| free(self); |
| } |
| |
| static struct class_member *class_member__clone(const struct class_member *from) |
| { |
| struct class_member *self = malloc(sizeof(*self)); |
| |
| if (self != NULL) |
| memcpy(self, from, sizeof(*self)); |
| |
| return self; |
| } |
| |
| size_t class_member__size(const struct class_member *self, |
| const struct cu *cu) |
| { |
| struct tag *type = cu__find_tag_by_id(cu, self->tag.type); |
| if (type == NULL) { |
| tag__type_not_found(&self->tag); |
| return -1; |
| } |
| return tag__size(type, cu); |
| } |
| |
| static struct parameter *parameter__new(Dwarf_Die *die) |
| { |
| struct parameter *self = zalloc(sizeof(*self)); |
| |
| if (self != NULL) { |
| tag__init(&self->tag, die); |
| self->name = strings__add(attr_string(die, |
| DW_AT_name)); |
| self->abstract_origin = attr_type(die, DW_AT_abstract_origin); |
| } |
| |
| return self; |
| } |
| |
| const char *parameter__name(struct parameter *self, const struct cu *cu) |
| { |
| /* Check if the tag doesn't comes with a DW_AT_name attribute... */ |
| if (self->name == NULL && self->abstract_origin != 0) { |
| /* No? Does it have a DW_AT_abstract_origin? */ |
| struct tag *alias = |
| cu__find_parameter_by_id(cu, self->abstract_origin); |
| if (alias == NULL) { |
| tag__id_not_found(&self->tag, self->abstract_origin); |
| return NULL; |
| } |
| /* Now cache the result in this tag ->name field */ |
| self->name = tag__parameter(alias)->name; |
| } |
| |
| return self->name; |
| } |
| |
| Dwarf_Off parameter__type(struct parameter *self, const struct cu *cu) |
| { |
| /* Check if the tag doesn't comes with a DW_AT_type attribute... */ |
| if (self->tag.type == 0 && self->abstract_origin != 0) { |
| /* No? Does it have a DW_AT_abstract_origin? */ |
| struct tag *alias = |
| cu__find_parameter_by_id(cu, self->abstract_origin); |
| if (alias == NULL) { |
| tag__id_not_found(&self->tag, self->abstract_origin); |
| return 0; |
| } |
| /* Now cache the result in this tag ->name and type fields */ |
| self->name = tag__parameter(alias)->name; |
| self->tag.type = tag__parameter(alias)->tag.type; |
| } |
| |
| return self->tag.type; |
| } |
| |
| static struct inline_expansion *inline_expansion__new(Dwarf_Die *die) |
| { |
| struct inline_expansion *self = zalloc(sizeof(*self)); |
| |
| if (self != NULL) { |
| tag__init(&self->tag, die); |
| self->tag.decl_file = |
| strings__add(attr_string(die, DW_AT_call_file)); |
| self->tag.decl_line = attr_numeric(die, DW_AT_call_line); |
| self->tag.type = attr_type(die, DW_AT_abstract_origin); |
| |
| if (dwarf_lowpc(die, &self->low_pc)) |
| self->low_pc = 0; |
| if (dwarf_lowpc(die, &self->high_pc)) |
| self->high_pc = 0; |
| |
| self->size = self->high_pc - self->low_pc; |
| if (self->size == 0) { |
| Dwarf_Addr base, start; |
| ptrdiff_t offset = 0; |
| |
| while (1) { |
| offset = dwarf_ranges(die, offset, &base, &start, |
| &self->high_pc); |
| start = (unsigned long)start; |
| self->high_pc = (unsigned long)self->high_pc; |
| if (offset <= 0) |
| break; |
| self->size += self->high_pc - start; |
| if (self->low_pc == 0) |
| self->low_pc = start; |
| } |
| } |
| } |
| |
| return self; |
| } |
| |
| static struct label *label__new(Dwarf_Die *die) |
| { |
| struct label *self = malloc(sizeof(*self)); |
| |
| if (self != NULL) { |
| tag__init(&self->tag, die); |
| self->name = strings__add(attr_string(die, DW_AT_name)); |
| if (dwarf_lowpc(die, &self->low_pc)) |
| self->low_pc = 0; |
| } |
| |
| return self; |
| } |
| |
| static size_t union__fprintf(struct type *self, const struct cu *cu, |
| const struct conf_fprintf *conf, FILE *fp); |
| |
| static size_t type__fprintf(struct tag *type, const struct cu *cu, |
| const char *name, const struct conf_fprintf *conf, |
| FILE *fp) |
| { |
| char tbf[128]; |
| char namebf[256]; |
| struct type *ctype; |
| struct conf_fprintf tconf; |
| size_t printed = 0; |
| int expand_types = conf->expand_types; |
| int suppress_offset_comment = conf->suppress_offset_comment; |
| |
| if (type == NULL) |
| goto out_type_not_found; |
| |
| if (conf->expand_pointers) { |
| int nr_indirections = 0; |
| |
| while (type->tag == DW_TAG_pointer_type && type->type != 0) { |
| type = cu__find_tag_by_id(cu, type->type); |
| if (type == NULL) |
| goto out_type_not_found; |
| ++nr_indirections; |
| } |
| |
| if (nr_indirections > 0) { |
| const size_t len = strlen(name); |
| if (len + nr_indirections >= sizeof(namebf)) |
| goto out_type_not_found; |
| memset(namebf, '*', nr_indirections); |
| memcpy(namebf + nr_indirections, name, len); |
| namebf[len + nr_indirections] = '\0'; |
| name = namebf; |
| } |
| |
| expand_types = nr_indirections; |
| if (!suppress_offset_comment) |
| suppress_offset_comment = !!nr_indirections; |
| |
| /* Avoid loops */ |
| if (type->recursivity_level != 0) |
| expand_types = 0; |
| ++type->recursivity_level; |
| } |
| |
| if (expand_types) { |
| int typedef_expanded = 0; |
| |
| while (type->tag == DW_TAG_typedef) { |
| ctype = tag__type(type); |
| if (typedef_expanded) |
| printed += fprintf(fp, " -> %s", |
| type__name(ctype, cu)); |
| else { |
| printed += fprintf(fp, "/* typedef %s", |
| type__name(ctype, cu)); |
| typedef_expanded = 1; |
| } |
| type = cu__find_tag_by_id(cu, type->type); |
| if (type == NULL) |
| goto out_type_not_found; |
| } |
| if (typedef_expanded) |
| printed += fprintf(fp, " */ "); |
| } |
| |
| if (tag__is_struct(type) || tag__is_union(type) || |
| tag__is_enumeration(type)) { |
| tconf = *conf; |
| tconf.type_spacing -= 8; |
| tconf.prefix = NULL; |
| tconf.suffix = name; |
| tconf.emit_stats = 0; |
| tconf.suppress_offset_comment = suppress_offset_comment; |
| } |
| |
| switch (type->tag) { |
| case DW_TAG_pointer_type: |
| if (type->type != 0) { |
| struct tag *ptype = cu__find_tag_by_id(cu, type->type); |
| if (ptype == NULL) |
| goto out_type_not_found; |
| if (ptype->tag == DW_TAG_subroutine_type) { |
| printed += ftype__fprintf(tag__ftype(ptype), |
| cu, name, 0, 1, |
| conf->type_spacing, |
| fp); |
| break; |
| } |
| } |
| /* Fall Thru */ |
| default: |
| printed += fprintf(fp, "%-*s %s", conf->type_spacing, |
| tag__name(type, cu, tbf, sizeof(tbf)), name); |
| break; |
| case DW_TAG_subroutine_type: |
| printed += ftype__fprintf(tag__ftype(type), cu, name, 0, 0, |
| conf->type_spacing, fp); |
| break; |
| case DW_TAG_array_type: |
| printed += array_type__fprintf(type, cu, name, conf, fp); |
| break; |
| case DW_TAG_structure_type: |
| ctype = tag__type(type); |
| |
| if (type__name(ctype, cu) != NULL && !expand_types) |
| printed += fprintf(fp, "struct %-*s %s", |
| conf->type_spacing - 7, |
| type__name(ctype, cu), name); |
| else { |
| struct class *class = tag__class(type); |
| |
| class__find_holes(class, cu); |
| printed += class__fprintf(class, cu, &tconf, fp); |
| } |
| break; |
| case DW_TAG_union_type: |
| ctype = tag__type(type); |
| |
| if (type__name(ctype, cu) != NULL && !expand_types) |
| printed += fprintf(fp, "union %-*s %s", |
| conf->type_spacing - 6, |
| type__name(ctype, cu), name); |
| else |
| printed += union__fprintf(ctype, cu, &tconf, fp); |
| break; |
| case DW_TAG_enumeration_type: |
| ctype = tag__type(type); |
| |
| if (type__name(ctype, cu) != NULL) |
| printed += fprintf(fp, "enum %-*s %s", |
| conf->type_spacing - 5, |
| type__name(ctype, cu), name); |
| else |
| printed += enumeration__fprintf(type, cu, &tconf, fp); |
| break; |
| } |
| out: |
| if (conf->expand_types) |
| --type->recursivity_level; |
| |
| return printed; |
| out_type_not_found: |
| printed = fprintf(fp, "%-*s %s", conf->type_spacing, "<ERROR>", name); |
| goto out; |
| } |
| |
| static size_t struct_member__fprintf(struct class_member *self, |
| struct tag *type, const struct cu *cu, |
| const struct conf_fprintf *conf, FILE *fp) |
| { |
| const int size = tag__size(type, cu); |
| struct conf_fprintf sconf = *conf; |
| uint32_t offset = self->offset; |
| size_t printed = 0; |
| const char *name = self->name; |
| |
| if (!sconf.rel_offset) { |
| sconf.base_offset += self->offset; |
| offset = sconf.base_offset; |
| } |
| |
| if (self->tag.tag == DW_TAG_inheritance) { |
| name = "<ancestor>"; |
| printed += fprintf(fp, "/* "); |
| } |
| |
| printed += type__fprintf(type, cu, name, &sconf, fp); |
| |
| if (self->bit_size != 0) |
| printed += fprintf(fp, ":%u;", self->bit_size); |
| else { |
| fputc(';', fp); |
| ++printed; |
| } |
| |
| if ((tag__is_union(type) || tag__is_struct(type) || |
| tag__is_enumeration(type)) && |
| /* Look if is a type defined inline */ |
| type__name(tag__type(type), cu) == NULL) { |
| if (!sconf.suppress_offset_comment) { |
| /* Check if this is a anonymous union */ |
| const int slen = self->name != NULL ? |
| (int)strlen(self->name) : -1; |
| printed += fprintf(fp, "%*s/* %5u %5u */", |
| (sconf.type_spacing + |
| sconf.name_spacing - slen - 3), |
| " ", offset, size); |
| } |
| } else { |
| int spacing = sconf.type_spacing + sconf.name_spacing - printed; |
| |
| if (self->tag.tag == DW_TAG_inheritance) { |
| const size_t p = fprintf(fp, " */"); |
| printed += p; |
| spacing -= p; |
| } |
| if (!sconf.suppress_offset_comment) { |
| int size_spacing = 5; |
| |
| printed += fprintf(fp, "%*s/* %5u", |
| spacing > 0 ? spacing : 0, " ", |
| offset); |
| |
| if (self->bit_size != 0) { |
| printed += fprintf(fp, ":%2d", self->bit_offset); |
| size_spacing -= 3; |
| } |
| |
| printed += fprintf(fp, " %*u */", size_spacing, size); |
| } |
| } |
| return printed; |
| } |
| |
| static size_t union_member__fprintf(struct class_member *self, |
| struct tag *type, const struct cu *cu, |
| const struct conf_fprintf *conf, FILE *fp) |
| { |
| const size_t size = tag__size(type, cu); |
| size_t printed = type__fprintf(type, cu, self->name, conf, fp); |
| |
| if ((tag__is_union(type) || tag__is_struct(type) || |
| tag__is_enumeration(type)) && |
| /* Look if is a type defined inline */ |
| type__name(tag__type(type), cu) == NULL) { |
| if (!conf->suppress_offset_comment) { |
| /* Check if this is a anonymous union */ |
| const int slen = self->name != NULL ? (int)strlen(self->name) : -1; |
| /* |
| * Add the comment with the union size after padding the |
| * '} member_name;' last line of the type printed in the |
| * above call to type__fprintf. |
| */ |
| printed += fprintf(fp, ";%*s/* %11zd */", |
| (conf->type_spacing + |
| conf->name_spacing - slen - 3), " ", size); |
| } |
| } else { |
| printed += fprintf(fp, ";"); |
| |
| if (!conf->suppress_offset_comment) { |
| const int spacing = conf->type_spacing + conf->name_spacing - printed; |
| printed += fprintf(fp, "%*s/* %11zd */", |
| spacing > 0 ? spacing : 0, " ", size); |
| } |
| } |
| |
| return printed; |
| } |
| |
| static size_t union__fprintf(struct type *self, const struct cu *cu, |
| const struct conf_fprintf *conf, FILE *fp) |
| { |
| struct class_member *pos; |
| size_t printed = 0; |
| int indent = conf->indent; |
| struct conf_fprintf uconf; |
| |
| if (indent >= (int)sizeof(tabs)) |
| indent = sizeof(tabs) - 1; |
| |
| if (conf->prefix != NULL) |
| printed += fprintf(fp, "%s ", conf->prefix); |
| printed += fprintf(fp, "union%s%s {\n", type__name(self, cu) ? " " : "", |
| type__name(self, cu) ?: ""); |
| |
| uconf = *conf; |
| uconf.indent = indent + 1; |
| type__for_each_member(self, pos) { |
| struct tag *type = cu__find_tag_by_id(cu, pos->tag.type); |
| |
| printed += fprintf(fp, "%.*s", uconf.indent, tabs); |
| printed += union_member__fprintf(pos, type, cu, &uconf, fp); |
| fputc('\n', fp); |
| ++printed; |
| } |
| |
| return printed + fprintf(fp, "%.*s}%s%s", indent, tabs, |
| conf->suffix ? " " : "", conf->suffix ?: ""); |
| } |
| |
| static struct class *class__new(Dwarf_Die *die) |
| { |
| struct class *self = zalloc(sizeof(*self)); |
| |
| if (self != NULL) { |
| type__init(&self->type, die); |
| INIT_LIST_HEAD(&self->vtable); |
| } |
| |
| return self; |
| } |
| |
| void class__delete(struct class *self) |
| { |
| struct class_member *pos, *next; |
| |
| type__for_each_member_safe(&self->type, pos, next) |
| class_member__delete(pos); |
| |
| free(self); |
| } |
| |
| static void class__add_vtable_entry(struct class *self, struct function *vtable_entry) |
| { |
| ++self->nr_vtable_entries; |
| list_add_tail(&vtable_entry->vtable_node, &self->vtable); |
| } |
| |
| static void namespace__add_tag(struct namespace *self, struct tag *tag) |
| { |
| ++self->nr_tags; |
| list_add_tail(&tag->node, &self->tags); |
| } |
| |
| static void type__add_member(struct type *self, struct class_member *member) |
| { |
| ++self->nr_members; |
| namespace__add_tag(&self->namespace, &member->tag); |
| } |
| |
| struct class_member *type__last_member(struct type *self) |
| { |
| struct class_member *pos; |
| |
| list_for_each_entry_reverse(pos, &self->namespace.tags, tag.node) |
| if (pos->tag.tag == DW_TAG_member) |
| return pos; |
| return NULL; |
| } |
| |
| static int type__clone_members(struct type *self, const struct type *from) |
| { |
| struct class_member *pos; |
| |
| self->nr_members = 0; |
| INIT_LIST_HEAD(&self->namespace.tags); |
| |
| type__for_each_member(from, pos) { |
| struct class_member *member_clone = class_member__clone(pos); |
| |
| if (member_clone == NULL) |
| return -1; |
| type__add_member(self, member_clone); |
| } |
| |
| return 0; |
| } |
| |
| struct class *class__clone(const struct class *from, |
| const char *new_class_name) |
| { |
| struct class *self = zalloc(sizeof(*self)); |
| |
| if (self != NULL) { |
| memcpy(self, from, sizeof(*self)); |
| if (type__clone_members(&self->type, &from->type) != 0) { |
| class__delete(self); |
| self = NULL; |
| } |
| if (new_class_name != NULL) |
| self->type.namespace.name = strings__add(new_class_name); |
| } |
| |
| return self; |
| } |
| |
| static void enumeration__add(struct type *self, struct enumerator *enumerator) |
| { |
| ++self->nr_members; |
| namespace__add_tag(&self->namespace, &enumerator->tag); |
| } |
| |
| static void lexblock__init(struct lexblock *self, Dwarf_Die *die) |
| { |
| if (dwarf_highpc(die, &self->high_pc)) |
| self->high_pc = 0; |
| if (dwarf_lowpc(die, &self->low_pc)) |
| self->low_pc = 0; |
| |
| INIT_LIST_HEAD(&self->tags); |
| |
| self->nr_inline_expansions = |
| self->nr_labels = |
| self->nr_lexblocks = |
| self->nr_variables = 0; |
| } |
| |
| static struct lexblock *lexblock__new(Dwarf_Die *die) |
| { |
| struct lexblock *self = malloc(sizeof(*self)); |
| |
| if (self != NULL) { |
| tag__init(&self->tag, die); |
| lexblock__init(self, die); |
| } |
| |
| return self; |
| } |
| |
| static void lexblock__add_lexblock(struct lexblock *self, |
| struct lexblock *child) |
| { |
| ++self->nr_lexblocks; |
| list_add_tail(&child->tag.node, &self->tags); |
| } |
| |
| static void ftype__init(struct ftype *self, Dwarf_Die *die) |
| { |
| const uint16_t tag = dwarf_tag(die); |
| assert(tag == DW_TAG_subprogram || tag == DW_TAG_subroutine_type); |
| |
| tag__init(&self->tag, die); |
| INIT_LIST_HEAD(&self->parms); |
| self->nr_parms = 0; |
| self->unspec_parms = 0; |
| } |
| |
| static struct ftype *ftype__new(Dwarf_Die *die) |
| { |
| struct ftype *self = malloc(sizeof(*self)); |
| |
| if (self != NULL) |
| ftype__init(self, die); |
| |
| return self; |
| } |
| |
| static struct function *function__new(Dwarf_Die *die) |
| { |
| struct function *self = zalloc(sizeof(*self)); |
| |
| if (self != NULL) { |
| ftype__init(&self->proto, die); |
| lexblock__init(&self->lexblock, die); |
| self->name = strings__add(attr_string(die, DW_AT_name)); |
| self->linkage_name = strings__add(attr_string(die, DW_AT_MIPS_linkage_name)); |
| self->inlined = attr_numeric(die, DW_AT_inline); |
| self->external = dwarf_hasattr(die, DW_AT_external); |
| self->abstract_origin = attr_type(die, DW_AT_abstract_origin); |
| self->specification = attr_type(die, DW_AT_specification); |
| self->accessibility = attr_numeric(die, DW_AT_accessibility); |
| self->virtuality = attr_numeric(die, DW_AT_virtuality); |
| INIT_LIST_HEAD(&self->vtable_node); |
| INIT_LIST_HEAD(&self->tool_node); |
| self->vtable_entry = -1; |
| if (dwarf_hasattr(die, DW_AT_vtable_elem_location)) |
| self->vtable_entry = attr_offset(die, DW_AT_vtable_elem_location); |
| } |
| |
| return self; |
| } |
| |
| const char *function__name(struct function *self, const struct cu *cu) |
| { |
| /* Check if the tag doesn't comes with a DW_AT_name attribute... */ |
| if (self->name == NULL) { |
| /* No? So it must have a DW_AT_abstract_origin... */ |
| struct tag *tag = cu__find_tag_by_id(cu, |
| self->abstract_origin); |
| if (tag == NULL) { |
| /* ... or a DW_TAG_specification... */ |
| tag = cu__find_tag_by_id(cu, self->specification); |
| if (tag == NULL) { |
| tag__id_not_found(&self->proto.tag, |
| self->specification); |
| return NULL; |
| } |
| } |
| /* ... and now we cache the result in this tag ->name field */ |
| self->name = tag__function(tag)->name; |
| } |
| |
| return self->name; |
| } |
| |
| int ftype__has_parm_of_type(const struct ftype *self, const struct tag *target, |
| const struct cu *cu) |
| { |
| struct parameter *pos; |
| |
| ftype__for_each_parameter(self, pos) { |
| struct tag *type = |
| cu__find_tag_by_id(cu, parameter__type(pos, cu)); |
| |
| if (type != NULL && type->tag == DW_TAG_pointer_type) { |
| type = cu__find_tag_by_id(cu, type->type); |
| if (type != NULL && type->id == target->id) |
| return 1; |
| } |
| } |
| return 0; |
| } |
| |
| static void ftype__add_parameter(struct ftype *self, struct parameter *parm) |
| { |
| ++self->nr_parms; |
| list_add_tail(&parm->tag.node, &self->parms); |
| } |
| |
| static void lexblock__add_tag(struct lexblock *self, struct tag *tag) |
| { |
| list_add_tail(&tag->node, &self->tags); |
| } |
| |
| static void lexblock__add_inline_expansion(struct lexblock *self, |
| struct inline_expansion *exp) |
| { |
| ++self->nr_inline_expansions; |
| self->size_inline_expansions += exp->size; |
| lexblock__add_tag(self, &exp->tag); |
| } |
| |
| static void lexblock__add_variable(struct lexblock *self, struct variable *var) |
| { |
| ++self->nr_variables; |
| lexblock__add_tag(self, &var->tag); |
| } |
| |
| static void lexblock__add_label(struct lexblock *self, struct label *label) |
| { |
| ++self->nr_labels; |
| lexblock__add_tag(self, &label->tag); |
| } |
| |
| const struct class_member *class__find_bit_hole(const struct class *self, |
| const struct class_member *trailer, |
| const size_t bit_hole_size) |
| { |
| struct class_member *pos; |
| const size_t byte_hole_size = bit_hole_size / 8; |
| |
| type__for_each_data_member(&self->type, pos) |
| if (pos == trailer) |
| break; |
| else if (pos->hole >= byte_hole_size || |
| pos->bit_hole >= bit_hole_size) |
| return pos; |
| |
| return NULL; |
| } |
| |
| void class__find_holes(struct class *self, const struct cu *cu) |
| { |
| const struct type *ctype = &self->type; |
| struct class_member *pos, *last = NULL; |
| size_t last_size = 0, size; |
| uint32_t bit_sum = 0; |
| uint32_t bitfield_real_offset = 0; |
| |
| self->nr_holes = 0; |
| self->nr_bit_holes = 0; |
| |
| type__for_each_member(ctype, pos) { |
| /* XXX for now just skip these */ |
| if (pos->tag.tag == DW_TAG_inheritance && |
| pos->virtuality == DW_VIRTUALITY_virtual) |
| continue; |
| |
| if (last != NULL) { |
| /* |
| * We have to cast both offsets to int64_t because |
| * the current offset can be before the last offset |
| * when we are starting a bitfield that combines with |
| * the previous, small size fields. |
| */ |
| const ssize_t cc_last_size = ((int64_t)pos->offset - |
| (int64_t)last->offset); |
| |
| /* |
| * If the offset is the same this better be a bitfield |
| * or an empty struct (see rwlock_t in the Linux kernel |
| * sources when compiled for UP) or... |
| */ |
| if (cc_last_size > 0) { |
| /* |
| * Check if the DWARF byte_size info is smaller |
| * than the size used by the compiler, i.e. |
| * when combining small bitfields with the next |
| * member. |
| */ |
| if ((size_t)cc_last_size < last_size) |
| last_size = cc_last_size; |
| |
| last->hole = cc_last_size - last_size; |
| if (last->hole > 0) |
| ++self->nr_holes; |
| |
| if (bit_sum != 0) { |
| if (bitfield_real_offset != 0) { |
| last_size = bitfield_real_offset - last->offset; |
| bitfield_real_offset = 0; |
| } |
| |
| last->bit_hole = (last_size * 8) - |
| bit_sum; |
| if (last->bit_hole != 0) |
| ++self->nr_bit_holes; |
| |
| last->bitfield_end = 1; |
| bit_sum = 0; |
| } |
| } else if (cc_last_size < 0 && bit_sum == 0) |
| bitfield_real_offset = last->offset + last_size; |
| } |
| |
| bit_sum += pos->bit_size; |
| size = class_member__size(pos, cu); |
| |
| /* |
| * check for bitfields, accounting for only the biggest of the |
| * byte_size in the fields in each bitfield set. |
| */ |
| |
| if (last == NULL || last->offset != pos->offset || |
| pos->bit_size == 0 || last->bit_size == 0) { |
| last_size = size; |
| } else if (size > last_size) |
| last_size = size; |
| |
| last = pos; |
| } |
| |
| if (last != NULL) { |
| if (last->offset + last_size != ctype->size) |
| self->padding = ctype->size - |
| (last->offset + last_size); |
| if (last->bit_size != 0) |
| self->bit_padding = (last_size * 8) - bit_sum; |
| } else |
| self->padding = ctype->size; |
| } |
| |
| /** class__has_hole_ge - check if class has a hole greater or equal to @size |
| * @self - class instance |
| * @size - hole size to check |
| */ |
| int class__has_hole_ge(const struct class *self, const uint16_t size) |
| { |
| struct class_member *pos; |
| |
| if (self->nr_holes == 0) |
| return 0; |
| |
| type__for_each_data_member(&self->type, pos) |
| if (pos->hole >= size) |
| return 1; |
| |
| return 0; |
| } |
| |
| struct class_member *type__find_member_by_name(const struct type *self, |
| const char *name) |
| { |
| if (name != NULL) { |
| struct class_member *pos; |
| type__for_each_data_member(self, pos) |
| if (pos->name != NULL && strcmp(pos->name, name) == 0) |
| return pos; |
| } |
| |
| return NULL; |
| } |
| |
| uint32_t type__nr_members_of_type(const struct type *self, const Dwarf_Off type) |
| { |
| struct class_member *pos; |
| uint32_t nr_members_of_type = 0; |
| |
| type__for_each_member(self, pos) |
| if (pos->tag.type == type) |
| ++nr_members_of_type; |
| |
| return nr_members_of_type; |
| } |
| |
| static void lexblock__account_inline_expansions(struct lexblock *self, |
| const struct cu *cu) |
| { |
| struct tag *pos, *type; |
| |
| if (self->nr_inline_expansions == 0) |
| return; |
| |
| list_for_each_entry(pos, &self->tags, node) { |
| if (pos->tag == DW_TAG_lexical_block) { |
| lexblock__account_inline_expansions(tag__lexblock(pos), |
| cu); |
| continue; |
| } else if (pos->tag != DW_TAG_inlined_subroutine) |
| continue; |
| |
| type = cu__find_tag_by_id(cu, pos->type); |
| if (type != NULL) { |
| struct function *ftype = tag__function(type); |
| |
| ftype->cu_total_nr_inline_expansions++; |
| ftype->cu_total_size_inline_expansions += |
| tag__inline_expansion(pos)->size; |
| } |
| |
| } |
| } |
| |
| void cu__account_inline_expansions(struct cu *self) |
| { |
| struct tag *pos; |
| struct function *fpos; |
| |
| list_for_each_entry(pos, &self->tags, node) { |
| if (pos->tag != DW_TAG_subprogram) |
| continue; |
| fpos = tag__function(pos); |
| lexblock__account_inline_expansions(&fpos->lexblock, self); |
| self->nr_inline_expansions += fpos->lexblock.nr_inline_expansions; |
| self->size_inline_expansions += fpos->lexblock.size_inline_expansions; |
| } |
| } |
| |
| static size_t ftype__fprintf_parms(const struct ftype *self, |
| const struct cu *cu, int indent, |
| FILE *fp) |
| { |
| struct parameter *pos; |
| int first_parm = 1; |
| char sbf[128]; |
| struct tag *type; |
| const char *name, *stype; |
| size_t printed = fprintf(fp, "("); |
| |
| ftype__for_each_parameter(self, pos) { |
| if (!first_parm) { |
| if (indent == 0) |
| printed += fprintf(fp, ", "); |
| else |
| printed += fprintf(fp, ",\n%.*s", |
| indent, tabs); |
| } else |
| first_parm = 0; |
| name = parameter__name(pos, cu); |
| type = cu__find_tag_by_id(cu, parameter__type(pos, cu)); |
| if (type == NULL) { |
| stype = "<ERROR>"; |
| goto print_it; |
| } |
| if (type->tag == DW_TAG_pointer_type) { |
| if (type->type != 0) { |
| struct tag *ptype = |
| cu__find_tag_by_id(cu, type->type); |
| if (ptype == NULL) { |
| printed += fprintf(fp, ">>>ERROR: type " |
| "for %s not found!", |
| name); |
| continue; |
| } |
| if (ptype->tag == DW_TAG_subroutine_type) { |
| printed += |
| ftype__fprintf(tag__ftype(ptype), |
| cu, name, 0, 1, 0, |
| fp); |
| continue; |
| } |
| } |
| } else if (type->tag == DW_TAG_subroutine_type) { |
| printed += ftype__fprintf(tag__ftype(type), cu, name, |
| 0, 0, 0, fp); |
| continue; |
| } |
| print_it: |
| stype = tag__name(type, cu, sbf, sizeof(sbf)); |
| printed += fprintf(fp, "%s%s%s", stype, name ? " " : "", |
| name ?: ""); |
| } |
| |
| /* No parameters? */ |
| if (first_parm) |
| printed += fprintf(fp, "void)"); |
| else if (self->unspec_parms) |
| printed += fprintf(fp, ", ...)"); |
| else |
| printed += fprintf(fp, ")"); |
| return printed; |
| } |
| |
| static size_t function__tag_fprintf(const struct tag *tag, const struct cu *cu, |
| struct function *function, |
| uint16_t indent, FILE *fp) |
| { |
| char bf[512]; |
| size_t printed = 0, n; |
| const void *vtag = tag; |
| int c; |
| |
| if (indent >= sizeof(tabs)) |
| indent = sizeof(tabs) - 1; |
| c = indent * 8; |
| |
| switch (tag->tag) { |
| case DW_TAG_inlined_subroutine: { |
| const struct inline_expansion *exp = vtag; |
| const struct tag *talias = |
| cu__find_tag_by_id(cu, exp->tag.type); |
| struct function *alias = tag__function(talias); |
| const char *name; |
| |
| if (alias == NULL) { |
| tag__type_not_found(&exp->tag); |
| break; |
| } |
| printed = fprintf(fp, "%.*s", indent, tabs); |
| name = function__name(alias, cu); |
| n = fprintf(fp, "%s", name); |
| n += ftype__fprintf_parms(&alias->proto, cu, |
| indent + (strlen(name) + 7) / 8, |
| fp); |
| n += fprintf(fp, "; /* size=%zd, low_pc=%#llx */", |
| exp->size, (unsigned long long)exp->low_pc); |
| #if 0 |
| n = fprintf(fp, "%s(); /* size=%zd, low_pc=%#llx */", |
| function__name(alias, cu), exp->size, |
| (unsigned long long)exp->low_pc); |
| #endif |
| c = 69; |
| printed += n; |
| } |
| break; |
| case DW_TAG_variable: |
| printed = fprintf(fp, "%.*s", indent, tabs); |
| n = fprintf(fp, "%s %s;", |
| variable__type_name(vtag, cu, bf, sizeof(bf)), |
| variable__name(vtag, cu)); |
| c += n; |
| printed += n; |
| break; |
| case DW_TAG_label: { |
| const struct label *label = vtag; |
| printed = fprintf(fp, "%.*s", indent, tabs); |
| fputc('\n', fp); |
| ++printed; |
| c = fprintf(fp, "%s:", label->name); |
| printed += c; |
| } |
| break; |
| case DW_TAG_lexical_block: |
| printed = lexblock__fprintf(vtag, cu, function, indent, fp); |
| fputc('\n', fp); |
| return printed + 1; |
| default: |
| printed = fprintf(fp, "%.*s", indent, tabs); |
| n = fprintf(fp, "%s <%llx>", dwarf_tag_name(tag->tag), |
| (unsigned long long)tag->id); |
| c += n; |
| printed += n; |
| break; |
| } |
| |
| return printed + fprintf(fp, "%-*.*s// %5u\n", 70 - c, 70 - c, " ", |
| tag->decl_line); |
| } |
| |
| size_t lexblock__fprintf(const struct lexblock *self, const struct cu *cu, |
| struct function *function, uint16_t indent, FILE *fp) |
| { |
| struct tag *pos; |
| size_t printed; |
| |
| if (indent >= sizeof(tabs)) |
| indent = sizeof(tabs) - 1; |
| printed = fprintf(fp, "%.*s{", indent, tabs); |
| if (self->low_pc != 0) { |
| Dwarf_Off offset = self->low_pc - function->lexblock.low_pc; |
| |
| if (offset == 0) |
| printed += fprintf(fp, " /* low_pc=%#llx */", |
| (unsigned long long)self->low_pc); |
| else |
| printed += fprintf(fp, " /* %s+%#llx */", |
| function__name(function, cu), |
| (unsigned long long)offset); |
| } |
| printed += fprintf(fp, "\n"); |
| list_for_each_entry(pos, &self->tags, node) |
| printed += function__tag_fprintf(pos, cu, function, indent + 1, fp); |
| printed += fprintf(fp, "%.*s}", indent, tabs); |
| |
| if (function->lexblock.low_pc != self->low_pc) { |
| const size_t size = self->high_pc - self->low_pc; |
| printed += fprintf(fp, " /* lexblock size=%zd */", size); |
| } |
| return printed; |
| } |
| |
| size_t ftype__fprintf(const struct ftype *self, const struct cu *cu, |
| const char *name, const int inlined, |
| const int is_pointer, int type_spacing, FILE *fp) |
| { |
| struct tag *type = cu__find_tag_by_id(cu, self->tag.type); |
| char sbf[128]; |
| const char *stype = tag__name(type, cu, sbf, sizeof(sbf)); |
| size_t printed = fprintf(fp, "%s%-*s %s%s%s%s", |
| inlined ? "inline " : "", |
| type_spacing, stype, |
| self->tag.tag == DW_TAG_subroutine_type ? |
| "(" : "", |
| is_pointer ? "*" : "", name ?: "", |
| self->tag.tag == DW_TAG_subroutine_type ? |
| ")" : ""); |
| |
| return printed + ftype__fprintf_parms(self, cu, 0, fp); |
| } |
| |
| static size_t function__fprintf(const struct tag *tag_self, |
| const struct cu *cu, FILE *fp) |
| { |
| struct function *self = tag__function(tag_self); |
| size_t printed = 0; |
| |
| if (self->virtuality == DW_VIRTUALITY_virtual || |
| self->virtuality == DW_VIRTUALITY_pure_virtual) |
| printed += fprintf(fp, "virtual "); |
| |
| printed += ftype__fprintf(&self->proto, cu, function__name(self, cu), |
| function__declared_inline(self), 0, 0, fp); |
| |
| if (self->virtuality == DW_VIRTUALITY_pure_virtual) |
| printed += fprintf(fp, " = 0"); |
| |
| return printed; |
| } |
| |
| size_t function__fprintf_stats(const struct tag *tag_self, |
| const struct cu *cu, FILE *fp) |
| { |
| struct function *self = tag__function(tag_self); |
| size_t printed = lexblock__fprintf(&self->lexblock, cu, self, 0, fp); |
| |
| printed += fprintf(fp, "/* size: %zd", function__size(self)); |
| if (self->lexblock.nr_variables > 0) |
| printed += fprintf(fp, ", variables: %u", |
| self->lexblock.nr_variables); |
| if (self->lexblock.nr_labels > 0) |
| printed += fprintf(fp, ", goto labels: %u", |
| self->lexblock.nr_labels); |
| if (self->lexblock.nr_inline_expansions > 0) |
| printed += fprintf(fp, ", inline expansions: %u (%zd bytes)", |
| self->lexblock.nr_inline_expansions, |
| self->lexblock.size_inline_expansions); |
| return printed + fprintf(fp, " */\n"); |
| } |
| |
| static size_t class__fprintf_cacheline_boundary(uint32_t last_cacheline, |
| size_t sum, size_t sum_holes, |
| uint8_t *newline, |
| uint32_t *cacheline, |
| int indent, FILE *fp) |
| { |
| const size_t real_sum = sum + sum_holes; |
| size_t printed = 0; |
| |
| *cacheline = real_sum / cacheline_size; |
| |
| if (*cacheline > last_cacheline) { |
| const uint32_t cacheline_pos = real_sum % cacheline_size; |
| const uint32_t cacheline_in_bytes = real_sum - cacheline_pos; |
| |
| if (*newline) { |
| fputc('\n', fp); |
| *newline = 0; |
| ++printed; |
| } |
| |
| printed += fprintf(fp, "%.*s", indent, tabs); |
| |
| if (cacheline_pos == 0) |
| printed += fprintf(fp, "/* --- cacheline %u boundary " |
| "(%u bytes) --- */\n", *cacheline, |
| cacheline_in_bytes); |
| else |
| printed += fprintf(fp, "/* --- cacheline %u boundary " |
| "(%u bytes) was %u bytes ago --- " |
| "*/\n", *cacheline, |
| cacheline_in_bytes, cacheline_pos); |
| } |
| return printed; |
| } |
| |
| static size_t class__vtable_fprintf(struct class *self, |
| const struct conf_fprintf *conf, FILE *fp) |
| { |
| struct function *pos; |
| size_t printed = 0; |
| |
| if (self->nr_vtable_entries == 0) |
| goto out; |
| |
| printed += fprintf(fp, "%.*s/* vtable has %u entries: {\n", |
| conf->indent, tabs, self->nr_vtable_entries); |
| |
| list_for_each_entry(pos, &self->vtable, vtable_node) { |
| printed += fprintf(fp, "%.*s [%d] = %s(%s), \n", |
| conf->indent, tabs, pos->vtable_entry, |
| pos->name, pos->linkage_name); |
| } |
| |
| printed += fprintf(fp, "%.*s} */", conf->indent, tabs); |
| out: |
| return printed; |
| } |
| |
| size_t class__fprintf(struct class *self, const struct cu *cu, |
| const struct conf_fprintf *conf, FILE *fp) |
| { |
| struct type *tself = &self->type; |
| size_t last_size = 0, size; |
| uint8_t newline = 0; |
| uint16_t nr_paddings = 0; |
| uint32_t sum = 0; |
| uint32_t sum_holes = 0; |
| uint32_t sum_paddings = 0; |
| uint32_t sum_bit_holes = 0; |
| uint32_t last_cacheline = 0; |
| uint32_t bitfield_real_offset = 0; |
| int first = 1; |
| struct class_member *pos, *last = NULL; |
| struct tag *tag_pos; |
| const char *current_accessibility = NULL; |
| struct conf_fprintf cconf = conf ? *conf : conf_fprintf__defaults; |
| size_t printed = fprintf(fp, "%s%sstruct%s%s", |
| cconf.prefix ?: "", cconf.prefix ? " " : "", |
| type__name(tself, cu) ? " " : "", |
| type__name(tself, cu) ?: ""); |
| int indent = cconf.indent; |
| |
| if (indent >= (int)sizeof(tabs)) |
| indent = sizeof(tabs) - 1; |
| |
| cconf.indent = indent + 1; |
| cconf.no_semicolon = 0; |
| |
| /* First look if we have DW_TAG_inheritance */ |
| type__for_each_tag(tself, tag_pos) { |
| struct tag *type; |
| const char *accessibility; |
| |
| if (tag_pos->tag != DW_TAG_inheritance) |
| continue; |
| |
| if (first) { |
| printed += fprintf(fp, " :"); |
| first = 0; |
| } else |
| printed += fprintf(fp, ","); |
| |
| pos = tag__class_member(tag_pos); |
| |
| if (pos->virtuality == DW_VIRTUALITY_virtual) |
| printed += fprintf(fp, " virtual"); |
| |
| accessibility = tag__accessibility(tag_pos); |
| if (accessibility != NULL) |
| printed += fprintf(fp, " %s", accessibility); |
| |
| type = cu__find_tag_by_id(cu, tag_pos->type); |
| printed += fprintf(fp, " %s", type__name(tag__type(type), cu)); |
| } |
| |
| printed += fprintf(fp, " {\n"); |
| |
| type__for_each_tag(tself, tag_pos) { |
| struct tag *type; |
| const char *accessibility = tag__accessibility(tag_pos); |
| |
| if (accessibility != NULL && |
| accessibility != current_accessibility) { |
| current_accessibility = accessibility; |
| printed += fprintf(fp, "%.*s%s:\n\n", |
| cconf.indent - 1, tabs, |
| accessibility); |
| } |
| |
| if (tag_pos->tag != DW_TAG_member && |
| tag_pos->tag != DW_TAG_inheritance) { |
| if (!cconf.show_only_data_members) { |
| printed += tag__fprintf(tag_pos, cu, &cconf, fp); |
| printed += fprintf(fp, "\n\n"); |
| } |
| continue; |
| } |
| pos = tag__class_member(tag_pos); |
| |
| if (last != NULL && |
| pos->offset != last->offset && |
| !cconf.suppress_comments) |
| printed += |
| class__fprintf_cacheline_boundary(last_cacheline, |
| sum, sum_holes, |
| &newline, |
| &last_cacheline, |
| cconf.indent, |
| fp); |
| /* |
| * These paranoid checks doesn't make much sense on |
| * DW_TAG_inheritance, have to understand why virtual public |
| * ancestors make the offset go backwards... |
| */ |
| if (last != NULL && tag_pos->tag == DW_TAG_member) { |
| if (pos->offset < last->offset || |
| (pos->offset == last->offset && |
| last->bit_size == 0 && |
| /* |
| * This is just when transitioning from a non-bitfield to |
| * a bitfield, think about zero sized arrays in the middle |
| * of a struct. |
| */ |
| pos->bit_size != 0)) { |
| if (!cconf.suppress_comments) { |
| if (!newline++) { |
| fputc('\n', fp); |
| ++printed; |
| } |
| printed += fprintf(fp, "%.*s/* Bitfield combined" |
| " with previous fields */\n", |
| cconf.indent, tabs); |
| } |
| if (pos->offset != last->offset) |
| bitfield_real_offset = last->offset + last_size; |
| } else { |
| const ssize_t cc_last_size = ((ssize_t)pos->offset - |
| (ssize_t)last->offset); |
| |
| if (cc_last_size > 0 && |
| (size_t)cc_last_size < last_size) { |
| if (!cconf.suppress_comments) { |
| if (!newline++) { |
| fputc('\n', fp); |
| ++printed; |
| } |
| printed += fprintf(fp, "%.*s/* Bitfield combined" |
| " with next fields */\n", |
| cconf.indent, tabs); |
| } |
| sum -= last_size; |
| sum += cc_last_size; |
| } |
| } |
| } |
| |
| if (newline) { |
| fputc('\n', fp); |
| newline = 0; |
| ++printed; |
| } |
| |
| type = cu__find_tag_by_id(cu, pos->tag.type); |
| if (type == NULL) { |
| tag__type_not_found(&pos->tag); |
| printed += fprintf(fp, "%.*s>>>ERROR: type for %s not " |
| "found!\n", cconf.indent, tabs, pos->name); |
| continue; |
| } |
| |
| size = tag__size(type, cu); |
| printed += fprintf(fp, "%.*s", cconf.indent, tabs); |
| printed += struct_member__fprintf(pos, type, cu, &cconf, fp); |
| |
| if (tag__is_struct(type) && !cconf.suppress_comments) { |
| const uint16_t padding = tag__class(type)->padding; |
| if (padding > 0) { |
| ++nr_paddings; |
| sum_paddings += padding; |
| if (!newline++) { |
| fputc('\n', fp); |
| ++printed; |
| } |
| |
| printed += fprintf(fp, "\n%.*s/* XXX last " |
| "struct has %d byte%s of " |
| "padding */", cconf.indent, |
| tabs, padding, |
| padding != 1 ? "s" : ""); |
| } |
| } |
| |
| if (pos->bit_hole != 0 && !cconf.suppress_comments) { |
| if (!newline++) { |
| fputc('\n', fp); |
| ++printed; |
| } |
| printed += fprintf(fp, "\n%.*s/* XXX %d bit%s hole, " |
| "try to pack */", cconf.indent, tabs, |
| pos->bit_hole, |
| pos->bit_hole != 1 ? "s" : ""); |
| sum_bit_holes += pos->bit_hole; |
| } |
| |
| if (pos->hole > 0 && !cconf.suppress_comments) { |
| if (!newline++) { |
| fputc('\n', fp); |
| ++printed; |
| } |
| printed += fprintf(fp, "\n%.*s/* XXX %d byte%s hole, " |
| "try to pack */", |
| cconf.indent, tabs, pos->hole, |
| pos->hole != 1 ? "s" : ""); |
| sum_holes += pos->hole; |
| } |
| |
| fputc('\n', fp); |
| ++printed; |
| |
| /* XXX for now just skip these */ |
| if (tag_pos->tag == DW_TAG_inheritance && |
| pos->virtuality == DW_VIRTUALITY_virtual) |
| continue; |
| |
| /* |
| * Check if we have to adjust size because bitfields were |
| * combined with previous fields. |
| */ |
| if (bitfield_real_offset != 0 && last->bitfield_end) { |
| size_t real_last_size = pos->offset - bitfield_real_offset; |
| sum -= last_size; |
| sum += real_last_size; |
| bitfield_real_offset = 0; |
| } |
| |
| if (last == NULL || /* First member */ |
| /* |
| * Last member was a zero sized array, typedef, struct, etc |
| */ |
| last_size == 0 || |
| /* |
| * We moved to a new offset |
| */ |
| last->offset != pos->offset) { |
| sum += size; |
| last_size = size; |
| } else if (last->bit_size == 0 && pos->bit_size != 0) { |
| /* |
| * Transitioned from from a non-bitfield to a |
| * bitfield sharing the same offset |
| */ |
| /* |
| * Compensate by removing the size of the |
| * last member that is "inside" this new |
| * member at the same offset. |
| * |
| * E.g.: |
| * struct foo { |
| * u8 a; / 0 1 / |
| * int b:1; / 0:23 4 / |
| * } |
| */ |
| sum += size - last_size; |
| last_size = size; |
| } |
| |
| last = pos; |
| } |
| |
| /* |
| * Check if we have to adjust size because bitfields were |
| * combined with previous fields and were the last fields |
| * in the struct. |
| */ |
| if (bitfield_real_offset != 0) { |
| size_t real_last_size = tself->size - bitfield_real_offset; |
| sum -= last_size; |
| sum += real_last_size; |
| bitfield_real_offset = 0; |
| } |
| |
| if (!cconf.suppress_comments) |
| printed += class__fprintf_cacheline_boundary(last_cacheline, |
| sum, sum_holes, |
| &newline, |
| &last_cacheline, |
| cconf.indent, fp); |
| class__vtable_fprintf(self, &cconf, fp); |
| if (!cconf.emit_stats) |
| goto out; |
| |
| printed += fprintf(fp, "\n%.*s/* size: %zd, cachelines: %zd */", |
| cconf.indent, tabs, |
| tself->size, tag__nr_cachelines(class__tag(self), |
| cu)); |
| if (sum_holes > 0) |
| printed += fprintf(fp, "\n%.*s/* sum members: %u, holes: %d, " |
| "sum holes: %u */", |
| cconf.indent, tabs, |
| sum, self->nr_holes, sum_holes); |
| if (sum_bit_holes > 0) |
| printed += fprintf(fp, "\n%.*s/* bit holes: %d, sum bit " |
| "holes: %u bits */", |
| cconf.indent, tabs, |
| self->nr_bit_holes, sum_bit_holes); |
| if (self->padding > 0) |
| printed += fprintf(fp, "\n%.*s/* padding: %u */", |
| cconf.indent, |
| tabs, self->padding); |
| if (nr_paddings > 0) |
| printed += fprintf(fp, "\n%.*s/* paddings: %u, sum paddings: " |
| "%u */", |
| cconf.indent, tabs, |
| nr_paddings, sum_paddings); |
| if (self->bit_padding > 0) |
| printed += fprintf(fp, "\n%.*s/* bit_padding: %u bits */", |
| cconf.indent, tabs, |
| self->bit_padding); |
| last_cacheline = tself->size % cacheline_size; |
| if (last_cacheline != 0) |
| printed += fprintf(fp, "\n%.*s/* last cacheline: %u bytes */", |
| cconf.indent, tabs, |
| last_cacheline); |
| if (cconf.show_first_biggest_size_base_type_member && |
| tself->nr_members != 0) { |
| struct class_member *m = type__find_first_biggest_size_base_type_member(tself, cu); |
| |
| printed += fprintf(fp, "\n%.*s/* first biggest size base type member: %s %u %zd */", |
| cconf.indent, tabs, m->name, m->offset, |
| class_member__size(m, cu)); |
| } |
| |
| if (sum + sum_holes != tself->size - self->padding) |
| printed += fprintf(fp, "\n\n%.*s/* BRAIN FART ALERT! %zd != %u " |
| "+ %u(holes), diff = %zd */\n", |
| cconf.indent, tabs, |
| tself->size, sum, sum_holes, |
| tself->size - (sum + sum_holes)); |
| fputc('\n', fp); |
| out: |
| return printed + fprintf(fp, "%.*s}%s%s", indent, tabs, |
| cconf.suffix ? " ": "", cconf.suffix ?: ""); |
| } |
| |
| static size_t variable__fprintf(const struct tag *tag, const struct cu *cu, |
| const struct conf_fprintf *conf, FILE *fp) |
| { |
| const struct variable *var = tag__variable(tag); |
| const char *name = variable__name(var, cu); |
| size_t printed = 0; |
| |
| if (name != NULL) { |
| struct tag *type = variable__type(var, cu); |
| if (type != NULL) { |
| const char *varprefix = variable__prefix(var); |
| |
| if (varprefix != NULL) |
| printed += fprintf(fp, "%s", varprefix); |
| printed += type__fprintf(type, cu, name, conf, fp); |
| } |
| } |
| return printed; |
| } |
| |
| static size_t namespace__fprintf(const struct tag *tself, const struct cu *cu, |
| const struct conf_fprintf *conf, FILE *fp) |
| { |
| struct namespace *self = tag__namespace(tself); |
| struct conf_fprintf cconf = *conf; |
| size_t printed = fprintf(fp, "namespace %s {\n", self->name); |
| struct tag *pos; |
| |
| ++cconf.indent; |
| cconf.no_semicolon = 0; |
| |
| namespace__for_each_tag(self, pos) { |
| printed += tag__fprintf(pos, cu, &cconf, fp); |
| printed += fprintf(fp, "\n\n"); |
| } |
| |
| return printed + fprintf(fp, "}"); |
| } |
| |
| size_t tag__fprintf(struct tag *self, const struct cu *cu, |
| const struct conf_fprintf *conf, FILE *fp) |
| { |
| size_t printed = 0; |
| struct conf_fprintf tconf; |
| const struct conf_fprintf *pconf = conf; |
| |
| if (conf == NULL) { |
| tconf = conf_fprintf__defaults; |
| pconf = &tconf; |
| |
| if (tconf.expand_types) |
| tconf.name_spacing = 55; |
| else if (tag__is_union(self)) |
| tconf.name_spacing = 21; |
| } else if (conf->name_spacing == 0 || conf->type_spacing == 0) { |
| tconf = *conf; |
| pconf = &tconf; |
| |
| if (tconf.name_spacing == 0) { |
| if (tconf.expand_types) |
| tconf.name_spacing = 55; |
| else |
| tconf.name_spacing = |
| tag__is_union(self) ? 21 : 23; |
| } |
| if (tconf.type_spacing == 0) |
| tconf.type_spacing = 26; |
| } |
| |
| if (pconf->expand_types) |
| ++self->recursivity_level; |
| |
| if (pconf->show_decl_info) { |
| printed += fprintf(fp, "%.*s", pconf->indent, tabs); |
| printed += tag__fprintf_decl_info(self, fp); |
| } |
| printed += fprintf(fp, "%.*s", pconf->indent, tabs); |
| |
| switch (self->tag) { |
| case DW_TAG_enumeration_type: |
| printed += enumeration__fprintf(self, cu, pconf, fp); |
| break; |
| case DW_TAG_typedef: |
| printed += typedef__fprintf(self, cu, pconf, fp); |
| break; |
| case DW_TAG_structure_type: |
| printed += class__fprintf(tag__class(self), cu, pconf, fp); |
| break; |
| case DW_TAG_namespace: |
| printed += namespace__fprintf(self, cu, pconf, fp); |
| break; |
| case DW_TAG_subprogram: |
| printed += function__fprintf(self, cu, fp); |
| break; |
| case DW_TAG_union_type: |
| printed += union__fprintf(tag__type(self), cu, pconf, fp); |
| break; |
| case DW_TAG_variable: |
| printed += variable__fprintf(self, cu, pconf, fp); |
| break; |
| case DW_TAG_imported_declaration: |
| printed += imported_declaration__fprintf(self, cu, fp); |
| break; |
| case DW_TAG_imported_module: |
| printed += imported_module__fprintf(self, cu, fp); |
| break; |
| default: |
| printed += fprintf(fp, "/* %s: %s tag not supported! */", __func__, |
| dwarf_tag_name(self->tag)); |
| break; |
| } |
| |
| if (!pconf->no_semicolon) { |
| fputc(';', fp); |
| ++printed; |
| } |
| |
| if (self->tag == DW_TAG_subprogram && |
| !pconf->suppress_comments) { |
| const struct function *fself = tag__function(self); |
| |
| if (fself->linkage_name != NULL) |
| printed += fprintf(fp, " /* linkage=%s */", fself->linkage_name); |
| } |
| |
| if (pconf->expand_types) |
| --self->recursivity_level; |
| |
| return printed; |
| } |
| |
| int cu__for_each_tag(struct cu *self, |
| int (*iterator)(struct tag *tag, struct cu *cu, |
| void *cookie), |
| void *cookie, |
| struct tag *(*filter)(struct tag *tag, struct cu *cu, |
| void *cookie)) |
| { |
| struct tag *pos; |
| |
| list_for_each_entry(pos, &self->tags, node) { |
| struct tag *tag = pos; |
| if (filter != NULL) { |
| tag = filter(pos, self, cookie); |
| if (tag == NULL) |
| continue; |
| } |
| if (iterator(tag, self, cookie)) |
| return 1; |
| } |
| return 0; |
| } |
| |
| void cus__for_each_cu(struct cus *self, |
| int (*iterator)(struct cu *cu, void *cookie), |
| void *cookie, |
| struct cu *(*filter)(struct cu *cu)) |
| { |
| struct cu *pos; |
| |
| list_for_each_entry(pos, &self->cus, node) { |
| struct cu *cu = pos; |
| if (filter != NULL) { |
| cu = filter(pos); |
| if (cu == NULL) |
| continue; |
| } |
| if (iterator(cu, cookie)) |
| break; |
| } |
| } |
| |
| static void oom(const char *msg) |
| { |
| fprintf(stderr, "libclasses: out of memory(%s)\n", msg); |
| exit(EXIT_FAILURE); |
| } |
| |
| static uint64_t attr_upper_bound(Dwarf_Die *die) |
| { |
| Dwarf_Attribute attr; |
| |
| if (dwarf_attr(die, DW_AT_upper_bound, &attr) != NULL) { |
| Dwarf_Word num; |
| |
| if (dwarf_formudata(&attr, &num) == 0) { |
| return (uintmax_t)num + 1; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static void __cu__tag_not_handled(Dwarf_Die *die, const char *fn) |
| { |
| fprintf(stderr, "%s: DW_TAG_%s @ <%#llx> not handled!\n", |
| fn, dwarf_tag_name(dwarf_tag(die)), |
| (unsigned long long)dwarf_dieoffset(die)); |
| } |
| |
| #define cu__tag_not_handled(die) __cu__tag_not_handled(die, __FUNCTION__) |
| |
| static struct tag *__die__process_tag(Dwarf_Die *die, struct cu *cu, |
| const char *fn); |
| |
| #define die__process_tag(die, cu) __die__process_tag(die, cu, __FUNCTION__) |
| |
| static struct tag *die__create_new_tag(Dwarf_Die *die) |
| { |
| struct tag *self = tag__new(die); |
| |
| if (self == NULL) |
| oom("tag__new"); |
| |
| if (dwarf_haschildren(die)) |
| fprintf(stderr, "%s: %s WITH children!\n", __FUNCTION__, |
| dwarf_tag_name(self->tag)); |
| |
| return self; |
| } |
| |
| static void die__process_class(Dwarf_Die *die, |
| struct type *class, struct cu *cu); |
| |
| static struct tag *die__create_new_class(Dwarf_Die *die, struct cu *cu) |
| { |
| Dwarf_Die child; |
| struct class *class = class__new(die); |
| |
| if (class == NULL) |
| oom("class__new"); |
| |
| if (dwarf_haschildren(die) != 0 && dwarf_child(die, &child) == 0) |
| die__process_class(&child, &class->type, cu); |
| |
| return &class->type.namespace.tag; |
| } |
| |
| static void die__process_namespace(Dwarf_Die *die, |
| struct namespace *namespace, struct cu *cu); |
| |
| static struct tag *die__create_new_namespace(Dwarf_Die *die, struct cu *cu) |
| { |
| Dwarf_Die child; |
| struct namespace *namespace = namespace__new(die); |
| |
| if (namespace == NULL) |
| oom("namespace__new"); |
| |
| if (dwarf_haschildren(die) != 0 && dwarf_child(die, &child) == 0) |
| die__process_namespace(&child, namespace, cu); |
| |
| return &namespace->tag; |
| } |
| |
| static struct tag *die__create_new_union(Dwarf_Die *die, struct cu *cu) |
| { |
| Dwarf_Die child; |
| struct type *utype = type__new(die); |
| |
| if (utype == NULL) |
| oom("type__new"); |
| |
| if (dwarf_haschildren(die) != 0 && dwarf_child(die, &child) == 0) |
| die__process_class(&child, utype, cu); |
| |
| return &utype->namespace.tag; |
| } |
| |
| static struct tag *die__create_new_base_type(Dwarf_Die *die) |
| { |
| struct base_type *base = base_type__new(die); |
| |
| if (base == NULL) |
| oom("base_type__new"); |
| |
| if (dwarf_haschildren(die)) |
| fprintf(stderr, "%s: DW_TAG_base_type WITH children!\n", |
| __FUNCTION__); |
| |
| return &base->tag; |
| } |
| |
| static struct tag *die__create_new_typedef(Dwarf_Die *die) |
| { |
| struct type *tdef = type__new(die); |
| |
| if (tdef == NULL) |
| oom("type__new"); |
| |
| if (dwarf_haschildren(die)) |
| fprintf(stderr, "%s: DW_TAG_typedef WITH children!\n", |
| __FUNCTION__); |
| |
| return &tdef->namespace.tag; |
| } |
| |
| static struct tag *die__create_new_array(Dwarf_Die *die) |
| { |
| Dwarf_Die child; |
| /* "64 dimensions will be enough for everybody." acme, 2006 */ |
| const uint8_t max_dimensions = 64; |
| uint32_t nr_entries[max_dimensions]; |
| struct array_type *array = array_type__new(die); |
| |
| if (array == NULL) |
| oom("array_type__new"); |
| |
| if (!dwarf_haschildren(die) || dwarf_child(die, &child) != 0) { |
| fprintf(stderr, "%s: DW_TAG_array_type with no children!\n", |
| __FUNCTION__); |
| return NULL; |
| } |
| |
| die = &child; |
| array->dimensions = 0; |
| do { |
| if (dwarf_tag(die) == DW_TAG_subrange_type) { |
| nr_entries[array->dimensions++] = attr_upper_bound(die); |
| if (array->dimensions == max_dimensions) { |
| fprintf(stderr, "%s: only %u dimensions are " |
| "supported!\n", |
| __FUNCTION__, max_dimensions); |
| break; |
| } |
| } else |
| cu__tag_not_handled(die); |
| } while (dwarf_siblingof(die, die) == 0); |
| |
| array->nr_entries = memdup(nr_entries, |
| array->dimensions * sizeof(uint32_t)); |
| if (array->nr_entries == NULL) |
| oom("memdup(array.nr_entries)"); |
| |
| return &array->tag; |
| } |
| |
| static void die__create_new_parameter(Dwarf_Die *die, struct ftype *ftype, |
| struct lexblock *lexblock) |
| { |
| struct parameter *parm = parameter__new(die); |
| |
| if (parm == NULL) |
| oom("parameter__new"); |
| |
| if (ftype != NULL) |
| ftype__add_parameter(ftype, parm); |
| else { |
| /* |
| * DW_TAG_formal_parameters on a non DW_TAG_subprogram nor |
| * DW_TAG_subroutine_type tag happens sometimes, likely due to |
| * compiler optimizing away a inline expansion (at least this |
| * was observed in some cases, such as in the Linux kernel |
| * current_kernel_time function circa 2.6.20-rc5), keep it in |
| * the lexblock tag list because it can be referenced as an |
| * DW_AT_abstract_origin in another DW_TAG_formal_parameter. |
| */ |
| lexblock__add_tag(lexblock, &parm->tag); |
| } |
| |
| } |
| |
| static void die__create_new_label(Dwarf_Die *die, struct lexblock *lexblock) |
| { |
| struct label *label = label__new(die); |
| |
| if (label == NULL) |
| oom("label__new"); |
| |
| lexblock__add_label(lexblock, label); |
| } |
| |
| static struct tag *die__create_new_variable(Dwarf_Die *die) |
| { |
| struct variable *var = variable__new(die); |
| if (var == NULL) |
| oom("variable__new"); |
| |
| return &var->tag; |
| } |
| |
| static struct tag *die__create_new_subroutine_type(Dwarf_Die *die) |
| { |
| Dwarf_Die child; |
| struct ftype *ftype = ftype__new(die); |
| |
| if (ftype == NULL) |
| oom("ftype__new"); |
| |
| if (!dwarf_haschildren(die) || dwarf_child(die, &child) != 0) |
| goto out; |
| |
| die = &child; |
| do { |
| switch (dwarf_tag(die)) { |
| case DW_TAG_formal_parameter: |
| die__create_new_parameter(die, ftype, NULL); |
| break; |
| case DW_TAG_unspecified_parameters: |
| ftype->unspec_parms = 1; |
| break; |
| default: |
| cu__tag_not_handled(die); |
| break; |
| } |
| } while (dwarf_siblingof(die, die) == 0); |
| out: |
| return &ftype->tag; |
| } |
| |
| static struct tag *die__create_new_enumeration(Dwarf_Die *die) |
| { |
| Dwarf_Die child; |
| struct type *enumeration = type__new(die); |
| |
| if (enumeration == NULL) |
| oom("class__new"); |
| |
| if (!dwarf_haschildren(die) || dwarf_child(die, &child) != 0) { |
| fprintf(stderr, "%s: DW_TAG_enumeration_type with no " |
| "children!\n", __FUNCTION__); |
| return NULL; |
| } |
| |
| die = &child; |
| do { |
| struct enumerator *enumerator; |
| |
| if (dwarf_tag(die) != DW_TAG_enumerator) { |
| cu__tag_not_handled(die); |
| continue; |
| } |
| enumerator = enumerator__new(die); |
| if (enumerator == NULL) |
| oom("enumerator__new"); |
| |
| enumeration__add(enumeration, enumerator); |
| } while (dwarf_siblingof(die, die) == 0); |
| |
| return &enumeration->namespace.tag; |
| } |
| |
| static void die__process_class(Dwarf_Die *die, struct type *class, |
| struct cu *cu) |
| { |
| do { |
| switch (dwarf_tag(die)) { |
| case DW_TAG_inheritance: |
| case DW_TAG_member: { |
| struct class_member *member = class_member__new(die); |
| |
| if (member == NULL) |
| oom("class_member__new"); |
| |
| type__add_member(class, member); |
| } |
| continue; |
| default: { |
| struct tag *tag = die__process_tag(die, cu); |
| |
| if (tag != NULL) { |
| namespace__add_tag(&class->namespace, tag); |
| if (tag->tag == DW_TAG_subprogram) { |
| struct function *fself = tag__function(tag); |
| |
| if (fself->vtable_entry != -1) |
| class__add_vtable_entry(type__class(class), fself); |
| } |
| } |
| continue; |
| } |
| } |
| } while (dwarf_siblingof(die, die) == 0); |
| } |
| |
| static void die__process_namespace(Dwarf_Die *die, |
| struct namespace *namespace, struct cu *cu) |
| { |
| do { |
| struct tag *tag = die__process_tag(die, cu); |
| |
| if (tag != NULL) |
| namespace__add_tag(namespace, tag); |
| } while (dwarf_siblingof(die, die) == 0); |
| } |
| |
| static void die__process_function(Dwarf_Die *die, struct ftype *ftype, |
| struct lexblock *lexblock, struct cu *cu); |
| |
| static void die__create_new_lexblock(Dwarf_Die *die, |
| struct cu *cu, struct lexblock *father) |
| { |
| struct lexblock *lexblock = lexblock__new(die); |
| |
| if (lexblock == NULL) |
| oom("lexblock__new"); |
| die__process_function(die, NULL, lexblock, cu); |
| lexblock__add_lexblock(father, lexblock); |
| } |
| |
| static void die__create_new_inline_expansion(Dwarf_Die *die, |
| struct lexblock *lexblock) |
| { |
| struct inline_expansion *exp = inline_expansion__new(die); |
| |
| if (exp == NULL) |
| oom("inline_expansion__new"); |
| |
| lexblock__add_inline_expansion(lexblock, exp); |
| } |
| |
| static void die__process_function(Dwarf_Die *die, struct ftype *ftype, |
| struct lexblock *lexblock, struct cu *cu) |
| { |
| Dwarf_Die child; |
| |
| if (!dwarf_haschildren(die) || dwarf_child(die, &child) != 0) |
| return; |
| |
| die = &child; |
| do { |
| switch (dwarf_tag(die)) { |
| case DW_TAG_formal_parameter: |
| die__create_new_parameter(die, ftype, lexblock); |
| continue; |
| case DW_TAG_variable: { |
| struct tag *tag = die__create_new_variable(die); |
| lexblock__add_variable(lexblock, tag__variable(tag)); |
| } |
| continue; |
| case DW_TAG_unspecified_parameters: |
| if (ftype != NULL) |
| ftype->unspec_parms = 1; |
| continue; |
| case DW_TAG_label: |
| die__create_new_label(die, lexblock); |
| continue; |
| case DW_TAG_inlined_subroutine: |
| die__create_new_inline_expansion(die, lexblock); |
| continue; |
| case DW_TAG_lexical_block: |
| die__create_new_lexblock(die, cu, lexblock); |
| continue; |
| default: { |
| struct tag *tag = die__process_tag(die, cu); |
| if (tag != NULL) |
| cu__add_tag(cu, tag); |
| } |
| } |
| } while (dwarf_siblingof(die, die) == 0); |
| } |
| |
| static struct tag *die__create_new_function(Dwarf_Die *die, struct cu *cu) |
| { |
| struct function *function = function__new(die); |
| |
| if (function == NULL) |
| oom("function__new"); |
| die__process_function(die, &function->proto, &function->lexblock, cu); |
| return &function->proto.tag; |
| } |
| |
| static struct tag *__die__process_tag(Dwarf_Die *die, struct cu *cu, |
| const char *fn) |
| { |
| switch (dwarf_tag(die)) { |
| case DW_TAG_array_type: |
| return die__create_new_array(die); |
| case DW_TAG_base_type: |
| return die__create_new_base_type(die); |
| case DW_TAG_const_type: |
| case DW_TAG_imported_declaration: |
| case DW_TAG_imported_module: |
| case DW_TAG_pointer_type: |
| case DW_TAG_reference_type: |
| case DW_TAG_volatile_type: |
| return die__create_new_tag(die); |
| case DW_TAG_enumeration_type: |
| return die__create_new_enumeration(die); |
| case DW_TAG_namespace: |
| return die__create_new_namespace(die, cu); |
| case DW_TAG_structure_type: |
| return die__create_new_class(die, cu); |
| case DW_TAG_subprogram: |
| return die__create_new_function(die, cu); |
| case DW_TAG_subroutine_type: |
| return die__create_new_subroutine_type(die); |
| case DW_TAG_typedef: |
| return die__create_new_typedef(die); |
| case DW_TAG_union_type: |
| return die__create_new_union(die, cu); |
| case DW_TAG_variable: |
| return die__create_new_variable(die); |
| default: |
| __cu__tag_not_handled(die, fn); |
| } |
| |
| return NULL; |
| } |
| |
| static void die__process_unit(Dwarf_Die *die, struct cu *cu) |
| { |
| do { |
| struct tag *tag = die__process_tag(die, cu); |
| if (tag != NULL) |
| cu__add_tag(cu, tag); |
| } while (dwarf_siblingof(die, die) == 0); |
| } |
| |
| static void die__process(Dwarf_Die *die, struct cu *cu) |
| { |
| Dwarf_Die child; |
| const uint16_t tag = dwarf_tag(die); |
| |
| if (tag != DW_TAG_compile_unit) { |
| fprintf(stderr, "%s: DW_TAG_compile_unit expected got %s!\n", |
| __FUNCTION__, dwarf_tag_name(tag)); |
| return; |
| } |
| |
| cu->language = attr_numeric(die, DW_AT_language); |
| |
| if (dwarf_child(die, &child) == 0) |
| die__process_unit(&child, cu); |
| |
| if (dwarf_siblingof(die, die) == 0) |
| fprintf(stderr, "%s: got %s unexpected tag after " |
| "DW_TAG_compile_unit!\n", |
| __FUNCTION__, dwarf_tag_name(tag)); |
| } |
| |
| int cus__load_dir(struct cus *self, const char *dirname, |
| const char *filename_mask, const int recursive) |
| { |
| struct dirent *entry; |
| int err = -1; |
| DIR *dir = opendir(dirname); |
| |
| if (dir == NULL) |
| goto out; |
| |
| err = 0; |
| while ((entry = readdir(dir)) != NULL) { |
| char pathname[PATH_MAX]; |
| struct stat st; |
| |
| if (strcmp(entry->d_name, ".") == 0 || |
| strcmp(entry->d_name, "..") == 0) |
| continue; |
| |
| snprintf(pathname, sizeof(pathname), "%s/%s", |
| dirname, entry->d_name); |
| |
| err = lstat(pathname, &st); |
| if (err != 0) |
| break; |
| |
| if (S_ISDIR(st.st_mode)) { |
| if (!recursive) |
| continue; |
| |
| err = cus__load_dir(self, pathname, |
| filename_mask, recursive); |
| if (err != 0) |
| break; |
| } else if (fnmatch(filename_mask, entry->d_name, 0) == 0) { |
| err = cus__load(self, pathname); |
| if (err != 0) |
| break; |
| } |
| } |
| |
| if (err == -1) |
| puts(dirname); |
| closedir(dir); |
| out: |
| return err; |
| } |
| |
| int cus__load(struct cus *self, const char *filename) |
| { |
| Dwarf_Off offset, last_offset, abbrev_offset; |
| uint8_t addr_size, offset_size; |
| size_t hdr_size; |
| Dwarf *dwarf; |
| int fd = open(filename, O_RDONLY); |
| int err; |
| |
| if (fd < 0) { |
| err = errno; |
| goto out; |
| } |
| |
| err = -EINVAL; |
| dwarf = dwarf_begin(fd, DWARF_C_READ); |
| if (dwarf == NULL) |
| goto out_close; |
| |
| offset = last_offset = 0; |
| while (dwarf_nextcu(dwarf, offset, &offset, &hdr_size, |
| &abbrev_offset, &addr_size, &offset_size) == 0) { |
| Dwarf_Die die; |
| |
| if (dwarf_offdie(dwarf, last_offset + hdr_size, &die) != NULL) { |
| struct cu *cu = cu__new(attr_string(&die, DW_AT_name), |
| addr_size, NULL, 0); |
| if (cu == NULL) |
| oom("cu__new"); |
| die__process(&die, cu); |
| cus__add(self, cu); |
| } |
| |
| last_offset = offset; |
| } |
| |
| dwarf_end(dwarf); |
| err = 0; |
| out_close: |
| close(fd); |
| out: |
| return err; |
| } |
| |
| static int with_executable_option(int argc, char *argv[]) |
| { |
| while (--argc != 0) |
| if (strcmp(argv[argc], "--help") == 0 || |
| strcmp(argv[argc], "-?") == 0 || |
| strcmp(argv[argc], "-h") == 0 || |
| strcmp(argv[argc], "--usage") == 0 || |
| strcmp(argv[argc], "--executable") == 0 || |
| (argv[argc][0] == '-' && argv[argc][1] != '-' && |
| strchr(argv[argc] + 1, 'e') != NULL)) |
| return 1; |
| return 0; |
| } |
| |
| static int cus__load_module(Dwfl_Module *mod, void **userdata __unused, |
| const char *name __unused, Dwarf_Addr base __unused, |
| Dwarf *dw, Dwarf_Addr bias __unused, void *self) |
| { |
| Dwarf_Off off = 0, noff; |
| size_t cuhl; |
| GElf_Addr vaddr; |
| const unsigned char *build_id = NULL; |
| /* |
| * FIXME: check how to do this properly using cmake to test for |
| * the existence of dwfl_module_build_id in the elfutils libraries. |
| */ |
| #if 1 |
| int build_id_len = dwfl_module_build_id(mod, &build_id, &vaddr); |
| #else |
| int build_id_len = 0; |
| #endif |
| while (dwarf_nextcu(dw, off, &noff, &cuhl, NULL, NULL, NULL) == 0) { |
| Dwarf_Die die_mem, tmp; |
| Dwarf_Die *cu_die = dwarf_offdie(dw, off + cuhl, &die_mem); |
| struct cu *cu; |
| uint8_t pointer_size, offset_size; |
| |
| dwarf_diecu(cu_die, &tmp, &pointer_size, &offset_size); |
| |
| cu = cu__new(attr_string(cu_die, DW_AT_name), pointer_size, |
| build_id, build_id_len); |
| if (cu == NULL) |
| oom("cu__new"); |
| die__process(cu_die, cu); |
| cus__add(self, cu); |
| off = noff; |
| } |
| |
| return DWARF_CB_OK; |
| } |
| |
| int cus__loadfl(struct cus *self, struct argp *argp, int argc, char *argv[]) |
| { |
| Dwfl *dwfl = NULL; |
| char **new_argv = NULL; |
| ptrdiff_t offset; |
| int err = -1; |
| |
| if (argc == 1) { |
| argp_help(argp ? : dwfl_standard_argp(), stderr, |
| ARGP_HELP_SEE, argv[0]); |
| return -1; |
| } |
| |
| if (!with_executable_option(argc, argv)) { |
| new_argv = malloc((argc + 2) * sizeof(char *)); |
| if (new_argv == NULL) { |
| fprintf(stderr, "%s: not enough memory!\n", __func__); |
| return -1; |
| } |
| memcpy(new_argv, argv, (argc - 1) * sizeof(char *)); |
| new_argv[argc - 1] = "-e"; |
| new_argv[argc] = argv[argc - 1]; |
| new_argv[argc + 1] = NULL; |
| argv = new_argv; |
| argc++; |
| } |
| |
| if (argp != NULL) { |
| const struct argp_child argp_children[] = { |
| { .argp = dwfl_standard_argp(), }, |
| { .argp = NULL } |
| }; |
| argp->children = argp_children; |
| argp_parse(argp, argc, argv, 0, NULL, &dwfl); |
| } else |
| argp_parse(dwfl_standard_argp(), argc, argv, 0, NULL, &dwfl); |
| |
| if (dwfl == NULL) |
| goto out; |
| |
| offset = 0; |
| do { |
| offset = dwfl_getdwarf(dwfl, cus__load_module, self, offset); |
| } while (offset > 0); |
| |
| dwfl_end(dwfl); |
| err = 0; |
| out: |
| free(new_argv); |
| return err; |
| } |
| |
| void cus__print_error_msg(const char *progname, const char *filename, |
| const int err) |
| { |
| if (err == -EINVAL) |
| fprintf(stderr, "%s: couldn't load DWARF info from %s\n", |
| progname, filename); |
| else |
| fprintf(stderr, "%s: %s\n", progname, strerror(err)); |
| } |
| |
| struct cus *cus__new(struct list_head *definitions, |
| struct list_head *fwd_decls) |
| { |
| struct cus *self = malloc(sizeof(*self)); |
| |
| if (self != NULL) { |
| INIT_LIST_HEAD(&self->cus); |
| INIT_LIST_HEAD(&self->priv_definitions); |
| INIT_LIST_HEAD(&self->priv_fwd_decls); |
| self->definitions = definitions ?: &self->priv_definitions; |
| self->fwd_decls = fwd_decls ?: &self->priv_fwd_decls; |
| } |
| |
| return self; |
| } |
| |
| void dwarves__init(size_t user_cacheline_size) |
| { |
| if (user_cacheline_size == 0) { |
| long sys_cacheline_size = sysconf(_SC_LEVEL1_DCACHE_LINESIZE); |
| |
| if (sys_cacheline_size > 0) |
| cacheline_size = sys_cacheline_size; |
| else |
| cacheline_size = 64; /* Fall back to a sane value */ |
| } else |
| cacheline_size = user_cacheline_size; |
| } |