/*
  SPDX-License-Identifier: GPL-2.0-only

  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>
*/

#include <assert.h>
#include <dirent.h>
#include <dwarf.h>
#include <errno.h>
#include <fcntl.h>
#include <fnmatch.h>
#include <libelf.h>
#include <limits.h>
#include <pthread.h>
#include <search.h>
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/utsname.h>

#include "config.h"
#include "list.h"
#include "dwarves.h"
#include "dutil.h"

#define min(x, y) ((x) < (y) ? (x) : (y))

#define obstack_chunk_alloc malloc
#define obstack_chunk_free free

static void *obstack_zalloc(struct obstack *obstack, size_t size)
{
	void *o = obstack_alloc(obstack, size);

	if (o)
		memset(o, 0, size);
	return o;
}

void *cu__zalloc(struct cu *cu, size_t size)
{
	if (cu->use_obstack)
		return obstack_zalloc(&cu->obstack, size);

	return zalloc(size);
}

void *cu__malloc(struct cu *cu, size_t size)
{
	if (cu->use_obstack)
		return obstack_alloc(&cu->obstack, size);

	return malloc(size);
}

void cu__free(struct cu *cu, void *ptr)
{
	if (!cu->use_obstack)
		free(ptr);

	// When using an obstack we'll free everything in cu__delete()
}

int tag__is_base_type(const struct tag *tag, const struct cu *cu)
{
	switch (tag->tag) {
	case DW_TAG_base_type:
		return 1;

	case DW_TAG_typedef: {
		const struct tag *type = cu__type(cu, tag->type);

		if (type == NULL)
			return 0;
		return tag__is_base_type(type, cu);
	}
	}
	return 0;
}

bool tag__is_array(const struct tag *tag, const struct cu *cu)
{
	switch (tag->tag) {
	case DW_TAG_array_type:
		return true;

	case DW_TAG_const_type:
	case DW_TAG_typedef: {
		const struct tag *type = cu__type(cu, tag->type);

		if (type == NULL)
			return 0;
		return tag__is_array(type, cu);
	}
	}
	return 0;
}

int __tag__has_type_loop(const struct tag *tag, const struct tag *type,
			 char *bf, size_t len, FILE *fp,
			 const char *fn, int line)
{
	char bbf[2048], *abf = bbf;

	if (type == NULL)
		return 0;

	if (tag->type == type->type) {
		int printed;

		if (bf != NULL)
			abf = bf;
		else
			len = sizeof(bbf);
		printed = snprintf(abf, len, "<ERROR(%s:%d): detected type loop: type=%d, tag=%s>",
			 fn, line, tag->type, dwarf_tag_name(tag->tag));
		if (bf == NULL)
			printed = fprintf(fp ?: stderr, "%s\n", abf);
		return printed;
	}

	return 0;
}

static void lexblock__delete_tags(struct tag *tag)
{
	struct lexblock *block = tag__lexblock(tag);
	struct tag *pos, *n;

	list_for_each_entry_safe_reverse(pos, n, &block->tags, node) {
		list_del_init(&pos->node);
		tag__delete(pos);
	}
}

void lexblock__delete(struct lexblock *block)
{
	if (block == NULL)
		return;

	lexblock__delete_tags(&block->ip.tag);
	free(block);
}

void tag__delete(struct tag *tag)
{
	if (tag == NULL)
		return;

	assert(list_empty(&tag->node));

	switch (tag->tag) {
	case DW_TAG_union_type:
		type__delete(tag__type(tag));		break;
	case DW_TAG_class_type:
	case DW_TAG_structure_type:
		class__delete(tag__class(tag));		break;
	case DW_TAG_enumeration_type:
		enumeration__delete(tag__type(tag));	break;
	case DW_TAG_subroutine_type:
		ftype__delete(tag__ftype(tag));		break;
	case DW_TAG_subprogram:
		function__delete(tag__function(tag));	break;
	case DW_TAG_lexical_block:
		lexblock__delete(tag__lexblock(tag));	break;
	default:
		free(tag);
	}
}

void tag__not_found_die(const char *file, int line, const char *func, int tag, const char *name)
{
	fprintf(stderr, "%s::%s(%d, related to the type of tag DW_TAG_%s \"%s\"): tag not found, please report to "
			"acme@kernel.org\n", file, func, line, dwarf_tag_name(tag), name);
	exit(1);
}

struct tag *tag__follow_typedef(const struct tag *tag, const struct cu *cu)
{
	struct tag *type = cu__type(cu, tag->type);

	if (type != NULL && tag__is_typedef(type))
		return tag__follow_typedef(type, cu);

	return type;
}

struct tag *tag__strip_typedefs_and_modifiers(const struct tag *tag, const struct cu *cu)
{
	struct tag *type = cu__type(cu, tag->type);

	while (type != NULL && (tag__is_typedef(type) || tag__is_modifier(type)))
		type = cu__type(cu, type->type);

	return type;
}

size_t __tag__id_not_found_fprintf(FILE *fp, type_id_t id,
				   const char *fn, int line)
{
	return fprintf(fp, "<ERROR(%s:%d): %d not found!>\n", fn, line, id);
}

static struct ase_type_name_to_size {
	const char *name;
	size_t	   size;
} base_type_name_to_size_table[] = {
	{ .name = "unsigned",		    .size = 32, },
	{ .name = "signed int",		    .size = 32, },
	{ .name = "unsigned int",  	    .size = 32, },
	{ .name = "int",		    .size = 32, },
	{ .name = "short unsigned int",	    .size = 16, },
	{ .name = "signed short",	    .size = 16, },
	{ .name = "unsigned short",	    .size = 16, },
	{ .name = "short int",		    .size = 16, },
	{ .name = "short",		    .size = 16, },
	{ .name = "char",		    .size =  8, },
	{ .name = "signed char",	    .size =  8, },
	{ .name = "unsigned char",	    .size =  8, },
	{ .name = "signed long",	    .size =  0, },
	{ .name = "long int",		    .size =  0, },
	{ .name = "long",		    .size =  0, },
	{ .name = "signed long",	    .size =  0, },
	{ .name = "unsigned long",	    .size =  0, },
	{ .name = "long unsigned int",	    .size =  0, },
	{ .name = "bool",		    .size =  8, },
	{ .name = "_Bool",		    .size =  8, },
	{ .name = "long long unsigned int", .size = 64, },
	{ .name = "long long int",	    .size = 64, },
	{ .name = "long long",		    .size = 64, },
	{ .name = "signed long long",	    .size = 64, },
	{ .name = "unsigned long long",	    .size = 64, },
	{ .name = "double",		    .size = 64, },
	{ .name = "double double",	    .size = 64, },
	{ .name = "single float",	    .size = 32, },
	{ .name = "float",		    .size = 32, },
	{ .name = "long double",	    .size = sizeof(long double) * 8, },
	{ .name = "long double long double", .size = sizeof(long double) * 8, },
	{ .name = "__int128",		    .size = 128, },
	{ .name = "unsigned __int128",	    .size = 128, },
	{ .name = "__int128 unsigned",	    .size = 128, },
	{ .name = "_Float128",              .size = 128, },
	{ .name = NULL },
};

bool base_type__language_defined(struct base_type *bt)
{
	int i = 0;
	char bf[64];
	const char *name;

	if (bt->name_has_encoding)
		name = bt->name;
	else
		name = base_type__name(bt, bf, sizeof(bf));

	while (base_type_name_to_size_table[i].name != NULL) {
		if (bt->name_has_encoding) {
			if (strcmp(base_type_name_to_size_table[i].name, bt->name) == 0)
				return true;
		} else if (strcmp(base_type_name_to_size_table[i].name, name) == 0)
			return true;
		++i;
	}

	return false;
}

size_t base_type__name_to_size(struct base_type *bt, struct cu *cu)
{
	int i = 0;
	char bf[64];
	const char *name, *orig_name;

	if (bt->name_has_encoding)
		name = bt->name;
	else
		name = base_type__name(bt, bf, sizeof(bf));
	orig_name = name;
try_again:
	while (base_type_name_to_size_table[i].name != NULL) {
		if (bt->name_has_encoding) {
			if (strcmp(base_type_name_to_size_table[i].name, bt->name) == 0) {
				size_t size;
found:
				size = base_type_name_to_size_table[i].size;

				return size ?: ((size_t)cu->addr_size * 8);
			}
		} else if (strcmp(base_type_name_to_size_table[i].name, name) == 0)
			goto found;
		++i;
	}

	if (strstarts(name, "signed ")) {
		i = 0;
		name += sizeof("signed");
		goto try_again;
	}

	fprintf(stderr, "%s: %s %s\n",
		 __func__, dwarf_tag_name(bt->tag.tag), orig_name);
	return 0;
}

static const char *base_type_fp_type_str[] = {
	[BT_FP_SINGLE]	   = "single",
	[BT_FP_DOUBLE]	   = "double",
	[BT_FP_CMPLX]	   = "complex",
	[BT_FP_CMPLX_DBL]  = "complex double",
	[BT_FP_CMPLX_LDBL] = "complex long double",
	[BT_FP_LDBL]	   = "long double",
	[BT_FP_INTVL]	   = "interval",
	[BT_FP_INTVL_DBL]  = "interval double",
	[BT_FP_INTVL_LDBL] = "interval long double",
	[BT_FP_IMGRY]	   = "imaginary",
	[BT_FP_IMGRY_DBL]  = "imaginary double",
	[BT_FP_IMGRY_LDBL] = "imaginary long double",
};

