root/usr/src/tools/ctf/stabs/common/fth_struct.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2003 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * Copyright 2020 OmniOS Community Edition (OmniOSce) Association.
 */

/*
 * Used to dump structures and unions in forth mode.
 *
 * structures and unions are a bit more complicated than enums.  To make things
 * just that much more interesting, we have to dump the members in reverse
 * order, which is nice.  But wait!  It gets better!  For compatibility reasons,
 * we need to dump the members in reverse-offset order, even if member-specific
 * mode was used to request the members in something other than that order.
 *
 * The header op prints the macro header and saves the type being printed.
 *
 * In member-specific mode, the member op will be invoked for each structure
 * or union member.  The member op adds the member name, format, type ID,
 * and offset to a list, sorted in reverse order by offset.
 *
 * The trailer op is called when the structure or enum is complete.  If no
 * members were specifically requested, then the trailer iterates through all
 * of the members of the structure, pretending they were.  Each member is thus
 * added, in reverse-offset order, to the list used in specific-member mode.
 * Either way, we then proceed through the list, dumping each member out with
 * fth_print_member.  Structure and union members are printed out differently,
 * depending on member type, as follows:
 *
 *  Integer:
 *      Normal integers: ' <format> <offset> <type>-field <name>
 *        <format> defaults to ".d" for enums, ".x" for others
 *        <offset> is the member offset, in bytes.
 *        <type> is "byte", "short", "long", or "ext" for 8-, 16-, 32-, and
 *          64-bit integers, respectively.
 *        <name> is the name of the member being printed
 *
 *      Bitfields:       ' <format> <shift> <mask> <offset> bits-field <name>
 *        <format> defaults to ".x"
 *        <shift> is the number of times to right-shift the masked value
 *        <mask> use to extract the bit-field value from the read value
 *        <offset> is the member offset, in bytes
 *        <name> is the name of the member being printed
 *
 *  Float:              Ignored
 *
 *  Pointer:             ' <format> <offset> ptr-field <name>
 *        <format> defaults to .x
 *        <offset> is in bytes
 *        <name> is the name of the member being printed
 *
 *  Array:
 *      Arrays have a content-type-specific prefix, followed by an array
 *      suffix.  The resulting line looks like this if the array contents
 *      type is an integer, a pointer, or an enum:
 *
 *                       ' <fldc> ' <fmt> <sz> <elsz> <off> array-field <name>
 *
 *      The following is printed for array contents that are arrays:
 *
 *                       ' noop ' .x <sz> <elsz> <off> array-field <name>
 *
 *      The following is printed for array contents that are structs:
 *
 *                       ' noop ' <fmt> <sz> <elsz> <off> array-field <name>
 *
 *        <fldc> is "c@", "w@", "l@", or "x@", depending on whether array
 *          elements are 8, 16, 32 or 64 bits wide.
 *        <fmt> defaults to ".x"
 *        <sz> is the size of the array, in bytes
 *        <elsz> is the size of the array elements
 *        <off> is the member offset, in bytes
 *        <name> is the nam eof the member being printed
 *
 *  Struct/Union:        ' <format> <offset> struct-field <name>
 *        <format> defaults to ".x"
 *        <offset> is the member offset, in bytes
 *        <name> is the name of the member being printed
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/list.h>

#include "ctf_headers.h"
#include "forth.h"
#include "memory.h"

static ctf_id_t fth_str_curtid;
static list_t   fth_str_curmems;

/*
 * Node type for the member-storage list (fth_str_curmems) built by
 * fth_struct_members()
 */
typedef struct fth_str_mem {
        list_node_t     fsm_node;
        char            *fsm_memname;
        char            *fsm_format;
        ctf_id_t        fsm_tid;
        ulong_t         fsm_off;
} fth_str_mem_t;

typedef struct fth_struct_members_data {
        char            *fsmd_strname;
        char            *fsmd_memfilter;
        char            *fsmd_format;
        int             fsmd_matched;
} fth_struct_members_data_t;

static int fth_print_member(fth_str_mem_t *, int);

/* Comparison routined used to insert members into the fth_str_curmems list */
static int
fth_struct_memcmp(void *m1, void *m2)
{
        fth_str_mem_t *mem1 = m1, *mem2 = m2;

        if (mem1->fsm_off < mem2->fsm_off)
                return (1);
        else if (mem1->fsm_off > mem2->fsm_off)
                return (-1);
        else
                return (0);
}

static void
fth_slist_add(fth_str_mem_t *mem)
{
        fth_str_mem_t *l;

        for (l = list_head(&fth_str_curmems); l != NULL;
            l = list_next(&fth_str_curmems, l)) {
                if (fth_struct_memcmp(l, mem) > 0) {
                        list_insert_before(&fth_str_curmems, l, mem);
                        return;
                }
        }
        list_insert_tail(&fth_str_curmems, mem);
}

