Blob Blame History Raw
/*
  SPDX-License-Identifier: GPL-2.0-only

  Copyright (C) 2006 Mandriva Conectiva S.A.
  Copyright (C) 2006 Arnaldo Carvalho de Melo <acme@mandriva.com>
*/

#include <argp.h>
#include <assert.h>
#include <dwarf.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#include "dwarves.h"
#include "dutil.h"

static int show_struct_diffs;
static int show_function_diffs;
static int verbose;
static int quiet;
static int show_terse_type_changes;

static struct conf_load conf_load = {
	.get_addr_info = true,
};

static struct strlist *structs_printed;

#define TCHANGEF__SIZE		(1 << 0)
#define TCHANGEF__NR_MEMBERS	(1 << 1)
#define TCHANGEF__TYPE		(1 << 2)
#define TCHANGEF__OFFSET	(1 << 3)
#define TCHANGEF__BIT_OFFSET	(1 << 4)
#define TCHANGEF__BIT_SIZE	(1 << 5)
#define TCHANGEF__PADDING	(1 << 6)
#define TCHANGEF__NR_HOLES	(1 << 7)
#define TCHANGEF__NR_BIT_HOLES	(1 << 8)

static uint32_t terse_type_changes;

static uint32_t total_cus_changed;
static uint32_t total_nr_functions_changed;
static uint32_t total_function_bytes_added;
static uint32_t total_function_bytes_removed;

struct diff_info {
	const struct tag *tag;
	const struct cu	 *cu;
	int32_t		 diff;
};

static struct diff_info *diff_info__new(const struct tag *twin,
					const struct cu *cu,
					int32_t diff)
{
	struct diff_info *dinfo = malloc(sizeof(*dinfo));

	if (dinfo == NULL) {
		puts("out of memory!");
		exit(1);
	}
	dinfo->tag  = twin;
	dinfo->cu   = cu;
	dinfo->diff = diff;
	return dinfo;
}

static void cu__check_max_len_changed_item(struct cu *cu, const char *name,
					   uint8_t addend)
{
	const uint32_t len = strlen(name) + addend;

	if (len > cu->max_len_changed_item)
		cu->max_len_changed_item = len;
}

static void diff_function(const struct cu *new_cu, struct function *function,
			  struct cu *cu)
{
	struct tag *new_tag;
	const char *name;

	if (function->inlined || function->abstract_origin != 0)
		return;

	name = function__name(function, cu);
	new_tag = cu__find_function_by_name(new_cu, name);
	if (new_tag != NULL) {
		struct function *new_function = tag__function(new_tag);
		int32_t diff = (function__size(new_function) -
				function__size(function));
		if (diff != 0) {
			function->priv = diff_info__new(&new_function->proto.tag, new_cu,
							diff);
			cu__check_max_len_changed_item(cu, name, 0);

			++cu->nr_functions_changed;
			if (diff > 0)
				cu->function_bytes_added += diff;
			else
				cu->function_bytes_removed += -diff;
		} else {
			char proto[1024], twin_proto[1024];

			if (strcmp(function__prototype(function, cu,
						       proto, sizeof(proto)),
				   function__prototype(new_function, new_cu,
						       twin_proto,
						       sizeof(twin_proto))) != 0) {
				++cu->nr_functions_changed;
				function->priv = diff_info__new(function__tag(new_function),
								new_cu, 0);
			}
		}
	} else {
		const uint32_t diff = -function__size(function);

		cu__check_max_len_changed_item(cu, name, 0);
		function->priv = diff_info__new(NULL, NULL, diff);
		++cu->nr_functions_changed;
		cu->function_bytes_removed += -diff;
	}
}

