root/usr/src/cmd/ctfdump/ctfdump.c
/*
 * This file and its contents are supplied under the terms of the
 * Common Development and Distribution License ("CDDL"), version 1.0.
 * You may only use this file in accordance with the terms of version
 * 1.0 of the CDDL.
 *
 * A full copy of the text of the CDDL should have accompanied this
 * source.  A copy of the CDDL is also available via the Internet at
 * http://www.illumos.org/license/CDDL.
 */

/*
 * Copyright 2020 Joyent, Inc.
 * Copyright 2025 Oxide Computer Company
 */

/*
 * Dump information about CTF containers.
 */

#include <stdio.h>
#include <unistd.h>
#include <libctf.h>
#include <libgen.h>
#include <stdarg.h>
#include <stdlib.h>
#include <stddef.h>
#include <sys/sysmacros.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/note.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <strings.h>
#include <err.h>
#include <stdbool.h>

#define MAX_NAMELEN (512)

typedef enum ctfdump_arg {
        CTFDUMP_OBJECTS =       0x001,
        CTFDUMP_FUNCTIONS =     0x002,
        CTFDUMP_HEADER =        0x004,
        CTFDUMP_LABELS =        0x008,
        CTFDUMP_STRINGS =       0x010,
        CTFDUMP_STATS =         0x020,
        CTFDUMP_TYPES =         0x040,
        CTFDUMP_DEFAULT =       0x07f,
        CTFDUMP_OUTPUT =        0x080,
        CTFDUMP_SOURCE =        0x100,
} ctfdump_arg_t;

typedef struct ctfdump_stat {
        ulong_t         cs_ndata;               /* number of data objects */
        ulong_t         cs_nfuncs;              /* number of functions */
        ulong_t         cs_nfuncargs;           /* number of function args */
        ulong_t         cs_nfuncmax;            /* largest number of args */
        ulong_t         cs_ntypes[CTF_K_MAX];   /* number of types */
        ulong_t         cs_nsmembs;             /* number of struct members */
        ulong_t         cs_nsmax;               /* largest number of members */
        ulong_t         cs_structsz;            /* sum of structures sizes */
        ulong_t         cs_sszmax;              /* largest structure */
        ulong_t         cs_numembs;             /* number of union members */
        ulong_t         cs_numax;               /* largest number of members */
        ulong_t         cs_unionsz;             /* sum of unions sizes */
        ulong_t         cs_uszmax;              /* largest union */
        ulong_t         cs_nemembs;             /* number of enum members */
        ulong_t         cs_nemax;               /* largest number of members */
        ulong_t         cs_nstrings;            /* number of strings */
        ulong_t         cs_strsz;               /* string size */
        ulong_t         cs_strmax;              /* longest string */
} ctfdump_stat_t;

typedef struct {
        char ci_name[MAX_NAMELEN];
        ctf_id_t ci_id;
        ulong_t ci_symidx;
        ctf_funcinfo_t ci_funcinfo;
} ctf_idname_t;

static ctf_idname_t *idnames;
static const char *g_progname;
static ctfdump_arg_t g_dump;
static ctf_file_t *g_fp;
static ctfdump_stat_t g_stats;
static ctf_id_t *g_fargc;
static int g_nfargc;

static int g_exit = 0;

static const char *ctfdump_fpenc[] = {
        NULL,
        "SINGLE",
        "DOUBLE",
        "COMPLEX",
        "DCOMPLEX",
        "LDCOMPLEX",
        "LDOUBLE",
        "INTERVAL",
        "DINTERVAL",
        "LDINTERVAL",
        "IMAGINARY",
        "DIMAGINARY",
        "LDIMAGINARY"
};

/*
 * When stats are requested, we have to go through everything. To make our lives
 * easier, we'll just always allow the code to print everything out, but only
 * output it if we have actually enabled that section.
 */
static void
ctfdump_printf(ctfdump_arg_t arg, const char *fmt, ...)
{
        va_list ap;

        if ((arg & g_dump) == 0)
                return;

        va_start(ap, fmt);
        (void) vfprintf(stdout, fmt, ap);
        va_end(ap);
}

static void __NORETURN
ctfdump_fatal(const char *fmt, ...)
{
        va_list ap;

        (void) fprintf(stderr, "%s: ", g_progname);
        va_start(ap, fmt);
        (void) vfprintf(stderr, fmt, ap);
        va_end(ap);

        exit(1);
}

static void
ctfdump_usage(const char *fmt, ...)
{
        if (fmt != NULL) {
                va_list ap;
                (void) fprintf(stderr, "%s: ", g_progname);
                va_start(ap, fmt);
                (void) vfprintf(stderr, fmt, ap);
                va_end(ap);
        }

        (void) fprintf(stderr, "Usage: %s [-cdfhlsSt] [-p parent] [-u outfile] "
            "file\n"
            "\n"
            "\t-c  dump C-style output\n"
            "\t-d  dump object data\n"
            "\t-f  dump function data\n"
            "\t-h  dump the CTF header\n"
            "\t-l  dump the label table\n"
            "\t-p  use parent to supply additional information\n"
            "\t-s  dump the string table\n"
            "\t-S  dump statistics about the CTF container\n"
            "\t-t  dump type information\n"
            "\t-u  dump uncompressed CTF data to outfile\n",
            g_progname);
}

static void
ctfdump_title(ctfdump_arg_t arg, const char *header)
{
        static const char line[] = "----------------------------------------"
            "----------------------------------------";
        ctfdump_printf(arg, "\n- %s %.*s\n\n", header, (int)78 - strlen(header),
            line);
}

static int
ctfdump_objects_cb(const char *name, ctf_id_t id, ulong_t symidx, void *arg)
{
        _NOTE(ARGUNUSED(arg));

        int len;

        len = snprintf(NULL, 0, "  [%lu] %ld", g_stats.cs_ndata, id);
        ctfdump_printf(CTFDUMP_OBJECTS, "  [%lu] %ld %*s%s (%lu)\n",
            g_stats.cs_ndata, id, MAX(15 - len, 0), "", name, symidx);
        g_stats.cs_ndata++;
        return (0);
}

static void
ctfdump_objects(void)
{
        ctfdump_title(CTFDUMP_OBJECTS, "Data Objects");
        if (ctf_object_iter(g_fp, ctfdump_objects_cb, NULL) == CTF_ERR) {
                warnx("failed to dump objects: %s",
                    ctf_errmsg(ctf_errno(g_fp)));
                g_exit = 1;
        }
}