const char *__base_type__name(const struct base_type *bt)
{
	return bt->name;
}

const char *base_type__name(const struct base_type *bt, char *bf, size_t len)
{
	if (bt->name_has_encoding)
		return __base_type__name(bt);

	if (bt->float_type)
		snprintf(bf, len, "%s %s", base_type_fp_type_str[bt->float_type], bt->name);
	else
		snprintf(bf, len, "%s%s%s", bt->is_bool ? "bool " : "", bt->is_varargs ? "... " : "", bt->name);
	return bf;
}

void namespace__delete(struct namespace *space)
{
	struct tag *pos, *n;

	if (space == NULL)
		return;

	namespace__for_each_tag_safe_reverse(space, pos, n) {
		list_del_init(&pos->node);

		/* Look for nested namespaces */
		if (tag__has_namespace(pos))
			namespace__delete(tag__namespace(pos));
		tag__delete(pos);
	}

	tag__delete(&space->tag);
}

void __type__init(struct type *type)
{
	INIT_LIST_HEAD(&type->node);
	INIT_LIST_HEAD(&type->type_enum);
	type->sizeof_member = NULL;
	type->member_prefix = NULL;
	type->member_prefix_len = 0;
	type->suffix_disambiguation = 0;
}

struct class_member *
	type__find_first_biggest_size_base_type_member(struct type *type,
						       const struct cu *cu)
{
	struct class_member *pos, *result = NULL;
	size_t result_size = 0;

	type__for_each_data_member(type, pos) {
		if (pos->is_static)
			continue;

		struct tag *type = cu__type(cu, pos->tag.type);
		size_t member_size = 0, power2;
		struct class_member *inner = NULL;

		if (type == NULL) {
			tag__id_not_found_fprintf(stderr, pos->tag.type);
			continue;
		}
reevaluate:
		switch (type->tag) {
		case DW_TAG_base_type:
			member_size = base_type__size(type);
			break;
		case DW_TAG_pointer_type:
		case DW_TAG_reference_type:
			member_size = cu->addr_size;
			break;
		case DW_TAG_class_type:
		case DW_TAG_union_type:
		case DW_TAG_structure_type:
			if (tag__type(type)->nr_members == 0)
				continue;
			inner = type__find_first_biggest_size_base_type_member(tag__type(type), cu);
			member_size = inner->byte_size;
			break;
		case DW_TAG_array_type:
		case DW_TAG_const_type:
		case DW_TAG_typedef:
		case DW_TAG_rvalue_reference_type:
		case DW_TAG_volatile_type:
		case DW_TAG_atomic_type: {
			struct tag *tag = cu__type(cu, type->type);
			if (tag == NULL) {
				tag__id_not_found_fprintf(stderr, type->type);
				continue;
			}
			type = tag;
		}
			goto reevaluate;
		case DW_TAG_enumeration_type:
			member_size = tag__type(type)->size / 8;
			break;
		}

		/* long long */
		if (member_size > cu->addr_size)
			return pos;

		for (power2 = cu->addr_size; power2 > result_size; power2 /= 2)
			if (member_size >= power2) {
				if (power2 == cu->addr_size)
					return inner ?: pos;
				result_size = power2;
				result = inner ?: pos;
			}
	}

	return result;
}

static void cu__find_class_holes(struct cu *cu)
{
	uint32_t id;
	struct class *pos;

	cu__for_each_struct(cu, id, pos)
		class__find_holes(pos);
}

struct cus {
	uint32_t	 nr_entries;
	struct list_head cus;
	pthread_mutex_t  mutex;
	void		 (*loader_exit)(struct cus *cus);
	void		 *priv; // Used in dwarf_loader__exit()
};

void cus__lock(struct cus *cus)
{
	pthread_mutex_lock(&cus->mutex);
}

void cus__unlock(struct cus *cus)
{
	pthread_mutex_unlock(&cus->mutex);
}

void cus__set_cu_state(struct cus *cus, struct cu *cu, enum cu_state state)
{
	cus__lock(cus);
	cu->state = state;
	cus__unlock(cus);
}

// Used only when reproducible builds are desired
struct cu *cus__get_next_processable_cu(struct cus *cus)
{
	struct cu *cu;

	cus__lock(cus);

	list_for_each_entry(cu, &cus->cus, node) {
		switch (cu->state) {
		case CU__LOADED:
			cu->state = CU__PROCESSING;
			goto found;
		case CU__PROCESSING:
			// This will happen when we get to parallel
			// reproducible BTF encoding, libbpf dedup work needed
			// here. The other possibility is when we're flushing
			// the DWARF processed CUs when the parallel DWARF
			// loading stoped and we still have CUs to encode to
			// BTF because of ordering requirements.
			continue;
		case CU__UNPROCESSED:
			// The first entry isn't loaded, signal the
			// caller to return and try another day, as we
			// need to respect the original DWARF CU ordering.
			goto out;
		}
	}
out:
	cu = NULL;
found:
	cus__unlock(cus);

	return cu;
}

bool cus__empty(const struct cus *cus)
{
	return list_empty(&cus->cus);
}

uint32_t cus__nr_entries(const struct cus *cus)
{
	return cus->nr_entries;
}

void __cus__remove(struct cus *cus, struct cu *cu)
{
	cus->nr_entries--;
	list_del_init(&cu->node);
}

void cus__remove(struct cus *cus, struct cu *cu)
{
	cus__lock(cus);
	__cus__remove(cus, cu);
	cus__unlock(cus);
}

void __cus__add(struct cus *cus, struct cu *cu)
{
	cus->nr_entries++;
	list_add_tail(&cu->node, &cus->cus);
}

void cus__add(struct cus *cus, struct cu *cu)
{
	cus__lock(cus);
	__cus__add(cus, cu);
	cus__unlock(cus);

	cu__find_class_holes(cu);
}

static void ptr_table__init(struct ptr_table *pt)
{
	pt->entries = NULL;
	pt->nr_entries = pt->allocated_entries = 0;
}

static void ptr_table__exit(struct ptr_table *pt)
{
	zfree(&pt->entries);
}

static int ptr_table__add(struct ptr_table *pt, void *ptr, uint32_t *idxp)
{
	const uint32_t nr_entries = pt->nr_entries + 1;
	const uint32_t rc = pt->nr_entries;

	if (nr_entries > pt->allocated_entries) {
		uint32_t allocated_entries = pt->allocated_entries + 2048;
		void *entries = realloc(pt->entries,
					sizeof(void *) * allocated_entries);
		if (entries == NULL)
			return -ENOMEM;

		/* Zero out the new range */
		memset(entries + pt->allocated_entries * sizeof(void *), 0,
		       (allocated_entries - pt->allocated_entries) * sizeof(void *));

		pt->allocated_entries = allocated_entries;
		pt->entries = entries;
	}

	pt->entries[rc] = ptr;
	pt->nr_entries = nr_entries;
	*idxp = rc;
	return 0;
}

static int ptr_table__add_with_id(struct ptr_table *pt, void *ptr,
				  uint32_t id)
{
	/* Assume we won't be fed with the same id more than once */
	if (id >= pt->allocated_entries) {
		uint32_t allocated_entries = roundup(id + 1, 2048);
		void *entries = realloc(pt->entries,
					sizeof(void *) * allocated_entries);
		if (entries == NULL)
			return -ENOMEM;

		/* Zero out the new range */
		memset(entries + pt->allocated_entries * sizeof(void *), 0,
		       (allocated_entries - pt->allocated_entries) * sizeof(void *));

		pt->allocated_entries = allocated_entries;
		pt->entries = entries;
	}

	pt->entries[id] = ptr;
	if (id >= pt->nr_entries)
		pt->nr_entries = id + 1;
	return 0;
}

static void *ptr_table__entry(const struct ptr_table *pt, uint32_t id)
{
	return id >= pt->nr_entries ? NULL : pt->entries[id];
}

static void cu__insert_function(struct cu *cu, struct tag *tag)
{
	struct function *function = tag__function(tag);
        struct rb_node **p = &cu->functions.rb_node;
        struct rb_node *parent = NULL;
        struct function *f;

        while (*p != NULL) {
                parent = *p;
                f = rb_entry(parent, struct function, rb_node);
                if (function->lexblock.ip.addr < f->lexblock.ip.addr)
                        p = &(*p)->rb_left;
                else
                        p = &(*p)->rb_right;
        }
        rb_link_node(&function->rb_node, parent, p);
        rb_insert_color(&function->rb_node, &cu->functions);
}

int cu__table_add_tag(struct cu *cu, struct tag *tag, uint32_t *type_id)
{
	struct ptr_table *pt = &cu->tags_table;

	if (tag__is_tag_type(tag))
		pt = &cu->types_table;
	else if (tag__is_function(tag)) {
		pt = &cu->functions_table;
		cu__insert_function(cu, tag);
	}

	return ptr_table__add(pt, tag, type_id) ? -ENOMEM : 0;
}

int cu__table_nullify_type_entry(struct cu *cu, uint32_t id)
{
	return ptr_table__add_with_id(&cu->types_table, NULL, id);
}

int cu__add_tag(struct cu *cu, struct tag *tag, uint32_t *id)
{
	int err = cu__table_add_tag(cu, tag, id);

	if (err == 0)
		list_add_tail(&tag->node, &cu->tags);

	return err;
}

