root/usr.sbin/pmcannotate/pmcannotate.c
/*-
 * 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);                                           \
        if (tbfl != NULL)                                               \
                remove(tbfl);                                           \
        if (tofl != NULL)                                               \
                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;

        tbfl = NULL;
        tofl = NULL;
        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);
}