static void
ctfdump_fargs_grow(int nargs)
{
        if (g_nfargc < nargs) {
                g_fargc = realloc(g_fargc, sizeof (ctf_id_t) * nargs);
                if (g_fargc == NULL)
                        ctfdump_fatal("failed to get memory for %d "
                            "ctf_id_t's\n", nargs);
                g_nfargc = nargs;
        }
}

static int
ctfdump_functions_cb(const char *name, ulong_t symidx, ctf_funcinfo_t *ctc,
    void *arg)
{
        _NOTE(ARGUNUSED(arg));
        int i;

        if (ctc->ctc_argc != 0) {
                ctfdump_fargs_grow(ctc->ctc_argc);
                if (ctf_func_args(g_fp, symidx, g_nfargc, g_fargc) == CTF_ERR)
                        ctfdump_fatal("failed to get arguments for function "
                            "%s: %s\n", name, ctf_errmsg(ctf_errno(g_fp)));
        }

        ctfdump_printf(CTFDUMP_FUNCTIONS,
            "  [%lu] %s (%lu) returns: %ld args: (", g_stats.cs_nfuncs, name,
            symidx, ctc->ctc_return);
        for (i = 0; i < ctc->ctc_argc; i++)
                ctfdump_printf(CTFDUMP_FUNCTIONS, "%ld%s", g_fargc[i],
                    i + 1 == ctc->ctc_argc ? "" : ", ");
        if (ctc->ctc_flags & CTF_FUNC_VARARG)
                ctfdump_printf(CTFDUMP_FUNCTIONS, "%s...",
                    ctc->ctc_argc == 0 ? "" : ", ");
        ctfdump_printf(CTFDUMP_FUNCTIONS, ")\n");

        g_stats.cs_nfuncs++;
        g_stats.cs_nfuncargs += ctc->ctc_argc;
        g_stats.cs_nfuncmax = MAX(ctc->ctc_argc, g_stats.cs_nfuncmax);

        return (0);
}

static void
ctfdump_functions(void)
{
        ctfdump_title(CTFDUMP_FUNCTIONS, "Functions");

        if (ctf_function_iter(g_fp, ctfdump_functions_cb, NULL) == CTF_ERR) {
                warnx("failed to dump functions: %s",
                    ctf_errmsg(ctf_errno(g_fp)));
                g_exit = 1;
        }
}

static void
ctfdump_header(void)
{
        const ctf_header_t *hp;
        const char *parname, *parlabel;

        ctfdump_title(CTFDUMP_HEADER, "CTF Header");
        ctf_dataptr(g_fp, (const void **)&hp, NULL);
        ctfdump_printf(CTFDUMP_HEADER, "  cth_magic    = 0x%04x\n",
            hp->cth_magic);
        ctfdump_printf(CTFDUMP_HEADER, "  cth_version  = %u\n",
            hp->cth_version);
        ctfdump_printf(CTFDUMP_HEADER, "  cth_flags    = 0x%02x\n",
            ctf_flags(g_fp));
        parname = ctf_parent_name(g_fp);
        parlabel = ctf_parent_label(g_fp);
        ctfdump_printf(CTFDUMP_HEADER, "  cth_parlabel = %s\n",
            parlabel == NULL ? "(anon)" : parlabel);
        ctfdump_printf(CTFDUMP_HEADER, "  cth_parname  = %s\n",
            parname == NULL ? "(anon)" : parname);
        ctfdump_printf(CTFDUMP_HEADER, "  cth_lbloff   = %u\n",
            hp->cth_lbloff);
        ctfdump_printf(CTFDUMP_HEADER, "  cth_objtoff  = %u\n",
            hp->cth_objtoff);
        ctfdump_printf(CTFDUMP_HEADER, "  cth_funcoff  = %u\n",
            hp->cth_funcoff);
        ctfdump_printf(CTFDUMP_HEADER, "  cth_typeoff  = %u\n",
            hp->cth_typeoff);
        ctfdump_printf(CTFDUMP_HEADER, "  cth_stroff   = %u\n",
            hp->cth_stroff);
        ctfdump_printf(CTFDUMP_HEADER, "  cth_strlen   = %u\n",
            hp->cth_strlen);
}

static int
ctfdump_labels_cb(const char *name, const ctf_lblinfo_t *li, void *arg)
{
        _NOTE(ARGUNUSED(arg));
        ctfdump_printf(CTFDUMP_LABELS, "  %5ld %s\n", li->ctb_typeidx, name);
        return (0);
}

static void
ctfdump_labels(void)
{
        ctfdump_title(CTFDUMP_LABELS, "Label Table");
        if (ctf_label_iter(g_fp, ctfdump_labels_cb, NULL) == CTF_ERR) {
                warnx("failed to dump labels: %s",
                    ctf_errmsg(ctf_errno(g_fp)));
                g_exit = 1;
        }
}

static int
ctfdump_strings_cb(const char *s, void *arg)
{
        size_t len = strlen(s) + 1;
        ulong_t *stroff = arg;
        ctfdump_printf(CTFDUMP_STRINGS, "  [%lu] %s\n", *stroff,
            *s == '\0' ? "\\0" : s);
        *stroff = *stroff + len;
        g_stats.cs_nstrings++;
        g_stats.cs_strsz += len;
        g_stats.cs_strmax = MAX(g_stats.cs_strmax, len);
        return (0);
}

static void
ctfdump_strings(void)
{
        ulong_t stroff = 0;

        ctfdump_title(CTFDUMP_STRINGS, "String Table");
        if (ctf_string_iter(g_fp, ctfdump_strings_cb, &stroff) == CTF_ERR) {
                warnx("failed to dump strings: %s",
                    ctf_errmsg(ctf_errno(g_fp)));
                g_exit = 1;
        }
}

static void
ctfdump_stat_int(const char *name, ulong_t value)
{
        ctfdump_printf(CTFDUMP_STATS, "  %-36s= %lu\n", name, value);
}

static void
ctfdump_stat_fp(const char *name, float value)
{
        ctfdump_printf(CTFDUMP_STATS, "  %-36s= %.2f\n", name, value);
}