int cu__table_add_tag_with_id(struct cu *cu, struct tag *tag, uint32_t id)
{
	struct ptr_table *pt = &cu->tags_table;

	if (tag__is_tag_type(tag)) {
		pt = &cu->types_table;
	} else if (tag__is_function(tag)) {
		pt = &cu->functions_table;
		cu__insert_function(cu, tag);
	}

	return ptr_table__add_with_id(pt, tag, id);
}

int cu__add_tag_with_id(struct cu *cu, struct tag *tag, uint32_t id)
{
	int err = cu__table_add_tag_with_id(cu, tag, id);

	if (err == 0)
		list_add_tail(&tag->node, &cu->tags);

	return err;
}

int cus__fprintf_ptr_table_stats_csv_header(FILE *fp)
{
	return fprintf(fp, "# cu,tags,allocated_tags,types,allocated_types,functions,allocated_functions\n");
}

int cu__fprintf_ptr_table_stats_csv(struct cu *cu, FILE *fp)
{
	int printed = fprintf(fp, "%s,%u,%u,%u,%u,%u,%u\n", cu->name,
			      cu->tags_table.nr_entries, cu->tags_table.allocated_entries,
			      cu->types_table.nr_entries, cu->types_table.allocated_entries,
			      cu->functions_table.nr_entries, cu->functions_table.allocated_entries);

	return printed;
}

struct cu *cu__new(const char *name, uint8_t addr_size,
		   const unsigned char *build_id, int build_id_len,
		   const char *filename, bool use_obstack)
{
	struct cu *cu = zalloc(sizeof(*cu) + build_id_len);

	if (cu != NULL) {
		uint32_t void_id;

		cu->use_obstack = use_obstack;
		if (cu->use_obstack)
			obstack_init(&cu->obstack);

		cu->name = strdup(name);
		if (cu->name == NULL)
			goto out_free;

		cu->filename = strdup(filename);
		if (cu->filename == NULL)
			goto out_free_name;

		ptr_table__init(&cu->tags_table);
		ptr_table__init(&cu->types_table);
		ptr_table__init(&cu->functions_table);
		/*
		 * the first entry is historically associated with void,
		 * so make sure we don't use it
		 */
		if (ptr_table__add(&cu->types_table, NULL, &void_id) < 0)
			goto out_free_filename;

		cu->functions = RB_ROOT;

		cu->dfops	= NULL;
		INIT_LIST_HEAD(&cu->tags);
		INIT_LIST_HEAD(&cu->tool_list);

		cu->addr_size = addr_size;
		cu->extra_dbg_info = 0;

		cu->state = CU__UNPROCESSED;

		cu->nr_inline_expansions   = 0;
		cu->size_inline_expansions = 0;
		cu->nr_structures_changed  = 0;
		cu->nr_functions_changed   = 0;
		cu->max_len_changed_item   = 0;
		cu->function_bytes_added   = 0;
		cu->function_bytes_removed = 0;
		cu->build_id_len	   = build_id_len;
		if (build_id_len > 0)
			memcpy(cu->build_id, build_id, build_id_len);
		cu->priv = NULL;
	}

	return cu;

out_free_filename:
	zfree(&cu->filename);
out_free_name:
	zfree(&cu->name);
out_free:
	free(cu);
	return NULL;
}

void cu__delete(struct cu *cu)
{
	if (cu == NULL)
		return;

	ptr_table__exit(&cu->tags_table);
	ptr_table__exit(&cu->types_table);
	ptr_table__exit(&cu->functions_table);
	if (cu->dfops && cu->dfops->cu__delete)
		cu->dfops->cu__delete(cu);

	if (cu->use_obstack)
		obstack_free(&cu->obstack, NULL);

	zfree(&cu->filename);
	zfree(&cu->name);
	free(cu);
}

bool cu__same_build_id(const struct cu *cu, const struct cu *other)
{
	return cu->build_id_len != 0 &&
	       cu->build_id_len == other->build_id_len &&
	       memcmp(cu->build_id, other->build_id, cu->build_id_len) == 0;
}

struct tag *cu__function(const struct cu *cu, const uint32_t id)
{
	return cu ? ptr_table__entry(&cu->functions_table, id) : NULL;
}

struct tag *cu__tag(const struct cu *cu, const uint32_t id)
{
	return cu ? ptr_table__entry(&cu->tags_table, id) : NULL;
}

struct tag *cu__type(const struct cu *cu, const type_id_t id)
{
	return cu ? ptr_table__entry(&cu->types_table, id) : NULL;
}

struct tag *cu__find_first_typedef_of_type(const struct cu *cu,
					   const type_id_t type)
{
	uint32_t id;
	struct tag *pos;

	if (cu == NULL || type == 0)
		return NULL;

	cu__for_each_type(cu, id, pos)
		if (tag__is_typedef(pos) && pos->type == type)
			return pos;

	return NULL;
}

struct tag *cu__find_base_type_by_name(const struct cu *cu,
				       const char *name, type_id_t *idp)
{
	uint32_t id;
	struct tag *pos;

	if (cu == NULL || name == NULL)
		return NULL;

	cu__for_each_type(cu, id, pos) {
		if (pos->tag != DW_TAG_base_type)
			continue;

		const struct base_type *bt = tag__base_type(pos);
		char bf[64];
		const char *bname = base_type__name(bt, bf, sizeof(bf));
		if (!bname || strcmp(bname, name) != 0)
			continue;

		if (idp != NULL)
			*idp = id;
		return pos;
	}

	return NULL;
}

struct tag *cu__find_base_type_by_name_and_size(const struct cu *cu, const char *name,
						uint16_t bit_size, type_id_t *idp)
{
	uint32_t id;
	struct tag *pos;

	if (name == NULL)
		return NULL;

	cu__for_each_type(cu, id, pos) {
		if (pos->tag == DW_TAG_base_type) {
			const struct base_type *bt = tag__base_type(pos);
			char bf[64];

			if (bt->bit_size == bit_size &&
			    strcmp(base_type__name(bt, bf, sizeof(bf)), name) == 0) {
				if (idp != NULL)
					*idp = id;
				return pos;
			}
		}
	}

	return NULL;
}

struct tag *cu__find_enumeration_by_name_and_size(const struct cu *cu, const char *name,
						  uint16_t bit_size, type_id_t *idp)
{
	uint32_t id;
	struct tag *pos;

	if (name == NULL)
		return NULL;

	cu__for_each_type(cu, id, pos) {
		if (pos->tag == DW_TAG_enumeration_type) {
			const struct type *t = tag__type(pos);

			if (t->size == bit_size &&
			    strcmp(type__name(t), name) == 0) {
				if (idp != NULL)
					*idp = id;
				return pos;
			}
		}
	}

	return NULL;
}

struct tag *cu__find_enumeration_by_name(const struct cu *cu, const char *name, type_id_t *idp)
{
	uint32_t id;
	struct tag *pos;

	if (name == NULL)
		return NULL;

	cu__for_each_type(cu, id, pos) {
		if (pos->tag == DW_TAG_enumeration_type) {
			const struct type *type = tag__type(pos);
			const char *tname = type__name(type);

			if (tname && strcmp(tname, name) == 0) {
				if (idp != NULL)
					*idp = id;
				return pos;
			}
		}
	}

	return NULL;
}

struct tag *cu__find_type_by_name(const struct cu *cu, const char *name, const int include_decls, type_id_t *idp)
{
	if (cu == NULL || name == NULL)
		return NULL;

	uint32_t id;
	struct tag *pos;
	cu__for_each_type(cu, id, pos) {
		struct type *type;

		if (!tag__is_type(pos))
			continue;

		type = tag__type(pos);
		const char *tname = type__name(type);
		if (tname && strcmp(tname, name) == 0) {
			if (!type->declaration)
				goto found;

			if (include_decls)
				goto found;
		}
	}

	return NULL;
found:
	if (idp != NULL)
		*idp = id;
	return pos;
}

struct tag *cus__find_type_by_name(struct cus *cus, struct cu **cu, const char *name,
				   const int include_decls, type_id_t *id)
{
	struct cu *pos;
	struct tag *tag = NULL;

	cus__lock(cus);

	list_for_each_entry(pos, &cus->cus, node) {
		tag = cu__find_type_by_name(pos, name, include_decls, id);
		if (tag != NULL) {
			if (cu != NULL)
				*cu = pos;
			break;
		}
	}

	cus__unlock(cus);

	return tag;
}

static struct tag *__cu__find_struct_by_name(const struct cu *cu, const char *name,
					     const int include_decls, bool unions, type_id_t *idp)
{
	if (cu == NULL || name == NULL)
		return NULL;

	uint32_t id;
	struct tag *pos;
	cu__for_each_type(cu, id, pos) {
		struct type *type;

		if (!(tag__is_struct(pos) || (unions && tag__is_union(pos))))
			continue;

		type = tag__type(pos);
		const char *tname = type__name(type);
		if (tname && strcmp(tname, name) == 0) {
			if (!type->declaration)
				goto found;

			if (include_decls)
				goto found;
		}
	}

	return NULL;
found:
	if (idp != NULL)
		*idp = id;
	return pos;
}

struct tag *cu__find_struct_by_name(const struct cu *cu, const char *name,
				    const int include_decls, type_id_t *idp)
{
	return __cu__find_struct_by_name(cu, name, include_decls, false, idp);
}