static int check_print_change(const struct class_member *old,
			      const struct cu *old_cu,
			      const struct class_member *new,
			      const struct cu *new_cu,
			      int print)
{
	size_t old_size, new_size;
	char old_type_name[128], new_type_name[128];
	const struct tag *old_type = cu__type(old_cu, old->tag.type);
	const struct tag *new_type = cu__type(new_cu, new->tag.type);
	int changes = 0;

	if (old_type == NULL || new_type == NULL)
		return 0;

	old_size = old->byte_size;
	new_size = new->byte_size;
	if (old_size != new_size)
		changes = 1;

	if (old->byte_offset != new->byte_offset) {
		changes = 1;
		terse_type_changes |= TCHANGEF__OFFSET;
	}

	if (old->bitfield_offset != new->bitfield_offset) {
		changes = 1;
		terse_type_changes |= TCHANGEF__BIT_OFFSET;
	}

	if (old->bitfield_size != new->bitfield_size) {
		changes = 1;
		terse_type_changes |= TCHANGEF__BIT_SIZE;
	}

	if (strcmp(tag__name(old_type, old_cu, old_type_name,
			     sizeof(old_type_name), NULL),
		   tag__name(new_type, new_cu, new_type_name,
			     sizeof(new_type_name), NULL)) != 0) {
		changes = 1;
		terse_type_changes |= TCHANGEF__TYPE;
	}

	if (changes && print && !show_terse_type_changes)
		printf("    %s\n"
		       "     from:    %-21s /* %5u(%2u) %5zd(%2d) */\n"
		       "     to:      %-21s /* %5u(%2u) %5zd(%2u) */\n",
		       class_member__name(old, old_cu),
		       old_type_name, old->byte_offset, old->bitfield_offset,
		       old_size, old->bitfield_size,
		       new_type_name, new->byte_offset, new->bitfield_offset,
		       new_size, new->bitfield_size);

	return changes;
}

static struct class_member *class__find_pair_member(const struct class *structure, const struct cu *cu,
						    const struct class_member *pair_member, const struct cu *pair_cu,
						    int *nr_anonymousp)
{
	const char *member_name = class_member__name(pair_member, pair_cu);
	struct class_member *member;

	if (member_name)
		return class__find_member_by_name(structure, cu, member_name);

	int nr_anonymous = ++*nr_anonymousp;

	/* Unnamed struct or union, lets look for the first unammed matchin tag.type */

	type__for_each_member(&structure->type, member) {
		if (member->tag.tag == pair_member->tag.tag && /* Both are class/union/struct (unnamed) */
		    class_member__name(member, cu) == member_name && /* Both are NULL? */
		    --nr_anonymous == 0)
			return member;
	}

	return NULL;
}

static int check_print_members_changes(const struct class *structure,
				       const struct cu *cu,
				       const struct class *new_structure,
				       const struct cu *new_cu,
				       int print)
{
	int changes = 0, nr_anonymous = 0;
	struct class_member *member;
	uint16_t nr_twins_found = 0;

	type__for_each_member(&structure->type, member) {
		struct class_member *twin = class__find_pair_member(new_structure, new_cu, member, cu, &nr_anonymous);
		if (twin != NULL) {
			twin->tag.visited = 1;
			++nr_twins_found;
			if (check_print_change(member, cu, twin, new_cu, print))
				changes = 1;
		} else {
			changes = 1;
			if (print) {
				char name[128];
				struct tag *type;
				type = cu__type(cu, member->tag.type);
				printf("    %s\n"
				       "     removed: %-21s /* %5u(%2u) %5zd(%2d) */\n",
				       class_member__name(member, cu),
				       tag__name(type, cu, name, sizeof(name), NULL),
				       member->byte_offset, member->bitfield_offset,
				       member->byte_size, member->bitfield_size);
			}
		}
	}

	if (nr_twins_found == (new_structure->type.nr_members +
			       new_structure->type.nr_static_members))
		goto out;

	changes = 1;
	if (!print)
		goto out;

	type__for_each_member(&new_structure->type, member) {
		if (!member->tag.visited) {
			char name[128];
			struct tag *type;
			type = cu__type(new_cu, member->tag.type);
			printf("    %s\n"
			       "     added:   %-21s /* %5u(%2u) %5zd(%2d) */\n",
			       class_member__name(member, new_cu),
			       tag__name(type, new_cu, name, sizeof(name), NULL),
			       member->byte_offset, member->bitfield_offset,
			       member->byte_size, member->bitfield_size);
		}
	}
out:
	return changes;
}

static void diff_struct(const struct cu *new_cu, struct class *structure,
			struct cu *cu)
{
	struct tag *new_tag;
	struct class *new_structure = NULL;
	int32_t diff;

	assert(class__is_struct(structure));

	if (class__size(structure) == 0 || class__name(structure, cu) == NULL)
		return;

	new_tag = cu__find_struct_by_name(new_cu,
					  class__name(structure, cu), 0, NULL);
	if (new_tag == NULL)
		return;

	new_structure = tag__class(new_tag);
	if (class__size(new_structure) == 0)
		return;

	assert(class__is_struct(new_structure));

	diff = class__size(structure) != class__size(new_structure) ||
	       class__nr_members(structure) != class__nr_members(new_structure) ||
	       check_print_members_changes(structure, cu,
			       		   new_structure, new_cu, 0) ||
	       structure->padding != new_structure->padding ||
	       structure->nr_holes != new_structure->nr_holes ||
	       structure->nr_bit_holes != new_structure->nr_bit_holes;