static void
fth_free_str_mem(fth_str_mem_t *mem)
{
        free(mem->fsm_memname);
        if (mem->fsm_format)
                free(mem->fsm_format);
        free(mem);
}

static int
fth_struct_header(ctf_id_t tid)
{
        ssize_t sz;

        fth_str_curtid = tid;
        list_create(&fth_str_curmems, sizeof (fth_str_mem_t),
            offsetof(fth_str_mem_t, fsm_node));

        if ((sz = ctf_type_size(ctf, fth_str_curtid)) == CTF_ERR)
                return (parse_warn("Can't get size for %s", fth_curtype));

        (void) fprintf(out, "\n");
        (void) fprintf(out, "vocabulary %s-words\n", fth_curtype);
        (void) fprintf(out, "h# %x constant %s-sz\n", sz, fth_curtype);
        (void) fprintf(out, "%x ' %s-words c-struct .%s\n", sz, fth_curtype,
            fth_curtype);
        (void) fprintf(out, "also %s-words definitions\n\n", fth_curtype);

        return (0);
}

/* Print the array prefix for integer and pointer members */
static int
fth_print_level(uint_t bits, char *format)
{
        if ((bits & (bits - 1)) != 0 ||(bits % 8) != 0 || bits > 64) {
                return (parse_warn("Unexpected bit size %d in %s",
                    bits, fth_curtype));
        }

        (void) fprintf(out, "' %c@ ' %s", " cw l   x"[bits / 8], format);

        return (0);
}

/*
 * Return the format to be used to print the member.  If one of the builtin
 * formats "d" or "x" were specified, return ".d" or ".x", respectively.
 * Otherwise, use the user-provided format as is, or use the default if none
 * was provided.
 */
static char *
fth_convert_format(char *format, char *def)
{
        static char dot[3] = ".";

        if (format == NULL)
                return (def);
        else if (strlen(format) == 1) {
                dot[1] = *format;
                return (dot);
        } else
                return (format);
}

static int
fth_print_integer(const char *memname, ulong_t off, uint_t bits, char *format,
    int level)
{
        format = fth_convert_format(format, ".x");

        if (bits > 64) {
                return (parse_warn("%s.%s is too large (>8 bytes)",
                    fth_curtype, memname));
        }

        if (level != 0)
                return (fth_print_level(bits, format));

        if ((bits % NBBY) != 0 || (bits & (bits - 1)) != 0) {
                /* bit field */
                uint_t offset, shift, mask;

                offset = (off / 32) * 4;
                shift = 32 - ((off % 32) + bits);
                mask = ((1 << bits) - 1) << shift;

                (void) fprintf(out, "' %s %x %x %x bits-field %s\n",
                    format, shift, mask, offset, memname);

        } else {
                char *type[] = {
                        NULL, "byte", "short", NULL, "long",
                        NULL, NULL, NULL, "ext"
                };

                (void) fprintf(out, "' %s %lx %s-field %s\n", format, off / 8,
                    type[bits / 8], memname);
        }

        return (0);
}

static int
fth_print_pointer(const char *memname, ulong_t off, uint_t bits, char *format,
    int level)
{
        format = fth_convert_format(format, ".x");

        if (level != 0)
                return (fth_print_level(bits, format));

        (void) fprintf(out, "' %s %lx ptr-field %s\n", format, off / 8,
            memname);

        return (0);
}

static int
fth_print_struct(char *memname, ulong_t off, char *format,
    int level)
{
        format = fth_convert_format(format, ".x");

        if (level != 0)
                (void) fprintf(out, "' noop ' %s", format);
        else {
                (void) fprintf(out, "' %s %lx struct-field %s\n", format,
                    off / 8, memname);
        }

        return (0);
}

static int
fth_print_enum(char *memname, ulong_t off, char *format,
    int level)
{
        format = fth_convert_format(format, ".d");

        if (level != 0)
                (void) fprintf(out, "' l@ ' %s", format);
        else {
                (void) fprintf(out, "' %s %lx long-field %s\n", format, off / 8,
                    memname);
        }

        return (0);
}

static int
fth_print_array(char *memname, ctf_id_t tid, ulong_t off, ssize_t sz,
    char *format, int level)
{
        if (level != 0)
                (void) fprintf(out, "' noop ' .x");
        else {
                fth_str_mem_t mem;
                ctf_arinfo_t ar;

                /*
                 * print the prefix for the array contents type, then print
                 * the array macro
                 */

                if (ctf_array_info(ctf, tid, &ar) == CTF_ERR) {
                        return (parse_warn("Can't read array in %s.%s",
                            fth_curtype, memname));
                }

                mem.fsm_memname = memname;
                mem.fsm_format = format;
                mem.fsm_tid = ar.ctr_contents;
                mem.fsm_off = off;

                if (fth_print_member(&mem, level + 1) < 0)
                        return (-1);

                (void) fprintf(out, " %x %x %lx array-field %s\n", sz,
                    (sz / ar.ctr_nelems), off / 8, memname);
        }

        return (0);
}