struct tag *cu__find_struct_or_union_by_name(const struct cu *cu, const char *name,
						    const int include_decls, type_id_t *idp)
{
	return __cu__find_struct_by_name(cu, name, include_decls, true, idp);
}

static struct tag *__cus__find_struct_by_name(struct cus *cus, struct cu **cu, const char *name,
					      const int include_decls, bool unions, type_id_t *id)
{
	struct tag *tag = NULL;
	struct cu *pos;

	cus__lock(cus);

	list_for_each_entry(pos, &cus->cus, node) {
		struct tag *tag = __cu__find_struct_by_name(pos, name, include_decls, unions, id);
		if (tag != NULL) {
			if (cu != NULL)
				*cu = pos;
			break;
		}
	}

	cus__unlock(cus);

	return tag;
}

struct tag *cus__find_struct_by_name(struct cus *cus, struct cu **cu, const char *name,
				     const int include_decls, type_id_t *idp)
{
	return __cus__find_struct_by_name(cus, cu, name, include_decls, false, idp);
}

struct tag *cus__find_struct_or_union_by_name(struct cus *cus, struct cu **cu, const char *name,
					      const int include_decls, type_id_t *idp)
{
	return __cus__find_struct_by_name(cus, cu, name, include_decls, true, idp);
}

struct function *cu__find_function_at_addr(const struct cu *cu,
					   uint64_t addr)
{
        struct rb_node *n;

        if (cu == NULL)
                return NULL;

        n = cu->functions.rb_node;

        while (n) {
                struct function *f = rb_entry(n, struct function, rb_node);

                if (addr < f->lexblock.ip.addr)
                        n = n->rb_left;
                else if (addr >= f->lexblock.ip.addr + f->lexblock.size)
                        n = n->rb_right;
                else
                        return f;
        }

        return NULL;

}

struct function *cus__find_function_at_addr(struct cus *cus, uint64_t addr, struct cu **cu)
{
	struct function *f = NULL;
	struct cu *pos;

	cus__lock(cus);

	list_for_each_entry(pos, &cus->cus, node) {
		f = cu__find_function_at_addr(pos, addr);

		if (f != NULL) {
			if (cu != NULL)
				*cu = pos;
			break;
		}
	}

	cus__unlock(cus);

	return f;
}

static struct cu *__cus__find_cu_by_name(struct cus *cus, const char *name)
{
	struct cu *pos;

	list_for_each_entry(pos, &cus->cus, node)
		if (pos->name && strcmp(pos->name, name) == 0)
			goto out;

	pos = NULL;
out:
	return pos;
}

struct cu *cus__find_cu_by_name(struct cus *cus, const char *name)
{
	struct cu *pos;

	cus__lock(cus);

	pos = __cus__find_cu_by_name(cus, name);

	cus__unlock(cus);

	return pos;
}

struct cu *cus__find_pair(struct cus *cus, const char *name)
{
	struct cu *cu;

	cus__lock(cus);

	if (cus->nr_entries == 1)
		cu = list_first_entry(&cus->cus, struct cu, node);
	else
		cu = __cus__find_cu_by_name(cus, name);

	cus__unlock(cus);

	return cu;
}

struct tag *cu__find_function_by_name(const struct cu *cu, const char *name)
{
	if (cu == NULL || name == NULL)
		return NULL;

	uint32_t id;
	struct function *pos;
	cu__for_each_function(cu, id, pos) {
		const char *fname = function__name(pos);
		if (fname && strcmp(fname, name) == 0)
			return function__tag(pos);
	}

	return NULL;
}

static size_t array_type__nr_entries(const struct array_type *at)
{
	int i;
	size_t nr_entries = 1;

	for (i = 0; i < at->dimensions; ++i)
		nr_entries *= at->nr_entries[i];

	return nr_entries;
}

size_t tag__size(const struct tag *tag, const struct cu *cu)
{
	size_t size;

	switch (tag->tag) {
	case DW_TAG_string_type:
		return tag__string_type(tag)->nr_entries;
	case DW_TAG_member: {
		struct class_member *member = tag__class_member(tag);
		if (member->is_static)
			return 0;
		/* Is it cached already? */
		size = member->byte_size;
		if (size != 0)
			return size;
		break;
	}
	case DW_TAG_pointer_type:
	case DW_TAG_reference_type:	return cu->addr_size;
	case DW_TAG_base_type:		return base_type__size(tag);
	case DW_TAG_enumeration_type:	return tag__type(tag)->size / 8;
	case DW_TAG_subroutine_type:	return tag__ftype(tag)->byte_size ?: cu->addr_size;
	}

	if (tag->type == 0) { /* struct class: unions, structs */
		struct type *type = tag__type(tag);

		/* empty base optimization trick */
		if (type->size == 1 && type->nr_members == 0)
			size = 0;
		else
			size = tag__type(tag)->size;
	} else {
		const struct tag *type = cu__type(cu, tag->type);

		if (type == NULL) {
			tag__id_not_found_fprintf(stderr, tag->type);
			return -1;
		} else if (tag__has_type_loop(tag, type, NULL, 0, NULL))
			return -1;
		size = tag__size(type, cu);
	}

	if (tag->tag == DW_TAG_array_type)
		return size * array_type__nr_entries(tag__array_type(tag));

	return size;
}

const char *variable__name(const struct variable *var)
{
	return var->name;
}

const char *variable__type_name(const struct variable *var,
				const struct cu *cu,
				char *bf, size_t len)
{
	const struct tag *tag = cu__type(cu, var->ip.tag.type);
	return tag != NULL ? tag__name(tag, cu, bf, len, NULL) : NULL;
}

void class_member__delete(struct class_member *member)
{
	free(member);
}

static struct class_member *class_member__clone(const struct class_member *from)
{
	struct class_member *member = malloc(sizeof(*member));

	if (member != NULL)
		memcpy(member, from, sizeof(*member));

	return member;
}

static void type__delete_class_members(struct type *type)
{
	struct class_member *pos, *next;

	type__for_each_tag_safe_reverse(type, pos, next) {
		list_del_init(&pos->tag.node);
		class_member__delete(pos);
	}
}

void class__delete(struct class *class)
{
	if (class == NULL)
		return;

	type__delete_class_members(&class->type);
	free(class);
}

void type__delete(struct type *type)
{
	if (type == NULL)
		return;

	type__delete_class_members(type);

	if (type->suffix_disambiguation)
		zfree(&type->namespace.name);

	free(type);
}

static void enumerator__delete(struct enumerator *enumerator)
{
	free(enumerator);
}

void enumeration__delete(struct type *type)
{
	struct enumerator *pos, *n;

	if (type == NULL)
		return;

	type__for_each_enumerator_safe_reverse(type, pos, n) {
		list_del_init(&pos->tag.node);
		enumerator__delete(pos);
	}

	if (type->suffix_disambiguation)
		zfree(&type->namespace.name);

	free(type);
}

void class__add_vtable_entry(struct class *class, struct function *vtable_entry)
{
	++class->nr_vtable_entries;
	list_add_tail(&vtable_entry->vtable_node, &class->vtable);
}

void namespace__add_tag(struct namespace *space, struct tag *tag)
{
	++space->nr_tags;
	list_add_tail(&tag->node, &space->tags);
}

void type__add_member(struct type *type, struct class_member *member)
{
	if (member->is_static)
		++type->nr_static_members;
	else
		++type->nr_members;
	namespace__add_tag(&type->namespace, &member->tag);
}

struct class_member *type__last_member(struct type *type)
{
	struct class_member *pos;

	list_for_each_entry_reverse(pos, &type->namespace.tags, tag.node)
		if (pos->tag.tag == DW_TAG_member)
			return pos;
	return NULL;
}

static int type__clone_members(struct type *type, const struct type *from)
{
	struct class_member *pos;

	type->nr_members = type->nr_static_members = 0;
	INIT_LIST_HEAD(&type->namespace.tags);

	type__for_each_member(from, pos) {
		struct class_member *clone = class_member__clone(pos);

		if (clone == NULL)
			return -1;
		type__add_member(type, clone);
	}

	return 0;
}

struct class *class__clone(const struct class *from, const char *new_class_name)
{
	struct class *class = malloc(sizeof(*class));

	 if (class != NULL) {
		memcpy(class, from, sizeof(*class));
		if (new_class_name != NULL) {
			class->type.namespace.name = strdup(new_class_name);
			if (class->type.namespace.name == NULL) {
				free(class);
				return NULL;
			}
		}
		if (type__clone_members(&class->type, &from->type) != 0) {
			class__delete(class);
			class = NULL;
		}
	}

	return class;
}

void enumeration__add(struct type *type, struct enumerator *enumerator)
{
	++type->nr_members;
	namespace__add_tag(&type->namespace, &enumerator->tag);
}

void lexblock__add_lexblock(struct lexblock *block, struct lexblock *child)
{
	++block->nr_lexblocks;
	list_add_tail(&child->ip.tag.node, &block->tags);
}

const char *function__name(struct function *func)
{
	return func->name;
}

static void parameter__delete(struct parameter *parm)
{
	free(parm);
}

void ftype__delete(struct ftype *type)
{
	struct parameter *pos, *n;

	if (type == NULL)
		return;

	ftype__for_each_parameter_safe_reverse(type, pos, n) {
		list_del_init(&pos->tag.node);
		parameter__delete(pos);
	}
	free(type);
}

void function__delete(struct function *func)
{
	if (func == NULL)
		return;

	lexblock__delete_tags(&func->lexblock.ip.tag);
	ftype__delete(&func->proto);
}

