diff options
Diffstat (limited to 'usr.sbin/pmcannotate/pmcannotate.c')
| -rw-r--r-- | usr.sbin/pmcannotate/pmcannotate.c | 848 | 
1 files changed, 848 insertions, 0 deletions
| diff --git a/usr.sbin/pmcannotate/pmcannotate.c b/usr.sbin/pmcannotate/pmcannotate.c new file mode 100644 index 000000000000..5958ff615e4b --- /dev/null +++ b/usr.sbin/pmcannotate/pmcannotate.c @@ -0,0 +1,848 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2008 Nokia Corporation + * All rights reserved. + * + * This software was developed by Attilio Rao for the IPSO project under + * contract to Nokia Corporation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + *    notice unmodified, this list of conditions, and the following + *    disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + *    notice, this list of conditions and the following disclaimer in the + *    documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <sys/param.h> +#include <sys/queue.h> + +#include <ctype.h> +#include <err.h> +#include <paths.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <unistd.h> + +/* NB: Make sure FNBUFF is as large as LNBUFF, otherwise it could overflow */ +#define	FNBUFF	512 +#define	LNBUFF	512 + +#define	TMPNAME	"pmcannotate.XXXXXX" + +#define	FATAL(ptr, x ...) do {						\ +	fqueue_deleteall();						\ +	general_deleteall();						\ +	if ((ptr) != NULL)						\ +		perror(ptr);						\ +	fprintf(stderr, ##x);						\ +	remove(tbfl);							\ +	remove(tofl);							\ +	exit(EXIT_FAILURE);						\ +} while (0) + +#define	PERCSAMP(x)	((float)(x) * 100 / totalsamples) + +struct entry { +        TAILQ_ENTRY(entry)	en_iter; +        char		*en_name; +	uintptr_t	en_pc; +	uintptr_t	en_ostart; +	uintptr_t	en_oend; +	u_int		en_nsamples; +}; + +struct aggent { +	TAILQ_ENTRY(aggent)	ag_fiter; +	long		ag_offset; +	uintptr_t	ag_ostart; +	uintptr_t	ag_oend; +	char		*ag_name; +	u_int		ag_nsamples; +}; + +static struct aggent	*agg_create(const char *name, u_int nsamples, +			    uintptr_t start, uintptr_t end); +static void		 agg_destroy(struct aggent *agg) __unused; +static void		 asmparse(FILE *fp); +static int		 cparse(FILE *fp); +static void		 entry_acqref(struct entry *entry); +static struct entry	*entry_create(const char *name, uintptr_t pc, +			    uintptr_t start, uintptr_t end); +static void		 entry_destroy(struct entry *entry) __unused; +static void		 fqueue_compact(float th); +static void		 fqueue_deleteall(void); +static struct aggent	*fqueue_findent_by_name(const char *name); +static int		 fqueue_getall(const char *bin, char *temp, int asmf); +static int		 fqueue_insertent(struct entry *entry); +static int		 fqueue_insertgen(void); +static void		 general_deleteall(void); +static struct entry	*general_findent(uintptr_t pc); +static void		 general_insertent(struct entry *entry); +static void		 general_printasm(FILE *fp, struct aggent *agg); +static int		 general_printc(FILE *fp, struct aggent *agg); +static int		 printblock(FILE *fp, struct aggent *agg); +static void		 usage(const char *progname) __dead2; + +static TAILQ_HEAD(, entry) mainlst = TAILQ_HEAD_INITIALIZER(mainlst); +static TAILQ_HEAD(, aggent) fqueue = TAILQ_HEAD_INITIALIZER(fqueue); + +/* + * Use a float value in order to automatically promote operations + * to return a float value rather than use casts. + */ +static u_int totalsamples; + +static enum { RAW, BLOCK_PERCENT, GLOBAL_PERCENT } print_mode; + +/* + * Identifies a string cointaining objdump's assembly printout. + */ +static inline int +isasminline(const char *str) +{ +	void *ptr; +	int nbytes; + +	if (sscanf(str, " %p%n", &ptr, &nbytes) != 1) +		return (0); +	if (str[nbytes] != ':' || isspace(str[nbytes + 1]) == 0) +		return (0); +	return (1); +} + +/* + * Identifies a string containing objdump's assembly printout + * for a new function. + */ +static inline int +newfunction(const char *str) +{ +	char fname[FNBUFF]; +	void *ptr; +	int nbytes; + +	if (isspace(str[0])) +		return (0); +	if (sscanf(str, "%p <%[^>:]>:%n", &ptr, fname, &nbytes) != 2) +		return (0); +	return (nbytes); +} + +/* + * Create a new first-level aggregation object for a specified + * function. + */ +static struct aggent * +agg_create(const char *name, u_int nsamples, uintptr_t start, uintptr_t end) +{ +	struct aggent *agg; + +	agg = calloc(1, sizeof(struct aggent)); +	if (agg == NULL) +		return (NULL); +	agg->ag_name = strdup(name); +	if (agg->ag_name == NULL) { +		free(agg); +		return (NULL); +	} +	agg->ag_nsamples = nsamples; +	agg->ag_ostart = start; +	agg->ag_oend = end; +	return (agg); +} + +/* + * Destroy a first-level aggregation object for a specified + * function. + */ +static void +agg_destroy(struct aggent *agg) +{ + +	free(agg->ag_name); +	free(agg); +} + +/* + * Analyze the "objdump -d" output, locate functions and start + * printing out the assembly functions content. + * We do not use newfunction() because we actually need the + * function name in available form, but the heurstic used is + * the same. + */ +static void +asmparse(FILE *fp) +{ +	char buffer[LNBUFF], fname[FNBUFF]; +	struct aggent *agg; +	void *ptr; + +	while (fgets(buffer, LNBUFF, fp) != NULL) { +		if (isspace(buffer[0])) +			continue; +		if (sscanf(buffer, "%p <%[^>:]>:", &ptr, fname) != 2) +			continue; +		agg = fqueue_findent_by_name(fname); +		if (agg == NULL) +			continue; +		agg->ag_offset = ftell(fp); +	} + +	TAILQ_FOREACH(agg, &fqueue, ag_fiter) { +		if (fseek(fp, agg->ag_offset, SEEK_SET) == -1) +			return; +		printf("Profile trace for function: %s() [%.2f%%]\n", +		    agg->ag_name, PERCSAMP(agg->ag_nsamples)); +		general_printasm(fp, agg); +		printf("\n"); +	} +} + +/* + * Analyze the "objdump -S" output, locate functions and start + * printing out the C functions content. + * We do not use newfunction() because we actually need the + * function name in available form, but the heurstic used is + * the same. + * In order to maintain the printout sorted, on the first pass it + * simply stores the file offsets in order to fastly moved later + * (when the file is hot-cached also) when the real printout will + * happen. + */ +static int +cparse(FILE *fp) +{ +	char buffer[LNBUFF], fname[FNBUFF]; +	struct aggent *agg; +	void *ptr; + +	while (fgets(buffer, LNBUFF, fp) != NULL) { +		if (isspace(buffer[0])) +			continue; +		if (sscanf(buffer, "%p <%[^>:]>:", &ptr, fname) != 2) +			continue; +		agg = fqueue_findent_by_name(fname); +		if (agg == NULL) +			continue; +		agg->ag_offset = ftell(fp); +	} + +	TAILQ_FOREACH(agg, &fqueue, ag_fiter) { +		if (fseek(fp, agg->ag_offset, SEEK_SET) == -1) +			return (-1); +		printf("Profile trace for function: %s() [%.2f%%]\n", +		    agg->ag_name, PERCSAMP(agg->ag_nsamples)); +		if (general_printc(fp, agg) == -1) +			return (-1); +		printf("\n"); +	} +	return (0); +} + +/* + * Bump the number of samples for any raw entry. + */ +static void +entry_acqref(struct entry *entry) +{ + +	entry->en_nsamples++; +} + +/* + * Create a new raw entry object for a specified function. + */ +static struct entry * +entry_create(const char *name, uintptr_t pc, uintptr_t start, uintptr_t end) +{ +	struct entry *obj; + +	obj = calloc(1, sizeof(struct entry)); +	if (obj == NULL) +		return (NULL); +	obj->en_name = strdup(name); +	if (obj->en_name == NULL) { +		free(obj); +		return (NULL); +	} +	obj->en_pc = pc; +	obj->en_ostart = start; +	obj->en_oend = end; +	obj->en_nsamples = 1; +	return (obj); +} + +/* + * Destroy a raw entry object for a specified function. + */ +static void +entry_destroy(struct entry *entry) +{ + +	free(entry->en_name); +	free(entry); +} + +/* + * Specify a lower bound in percentage and drop from the + * first-level aggregation queue all the objects with a + * smaller impact. + */ +static void +fqueue_compact(float th) +{ +	u_int thi; +	struct aggent *agg, *tmpagg; + +	if (totalsamples == 0) +		return; + +	/* Revert the percentage calculation. */ +	thi = th * totalsamples / 100; +	TAILQ_FOREACH_SAFE(agg, &fqueue, ag_fiter, tmpagg) +		if (agg->ag_nsamples < thi) +			TAILQ_REMOVE(&fqueue, agg, ag_fiter); +} + +/* + * Flush the first-level aggregates queue. + */ +static void +fqueue_deleteall(void) +{ +	struct aggent *agg; + +	while (TAILQ_EMPTY(&fqueue) == 0) { +		agg = TAILQ_FIRST(&fqueue); +		TAILQ_REMOVE(&fqueue, agg, ag_fiter); +	} +} + +/* + * Insert a raw entry into the aggregations queue. + * If the respective first-level aggregation object + * does not exist create it and maintain it sorted + * in respect of the number of samples. + */ +static int +fqueue_insertent(struct entry *entry) +{ +	struct aggent *obj, *tmp; +	int found; + +	found = 0; +	TAILQ_FOREACH(obj, &fqueue, ag_fiter) +		if (!strcmp(obj->ag_name, entry->en_name)) { +			found = 1; +			obj->ag_nsamples += entry->en_nsamples; +			break; +		} + +	/* +	 * If the first-level aggregation object already exists, +	 * just aggregate the samples and, if needed, resort +	 * it. +	 */ +	if (found) { +		TAILQ_REMOVE(&fqueue, obj, ag_fiter); +		found = 0; +		TAILQ_FOREACH(tmp, &fqueue, ag_fiter) +			if (obj->ag_nsamples > tmp->ag_nsamples) { +				found = 1; +				break; +			} +		if (found) +			TAILQ_INSERT_BEFORE(tmp, obj, ag_fiter); +		else +			TAILQ_INSERT_TAIL(&fqueue, obj, ag_fiter); +		return (0); +	} + +	/* +	 * If the first-level aggregation object does not +	 * exist, create it and put in the sorted queue. +	 * If this is the first object, we need to set the +	 * head of the queue. +	 */ +	obj = agg_create(entry->en_name, entry->en_nsamples, entry->en_ostart, +	    entry->en_oend); +	if (obj == NULL) +		return (-1); +	if (TAILQ_EMPTY(&fqueue) != 0) { +		TAILQ_INSERT_HEAD(&fqueue, obj, ag_fiter); +		return (0); +	} +	TAILQ_FOREACH(tmp, &fqueue, ag_fiter) +		if (obj->ag_nsamples > tmp->ag_nsamples) { +			found = 1; +			break; +		} +	if (found) +		TAILQ_INSERT_BEFORE(tmp, obj, ag_fiter); +	else +		TAILQ_INSERT_TAIL(&fqueue, obj, ag_fiter); +	return (0); +} + +/* + * Lookup a first-level aggregation object by name. + */ +static struct aggent * +fqueue_findent_by_name(const char *name) +{ +	struct aggent *obj; + +	TAILQ_FOREACH(obj, &fqueue, ag_fiter) +		if (!strcmp(obj->ag_name, name)) +			return (obj); +	return (NULL); +} + +/* + * Return the number of object in the first-level aggregations queue. + */ +static int +fqueue_getall(const char *bin, char *temp, int asmf) +{ +	char tmpf[MAXPATHLEN * 2 + 50]; +	struct aggent *agg; +	uintptr_t start, end; + +	if (mkstemp(temp) == -1) +		return (-1); +	TAILQ_FOREACH(agg, &fqueue, ag_fiter) { +		bzero(tmpf, sizeof(tmpf)); +		start = agg->ag_ostart; +		end = agg->ag_oend; + +		if (asmf) +			snprintf(tmpf, sizeof(tmpf), +			    "objdump --start-address=%p " +			    "--stop-address=%p -d %s >> %s", (void *)start, +			    (void *)end, bin, temp); +		else +			snprintf(tmpf, sizeof(tmpf), +			    "objdump --start-address=%p " +			    "--stop-address=%p -S %s >> %s", (void *)start, +			    (void *)end, bin, temp); +		if (system(tmpf) != 0) +			return (-1); +	} +	return (0); +} + +/* + * Insert all the raw entries present in the general queue + * into the first-level aggregations queue. + */ +static int +fqueue_insertgen(void) +{ +	struct entry *obj; + +	TAILQ_FOREACH(obj, &mainlst, en_iter) +		if (fqueue_insertent(obj) == -1) +			return (-1); +	return (0); +} + +/* + * Flush the raw entries general queue. + */ +static void +general_deleteall(void) +{ +	struct entry *obj; + +	while (TAILQ_EMPTY(&mainlst) == 0) { +		obj = TAILQ_FIRST(&mainlst); +		TAILQ_REMOVE(&mainlst, obj, en_iter); +	} +} + +/* + * Lookup a raw entry by the PC. + */ +static struct entry * +general_findent(uintptr_t pc) +{ +	struct entry *obj; + +	TAILQ_FOREACH(obj, &mainlst, en_iter) +		if (obj->en_pc == pc) +			return (obj); +	return (NULL); +} + +/* + * Insert a new raw entry in the general queue. + */ +static void +general_insertent(struct entry *entry) +{ + +	TAILQ_INSERT_TAIL(&mainlst, entry, en_iter); +} + +/* + * Return a string either holding a percentage or the raw count value. + */ +static const char * +print_count(u_int nsamples, u_int totsamples) +{ +	static char buf[16]; + +	switch (print_mode) { +	case RAW: +		snprintf(buf, sizeof(buf), "%u", nsamples); +		break; +	case BLOCK_PERCENT: +		snprintf(buf, sizeof(buf), "%.2f%%", (float)nsamples * 100 / +		    totsamples); +		break; +	case GLOBAL_PERCENT: +		snprintf(buf, sizeof(buf), "%.2f%%", (float)nsamples * 100 / +		    totalsamples); +		break; +	} +	return (buf); +} + +/* + * Printout the body of an "objdump -d" assembly function. + * It does simply stops when a new function is encountered, + * bringing back the file position in order to not mess up + * subsequent analysis. + * C lines and others not recognized are simply skipped. + */ +static void +general_printasm(FILE *fp, struct aggent *agg) +{ +	char buffer[LNBUFF]; +	struct entry *obj; +	int nbytes; +	void *ptr; + +	while (fgets(buffer, LNBUFF, fp) != NULL) { +		if ((nbytes = newfunction(buffer)) != 0) { +			fseek(fp, nbytes * -1, SEEK_CUR); +			break; +		} +		if (!isasminline(buffer)) +			continue; +		if (sscanf(buffer, " %p:", &ptr) != 1) +			continue; +		obj = general_findent((uintptr_t)ptr); +		if (obj == NULL) +			printf("\t| %s", buffer); +		else +			printf("%7s | %s", +			    print_count(obj->en_nsamples, agg->ag_nsamples), +			    buffer); +	} +} + +/* + * Printout the body of an "objdump -S" function. + * It does simply stops when a new function is encountered, + * bringing back the file position in order to not mess up + * subsequent analysis. + * It expect from the starting to the end to find, always, valid blocks + * (see below for an explanation of the "block" concept). + */ +static int +general_printc(FILE *fp, struct aggent *agg) +{ +	char buffer[LNBUFF]; + +	while (fgets(buffer, LNBUFF, fp) != NULL) { +		fseek(fp, strlen(buffer) * -1, SEEK_CUR); +		if (newfunction(buffer) != 0) +			break; +		if (printblock(fp, agg) == -1) +			return (-1); +	} +	return (0); +} + +/* + * Printout a single block inside an "objdump -S" function. + * The block is composed of a first part in C and subsequent translation + * in assembly. + * This code also operates a second-level aggregation packing together + * samples relative to PCs into a (lower bottom) block with their + * C (higher half) counterpart. + */ +static int +printblock(FILE *fp, struct aggent *agg) +{ +	char buffer[LNBUFF]; +	long lstart; +	struct entry *obj; +	u_int tnsamples; +	int done, nbytes, sentinel; +	void *ptr; + +	/* +	 * We expect the first thing of the block is C code, so simply give +	 * up if asm line is found. +	 */ +	lstart = ftell(fp); +	sentinel = 0; +	for (;;) { +		if (fgets(buffer, LNBUFF, fp) == NULL) +			return (0); +		if (isasminline(buffer) != 0) +			break; +		sentinel = 1; +		nbytes = newfunction(buffer); +		if (nbytes != 0) { +			if (fseek(fp, nbytes * -1, SEEK_CUR) == -1) +				return (-1); +			return (0); +		} +	} + +	/* +	 * If the sentinel is not set, it means it did not match any +	 * "high half" for this code so simply give up. +	 * Operates the second-level aggregation. +	 */ +	tnsamples = 0; +	do { +		if (sentinel == 0) +			return (-1); +		if (sscanf(buffer, " %p:", &ptr) != 1) +			return (-1); +		obj = general_findent((uintptr_t)ptr); +		if (obj != NULL) +			tnsamples += obj->en_nsamples; +	} while (fgets(buffer, LNBUFF, fp) != NULL && isasminline(buffer) != 0); + +	/* Rewind to the start of the block in order to start the printout. */ +	if (fseek(fp, lstart, SEEK_SET) == -1) +		return (-1); + +	/* Again the high half of the block rappresenting the C part. */ +	done = 0; +	while (fgets(buffer, LNBUFF, fp) != NULL && isasminline(buffer) == 0) { +		if (tnsamples == 0 || done != 0) +			printf("\t| %s", buffer); +		else { +			done = 1; +			printf("%7s | %s", +			    print_count(tnsamples, agg->ag_nsamples), buffer); +		} +	} + +	/* +	 * Again the low half of the block rappresenting the asm +	 * translation part. +	 */ +	for (;;) { +		if (fgets(buffer, LNBUFF, fp) == NULL) +			return (0); +		if (isasminline(buffer) == 0) +			break; +		nbytes = newfunction(buffer); +		if (nbytes != 0) { +			if (fseek(fp, nbytes * -1, SEEK_CUR) == -1) +				return (-1); +			return (0); +		} +	} +	if (fseek(fp, strlen(buffer) * -1, SEEK_CUR) == -1) +		return (-1); +	return (0); +} + +/* + * Helper printout functions. + */ +static void +usage(const char *progname) +{ + +	fprintf(stderr, +	    "usage: %s [-a] [-h] [-k kfile] [-l lb] [-m mode] pmcraw.out binary\n", +	    progname); +	exit(EXIT_SUCCESS); +} + +int +main(int argc, char *argv[]) +{ +	char buffer[LNBUFF], fname[FNBUFF]; +	char *tbfl, *tofl, *tmpdir; +	char tmpf[MAXPATHLEN * 2 + 50]; +	float limit; +	char *bin, *exec, *kfile, *ofile; +	struct entry *obj; +	FILE *gfp, *bfp; +	void *ptr, *hstart, *hend; +	uintptr_t tmppc, ostart, oend; +	int cget, asmsrc; + +	exec = argv[0]; +	ofile = NULL; +	bin = NULL; +	kfile = NULL; +	asmsrc = 0; +	limit = 0.5; +	print_mode = BLOCK_PERCENT; +	while ((cget = getopt(argc, argv, "ahl:m:k:")) != -1) +		switch(cget) { +		case 'a': +			asmsrc = 1; +			break; +		case 'k': +			kfile = optarg; +			break; +		case 'l': +			limit = (float)atof(optarg); +			break; +		case 'm': +			if (strcasecmp(optarg, "raw") == 0) +				print_mode = RAW; +			else if (strcasecmp(optarg, "global") == 0) +				print_mode = GLOBAL_PERCENT; +			else if (strcasecmp(optarg, "block") == 0) +				print_mode = BLOCK_PERCENT; +			else +				errx(1, "Invalid mode %s", optarg); +			break; +		case 'h': +		case '?': +		default: +			usage(exec); +		} +	argc -= optind; +	argv += optind; +	if (argc != 2) +		usage(exec); +	ofile = argv[0]; +	bin = argv[1]; + +	if (access(bin, R_OK | F_OK) == -1) +		FATAL(exec, "%s: Impossible to locate the binary file\n", +		    exec); +	if (access(ofile, R_OK | F_OK) == -1) +		FATAL(exec, "%s: Impossible to locate the pmcstat file\n", +		    exec); +	if (kfile != NULL && access(kfile, R_OK | F_OK) == -1) +		FATAL(exec, "%s: Impossible to locate the kernel file\n", +		    exec); + +	bzero(tmpf, sizeof(tmpf)); +	tmpdir = getenv("TMPDIR"); +	if (tmpdir == NULL) { +		asprintf(&tbfl, "%s/%s", _PATH_TMP, TMPNAME); +		asprintf(&tofl, "%s/%s", _PATH_TMP, TMPNAME); +	} else { +		asprintf(&tbfl, "%s/%s", tmpdir, TMPNAME); +		asprintf(&tofl, "%s/%s", tmpdir, TMPNAME); +	} +	if (tofl == NULL || tbfl == NULL) +		FATAL(exec, "%s: Cannot create tempfile templates\n", +		    exec); +	if (mkstemp(tofl) == -1) +		FATAL(exec, "%s: Impossible to create the tmp file\n", +		    exec); +	if (kfile != NULL) +		snprintf(tmpf, sizeof(tmpf), "pmcstat -k %s -R %s -m %s", +		    kfile, ofile, tofl); +	else +		snprintf(tmpf, sizeof(tmpf), "pmcstat -R %s -m %s", ofile, +		    tofl); +	if (system(tmpf) != 0) +		FATAL(exec, "%s: Impossible to create the tmp file\n", +		    exec); + +	gfp = fopen(tofl, "r"); +	if (gfp == NULL) +		FATAL(exec, "%s: Impossible to open the map file\n", +		    exec); + +	/* +	 * Make the collection of raw entries from a pmcstat mapped file. +	 * The heuristic here wants strings in the form: +	 * "addr funcname startfaddr endfaddr". +	 */ +	while (fgets(buffer, LNBUFF, gfp) != NULL) { +		if (isspace(buffer[0])) +			continue; +		if (sscanf(buffer, "%p %s %p %p\n", &ptr, fname, +		    &hstart, &hend) != 4) +			FATAL(NULL, +			    "%s: Invalid scan of function in the map file\n", +			    exec); +		ostart = (uintptr_t)hstart; +		oend = (uintptr_t)hend; +		tmppc = (uintptr_t)ptr; +		totalsamples++; +		obj = general_findent(tmppc); +		if (obj != NULL) { +			entry_acqref(obj); +			continue; +		} +		obj = entry_create(fname, tmppc, ostart, oend); +		if (obj == NULL) +			FATAL(exec, +			    "%s: Impossible to create a new object\n", exec); +		general_insertent(obj); +	} +	if (fclose(gfp) == EOF) +		FATAL(exec, "%s: Impossible to close the filedesc\n", +		    exec); +	if (remove(tofl) == -1) +                FATAL(exec, "%s: Impossible to remove the tmpfile\n", +                    exec); + +	/* +	 * Remove the loose end objects and feed the first-level aggregation +	 * queue. +	 */ +	if (fqueue_insertgen() == -1) +		FATAL(exec, "%s: Impossible to generate an analysis\n", +		    exec); +	fqueue_compact(limit); +	if (fqueue_getall(bin, tbfl, asmsrc) == -1) +		FATAL(exec, "%s: Impossible to create the tmp file\n", +		    exec); + +	bfp = fopen(tbfl, "r"); +	if (bfp == NULL) +		FATAL(exec, "%s: Impossible to open the binary file\n", +		    exec); + +	if (asmsrc != 0) +		asmparse(bfp); +	else if (cparse(bfp) == -1) +		FATAL(NULL, "%s: Invalid format for the C file\n", exec); +	if (fclose(bfp) == EOF) +                FATAL(exec, "%s: Impossible to close the filedesc\n", +                    exec); +	if (remove(tbfl) == -1) +                FATAL(exec, "%s: Impossible to remove the tmpfile\n", +                    exec); +	return (0); +} | 