/* dump a structure or union member */
static int
fth_print_member(fth_str_mem_t *mem, int level)
{
        ctf_encoding_t e;
        ctf_id_t tid;
        int kind;
        ssize_t sz;

        if ((tid = ctf_type_resolve(ctf, mem->fsm_tid)) == CTF_ERR) {
                return (parse_warn("Can't resolve %s.%s", fth_curtype,
                    mem->fsm_memname));
        }

        if ((kind = ctf_type_kind(ctf, tid)) == CTF_ERR) {
                return (parse_warn("Can't get kind for %s.%s",
                    fth_curtype, mem->fsm_memname));
        }

        if ((sz = ctf_type_size(ctf, tid)) == CTF_ERR) {
                return (parse_warn("Can't get size for %s.%s",
                    fth_curtype, mem->fsm_memname));
        }

        switch (kind) {
        case CTF_K_INTEGER:
                if (ctf_type_encoding(ctf, tid, &e) == CTF_ERR)
                        return (parse_warn("Can't get encoding for %ld", tid));

                return (fth_print_integer(mem->fsm_memname, mem->fsm_off,
                    e.cte_bits, mem->fsm_format, level));

        case CTF_K_FLOAT:
                (void) parse_warn("Ignoring floating point member %s.%s",
                    fth_curtype, mem->fsm_memname);
                return (0);

        case CTF_K_POINTER:
                return (fth_print_pointer(mem->fsm_memname, mem->fsm_off,
                    sz * 8, mem->fsm_format, level));

        case CTF_K_ARRAY:
                return (fth_print_array(mem->fsm_memname, tid, mem->fsm_off, sz,
                    mem->fsm_format, level));

        case CTF_K_STRUCT:
        case CTF_K_UNION:
                return (fth_print_struct(mem->fsm_memname, mem->fsm_off,
                    mem->fsm_format, level));

        case CTF_K_ENUM:
                return (fth_print_enum(mem->fsm_memname, mem->fsm_off,
                    mem->fsm_format, level));

        case CTF_K_FORWARD:
                return (parse_warn("Type %ld in %s.%s is undefined", tid,
                    fth_curtype, mem->fsm_memname));

        default:
                return (parse_warn("Unexpected kind %d for %s.%s", kind,
                    fth_curtype, mem->fsm_memname));
        }
}

/*
 * Add a member to list of members to be printed (fth_str_curmems).  If
 * fsmd_memfilter is non-null, only add this member if its name matches that
 * in the filter.
 */
static int
fth_struct_members_cb(const char *memname, ctf_id_t tid, ulong_t off, void *arg)
{
        fth_struct_members_data_t *fsmd = arg;
        fth_str_mem_t *mem;

        if (fsmd->fsmd_memfilter != NULL && strcmp(fsmd->fsmd_memfilter,
            memname) != 0)
                return (0);

        fsmd->fsmd_matched = 1;

        mem = xcalloc(sizeof (fth_str_mem_t));
        mem->fsm_memname = xstrdup(memname);
        if (fsmd->fsmd_format)
                mem->fsm_format = xstrdup(fsmd->fsmd_format);
        mem->fsm_tid = tid;
        mem->fsm_off = off;

        fth_slist_add(mem);

        return (0);
}

/*
 * If memfilter is non-null, iterate through the members of this type, causing
 * every member to be added to the list.  Otherwise, use the iterator and
 * the callback to add only the specified member.
 */
static int
fth_struct_members(char *memfilter, char *format)
{
        fth_struct_members_data_t fsmd;

        fsmd.fsmd_strname = fth_curtype;
        fsmd.fsmd_memfilter = memfilter;
        fsmd.fsmd_format = format;
        fsmd.fsmd_matched = 0;

        if (ctf_member_iter(ctf, fth_str_curtid, fth_struct_members_cb,
            &fsmd) != 0)
                return (-1);

        if (memfilter != NULL && fsmd.fsmd_matched == 0) {
                return (parse_warn("Invalid member %s.%s", fth_curtype,
                    memfilter));
        }

        return (0);
}

static int
fth_struct_trailer(void)
{
        fth_str_mem_t *mem;

        if (list_is_empty(&fth_str_curmems)) {
                if (fth_struct_members(NULL, NULL) < 0)
                        return (-1);
        }

        while ((mem = list_remove_head(&fth_str_curmems)) != NULL) {
                if (fth_print_member(mem, 0) < 0)
                        return (-1);

                fth_free_str_mem(mem);
        }
        list_destroy(&fth_str_curmems);

        (void) fprintf(out, "\n");
        (void) fprintf(out, "kdbg-words definitions\n");
        (void) fprintf(out, "previous\n");
        (void) fprintf(out, "\n");
        (void) fprintf(out, "\\ end %s section\n", fth_curtype);
        (void) fprintf(out, "\n");

        return (0);
}

fth_type_ops_t fth_struct_ops = {
        fth_struct_header,
        fth_struct_members,
        fth_struct_trailer
};