int ftype__has_parm_of_type(const struct ftype *ftype, const type_id_t target,
			    const struct cu *cu)
{
	struct parameter *pos;

	if (ftype->tag.tag == DW_TAG_subprogram) {
		struct function *func = (struct function *)ftype;

		if (func->btf)
			ftype = tag__ftype(cu__type(cu, ftype->tag.type));
	}

	ftype__for_each_parameter(ftype, pos) {
		struct tag *type = cu__type(cu, pos->tag.type);

		if (type != NULL && tag__is_pointer(type)) {
			if (type->type == target)
				return 1;
		}
	}
	return 0;
}

void ftype__add_parameter(struct ftype *ftype, struct parameter *parm)
{
	++ftype->nr_parms;
	list_add_tail(&parm->tag.node, &ftype->parms);
}

void lexblock__add_tag(struct lexblock *block, struct tag *tag)
{
	list_add_tail(&tag->node, &block->tags);
}

void lexblock__add_inline_expansion(struct lexblock *block,
				    struct inline_expansion *exp)
{
	++block->nr_inline_expansions;
	block->size_inline_expansions += exp->size;
	lexblock__add_tag(block, &exp->ip.tag);
}

void lexblock__add_variable(struct lexblock *block, struct variable *var)
{
	++block->nr_variables;
	lexblock__add_tag(block, &var->ip.tag);
}

void lexblock__add_label(struct lexblock *block, struct label *label)
{
	++block->nr_labels;
	lexblock__add_tag(block, &label->ip.tag);
}

const struct class_member *class__find_bit_hole(const struct class *class,
					    const struct class_member *trailer,
						const uint16_t bit_hole_size)
{
	struct class_member *pos;
	const size_t byte_hole_size = bit_hole_size / 8;

	type__for_each_data_member(&class->type, pos)
		if (pos == trailer)
			break;
		else if (pos->hole >= byte_hole_size ||
			 pos->bit_hole >= bit_hole_size)
			return pos;

	return NULL;
}

void class__find_holes(struct class *class)
{
	const struct type *ctype = &class->type;
	struct class_member *pos, *last = NULL;
	uint32_t cur_bitfield_end = ctype->size * 8, cur_bitfield_size = 0;
	int bit_holes = 0, byte_holes = 0;
	uint32_t bit_start, bit_end, last_seen_bit = 0;
	bool in_bitfield = false;

	if (!tag__is_struct(class__tag(class)))
		return;

	if (class->holes_searched)
		return;

	class->nr_holes = 0;
	class->nr_bit_holes = 0;

	type__for_each_member(ctype, pos) {
		/* XXX for now just skip these */
		if (pos->tag.tag == DW_TAG_inheritance &&
		    pos->virtuality == DW_VIRTUALITY_virtual)
			continue;

		if (pos->is_static)
			continue;

		pos->bit_hole = 0;
		pos->hole = 0;

		bit_start = pos->bit_offset;
		if (pos->bitfield_size) {
			bit_end = bit_start + pos->bitfield_size;
		} else {
			bit_end = bit_start + pos->byte_size * 8;
		}

		bit_holes = 0;
		byte_holes = 0;
		if (in_bitfield) {
			/* check if we have some trailing bitfield bits left */
			int bitfield_end = min(bit_start, cur_bitfield_end);
			bit_holes = bitfield_end - last_seen_bit;
			last_seen_bit = bitfield_end;
		}
		if (pos->bitfield_size) {
			uint32_t aligned_start = pos->byte_offset * 8;
			/* we can have some alignment byte padding left,
			 * but we need to be careful about bitfield spanning
			 * multiple aligned boundaries */
			if (last_seen_bit < aligned_start && aligned_start <= bit_start) {
				byte_holes = pos->byte_offset - last_seen_bit / 8;
				last_seen_bit = aligned_start;
			}
			bit_holes += bit_start - last_seen_bit;
		} else {
			byte_holes = bit_start/8 - last_seen_bit/8;
		}
		last_seen_bit = bit_end;

		if (pos->bitfield_size) {
			in_bitfield = true;
			/* if it's a new bitfield set or same, but with
			 * bigger-sized type, readjust size and end bit */
			if (bit_end > cur_bitfield_end || pos->bit_size > cur_bitfield_size) {
				cur_bitfield_size = pos->bit_size;
				cur_bitfield_end = pos->byte_offset * 8 + cur_bitfield_size;
				/*
				 * if current bitfield "borrowed" bits from
				 * previous bitfield, it will have byte_offset
				 * of previous bitfield's backing integral
				 * type, but its end bit will be in a new
				 * bitfield "area", so we need to adjust
				 * bitfield end appropriately
				 */
				if (bit_end > cur_bitfield_end) {
					cur_bitfield_end += cur_bitfield_size;
				}
			}
		} else {
			in_bitfield = false;
			cur_bitfield_size = 0;
			cur_bitfield_end = bit_end;
		}

		if (last) {
			last->hole = byte_holes;
			last->bit_hole = bit_holes;
		} else {
			class->pre_hole = byte_holes;
			class->pre_bit_hole = bit_holes;
		}
		if (bit_holes)
			class->nr_bit_holes++;
		if (byte_holes > 0)
			class->nr_holes++;

		last = pos;
	}

	if (in_bitfield) {
		int bitfield_end = min(ctype->size * 8, cur_bitfield_end);
		class->bit_padding = bitfield_end - last_seen_bit;
		last_seen_bit = bitfield_end;
	} else {
		class->bit_padding = 0;
	}
	class->padding = ctype->size - last_seen_bit / 8;

	class->holes_searched = true;
}

static size_t type__natural_alignment(struct type *type, const struct cu *cu);

size_t tag__natural_alignment(struct tag *tag, const struct cu *cu)
{
	size_t natural_alignment = 1;

	if (tag == NULL) // Maybe its a non supported type, like DW_TAG_subrange_type, ADA stuff
		return natural_alignment;

	if (tag__is_pointer(tag)) {
		natural_alignment = cu->addr_size;
	} else if (tag->tag == DW_TAG_base_type) {
		natural_alignment = base_type__size(tag);
	} else if (tag__is_enumeration(tag)) {
		natural_alignment = tag__type(tag)->size / 8;
	} else if (tag__is_struct(tag) || tag__is_union(tag)) {
		natural_alignment = type__natural_alignment(tag__type(tag), cu);
	} else if (tag->tag == DW_TAG_array_type) {
		tag = tag__strip_typedefs_and_modifiers(tag, cu);
		if (tag != NULL) // Maybe its a non supported type, like DW_TAG_subrange_type, ADA stuff
			natural_alignment = tag__natural_alignment(tag, cu);
	}

	/*
	 * Cope with zero sized types, like:
	 *
	 *	struct u64_stats_sync {
	 *	#if BITS_PER_LONG==32 && defined(CONFIG_SMP)
	 *	seqcount_t      seq;
	 *	#endif
	 *	};
	 *
	 */
	return natural_alignment ?: 1;
}

static size_t type__natural_alignment(struct type *type, const struct cu *cu)
{
	struct class_member *member;

	if (type->natural_alignment != 0)
		return type->natural_alignment;

	type__for_each_member(type, member) {
		/* XXX for now just skip these */
		if (member->tag.tag == DW_TAG_inheritance &&
		    member->virtuality == DW_VIRTUALITY_virtual)
			continue;
		if (member->is_static) continue;

		struct tag *member_type = tag__strip_typedefs_and_modifiers(&member->tag, cu);

		if (member_type == NULL) // Maybe its a DW_TAG_subrange_type, ADA stuff still not supported
			continue;

		size_t member_natural_alignment = tag__natural_alignment(member_type, cu);

		if (type->natural_alignment < member_natural_alignment)
			type->natural_alignment = member_natural_alignment;
	}

	return type->natural_alignment;
}

/*
 * Sometimes the only indication that a struct is __packed__ is for it to
 * appear embedded in another and at an offset that is not natural for it,
 * so, in !__packed__ parked struct, check for that and mark the types of
 * members at unnatural alignments.
 */
void type__check_structs_at_unnatural_alignments(struct type *type, const struct cu *cu)
{
	struct class_member *member;

	type__for_each_member(type, member) {
		struct tag *member_type = tag__strip_typedefs_and_modifiers(&member->tag, cu);

		if (member_type == NULL) {
			// just be conservative and ignore
			// Found first when a FORTRAN95 DWARF file was processed
			// and the DW_TAG_string_type wasn't yet supported
			continue;
		}

		if (!tag__is_struct(member_type))
			continue;

		size_t natural_alignment = tag__natural_alignment(member_type, cu);

		/* Would this break the natural alignment */
		if ((member->byte_offset % natural_alignment) != 0) {
			struct class *cls = tag__class(member_type);

			cls->is_packed = true;
			cls->type.packed_attributes_inferred = 1;
		}
       }
}