static void
ctfdump_stats(void)
{
        int i;
        ulong_t sum;

        ctfdump_title(CTFDUMP_STATS, "CTF Statistics");

        ctfdump_stat_int("total number of data objects", g_stats.cs_ndata);
        ctfdump_printf(CTFDUMP_STATS, "\n");
        ctfdump_stat_int("total number of functions", g_stats.cs_nfuncs);
        ctfdump_stat_int("total number of function arguments",
            g_stats.cs_nfuncargs);
        ctfdump_stat_int("maximum argument list length", g_stats.cs_nfuncmax);
        if (g_stats.cs_nfuncs != 0)
                ctfdump_stat_fp("average argument list length",
                    (float)g_stats.cs_nfuncargs / (float)g_stats.cs_nfuncs);
        ctfdump_printf(CTFDUMP_STATS, "\n");

        sum = 0;
        for (i = 0; i < CTF_K_MAX; i++)
                sum += g_stats.cs_ntypes[i];
        ctfdump_stat_int("total number of types", sum);
        ctfdump_stat_int("total number of integers",
            g_stats.cs_ntypes[CTF_K_INTEGER]);
        ctfdump_stat_int("total number of floats",
            g_stats.cs_ntypes[CTF_K_FLOAT]);
        ctfdump_stat_int("total number of pointers",
            g_stats.cs_ntypes[CTF_K_POINTER]);
        ctfdump_stat_int("total number of arrays",
            g_stats.cs_ntypes[CTF_K_ARRAY]);
        ctfdump_stat_int("total number of func types",
            g_stats.cs_ntypes[CTF_K_FUNCTION]);
        ctfdump_stat_int("total number of structs",
            g_stats.cs_ntypes[CTF_K_STRUCT]);
        ctfdump_stat_int("total number of unions",
            g_stats.cs_ntypes[CTF_K_UNION]);
        ctfdump_stat_int("total number of enums",
            g_stats.cs_ntypes[CTF_K_ENUM]);
        ctfdump_stat_int("total number of forward tags",
            g_stats.cs_ntypes[CTF_K_FORWARD]);
        ctfdump_stat_int("total number of typedefs",
            g_stats.cs_ntypes[CTF_K_TYPEDEF]);
        ctfdump_stat_int("total number of volatile types",
            g_stats.cs_ntypes[CTF_K_VOLATILE]);
        ctfdump_stat_int("total number of const types",
            g_stats.cs_ntypes[CTF_K_CONST]);
        ctfdump_stat_int("total number of restrict types",
            g_stats.cs_ntypes[CTF_K_RESTRICT]);
        ctfdump_stat_int("total number of unknowns (holes)",
            g_stats.cs_ntypes[CTF_K_UNKNOWN]);

        ctfdump_printf(CTFDUMP_STATS, "\n");
        ctfdump_stat_int("total number of struct members", g_stats.cs_nsmembs);
        ctfdump_stat_int("maximum number of struct members", g_stats.cs_nsmax);
        ctfdump_stat_int("total size of all structs", g_stats.cs_structsz);
        ctfdump_stat_int("maximum size of a struct", g_stats.cs_sszmax);
        if (g_stats.cs_ntypes[CTF_K_STRUCT] != 0) {
                ctfdump_stat_fp("average number of struct members",
                    (float)g_stats.cs_nsmembs /
                    (float)g_stats.cs_ntypes[CTF_K_STRUCT]);
                ctfdump_stat_fp("average size of a struct",
                    (float)g_stats.cs_structsz /
                    (float)g_stats.cs_ntypes[CTF_K_STRUCT]);
        }
        ctfdump_printf(CTFDUMP_STATS, "\n");
        ctfdump_stat_int("total number of union members", g_stats.cs_numembs);
        ctfdump_stat_int("maximum number of union members", g_stats.cs_numax);
        ctfdump_stat_int("total size of all unions", g_stats.cs_unionsz);
        ctfdump_stat_int("maximum size of a union", g_stats.cs_uszmax);
        if (g_stats.cs_ntypes[CTF_K_UNION] != 0) {
                ctfdump_stat_fp("average number of union members",
                    (float)g_stats.cs_numembs /
                    (float)g_stats.cs_ntypes[CTF_K_UNION]);
                ctfdump_stat_fp("average size of a union",
                    (float)g_stats.cs_unionsz /
                    (float)g_stats.cs_ntypes[CTF_K_UNION]);
        }
        ctfdump_printf(CTFDUMP_STATS, "\n");

        ctfdump_stat_int("total number of enum members", g_stats.cs_nemembs);
        ctfdump_stat_int("maximum number of enum members", g_stats.cs_nemax);
        if (g_stats.cs_ntypes[CTF_K_ENUM] != 0) {
                ctfdump_stat_fp("average number of enum members",
                    (float)g_stats.cs_nemembs /
                    (float)g_stats.cs_ntypes[CTF_K_ENUM]);
        }
        ctfdump_printf(CTFDUMP_STATS, "\n");

        ctfdump_stat_int("total number of strings", g_stats.cs_nstrings);
        ctfdump_stat_int("bytes of string data", g_stats.cs_strsz);
        ctfdump_stat_int("maximum string length", g_stats.cs_strmax);
        if (g_stats.cs_nstrings != 0)
                ctfdump_stat_fp("average string length",
                    (float)g_stats.cs_strsz / (float)g_stats.cs_nstrings);
        ctfdump_printf(CTFDUMP_STATS, "\n");
}

static void
ctfdump_intenc_name(ctf_encoding_t *cte, char *buf, int len)
{
        int off = 0;
        boolean_t space = B_FALSE;

        if (cte->cte_format == 0 || (cte->cte_format &
            ~(CTF_INT_SIGNED | CTF_INT_CHAR | CTF_INT_BOOL |
            CTF_INT_VARARGS)) != 0) {
                (void) snprintf(buf, len, "0x%x", cte->cte_format);
                return;
        }

        if (cte->cte_format & CTF_INT_SIGNED) {
                off += snprintf(buf + off, MAX(len - off, 0), "%sSIGNED",
                    space == B_TRUE ? " " : "");
                space = B_TRUE;
        }

        if (cte->cte_format & CTF_INT_CHAR) {
                off += snprintf(buf + off, MAX(len - off, 0), "%sCHAR",
                    space == B_TRUE ? " " : "");
                space = B_TRUE;
        }

        if (cte->cte_format & CTF_INT_BOOL) {
                off += snprintf(buf + off, MAX(len - off, 0), "%sBOOL",
                    space == B_TRUE ? " " : "");
                space = B_TRUE;
        }

        if (cte->cte_format & CTF_INT_VARARGS) {
                off += snprintf(buf + off, MAX(len - off, 0), "%sVARARGS",
                    space == B_TRUE ? " " : "");
                space = B_TRUE;
        }
}

