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