bool class__infer_packed_attributes(struct class *cls, const struct cu *cu)
{
	struct type *ctype = &cls->type;
	struct class_member *pos;
	uint16_t max_natural_alignment = 1;

	if (!tag__is_struct(class__tag(cls)))
		return false;

	if (ctype->packed_attributes_inferred)
		return cls->is_packed;

	class__find_holes(cls);

	if (cls->padding != 0 || cls->nr_holes != 0) {
		type__check_structs_at_unnatural_alignments(ctype, cu);
		cls->is_packed = false;
		goto out;
	}

	type__for_each_member(ctype, pos) {
		/* XXX for now just skip these */
		if (pos->tag.tag == DW_TAG_inheritance &&
		    pos->virtuality == DW_VIRTUALITY_virtual)
			continue;

		if (pos->is_static)
			continue;

		struct tag *member_type = tag__strip_typedefs_and_modifiers(&pos->tag, cu);
		size_t natural_alignment = tag__natural_alignment(member_type, cu);

		/* Always aligned: */
		if (natural_alignment == sizeof(char))
			continue;

		if (max_natural_alignment < natural_alignment)
			max_natural_alignment = natural_alignment;

		if ((pos->byte_offset % natural_alignment) == 0)
			continue;

		cls->is_packed = true;
		goto out;
	}

	if ((max_natural_alignment != 1 && ctype->alignment == 1) ||
	    (class__size(cls) % max_natural_alignment) != 0)
		cls->is_packed = true;

out:
	ctype->packed_attributes_inferred = 1;

	return cls->is_packed;
}

/*
 * If structs embedded in unions, nameless or not, have a size which isn't
 * isn't a multiple of the union size, then it must be packed, even if
 * it has no holes nor padding, as an array of such unions would have the
 * natural alignments of non-multiple structs inside it broken.
 */
void union__infer_packed_attributes(struct type *type, const struct cu *cu)
{
	const uint32_t union_size = type->size;
	struct class_member *member;

	if (type->packed_attributes_inferred)
		return;

	type__for_each_member(type, member) {
		struct tag *member_type = tag__strip_typedefs_and_modifiers(&member->tag, cu);

		if (!tag__is_struct(member_type))
			continue;

		size_t natural_alignment = tag__natural_alignment(member_type, cu);

		/* Would this break the natural alignment */
		if ((union_size % natural_alignment) != 0) {
			struct class *cls = tag__class(member_type);

			cls->is_packed = true;
			cls->type.packed_attributes_inferred = 1;
		}
	}

	type->packed_attributes_inferred = 1;
}

/** class__has_hole_ge - check if class has a hole greater or equal to @size
 * @class - class instance
 * @size - hole size to check
 */
int class__has_hole_ge(const struct class *class, const uint16_t size)
{
	struct class_member *pos;

	if (class->nr_holes == 0)
		return 0;

	type__for_each_data_member(&class->type, pos)
		if (pos->hole >= size)
			return 1;

	return 0;
}

struct class_member *type__find_member_by_name(const struct type *type, const char *name)
{
	if (name == NULL)
		return NULL;

	struct class_member *pos;
	type__for_each_data_member(type, pos) {
		const char *curr_name = class_member__name(pos);
		if (curr_name && strcmp(curr_name, name) == 0)
			return pos;
	}

	return NULL;
}

static int strcommon(const char *a, const char *b)
{
	int i = 0;

	while (*a != '\0' && *a == *b) {
		++a;
		++b;
		++i;
	}

	return i;
}

static void enumeration__calc_prefix(struct type *enumeration)
{
	if (enumeration->member_prefix)
		return;

	const char *previous_name = NULL, *curr_name = "";
	int common_part = INT32_MAX;
	struct enumerator *entry;

	type__for_each_enumerator(enumeration, entry) {
		const char *curr_name = enumerator__name(entry);

		if (previous_name) {
			int curr_common_part = strcommon(curr_name, previous_name);
			if (common_part > curr_common_part)
				common_part = curr_common_part;

		}

		previous_name = curr_name;
	}

	enumeration->member_prefix     = NULL;
	enumeration->member_prefix_len = 0;

	if (common_part != INT32_MAX) {
		enumeration->member_prefix = strndup(curr_name, common_part);
		if (enumeration->member_prefix != NULL)
			enumeration->member_prefix_len = common_part;
	}
}

void enumerations__calc_prefix(struct list_head *enumerations)
{
	struct tag_cu_node *pos;

	list_for_each_entry(pos, enumerations, node)
		enumeration__calc_prefix(tag__type(pos->tc.tag));
}

uint32_t type__nr_members_of_type(const struct type *type, const type_id_t type_id)
{
	struct class_member *pos;
	uint32_t nr_members_of_type = 0;

	type__for_each_member(type, pos)
		if (pos->tag.type == type_id)
			++nr_members_of_type;

	return nr_members_of_type;
}

static void lexblock__account_inline_expansions(struct lexblock *block,
						const struct cu *cu)
{
	struct tag *pos, *type;

	if (block->nr_inline_expansions == 0)
		return;

	list_for_each_entry(pos, &block->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__function(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 *cu)
{
	struct tag *pos;
	struct function *fpos;

	list_for_each_entry(pos, &cu->tags, node) {
		if (!tag__is_function(pos))
			continue;
		fpos = tag__function(pos);
		lexblock__account_inline_expansions(&fpos->lexblock, cu);
		cu->nr_inline_expansions   += fpos->lexblock.nr_inline_expansions;
		cu->size_inline_expansions += fpos->lexblock.size_inline_expansions;
	}
}

static int list__for_all_tags(struct list_head *list, struct cu *cu,
			      int (*iterator)(struct tag *tag,
					      struct cu *cu, void *cookie),
			      void *cookie)
{
	struct tag *pos, *n;

	if (list_empty(list) || !list->next)
		return 0;

	list_for_each_entry_safe_reverse(pos, n, list, node) {
		if (tag__has_namespace(pos)) {
			struct namespace *space = tag__namespace(pos);

			/*
			 * See comment in type__for_each_enumerator, the
			 * enumerators (enum entries) are shared, but the
			 * enumeration tag must be deleted.
			 */
			if (!space->shared_tags &&
			    list__for_all_tags(&space->tags, cu,
					       iterator, cookie))
				return 1;
			/*
			 * vtable functions are already in the class tags list
			 */
		} else if (tag__is_function(pos)) {
			if (list__for_all_tags(&tag__ftype(pos)->parms,
					       cu, iterator, cookie))
				return 1;
			if (list__for_all_tags(&tag__function(pos)->lexblock.tags,
					       cu, iterator, cookie))
				return 1;
		} else if (pos->tag == DW_TAG_subroutine_type) {
			if (list__for_all_tags(&tag__ftype(pos)->parms,
					       cu, iterator, cookie))
				return 1;
		} else if (pos->tag == DW_TAG_lexical_block) {
			if (list__for_all_tags(&tag__lexblock(pos)->tags,
					       cu, iterator, cookie))
				return 1;
		}

		if (iterator(pos, cu, cookie))
			return 1;
	}
	return 0;
}

int cu__for_all_tags(struct cu *cu,
		     int (*iterator)(struct tag *tag,
				     struct cu *cu, void *cookie),
		     void *cookie)
{
	return list__for_all_tags(&cu->tags, cu, iterator, cookie);
}

void cus__for_each_cu(struct cus *cus,
		      int (*iterator)(struct cu *cu, void *cookie),
		      void *cookie,
		      struct cu *(*filter)(struct cu *cu))
{
	struct cu *pos;

	cus__lock(cus);

	list_for_each_entry(pos, &cus->cus, node) {
		struct cu *cu = pos;
		if (filter != NULL) {
			cu = filter(pos);
			if (cu == NULL)
				continue;
		}
		if (iterator(cu, cookie))
			break;
	}

	cus__unlock(cus);
}

int cus__load_dir(struct cus *cus, struct conf_load *conf,
		  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",
			 (int)(sizeof(pathname) - sizeof(entry->d_name) - 1), 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(cus, conf, pathname,
					    filename_mask, recursive);
			if (err != 0)
				break;
		} else if (fnmatch(filename_mask, entry->d_name, 0) == 0) {
			err = cus__load_file(cus, conf, pathname);
			if (err != 0)
				break;
		}
	}

	if (err == -1)
		puts(dirname);
	closedir(dir);
out:
	return err;
}

/*
 * This should really do demand loading of DSOs, STABS anyone? 8-)
 */
extern struct debug_fmt_ops dwarf__ops, ctf__ops, btf__ops;

static struct debug_fmt_ops *debug_fmt_table[] = {
	&dwarf__ops,
	&btf__ops,
	&ctf__ops,
	NULL,
};

static int debugging_formats__loader(const char *name)
{
	int i = 0;
	while (debug_fmt_table[i] != NULL) {
		if (strcmp(debug_fmt_table[i]->name, name) == 0)
			return i;
		++i;
	}
	return -1;
}

int cus__load_file(struct cus *cus, struct conf_load *conf,
		   const char *filename)
{
	int i = 0, err = 0;
	int loader;

	if (conf && conf->format_path != NULL) {
		char *fpath = strdup(conf->format_path);
		if (fpath == NULL)
			return -ENOMEM;
		char *fp = fpath;
		while (1) {
			char *sep = strchr(fp, ',');

			if (sep != NULL)
				*sep = '\0';

			err = -ENOTSUP;
			loader = debugging_formats__loader(fp);
			if (loader == -1)
				break;

			if (conf->conf_fprintf)
				conf->conf_fprintf->has_alignment_info = debug_fmt_table[loader]->has_alignment_info;

			err = 0;
			if (debug_fmt_table[loader]->load_file(cus, conf,
							       filename) == 0)
				break;

			err = -EINVAL;
			if (sep == NULL)
				break;

			fp = sep + 1;
		}
		free(fpath);
		return err;
	}

	while (debug_fmt_table[i] != NULL) {
		if (conf && conf->conf_fprintf)
			conf->conf_fprintf->has_alignment_info = debug_fmt_table[i]->has_alignment_info;
		if (debug_fmt_table[i]->load_file(cus, conf, filename) == 0)
			return 0;
		++i;
	}

	return -EINVAL;
}