static int
ctfdump_member_cb(const char *member, ctf_id_t type, ulong_t off, void *arg)
{
        int *count = arg;
        if (*member == '\0')
                member = "<anon>";
        ctfdump_printf(CTFDUMP_TYPES, "\t%s type=%ld off=%lu bits (%lu.%lu "
            "bytes)\n", member, type, off, off / 8, off % 8);
        *count = *count + 1;
        return (0);
}

static int
ctfdump_enum_cb(const char *name, int value, void *arg)
{
        int *count = arg;
        ctfdump_printf(CTFDUMP_TYPES, "\t%s = %d\n", name, value);
        *count = *count + 1;
        return (0);
}

static bool
is_anon_refname(const char *refname)
{
        return ((strcmp(refname, "struct ") == 0 ||
            strcmp(refname, "union ") == 0 ||
            strcmp(refname, "enum ") == 0));
}

static int
ctfdump_types_cb(ctf_id_t id, boolean_t root, void *arg)
{
        _NOTE(ARGUNUSED(arg));
        int kind, i, count;
        ctf_id_t ref;
        char name[MAX_NAMELEN], ienc[128];
        const char *encn;
        ctf_funcinfo_t ctc;
        ctf_arinfo_t ar;
        ctf_encoding_t cte;
        ssize_t size;

        if ((kind = ctf_type_kind(g_fp, id)) == CTF_ERR)
                ctfdump_fatal("encountered malformed ctf, type %s does not "
                    "have a kind: %s\n", name, ctf_errmsg(ctf_errno(g_fp)));

        if (ctf_type_name(g_fp, id, name, sizeof (name)) == NULL) {
                if (ctf_errno(g_fp) != ECTF_NOPARENT)
                        ctfdump_fatal("type %ld missing name: %s\n", id,
                            ctf_errmsg(ctf_errno(g_fp)));
                (void) snprintf(name, sizeof (name), "(unknown %s)",
                    ctf_kind_name(g_fp, kind));
        }

        g_stats.cs_ntypes[kind]++;
        if (root == B_TRUE)
                ctfdump_printf(CTFDUMP_TYPES, "  <%ld> ", id);
        else
                ctfdump_printf(CTFDUMP_TYPES, "  [%ld] ", id);

        switch (kind) {
        case CTF_K_UNKNOWN:
                break;
        case CTF_K_INTEGER:
                if (ctf_type_encoding(g_fp, id, &cte) == CTF_ERR)
                        ctfdump_fatal("failed to get encoding information "
                            "for %s: %s\n", name, ctf_errmsg(ctf_errno(g_fp)));
                ctfdump_intenc_name(&cte, ienc, sizeof (ienc));
                ctfdump_printf(CTFDUMP_TYPES,
                    "%s encoding=%s offset=%u bits=%u",
                    name, ienc, cte.cte_offset, cte.cte_bits);
                break;
        case CTF_K_FLOAT:
                if (ctf_type_encoding(g_fp, id, &cte) == CTF_ERR)
                        ctfdump_fatal("failed to get encoding information "
                            "for %s: %s\n", name, ctf_errmsg(ctf_errno(g_fp)));
                if (cte.cte_format < 1 || cte.cte_format > 12)
                        encn = "unknown";
                else
                        encn = ctfdump_fpenc[cte.cte_format];
                ctfdump_printf(CTFDUMP_TYPES, "%s encoding=%s offset=%u "
                    "bits=%u", name, encn, cte.cte_offset, cte.cte_bits);
                break;
        case CTF_K_POINTER:
                if ((ref = ctf_type_reference(g_fp, id)) == CTF_ERR)
                        ctfdump_fatal("failed to get reference type for %s: "
                            "%s\n", name, ctf_errmsg(ctf_errno(g_fp)));
                ctfdump_printf(CTFDUMP_TYPES, "%s refers to %ld", name,
                    ref);
                break;
        case CTF_K_ARRAY:
                if (ctf_array_info(g_fp, id, &ar) == CTF_ERR)
                        ctfdump_fatal("failed to get array information for "
                            "%s: %s\n", name, ctf_errmsg(ctf_errno(g_fp)));
                ctfdump_printf(CTFDUMP_TYPES, "%s contents: %ld, index: %ld",
                    name, ar.ctr_contents, ar.ctr_index);
                break;
        case CTF_K_FUNCTION:
                if (ctf_func_info_by_id(g_fp, id, &ctc) == CTF_ERR)
                        ctfdump_fatal("failed to get function info for %s: "
                            "%s\n", name, ctf_errmsg(ctf_errno(g_fp)));
                if (ctc.ctc_argc > 0) {
                        ctfdump_fargs_grow(ctc.ctc_argc);
                        if (ctf_func_args_by_id(g_fp, id, g_nfargc, g_fargc) ==
                            CTF_ERR)
                                ctfdump_fatal("failed to get function "
                                    "arguments for %s: %s\n", name,
                                    ctf_errmsg(ctf_errno(g_fp)));
                }
                ctfdump_printf(CTFDUMP_TYPES,
                    "%s returns: %ld args: (", name, ctc.ctc_return);
                for (i = 0; i < ctc.ctc_argc; i++) {
                        ctfdump_printf(CTFDUMP_TYPES, "%ld%s", g_fargc[i],
                            i + 1 == ctc.ctc_argc ? "" : ", ");
                }
                if (ctc.ctc_flags & CTF_FUNC_VARARG)
                        ctfdump_printf(CTFDUMP_TYPES, "%s...",
                            ctc.ctc_argc == 0 ? "" : ", ");
                ctfdump_printf(CTFDUMP_TYPES, ")");
                break;
        case CTF_K_STRUCT:
        case CTF_K_UNION:
                size = ctf_type_size(g_fp, id);
                if (size == CTF_ERR)
                        ctfdump_fatal("failed to get size of %s: %s\n", name,
                            ctf_errmsg(ctf_errno(g_fp)));
                if (is_anon_refname(name)) {
                        (void) strlcat(name, "<anon>", sizeof (name));
                }
                ctfdump_printf(CTFDUMP_TYPES, "%s (%zd bytes)\n", name, size);
                count = 0;
                if (ctf_member_iter(g_fp, id, ctfdump_member_cb, &count) != 0)
                        ctfdump_fatal("failed to iterate members of %s: %s\n",
                            name, ctf_errmsg(ctf_errno(g_fp)));
                if (kind == CTF_K_STRUCT) {
                        g_stats.cs_nsmembs += count;
                        g_stats.cs_nsmax = MAX(count, g_stats.cs_nsmax);
                        g_stats.cs_structsz += size;
                        g_stats.cs_sszmax = MAX(size, g_stats.cs_sszmax);
                } else {
                        g_stats.cs_numembs += count;
                        g_stats.cs_numax = MAX(count, g_stats.cs_numax);
                        g_stats.cs_unionsz += size;
                        g_stats.cs_uszmax = MAX(count, g_stats.cs_uszmax);
                }
                break;
        case CTF_K_ENUM:
                size = ctf_type_size(g_fp, id);

                /* Only the oddest enums are worth reporting on size. */
                if (size != CTF_ERR && size != sizeof (int)) {
                        ctfdump_printf(CTFDUMP_TYPES, "%s (%zd bytes)\n",
                            name, size);
                } else {
                        ctfdump_printf(CTFDUMP_TYPES, "%s\n", name);
                }

                count = 0;
                if (ctf_enum_iter(g_fp, id, ctfdump_enum_cb, &count) != 0)
                        ctfdump_fatal("failed to iterate enumerators of %s: "
                            "%s\n", name, ctf_errmsg(ctf_errno(g_fp)));
                g_stats.cs_nemembs += count;
                g_stats.cs_nemax = MAX(g_stats.cs_nemax, count);
                break;
        case CTF_K_FORWARD:
                ctfdump_printf(CTFDUMP_TYPES, "forward %s\n", name);
                break;
        case CTF_K_TYPEDEF:
                if ((ref = ctf_type_reference(g_fp, id)) == CTF_ERR)
                        ctfdump_fatal("failed to get reference type for %s: "
                            "%s\n", name, ctf_errmsg(ctf_errno(g_fp)));
                ctfdump_printf(CTFDUMP_TYPES, "typedef %s refers to %ld", name,
                    ref);
                break;
        case CTF_K_VOLATILE:
                if ((ref = ctf_type_reference(g_fp, id)) == CTF_ERR)
                        ctfdump_fatal("failed to get reference type for %s: "
                            "%s\n", name, ctf_errmsg(ctf_errno(g_fp)));
                ctfdump_printf(CTFDUMP_TYPES, "%s refers to %ld", name,
                    ref);
                break;
        case CTF_K_CONST:
                if ((ref = ctf_type_reference(g_fp, id)) == CTF_ERR)
                        ctfdump_fatal("failed to get reference type for %s: "
                            "%s\n", name, ctf_errmsg(ctf_errno(g_fp)));
                ctfdump_printf(CTFDUMP_TYPES, "%s refers to %ld", name,
                    ref);
                break;
        case CTF_K_RESTRICT:
                if ((ref = ctf_type_reference(g_fp, id)) == CTF_ERR)
                        ctfdump_fatal("failed to get reference type for %s: "
                            "%s\n", name, ctf_errmsg(ctf_errno(g_fp)));
                ctfdump_printf(CTFDUMP_TYPES, "%s refers to %ld", name,
                    ref);
                break;
        default:
                ctfdump_fatal("encountered unknown kind for type %s: %d\n",
                    name, kind);
        }

        ctfdump_printf(CTFDUMP_TYPES, "\n");

        return (0);
}