	if (diff == 0)
		return;

	++cu->nr_structures_changed;
	cu__check_max_len_changed_item(cu, class__name(structure, cu),
				       sizeof("struct"));
	structure->priv = diff_info__new(class__tag(new_structure),
					 new_cu, diff);
}

static struct cu *cus__find_pair(struct cus *cus, const char *name)
{
	if (cus->nr_entries == 1)
		return list_first_entry(&cus->cus, struct cu, node);

	return cus__find_cu_by_name(cus, name);
}

static int cu_find_new_tags_iterator(struct cu *new_cu, void *old_cus)
{
	struct cu *old_cu = cus__find_pair(old_cus, new_cu->name);

	if (old_cu != NULL && cu__same_build_id(old_cu, new_cu))
		return 0;

	struct function *function;
	uint32_t id;
	cu__for_each_function(new_cu, id, function) {
		/*
		 * We're not interested in aliases, just real function definitions,
		 * where we'll know if the kind of inlining
		 */
		if (function->abstract_origin || function->inlined)
			continue;

		const char *name = function__name(function, new_cu);
		struct tag *old_function = cu__find_function_by_name(old_cu,
								     name);
		if (old_function != NULL && !tag__function(old_function)->inlined)
			continue;

		const int32_t diff = function__size(function);

		cu__check_max_len_changed_item(new_cu, name, 0);
		++new_cu->nr_functions_changed;
		new_cu->function_bytes_added += diff;
		function->priv = diff_info__new(old_function, new_cu, diff);
	}

	struct class *class;
	cu__for_each_struct(new_cu, id, class) {
		const char *name = class__name(class, new_cu);
		if (name == NULL || class__size(class) == 0 ||
		    cu__find_struct_by_name(old_cu, name, 0, NULL))
			continue;

		class->priv = diff_info__new(NULL, NULL, 1);
		++new_cu->nr_structures_changed;

		cu__check_max_len_changed_item(new_cu, name, sizeof("struct"));
	}

	return 0;
}

static int cu_diff_iterator(struct cu *cu, void *new_cus)
{
	struct cu *new_cu = cus__find_pair(new_cus, cu->name);

	if (new_cu != NULL && cu__same_build_id(cu, new_cu))
		return 0;

	uint32_t id;
	struct class *class;
	cu__for_each_struct(cu, id, class)
		diff_struct(new_cu, class, cu);

	struct function *function;
	cu__for_each_function(cu, id, function)
		diff_function(new_cu, function, cu);

	return 0;
}

static void show_diffs_function(struct function *function, const struct cu *cu,
				const void *cookie)
{
	const struct diff_info *di = function->priv;

	printf("  %-*.*s | %+4d",
	       (int)cu->max_len_changed_item, (int)cu->max_len_changed_item,
	       function__name(function, cu), di->diff);

	if (!verbose) {
		putchar('\n');
		return;
	}

	if (di->tag == NULL)
		puts(cookie ? " (added)" : " (removed)");
	else {
		struct function *twin = tag__function(di->tag);

		if (twin->inlined)
			puts(cookie ? " (uninlined)" : " (inlined)");
		else if (strcmp(function__name(function, cu),
				function__name(twin, di->cu)) != 0)
			printf("%s: BRAIN FART ALERT: comparing %s to %s, "
			       "should be the same name\n", __FUNCTION__,
			       function__name(function, cu),
			       function__name(twin, di->cu));
		else {
			char proto[1024], twin_proto[1024];

			printf(" # %d -> %d", function__size(function),
			       function__size(twin));
			if (function->lexblock.nr_lexblocks !=
			    twin->lexblock.nr_lexblocks)
				printf(", lexblocks: %d -> %d",
				       function->lexblock.nr_lexblocks,
				       twin->lexblock.nr_lexblocks);
			if (function->lexblock.nr_inline_expansions !=
			    twin->lexblock.nr_inline_expansions)
				printf(", # inlines: %d -> %d",
				       function->lexblock.nr_inline_expansions,
				       twin->lexblock.nr_inline_expansions);
			if (function->lexblock.size_inline_expansions !=
			    twin->lexblock.size_inline_expansions)
				printf(", size inlines: %d -> %d",
				       function->lexblock.size_inline_expansions,
				       twin->lexblock.size_inline_expansions);

			if (strcmp(function__prototype(function, cu,
					     proto, sizeof(proto)),
				   function__prototype(twin, di->cu,
				   	     twin_proto, sizeof(twin_proto))) != 0)
				printf(", prototype: %s -> %s", proto, twin_proto);
			putchar('\n');
		}
	}
}

