3745 lines
94 KiB
C
3745 lines
94 KiB
C
/*
|
|
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.
|
|
*/
|
|
|
|
#define _GNU_SOURCE
|
|
#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"
|
|
|
|
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 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 Dwarf_Off attr_offset(Dwarf_Die *die)
|
|
{
|
|
Dwarf_Attribute attr;
|
|
|
|
if (dwarf_attr(die, DW_AT_data_member_location, &attr) != NULL) {
|
|
Dwarf_Block block;
|
|
|
|
if (dwarf_formblock(&attr, &block) == 0) {
|
|
uint64_t uleb;
|
|
const uint8_t *data = block.data + 1;
|
|
get_uleb128(uleb, data);
|
|
return uleb;
|
|
}
|
|
}
|
|
|
|
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);
|
|
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;
|
|
}
|
|
|
|
static struct tag *tag__new(Dwarf_Die *die)
|
|
{
|
|
struct tag *self = malloc(sizeof(*self));
|
|
|
|
if (self != NULL)
|
|
tag__init(self, die);
|
|
|
|
return self;
|
|
}
|
|
|
|
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__type_not_found(const struct tag *self, const char *fn)
|
|
{
|
|
fprintf(stderr, "%s: %#llx type not found for %s (id=%#llx)\n",
|
|
fn, (unsigned long long)self->type, dwarf_tag_name(self->tag),
|
|
(unsigned long long)self->id);
|
|
fflush(stderr);
|
|
}
|
|
|
|
#define tag__type_not_found(self) __tag__type_not_found(self, __func__)
|
|
|
|
static 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 array_type__fprintf(const struct tag *tag_self,
|
|
const struct cu *cu, const char *name,
|
|
int type_spacing, FILE *fp)
|
|
{
|
|
struct array_type *self = tag__array_type(tag_self);
|
|
char tbf[128];
|
|
int i;
|
|
size_t printed = fprintf(fp, "%-*s %s", type_spacing,
|
|
tag__name(tag_self, cu, tbf, sizeof(tbf)),
|
|
name);
|
|
for (i = 0; i < self->dimensions; ++i)
|
|
printed += fprintf(fp, "[%u]", self->nr_entries[i]);
|
|
return printed;
|
|
}
|
|
|
|
static void type__init(struct type *self, Dwarf_Die *die)
|
|
{
|
|
tag__init(&self->tag, die);
|
|
INIT_LIST_HEAD(&self->node);
|
|
INIT_LIST_HEAD(&self->members);
|
|
self->name = strings__add(attr_string(die, DW_AT_name));
|
|
self->size = attr_numeric(die, DW_AT_byte_size);
|
|
self->declaration = attr_numeric(die, DW_AT_declaration);
|
|
self->definition_emitted = 0;
|
|
self->fwd_decl_emitted = 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;
|
|
}
|
|
|
|
static size_t typedef__fprintf(const struct tag *tag_self, const struct cu *cu,
|
|
FILE *fp)
|
|
{
|
|
const struct type *self = tag__type(tag_self);
|
|
const struct tag *type = cu__find_tag_by_id(cu, tag_self->type);
|
|
const struct tag *ptr_type;
|
|
char bf[512];
|
|
int is_pointer = 0;
|
|
size_t printed;
|
|
|
|
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,
|
|
self->name, 0, 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,
|
|
self->name, 0, is_pointer, 0,
|
|
fp);
|
|
case DW_TAG_structure_type: {
|
|
const struct type *ctype = tag__type(type);
|
|
|
|
if (ctype->name != NULL)
|
|
return fprintf(fp, "typedef struct %s %s",
|
|
ctype->name, self->name);
|
|
}
|
|
}
|
|
|
|
return fprintf(fp, "typedef %s %s",
|
|
tag__name(type, cu, bf, sizeof(bf)), self->name);
|
|
}
|
|
|
|
static size_t enumeration__fprintf(const struct tag *tag_self,
|
|
const char *suffix, uint8_t indent,
|
|
FILE *fp)
|
|
{
|
|
const struct type *self = tag__type(tag_self);
|
|
struct enumerator *pos;
|
|
size_t printed = fprintf(fp, "enum%s%s {\n", self->name ? " " : "",
|
|
self->name ?: "");
|
|
|
|
if (indent >= sizeof(tabs))
|
|
indent = sizeof(tabs) - 1;
|
|
list_for_each_entry(pos, &self->members, tag.node)
|
|
printed += fprintf(fp, "%.*s\t%s = %u,\n", indent, tabs,
|
|
pos->name, pos->value);
|
|
|
|
return printed + fprintf(fp, "%.*s}%s%s", indent, tabs,
|
|
suffix ? " " : "", 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)
|
|
{
|
|
struct cu *self = malloc(sizeof(*self));
|
|
|
|
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;
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
static void cu__add_tag(struct cu *self, struct tag *tag)
|
|
{
|
|
list_add_tail(&tag->node, &self->tags);
|
|
}
|
|
|
|
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 "";
|
|
}
|
|
|
|
struct tag *cu__find_tag_by_id(const struct cu *self, const Dwarf_Off id)
|
|
{
|
|
struct tag *pos;
|
|
|
|
if (id == 0)
|
|
return NULL;
|
|
|
|
list_for_each_entry(pos, &self->tags, node)
|
|
if (pos->id == id)
|
|
return pos;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
struct tag *cu__find_first_typedef_of_type(const struct cu *self,
|
|
const Dwarf_Off type)
|
|
{
|
|
struct tag *pos;
|
|
|
|
if (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 (name == NULL)
|
|
return NULL;
|
|
|
|
list_for_each_entry(pos, &self->tags, node) {
|
|
if (pos->tag == DW_TAG_base_type &&
|
|
strcmp(tag__base_type(pos)->name, name) == 0)
|
|
return pos;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
struct tag *cu__find_struct_by_name(const struct cu *self, const char *name)
|
|
{
|
|
struct tag *pos;
|
|
|
|
if (name == NULL)
|
|
return NULL;
|
|
|
|
list_for_each_entry(pos, &self->tags, node) {
|
|
struct type *type;
|
|
|
|
if (pos->tag != DW_TAG_structure_type)
|
|
continue;
|
|
|
|
type = tag__type(pos);
|
|
if (!type->declaration &&
|
|
type->name != NULL &&
|
|
strcmp(type->name, name) == 0)
|
|
return pos;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
struct tag *cus__find_struct_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 *tag = cu__find_struct_by_name(pos, name);
|
|
|
|
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 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 type *cus__find_definition(const struct cus *self, const char *name)
|
|
{
|
|
struct type *pos;
|
|
|
|
if (name == NULL)
|
|
return NULL;
|
|
|
|
list_for_each_entry(pos, self->definitions, node)
|
|
if (pos->name != NULL && strcmp(pos->name, name) == 0)
|
|
return pos;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
struct type *cus__find_fwd_decl(const struct cus *self, const char *name)
|
|
{
|
|
struct type *pos;
|
|
|
|
list_for_each_entry(pos, self->fwd_decls, node)
|
|
if (strcmp(pos->name, name) == 0)
|
|
return pos;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void cus__add_definition(struct cus *self, struct type *type)
|
|
{
|
|
type->definition_emitted = 1;
|
|
if (!list_empty(&type->node))
|
|
list_del(&type->node);
|
|
list_add_tail(&type->node, self->definitions);
|
|
}
|
|
|
|
static void cus__add_fwd_decl(struct cus *self, struct type *type)
|
|
{
|
|
type->fwd_decl_emitted = 1;
|
|
if (list_empty(&type->node))
|
|
list_add_tail(&type->node, self->fwd_decls);
|
|
}
|
|
|
|
struct tag *cu__find_function_by_name(const struct cu *self, const char *name)
|
|
{
|
|
struct tag *pos;
|
|
struct function *fpos;
|
|
|
|
if (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;
|
|
|
|
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 *ftype__find_parm_by_id(const struct ftype *self,
|
|
const Dwarf_Off id)
|
|
{
|
|
struct tag *pos;
|
|
|
|
list_for_each_entry(pos, &self->parms, node)
|
|
if (pos->id == id)
|
|
return pos;
|
|
return 0;
|
|
}
|
|
|
|
static struct parameter *cu__find_parameter_by_id(const struct cu *self,
|
|
const Dwarf_Off id)
|
|
{
|
|
struct tag *pos;
|
|
|
|
list_for_each_entry(pos, &self->tags, node) {
|
|
/*
|
|
* There can't be any at the top level CU tags list,
|
|
* it'll be in the ftype->parms list or in the lexblock->tags
|
|
* list, see comment in die__create_new_parameter to see why
|
|
* the later is possible.
|
|
*/
|
|
if (pos->tag == DW_TAG_subprogram) {
|
|
struct function *fn = tag__function(pos);
|
|
struct tag *tag = ftype__find_parm_by_id(&fn->proto,
|
|
id);
|
|
if (tag != NULL) {
|
|
tag = lexblock__find_tag_by_id(&fn->lexblock,
|
|
id);
|
|
if (tag != NULL)
|
|
return tag__parameter(tag);
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
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:
|
|
strncpy(bf, tag__base_type(self)->name, 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),
|
|
tag__type(self)->name ?: "");
|
|
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);
|
|
self->bit_size = attr_numeric(die, DW_AT_bit_size);
|
|
self->bit_offset = attr_numeric(die, DW_AT_bit_offset);
|
|
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 parameter *alias =
|
|
cu__find_parameter_by_id(cu, self->abstract_origin);
|
|
if (alias == NULL) {
|
|
tag__type_not_found(&self->tag);
|
|
return NULL;
|
|
}
|
|
/* Now cache the result in this tag ->name field */
|
|
self->name = 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 parameter *alias =
|
|
cu__find_parameter_by_id(cu, self->abstract_origin);
|
|
if (alias == NULL) {
|
|
tag__type_not_found(&self->tag);
|
|
return 0;
|
|
}
|
|
/* Now cache the result in this tag ->name and type fields */
|
|
self->name = alias->name;
|
|
self->tag.type = 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(const struct type *self, const struct cu *cu,
|
|
const char *prefix, const char *suffix,
|
|
uint8_t expand_types, uint8_t indent,
|
|
size_t type_spacing, size_t name_spacing,
|
|
FILE *fp);
|
|
|
|
static size_t type__fprintf(struct tag *type, const char *name,
|
|
const struct cu *cu, uint8_t expand_types,
|
|
int indent, int type_spacing,
|
|
int name_spacing, FILE *fp)
|
|
{
|
|
char tbf[128];
|
|
struct type *ctype;
|
|
size_t printed = 0;
|
|
int typedef_expanded = 0;
|
|
|
|
if (type == NULL)
|
|
goto out_type_not_found;
|
|
|
|
if (expand_types) {
|
|
while (type->tag == DW_TAG_typedef) {
|
|
ctype = tag__type(type);
|
|
if (typedef_expanded)
|
|
printed += fprintf(fp, " -> %s", ctype->name);
|
|
else {
|
|
printed += fprintf(fp, "/* typedef %s", ctype->name);
|
|
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, " */ ");
|
|
}
|
|
|
|
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)
|
|
return (printed +
|
|
ftype__fprintf(tag__ftype(ptype), cu,
|
|
name, 0, 1, type_spacing,
|
|
fp));
|
|
}
|
|
break;
|
|
case DW_TAG_subroutine_type:
|
|
return printed + ftype__fprintf(tag__ftype(type), cu, name,
|
|
0, 0, type_spacing, fp);
|
|
case DW_TAG_array_type:
|
|
return printed + array_type__fprintf(type, cu, name,
|
|
type_spacing, fp);
|
|
case DW_TAG_structure_type:
|
|
ctype = tag__type(type);
|
|
|
|
if (ctype->name != NULL && !expand_types)
|
|
return fprintf(fp, "struct %-*s %s",
|
|
type_spacing - 7, ctype->name, name);
|
|
return (printed +
|
|
class__fprintf(tag__class(type), cu, NULL, name,
|
|
expand_types, indent,
|
|
type_spacing - 8, name_spacing, 0, fp));
|
|
case DW_TAG_union_type:
|
|
ctype = tag__type(type);
|
|
|
|
if (ctype->name != NULL && !expand_types)
|
|
return fprintf(fp, "union %-*s %s", type_spacing - 6,
|
|
ctype->name, name);
|
|
return printed + union__fprintf(ctype, cu, NULL, name,
|
|
expand_types, indent,
|
|
type_spacing - 8,
|
|
name_spacing, fp);
|
|
case DW_TAG_enumeration_type:
|
|
ctype = tag__type(type);
|
|
|
|
if (ctype->name != NULL)
|
|
return printed + fprintf(fp, "enum %-*s %s",
|
|
type_spacing - 5,
|
|
ctype->name, name);
|
|
return printed + enumeration__fprintf(type, name, indent, fp);
|
|
}
|
|
|
|
return printed + fprintf(fp, "%-*s %s", type_spacing,
|
|
tag__name(type, cu, tbf, sizeof(tbf)), name);
|
|
out_type_not_found:
|
|
return fprintf(fp, "%-*s %s", type_spacing, "<ERROR>", name);
|
|
}
|
|
|
|
static size_t struct_member__fprintf(struct class_member *self,
|
|
struct tag *type, const struct cu *cu,
|
|
uint8_t expand_types, int indent,
|
|
int type_spacing, int name_spacing,
|
|
FILE *fp)
|
|
{
|
|
int spacing;
|
|
const int size = tag__size(type, cu);
|
|
size_t printed = type__fprintf(type, self->name, cu, expand_types,
|
|
indent, type_spacing, name_spacing, fp);
|
|
|
|
if (self->bit_size != 0)
|
|
printed += fprintf(fp, ":%u;", self->bit_size);
|
|
else {
|
|
fputc(';', fp);
|
|
++printed;
|
|
}
|
|
|
|
if ((type->tag == DW_TAG_union_type ||
|
|
type->tag == DW_TAG_enumeration_type ||
|
|
type->tag == DW_TAG_structure_type) &&
|
|
/* Look if is a type defined inline */
|
|
tag__type(type)->name == NULL) {
|
|
/* Check if this is a anonymous union */
|
|
const int slen = self->name != NULL ?
|
|
(int)strlen(self->name) : -1;
|
|
return printed + fprintf(fp, "%*s/* %5u %5u */",
|
|
type_spacing + name_spacing - slen - 3,
|
|
" ", self->offset, size);
|
|
}
|
|
spacing = type_spacing + name_spacing - printed;
|
|
return printed + fprintf(fp, "%*s/* %5u %5u */",
|
|
spacing > 0 ? spacing : 0, " ",
|
|
self->offset, size);
|
|
}
|
|
|
|
static size_t union_member__fprintf(struct class_member *self,
|
|
struct tag *type, const struct cu *cu,
|
|
uint8_t expand_types, int indent,
|
|
int type_spacing, int name_spacing,
|
|
FILE *fp)
|
|
{
|
|
int spacing;
|
|
const size_t size = tag__size(type, cu);
|
|
size_t printed = type__fprintf(type, self->name, cu, expand_types,
|
|
indent, type_spacing, name_spacing, fp);
|
|
|
|
if ((type->tag == DW_TAG_union_type ||
|
|
type->tag == DW_TAG_enumeration_type ||
|
|
type->tag == DW_TAG_structure_type) &&
|
|
/* Look if is a type defined inline */
|
|
tag__type(type)->name == NULL) {
|
|
/* 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.
|
|
*/
|
|
return printed + fprintf(fp, ";%*s/* %11zd */",
|
|
type_spacing + name_spacing - slen - 3,
|
|
" ", size);
|
|
}
|
|
spacing = type_spacing + name_spacing - (printed + 1);
|
|
return printed + fprintf(fp, ";%*s/* %11zd */",
|
|
spacing > 0 ? spacing : 0, " ", size);
|
|
}
|
|
|
|
static size_t union__fprintf(const struct type *self, const struct cu *cu,
|
|
const char *prefix, const char *suffix,
|
|
uint8_t expand_types, uint8_t indent,
|
|
size_t type_spacing, size_t name_spacing,
|
|
FILE *fp)
|
|
{
|
|
struct class_member *pos;
|
|
size_t printed = 0;
|
|
|
|
if (indent >= sizeof(tabs))
|
|
indent = sizeof(tabs) - 1;
|
|
|
|
if (prefix != NULL)
|
|
printed += fprintf(fp, "%s ", prefix);
|
|
printed += fprintf(fp, "union%s%s {\n", self->name ? " " : "",
|
|
self->name ?: "");
|
|
list_for_each_entry(pos, &self->members, tag.node) {
|
|
struct tag *type = cu__find_tag_by_id(cu, pos->tag.type);
|
|
|
|
printed += fprintf(fp, "%.*s", indent + 1, tabs);
|
|
printed += union_member__fprintf(pos, type, cu, expand_types,
|
|
indent + 1, type_spacing,
|
|
name_spacing, fp);
|
|
fputc('\n', fp);
|
|
++printed;
|
|
}
|
|
|
|
return printed + fprintf(fp, "%.*s}%s%s", indent, tabs,
|
|
suffix ? " " : "", suffix ?: "");
|
|
}
|
|
|
|
static struct class *class__new(Dwarf_Die *die)
|
|
{
|
|
struct class *self = zalloc(sizeof(*self));
|
|
|
|
if (self != NULL)
|
|
type__init(&self->type, die);
|
|
|
|
return self;
|
|
}
|
|
|
|
void class__delete(struct class *self)
|
|
{
|
|
struct class_member *pos, *next;
|
|
|
|
list_for_each_entry_safe(pos, next, &self->type.members, tag.node)
|
|
class_member__delete(pos);
|
|
|
|
free(self);
|
|
}
|
|
|
|
static void type__add_member(struct type *self, struct class_member *member)
|
|
{
|
|
++self->nr_members;
|
|
list_add_tail(&member->tag.node, &self->members);
|
|
}
|
|
|
|
static int type__clone_members(struct type *self, const struct type *from)
|
|
{
|
|
struct class_member *pos;
|
|
|
|
self->nr_members = 0;
|
|
INIT_LIST_HEAD(&self->members);
|
|
|
|
list_for_each_entry(pos, &from->members, tag.node) {
|
|
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.name = strings__add(new_class_name);
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
static void enumeration__add(struct type *self, struct enumerator *enumerator)
|
|
{
|
|
++self->nr_members;
|
|
list_add_tail(&enumerator->tag.node, &self->members);
|
|
}
|
|
|
|
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->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);
|
|
}
|
|
|
|
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__type_not_found(&self->proto.tag);
|
|
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;
|
|
|
|
list_for_each_entry(pos, &self->parms, tag.node) {
|
|
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;
|
|
|
|
list_for_each_entry(pos, &self->type.members, tag.node)
|
|
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;
|
|
|
|
self->nr_holes = 0;
|
|
self->nr_bit_holes = 0;
|
|
|
|
list_for_each_entry(pos, &ctype->members, tag.node) {
|
|
if (last != NULL) {
|
|
const ssize_t cc_last_size = pos->offset - 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) {
|
|
last->bit_hole = (last_size * 8) -
|
|
bit_sum;
|
|
if (last->bit_hole != 0)
|
|
++self->nr_bit_holes;
|
|
|
|
bit_sum = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
struct class_member *type__find_member_by_name(const struct type *self,
|
|
const char *name)
|
|
{
|
|
if (name != NULL) {
|
|
struct class_member *pos;
|
|
list_for_each_entry(pos, &self->members, tag.node)
|
|
if (pos->name != NULL && strcmp(pos->name, name) == 0)
|
|
return pos;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
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 function__tag_fprintf(const struct tag *tag, const struct cu *cu,
|
|
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);
|
|
|
|
if (alias == NULL) {
|
|
tag__type_not_found(&exp->tag);
|
|
break;
|
|
}
|
|
printed = fprintf(fp, "%.*s", indent, tabs);
|
|
n = fprintf(fp, "%s(); /* low_pc=%#llx */",
|
|
function__name(alias, cu),
|
|
(unsigned long long)exp->low_pc);
|
|
c += n;
|
|
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:
|
|
return lexblock__fprintf(vtag, cu, indent, fp);
|
|
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,
|
|
uint16_t indent, FILE *fp)
|
|
{
|
|
struct tag *pos;
|
|
size_t printed;
|
|
|
|
if (indent >= sizeof(tabs))
|
|
indent = sizeof(tabs) - 1;
|
|
printed = fprintf(fp, "%.*s{\n", indent, tabs);
|
|
list_for_each_entry(pos, &self->tags, node)
|
|
printed += function__tag_fprintf(pos, cu, indent + 1, fp);
|
|
return printed + fprintf(fp, "%.*s}\n", indent, tabs);
|
|
}
|
|
|
|
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 parameter *pos;
|
|
struct tag *type = cu__find_tag_by_id(cu, self->tag.type);
|
|
int first_parm = 1;
|
|
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 ?
|
|
")" : "");
|
|
|
|
list_for_each_entry(pos, &self->parms, tag.node) {
|
|
const char *name;
|
|
|
|
if (!first_parm)
|
|
printed += fprintf(fp, ", ");
|
|
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__fprintf(const struct tag *tag_self,
|
|
const struct cu *cu, FILE *fp)
|
|
{
|
|
struct function *self = tag__function(tag_self);
|
|
|
|
return ftype__fprintf(&self->proto, cu, function__name(self, cu),
|
|
function__declared_inline(self), 0, 0, fp);
|
|
}
|
|
|
|
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, 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");
|
|
}
|
|
|
|
void class__subtract_offsets_from(struct class *self, const struct cu *cu,
|
|
struct class_member *from,
|
|
const uint16_t size)
|
|
{
|
|
struct class_member *member =
|
|
list_prepare_entry(from, &self->type.members, tag.node);
|
|
|
|
list_for_each_entry_continue(member, &self->type.members, tag.node)
|
|
member->offset -= size;
|
|
|
|
if (self->padding != 0) {
|
|
struct class_member *last_member =
|
|
list_entry(self->type.members.prev,
|
|
struct class_member, tag.node);
|
|
const size_t last_member_size =
|
|
class_member__size(last_member, cu);
|
|
const ssize_t new_padding =
|
|
(class__size(self) -
|
|
(last_member->offset + last_member_size));
|
|
if (new_padding > 0)
|
|
self->padding = new_padding;
|
|
else
|
|
self->padding = 0;
|
|
}
|
|
}
|
|
|
|
static struct class_member *
|
|
class__find_next_hole_of_size(struct class *class,
|
|
struct class_member *from,
|
|
const struct cu *cu, size_t size)
|
|
{
|
|
struct class_member *member =
|
|
list_prepare_entry(from, &class->type.members, tag.node);
|
|
struct class_member *bitfield_head = NULL;
|
|
|
|
list_for_each_entry_continue(member, &class->type.members, tag.node) {
|
|
if (member->bit_size != 0) {
|
|
if (bitfield_head == NULL)
|
|
bitfield_head = member;
|
|
} else
|
|
bitfield_head = NULL;
|
|
if (member->hole != 0) {
|
|
const size_t member_size = class_member__size(member, cu);
|
|
|
|
if (member_size != 0 && member_size <= size)
|
|
return bitfield_head ? : member;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static struct class_member *
|
|
class__find_last_member_of_size(struct class *class,
|
|
struct class_member *to,
|
|
const struct cu *cu, size_t size)
|
|
{
|
|
struct class_member *member;
|
|
|
|
list_for_each_entry_reverse(member, &class->type.members, tag.node) {
|
|
size_t member_size;
|
|
|
|
if (member == to)
|
|
break;
|
|
|
|
if (member->bit_size != 0) {
|
|
struct class_member *prev =
|
|
list_entry(member->tag.node.prev,
|
|
struct class_member,
|
|
tag.node);
|
|
if (member->bit_size != 0)
|
|
continue;
|
|
|
|
}
|
|
|
|
member_size = class_member__size(member, cu);
|
|
if (member_size != 0 && member_size <= size)
|
|
return member;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static struct class_member *
|
|
class__find_next_bit_hole_of_size(const struct class *class,
|
|
struct class_member *from,
|
|
size_t size)
|
|
{
|
|
struct class_member *member =
|
|
list_prepare_entry(from, &class->type.members, tag.node);
|
|
|
|
list_for_each_entry_continue(member, &class->type.members, tag.node)
|
|
if (member->bit_hole != 0 &&
|
|
member->bit_size <= size)
|
|
return member;
|
|
#if 0
|
|
/*
|
|
* FIXME: Handle the case where the bit padding is on the same bitfield
|
|
* that we're looking, i.e. we can't combine a bitfield with itself,
|
|
* perhaps we should tag bitfields with a sequential, clearly marking
|
|
* each of the bitfields in advance, so that all the algoriths that
|
|
* have to deal with bitfields, moving them around, demoting, etc, can
|
|
* be simplified.
|
|
*/
|
|
/*
|
|
* Now look if the last member is a one member bitfield,
|
|
* i.e. if we have bit_padding
|
|
*/
|
|
if (class->bit_padding != 0)
|
|
return list_entry(class->type.members.prev,
|
|
struct class_member, tag.node);
|
|
#endif
|
|
return NULL;
|
|
}
|
|
|
|
static void class__move_member(struct class *class, struct class_member *dest,
|
|
struct class_member *from, const struct cu *cu,
|
|
int from_padding, const int verbose, FILE *fp)
|
|
{
|
|
const size_t from_size = class_member__size(from, cu);
|
|
const size_t dest_size = class_member__size(dest, cu);
|
|
struct class_member *tail_from = from;
|
|
struct class_member *from_prev = list_entry(from->tag.node.prev,
|
|
struct class_member,
|
|
tag.node);
|
|
uint16_t orig_tail_from_hole = tail_from->hole;
|
|
const uint16_t orig_from_offset = from->offset;
|
|
/*
|
|
* Align 'from' after 'dest':
|
|
*/
|
|
const uint16_t offset = dest->hole % (from_size > cu->addr_size ?
|
|
cu->addr_size : from_size);
|
|
/*
|
|
* Set new 'from' offset, after 'dest->offset', aligned
|
|
*/
|
|
const uint16_t new_from_offset = dest->offset + dest_size + offset;
|
|
|
|
if (verbose)
|
|
fputs("/* Moving", fp);
|
|
|
|
if (from->bit_size != 0) {
|
|
struct class_member *pos =
|
|
list_prepare_entry(from, &class->type.members,
|
|
tag.node);
|
|
struct class_member *tmp;
|
|
uint8_t orig_tail_from_bit_hole;
|
|
LIST_HEAD(from_list);
|
|
|
|
if (verbose)
|
|
fprintf(fp, " bitfield('%s' ... ", from->name);
|
|
list_for_each_entry_safe_from(pos, tmp, &class->type.members,
|
|
tag.node) {
|
|
/*
|
|
* Have we reached the end of the bitfield?
|
|
*/
|
|
if (pos->offset != orig_from_offset)
|
|
break;
|
|
tail_from = pos;
|
|
orig_tail_from_hole = tail_from->hole;
|
|
orig_tail_from_bit_hole = tail_from->bit_hole;
|
|
pos->offset = new_from_offset;
|
|
pos->hole = 0;
|
|
pos->bit_hole = 0;
|
|
list_move_tail(&pos->tag.node, &from_list);
|
|
}
|
|
tail_from->bit_hole = orig_tail_from_bit_hole;
|
|
list_splice(&from_list, &dest->tag.node);
|
|
if (verbose)
|
|
fprintf(fp, "'%s')", tail_from->name);
|
|
} else {
|
|
if (verbose)
|
|
fprintf(fp, " '%s'", from->name);
|
|
/*
|
|
* Remove 'from' from the list
|
|
*/
|
|
list_del(&from->tag.node);
|
|
/*
|
|
* Add 'from' after 'dest':
|
|
*/
|
|
__list_add(&from->tag.node, &dest->tag.node,
|
|
dest->tag.node.next);
|
|
from->offset = new_from_offset;
|
|
}
|
|
|
|
if (verbose)
|
|
fprintf(fp, " from after '%s' to after '%s' */\n",
|
|
from_prev->name, dest->name);
|
|
|
|
if (from_padding) {
|
|
/*
|
|
* Check if we're eliminating the need for padding:
|
|
*/
|
|
if (orig_from_offset % cu->addr_size == 0) {
|
|
/*
|
|
* Good, no need for padding anymore:
|
|
*/
|
|
class->type.size -= from_size + class->padding;
|
|
class->padding = 0;
|
|
} else {
|
|
/*
|
|
* No, so just add from_size to the padding:
|
|
*/
|
|
class->padding += from_size;
|
|
if (verbose)
|
|
fprintf(fp, "/* adding %zd bytes from %s to "
|
|
"the padding */\n",
|
|
from_size, from->name);
|
|
}
|
|
} else {
|
|
/*
|
|
* See if we are adding a new hole that is bigger than
|
|
* sizeof(long), this may have problems with explicit alignment
|
|
* made by the programmer, perhaps we need A switch that allows
|
|
* us to avoid realignment, just using existing holes but
|
|
* keeping the existing alignment, anyway the programmer has to
|
|
* check the resulting rerganization before using it, and for
|
|
* automatic stuff such as the one that will be used for struct
|
|
* "views" in tools such as ctracer we are more interested in
|
|
* packing the subset as tightly as possible.
|
|
*/
|
|
if (orig_tail_from_hole + from_size >= cu->addr_size) {
|
|
class->type.size -= cu->addr_size;
|
|
class__subtract_offsets_from(class, cu, from_prev,
|
|
cu->addr_size);
|
|
} else {
|
|
/*
|
|
* Add the hole after 'from' + its size to the member
|
|
* before it:
|
|
*/
|
|
from_prev->hole += orig_tail_from_hole + from_size;
|
|
}
|
|
/*
|
|
* Check if we have eliminated a hole
|
|
*/
|
|
if (dest->hole == from_size)
|
|
class->nr_holes--;
|
|
}
|
|
|
|
tail_from->hole = dest->hole - (from_size + offset);
|
|
dest->hole = offset;
|
|
|
|
if (verbose > 1) {
|
|
class__find_holes(class, cu);
|
|
class__fprintf(class, cu, NULL, NULL, 0, 0, 26, 23, 1, fp);
|
|
fputc('\n', fp);
|
|
}
|
|
}
|
|
|
|
static void class__move_bit_member(struct class *class, const struct cu *cu,
|
|
struct class_member *dest,
|
|
struct class_member *from,
|
|
const int verbose, FILE *fp)
|
|
{
|
|
struct class_member *from_prev = list_entry(from->tag.node.prev,
|
|
struct class_member,
|
|
tag.node);
|
|
const uint8_t is_last_member = (from->tag.node.next ==
|
|
&class->type.members);
|
|
|
|
if (verbose)
|
|
fprintf(fp, "/* Moving '%s:%u' from after '%s' to "
|
|
"after '%s:%u' */\n",
|
|
from->name, from->bit_size, from_prev->name,
|
|
dest->name, dest->bit_size);
|
|
/*
|
|
* Remove 'from' from the list
|
|
*/
|
|
list_del(&from->tag.node);
|
|
/*
|
|
* Add from after dest:
|
|
*/
|
|
__list_add(&from->tag.node,
|
|
&dest->tag.node,
|
|
dest->tag.node.next);
|
|
|
|
/* Check if this was the last entry in the bitfield */
|
|
if (from_prev->bit_size == 0) {
|
|
size_t from_size = class_member__size(from, cu);
|
|
/*
|
|
* Are we shrinking the struct?
|
|
*/
|
|
if (from_size + from->hole >= cu->addr_size) {
|
|
class->type.size -= from_size + from->hole;
|
|
class__subtract_offsets_from(class, cu, from_prev,
|
|
from_size + from->hole);
|
|
} else if (is_last_member)
|
|
class->padding += from_size;
|
|
else
|
|
from_prev->hole += from_size + from->hole;
|
|
if (is_last_member) {
|
|
/*
|
|
* Now we don't have bit_padding anymore
|
|
*/
|
|
class->bit_padding = 0;
|
|
} else
|
|
class->nr_bit_holes--;
|
|
} else {
|
|
/*
|
|
* Add add the holes after from + its size to the member
|
|
* before it:
|
|
*/
|
|
from_prev->bit_hole += from->bit_hole + from->bit_size;
|
|
from_prev->hole = from->hole;
|
|
}
|
|
from->bit_hole = dest->bit_hole - from->bit_size;
|
|
/*
|
|
* Tricky, what are the rules for bitfield layouts on this arch?
|
|
* Assume its IA32
|
|
*/
|
|
from->bit_offset = dest->bit_offset + dest->bit_size;
|
|
/*
|
|
* Now both have the some offset:
|
|
*/
|
|
from->offset = dest->offset;
|
|
dest->bit_hole = 0;
|
|
from->hole = dest->hole;
|
|
dest->hole = 0;
|
|
if (verbose > 1) {
|
|
class__find_holes(class, cu);
|
|
class__fprintf(class, cu, NULL, NULL, 0, 0, 26, 23, 1, fp);
|
|
fputc('\n', fp);
|
|
}
|
|
}
|
|
|
|
static void class__demote_bitfield_members(struct class *class,
|
|
struct class_member *from,
|
|
struct class_member *to,
|
|
const struct base_type *old_type,
|
|
const struct base_type *new_type)
|
|
{
|
|
const uint8_t bit_diff = (old_type->size - new_type->size) * 8;
|
|
struct class_member *member =
|
|
list_prepare_entry(from, &class->type.members, tag.node);
|
|
|
|
list_for_each_entry_from(member, &class->type.members, tag.node) {
|
|
/*
|
|
* Assume IA32 bitfield layout
|
|
*/
|
|
member->bit_offset -= bit_diff;
|
|
member->tag.type = new_type->tag.id;
|
|
if (member == to)
|
|
break;
|
|
member->bit_hole = 0;
|
|
}
|
|
}
|
|
|
|
static struct tag *cu__find_base_type_of_size(const struct cu *cu,
|
|
const size_t size)
|
|
{
|
|
const char *type_name;
|
|
|
|
switch (size) {
|
|
case sizeof(unsigned char):
|
|
type_name = "unsigned char"; break;
|
|
case sizeof(unsigned short int):
|
|
type_name = "short unsigned int"; break;
|
|
case sizeof(unsigned int):
|
|
type_name = "unsigned int"; break;
|
|
case sizeof(unsigned long long):
|
|
if (cu->addr_size == 8)
|
|
type_name = "long unsigned int";
|
|
else
|
|
type_name = "long long unsigned int";
|
|
break;
|
|
default:
|
|
return NULL;
|
|
}
|
|
|
|
return cu__find_base_type_by_name(cu, type_name);
|
|
}
|
|
|
|
static int class__demote_bitfields(struct class *class, const struct cu *cu,
|
|
const int verbose, FILE *fp)
|
|
{
|
|
struct class_member *member;
|
|
struct class_member *bitfield_head = NULL;
|
|
const struct tag *old_type_tag, *new_type_tag;
|
|
size_t current_bitfield_size = 0, size, bytes_needed, new_size;
|
|
int some_was_demoted = 0;
|
|
|
|
list_for_each_entry(member, &class->type.members, tag.node) {
|
|
/*
|
|
* Check if we are moving away from a bitfield
|
|
*/
|
|
if (member->bit_size == 0) {
|
|
current_bitfield_size = 0;
|
|
bitfield_head = NULL;
|
|
} else {
|
|
if (bitfield_head == NULL) {
|
|
bitfield_head = member;
|
|
current_bitfield_size = member->bit_size;
|
|
} else if (bitfield_head->offset != member->offset) {
|
|
/*
|
|
* We moved from one bitfield to another, for
|
|
* now don't handle this case, just move on to
|
|
* the next bitfield, we may well move it to
|
|
* another place and then the first bitfield will
|
|
* be isolated and will be handled in the next
|
|
* pass.
|
|
*/
|
|
bitfield_head = member;
|
|
current_bitfield_size = member->bit_size;
|
|
} else
|
|
current_bitfield_size += member->bit_size;
|
|
}
|
|
|
|
/*
|
|
* Have we got to the end of a bitfield with holes?
|
|
*/
|
|
if (member->bit_hole == 0)
|
|
continue;
|
|
|
|
size = class_member__size(member, cu);
|
|
bytes_needed = (current_bitfield_size + 7) / 8;
|
|
if (bytes_needed == size)
|
|
continue;
|
|
|
|
old_type_tag = cu__find_tag_by_id(cu, member->tag.type);
|
|
new_type_tag = cu__find_base_type_of_size(cu, bytes_needed);
|
|
if (verbose)
|
|
fprintf(fp, "/* Demoting bitfield ('%s' ... '%s') "
|
|
"from '%s' to '%s' */\n",
|
|
bitfield_head->name, member->name,
|
|
tag__base_type(old_type_tag)->name,
|
|
tag__base_type(new_type_tag)->name);
|
|
|
|
class__demote_bitfield_members(class,
|
|
bitfield_head, member,
|
|
tag__base_type(old_type_tag),
|
|
tag__base_type(new_type_tag));
|
|
new_size = class_member__size(member, cu);
|
|
member->hole = size - new_size;
|
|
if (member->hole != 0)
|
|
++class->nr_holes;
|
|
member->bit_hole = new_size * 8 - current_bitfield_size;
|
|
some_was_demoted = 1;
|
|
/*
|
|
* Have we packed it so that there are no hole now?
|
|
*/
|
|
if (member->bit_hole == 0)
|
|
--class->nr_bit_holes;
|
|
if (verbose > 1) {
|
|
class__find_holes(class, cu);
|
|
class__fprintf(class, cu, NULL, NULL, 0, 0,
|
|
26, 23, 1, fp);
|
|
fputc('\n', fp);
|
|
}
|
|
}
|
|
/*
|
|
* Now look if we have bit padding, i.e. if the the last member
|
|
* is a bitfield and its the sole member in this bitfield, i.e.
|
|
* if it wasn't already demoted as part of a bitfield of more than
|
|
* one member:
|
|
*/
|
|
member = list_entry(class->type.members.prev,
|
|
struct class_member, tag.node);
|
|
if (class->bit_padding != 0 && bitfield_head == member) {
|
|
size = class_member__size(member, cu);
|
|
bytes_needed = (member->bit_size + 7) / 8;
|
|
if (bytes_needed < size) {
|
|
old_type_tag =
|
|
cu__find_tag_by_id(cu, member->tag.type);
|
|
new_type_tag =
|
|
cu__find_base_type_of_size(cu, bytes_needed);
|
|
|
|
if (verbose)
|
|
fprintf(fp, "/* Demoting bitfield ('%s') "
|
|
"from '%s' to '%s' */\n",
|
|
member->name,
|
|
tag__base_type(old_type_tag)->name,
|
|
tag__base_type(new_type_tag)->name);
|
|
class__demote_bitfield_members(class,
|
|
member, member,
|
|
tag__base_type(old_type_tag),
|
|
tag__base_type(new_type_tag));
|
|
new_size = class_member__size(member, cu);
|
|
member->hole = 0;
|
|
/*
|
|
* Do we need byte padding?
|
|
*/
|
|
if (member->offset + new_size < class__size(class)) {
|
|
class->padding = (class__size(class) -
|
|
(member->offset + new_size));
|
|
class->bit_padding = 0;
|
|
member->bit_hole = (new_size * 8 -
|
|
member->bit_size);
|
|
} else {
|
|
class->padding = 0;
|
|
class->bit_padding = (new_size * 8 -
|
|
member->bit_size);
|
|
member->bit_hole = 0;
|
|
}
|
|
some_was_demoted = 1;
|
|
if (verbose > 1) {
|
|
class__find_holes(class, cu);
|
|
class__fprintf(class, cu, NULL, NULL, 0, 0,
|
|
26, 23, 1, fp);
|
|
fputc('\n', fp);
|
|
}
|
|
}
|
|
}
|
|
|
|
return some_was_demoted;
|
|
}
|
|
|
|
static void class__reorganize_bitfields(struct class *class,
|
|
const struct cu *cu,
|
|
const int verbose, FILE *fp)
|
|
{
|
|
struct class_member *member, *brother;
|
|
restart:
|
|
list_for_each_entry(member, &class->type.members, tag.node) {
|
|
/* See if we have a hole after this member */
|
|
if (member->bit_hole != 0) {
|
|
/*
|
|
* OK, try to find a member that has a bit hole after
|
|
* it and that has a size that fits the current hole:
|
|
*/
|
|
brother =
|
|
class__find_next_bit_hole_of_size(class, member,
|
|
member->bit_hole);
|
|
if (brother != NULL) {
|
|
class__move_bit_member(class, cu,
|
|
member, brother,
|
|
verbose, fp);
|
|
goto restart;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void class__fixup_bitfield_types(const struct class *self,
|
|
struct class_member *from,
|
|
struct class_member *to_before,
|
|
Dwarf_Off type)
|
|
{
|
|
struct class_member *member =
|
|
list_prepare_entry(from, &self->type.members, tag.node);
|
|
|
|
list_for_each_entry_from(member, &self->type.members, tag.node) {
|
|
if (member == to_before)
|
|
break;
|
|
member->tag.type = type;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Think about this pahole output a bit:
|
|
*
|
|
* [filo examples]$ pahole swiss_cheese cheese
|
|
* / * <11b> /home/acme/git/pahole/examples/swiss_cheese.c:3 * /
|
|
* struct cheese {
|
|
* <SNIP>
|
|
* int bitfield1:1; / * 64 4 * /
|
|
* int bitfield2:1; / * 64 4 * /
|
|
*
|
|
* / * XXX 14 bits hole, try to pack * /
|
|
* / * Bitfield WARNING: DWARF size=4, real size=2 * /
|
|
*
|
|
* short int d; / * 66 2 * /
|
|
* <SNIP>
|
|
*
|
|
* The compiler (gcc 4.1.1 20070105 (Red Hat 4.1.1-51) in the above example),
|
|
* Decided to combine what was declared as an int (4 bytes) bitfield but doesn't
|
|
* uses even one byte with the next field, that is a short int (2 bytes),
|
|
* without demoting the type of the bitfield to short int (2 bytes), so in terms
|
|
* of alignment the real size is 2, not 4, to make things easier for the rest of
|
|
* the reorganizing routines we just do the demotion ourselves, fixing up the
|
|
* sizes.
|
|
*/
|
|
static void class__fixup_member_types(struct class *self, const struct cu *cu,
|
|
const uint8_t verbose, FILE *fp)
|
|
{
|
|
struct class_member *pos, *bitfield_head = NULL;
|
|
uint8_t fixup_was_done = 0;
|
|
|
|
list_for_each_entry(pos, &self->type.members, tag.node) {
|
|
/*
|
|
* Is this bitfield member?
|
|
*/
|
|
if (pos->bit_size != 0) {
|
|
/*
|
|
* The first entry in a bitfield?
|
|
*/
|
|
if (bitfield_head == NULL)
|
|
bitfield_head = pos;
|
|
continue;
|
|
}
|
|
/*
|
|
* OK, not a bitfield member, but have we just passed
|
|
* by a bitfield?
|
|
*/
|
|
if (bitfield_head != NULL) {
|
|
const uint16_t real_size = (pos->offset -
|
|
bitfield_head->offset);
|
|
const size_t size = class_member__size(bitfield_head,
|
|
cu);
|
|
if (real_size != size) {
|
|
struct tag *new_type_tag =
|
|
cu__find_base_type_of_size(cu,
|
|
real_size);
|
|
if (new_type_tag == NULL) {
|
|
fprintf(stderr, "pahole: couldn't find"
|
|
" a base_type of %d bytes!\n",
|
|
real_size);
|
|
continue;
|
|
}
|
|
class__fixup_bitfield_types(self,
|
|
bitfield_head, pos,
|
|
new_type_tag->id);
|
|
fixup_was_done = 1;
|
|
}
|
|
}
|
|
bitfield_head = NULL;
|
|
}
|
|
if (verbose && fixup_was_done) {
|
|
fprintf(fp, "/* bitfield types were fixed */\n");
|
|
if (verbose > 1) {
|
|
class__find_holes(self, cu);
|
|
class__fprintf(self, cu, NULL, NULL, 0, 0,
|
|
26, 23, 1, fp);
|
|
fputc('\n', fp);
|
|
}
|
|
}
|
|
}
|
|
|
|
struct class *class__reorganize(struct class *self, const struct cu *cu,
|
|
const int verbose, FILE *fp)
|
|
{
|
|
struct class_member *member, *brother, *last_member;
|
|
size_t last_member_size;
|
|
|
|
class__fixup_member_types(self, cu, verbose, fp);
|
|
|
|
while (class__demote_bitfields(self, cu, verbose, fp))
|
|
class__reorganize_bitfields(self, cu, verbose, fp);
|
|
|
|
/* Now try to combine holes */
|
|
restart:
|
|
class__find_holes(self, cu);
|
|
last_member = list_entry(self->type.members.prev,
|
|
struct class_member, tag.node);
|
|
last_member_size = class_member__size(last_member, cu);
|
|
|
|
list_for_each_entry(member, &self->type.members, tag.node) {
|
|
/* See if we have a hole after this member */
|
|
if (member->hole != 0) {
|
|
/*
|
|
* OK, try to find a member that has a hole after it
|
|
* and that has a size that fits the current hole:
|
|
*/
|
|
brother = class__find_next_hole_of_size(self, member,
|
|
cu,
|
|
member->hole);
|
|
if (brother != NULL) {
|
|
struct class_member *brother_prev =
|
|
list_entry(brother->tag.node.prev,
|
|
struct class_member,
|
|
tag.node);
|
|
/*
|
|
* If it the next member, avoid moving it closer,
|
|
* it could be a explicit alignment rule, like
|
|
* ____cacheline_aligned_in_smp in the Linux
|
|
* kernel.
|
|
*/
|
|
if (brother_prev != member) {
|
|
class__move_member(self, member,
|
|
brother, cu, 0,
|
|
verbose, fp);
|
|
goto restart;
|
|
}
|
|
}
|
|
/*
|
|
* OK, but is there padding? If so the last member
|
|
* has a hole, if we are not at the last member and
|
|
* it has a size that is smaller than the current hole
|
|
* we can move it after the current member, reducing
|
|
* the padding or eliminating it altogether.
|
|
*/
|
|
if (self->padding > 0 &&
|
|
member != last_member &&
|
|
last_member_size != 0 &&
|
|
last_member_size <= member->hole) {
|
|
class__move_member(self, member, last_member,
|
|
cu, 1, verbose, fp);
|
|
goto restart;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Now try to move members at the tail to after holes */
|
|
if (self->nr_holes == 0)
|
|
goto out;
|
|
|
|
list_for_each_entry(member, &self->type.members, tag.node) {
|
|
/* See if we have a hole after this member */
|
|
if (member->hole != 0) {
|
|
brother = class__find_last_member_of_size(self, member,
|
|
cu,
|
|
member->hole);
|
|
if (brother != NULL) {
|
|
struct class_member *brother_prev =
|
|
list_entry(brother->tag.node.prev,
|
|
struct class_member,
|
|
tag.node);
|
|
/*
|
|
* If it the next member, avoid moving it closer,
|
|
* it could be a explicit alignment rule, like
|
|
* ____cacheline_aligned_in_smp in the Linux
|
|
* kernel.
|
|
*/
|
|
if (brother_prev != member) {
|
|
class__move_member(self, member,
|
|
brother, cu, 0,
|
|
verbose, fp);
|
|
goto restart;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
out:
|
|
return self;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
size_t class__fprintf(const struct class *self, const struct cu *cu,
|
|
const char *prefix, const char *suffix,
|
|
uint8_t expand_types, uint8_t indent,
|
|
int type_spacing, int name_spacing,
|
|
int emit_stats, FILE *fp)
|
|
{
|
|
const struct type *tself = &self->type;
|
|
size_t last_size = 0, size;
|
|
size_t last_bit_size = 0;
|
|
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;
|
|
int last_offset = -1;
|
|
struct class_member *pos;
|
|
size_t printed = fprintf(fp, "%s%sstruct%s%s {\n",
|
|
prefix ?: "", prefix ? " " : "",
|
|
tself->name ? " " : "", tself->name ?: "");
|
|
|
|
if (indent >= sizeof(tabs))
|
|
indent = sizeof(tabs) - 1;
|
|
|
|
list_for_each_entry(pos, &tself->members, tag.node) {
|
|
struct tag *type;
|
|
|
|
if (pos->offset != last_offset)
|
|
printed +=
|
|
class__fprintf_cacheline_boundary(last_cacheline,
|
|
sum, sum_holes,
|
|
&newline,
|
|
&last_cacheline,
|
|
indent + 1, fp);
|
|
if (last_offset != -1) {
|
|
const ssize_t cc_last_size = pos->offset - last_offset;
|
|
|
|
if (pos->offset < last_offset) {
|
|
if (!newline++) {
|
|
fputc('\n', fp);
|
|
++printed;
|
|
}
|
|
printed += fprintf(fp, "%.*s/* WARNING: DWARF"
|
|
" offset=%zd, real offset="
|
|
"%zd */\n",
|
|
indent + 1, tabs,
|
|
pos->offset,
|
|
last_offset + last_size);
|
|
} else if (cc_last_size > 0 &&
|
|
(size_t)cc_last_size < last_size) {
|
|
if (!newline++) {
|
|
fputc('\n', fp);
|
|
++printed;
|
|
}
|
|
printed += fprintf(fp, "%.*s/* Bitfield "
|
|
"WARNING: DWARF size=%zd, "
|
|
"real size=%zd */\n",
|
|
indent + 1, tabs,
|
|
last_size, cc_last_size);
|
|
sum -= last_size - cc_last_size;
|
|
/*
|
|
* Confusing huh? think about this case then,
|
|
* should clarify:
|
|
*/
|
|
#if 0
|
|
struct foo {
|
|
int a:1; /* 0 4 */
|
|
|
|
/* XXX 7 bits hole, try to pack */
|
|
/* WARNING: DWARF size: 4, compiler size: 1 */
|
|
|
|
char b; /* 1 1 */
|
|
}; /* size: 4, cachelines: 1 */
|
|
/* bit holes: 1, sum bit holes: 7 bits */
|
|
/* padding: 2 */
|
|
/* last cacheline: 4 bytes */
|
|
#endif
|
|
/*
|
|
* Yeah, this could somehow be simplified,
|
|
* send me a patch 8-)
|
|
*/
|
|
}
|
|
}
|
|
|
|
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", indent + 1, tabs, pos->name);
|
|
continue;
|
|
}
|
|
|
|
size = tag__size(type, cu);
|
|
printed += fprintf(fp, "%.*s", indent + 1, tabs);
|
|
printed += struct_member__fprintf(pos, type, cu, expand_types,
|
|
indent + 1, type_spacing,
|
|
name_spacing, fp);
|
|
|
|
if (type->tag == DW_TAG_structure_type) {
|
|
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 */", indent + 1,
|
|
tabs, padding,
|
|
padding != 1 ? "s" : "");
|
|
}
|
|
}
|
|
|
|
if (pos->bit_hole != 0) {
|
|
if (!newline++) {
|
|
fputc('\n', fp);
|
|
++printed;
|
|
}
|
|
printed += fprintf(fp, "\n%.*s/* XXX %d bit%s hole, "
|
|
"try to pack */", indent + 1, tabs,
|
|
pos->bit_hole,
|
|
pos->bit_hole != 1 ? "s" : "");
|
|
sum_bit_holes += pos->bit_hole;
|
|
}
|
|
|
|
if (pos->hole > 0) {
|
|
if (!newline++) {
|
|
fputc('\n', fp);
|
|
++printed;
|
|
}
|
|
printed += fprintf(fp, "\n%.*s/* XXX %d byte%s hole, "
|
|
"try to pack */",
|
|
indent + 1, tabs, pos->hole,
|
|
pos->hole != 1 ? "s" : "");
|
|
sum_holes += pos->hole;
|
|
}
|
|
|
|
fputc('\n', fp);
|
|
++printed;
|
|
/*
|
|
* check for bitfields, accounting for only the biggest
|
|
* of the byte_size in the fields in each bitfield set.
|
|
*/
|
|
if (last_offset != pos->offset ||
|
|
pos->bit_size == 0 || last_bit_size == 0) {
|
|
last_size = size;
|
|
sum += last_size;
|
|
} else if (size > last_size) {
|
|
sum += size - last_size;
|
|
last_size = size;
|
|
}
|
|
|
|
last_offset = pos->offset;
|
|
last_bit_size = pos->bit_size;
|
|
}
|
|
|
|
printed += class__fprintf_cacheline_boundary(last_cacheline, sum,
|
|
sum_holes, &newline,
|
|
&last_cacheline,
|
|
indent + 1, fp);
|
|
printed += fprintf(fp, "%.*s}%s%s", indent, tabs, suffix ? " ": "",
|
|
suffix ?: "");
|
|
if (!emit_stats)
|
|
goto out;
|
|
|
|
printed += fprintf(fp, "; /* size: %zd, cachelines: %zd */\n",
|
|
tself->size, tag__nr_cachelines(class__tag(self),
|
|
cu));
|
|
if (sum_holes > 0)
|
|
printed += fprintf(fp, "%.*s /* sum members: %u, holes: %d, "
|
|
"sum holes: %u */\n", indent, tabs, sum,
|
|
self->nr_holes, sum_holes);
|
|
if (sum_bit_holes > 0)
|
|
printed += fprintf(fp, "%.*s /* bit holes: %d, sum bit "
|
|
"holes: %u bits */\n", indent, tabs,
|
|
self->nr_bit_holes, sum_bit_holes);
|
|
if (self->padding > 0)
|
|
printed += fprintf(fp, "%.*s /* padding: %u */\n", indent,
|
|
tabs, self->padding);
|
|
if (nr_paddings > 0)
|
|
printed += fprintf(fp, "%.*s /* paddings: %u, sum paddings: "
|
|
"%u */\n", indent, tabs, nr_paddings,
|
|
sum_paddings);
|
|
if (self->bit_padding > 0)
|
|
printed += fprintf(fp, "%.*s /* bit_padding: %u bits */\n",
|
|
indent, tabs, self->bit_padding);
|
|
last_cacheline = tself->size % cacheline_size;
|
|
if (last_cacheline != 0)
|
|
printed += fprintf(fp, "%.*s /* last cacheline: %u bytes "
|
|
"*/\n", indent, tabs, last_cacheline);
|
|
|
|
if (sum + sum_holes != tself->size - self->padding)
|
|
printed += fprintf(fp, "\n%.*s/* BRAIN FART ALERT! %zd != %u "
|
|
"+ %u(holes), diff = %zd */\n\n",
|
|
indent, tabs, tself->size, sum, sum_holes,
|
|
tself->size - (sum + sum_holes));
|
|
out:
|
|
return printed;
|
|
}
|
|
|
|
static size_t variable__fprintf(const struct tag *tag, const struct cu *cu,
|
|
uint8_t expand_types, 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, name, cu, expand_types,
|
|
0, 0, 0, fp);
|
|
}
|
|
}
|
|
return printed;
|
|
}
|
|
|
|
size_t tag__fprintf(const struct tag *self, const struct cu *cu,
|
|
const char *prefix, const char *suffix,
|
|
uint8_t expand_types, FILE *fp)
|
|
{
|
|
size_t printed = tag__fprintf_decl_info(self, fp);
|
|
|
|
switch (self->tag) {
|
|
case DW_TAG_enumeration_type:
|
|
printed += enumeration__fprintf(self, suffix, 0, fp);
|
|
break;
|
|
case DW_TAG_typedef:
|
|
printed += typedef__fprintf(self, cu, fp);
|
|
break;
|
|
case DW_TAG_structure_type:
|
|
printed += class__fprintf(tag__class(self), cu, prefix, suffix,
|
|
expand_types, 0, 26,
|
|
expand_types ? 55 : 23, 1, 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, prefix, suffix,
|
|
expand_types, 0, 26,
|
|
expand_types ? 55 : 21, fp);
|
|
break;
|
|
case DW_TAG_variable:
|
|
printed += variable__fprintf(self, cu, expand_types, fp);
|
|
break;
|
|
default:
|
|
printed += fprintf(fp, "%s: %s tag not supported!\n", __func__,
|
|
dwarf_tag_name(self->tag));
|
|
break;
|
|
}
|
|
|
|
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 void __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.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->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->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->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:
|
|
die__process_tag(die, cu);
|
|
continue;
|
|
}
|
|
} 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:
|
|
die__process_tag(die, cu);
|
|
}
|
|
} 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 void __die__process_tag(Dwarf_Die *die, struct cu *cu, const char *fn)
|
|
{
|
|
struct tag *new_tag = NULL;
|
|
|
|
switch (dwarf_tag(die)) {
|
|
case DW_TAG_array_type:
|
|
new_tag = die__create_new_array(die); break;
|
|
case DW_TAG_base_type:
|
|
new_tag = die__create_new_base_type(die); break;
|
|
case DW_TAG_const_type:
|
|
case DW_TAG_pointer_type:
|
|
case DW_TAG_reference_type:
|
|
case DW_TAG_volatile_type:
|
|
new_tag = die__create_new_tag(die); break;
|
|
case DW_TAG_enumeration_type:
|
|
new_tag = die__create_new_enumeration(die); break;
|
|
case DW_TAG_structure_type:
|
|
new_tag = die__create_new_class(die, cu); break;
|
|
case DW_TAG_subprogram:
|
|
new_tag = die__create_new_function(die, cu); break;
|
|
case DW_TAG_subroutine_type:
|
|
new_tag = die__create_new_subroutine_type(die); break;
|
|
case DW_TAG_typedef:
|
|
new_tag = die__create_new_typedef(die); break;
|
|
case DW_TAG_union_type:
|
|
new_tag = die__create_new_union(die, cu); break;
|
|
case DW_TAG_variable:
|
|
new_tag = die__create_new_variable(die); break;
|
|
default:
|
|
__cu__tag_not_handled(die, fn); return;
|
|
}
|
|
|
|
if (new_tag != NULL)
|
|
cu__add_tag(cu, new_tag);
|
|
}
|
|
|
|
static void die__process_unit(Dwarf_Die *die, struct cu *cu)
|
|
{
|
|
do {
|
|
die__process_tag(die, cu);
|
|
} 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);
|
|
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;
|
|
}
|
|
|
|
int cus__loadfl(struct cus *self, struct argp *argp, int argc, char *argv[],
|
|
int *remaining)
|
|
{
|
|
Dwfl *dwfl = NULL;
|
|
Dwarf_Die *cu_die = NULL;
|
|
Dwarf_Addr dwbias;
|
|
|
|
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, remaining, &dwfl);
|
|
} else
|
|
argp_parse(dwfl_standard_argp(), argc, argv, 0, remaining, &dwfl);
|
|
|
|
if (dwfl == NULL)
|
|
return -1;
|
|
|
|
while ((cu_die = dwfl_nextcu(dwfl, cu_die, &dwbias)) != NULL) {
|
|
Dwarf_Die tmp;
|
|
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);
|
|
if (cu == NULL)
|
|
oom("cu__new");
|
|
die__process(cu_die, cu);
|
|
cus__add(self, cu);
|
|
}
|
|
|
|
dwfl_end(dwfl);
|
|
return 0;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
static int cus__emit_enumeration_definitions(struct cus *self, struct tag *tag,
|
|
const char *suffix, FILE *fp)
|
|
{
|
|
struct type *etype = tag__type(tag);
|
|
|
|
/* Have we already emitted this in this CU? */
|
|
if (etype->definition_emitted)
|
|
return 0;
|
|
|
|
/* Ok, lets look at the previous CUs: */
|
|
if (cus__find_definition(self, etype->name) != NULL) {
|
|
/*
|
|
* Yes, so lets mark it visited on this CU too,
|
|
* to speed up the lookup.
|
|
*/
|
|
etype->definition_emitted = 1;
|
|
return 0;
|
|
}
|
|
|
|
enumeration__fprintf(tag, suffix, 0, fp);
|
|
fputs(";", fp);
|
|
cus__add_definition(self, etype);
|
|
return 1;
|
|
}
|
|
|
|
static int cus__emit_tag_definitions(struct cus *self, struct cu *cu,
|
|
struct tag *tag, FILE *fp);
|
|
|
|
static int cus__emit_typedef_definitions(struct cus *self, struct cu *cu,
|
|
struct tag *tdef, FILE *fp)
|
|
{
|
|
struct type *def = tag__type(tdef);
|
|
struct tag *type, *ptr_type;
|
|
int is_pointer = 0;
|
|
|
|
/* Have we already emitted this in this CU? */
|
|
if (def->definition_emitted)
|
|
return 0;
|
|
|
|
/* Ok, lets look at the previous CUs: */
|
|
if (cus__find_definition(self, def->name) != NULL) {
|
|
/*
|
|
* Yes, so lets mark it visited on this CU too,
|
|
* to speed up the lookup.
|
|
*/
|
|
def->definition_emitted = 1;
|
|
return 0;
|
|
}
|
|
|
|
type = cu__find_tag_by_id(cu, tdef->type);
|
|
|
|
switch (type->tag) {
|
|
case DW_TAG_array_type:
|
|
cus__emit_tag_definitions(self, cu, type, fp);
|
|
break;
|
|
case DW_TAG_typedef:
|
|
cus__emit_typedef_definitions(self, cu, type, fp);
|
|
break;
|
|
case DW_TAG_pointer_type:
|
|
ptr_type = cu__find_tag_by_id(cu, type->type);
|
|
if (ptr_type->tag != DW_TAG_subroutine_type)
|
|
break;
|
|
type = ptr_type;
|
|
is_pointer = 1;
|
|
/* Fall thru */
|
|
case DW_TAG_subroutine_type:
|
|
cus__emit_ftype_definitions(self, cu, tag__ftype(type), fp);
|
|
break;
|
|
case DW_TAG_enumeration_type: {
|
|
const struct type *ctype = tag__type(type);
|
|
|
|
tag__fprintf_decl_info(type, fp);
|
|
if (ctype->name == NULL) {
|
|
fputs("typedef ", fp);
|
|
cus__emit_enumeration_definitions(self, type,
|
|
def->name, fp);
|
|
goto out;
|
|
} else
|
|
cus__emit_enumeration_definitions(self, type,
|
|
NULL, fp);
|
|
}
|
|
break;
|
|
case DW_TAG_structure_type:
|
|
case DW_TAG_union_type: {
|
|
const struct type *ctype = tag__type(type);
|
|
|
|
if (ctype->name == NULL) {
|
|
if (cus__emit_type_definitions(self, cu, type, fp))
|
|
type__emit(type, cu, "typedef", def->name, fp);
|
|
goto out;
|
|
} else if (cus__emit_type_definitions(self, cu, type, fp))
|
|
type__emit(type, cu, NULL, NULL, fp);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Recheck if the typedef was emitted, as there are cases, like
|
|
* wait_queue_t in the Linux kernel, that is against struct
|
|
* __wait_queue, that has a wait_queue_func_t member, a function
|
|
* typedef that has as one of its parameters a... wait_queue_t, that
|
|
* will thus be emitted before the function typedef, making a no go to
|
|
* redefine the typedef after struct __wait_queue.
|
|
*/
|
|
if (!def->definition_emitted) {
|
|
typedef__fprintf(tdef, cu, fp);
|
|
fputs(";", fp);
|
|
}
|
|
out:
|
|
cus__add_definition(self, def);
|
|
return 1;
|
|
}
|
|
|
|
int cus__emit_fwd_decl(struct cus *self, struct type *ctype, FILE *fp)
|
|
{
|
|
/* Have we already emitted this in this CU? */
|
|
if (ctype->fwd_decl_emitted)
|
|
return 0;
|
|
|
|
/* Ok, lets look at the previous CUs: */
|
|
if (cus__find_fwd_decl(self, ctype->name) != NULL) {
|
|
/*
|
|
* Yes, so lets mark it visited on this CU too,
|
|
* to speed up the lookup.
|
|
*/
|
|
ctype->fwd_decl_emitted = 1;
|
|
return 0;
|
|
}
|
|
|
|
fprintf(fp, "%s %s;\n",
|
|
ctype->tag.tag == DW_TAG_union_type ? "union" : "struct",
|
|
ctype->name);
|
|
cus__add_fwd_decl(self, ctype);
|
|
return 1;
|
|
}
|
|
|
|
static int cus__emit_tag_definitions(struct cus *self, struct cu *cu,
|
|
struct tag *tag, FILE *fp)
|
|
{
|
|
struct tag *type = cu__find_tag_by_id(cu, tag->type);
|
|
int pointer = 0;
|
|
|
|
if (type == NULL)
|
|
return 0;
|
|
next_indirection:
|
|
switch (type->tag) {
|
|
case DW_TAG_pointer_type:
|
|
case DW_TAG_reference_type:
|
|
pointer = 1;
|
|
/* Fall thru */
|
|
case DW_TAG_array_type:
|
|
case DW_TAG_const_type:
|
|
case DW_TAG_volatile_type:
|
|
type = cu__find_tag_by_id(cu, type->type);
|
|
if (type == NULL)
|
|
return 0;
|
|
goto next_indirection;
|
|
case DW_TAG_typedef:
|
|
return cus__emit_typedef_definitions(self, cu, type, fp);
|
|
case DW_TAG_enumeration_type:
|
|
if (tag__type(type)->name != NULL) {
|
|
tag__fprintf_decl_info(type, fp);
|
|
return cus__emit_enumeration_definitions(self, type,
|
|
NULL, fp);
|
|
}
|
|
break;
|
|
case DW_TAG_structure_type:
|
|
case DW_TAG_union_type:
|
|
if (pointer)
|
|
return cus__emit_fwd_decl(self, tag__type(type), fp);
|
|
if (cus__emit_type_definitions(self, cu, type, fp))
|
|
type__emit(type, cu, NULL, NULL, fp);
|
|
return 1;
|
|
case DW_TAG_subroutine_type:
|
|
return cus__emit_ftype_definitions(self, cu,
|
|
tag__ftype(type), fp);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int cus__emit_ftype_definitions(struct cus *self, struct cu *cu,
|
|
struct ftype *ftype, FILE *fp)
|
|
{
|
|
struct parameter *pos;
|
|
/* First check the function return type */
|
|
int printed = cus__emit_tag_definitions(self, cu, &ftype->tag, fp);
|
|
|
|
/* Then its parameters */
|
|
list_for_each_entry(pos, &ftype->parms, tag.node)
|
|
if (cus__emit_tag_definitions(self, cu, &pos->tag, fp))
|
|
printed = 1;
|
|
|
|
if (printed)
|
|
fputc('\n', fp);
|
|
return printed;
|
|
}
|
|
|
|
int cus__emit_type_definitions(struct cus *self, struct cu *cu,
|
|
struct tag *tag, FILE *fp)
|
|
{
|
|
struct type *ctype = tag__type(tag);
|
|
struct class_member *pos;
|
|
int printed = 0;
|
|
|
|
if (ctype->definition_emitted)
|
|
return 0;
|
|
|
|
/* Ok, lets look at the previous CUs: */
|
|
if (cus__find_definition(self, ctype->name) != NULL) {
|
|
ctype->definition_emitted = 1;
|
|
return 0;
|
|
}
|
|
|
|
cus__add_definition(self, ctype);
|
|
|
|
list_for_each_entry(pos, &ctype->members, tag.node)
|
|
if (cus__emit_tag_definitions(self, cu, &pos->tag, fp))
|
|
printed = 1;
|
|
|
|
if (printed)
|
|
fputc('\n', fp);
|
|
return 1;
|
|
}
|
|
|
|
void type__emit(struct tag *tag_self, struct cu *cu,
|
|
const char *prefix, const char *suffix, FILE *fp)
|
|
{
|
|
struct type *ctype = tag__type(tag_self);
|
|
|
|
if (tag_self->tag == DW_TAG_structure_type)
|
|
class__find_holes(tag__class(tag_self), cu);
|
|
|
|
if (ctype->name != NULL || suffix != NULL || prefix != NULL) {
|
|
tag__fprintf(tag_self, cu, prefix, suffix, 0, fp);
|
|
|
|
if (tag_self->tag != DW_TAG_structure_type)
|
|
fputc(';', fp);
|
|
fputc('\n', fp);
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|