1/*-
2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3 *
4 * Copyright (c) 2008 Nokia Corporation
5 * All rights reserved.
6 *
7 * This software was developed by Attilio Rao for the IPSO project under
8 * contract to Nokia Corporation.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 *    notice unmodified, this list of conditions, and the following
15 *    disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 *    notice, this list of conditions and the following disclaimer in the
18 *    documentation and/or other materials provided with the distribution.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
21 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
22 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
23 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
24 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
25 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
29 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 *
31 */
32
33#include <sys/cdefs.h>
34__FBSDID("$FreeBSD: stable/11/usr.sbin/pmcannotate/pmcannotate.c 330449 2018-03-05 07:26:05Z eadler $");
35
36#include <sys/param.h>
37#include <sys/queue.h>
38
39#include <ctype.h>
40#include <stdio.h>
41#include <stdlib.h>
42#include <string.h>
43
44#include <unistd.h>
45
46/* NB: Make sure FNBUFF is as large as LNBUFF, otherwise it could overflow */
47#define	FNBUFF	512
48#define	LNBUFF	512
49
50#define	TMPPATH	"/tmp/pmcannotate.XXXXXX"
51
52#define	FATAL(ptr, x ...) do {						\
53	fqueue_deleteall();						\
54	general_deleteall();						\
55	if ((ptr) != NULL)						\
56		perror(ptr);						\
57	fprintf(stderr, ##x);						\
58	remove(tbfl);							\
59	remove(tofl);							\
60	exit(EXIT_FAILURE);						\
61} while (0)
62
63#define	PERCSAMP(x)	((x) * 100 / totalsamples)
64
65struct entry {
66        TAILQ_ENTRY(entry)	en_iter;
67        char		*en_name;
68	uintptr_t	en_pc;
69	uintptr_t	en_ostart;
70	uintptr_t	en_oend;
71	u_int		en_nsamples;
72};
73
74struct aggent {
75	TAILQ_ENTRY(aggent)	ag_fiter;
76	long		ag_offset;
77	uintptr_t	ag_ostart;
78	uintptr_t	ag_oend;
79	char		*ag_name;
80	u_int		ag_nsamples;
81};
82
83static struct aggent	*agg_create(const char *name, u_int nsamples,
84			    uintptr_t start, uintptr_t end);
85static void		 agg_destroy(struct aggent *agg) __unused;
86static void		 asmparse(FILE *fp);
87static int		 cparse(FILE *fp);
88static void		 entry_acqref(struct entry *entry);
89static struct entry	*entry_create(const char *name, uintptr_t pc,
90			    uintptr_t start, uintptr_t end);
91static void		 entry_destroy(struct entry *entry) __unused;
92static void		 fqueue_compact(float th);
93static void		 fqueue_deleteall(void);
94static struct aggent	*fqueue_findent_by_name(const char *name);
95static int		 fqueue_getall(const char *bin, char *temp, int asmf);
96static int		 fqueue_insertent(struct entry *entry);
97static int		 fqueue_insertgen(void);
98static void		 general_deleteall(void);
99static struct entry	*general_findent(uintptr_t pc);
100static void		 general_insertent(struct entry *entry);
101static void		 general_printasm(FILE *fp, struct aggent *agg);
102static int		 general_printc(FILE *fp, struct aggent *agg);
103static int		 printblock(FILE *fp, struct aggent *agg);
104static void		 usage(const char *progname) __dead2;
105
106static TAILQ_HEAD(, entry) mainlst = TAILQ_HEAD_INITIALIZER(mainlst);
107static TAILQ_HEAD(, aggent) fqueue = TAILQ_HEAD_INITIALIZER(fqueue);
108
109/*
110 * Use a float value in order to automatically promote operations
111 * to return a float value rather than use casts.
112 */
113static float totalsamples;
114
115/*
116 * Identifies a string cointaining objdump's assembly printout.
117 */
118static inline int
119isasminline(const char *str)
120{
121	void *ptr;
122	int nbytes;
123
124	if (sscanf(str, " %p%n", &ptr, &nbytes) != 1)
125		return (0);
126	if (str[nbytes] != ':' || isspace(str[nbytes + 1]) == 0)
127		return (0);
128	return (1);
129}
130
131/*
132 * Identifies a string containing objdump's assembly printout
133 * for a new function.
134 */
135static inline int
136newfunction(const char *str)
137{
138	char fname[FNBUFF];
139	void *ptr;
140	int nbytes;
141
142	if (isspace(str[0]))
143		return (0);
144	if (sscanf(str, "%p <%[^>:]>:%n", &ptr, fname, &nbytes) != 2)
145		return (0);
146	return (nbytes);
147}
148
149/*
150 * Create a new first-level aggregation object for a specified
151 * function.
152 */
153static struct aggent *
154agg_create(const char *name, u_int nsamples, uintptr_t start, uintptr_t end)
155{
156	struct aggent *agg;
157
158	agg = calloc(1, sizeof(struct aggent));
159	if (agg == NULL)
160		return (NULL);
161	agg->ag_name = strdup(name);
162	if (agg->ag_name == NULL) {
163		free(agg);
164		return (NULL);
165	}
166	agg->ag_nsamples = nsamples;
167	agg->ag_ostart = start;
168	agg->ag_oend = end;
169	return (agg);
170}
171
172/*
173 * Destroy a first-level aggregation object for a specified
174 * function.
175 */
176static void
177agg_destroy(struct aggent *agg)
178{
179
180	free(agg->ag_name);
181	free(agg);
182}
183
184/*
185 * Analyze the "objdump -d" output, locate functions and start
186 * printing out the assembly functions content.
187 * We do not use newfunction() because we actually need the
188 * function name in available form, but the heurstic used is
189 * the same.
190 */
191static void
192asmparse(FILE *fp)
193{
194	char buffer[LNBUFF], fname[FNBUFF];
195	struct aggent *agg;
196	void *ptr;
197
198	while (fgets(buffer, LNBUFF, fp) != NULL) {
199		if (isspace(buffer[0]))
200			continue;
201		if (sscanf(buffer, "%p <%[^>:]>:", &ptr, fname) != 2)
202			continue;
203		agg = fqueue_findent_by_name(fname);
204		if (agg == NULL)
205			continue;
206		agg->ag_offset = ftell(fp);
207	}
208
209	TAILQ_FOREACH(agg, &fqueue, ag_fiter) {
210		if (fseek(fp, agg->ag_offset, SEEK_SET) == -1)
211			return;
212		printf("Profile trace for function: %s() [%.2f%%]\n",
213		    agg->ag_name, PERCSAMP(agg->ag_nsamples));
214		general_printasm(fp, agg);
215		printf("\n");
216	}
217}
218
219/*
220 * Analyze the "objdump -S" output, locate functions and start
221 * printing out the C functions content.
222 * We do not use newfunction() because we actually need the
223 * function name in available form, but the heurstic used is
224 * the same.
225 * In order to maintain the printout sorted, on the first pass it
226 * simply stores the file offsets in order to fastly moved later
227 * (when the file is hot-cached also) when the real printout will
228 * happen.
229 */
230static int
231cparse(FILE *fp)
232{
233	char buffer[LNBUFF], fname[FNBUFF];
234	struct aggent *agg;
235	void *ptr;
236
237	while (fgets(buffer, LNBUFF, fp) != NULL) {
238		if (isspace(buffer[0]))
239			continue;
240		if (sscanf(buffer, "%p <%[^>:]>:", &ptr, fname) != 2)
241			continue;
242		agg = fqueue_findent_by_name(fname);
243		if (agg == NULL)
244			continue;
245		agg->ag_offset = ftell(fp);
246	}
247
248	TAILQ_FOREACH(agg, &fqueue, ag_fiter) {
249		if (fseek(fp, agg->ag_offset, SEEK_SET) == -1)
250			return (-1);
251		printf("Profile trace for function: %s() [%.2f%%]\n",
252		    agg->ag_name, PERCSAMP(agg->ag_nsamples));
253		if (general_printc(fp, agg) == -1)
254			return (-1);
255		printf("\n");
256	}
257	return (0);
258}
259
260/*
261 * Bump the number of samples for any raw entry.
262 */
263static void
264entry_acqref(struct entry *entry)
265{
266
267	entry->en_nsamples++;
268}
269
270/*
271 * Create a new raw entry object for a specified function.
272 */
273static struct entry *
274entry_create(const char *name, uintptr_t pc, uintptr_t start, uintptr_t end)
275{
276	struct entry *obj;
277
278	obj = calloc(1, sizeof(struct entry));
279	if (obj == NULL)
280		return (NULL);
281	obj->en_name = strdup(name);
282	if (obj->en_name == NULL) {
283		free(obj);
284		return (NULL);
285	}
286	obj->en_pc = pc;
287	obj->en_ostart = start;
288	obj->en_oend = end;
289	obj->en_nsamples = 1;
290	return (obj);
291}
292
293/*
294 * Destroy a raw entry object for a specified function.
295 */
296static void
297entry_destroy(struct entry *entry)
298{
299
300	free(entry->en_name);
301	free(entry);
302}
303
304/*
305 * Specify a lower bound in percentage and drop from the
306 * first-level aggregation queue all the objects with a
307 * smaller impact.
308 */
309static void
310fqueue_compact(float th)
311{
312	u_int thi;
313	struct aggent *agg, *tmpagg;
314
315	if (totalsamples == 0)
316		return;
317
318	/* Revert the percentage calculation. */
319	thi = th * totalsamples / 100;
320	TAILQ_FOREACH_SAFE(agg, &fqueue, ag_fiter, tmpagg)
321		if (agg->ag_nsamples < thi)
322			TAILQ_REMOVE(&fqueue, agg, ag_fiter);
323}
324
325/*
326 * Flush the first-level aggregates queue.
327 */
328static void
329fqueue_deleteall(void)
330{
331	struct aggent *agg;
332
333	while (TAILQ_EMPTY(&fqueue) == 0) {
334		agg = TAILQ_FIRST(&fqueue);
335		TAILQ_REMOVE(&fqueue, agg, ag_fiter);
336	}
337}
338
339/*
340 * Insert a raw entry into the aggregations queue.
341 * If the respective first-level aggregation object
342 * does not exist create it and maintain it sorted
343 * in respect of the number of samples.
344 */
345static int
346fqueue_insertent(struct entry *entry)
347{
348	struct aggent *obj, *tmp;
349	int found;
350
351	found = 0;
352	TAILQ_FOREACH(obj, &fqueue, ag_fiter)
353		if (!strcmp(obj->ag_name, entry->en_name)) {
354			found = 1;
355			obj->ag_nsamples += entry->en_nsamples;
356			break;
357		}
358
359	/*
360	 * If the first-level aggregation object already exists,
361	 * just aggregate the samples and, if needed, resort
362	 * it.
363	 */
364	if (found) {
365		TAILQ_REMOVE(&fqueue, obj, ag_fiter);
366		found = 0;
367		TAILQ_FOREACH(tmp, &fqueue, ag_fiter)
368			if (obj->ag_nsamples > tmp->ag_nsamples) {
369				found = 1;
370				break;
371			}
372		if (found)
373			TAILQ_INSERT_BEFORE(tmp, obj, ag_fiter);
374		else
375			TAILQ_INSERT_TAIL(&fqueue, obj, ag_fiter);
376		return (0);
377	}
378
379	/*
380	 * If the first-level aggregation object does not
381	 * exist, create it and put in the sorted queue.
382	 * If this is the first object, we need to set the
383	 * head of the queue.
384	 */
385	obj = agg_create(entry->en_name, entry->en_nsamples, entry->en_ostart,
386	    entry->en_oend);
387	if (obj == NULL)
388		return (-1);
389	if (TAILQ_EMPTY(&fqueue) != 0) {
390		TAILQ_INSERT_HEAD(&fqueue, obj, ag_fiter);
391		return (0);
392	}
393	TAILQ_FOREACH(tmp, &fqueue, ag_fiter)
394		if (obj->ag_nsamples > tmp->ag_nsamples) {
395			found = 1;
396			break;
397		}
398	if (found)
399		TAILQ_INSERT_BEFORE(tmp, obj, ag_fiter);
400	else
401		TAILQ_INSERT_TAIL(&fqueue, obj, ag_fiter);
402	return (0);
403}
404
405/*
406 * Lookup a first-level aggregation object by name.
407 */
408static struct aggent *
409fqueue_findent_by_name(const char *name)
410{
411	struct aggent *obj;
412
413	TAILQ_FOREACH(obj, &fqueue, ag_fiter)
414		if (!strcmp(obj->ag_name, name))
415			return (obj);
416	return (NULL);
417}
418
419/*
420 * Return the number of object in the first-level aggregations queue.
421 */
422static int
423fqueue_getall(const char *bin, char *temp, int asmf)
424{
425	char tmpf[MAXPATHLEN * 2 + 50];
426	struct aggent *agg;
427	uintptr_t start, end;
428
429	if (mkstemp(temp) == -1)
430		return (-1);
431	TAILQ_FOREACH(agg, &fqueue, ag_fiter) {
432		bzero(tmpf, sizeof(tmpf));
433		start = agg->ag_ostart;
434		end = agg->ag_oend;
435
436		/*
437		 * Fix-up the end address in order to show it in the objdump's
438		 * trace.
439		 */
440		end++;
441		if (asmf)
442			snprintf(tmpf, sizeof(tmpf),
443			    "objdump --start-address=%p "
444			    "--stop-address=%p -d %s >> %s", (void *)start,
445			    (void *)end, bin, temp);
446		else
447			snprintf(tmpf, sizeof(tmpf),
448			    "objdump --start-address=%p "
449			    "--stop-address=%p -S %s >> %s", (void *)start,
450			    (void *)end, bin, temp);
451		if (system(tmpf) != 0)
452			return (-1);
453	}
454	return (0);
455}
456
457/*
458 * Insert all the raw entries present in the general queue
459 * into the first-level aggregations queue.
460 */
461static int
462fqueue_insertgen(void)
463{
464	struct entry *obj;
465
466	TAILQ_FOREACH(obj, &mainlst, en_iter)
467		if (fqueue_insertent(obj) == -1)
468			return (-1);
469	return (0);
470}
471
472/*
473 * Flush the raw entries general queue.
474 */
475static void
476general_deleteall(void)
477{
478	struct entry *obj;
479
480	while (TAILQ_EMPTY(&mainlst) == 0) {
481		obj = TAILQ_FIRST(&mainlst);
482		TAILQ_REMOVE(&mainlst, obj, en_iter);
483	}
484}
485
486/*
487 * Lookup a raw entry by the PC.
488 */
489static struct entry *
490general_findent(uintptr_t pc)
491{
492	struct entry *obj;
493
494	TAILQ_FOREACH(obj, &mainlst, en_iter)
495		if (obj->en_pc == pc)
496			return (obj);
497	return (NULL);
498}
499
500/*
501 * Insert a new raw entry in the general queue.
502 */
503static void
504general_insertent(struct entry *entry)
505{
506
507	TAILQ_INSERT_TAIL(&mainlst, entry, en_iter);
508}
509
510/*
511 * Printout the body of an "objdump -d" assembly function.
512 * It does simply stops when a new function is encountered,
513 * bringing back the file position in order to not mess up
514 * subsequent analysis.
515 * C lines and others not recognized are simply skipped.
516 */
517static void
518general_printasm(FILE *fp, struct aggent *agg)
519{
520	char buffer[LNBUFF];
521	struct entry *obj;
522	int nbytes;
523	void *ptr;
524
525	while (fgets(buffer, LNBUFF, fp) != NULL) {
526		if ((nbytes = newfunction(buffer)) != 0) {
527			fseek(fp, nbytes * -1, SEEK_CUR);
528			break;
529		}
530		if (!isasminline(buffer))
531			continue;
532		if (sscanf(buffer, " %p:", &ptr) != 1)
533			continue;
534		obj = general_findent((uintptr_t)ptr);
535		if (obj == NULL)
536			printf("\t| %s", buffer);
537		else
538			printf("%.2f%%\t| %s",
539			    (float)obj->en_nsamples * 100 / agg->ag_nsamples,
540			    buffer);
541	}
542}
543
544/*
545 * Printout the body of an "objdump -S" function.
546 * It does simply stops when a new function is encountered,
547 * bringing back the file position in order to not mess up
548 * subsequent analysis.
549 * It expect from the starting to the end to find, always, valid blocks
550 * (see below for an explanation of the "block" concept).
551 */
552static int
553general_printc(FILE *fp, struct aggent *agg)
554{
555	char buffer[LNBUFF];
556
557	while (fgets(buffer, LNBUFF, fp) != NULL) {
558		fseek(fp, strlen(buffer) * -1, SEEK_CUR);
559		if (newfunction(buffer) != 0)
560			break;
561		if (printblock(fp, agg) == -1)
562			return (-1);
563	}
564	return (0);
565}
566
567/*
568 * Printout a single block inside an "objdump -S" function.
569 * The block is composed of a first part in C and subsequent translation
570 * in assembly.
571 * This code also operates a second-level aggregation packing together
572 * samples relative to PCs into a (lower bottom) block with their
573 * C (higher half) counterpart.
574 */
575static int
576printblock(FILE *fp, struct aggent *agg)
577{
578	char buffer[LNBUFF];
579	long lstart;
580	struct entry *obj;
581	u_int tnsamples;
582	int done, nbytes, sentinel;
583	void *ptr;
584
585	/*
586	 * We expect the first thing of the block is C code, so simply give
587	 * up if asm line is found.
588	 */
589	lstart = ftell(fp);
590	sentinel = 0;
591	for (;;) {
592		if (fgets(buffer, LNBUFF, fp) == NULL)
593			return (0);
594		if (isasminline(buffer) != 0)
595			break;
596		sentinel = 1;
597		nbytes = newfunction(buffer);
598		if (nbytes != 0) {
599			if (fseek(fp, nbytes * -1, SEEK_CUR) == -1)
600				return (-1);
601			return (0);
602		}
603	}
604
605	/*
606	 * If the sentinel is not set, it means it did not match any
607	 * "high half" for this code so simply give up.
608	 * Operates the second-level aggregation.
609	 */
610	tnsamples = 0;
611	do {
612		if (sentinel == 0)
613			return (-1);
614		if (sscanf(buffer, " %p:", &ptr) != 1)
615			return (-1);
616		obj = general_findent((uintptr_t)ptr);
617		if (obj != NULL)
618			tnsamples += obj->en_nsamples;
619	} while (fgets(buffer, LNBUFF, fp) != NULL && isasminline(buffer) != 0);
620
621	/* Rewind to the start of the block in order to start the printout. */
622	if (fseek(fp, lstart, SEEK_SET) == -1)
623		return (-1);
624
625	/* Again the high half of the block rappresenting the C part. */
626	done = 0;
627	while (fgets(buffer, LNBUFF, fp) != NULL && isasminline(buffer) == 0) {
628		if (tnsamples == 0 || done != 0)
629			printf("\t| %s", buffer);
630		else {
631			done = 1;
632			printf("%.2f%%\t| %s",
633			    (float)tnsamples * 100 / agg->ag_nsamples, buffer);
634		}
635	}
636
637	/*
638	 * Again the low half of the block rappresenting the asm
639	 * translation part.
640	 */
641	for (;;) {
642		if (fgets(buffer, LNBUFF, fp) == NULL)
643			return (0);
644		if (isasminline(buffer) == 0)
645			break;
646		nbytes = newfunction(buffer);
647		if (nbytes != 0) {
648			if (fseek(fp, nbytes * -1, SEEK_CUR) == -1)
649				return (-1);
650			return (0);
651		}
652	}
653	if (fseek(fp, strlen(buffer) * -1, SEEK_CUR) == -1)
654		return (-1);
655	return (0);
656}
657
658/*
659 * Helper printout functions.
660 */
661static void
662usage(const char *progname)
663{
664
665	fprintf(stderr,
666	    "usage: %s [-a] [-h] [-k kfile] [-l lb] pmcraw.out binary\n",
667	    progname);
668	exit(EXIT_SUCCESS);
669}
670
671int
672main(int argc, char *argv[])
673{
674	char buffer[LNBUFF], fname[FNBUFF], tbfl[] = TMPPATH, tofl[] = TMPPATH;
675	char tmpf[MAXPATHLEN * 2 + 50];
676	float limit;
677	char *bin, *exec, *kfile, *ofile;
678	struct entry *obj;
679	FILE *gfp, *bfp;
680	void *ptr, *hstart, *hend;
681	uintptr_t tmppc, ostart, oend;
682	int cget, asmsrc;
683
684	exec = argv[0];
685	ofile = NULL;
686	bin = NULL;
687	kfile = NULL;
688	asmsrc = 0;
689	limit = 0.5;
690	while ((cget = getopt(argc, argv, "ahl:k:")) != -1)
691		switch(cget) {
692		case 'a':
693			asmsrc = 1;
694			break;
695		case 'k':
696			kfile = optarg;
697			break;
698		case 'l':
699			limit = (float)atof(optarg);
700			break;
701		case 'h':
702		case '?':
703		default:
704			usage(exec);
705		}
706	argc -= optind;
707	argv += optind;
708	if (argc != 2)
709		usage(exec);
710	ofile = argv[0];
711	bin = argv[1];
712
713	if (access(bin, R_OK | F_OK) == -1)
714		FATAL(exec, "%s: Impossible to locate the binary file\n",
715		    exec);
716	if (access(ofile, R_OK | F_OK) == -1)
717		FATAL(exec, "%s: Impossible to locate the pmcstat file\n",
718		    exec);
719	if (kfile != NULL && access(kfile, R_OK | F_OK) == -1)
720		FATAL(exec, "%s: Impossible to locate the kernel file\n",
721		    exec);
722
723	bzero(tmpf, sizeof(tmpf));
724	if (mkstemp(tofl) == -1)
725		FATAL(exec, "%s: Impossible to create the tmp file\n",
726		    exec);
727	if (kfile != NULL)
728		snprintf(tmpf, sizeof(tmpf), "pmcstat -k %s -R %s -m %s",
729		    kfile, ofile, tofl);
730	else
731		snprintf(tmpf, sizeof(tmpf), "pmcstat -R %s -m %s", ofile,
732		    tofl);
733	if (system(tmpf) != 0)
734		FATAL(exec, "%s: Impossible to create the tmp file\n",
735		    exec);
736
737	gfp = fopen(tofl, "r");
738	if (gfp == NULL)
739		FATAL(exec, "%s: Impossible to open the map file\n",
740		    exec);
741
742	/*
743	 * Make the collection of raw entries from a pmcstat mapped file.
744	 * The heuristic here wants strings in the form:
745	 * "addr funcname startfaddr endfaddr".
746	 */
747	while (fgets(buffer, LNBUFF, gfp) != NULL) {
748		if (isspace(buffer[0]))
749			continue;
750		if (sscanf(buffer, "%p %s %p %p\n", &ptr, fname,
751		    &hstart, &hend) != 4)
752			FATAL(NULL,
753			    "%s: Invalid scan of function in the map file\n",
754			    exec);
755		ostart = (uintptr_t)hstart;
756		oend = (uintptr_t)hend;
757		tmppc = (uintptr_t)ptr;
758		totalsamples++;
759		obj = general_findent(tmppc);
760		if (obj != NULL) {
761			entry_acqref(obj);
762			continue;
763		}
764		obj = entry_create(fname, tmppc, ostart, oend);
765		if (obj == NULL)
766			FATAL(exec,
767			    "%s: Impossible to create a new object\n", exec);
768		general_insertent(obj);
769	}
770	if (fclose(gfp) == EOF)
771		FATAL(exec, "%s: Impossible to close the filedesc\n",
772		    exec);
773	if (remove(tofl) == -1)
774                FATAL(exec, "%s: Impossible to remove the tmpfile\n",
775                    exec);
776
777	/*
778	 * Remove the loose end objects and feed the first-level aggregation
779	 * queue.
780	 */
781	if (fqueue_insertgen() == -1)
782		FATAL(exec, "%s: Impossible to generate an analysis\n",
783		    exec);
784	fqueue_compact(limit);
785	if (fqueue_getall(bin, tbfl, asmsrc) == -1)
786		FATAL(exec, "%s: Impossible to create the tmp file\n",
787		    exec);
788
789	bfp = fopen(tbfl, "r");
790	if (bfp == NULL)
791		FATAL(exec, "%s: Impossible to open the binary file\n",
792		    exec);
793
794	if (asmsrc != 0)
795		asmparse(bfp);
796	else if (cparse(bfp) == -1)
797		FATAL(NULL, "%s: Invalid format for the C file\n", exec);
798	if (fclose(bfp) == EOF)
799                FATAL(exec, "%s: Impossible to close the filedesc\n",
800                    exec);
801	if (remove(tbfl) == -1)
802                FATAL(exec, "%s: Impossible to remove the tmpfile\n",
803                    exec);
804	return (0);
805}
806