root/usr/src/cmd/mdb/common/mdb/mdb_rawfile.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (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 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 *
 * Copyright 2018 Joyent, Inc.
 * Copyright 2024 Oxide Computer Company
 */

/*
 * Raw File Target
 *
 * The raw file target is invoked whenever a file of unrecognizable type is
 * specified on the command line, or when raw file examination is forced using
 * the -f option.  If one file is specified, that file will be opened as the
 * "object" file.  If two files are specified, the second one will be opened
 * as the "core" file.  Each file is opened using the fdio backend, which
 * internally supports both byte-oriented i/o and block-oriented i/o as needed.
 */

#include <mdb/mdb_modapi.h>
#include <mdb/mdb_target_impl.h>
#include <mdb/mdb_io_impl.h>
#include <mdb/mdb_conf.h>
#include <mdb/mdb_err.h>
#include <mdb/mdb.h>

#include <sys/dtrace.h>
#include <fcntl.h>

typedef struct rf_data {
        mdb_io_t *r_object_fio;
        mdb_io_t *r_core_fio;
} rf_data_t;

#define RF_OBJECT(p)    (((rf_data_t *)(p))->r_object_fio)
#define RF_CORE(p)      (((rf_data_t *)(p))->r_core_fio)

static void
rf_data_destroy(rf_data_t *rf)
{
        if (rf->r_object_fio != NULL)
                mdb_io_destroy(rf->r_object_fio);

        if (rf->r_core_fio != NULL)
                mdb_io_destroy(rf->r_core_fio);

        mdb_free(rf, sizeof (rf_data_t));
}

static int
rf_setflags(mdb_tgt_t *t, int flags)
{
        if ((flags ^ t->t_flags) & MDB_TGT_F_RDWR) {
                uint_t otflags = t->t_flags;
                rf_data_t *orf = t->t_data;
                const char *argv[2];
                int argc = 0;

                if (orf->r_object_fio != NULL)
                        argv[argc++] = IOP_NAME(orf->r_object_fio);
                if (orf->r_core_fio != NULL)
                        argv[argc++] = IOP_NAME(orf->r_core_fio);

                t->t_flags = (t->t_flags & ~MDB_TGT_F_RDWR) |
                    (flags & MDB_TGT_F_RDWR);

                if (mdb_rawfile_tgt_create(t, argc, argv) == -1) {
                        t->t_flags = otflags;
                        t->t_data = orf;
                        return (-1);
                }

                rf_data_destroy(orf);
        }

        return (0);
}

static void
rf_destroy(mdb_tgt_t *t)
{
        rf_data_destroy(t->t_data);
}

/*ARGSUSED*/
static const char *
rf_name(mdb_tgt_t *t)
{
        return ("raw");
}

static ssize_t
rf_read(mdb_io_t *io, void *buf, size_t nbytes, uint64_t addr)
{
        ssize_t rbytes;

        if (io == NULL)
                return (set_errno(EMDB_NOMAP));

        if (IOP_SEEK(io, addr, SEEK_SET) == -1)
                return (-1); /* errno is set for us */

        if ((rbytes = IOP_READ(io, buf, nbytes)) == 0)
                (void) set_errno(EMDB_EOF);

        return (rbytes);
}

static ssize_t
rf_write(mdb_io_t *io, const void *buf, size_t nbytes, uint64_t addr)
{
        if (io == NULL)
                return (set_errno(EMDB_NOMAP));

        if (IOP_SEEK(io, addr, SEEK_SET) == -1)
                return (-1); /* errno is set for us */

        return (IOP_WRITE(io, buf, nbytes));
}

static ssize_t
rf_aread(mdb_tgt_t *t, mdb_tgt_as_t as, void *buf,
    size_t len, mdb_tgt_addr_t addr)
{
        switch ((uintptr_t)as) {
        case (uintptr_t)MDB_TGT_AS_VIRT:
        case (uintptr_t)MDB_TGT_AS_VIRT_I:
        case (uintptr_t)MDB_TGT_AS_VIRT_S:
        case (uintptr_t)MDB_TGT_AS_PHYS:
                if (RF_CORE(t->t_data) != NULL)
                        return (rf_read(RF_CORE(t->t_data), buf, len, addr));
                /*FALLTHRU*/
        case (uintptr_t)MDB_TGT_AS_FILE:
                return (rf_read(RF_OBJECT(t->t_data), buf, len, addr));
        default:
                return (set_errno(EMDB_NOMAP));
        }
}

static ssize_t
rf_awrite(mdb_tgt_t *t, mdb_tgt_as_t as, const void *buf,
    size_t len, mdb_tgt_addr_t addr)
{
        switch ((uintptr_t)as) {
        case (uintptr_t)MDB_TGT_AS_VIRT:
        case (uintptr_t)MDB_TGT_AS_VIRT_I:
        case (uintptr_t)MDB_TGT_AS_VIRT_S:
        case (uintptr_t)MDB_TGT_AS_PHYS:
                if (RF_CORE(t->t_data) != NULL)
                        return (rf_write(RF_CORE(t->t_data), buf, len, addr));
                /*FALLTHRU*/
        case (uintptr_t)MDB_TGT_AS_FILE:
                return (rf_write(RF_OBJECT(t->t_data), buf, len, addr));
        default:
                return (set_errno(EMDB_NOMAP));
        }
}

static ssize_t
rf_vread(mdb_tgt_t *t, void *buf, size_t nbytes, uintptr_t addr)
{
        if (RF_CORE(t->t_data) != NULL)
                return (rf_read(RF_CORE(t->t_data), buf, nbytes, addr));

        return (rf_read(RF_OBJECT(t->t_data), buf, nbytes, addr));
}

static ssize_t
rf_vwrite(mdb_tgt_t *t, const void *buf, size_t nbytes, uintptr_t addr)
{
        if (RF_CORE(t->t_data) != NULL)
                return (rf_write(RF_CORE(t->t_data), buf, nbytes, addr));

        return (rf_write(RF_OBJECT(t->t_data), buf, nbytes, addr));
}

static ssize_t
rf_pread(mdb_tgt_t *t, void *buf, size_t nbytes, physaddr_t addr)
{
        if (RF_CORE(t->t_data) != NULL)
                return (rf_read(RF_CORE(t->t_data), buf, nbytes, addr));

        return (rf_read(RF_OBJECT(t->t_data), buf, nbytes, addr));
}

static ssize_t
rf_pwrite(mdb_tgt_t *t, const void *buf, size_t nbytes, physaddr_t addr)
{
        if (RF_CORE(t->t_data) != NULL)
                return (rf_write(RF_CORE(t->t_data), buf, nbytes, addr));

        return (rf_write(RF_OBJECT(t->t_data), buf, nbytes, addr));
}

static ssize_t
rf_fread(mdb_tgt_t *t, void *buf, size_t nbytes, uintptr_t addr)
{
        return (rf_read(RF_OBJECT(t->t_data), buf, nbytes, addr));
}

static ssize_t
rf_fwrite(mdb_tgt_t *t, const void *buf, size_t nbytes, uintptr_t addr)
{
        return (rf_write(RF_OBJECT(t->t_data), buf, nbytes, addr));
}


static int
rf_print_map(mdb_io_t *io, const char *type, int tflags,
    mdb_tgt_map_f *func, void *private)
{
        mdb_map_t map;

        (void) mdb_iob_snprintf(map.map_name, MDB_TGT_MAPSZ,
            "%s (%s)", IOP_NAME(io), type);

        map.map_base = 0;
        map.map_size = IOP_SEEK(io, 0, SEEK_END);
        map.map_flags = MDB_TGT_MAP_R;

        if (tflags & MDB_TGT_F_RDWR)
                map.map_flags |= MDB_TGT_MAP_W;

        return (func(private, &map, map.map_name));
}

static int
rf_mapping_iter(mdb_tgt_t *t, mdb_tgt_map_f *func, void *private)
{
        rf_data_t *rf = t->t_data;

        if (rf->r_object_fio != NULL && rf_print_map(rf->r_object_fio,
            "object file", t->t_flags, func, private) != 0)
                return (0);

        if (rf->r_core_fio != NULL && rf_print_map(rf->r_core_fio,
            "core file", t->t_flags, func, private) != 0)
                return (0);

        return (0);
}

/*ARGSUSED*/
static int
rf_status(mdb_tgt_t *t, mdb_tgt_status_t *tsp)
{
        bzero(tsp, sizeof (mdb_tgt_status_t));

        if (RF_CORE(t->t_data) != NULL)
                tsp->st_state = MDB_TGT_DEAD;
        else
                tsp->st_state = MDB_TGT_IDLE;

        return (0);
}

/*ARGSUSED*/
static int
rf_status_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
{
        rf_data_t *rf = mdb.m_target->t_data;

        if (rf->r_object_fio != NULL) {
                mdb_printf("debugging file '%s' (object file)",
                    IOP_NAME(rf->r_object_fio));

                if (rf->r_core_fio != NULL) {
                        mdb_printf(" and file '%s' (core file)",
                            IOP_NAME(rf->r_core_fio));
                }

                mdb_printf("\n");
        } else {
                mdb_printf("debugging empty target\n");
        }

        return (DCMD_OK);
}

static const mdb_dcmd_t rf_dcmds[] = {
        { "status", NULL, "print summary of current target", rf_status_dcmd },
        { NULL }
};

static const struct rf_magic {
        const char *rfm_str;
        size_t rfm_len;
        const char *rfm_mod;
} rf_magic[] = {
        { DOF_MAG_STRING, DOF_MAG_STRLEN, "dof" },
        { NULL, 0, NULL }
};

static void
rf_activate(mdb_tgt_t *t)
{
        rf_data_t *rf = t->t_data;
        const struct rf_magic *m;
        mdb_var_t *v;
        off64_t size;

        (void) mdb_tgt_register_dcmds(t, &rf_dcmds[0], MDB_MOD_FORCE);

        /*
         * We set the legacy adb variable 'd' to be the size of the file (data
         * segment).  To get this value, we call seek() on the underlying fdio.
         */
        if (rf->r_object_fio != NULL) {
                size = IOP_SEEK(rf->r_object_fio, 0, SEEK_END);
                if ((v = mdb_nv_lookup(&mdb.m_nv, "d")) != NULL)
                        mdb_nv_set_value(v, size);
        }

        /*
         * Load any debugging support modules that match the file type, as
         * determined by our poor man's /etc/magic.  If many clients need
         * to use this feature, rf_magic[] should be computed dynamically.
         */
        for (m = rf_magic; m->rfm_str != NULL; m++) {
                char *buf = mdb_alloc(m->rfm_len, UM_SLEEP);

                if (mdb_tgt_vread(t, buf, m->rfm_len, 0) == m->rfm_len &&
                    bcmp(buf, m->rfm_str, m->rfm_len) == 0) {
                        (void) mdb_module_load(m->rfm_mod,
                            MDB_MOD_LOCAL | MDB_MOD_SILENT);
                }

                mdb_free(buf, m->rfm_len);
        }
}

static void
rf_deactivate(mdb_tgt_t *t)
{
        const mdb_dcmd_t *dcp;

        for (dcp = &rf_dcmds[0]; dcp->dc_name != NULL; dcp++) {
                if (mdb_module_remove_dcmd(t->t_module, dcp->dc_name) == -1)
                        warn("failed to remove dcmd %s", dcp->dc_name);
        }
}

static const mdb_tgt_ops_t rawfile_ops = {
        .t_setflags = rf_setflags,
        .t_setcontext = (int (*)())(uintptr_t)mdb_tgt_notsup,
        .t_activate = rf_activate,
        .t_deactivate = rf_deactivate,
        .t_periodic = (void (*)())(uintptr_t)mdb_tgt_nop,
        .t_destroy = rf_destroy,
        .t_name = rf_name,
        .t_isa = (const char *(*)())mdb_conf_isa,
        .t_platform = (const char *(*)())mdb_conf_platform,
        .t_uname = (int (*)())(uintptr_t)mdb_tgt_notsup,
        .t_dmodel = (int (*)())(uintptr_t)mdb_tgt_notsup,
        .t_aread = rf_aread,
        .t_awrite = rf_awrite,
        .t_vread = rf_vread,
        .t_vwrite = rf_vwrite,
        .t_pread = rf_pread,
        .t_pwrite = rf_pwrite,
        .t_fread = rf_fread,
        .t_fwrite = rf_fwrite,
        .t_ioread = (ssize_t (*)())mdb_tgt_notsup,
        .t_iowrite = (ssize_t (*)())mdb_tgt_notsup,
        .t_vtop = (int (*)())(uintptr_t)mdb_tgt_notsup,
        .t_lookup_by_name = (int (*)())(uintptr_t)mdb_tgt_notsup,
        .t_lookup_by_addr = (int (*)())(uintptr_t)mdb_tgt_notsup,
        .t_symbol_iter = (int (*)())(uintptr_t)mdb_tgt_notsup,
        .t_mapping_iter = rf_mapping_iter,
        .t_object_iter = rf_mapping_iter,
        .t_addr_to_map = (const mdb_map_t *(*)())mdb_tgt_null,
        .t_name_to_map = (const mdb_map_t *(*)())mdb_tgt_null,
        .t_addr_to_ctf = (struct ctf_file *(*)())mdb_tgt_null,
        .t_name_to_ctf = (struct ctf_file *(*)())mdb_tgt_null,
        .t_status = rf_status,
        .t_run = (int (*)())(uintptr_t)mdb_tgt_notsup,
        .t_step = (int (*)())(uintptr_t)mdb_tgt_notsup,
        .t_step_out = (int (*)())(uintptr_t)mdb_tgt_notsup,
        .t_next = (int (*)())(uintptr_t)mdb_tgt_notsup,
        .t_cont = (int (*)())(uintptr_t)mdb_tgt_notsup,
        .t_signal = (int (*)())(uintptr_t)mdb_tgt_notsup,
        .t_add_vbrkpt = (int (*)())(uintptr_t)mdb_tgt_null,
        .t_add_sbrkpt = (int (*)())(uintptr_t)mdb_tgt_null,
        .t_add_pwapt = (int (*)())(uintptr_t)mdb_tgt_null,
        .t_add_vwapt = (int (*)())(uintptr_t)mdb_tgt_null,
        .t_add_iowapt = (int (*)())(uintptr_t)mdb_tgt_null,
        .t_add_sysenter = (int (*)())(uintptr_t)mdb_tgt_null,
        .t_add_sysexit = (int (*)())(uintptr_t)mdb_tgt_null,
        .t_add_signal = (int (*)())(uintptr_t)mdb_tgt_null,
        .t_add_fault = (int (*)())(uintptr_t)mdb_tgt_null,
        .t_getareg = (int (*)())(uintptr_t)mdb_tgt_notsup,
        .t_putareg = (int (*)())(uintptr_t)mdb_tgt_notsup,
        .t_stack_iter = (int (*)())(uintptr_t)mdb_tgt_notsup,
        .t_auxv = (int (*)())(uintptr_t)mdb_tgt_notsup,
        .t_thread_name = (int (*)())(uintptr_t)mdb_tgt_notsup,
};

int
mdb_rawfile_tgt_create(mdb_tgt_t *t, int argc, const char *argv[])
{
        mdb_io_t *io[2] = { NULL, NULL };
        rf_data_t *rf;
        int oflags, i;

        if (argc > 2)
                return (set_errno(EINVAL));

        rf = mdb_zalloc(sizeof (rf_data_t), UM_SLEEP);
        t->t_ops = &rawfile_ops;
        t->t_data = rf;

        if (t->t_flags & MDB_TGT_F_RDWR)
                oflags = O_RDWR;
        else
                oflags = O_RDONLY;

        for (i = 0; i < argc; i++) {
                io[i] = mdb_fdio_create_path(NULL, argv[i], oflags, 0);
                if (io[i] == NULL) {
                        warn("failed to open %s", argv[i]);
                        goto err;
                }
        }

        rf->r_object_fio = io[0];       /* first file is the "object" */
        rf->r_core_fio = io[1];         /* second file is the "core" */
        t->t_flags |= MDB_TGT_F_ASIO;   /* do i/o using aread and awrite */

        return (0);

err:
        for (i = 0; i < argc; i++) {
                if (io[i] != NULL)
                        mdb_io_destroy(io[i]);
        }


        mdb_free(rf, sizeof (rf_data_t));
        return (set_errno(EMDB_TGT));
}