static void
ctfdump_types(void)
{
        ctfdump_title(CTFDUMP_TYPES, "Types");

        if (ctf_type_iter(g_fp, B_TRUE, ctfdump_types_cb, NULL) == CTF_ERR) {
                warnx("failed to dump types: %s",
                    ctf_errmsg(ctf_errno(g_fp)));
                g_exit = 1;
        }
}

/*
 * C-style output. This is designed mainly for comparison purposes, and doesn't
 * produce directly valid C:
 *
 * - the declarations are sorted alphabetically not semantically
 * - anonymous enums without other users are elided (e.g. IDCS_PROBE_SENT)
 * - doubly-pointed-to functions are wrong (e.g. in kiconv_ops_t)
 * - anon unions declared within SOUs aren't expanded
 * - function arguments aren't expanded recursively
 */

static const char *
ctfsrc_refname(ctf_id_t id, char *buf, size_t bufsize)
{
        ctf_id_t ref;

        if ((ref = ctf_type_reference(g_fp, id)) == CTF_ERR) {
                ctfdump_fatal("failed to get reference type for %ld: "
                    "%s\n", id, ctf_errmsg(ctf_errno(g_fp)));
        }

        return (ctf_type_name(g_fp, ref, buf, bufsize));
}

static int
ctfsrc_member_cb(const char *member, ctf_id_t type, ulong_t off, void *arg)
{
        _NOTE(ARGUNUSED(arg));
        char name[MAX_NAMELEN];

        if (ctf_type_cname(g_fp, type, name, sizeof (name), member) == NULL) {
                if (ctf_errno(g_fp) != ECTF_NOPARENT) {
                        ctfdump_fatal("type %ld missing name: %s\n", type,
                            ctf_errmsg(ctf_errno(g_fp)));
                }

                (void) snprintf(name, sizeof (name), "unknown_t %s", member);
        }

        /*
         * A byte offset is friendlier, but we'll print bits too if it's not
         * aligned (i.e. a bitfield).
         */
        if (off % NBBY != 0) {
                printf("\t%s; /* offset: %lu bytes (%lu bits) */\n",
                    name, off / NBBY, off);
        } else {
                printf("\t%s; /* offset: %lu bytes */\n",
                    name, off / NBBY);
        }
        return (0);
}

static int
ctfsrc_enum_cb(const char *name, int value, void *arg)
{
        _NOTE(ARGUNUSED(arg));
        printf("\t%s = %d,\n", name, value);
        return (0);
}

static int
ctfsrc_collect_types_cb(ctf_id_t id, boolean_t root, void *arg)
{
        _NOTE(ARGUNUSED(root, arg));
        (void) ctf_type_name(g_fp, id, idnames[id].ci_name,
            sizeof (idnames[id].ci_name));
        idnames[id].ci_id = id;
        return (0);
}