static void show_changed_member(char change, const struct class_member *member,
				const struct cu *cu)
{
	const struct tag *type = cu__type(cu, member->tag.type);
	char bf[128];

	tag__assert_search_result(type);
	printf("    %c%-26s %-21s /* %5u %5zd */\n",
	       change, tag__name(type, cu, bf, sizeof(bf), NULL),
	       class_member__name(member, cu),
	       member->byte_offset, member->byte_size);
}

static void show_nr_members_changes(const struct class *structure,
				    const struct cu *cu,
				    const struct class *new_structure,
				    const struct cu *new_cu)
{
	struct class_member *member;
	int nr_anonymous = 0;

	/* Find the removed ones */
	type__for_each_member(&structure->type, member) {
		struct class_member *twin = class__find_pair_member(new_structure, new_cu, member, cu, &nr_anonymous);
		if (twin == NULL)
			show_changed_member('-', member, cu);
	}

	nr_anonymous = 0;
	/* Find the new ones */
	type__for_each_member(&new_structure->type, member) {
		struct class_member *twin = class__find_pair_member(structure, cu, member, new_cu, &nr_anonymous);
		if (twin == NULL)
			show_changed_member('+', member, new_cu);
	}
}

static void print_terse_type_changes(struct class *structure,
				     const struct cu *cu)
{
	const char *sep = "";

	printf("struct %s: ", class__name(structure, cu));

	if (terse_type_changes & TCHANGEF__SIZE) {
		fputs("size", stdout);
		sep = ", ";
	}
	if (terse_type_changes & TCHANGEF__NR_MEMBERS) {
		printf("%snr_members", sep);
		sep = ", ";
	}
	if (terse_type_changes & TCHANGEF__TYPE) {
		printf("%stype", sep);
		sep = ", ";
	}
	if (terse_type_changes & TCHANGEF__OFFSET) {
		printf("%soffset", sep);
		sep = ", ";
	}
	if (terse_type_changes & TCHANGEF__BIT_OFFSET) {
		printf("%sbit_offset", sep);
		sep = ", ";
	}
	if (terse_type_changes & TCHANGEF__BIT_SIZE) {
		printf("%sbit_size", sep);
		sep = ", ";
	}
	if (terse_type_changes & TCHANGEF__PADDING) {
		printf("%spadding", sep);
		sep = ", ";
	}
	if (terse_type_changes & TCHANGEF__NR_HOLES) {
		printf("%snr_holes", sep);
		sep = ", ";
	}
	if (terse_type_changes & TCHANGEF__NR_BIT_HOLES)
		printf("%snr_bit_holes", sep);

	putchar('\n');
}

static void show_diffs_structure(struct class *structure,
				 const struct cu *cu)
{
	const struct diff_info *di = structure->priv;
	const struct class *new_structure;
	int diff;
	/*
	 * This is when the struct was not present in the new object file.
	 * Meaning that it either was not referenced or that it was completely
	 * removed.
	 */
	if (di == NULL)
		return;

	new_structure = tag__class(di->tag);
	/*
	 * If there is a diff_info but its di->tag is NULL we have a new structure,
	 * one that didn't appears in the old object. See find_new_classes_iterator.
	 */
	if (new_structure == NULL)
		diff = class__size(structure);
	else
		diff = class__size(new_structure) - class__size(structure);

	terse_type_changes = 0;

	if (!show_terse_type_changes)
		printf("  struct %-*.*s | %+4d\n",
		       (int)(cu->max_len_changed_item - sizeof("struct")),
		       (int)(cu->max_len_changed_item - sizeof("struct")),
		       class__name(structure, cu), diff);

	if (diff != 0)
		terse_type_changes |= TCHANGEF__SIZE;

	if (!verbose && !show_terse_type_changes)
		return;

	if (new_structure == NULL)
		diff = -class__nr_members(structure);
	else
		diff = (class__nr_members(new_structure) -
		        class__nr_members(structure));
	if (diff != 0) {
		terse_type_changes |= TCHANGEF__NR_MEMBERS;
		if (!show_terse_type_changes) {
			printf("   nr_members: %+d\n", diff);
			if (new_structure != NULL)
				show_nr_members_changes(structure, cu,
							new_structure, di->cu);
		}
	}
	if (new_structure != NULL) {
		diff = (int)new_structure->padding - (int)structure->padding;
		if (diff) {
			terse_type_changes |= TCHANGEF__PADDING;
			if (!show_terse_type_changes)
				printf("   padding: %+d\n", diff);
		}
		diff = (int)new_structure->nr_holes - (int)structure->nr_holes;
		if (diff) {
			terse_type_changes |= TCHANGEF__NR_HOLES;
			if (!show_terse_type_changes)
				printf("   nr_holes: %+d\n", diff);
		}
		diff = ((int)new_structure->nr_bit_holes -
			(int)structure->nr_bit_holes);
		if (structure->nr_bit_holes != new_structure->nr_bit_holes) {
			terse_type_changes |= TCHANGEF__NR_BIT_HOLES;
			if (!show_terse_type_changes)
				printf("   nr_bit_holes: %+d\n", diff);
		}
		check_print_members_changes(structure, cu,
					    new_structure, di->cu, 1);
	}
	if (show_terse_type_changes)
		print_terse_type_changes(structure, cu);
}