#define BUILD_ID_SIZE   20
#define SBUILD_ID_SIZE  (BUILD_ID_SIZE * 2 + 1)

#define NOTE_ALIGN(sz) (((sz) + 3) & ~3)

#define NT_GNU_BUILD_ID	3

#ifndef min
#define min(x, y) ({				\
	typeof(x) _min1 = (x);			\
	typeof(y) _min2 = (y);			\
	(void) (&_min1 == &_min2);		\
	_min1 < _min2 ? _min1 : _min2; })
#endif

#ifndef DW_LANG_C89
#define DW_LANG_C89		0x0001
#endif
#ifndef DW_LANG_C
#define DW_LANG_C		0x0002
#endif
#ifndef DW_LANG_Ada83
#define DW_LANG_Ada83		0x0003
#endif
#ifndef DW_LANG_C_plus_plus
#define DW_LANG_C_plus_plus	0x0004
#endif
#ifndef DW_LANG_Cobol74
#define DW_LANG_Cobol74		0x0005
#endif
#ifndef DW_LANG_Cobol85
#define DW_LANG_Cobol85		0x0006
#endif
#ifndef DW_LANG_Fortran77
#define DW_LANG_Fortran77	0x0007
#endif
#ifndef DW_LANG_Fortran90
#define DW_LANG_Fortran90	0x0008
#endif
#ifndef DW_LANG_Pascal83
#define DW_LANG_Pascal83	0x0009
#endif
#ifndef DW_LANG_Modula2
#define DW_LANG_Modula2		0x000a
#endif
#ifndef DW_LANG_Java
#define DW_LANG_Java		0x000b
#endif
#ifndef DW_LANG_C99
#define DW_LANG_C99		0x000c
#endif
#ifndef DW_LANG_Ada95
#define DW_LANG_Ada95		0x000d
#endif
#ifndef DW_LANG_Fortran95
#define DW_LANG_Fortran95	0x000e
#endif
#ifndef DW_LANG_PLI
#define DW_LANG_PLI		0x000f
#endif
#ifndef DW_LANG_ObjC
#define DW_LANG_ObjC		0x0010
#endif
#ifndef DW_LANG_ObjC_plus_plus
#define DW_LANG_ObjC_plus_plus	0x0011
#endif
#ifndef DW_LANG_UPC
#define DW_LANG_UPC		0x0012
#endif
#ifndef DW_LANG_D
#define DW_LANG_D		0x0013
#endif
#ifndef DW_LANG_Python
#define DW_LANG_Python		0x0014
#endif
#ifndef DW_LANG_OpenCL
#define DW_LANG_OpenCL		0x0015
#endif
#ifndef DW_LANG_Go
#define DW_LANG_Go		0x0016
#endif
#ifndef DW_LANG_Modula3
#define DW_LANG_Modula3		0x0017
#endif
#ifndef DW_LANG_Haskell
#define DW_LANG_Haskell		0x0018
#endif
#ifndef DW_LANG_C_plus_plus_03
#define DW_LANG_C_plus_plus_03	0x0019
#endif
#ifndef DW_LANG_C_plus_plus_11
#define DW_LANG_C_plus_plus_11	0x001a
#endif
#ifndef DW_LANG_OCaml
#define DW_LANG_OCaml		0x001b
#endif
#ifndef DW_LANG_Rust
#define DW_LANG_Rust		0x001c
#endif
#ifndef DW_LANG_C11
#define DW_LANG_C11		0x001d
#endif
#ifndef DW_LANG_Swift
#define DW_LANG_Swift		0x001e
#endif
#ifndef DW_LANG_Julia
#define DW_LANG_Julia		0x001f
#endif
#ifndef DW_LANG_Dylan
#define DW_LANG_Dylan		0x0020
#endif
#ifndef DW_LANG_C_plus_plus_14
#define DW_LANG_C_plus_plus_14	0x0021
#endif
#ifndef DW_LANG_Fortran03
#define DW_LANG_Fortran03	0x0022
#endif
#ifndef DW_LANG_Fortran08
#define DW_LANG_Fortran08	0x0023
#endif
#ifndef DW_LANG_RenderScript
#define DW_LANG_RenderScript	0x0024
#endif
#ifndef DW_LANG_BLISS
#define DW_LANG_BLISS		0x0025
#endif

int lang__str2int(const char *lang)
{
	static const char *languages[] = {
	[DW_LANG_Ada83]		 = "ada83",
	[DW_LANG_Ada95]		 = "ada95",
	[DW_LANG_BLISS]		 = "bliss",
	[DW_LANG_C11]		 = "c11",
	[DW_LANG_C89]		 = "c89",
	[DW_LANG_C99]		 = "c99",
	[DW_LANG_C]		 = "c",
	[DW_LANG_Cobol74]	 = "cobol74",
	[DW_LANG_Cobol85]	 = "cobol85",
	[DW_LANG_C_plus_plus_03] = "c++03",
	[DW_LANG_C_plus_plus_11] = "c++11",
	[DW_LANG_C_plus_plus_14] = "c++14",
	[DW_LANG_C_plus_plus]	 = "c++",
	[DW_LANG_D]		 = "d",
	[DW_LANG_Dylan]		 = "dylan",
	[DW_LANG_Fortran03]	 = "fortran03",
	[DW_LANG_Fortran08]	 = "fortran08",
	[DW_LANG_Fortran77]	 = "fortran77",
	[DW_LANG_Fortran90]	 = "fortran90",
	[DW_LANG_Fortran95]	 = "fortran95",
	[DW_LANG_Go]		 = "go",
	[DW_LANG_Haskell]	 = "haskell",
	[DW_LANG_Java]		 = "java",
	[DW_LANG_Julia]		 = "julia",
	[DW_LANG_Modula2]	 = "modula2",
	[DW_LANG_Modula3]	 = "modula3",
	[DW_LANG_ObjC]		 = "objc",
	[DW_LANG_ObjC_plus_plus] = "objc++",
	[DW_LANG_OCaml]		 = "ocaml",
	[DW_LANG_OpenCL]	 = "opencl",
	[DW_LANG_Pascal83]	 = "pascal83",
	[DW_LANG_PLI]		 = "pli",
	[DW_LANG_Python]	 = "python",
	[DW_LANG_RenderScript]	 = "renderscript",
	[DW_LANG_Rust]		 = "rust",
	[DW_LANG_Swift]		 = "swift",
	[DW_LANG_UPC]		 = "upc",
	};

	if (strcasecmp(lang, "asm") == 0)
		return DW_LANG_Mips_Assembler;

	// c89 is the first, bliss is the last, see /usr/include/dwarf.h
	for (int id = DW_LANG_C89; id <= DW_LANG_BLISS; ++id)
		if (languages[id] && strcasecmp(lang, languages[id]) == 0)
			return id;

	return -1;
}


static int sysfs__read_build_id(const char *filename, void *build_id, size_t size)
{
	int fd, err = -1;

	if (size < BUILD_ID_SIZE)
		goto out;

	fd = open(filename, O_RDONLY);
	if (fd < 0)
		goto out;

	while (1) {
		char bf[BUFSIZ];
		GElf_Nhdr nhdr;
		size_t namesz, descsz;

		if (read(fd, &nhdr, sizeof(nhdr)) != sizeof(nhdr))
			break;

		namesz = NOTE_ALIGN(nhdr.n_namesz);
		descsz = NOTE_ALIGN(nhdr.n_descsz);
		if (nhdr.n_type == NT_GNU_BUILD_ID &&
		    nhdr.n_namesz == sizeof("GNU")) {
			if (read(fd, bf, namesz) != (ssize_t)namesz)
				break;
			if (memcmp(bf, "GNU", sizeof("GNU")) == 0) {
				size_t sz = min(descsz, size);
				if (read(fd, build_id, sz) == (ssize_t)sz) {
					memset(build_id + sz, 0, size - sz);
					err = 0;
					break;
				}
			} else if (read(fd, bf, descsz) != (ssize_t)descsz)
				break;
		} else {
			int n = namesz + descsz;

			if (n > (int)sizeof(bf)) {
				n = sizeof(bf);
				fprintf(stderr, "%s: truncating reading of build id in sysfs file %s: n_namesz=%u, n_descsz=%u.\n",
					 __func__, filename, nhdr.n_namesz, nhdr.n_descsz);
			}
			if (read(fd, bf, n) != n)
				break;
		}
	}
	close(fd);
out:
	return err;
}