static void
ctfsrc_type(ctf_id_t id, const char *name)
{
        char refname[MAX_NAMELEN] = "unknown_t";
        ctf_id_t ref;
        ssize_t size;
        int kind;

        if ((kind = ctf_type_kind(g_fp, id)) == CTF_ERR) {
                ctfdump_fatal("encountered malformed ctf, type %s does not "
                    "have a kind: %s\n", name, ctf_errmsg(ctf_errno(g_fp)));
        }

        switch (kind) {
        case CTF_K_STRUCT:
        case CTF_K_UNION:
                /*
                 * Delay printing anonymous SOUs; a later typedef will usually
                 * pick them up.
                 */
                if (is_anon_refname(name))
                        break;

                if ((size = ctf_type_size(g_fp, id)) == CTF_ERR) {
                        ctfdump_fatal("failed to get size of %s: %s\n", name,
                            ctf_errmsg(ctf_errno(g_fp)));
                }

                printf("%s { /* 0x%x bytes */\n", name, size);

                if (ctf_member_iter(g_fp, id, ctfsrc_member_cb, NULL) != 0) {
                        ctfdump_fatal("failed to iterate members of %s: %s\n",
                            name, ctf_errmsg(ctf_errno(g_fp)));
                }

                printf("};\n\n");
                break;
        case CTF_K_ENUM:
                /*
                 * This will throw away any anon enum that isn't followed by a
                 * typedef...
                 */
                if (is_anon_refname(name))
                        break;

                printf("%s {\n", name);

                if (ctf_enum_iter(g_fp, id, ctfsrc_enum_cb, NULL) != 0) {
                        ctfdump_fatal("failed to iterate enumerators of %s: "
                            "%s\n", name, ctf_errmsg(ctf_errno(g_fp)));
                }

                size = ctf_type_size(g_fp, id);

                /* Only the oddest enums are worth reporting on size. */
                if (size != CTF_ERR && size != sizeof (int)) {
                        printf("} /* 0x%x bytes */;\n\n", size);
                } else {
                        printf("};\n\n");
                }
                break;
        case CTF_K_TYPEDEF:
                /*
                 * If this fails, it's probably because the referent type is in
                 * a parent container that was not supplied via -p.
                 */
                if (ctfsrc_refname(id, refname, sizeof (refname)) == NULL) {
                        printf("typedef %s %s;\n\n", refname, name);
                        break;
                }

                if (!is_anon_refname(refname)) {
                        (void) ctf_type_cname(g_fp,
                            ctf_type_reference(g_fp, id), refname,
                            sizeof (refname), name);

                        printf("typedef %s;\n\n", refname);
                        break;
                }

                ref = ctf_type_reference(g_fp, id);

                if (ctf_type_kind(g_fp, ref) == CTF_K_ENUM) {
                        printf("typedef enum {\n");

                        if (ctf_enum_iter(g_fp, ref,
                            ctfsrc_enum_cb, NULL) != 0) {
                                ctfdump_fatal("failed to iterate enumerators "
                                    "of %s: %s\n", refname,
                                    ctf_errmsg(ctf_errno(g_fp)));
                        }

                        printf("} %s;\n\n", name);
                } else {
                        if ((size = ctf_type_size(g_fp, ref)) == CTF_ERR) {
                                ctfdump_fatal("failed to get size of %s: %s\n",
                                    refname, ctf_errmsg(ctf_errno(g_fp)));
                        }

                        printf("typedef %s{ /* 0x%zx bytes */\n",
                            refname, size);

                        if (ctf_member_iter(g_fp, ref,
                            ctfsrc_member_cb, NULL) != 0) {
                                ctfdump_fatal("failed to iterate members "
                                    "of %s: %s\n", refname,
                                    ctf_errmsg(ctf_errno(g_fp)));
                        }

                        printf("} %s;\n\n", name);
                }

                break;
        case CTF_K_FORWARD:
                printf("%s;\n\n", name);
                break;
        case CTF_K_UNKNOWN:
        case CTF_K_INTEGER:
        case CTF_K_FLOAT:
        case CTF_K_POINTER:
        case CTF_K_ARRAY:
        case CTF_K_FUNCTION:
        case CTF_K_VOLATILE:
        case CTF_K_CONST:
        case CTF_K_RESTRICT:
                break;
        default:
                ctfdump_fatal("encountered unknown kind for type %s: %d\n",
                    name, kind);
                break;
        }
}

static int
ctfsrc_collect_objects_cb(const char *name, ctf_id_t id,
    ulong_t symidx, void *arg)
{
        size_t *count = arg;

        /* local static vars can have an unknown ID */
        if (id == 0)
                return (0);

        (void) strlcpy(idnames[*count].ci_name, name,
            sizeof (idnames[*count].ci_name));
        idnames[*count].ci_id = id;
        idnames[*count].ci_symidx = symidx;
        *count = *count + 1;
        return (0);
}

static void
ctfsrc_object(ctf_id_t id, const char *name)
{
        char tname[MAX_NAMELEN];

        if (ctf_type_cname(g_fp, id, tname, sizeof (tname), name) == NULL) {
                if (ctf_errno(g_fp) != ECTF_NOPARENT) {
                        ctfdump_fatal("type %ld missing name: %s\n", id,
                            ctf_errmsg(ctf_errno(g_fp)));
                }
                (void) snprintf(tname, sizeof (tname), "unknown_t %s", name);
        }

        printf("extern %s;\n", tname);
}

static int
ctfsrc_collect_functions_cb(const char *name, ulong_t symidx,
    ctf_funcinfo_t *ctc, void *arg)
{
        size_t *count = arg;

        (void) strlcpy(idnames[*count].ci_name, name,
            sizeof (idnames[*count].ci_name));
        bcopy(ctc, &idnames[*count].ci_funcinfo, sizeof (*ctc));
        idnames[*count].ci_id = 0;
        idnames[*count].ci_symidx = symidx;
        *count = *count + 1;
        return (0);
}