static void show_structure_diffs_iterator(struct class *class, struct cu *cu)
{
	if (class->priv != NULL) {
		const char *name = class__name(class, cu);
		if (!strlist__has_entry(structs_printed, name)) {
			show_diffs_structure(class, cu);
			strlist__add(structs_printed, name);
		}
	}
}

static int cu_show_diffs_iterator(struct cu *cu, void *cookie)
{
	static int first_cu_printed;

	if (cu->nr_functions_changed == 0 &&
	    cu->nr_structures_changed == 0)
		return 0;

	if (first_cu_printed) {
		if (!quiet)
			putchar('\n');
	} else {
		first_cu_printed = 1;
	}

	++total_cus_changed;

	if (!quiet)
		printf("%s:\n", cu->name);

	uint32_t id;
	struct class *class;

	if (show_terse_type_changes) {
		cu__for_each_struct(cu, id, class)
			show_structure_diffs_iterator(class, cu);
		return 0;
	}

	if (cu->nr_structures_changed != 0 && show_struct_diffs) {
		cu__for_each_struct(cu, id, class)
			show_structure_diffs_iterator(class, cu);
		printf(" %u struct%s changed\n", cu->nr_structures_changed,
		       cu->nr_structures_changed > 1 ? "s" : "");
	}

	if (cu->nr_functions_changed != 0 && show_function_diffs) {
		total_nr_functions_changed += cu->nr_functions_changed;

		struct function *function;
		cu__for_each_function(cu, id, function) {
			if (function->priv != NULL)
				show_diffs_function(function, cu, cookie);
		}

		printf(" %u function%s changed", cu->nr_functions_changed,
		       cu->nr_functions_changed > 1 ? "s" : "");
		if (cu->function_bytes_added != 0) {
			total_function_bytes_added += cu->function_bytes_added;
			printf(", %zd bytes added", cu->function_bytes_added);
		}
		if (cu->function_bytes_removed != 0) {
			total_function_bytes_removed += cu->function_bytes_removed;
			printf(", %zd bytes removed",
			       cu->function_bytes_removed);
		}
		printf(", diff: %+zd",
		       cu->function_bytes_added - cu->function_bytes_removed);
		putchar('\n');
	}
	return 0;
}

static int cu_delete_priv(struct cu *cu, void *cookie __unused)
{
	struct class *c;
	struct function *f;
	uint32_t id;

	cu__for_each_struct(cu, id, c)
		free(c->priv);

	cu__for_each_function(cu, id, f)
		free(f->priv);

	return 0;
}

static void print_total_function_diff(const char *filename)
{
	printf("\n%s:\n", filename);

	printf(" %u function%s changed", total_nr_functions_changed,
	       total_nr_functions_changed > 1 ? "s" : "");

	if (total_function_bytes_added != 0)
		printf(", %u bytes added", total_function_bytes_added);

	if (total_function_bytes_removed != 0)
		printf(", %u bytes removed", total_function_bytes_removed);

	printf(", diff: %+d",
	       (total_function_bytes_added -
	        total_function_bytes_removed));
	putchar('\n');
}

/* Name and version of program.  */
ARGP_PROGRAM_VERSION_HOOK_DEF = dwarves_print_version;