static int elf_read_build_id(Elf *elf, void *bf, size_t size)
{
	int err = -1;
	GElf_Ehdr ehdr;
	GElf_Shdr shdr;
	Elf_Data *data;
	Elf_Scn *sec;
	Elf_Kind ek;
	void *ptr;

	if (size < BUILD_ID_SIZE)
		goto out;

	ek = elf_kind(elf);
	if (ek != ELF_K_ELF)
		goto out;

	if (gelf_getehdr(elf, &ehdr) == NULL) {
		fprintf(stderr, "%s: cannot get elf header.\n", __func__);
		goto out;
	}

	/*
	 * Check following sections for notes:
	 *   '.note.gnu.build-id'
	 *   '.notes'
	 *   '.note' (VDSO specific)
	 */
	do {
		sec = elf_section_by_name(elf, &shdr, ".note.gnu.build-id", NULL);
		if (sec)
			break;

		sec = elf_section_by_name(elf, &shdr, ".notes", NULL);
		if (sec)
			break;

		sec = elf_section_by_name(elf, &shdr, ".note", NULL);
		if (sec)
			break;

		return err;

	} while (0);

	data = elf_getdata(sec, NULL);
	if (data == NULL)
		goto out;

	ptr = data->d_buf;
	while (ptr < (data->d_buf + data->d_size)) {
		GElf_Nhdr *nhdr = ptr;
		size_t namesz = NOTE_ALIGN(nhdr->n_namesz),
		       descsz = NOTE_ALIGN(nhdr->n_descsz);
		const char *name;

		ptr += sizeof(*nhdr);
		name = ptr;
		ptr += namesz;
		if (nhdr->n_type == NT_GNU_BUILD_ID &&
		    nhdr->n_namesz == sizeof("GNU")) {
			if (memcmp(name, "GNU", sizeof("GNU")) == 0) {
				size_t sz = min(size, descsz);
				memcpy(bf, ptr, sz);
				memset(bf + sz, 0, size - sz);
				err = descsz;
				break;
			}
		}
		ptr += descsz;
	}

out:
	return err;
}

static int filename__read_build_id(const char *filename, void *bf, size_t size)
{
	int fd, err = -1;
	Elf *elf;

	if (size < BUILD_ID_SIZE)
		goto out;

	fd = open(filename, O_RDONLY);
	if (fd < 0)
		goto out;

	elf = elf_begin(fd, ELF_C_READ, NULL);
	if (elf == NULL) {
		fprintf(stderr, "%s: cannot read %s ELF file.\n", __func__, filename);
		goto out_close;
	}

	err = elf_read_build_id(elf, bf, size);

	elf_end(elf);
out_close:
	close(fd);
out:
	return err;
}

static int build_id__sprintf(const unsigned char *build_id, int len, char *bf)
{
	char *bid = bf;
	const unsigned char *raw = build_id;
	int i;

	for (i = 0; i < len; ++i) {
		sprintf(bid, "%02x", *raw);
		++raw;
		bid += 2;
	}

	return (bid - bf) + 1;
}

static int sysfs__sprintf_build_id(const char *root_dir, char *sbuild_id)
{
	char notes[PATH_MAX];
	unsigned char build_id[BUILD_ID_SIZE];
	int ret;

	if (!root_dir)
		root_dir = "";

	snprintf(notes, sizeof(notes), "%s/sys/kernel/notes", root_dir);

	ret = sysfs__read_build_id(notes, build_id, sizeof(build_id));
	if (ret < 0)
		return ret;

	return build_id__sprintf(build_id, sizeof(build_id), sbuild_id);
}

static int filename__sprintf_build_id(const char *pathname, char *sbuild_id)
{
	unsigned char build_id[BUILD_ID_SIZE];
	int ret;

	ret = filename__read_build_id(pathname, build_id, sizeof(build_id));
	if (ret < 0)
		return ret;
	else if (ret != sizeof(build_id))
		return -EINVAL;

	return build_id__sprintf(build_id, sizeof(build_id), sbuild_id);
}

static int vmlinux_path__nr_entries;
static char **vmlinux_path;

static void vmlinux_path__exit(void)
{
	while (--vmlinux_path__nr_entries >= 0)
		zfree(&vmlinux_path[vmlinux_path__nr_entries]);
	vmlinux_path__nr_entries = 0;

	zfree(&vmlinux_path);
}

static const char * const vmlinux_paths[] = {
	"vmlinux",
	"/boot/vmlinux"
};

static const char * const vmlinux_paths_upd[] = {
	"/boot/vmlinux-%s",
	"/usr/lib/debug/boot/vmlinux-%s",
	"/lib/modules/%s/build/vmlinux",
	"/usr/lib/debug/lib/modules/%s/vmlinux",
	"/usr/lib/debug/boot/vmlinux-%s.debug"
};

static int vmlinux_path__add(const char *new_entry)
{
	vmlinux_path[vmlinux_path__nr_entries] = strdup(new_entry);
	if (vmlinux_path[vmlinux_path__nr_entries] == NULL)
		return -1;
	++vmlinux_path__nr_entries;

	return 0;
}

static int vmlinux_path__init(void)
{
	struct utsname uts;
	char bf[PATH_MAX];
	char *kernel_version;
	unsigned int i;

	vmlinux_path = malloc(sizeof(char *) * (ARRAY_SIZE(vmlinux_paths) +
			      ARRAY_SIZE(vmlinux_paths_upd)));
	if (vmlinux_path == NULL)
		return -1;

	for (i = 0; i < ARRAY_SIZE(vmlinux_paths); i++)
		if (vmlinux_path__add(vmlinux_paths[i]) < 0)
			goto out_fail;

	if (uname(&uts) < 0)
		goto out_fail;

	kernel_version = uts.release;

	for (i = 0; i < ARRAY_SIZE(vmlinux_paths_upd); i++) {
		snprintf(bf, sizeof(bf), vmlinux_paths_upd[i], kernel_version);
		if (vmlinux_path__add(bf) < 0)
			goto out_fail;
	}

	return 0;

out_fail:
	vmlinux_path__exit();
	return -1;
}

static int cus__load_running_kernel(struct cus *cus, struct conf_load *conf)
{
	int i, err = 0;
	char running_sbuild_id[SBUILD_ID_SIZE];

	if ((!conf || conf->format_path == NULL || strncmp(conf->format_path, "btf", 3) == 0) &&
	    access("/sys/kernel/btf/vmlinux", R_OK) == 0) {
		int loader = debugging_formats__loader("btf");
		if (loader == -1)
			goto try_elf;

		if (conf && conf->conf_fprintf)
			conf->conf_fprintf->has_alignment_info = debug_fmt_table[loader]->has_alignment_info;

		if (debug_fmt_table[loader]->load_file(cus, conf, "/sys/kernel/btf/vmlinux") == 0)
			return 0;
	}
try_elf:
	elf_version(EV_CURRENT);
	vmlinux_path__init();

	sysfs__sprintf_build_id(NULL, running_sbuild_id);

	for (i = 0; i < vmlinux_path__nr_entries; ++i) {
		char sbuild_id[SBUILD_ID_SIZE];

		if (filename__sprintf_build_id(vmlinux_path[i], sbuild_id) > 0 &&
		    strcmp(sbuild_id, running_sbuild_id) == 0) {
			err = cus__load_file(cus, conf, vmlinux_path[i]);
			break;
		}
	}

	vmlinux_path__exit();

	return err;
}

int cus__load_files(struct cus *cus, struct conf_load *conf,
		    char *filenames[])
{
	int i = 0;

	while (filenames[i] != NULL) {
		int err = cus__load_file(cus, conf, filenames[i]);
		if (err) {
			errno = -err;
			return -++i;
		}
		++i;
	}

	return i ? 0 : cus__load_running_kernel(cus, conf);
}

int cus__fprintf_load_files_err(struct cus *cus __maybe_unused, const char *tool, char *argv[], int err, FILE *output)
{
	/* errno is not properly preserved in some cases, sigh */
	return fprintf(output, "%s: %s: %s\n", tool, argv[-err - 1],
		       errno ? strerror(errno) : "No debugging information found");
}

struct cus *cus__new(void)
{
	struct cus *cus = zalloc(sizeof(*cus));

	if (cus != NULL) {
		INIT_LIST_HEAD(&cus->cus);
		pthread_mutex_init(&cus->mutex, NULL);
	}

	return cus;
}

void cus__delete(struct cus *cus)
{
	struct cu *pos, *n;

	if (cus == NULL)
		return;

	cus__lock(cus);

	list_for_each_entry_safe(pos, n, &cus->cus, node) {
		list_del_init(&pos->node);
		cu__delete(pos);
	}

	if (cus->loader_exit)
		cus->loader_exit(cus);

	cus__unlock(cus);

	free(cus);
}

void cus__set_priv(struct cus *cus, void *priv)
{
	cus->priv = priv;
}

void *cus__priv(struct cus *cus)
{
	return cus->priv;
}

void cus__set_loader_exit(struct cus *cus, void (*loader_exit)(struct cus *cus))
{
	cus->loader_exit = loader_exit;
}

int dwarves__init(void)
{
	int i = 0;
	int err = 0;

	while (debug_fmt_table[i] != NULL) {
		if (debug_fmt_table[i]->init) {
			err = debug_fmt_table[i]->init();
			if (err)
				goto out_fail;
		}
		++i;
	}

	return 0;
out_fail:
	while (i-- != 0)
		if (debug_fmt_table[i]->exit)
			debug_fmt_table[i]->exit();
	return err;
}

void dwarves__exit(void)
{
	int i = 0;

	while (debug_fmt_table[i] != NULL) {
		if (debug_fmt_table[i]->exit)
			debug_fmt_table[i]->exit();
		++i;
	}
}

struct argp_state;

void dwarves_print_version(FILE *fp, struct argp_state *state __maybe_unused)
{
	fprintf(fp, "v%u.%u\n", DWARVES_MAJOR_VERSION, DWARVES_MINOR_VERSION);
}

bool print_numeric_version;

void dwarves_print_numeric_version(FILE *fp)
{
	fprintf(fp, "%u%u\n", DWARVES_MAJOR_VERSION, DWARVES_MINOR_VERSION);
}