static void
ctfsrc_function(ctf_idname_t *idn)
{
        ctf_funcinfo_t *cfi = &idn->ci_funcinfo;
        char name[MAX_NAMELEN] = "unknown_t";

        (void) ctf_type_name(g_fp, cfi->ctc_return, name, sizeof (name));

        printf("extern %s %s(", name, idn->ci_name);

        if (cfi->ctc_argc != 0) {
                ctfdump_fargs_grow(cfi->ctc_argc);
                if (ctf_func_args(g_fp, idn->ci_symidx,
                    g_nfargc, g_fargc) == CTF_ERR) {
                        ctfdump_fatal("failed to get arguments for function "
                            "%s: %s\n", idn->ci_name,
                            ctf_errmsg(ctf_errno(g_fp)));
                }

                for (size_t i = 0; i < cfi->ctc_argc; i++) {
                        ctf_id_t aid = g_fargc[i];

                        (void) strlcpy(name, "unknown_t", sizeof (name));

                        (void) ctf_type_name(g_fp, aid, name, sizeof (name));

                        printf("%s%s", name,
                            i + 1 == cfi->ctc_argc ? "" : ", ");
                }
        } else {
                if (!(cfi->ctc_flags & CTF_FUNC_VARARG))
                        printf("void");
        }

        if (cfi->ctc_flags & CTF_FUNC_VARARG)
                printf("%s...", cfi->ctc_argc == 0 ? "" : ", ");

        printf(");\n");
}

static int
idname_compare(const void *lhs, const void *rhs)
{
        int ret;
        char lname[MAX_NAMELEN] = {0};
        char rname[MAX_NAMELEN] = {0};
        const ctf_idname_t *l = lhs;
        const ctf_idname_t *r = rhs;
        uint_t arity = 0;

        if ((ret = strcmp(l->ci_name, r->ci_name)) != 0)
                return (ret);

        /* If the names match, try arity */
        if (l->ci_funcinfo.ctc_argc < r->ci_funcinfo.ctc_argc)
                return (-1);
        else if (l->ci_funcinfo.ctc_argc > r->ci_funcinfo.ctc_argc)
                return (1);
        else
                arity = l->ci_funcinfo.ctc_argc;

        /* If arity doesn't help, try return type */
        (void) strlcpy(lname, "unknown_t", sizeof (lname));
        (void) strlcpy(rname, "unknown_t", sizeof (rname));
        (void) ctf_type_name(g_fp, l->ci_funcinfo.ctc_return, lname,
            sizeof (lname));
        (void) ctf_type_name(g_fp, r->ci_funcinfo.ctc_return, rname,
            sizeof (rname));

        if ((ret = strcmp(lname, rname)) != 0)
                return (ret);

        /* if return type doesn't help, try parameter types */
        if (arity == 0)
                return (0);

        ctf_id_t *largs = calloc(arity, sizeof (ctf_id_t));
        ctf_id_t *rargs = calloc(arity, sizeof (ctf_id_t));

        if ((largs == NULL) || (rargs == NULL)) {
                free(rargs);
                free(largs);
                ctfdump_fatal("failed to alloc argument ids for sorting: "
                    " %s\n", strerror(errno));
        }

        if (ctf_func_args(g_fp, l->ci_symidx, arity, largs) == CTF_ERR) {
                free(rargs);
                free(largs);
                ctfdump_fatal("failed to get arguments for function "
                    "%s: %s\n", l->ci_name,
                    ctf_errmsg(ctf_errno(g_fp)));
        }

        if (ctf_func_args(g_fp, r->ci_symidx, arity, rargs) == CTF_ERR) {
                free(rargs);
                free(largs);
                ctfdump_fatal("failed to get arguments for function "
                    "%s: %s\n", r->ci_name,
                    ctf_errmsg(ctf_errno(g_fp)));
        }

        for (uint_t i = 0; i < arity; i++) {
                (void) strlcpy(lname, "unknown_t", sizeof (lname));
                (void) ctf_type_name(g_fp, largs[i], lname, sizeof (lname));

                (void) strlcpy(rname, "unknown_t", sizeof (rname));
                (void) ctf_type_name(g_fp, rargs[i], rname, sizeof (rname));

                if ((ret = strcmp(lname, rname)) != 0) {
                        free(rargs);
                        free(largs);
                        return (ret);
                }
        }

        free(rargs);
        free(largs);
        return (0);
}

static void
ctfdump_source(void)
{
        ulong_t nr_syms = ctf_nr_syms(g_fp);
        ctf_id_t max_id = ctf_max_id(g_fp);
        size_t count = 0;

        printf("/* Types */\n\n");

        if ((idnames = calloc(max_id + 1, sizeof (idnames[0]))) == NULL) {
                ctfdump_fatal("failed to alloc idnames: %s\n",
                    strerror(errno));
        }

        /*
         * Prep for any unknown types (most likely, they exist in the parent,
         * but we weren't given the -p option).
         */
        for (size_t i = 0; i <= max_id; i++) {
                (void) strlcpy(idnames[i].ci_name, "unknown_t",
                    sizeof (idnames[i].ci_name));
        }

        if (ctf_type_iter(g_fp, B_TRUE, ctfsrc_collect_types_cb,
            idnames) == CTF_ERR) {
                warnx("failed to collect types: %s",
                    ctf_errmsg(ctf_errno(g_fp)));
                g_exit = 1;
        }

        qsort(idnames, max_id, sizeof (ctf_idname_t), idname_compare);

        for (size_t i = 0; i <= max_id; i++) {
                if (idnames[i].ci_id != 0)
                        ctfsrc_type(idnames[i].ci_id, idnames[i].ci_name);
        }

        free(idnames);

        printf("\n\n/* Data Objects */\n\n");

        if ((idnames = calloc(nr_syms, sizeof (idnames[0]))) == NULL) {
                ctfdump_fatal("failed to alloc idnames: %s\n",
                    strerror(errno));
        }

        if (ctf_object_iter(g_fp, ctfsrc_collect_objects_cb,
            &count) == CTF_ERR) {
                warnx("failed to collect objects: %s",
                    ctf_errmsg(ctf_errno(g_fp)));
                g_exit = 1;
        }

        qsort(idnames, count, sizeof (ctf_idname_t), idname_compare);

        for (size_t i = 0; i < count; i++)
                ctfsrc_object(idnames[i].ci_id, idnames[i].ci_name);

        free(idnames);

        printf("\n\n/* Functions */\n\n");

        if ((idnames = calloc(nr_syms, sizeof (idnames[0]))) == NULL) {
                ctfdump_fatal("failed to alloc idnames: %s\n",
                    strerror(errno));
        }

        count = 0;

        if (ctf_function_iter(g_fp, ctfsrc_collect_functions_cb,
            &count) == CTF_ERR) {
                warnx("failed to collect functions: %s",
                    ctf_errmsg(ctf_errno(g_fp)));
                g_exit = 1;
        }

        qsort(idnames, count, sizeof (ctf_idname_t), idname_compare);

        for (size_t i = 0; i < count; i++)
                ctfsrc_function(&idnames[i]);

        free(idnames);
}