static const struct argp_option codiff__options[] = {
	{
		.key  = 's',
		.name = "structs",
		.doc  = "show struct diffs",
	},
	{
		.key  = 'f',
		.name = "functions",
		.doc  = "show function diffs",
	},
	{
		.name = "format_path",
		.key  = 'F',
		.arg  = "FORMAT_LIST",
		.doc  = "List of debugging formats to try"
	},
	{
		.key  = 't',
		.name = "terse_type_changes",
		.doc  = "show terse type changes",
	},
	{
		.key  = 'V',
		.name = "verbose",
		.doc  = "show diffs details",
	},
	{
		.key  = 'q',
		.name = "quiet",
		.doc  = "Show only differences, no difference? No output",
	},
	{
		.name = NULL,
	}
};

static error_t codiff__options_parser(int key, char *arg __unused,
				      struct argp_state *state __unused)
{
	switch (key) {
	case 'f': show_function_diffs = 1;	break;
	case 'F': conf_load.format_path = arg;	break;
	case 's': show_struct_diffs = 1;	break;
	case 't': show_terse_type_changes = 1;	break;
	case 'V': verbose = 1;			break;
	case 'q': quiet = 1;			break;
	default:  return ARGP_ERR_UNKNOWN;
	}
	return 0;
}

static const char codiff__args_doc[] = "OLD_FILE NEW_FILE";

static struct argp codiff__argp = {
	.options  = codiff__options,
	.parser	  = codiff__options_parser,
	.args_doc = codiff__args_doc,
};

int main(int argc, char *argv[])
{
	int remaining, err, rc = EXIT_FAILURE;
	char *old_filename, *new_filename;
	struct stat st;

	if (argp_parse(&codiff__argp, argc, argv, 0, &remaining, NULL) ||
	    remaining < argc) {
		switch (argc - remaining) {
		case 2:	 old_filename = argv[remaining++];
			 new_filename = argv[remaining++]; break;
		case 1:
		default: goto failure;
		}
	} else {
failure:
		argp_help(&codiff__argp, stderr, ARGP_HELP_SEE, argv[0]);
		goto out;
	}

	if (dwarves__init(0)) {
		fputs("codiff: insufficient memory\n", stderr);
		goto out;
	}

	if (show_function_diffs == 0 && show_struct_diffs == 0 &&
	    show_terse_type_changes == 0)
		show_function_diffs = show_struct_diffs = 1;

	structs_printed = strlist__new(false);
	struct cus *old_cus = cus__new(),
		   *new_cus = cus__new();
	if (old_cus == NULL || new_cus == NULL || structs_printed == NULL) {
		fputs("codiff: insufficient memory\n", stderr);
		goto out_cus_delete;
	}

	if (stat(old_filename, &st) != 0) {
		fprintf(stderr, "codiff: %s (%s)\n", strerror(errno), old_filename);
		goto out_cus_delete;
	}

	/* If old_file is a character device, leave its cus empty */
	if (!S_ISCHR(st.st_mode)) {
		err = cus__load_file(old_cus, &conf_load, old_filename);
		if (err != 0) {
			cus__print_error_msg("codiff", old_cus, old_filename, err);
			goto out_cus_delete_priv;
		}
	}

	if (stat(new_filename, &st) != 0) {
		fprintf(stderr, "codiff: %s (%s)\n", strerror(errno), new_filename);
		goto out_cus_delete_priv;
	}

	/* If old_file is a character device, leave its cus empty */
	if (!S_ISCHR(st.st_mode)) {
		err = cus__load_file(new_cus, &conf_load, new_filename);
		if (err != 0) {
			cus__print_error_msg("codiff", new_cus, new_filename, err);
			goto out_cus_delete_priv;
		}
	}

	cus__for_each_cu(old_cus, cu_diff_iterator, new_cus, NULL);
	cus__for_each_cu(new_cus, cu_find_new_tags_iterator, old_cus, NULL);
	cus__for_each_cu(old_cus, cu_show_diffs_iterator, NULL, NULL);
	if (new_cus->nr_entries > 1)
		cus__for_each_cu(new_cus, cu_show_diffs_iterator, (void *)1, NULL);

	if (total_cus_changed > 1) {
		if (show_function_diffs)
			print_total_function_diff(new_filename);
	}

	rc = EXIT_SUCCESS;
out_cus_delete_priv:
	cus__for_each_cu(old_cus, cu_delete_priv, NULL, NULL);
	cus__for_each_cu(new_cus, cu_delete_priv, NULL, NULL);
out_cus_delete:
	cus__delete(old_cus);
	cus__delete(new_cus);
	strlist__delete(structs_printed);
	dwarves__exit();
out:
	return rc;
}