8630ce4042
This patch fixed two issues with BTF. One is related to struct/union
bitfield encoding and the other is related to forward type.
Issue #1 and solution:
======================
Current btf encoding of bitfield follows what pahole generates.
For each bitfield, pahole will duplicate the type chain and
put the bitfield size at the final int or enum type.
Since the BTF enum type cannot encode bit size,
commit b18354f64c
("btf: Generate correct struct bitfield
member types") workarounds the issue by generating
an int type whenever the enum bit size is not 32.
The above workaround is not ideal as we lost original type
in BTF. Another undesiable fact is the type duplication
as the pahole duplicates the type chain.
To fix this issue, this patch implemented a compatible
change for BTF struct type encoding:
. the bit 31 of type->info, previously reserved,
now is used to indicate whether bitfield_size is
encoded in btf_member or not.
. if bit 31 of struct_type->info is set,
btf_member->offset will encode like:
bit 0 - 23: bit offset
bit 24 - 31: bitfield size
if bit 31 is not set, the old behavior is preserved:
bit 0 - 31: bit offset
So if the struct contains a bit field, the maximum bit offset
will be reduced to (2^24 - 1) instead of MAX_UINT. The maximum
bitfield size will be 255 which is enough for today as maximum
bitfield in compiler can be 128 where int128 type is supported.
A new global, no_bitfield_type_recode, is introduced and which
will be set to true if BTF encoding is enabled. This global
will prevent pahole duplicating the bitfield types to avoid
type duplication in BTF.
Issue #2 and solution:
======================
Current forward type in BTF does not specify whether the original
type is struct or union. This will not work for type pretty print
and BTF-to-header-file conversion as struct/union must be specified.
To fix this issue, similar to issue #1, type->info bit 31
is used. If the bit is set, it is union type. Otherwise, it is
a struct type.
Examples:
=========
-bash-4.4$ cat t.c
struct s;
union u;
typedef int ___int;
enum A { A1, A2, A3 };
struct t {
int a[5];
___int b:4;
volatile enum A c:4;
struct s *p1;
union u *p2;
} g;
-bash-4.4$ gcc -c -O2 -g t.c
Without this patch:
$ pahole -JV t.o
[1] TYPEDEF ___int type_id=2
[2] INT int size=4 bit_offset=0 nr_bits=32 encoding=SIGNED
[3] ENUM A size=4 vlen=3
A1 val=0
A2 val=1
A3 val=2
[4] STRUCT t size=40 vlen=5
a type_id=5 bits_offset=0
b type_id=13 bits_offset=160
c type_id=15 bits_offset=164
p1 type_id=9 bits_offset=192
p2 type_id=11 bits_offset=256
[5] ARRAY (anon) type_id=2 index_type_id=2 nr_elems=5
[6] INT sizetype size=8 bit_offset=0 nr_bits=64 encoding=(none)
[7] VOLATILE (anon) type_id=3
[8] FWD s type_id=0
[9] PTR (anon) type_id=8
[10] FWD u type_id=0
[11] PTR (anon) type_id=10
[12] INT int size=1 bit_offset=0 nr_bits=4 encoding=(none)
[13] TYPEDEF ___int type_id=12
[14] INT (anon) size=1 bit_offset=0 nr_bits=4 encoding=SIGNED
[15] VOLATILE (anon) type_id=14
With this patch:
$ pahole -JV t.o
File t.o:
[1] TYPEDEF ___int type_id=2
[2] INT int size=4 bit_offset=0 nr_bits=32 encoding=SIGNED
[3] ENUM A size=4 vlen=3
A1 val=0
A2 val=1
A3 val=2
[4] STRUCT t kind_flag=1 size=40 vlen=5
a type_id=5 bitfield_size=0 bits_offset=0
b type_id=1 bitfield_size=4 bits_offset=160
c type_id=7 bitfield_size=4 bits_offset=164
p1 type_id=9 bitfield_size=0 bits_offset=192
p2 type_id=11 bitfield_size=0 bits_offset=256
[5] ARRAY (anon) type_id=2 index_type_id=2 nr_elems=5
[6] INT sizetype size=8 bit_offset=0 nr_bits=64 encoding=(none)
[7] VOLATILE (anon) type_id=3
[8] FWD s struct
[9] PTR (anon) type_id=8
[10] FWD u union
[11] PTR (anon) type_id=10
The fix removed the type duplication, preserved the enum type for the
bitfield, and have correct struct/union information for the forward
type.
Signed-off-by: Yonghong Song <yhs@fb.com>
Acked-by: Martin KaFai Lau <kafai@fb.com>
Tested-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Cc: Alexei Starovoitov <ast@fb.com>
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
1295 lines
30 KiB
C
1295 lines
30 KiB
C
/*
|
|
Copyright (C) 2006 Mandriva Conectiva S.A.
|
|
Copyright (C) 2006 Arnaldo Carvalho de Melo <acme@mandriva.com>
|
|
Copyright (C) 2007-2008 Arnaldo Carvalho de Melo <acme@redhat.com>
|
|
|
|
This program is free software; you can redistribute it and/or modify it
|
|
under the terms of version 2 of the GNU General Public License as
|
|
published by the Free Software Foundation.
|
|
*/
|
|
|
|
#include <argp.h>
|
|
#include <assert.h>
|
|
#include <stdio.h>
|
|
#include <dwarf.h>
|
|
#include <search.h>
|
|
#include <stdarg.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "dwarves_reorganize.h"
|
|
#include "dwarves.h"
|
|
#include "dutil.h"
|
|
#include "ctf_encoder.h"
|
|
#include "btf_encoder.h"
|
|
|
|
static bool btf_encode;
|
|
static bool ctf_encode;
|
|
static bool first_obj_only;
|
|
|
|
static uint8_t class__include_anonymous;
|
|
static uint8_t class__include_nested_anonymous;
|
|
static uint8_t word_size, original_word_size;
|
|
|
|
static char *class__exclude_prefix;
|
|
static size_t class__exclude_prefix_len;
|
|
|
|
static char *class__include_prefix;
|
|
static size_t class__include_prefix_len;
|
|
|
|
static char *cu__exclude_prefix;
|
|
static size_t cu__exclude_prefix_len;
|
|
|
|
static char *decl_exclude_prefix;
|
|
static size_t decl_exclude_prefix_len;
|
|
|
|
static uint16_t nr_holes;
|
|
static uint16_t nr_bit_holes;
|
|
static uint16_t hole_size_ge;
|
|
static uint8_t show_packable;
|
|
static uint8_t global_verbose;
|
|
static uint8_t recursive;
|
|
static size_t cacheline_size;
|
|
static uint8_t find_containers;
|
|
static uint8_t find_pointers_in_structs;
|
|
static int reorganize;
|
|
static bool show_private_classes;
|
|
static bool defined_in;
|
|
static int show_reorg_steps;
|
|
static char *class_name;
|
|
static struct strlist *class_names;
|
|
static char separator = '\t';
|
|
|
|
static struct conf_fprintf conf = {
|
|
.emit_stats = 1,
|
|
};
|
|
|
|
static struct conf_load conf_load;
|
|
|
|
struct structure {
|
|
struct list_head node;
|
|
struct rb_node rb_node;
|
|
char *name;
|
|
uint32_t nr_files;
|
|
uint32_t nr_methods;
|
|
};
|
|
|
|
static struct structure *structure__new(const char *name)
|
|
{
|
|
struct structure *st = malloc(sizeof(*st));
|
|
|
|
if (st != NULL) {
|
|
st->name = strdup(name);
|
|
if (st->name == NULL) {
|
|
free(st);
|
|
return NULL;
|
|
}
|
|
st->nr_files = 1;
|
|
st->nr_methods = 0;
|
|
}
|
|
|
|
return st;
|
|
}
|
|
|
|
static void structure__delete(struct structure *st)
|
|
{
|
|
free(st->name);
|
|
free(st);
|
|
}
|
|
|
|
static struct rb_root structures__tree = RB_ROOT;
|
|
static LIST_HEAD(structures__list);
|
|
|
|
static struct structure *structures__add(struct class *class,
|
|
const struct cu *cu,
|
|
bool *existing_entry)
|
|
{
|
|
struct rb_node **p = &structures__tree.rb_node;
|
|
struct rb_node *parent = NULL;
|
|
struct structure *str;
|
|
const char *new_class_name = class__name(class, cu);
|
|
|
|
while (*p != NULL) {
|
|
int rc;
|
|
|
|
parent = *p;
|
|
str = rb_entry(parent, struct structure, rb_node);
|
|
rc = strcmp(str->name, new_class_name);
|
|
|
|
if (rc > 0)
|
|
p = &(*p)->rb_left;
|
|
else if (rc < 0)
|
|
p = &(*p)->rb_right;
|
|
else {
|
|
*existing_entry = true;
|
|
return str;
|
|
}
|
|
}
|
|
|
|
str = structure__new(new_class_name);
|
|
if (str == NULL)
|
|
return NULL;
|
|
|
|
*existing_entry = false;
|
|
rb_link_node(&str->rb_node, parent, p);
|
|
rb_insert_color(&str->rb_node, &structures__tree);
|
|
|
|
/* For linear traversals */
|
|
list_add_tail(&str->node, &structures__list);
|
|
|
|
return str;
|
|
}
|
|
|
|
void structures__delete(void)
|
|
{
|
|
struct rb_node *next = rb_first(&structures__tree);
|
|
|
|
while (next) {
|
|
struct structure *pos = rb_entry(next, struct structure, rb_node);
|
|
next = rb_next(&pos->rb_node);
|
|
rb_erase(&pos->rb_node, &structures__tree);
|
|
structure__delete(pos);
|
|
}
|
|
}
|
|
|
|
static void nr_definitions_formatter(struct structure *st)
|
|
{
|
|
printf("%s%c%u\n", st->name, separator, st->nr_files);
|
|
}
|
|
|
|
static void nr_members_formatter(struct class *class,
|
|
struct cu *cu, uint16_t id __unused)
|
|
{
|
|
printf("%s%c%u\n", class__name(class, cu), separator,
|
|
class__nr_members(class));
|
|
}
|
|
|
|
static void nr_methods_formatter(struct structure *st)
|
|
{
|
|
printf("%s%c%u\n", st->name, separator, st->nr_methods);
|
|
}
|
|
|
|
static void size_formatter(struct class *class,
|
|
struct cu *cu, uint16_t id __unused)
|
|
{
|
|
printf("%s%c%d%c%u\n", class__name(class, cu), separator,
|
|
class__size(class), separator, class->nr_holes);
|
|
}
|
|
|
|
static void class_name_len_formatter(struct class *class, struct cu *cu,
|
|
uint16_t id __unused)
|
|
{
|
|
const char *name = class__name(class, cu);
|
|
printf("%s%c%zd\n", name, separator, strlen(name));
|
|
}
|
|
|
|
static void class_name_formatter(struct class *class,
|
|
struct cu *cu, uint16_t id __unused)
|
|
{
|
|
puts(class__name(class, cu));
|
|
}
|
|
|
|
static void class_formatter(struct class *class, struct cu *cu, uint16_t id)
|
|
{
|
|
struct tag *typedef_alias = NULL;
|
|
struct tag *tag = class__tag(class);
|
|
const char *name = class__name(class, cu);
|
|
|
|
if (name == NULL) {
|
|
/*
|
|
* Find the first typedef for this struct, this is enough
|
|
* as if we optimize the struct all the typedefs will be
|
|
* affected.
|
|
*/
|
|
typedef_alias = cu__find_first_typedef_of_type(cu, id);
|
|
/*
|
|
* If there is no typedefs for this anonymous struct it is
|
|
* found just inside another struct, and in this case it'll
|
|
* be printed when the type it is in is printed, but if
|
|
* the user still wants to see its statistics, just use
|
|
* --nested_anon_include.
|
|
*/
|
|
if (typedef_alias == NULL && !class__include_nested_anonymous)
|
|
return;
|
|
}
|
|
|
|
if (typedef_alias != NULL) {
|
|
struct type *tdef = tag__type(typedef_alias);
|
|
|
|
conf.prefix = "typedef";
|
|
conf.suffix = type__name(tdef, cu);
|
|
} else
|
|
conf.prefix = conf.suffix = NULL;
|
|
|
|
tag__fprintf(tag, cu, &conf, stdout);
|
|
|
|
putchar('\n');
|
|
}
|
|
|
|
static void print_packable_info(struct class *c, struct cu *cu, uint16_t id)
|
|
{
|
|
const struct tag *t = class__tag(c);
|
|
const size_t orig_size = class__size(c);
|
|
const size_t new_size = class__size(c->priv);
|
|
const size_t savings = orig_size - new_size;
|
|
const char *name = class__name(c, cu);
|
|
|
|
/* Anonymous struct? Try finding a typedef */
|
|
if (name == NULL) {
|
|
const struct tag *tdef =
|
|
cu__find_first_typedef_of_type(cu, id);
|
|
|
|
if (tdef != NULL)
|
|
name = class__name(tag__class(tdef), cu);
|
|
}
|
|
if (name != NULL)
|
|
printf("%s%c%zd%c%zd%c%zd\n",
|
|
name, separator,
|
|
orig_size, separator,
|
|
new_size, separator,
|
|
savings);
|
|
else
|
|
printf("%s(%d)%c%zd%c%zd%c%zd\n",
|
|
tag__decl_file(t, cu),
|
|
tag__decl_line(t, cu),
|
|
separator,
|
|
orig_size, separator,
|
|
new_size, separator,
|
|
savings);
|
|
}
|
|
|
|
static void (*stats_formatter)(struct structure *st);
|
|
|
|
static void print_stats(void)
|
|
{
|
|
struct structure *pos;
|
|
|
|
list_for_each_entry(pos, &structures__list, node)
|
|
stats_formatter(pos);
|
|
}
|
|
|
|
static struct class *class__filter(struct class *class, struct cu *cu,
|
|
uint16_t tag_id);
|
|
|
|
static void (*formatter)(struct class *class,
|
|
struct cu *cu, uint16_t id) = class_formatter;
|
|
|
|
static void print_classes(struct cu *cu)
|
|
{
|
|
uint16_t id;
|
|
struct class *pos;
|
|
|
|
cu__for_each_struct_or_union(cu, id, pos) {
|
|
bool existing_entry;
|
|
struct structure *str;
|
|
|
|
if (pos->type.namespace.name == 0 &&
|
|
!(class__include_anonymous ||
|
|
class__include_nested_anonymous))
|
|
continue;
|
|
|
|
if (!class__filter(pos, cu, id))
|
|
continue;
|
|
/*
|
|
* FIXME: No sense in adding an anonymous struct to the list of
|
|
* structs already printed, as we look for the name... The
|
|
* right fix probably will be to call class__fprintf on a
|
|
* in-memory FILE, do a hash, and look it by full contents, not
|
|
* by name. And this is needed for CTF as well, but its late now
|
|
* and I'm sleepy, will leave for later...
|
|
*/
|
|
if (pos->type.namespace.name != 0) {
|
|
str = structures__add(pos, cu, &existing_entry);
|
|
if (str == NULL) {
|
|
fprintf(stderr, "pahole: insufficient memory for "
|
|
"processing %s, skipping it...\n", cu->name);
|
|
return;
|
|
}
|
|
|
|
/* Already printed... */
|
|
if (existing_entry) {
|
|
str->nr_files++;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (show_packable && !global_verbose)
|
|
print_packable_info(pos, cu, id);
|
|
else if (formatter != NULL)
|
|
formatter(pos, cu, id);
|
|
}
|
|
}
|
|
|
|
static struct cu *cu__filter(struct cu *cu)
|
|
{
|
|
if (cu__exclude_prefix != NULL &&
|
|
(cu->name == NULL ||
|
|
strncmp(cu__exclude_prefix, cu->name,
|
|
cu__exclude_prefix_len) == 0))
|
|
return NULL;
|
|
|
|
return cu;
|
|
}
|
|
|
|
static int class__packable(struct class *class, struct cu *cu)
|
|
{
|
|
struct class *clone;
|
|
|
|
if (class->nr_holes == 0 && class->nr_bit_holes == 0)
|
|
return 0;
|
|
|
|
clone = class__clone(class, NULL, cu);
|
|
if (clone == NULL)
|
|
return 0;
|
|
class__reorganize(clone, cu, 0, stdout);
|
|
if (class__size(class) > class__size(clone)) {
|
|
class->priv = clone;
|
|
return 1;
|
|
}
|
|
/* FIXME: we need to free in the right order,
|
|
* cu->obstack is being corrupted...
|
|
class__delete(clone, cu);
|
|
*/
|
|
return 0;
|
|
}
|
|
|
|
static struct class *class__filter(struct class *class, struct cu *cu,
|
|
uint16_t tag_id)
|
|
{
|
|
struct tag *tag = class__tag(class);
|
|
const char *name;
|
|
|
|
if (!tag->top_level) {
|
|
class__find_holes(class);
|
|
|
|
if (!show_private_classes)
|
|
return NULL;
|
|
}
|
|
|
|
name = class__name(class, cu);
|
|
|
|
if (class__is_declaration(class))
|
|
return NULL;
|
|
|
|
if (!class__include_anonymous && name == NULL)
|
|
return NULL;
|
|
|
|
if (class__exclude_prefix != NULL) {
|
|
if (name == NULL) {
|
|
const struct tag *tdef =
|
|
cu__find_first_typedef_of_type(cu, tag_id);
|
|
if (tdef != NULL) {
|
|
struct class *c = tag__class(tdef);
|
|
|
|
name = class__name(c, cu);
|
|
}
|
|
}
|
|
if (name != NULL && strncmp(class__exclude_prefix, name,
|
|
class__exclude_prefix_len) == 0)
|
|
return NULL;
|
|
}
|
|
|
|
if (class__include_prefix != NULL) {
|
|
if (name == NULL) {
|
|
const struct tag *tdef =
|
|
cu__find_first_typedef_of_type(cu, tag_id);
|
|
if (tdef != NULL) {
|
|
struct class *c = tag__class(tdef);
|
|
|
|
name = class__name(c, cu);
|
|
}
|
|
}
|
|
if (name != NULL && strncmp(class__include_prefix, name,
|
|
class__include_prefix_len) != 0)
|
|
return NULL;
|
|
}
|
|
|
|
if (decl_exclude_prefix != NULL &&
|
|
(!tag__decl_file(tag, cu) ||
|
|
strncmp(decl_exclude_prefix, tag__decl_file(tag, cu),
|
|
decl_exclude_prefix_len) == 0))
|
|
return NULL;
|
|
|
|
if (tag->top_level)
|
|
class__find_holes(class);
|
|
|
|
if (class->nr_holes < nr_holes ||
|
|
class->nr_bit_holes < nr_bit_holes ||
|
|
(hole_size_ge != 0 && !class__has_hole_ge(class, hole_size_ge)))
|
|
return NULL;
|
|
|
|
if (show_packable && !class__packable(class, cu))
|
|
return NULL;
|
|
|
|
return class;
|
|
}
|
|
|
|
static void union__find_new_size(struct tag *tag, struct cu *cu);
|
|
|
|
static void class__resize_LP(struct tag *tag, struct cu *cu)
|
|
{
|
|
struct tag *tag_pos;
|
|
struct class *class = tag__class(tag);
|
|
size_t word_size_diff;
|
|
size_t orig_size = class->type.size;
|
|
|
|
if (tag__type(tag)->resized)
|
|
return;
|
|
|
|
tag__type(tag)->resized = 1;
|
|
|
|
if (original_word_size > word_size)
|
|
word_size_diff = original_word_size - word_size;
|
|
else
|
|
word_size_diff = word_size - original_word_size;
|
|
|
|
type__for_each_tag(tag__type(tag), tag_pos) {
|
|
struct tag *type;
|
|
size_t diff = 0;
|
|
size_t array_multiplier = 1;
|
|
|
|
/* we want only data members, i.e. with byte_offset attr */
|
|
if (tag_pos->tag != DW_TAG_member &&
|
|
tag_pos->tag != DW_TAG_inheritance)
|
|
continue;
|
|
|
|
type = cu__type(cu, tag_pos->type);
|
|
tag__assert_search_result(type);
|
|
if (type->tag == DW_TAG_array_type) {
|
|
int i;
|
|
for (i = 0; i < tag__array_type(type)->dimensions; ++i)
|
|
array_multiplier *= tag__array_type(type)->nr_entries[i];
|
|
|
|
type = cu__type(cu, type->type);
|
|
tag__assert_search_result(type);
|
|
}
|
|
|
|
if (tag__is_typedef(type)) {
|
|
type = tag__follow_typedef(type, cu);
|
|
tag__assert_search_result(type);
|
|
}
|
|
|
|
switch (type->tag) {
|
|
case DW_TAG_base_type: {
|
|
struct base_type *bt = tag__base_type(type);
|
|
char bf[64];
|
|
const char *name = base_type__name(bt, cu, bf,
|
|
sizeof(bf));
|
|
if (strcmp(name, "long int") != 0 &&
|
|
strcmp(name, "long unsigned int") != 0)
|
|
break;
|
|
/* fallthru */
|
|
}
|
|
case DW_TAG_pointer_type:
|
|
diff = word_size_diff;
|
|
break;
|
|
case DW_TAG_structure_type:
|
|
case DW_TAG_union_type:
|
|
if (tag__is_union(type))
|
|
union__find_new_size(type, cu);
|
|
else
|
|
class__resize_LP(type, cu);
|
|
diff = tag__type(type)->size_diff;
|
|
break;
|
|
}
|
|
|
|
diff *= array_multiplier;
|
|
|
|
if (diff != 0) {
|
|
struct class_member *m = tag__class_member(tag_pos);
|
|
if (original_word_size > word_size) {
|
|
class->type.size -= diff;
|
|
class__subtract_offsets_from(class, m, diff);
|
|
} else {
|
|
class->type.size += diff;
|
|
class__add_offsets_from(class, m, diff);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (original_word_size > word_size)
|
|
tag__type(tag)->size_diff = orig_size - class->type.size;
|
|
else
|
|
tag__type(tag)->size_diff = class->type.size - orig_size;
|
|
|
|
class__find_holes(class);
|
|
class__fixup_alignment(class, cu);
|
|
}
|
|
|
|
static void union__find_new_size(struct tag *tag, struct cu *cu)
|
|
{
|
|
struct tag *tag_pos;
|
|
struct type *type = tag__type(tag);
|
|
size_t max_size = 0;
|
|
|
|
if (type->resized)
|
|
return;
|
|
|
|
type->resized = 1;
|
|
|
|
type__for_each_tag(type, tag_pos) {
|
|
struct tag *type;
|
|
size_t size;
|
|
|
|
/* we want only data members, i.e. with byte_offset attr */
|
|
if (tag_pos->tag != DW_TAG_member &&
|
|
tag_pos->tag != DW_TAG_inheritance)
|
|
continue;
|
|
|
|
type = cu__type(cu, tag_pos->type);
|
|
tag__assert_search_result(type);
|
|
if (tag__is_typedef(type))
|
|
type = tag__follow_typedef(type, cu);
|
|
|
|
if (tag__is_union(type))
|
|
union__find_new_size(type, cu);
|
|
else if (tag__is_struct(type))
|
|
class__resize_LP(type, cu);
|
|
|
|
size = tag__size(type, cu);
|
|
if (size > max_size)
|
|
max_size = size;
|
|
}
|
|
|
|
if (max_size > type->size)
|
|
type->size_diff = max_size - type->size;
|
|
else
|
|
type->size_diff = type->size - max_size;
|
|
|
|
type->size = max_size;
|
|
}
|
|
|
|
static void tag__fixup_word_size(struct tag *tag, struct cu *cu)
|
|
{
|
|
if (tag__is_struct(tag) || tag__is_union(tag)) {
|
|
struct tag *pos;
|
|
|
|
namespace__for_each_tag(tag__namespace(tag), pos)
|
|
tag__fixup_word_size(pos, cu);
|
|
}
|
|
|
|
switch (tag->tag) {
|
|
case DW_TAG_base_type: {
|
|
struct base_type *bt = tag__base_type(tag);
|
|
|
|
/*
|
|
* This shouldn't happen, but at least on a tcp_ipv6.c
|
|
* built with GNU C 4.3.0 20080130 (Red Hat 4.3.0-0.7),
|
|
* one was found, so just bail out.
|
|
*/
|
|
if (!bt->name)
|
|
return;
|
|
char bf[64];
|
|
const char *name = base_type__name(bt, cu, bf, sizeof(bf));
|
|
|
|
if (strcmp(name, "long int") == 0 ||
|
|
strcmp(name, "long unsigned int") == 0)
|
|
bt->bit_size = word_size * 8;
|
|
}
|
|
break;
|
|
case DW_TAG_structure_type:
|
|
class__resize_LP(tag, cu);
|
|
break;
|
|
case DW_TAG_union_type:
|
|
union__find_new_size(tag, cu);
|
|
break;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
static void cu_fixup_word_size_iterator(struct cu *cu)
|
|
{
|
|
original_word_size = cu->addr_size;
|
|
cu->addr_size = word_size;
|
|
|
|
uint16_t id;
|
|
struct tag *pos;
|
|
cu__for_each_type(cu, id, pos)
|
|
tag__fixup_word_size(pos, cu);
|
|
}
|
|
|
|
static void cu__account_nr_methods(struct cu *cu)
|
|
{
|
|
struct function *pos_function;
|
|
struct structure *str;
|
|
uint32_t id;
|
|
|
|
cu__for_each_function(cu, id, pos_function) {
|
|
struct class_member *pos;
|
|
list_for_each_entry(pos, &pos_function->proto.parms, tag.node) {
|
|
struct tag *type = cu__type(cu, pos->tag.type);
|
|
|
|
if (type == NULL || type->tag != DW_TAG_pointer_type)
|
|
continue;
|
|
|
|
type = cu__type(cu, type->type);
|
|
if (type == NULL || !tag__is_struct(type))
|
|
continue;
|
|
|
|
struct type *ctype = tag__type(type);
|
|
if (ctype->namespace.name == 0)
|
|
continue;
|
|
|
|
struct class *class = tag__class(type);
|
|
|
|
if (!class__filter(class, cu, 0))
|
|
continue;
|
|
|
|
bool existing_entry;
|
|
str = structures__add(class, cu, &existing_entry);
|
|
if (str == NULL) {
|
|
fprintf(stderr, "pahole: insufficient memory "
|
|
"for processing %s, skipping it...\n",
|
|
cu->name);
|
|
return;
|
|
}
|
|
|
|
if (!existing_entry)
|
|
class__find_holes(class);
|
|
++str->nr_methods;
|
|
}
|
|
}
|
|
}
|
|
|
|
static char tab[128];
|
|
|
|
static void print_structs_with_pointer_to(const struct cu *cu, uint16_t type)
|
|
{
|
|
struct class *pos;
|
|
struct class_member *pos_member;
|
|
uint16_t id;
|
|
|
|
cu__for_each_struct(cu, id, pos) {
|
|
bool looked = false;
|
|
struct structure *str;
|
|
|
|
if (pos->type.namespace.name == 0)
|
|
continue;
|
|
|
|
type__for_each_member(&pos->type, pos_member) {
|
|
struct tag *ctype = cu__type(cu, pos_member->tag.type);
|
|
|
|
tag__assert_search_result(ctype);
|
|
if (ctype->tag != DW_TAG_pointer_type || ctype->type != type)
|
|
continue;
|
|
|
|
if (!looked) {
|
|
bool existing_entry;
|
|
|
|
str = structures__add(pos, cu, &existing_entry);
|
|
if (str == NULL) {
|
|
fprintf(stderr, "pahole: insufficient memory for "
|
|
"processing %s, skipping it...\n",
|
|
cu->name);
|
|
return;
|
|
}
|
|
/*
|
|
* We already printed this struct in another CU
|
|
*/
|
|
if (existing_entry)
|
|
break;
|
|
looked = true;
|
|
}
|
|
printf("%s: %s\n", str->name,
|
|
class_member__name(pos_member, cu));
|
|
}
|
|
}
|
|
}
|
|
|
|
static void print_containers(const struct cu *cu, uint16_t type, int ident)
|
|
{
|
|
struct class *pos;
|
|
uint16_t id;
|
|
|
|
cu__for_each_struct(cu, id, pos) {
|
|
if (pos->type.namespace.name == 0)
|
|
continue;
|
|
|
|
const uint32_t n = type__nr_members_of_type(&pos->type, type);
|
|
if (n == 0)
|
|
continue;
|
|
|
|
if (ident == 0) {
|
|
bool existing_entry;
|
|
struct structure *str = structures__add(pos, cu, &existing_entry);
|
|
if (str == NULL) {
|
|
fprintf(stderr, "pahole: insufficient memory for "
|
|
"processing %s, skipping it...\n",
|
|
cu->name);
|
|
return;
|
|
}
|
|
/*
|
|
* We already printed this struct in another CU
|
|
*/
|
|
if (existing_entry)
|
|
break;
|
|
}
|
|
|
|
printf("%.*s%s", ident * 2, tab, class__name(pos, cu));
|
|
if (global_verbose)
|
|
printf(": %u", n);
|
|
putchar('\n');
|
|
if (recursive)
|
|
print_containers(cu, id, ident + 1);
|
|
}
|
|
}
|
|
|
|
/* Name and version of program. */
|
|
ARGP_PROGRAM_VERSION_HOOK_DEF = dwarves_print_version;
|
|
|
|
#define ARGP_flat_arrays 300
|
|
#define ARGP_show_private_classes 301
|
|
#define ARGP_fixup_silly_bitfields 302
|
|
#define ARGP_first_obj_only 303
|
|
#define ARGP_classes_as_structs 304
|
|
#define ARGP_hex_fmt 305
|
|
|
|
static const struct argp_option pahole__options[] = {
|
|
{
|
|
.name = "bit_holes",
|
|
.key = 'B',
|
|
.arg = "NR_HOLES",
|
|
.doc = "Show only structs at least NR_HOLES bit holes"
|
|
},
|
|
{
|
|
.name = "cacheline_size",
|
|
.key = 'c',
|
|
.arg = "SIZE",
|
|
.doc = "set cacheline size to SIZE"
|
|
},
|
|
{
|
|
.name = "class_name",
|
|
.key = 'C',
|
|
.arg = "CLASS_NAME",
|
|
.doc = "Show just this class"
|
|
},
|
|
{
|
|
.name = "find_pointers_to",
|
|
.key = 'f',
|
|
.arg = "CLASS_NAME",
|
|
.doc = "Find pointers to CLASS_NAME"
|
|
},
|
|
{
|
|
.name = "format_path",
|
|
.key = 'F',
|
|
.arg = "FORMAT_LIST",
|
|
.doc = "List of debugging formats to try"
|
|
},
|
|
{
|
|
.name = "contains",
|
|
.key = 'i',
|
|
.arg = "CLASS_NAME",
|
|
.doc = "Show classes that contains CLASS_NAME"
|
|
},
|
|
{
|
|
.name = "show_decl_info",
|
|
.key = 'I',
|
|
.doc = "Show the file and line number where the tags were defined"
|
|
},
|
|
{
|
|
.name = "holes",
|
|
.key = 'H',
|
|
.arg = "NR_HOLES",
|
|
.doc = "show only structs with at least NR_HOLES holes",
|
|
},
|
|
{
|
|
.name = "hole_size_ge",
|
|
.key = 'z',
|
|
.arg = "HOLE_SIZE",
|
|
.doc = "show only structs with at least one hole greater "
|
|
"or equal to HOLE_SIZE",
|
|
},
|
|
{
|
|
.name = "packable",
|
|
.key = 'P',
|
|
.doc = "show only structs that has holes that can be packed",
|
|
},
|
|
{
|
|
.name = "expand_types",
|
|
.key = 'E',
|
|
.doc = "expand class members",
|
|
},
|
|
{
|
|
.name = "nr_members",
|
|
.key = 'n',
|
|
.doc = "show number of members",
|
|
},
|
|
{
|
|
.name = "rel_offset",
|
|
.key = 'r',
|
|
.doc = "show relative offsets of members in inner structs"
|
|
},
|
|
{
|
|
.name = "recursive",
|
|
.key = 'd',
|
|
.doc = "recursive mode, affects several other flags",
|
|
},
|
|
{
|
|
.name = "reorganize",
|
|
.key = 'R',
|
|
.doc = "reorg struct trying to kill holes",
|
|
},
|
|
{
|
|
.name = "show_reorg_steps",
|
|
.key = 'S',
|
|
.doc = "show the struct layout at each reorganization step",
|
|
},
|
|
{
|
|
.name = "class_name_len",
|
|
.key = 'N',
|
|
.doc = "show size of classes",
|
|
},
|
|
{
|
|
.name = "show_first_biggest_size_base_type_member",
|
|
.key = 'l',
|
|
.doc = "show first biggest size base_type member",
|
|
},
|
|
{
|
|
.name = "nr_methods",
|
|
.key = 'm',
|
|
.doc = "show number of methods",
|
|
},
|
|
{
|
|
.name = "show_only_data_members",
|
|
.key = 'M',
|
|
.doc = "show only the members that use space in the class layout",
|
|
},
|
|
{
|
|
.name = "expand_pointers",
|
|
.key = 'p',
|
|
.doc = "expand class pointer members",
|
|
},
|
|
{
|
|
.name = "sizes",
|
|
.key = 's',
|
|
.doc = "show size of classes",
|
|
},
|
|
{
|
|
.name = "separator",
|
|
.key = 't',
|
|
.arg = "SEP",
|
|
.doc = "use SEP as the field separator",
|
|
},
|
|
{
|
|
.name = "nr_definitions",
|
|
.key = 'T',
|
|
.doc = "show how many times struct was defined",
|
|
},
|
|
{
|
|
.name = "decl_exclude",
|
|
.key = 'D',
|
|
.arg = "PREFIX",
|
|
.doc = "exclude classes declared in files with PREFIX",
|
|
},
|
|
{
|
|
.name = "exclude",
|
|
.key = 'x',
|
|
.arg = "PREFIX",
|
|
.doc = "exclude PREFIXed classes",
|
|
},
|
|
{
|
|
.name = "prefix_filter",
|
|
.key = 'y',
|
|
.arg = "PREFIX",
|
|
.doc = "include PREFIXed classes",
|
|
},
|
|
{
|
|
.name = "cu_exclude",
|
|
.key = 'X',
|
|
.arg = "PREFIX",
|
|
.doc = "exclude PREFIXed compilation units",
|
|
},
|
|
{
|
|
.name = "anon_include",
|
|
.key = 'a',
|
|
.doc = "include anonymous classes",
|
|
},
|
|
{
|
|
.name = "nested_anon_include",
|
|
.key = 'A',
|
|
.doc = "include nested (inside other structs) anonymous classes",
|
|
},
|
|
{
|
|
.name = "quiet",
|
|
.key = 'q',
|
|
.doc = "be quieter",
|
|
},
|
|
{
|
|
.name = "defined_in",
|
|
.key = 'u',
|
|
.doc = "show CUs where CLASS_NAME (-C) is defined",
|
|
},
|
|
{
|
|
.name = "verbose",
|
|
.key = 'V',
|
|
.doc = "be verbose",
|
|
},
|
|
{
|
|
.name = "word_size",
|
|
.key = 'w',
|
|
.arg = "WORD_SIZE",
|
|
.doc = "change the arch word size to WORD_SIZE"
|
|
},
|
|
{
|
|
.name = "ctf_encode",
|
|
.key = 'Z',
|
|
.doc = "Encode as CTF",
|
|
},
|
|
{
|
|
.name = "flat_arrays",
|
|
.key = ARGP_flat_arrays,
|
|
.doc = "Flat arrays",
|
|
},
|
|
{
|
|
.name = "show_private_classes",
|
|
.key = ARGP_show_private_classes,
|
|
.doc = "Show classes that are defined inside other classes or in functions",
|
|
},
|
|
{
|
|
.name = "fixup_silly_bitfields",
|
|
.key = ARGP_fixup_silly_bitfields,
|
|
.doc = "Fix silly bitfields such as int foo:32",
|
|
},
|
|
{
|
|
.name = "first_obj_only",
|
|
.key = ARGP_first_obj_only,
|
|
.doc = "Only process the first object file in the binary",
|
|
},
|
|
{
|
|
.name = "classes_as_structs",
|
|
.key = ARGP_classes_as_structs,
|
|
.doc = "Use 'struct' when printing classes",
|
|
},
|
|
{
|
|
.name = "hex",
|
|
.key = ARGP_hex_fmt,
|
|
.doc = "Print offsets and sizes in hexadecimal",
|
|
},
|
|
{
|
|
.name = "btf_encode",
|
|
.key = 'J',
|
|
.doc = "Encode as BTF",
|
|
},
|
|
{
|
|
.name = NULL,
|
|
}
|
|
};
|
|
|
|
static error_t pahole__options_parser(int key, char *arg,
|
|
struct argp_state *state)
|
|
{
|
|
switch (key) {
|
|
case ARGP_KEY_INIT:
|
|
if (state->child_inputs != NULL)
|
|
state->child_inputs[0] = state->input;
|
|
break;
|
|
case 'A': class__include_nested_anonymous = 1; break;
|
|
case 'a': class__include_anonymous = 1; break;
|
|
case 'B': nr_bit_holes = atoi(arg); break;
|
|
case 'C': class_name = arg; break;
|
|
case 'c': cacheline_size = atoi(arg); break;
|
|
case 'D': decl_exclude_prefix = arg;
|
|
decl_exclude_prefix_len = strlen(decl_exclude_prefix);
|
|
conf_load.extra_dbg_info = 1; break;
|
|
case 'd': recursive = 1; break;
|
|
case 'E': conf.expand_types = 1; break;
|
|
case 'f': find_pointers_in_structs = 1;
|
|
class_name = arg; break;
|
|
case 'F': conf_load.format_path = arg; break;
|
|
case 'H': nr_holes = atoi(arg); break;
|
|
case 'I': conf.show_decl_info = 1;
|
|
conf_load.extra_dbg_info = 1; break;
|
|
case 'i': find_containers = 1;
|
|
class_name = arg; break;
|
|
case 'J': btf_encode = 1;
|
|
no_bitfield_type_recode = true; break;
|
|
case 'l': conf.show_first_biggest_size_base_type_member = 1; break;
|
|
case 'M': conf.show_only_data_members = 1; break;
|
|
case 'm': stats_formatter = nr_methods_formatter; break;
|
|
case 'N': formatter = class_name_len_formatter; break;
|
|
case 'n': formatter = nr_members_formatter; break;
|
|
case 'P': show_packable = 1;
|
|
conf_load.extra_dbg_info = 1; break;
|
|
case 'p': conf.expand_pointers = 1; break;
|
|
case 'q': conf.emit_stats = 0;
|
|
conf.suppress_comments = 1;
|
|
conf.suppress_offset_comment = 1; break;
|
|
case 'R': reorganize = 1; break;
|
|
case 'r': conf.rel_offset = 1; break;
|
|
case 'S': show_reorg_steps = 1; break;
|
|
case 's': formatter = size_formatter; break;
|
|
case 'T': stats_formatter = nr_definitions_formatter;
|
|
formatter = NULL; break;
|
|
case 't': separator = arg[0]; break;
|
|
case 'u': defined_in = 1; break;
|
|
case 'V': global_verbose = 1; break;
|
|
case 'w': word_size = atoi(arg); break;
|
|
case 'X': cu__exclude_prefix = arg;
|
|
cu__exclude_prefix_len = strlen(cu__exclude_prefix);
|
|
break;
|
|
case 'x': class__exclude_prefix = arg;
|
|
class__exclude_prefix_len = strlen(class__exclude_prefix);
|
|
break;
|
|
case 'y': class__include_prefix = arg;
|
|
class__include_prefix_len = strlen(class__include_prefix);
|
|
break;
|
|
case 'z':
|
|
hole_size_ge = atoi(arg);
|
|
if (!global_verbose)
|
|
formatter = class_name_formatter;
|
|
break;
|
|
case 'Z': ctf_encode = 1; break;
|
|
case ARGP_flat_arrays: conf.flat_arrays = 1; break;
|
|
case ARGP_show_private_classes:
|
|
show_private_classes = true;
|
|
conf.show_only_data_members = 1; break;
|
|
case ARGP_fixup_silly_bitfields:
|
|
conf_load.fixup_silly_bitfields = 1; break;
|
|
case ARGP_first_obj_only:
|
|
first_obj_only = true; break;
|
|
case ARGP_classes_as_structs:
|
|
conf.classes_as_structs = 1; break;
|
|
case ARGP_hex_fmt:
|
|
conf.hex_fmt = 1; break;
|
|
default:
|
|
return ARGP_ERR_UNKNOWN;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static const char pahole__args_doc[] = "FILE";
|
|
|
|
static struct argp pahole__argp = {
|
|
.options = pahole__options,
|
|
.parser = pahole__options_parser,
|
|
.args_doc = pahole__args_doc,
|
|
};
|
|
|
|
static void do_reorg(struct tag *class, struct cu *cu)
|
|
{
|
|
int savings;
|
|
const uint8_t reorg_verbose =
|
|
show_reorg_steps ? 2 : global_verbose;
|
|
struct class *clone = class__clone(tag__class(class), NULL, cu);
|
|
if (clone == NULL) {
|
|
fprintf(stderr, "pahole: out of memory!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
class__reorganize(clone, cu, reorg_verbose, stdout);
|
|
savings = class__size(tag__class(class)) - class__size(clone);
|
|
if (savings != 0 && reorg_verbose) {
|
|
putchar('\n');
|
|
if (show_reorg_steps)
|
|
puts("/* Final reorganized struct: */");
|
|
}
|
|
tag__fprintf(class__tag(clone), cu, &conf, stdout);
|
|
if (savings != 0) {
|
|
const size_t cacheline_savings =
|
|
(tag__nr_cachelines(class, cu) -
|
|
tag__nr_cachelines(class__tag(clone), cu));
|
|
|
|
printf(" /* saved %d byte%s", savings,
|
|
savings != 1 ? "s" : "");
|
|
if (cacheline_savings != 0)
|
|
printf(" and %zu cacheline%s",
|
|
cacheline_savings,
|
|
cacheline_savings != 1 ?
|
|
"s" : "");
|
|
puts("! */");
|
|
} else
|
|
putchar('\n');
|
|
|
|
/* FIXME: we need to free in the right order,
|
|
* cu->obstack is being corrupted...
|
|
class__delete(clone, cu);
|
|
*/
|
|
}
|
|
|
|
static enum load_steal_kind pahole_stealer(struct cu *cu,
|
|
struct conf_load *conf_load __unused)
|
|
{
|
|
int ret = LSK__DELETE;
|
|
|
|
if (!cu__filter(cu))
|
|
goto filter_it;
|
|
|
|
if (btf_encode) {
|
|
cu__encode_btf(cu, global_verbose);
|
|
return LSK__KEEPIT;
|
|
}
|
|
|
|
if (ctf_encode) {
|
|
cu__encode_ctf(cu, global_verbose);
|
|
/*
|
|
* We still have to get the type signature code merged to eliminate
|
|
* dups, reference another CTF file, etc, so for now just encode the
|
|
* first cu that is let thru by cu__filter.
|
|
*/
|
|
goto dump_and_stop;
|
|
}
|
|
|
|
if (class_name == NULL) {
|
|
if (stats_formatter == nr_methods_formatter) {
|
|
cu__account_nr_methods(cu);
|
|
goto dump_it;
|
|
}
|
|
|
|
if (word_size != 0)
|
|
cu_fixup_word_size_iterator(cu);
|
|
|
|
memset(tab, ' ', sizeof(tab) - 1);
|
|
|
|
print_classes(cu);
|
|
goto dump_it;
|
|
}
|
|
|
|
struct str_node *pos;
|
|
struct rb_node *next = rb_first(&class_names->entries);
|
|
|
|
while (next) {
|
|
pos = rb_entry(next, struct str_node, rb_node);
|
|
next = rb_next(&pos->rb_node);
|
|
|
|
static uint16_t class_id;
|
|
bool include_decls = find_pointers_in_structs != 0 ||
|
|
stats_formatter == nr_methods_formatter;
|
|
struct tag *class = cu__find_struct_or_union_by_name(cu, pos->s, include_decls, &class_id);
|
|
if (class == NULL)
|
|
continue;
|
|
|
|
if (defined_in) {
|
|
puts(cu->name);
|
|
goto dump_it;
|
|
}
|
|
/*
|
|
* Ok, found it, so remove from the list to avoid printing it
|
|
* twice, in another CU.
|
|
*/
|
|
strlist__remove(class_names, pos);
|
|
|
|
class__find_holes(tag__class(class));
|
|
if (reorganize) {
|
|
if (tag__is_struct(class))
|
|
do_reorg(class, cu);
|
|
} else if (find_containers)
|
|
print_containers(cu, class_id, 0);
|
|
else if (find_pointers_in_structs)
|
|
print_structs_with_pointer_to(cu, class_id);
|
|
else {
|
|
/*
|
|
* We don't need to print it for every compile unit
|
|
* but the previous options need
|
|
*/
|
|
tag__fprintf(class, cu, &conf, stdout);
|
|
putchar('\n');
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If we found all the entries in --class_name, stop
|
|
*/
|
|
if (strlist__empty(class_names)) {
|
|
dump_and_stop:
|
|
ret = LSK__STOP_LOADING;
|
|
}
|
|
dump_it:
|
|
if (first_obj_only)
|
|
ret = LSK__STOP_LOADING;
|
|
filter_it:
|
|
return ret;
|
|
}
|
|
|
|
static int add_class_name_entry(const char *s)
|
|
{
|
|
if (strncmp(s, "file://", 7) == 0) {
|
|
if (strlist__load(class_names, s + 7))
|
|
return -1;
|
|
} else switch (strlist__add(class_names, s)) {
|
|
case -EEXIST:
|
|
if (global_verbose)
|
|
fprintf(stderr,
|
|
"pahole: %s dup in -C, ignoring\n", s);
|
|
break;
|
|
case -ENOMEM:
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int populate_class_names(void)
|
|
{
|
|
char *s = class_name, *sep;
|
|
|
|
while ((sep = strchr(s, ',')) != NULL) {
|
|
*sep = '\0';
|
|
if (add_class_name_entry(s))
|
|
return -1;
|
|
*sep = ',';
|
|
s = sep + 1;
|
|
}
|
|
|
|
return *s ? add_class_name_entry(s) : 0;
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
int err, remaining, rc = EXIT_FAILURE;
|
|
|
|
if (argp_parse(&pahole__argp, argc, argv, 0, &remaining, NULL) ||
|
|
(remaining == argc && class_name == NULL)) {
|
|
argp_help(&pahole__argp, stderr, ARGP_HELP_SEE, argv[0]);
|
|
goto out;
|
|
}
|
|
|
|
class_names = strlist__new(true);
|
|
|
|
if (class_names == NULL || dwarves__init(cacheline_size)) {
|
|
fputs("pahole: insufficient memory\n", stderr);
|
|
goto out;
|
|
}
|
|
|
|
if (class_name && populate_class_names())
|
|
goto out_dwarves_exit;
|
|
|
|
struct cus *cus = cus__new();
|
|
if (cus == NULL) {
|
|
fputs("pahole: insufficient memory\n", stderr);
|
|
goto out_dwarves_exit;
|
|
}
|
|
|
|
conf_load.steal = pahole_stealer;
|
|
|
|
err = cus__load_files(cus, &conf_load, argv + remaining);
|
|
if (err != 0) {
|
|
cus__fprintf_load_files_err(cus, "pahole", argv + remaining, err, stderr);
|
|
goto out_cus_delete;
|
|
}
|
|
|
|
if (btf_encode) {
|
|
err = btf_encoder__encode();
|
|
if (err) {
|
|
fputs("Failed to encode BTF\n", stderr);
|
|
goto out_cus_delete;
|
|
}
|
|
}
|
|
|
|
if (stats_formatter != NULL)
|
|
print_stats();
|
|
rc = EXIT_SUCCESS;
|
|
out_cus_delete:
|
|
#ifdef DEBUG_CHECK_LEAKS
|
|
cus__delete(cus);
|
|
structures__delete();
|
|
#endif
|
|
out_dwarves_exit:
|
|
#ifdef DEBUG_CHECK_LEAKS
|
|
dwarves__exit();
|
|
#endif
|
|
out:
|
|
#ifdef DEBUG_CHECK_LEAKS
|
|
strlist__delete(class_names);
|
|
#endif
|
|
return rc;
|
|
}
|