static void
ctfdump_output(const char *out)
{
        int fd, ret;
        const void *data;
        size_t len;

        ctf_dataptr(g_fp, &data, &len);
        if ((fd = open(out, O_WRONLY | O_CREAT | O_TRUNC, 0666)) < 0)
                ctfdump_fatal("failed to open output file %s: %s\n", out,
                    strerror(errno));

        while (len > 0) {
                ret = write(fd, data, len);
                if (ret == -1 && errno == EINTR)
                        continue;
                else if (ret == -1 && (errno == EFAULT || errno == EBADF))
                        abort();
                else if (ret == -1)
                        ctfdump_fatal("failed to write to %s: %s\n", out,
                            strerror(errno));
                data = ((char *)data) + ret;
                len -= ret;
        }

        do {
                ret = close(fd);
        } while (ret == -1 && errno == EINTR);
        if (ret != 0 && errno == EBADF)
                abort();
        if (ret != 0)
                ctfdump_fatal("failed to close %s: %s\n", out, strerror(errno));
}

int
main(int argc, char *argv[])
{
        int c, fd, err;
        const char *ufile = NULL, *parent = NULL;

        g_progname = basename(argv[0]);
        while ((c = getopt(argc, argv, ":cdfhlp:sStu:")) != -1) {
                switch (c) {
                case 'c':
                        g_dump |= CTFDUMP_SOURCE;
                        break;
                case 'd':
                        g_dump |= CTFDUMP_OBJECTS;
                        break;
                case 'f':
                        g_dump |= CTFDUMP_FUNCTIONS;
                        break;
                case 'h':
                        g_dump |= CTFDUMP_HEADER;
                        break;
                case 'l':
                        g_dump |= CTFDUMP_LABELS;
                        break;
                case 'p':
                        parent = optarg;
                        break;
                case 's':
                        g_dump |= CTFDUMP_STRINGS;
                        break;
                case 'S':
                        g_dump |= CTFDUMP_STATS;
                        break;
                case 't':
                        g_dump |= CTFDUMP_TYPES;
                        break;
                case 'u':
                        g_dump |= CTFDUMP_OUTPUT;
                        ufile = optarg;
                        break;
                case '?':
                        ctfdump_usage("Unknown option: -%c\n", optopt);
                        return (2);
                case ':':
                        ctfdump_usage("Option -%c requires an operand\n",
                            optopt);
                        return (2);
                }
        }

        argc -= optind;
        argv += optind;

        if ((g_dump & CTFDUMP_SOURCE) && !!(g_dump & ~CTFDUMP_SOURCE)) {
                ctfdump_usage("-c must be specified on its own\n");
                return (2);
        }

        /*
         * Dump all information except C source by default.
         */
        if (g_dump == 0)
                g_dump = CTFDUMP_DEFAULT;

        if (argc != 1) {
                ctfdump_usage("no file to dump\n");
                return (2);
        }

        if ((fd = open(argv[0], O_RDONLY)) < 0)
                ctfdump_fatal("failed to open file %s: %s\n", argv[0],
                    strerror(errno));

        g_fp = ctf_fdopen(fd, &err);
        if (g_fp == NULL)
                ctfdump_fatal("failed to open file %s: %s\n", argv[0],
                    ctf_errmsg(err));

        /*
         * Check to see if this file needs a parent. If it does not and we were
         * given one, that should be an error. If it does need one and the
         * parent is not specified, that is fine, we just won't know how to
         * find child types. If we are given a parent, check at least that the
         * labels match.
         */
        if (ctf_parent_name(g_fp) == NULL) {
                if (parent != NULL)
                        ctfdump_fatal("cannot use %s as a parent file, %s is "
                            "not a child\n", parent, argv[0]);
        } else if (parent != NULL) {
                const char *explabel, *label;
                ctf_file_t *pfp = ctf_open(parent, &err);

                if (pfp == NULL)
                        ctfdump_fatal("failed to open parent file %s: %s\n",
                            parent, ctf_errmsg(err));

                /*
                 * Before we import the parent into the child, check that the
                 * labels match. While there is also the notion of the parent
                 * name, it's less straightforward to match that. Require that
                 * labels match.
                 */
                explabel = ctf_parent_label(g_fp);
                label = ctf_label_topmost(pfp);
                if (explabel == NULL || label == NULL ||
                    strcmp(explabel, label) != 0) {
                        if (label == NULL)
                                label = "<missing>";
                        if (explabel == NULL)
                                explabel = "<missing>";
                        ctfdump_fatal("label mismatch between parent %s and "
                            "child %s, parent has %s, child expects %s\n",
                            parent, argv[0], label, explabel);
                }

                if (ctf_import(g_fp, pfp) != 0)
                        ctfdump_fatal("failed to import parent %s: %s\n",
                            parent, ctf_errmsg(ctf_errno(g_fp)));
        } else {
                if (g_dump & CTFDUMP_SOURCE) {
                        printf("/* Warning: parent \"%s\" not supplied: many "
                            "types will be unknown. */\n\n",
                            ctf_parent_name(g_fp));
                } else {
                        fprintf(stderr, "warning: parent \"%s\" not supplied: "
                            "many types will be unknown\n\n",
                            ctf_parent_name(g_fp));
                }
        }

        if (g_dump & CTFDUMP_SOURCE) {
                ctfdump_source();
                return (0);
        }

        /*
         * If stats is set, we must run through everything exect CTFDUMP_OUTPUT.
         * We also do CTFDUMP_STATS last as a result.
         */
        if (g_dump & CTFDUMP_HEADER)
                ctfdump_header();

        if (g_dump & (CTFDUMP_LABELS | CTFDUMP_STATS))
                ctfdump_labels();

        if (g_dump & (CTFDUMP_OBJECTS | CTFDUMP_STATS))
                ctfdump_objects();

        if (g_dump & (CTFDUMP_FUNCTIONS | CTFDUMP_STATS))
                ctfdump_functions();

        if (g_dump & (CTFDUMP_TYPES | CTFDUMP_STATS))
                ctfdump_types();

        if (g_dump & (CTFDUMP_STRINGS | CTFDUMP_STATS))
                ctfdump_strings();

        if (g_dump & CTFDUMP_STATS)
                ctfdump_stats();

        if (g_dump & CTFDUMP_OUTPUT)
                ctfdump_output(ufile);

        return (g_exit);
}