/*
 * Target specific user-mode handling
 *
 * Copyright (c) 2003-2005 Fabrice Bellard
 * Copyright (c) 2022 Linaro Ltd
 *
 * SPDX-License-Identifier: LGPL-2.0+
 */

#include "qemu/osdep.h"
#include "exec/gdbstub.h"
#include "gdbstub/commands.h"
#include "qemu.h"
#include "internals.h"
#ifdef CONFIG_LINUX
#include "linux-user/loader.h"
#include "linux-user/qemu.h"
#endif

/*
 * Map target signal numbers to GDB protocol signal numbers and vice
 * versa.  For user emulation's currently supported systems, we can
 * assume most signals are defined.
 */

static int gdb_signal_table[] = {
    0,
    TARGET_SIGHUP,
    TARGET_SIGINT,
    TARGET_SIGQUIT,
    TARGET_SIGILL,
    TARGET_SIGTRAP,
    TARGET_SIGABRT,
    -1, /* SIGEMT */
    TARGET_SIGFPE,
    TARGET_SIGKILL,
    TARGET_SIGBUS,
    TARGET_SIGSEGV,
    TARGET_SIGSYS,
    TARGET_SIGPIPE,
    TARGET_SIGALRM,
    TARGET_SIGTERM,
    TARGET_SIGURG,
    TARGET_SIGSTOP,
    TARGET_SIGTSTP,
    TARGET_SIGCONT,
    TARGET_SIGCHLD,
    TARGET_SIGTTIN,
    TARGET_SIGTTOU,
    TARGET_SIGIO,
    TARGET_SIGXCPU,
    TARGET_SIGXFSZ,
    TARGET_SIGVTALRM,
    TARGET_SIGPROF,
    TARGET_SIGWINCH,
    -1, /* SIGLOST */
    TARGET_SIGUSR1,
    TARGET_SIGUSR2,
#ifdef TARGET_SIGPWR
    TARGET_SIGPWR,
#else
    -1,
#endif
    -1, /* SIGPOLL */
    -1,
    -1,
    -1,
    -1,
    -1,
    -1,
    -1,
    -1,
    -1,
    -1,
    -1,
#ifdef __SIGRTMIN
    __SIGRTMIN + 1,
    __SIGRTMIN + 2,
    __SIGRTMIN + 3,
    __SIGRTMIN + 4,
    __SIGRTMIN + 5,
    __SIGRTMIN + 6,
    __SIGRTMIN + 7,
    __SIGRTMIN + 8,
    __SIGRTMIN + 9,
    __SIGRTMIN + 10,
    __SIGRTMIN + 11,
    __SIGRTMIN + 12,
    __SIGRTMIN + 13,
    __SIGRTMIN + 14,
    __SIGRTMIN + 15,
    __SIGRTMIN + 16,
    __SIGRTMIN + 17,
    __SIGRTMIN + 18,
    __SIGRTMIN + 19,
    __SIGRTMIN + 20,
    __SIGRTMIN + 21,
    __SIGRTMIN + 22,
    __SIGRTMIN + 23,
    __SIGRTMIN + 24,
    __SIGRTMIN + 25,
    __SIGRTMIN + 26,
    __SIGRTMIN + 27,
    __SIGRTMIN + 28,
    __SIGRTMIN + 29,
    __SIGRTMIN + 30,
    __SIGRTMIN + 31,
    -1, /* SIGCANCEL */
    __SIGRTMIN,
    __SIGRTMIN + 32,
    __SIGRTMIN + 33,
    __SIGRTMIN + 34,
    __SIGRTMIN + 35,
    __SIGRTMIN + 36,
    __SIGRTMIN + 37,
    __SIGRTMIN + 38,
    __SIGRTMIN + 39,
    __SIGRTMIN + 40,
    __SIGRTMIN + 41,
    __SIGRTMIN + 42,
    __SIGRTMIN + 43,
    __SIGRTMIN + 44,
    __SIGRTMIN + 45,
    __SIGRTMIN + 46,
    __SIGRTMIN + 47,
    __SIGRTMIN + 48,
    __SIGRTMIN + 49,
    __SIGRTMIN + 50,
    __SIGRTMIN + 51,
    __SIGRTMIN + 52,
    __SIGRTMIN + 53,
    __SIGRTMIN + 54,
    __SIGRTMIN + 55,
    __SIGRTMIN + 56,
    __SIGRTMIN + 57,
    __SIGRTMIN + 58,
    __SIGRTMIN + 59,
    __SIGRTMIN + 60,
    __SIGRTMIN + 61,
    __SIGRTMIN + 62,
    __SIGRTMIN + 63,
    __SIGRTMIN + 64,
    __SIGRTMIN + 65,
    __SIGRTMIN + 66,
    __SIGRTMIN + 67,
    __SIGRTMIN + 68,
    __SIGRTMIN + 69,
    __SIGRTMIN + 70,
    __SIGRTMIN + 71,
    __SIGRTMIN + 72,
    __SIGRTMIN + 73,
    __SIGRTMIN + 74,
    __SIGRTMIN + 75,
    __SIGRTMIN + 76,
    __SIGRTMIN + 77,
    __SIGRTMIN + 78,
    __SIGRTMIN + 79,
    __SIGRTMIN + 80,
    __SIGRTMIN + 81,
    __SIGRTMIN + 82,
    __SIGRTMIN + 83,
    __SIGRTMIN + 84,
    __SIGRTMIN + 85,
    __SIGRTMIN + 86,
    __SIGRTMIN + 87,
    __SIGRTMIN + 88,
    __SIGRTMIN + 89,
    __SIGRTMIN + 90,
    __SIGRTMIN + 91,
    __SIGRTMIN + 92,
    __SIGRTMIN + 93,
    __SIGRTMIN + 94,
    __SIGRTMIN + 95,
    -1, /* SIGINFO */
    -1, /* UNKNOWN */
    -1, /* DEFAULT */
    -1,
    -1,
    -1,
    -1,
    -1,
    -1
#endif
};

int gdb_signal_to_target(int sig)
{
    if (sig < ARRAY_SIZE(gdb_signal_table)) {
        return gdb_signal_table[sig];
    } else {
        return -1;
    }
}

int gdb_target_signal_to_gdb(int sig)
{
    int i;
    for (i = 0; i < ARRAY_SIZE(gdb_signal_table); i++) {
        if (gdb_signal_table[i] == sig) {
            return i;
        }
    }
    return GDB_SIGNAL_UNKNOWN;
}

int gdb_get_cpu_index(CPUState *cpu)
{
    TaskState *ts = get_task_state(cpu);
    return ts ? ts->ts_tid : -1;
}

/*
 * User-mode specific command helpers
 */

void gdb_handle_query_offsets(GArray *params, void *user_ctx)
{
    TaskState *ts;

    ts = get_task_state(gdbserver_state.c_cpu);
    g_string_printf(gdbserver_state.str_buf,
                    "Text=" TARGET_ABI_FMT_lx
                    ";Data=" TARGET_ABI_FMT_lx
                    ";Bss=" TARGET_ABI_FMT_lx,
                    ts->info->code_offset,
                    ts->info->data_offset,
                    ts->info->data_offset);
    gdb_put_strbuf();
}

#if defined(CONFIG_LINUX)
/* Partial user only duplicate of helper in gdbstub.c */
static inline int target_memory_rw_debug(CPUState *cpu, target_ulong addr,
                                         uint8_t *buf, int len, bool is_write)
{
    CPUClass *cc;
    cc = CPU_GET_CLASS(cpu);
    if (cc->memory_rw_debug) {
        return cc->memory_rw_debug(cpu, addr, buf, len, is_write);
    }
    return cpu_memory_rw_debug(cpu, addr, buf, len, is_write);
}

void gdb_handle_query_xfer_auxv(GArray *params, void *user_ctx)
{
    TaskState *ts;
    unsigned long offset, len, saved_auxv, auxv_len;

    if (params->len < 2) {
        gdb_put_packet("E22");
        return;
    }

    offset = gdb_get_cmd_param(params, 0)->val_ul;
    len = gdb_get_cmd_param(params, 1)->val_ul;
    ts = get_task_state(gdbserver_state.c_cpu);
    saved_auxv = ts->info->saved_auxv;
    auxv_len = ts->info->auxv_len;

    if (offset >= auxv_len) {
        gdb_put_packet("E00");
        return;
    }

    if (len > (MAX_PACKET_LENGTH - 5) / 2) {
        len = (MAX_PACKET_LENGTH - 5) / 2;
    }

    if (len < auxv_len - offset) {
        g_string_assign(gdbserver_state.str_buf, "m");
    } else {
        g_string_assign(gdbserver_state.str_buf, "l");
        len = auxv_len - offset;
    }

    g_byte_array_set_size(gdbserver_state.mem_buf, len);
    if (target_memory_rw_debug(gdbserver_state.g_cpu, saved_auxv + offset,
                               gdbserver_state.mem_buf->data, len, false)) {
        gdb_put_packet("E14");
        return;
    }

    gdb_memtox(gdbserver_state.str_buf,
           (const char *)gdbserver_state.mem_buf->data, len);
    gdb_put_packet_binary(gdbserver_state.str_buf->str,
                      gdbserver_state.str_buf->len, true);
}
#endif

static const char *get_filename_param(GArray *params, int i)
{
    const char *hex_filename = gdb_get_cmd_param(params, i)->data;
    gdb_hextomem(gdbserver_state.mem_buf, hex_filename,
                 strlen(hex_filename) / 2);
    g_byte_array_append(gdbserver_state.mem_buf, (const guint8 *)"", 1);
    return (const char *)gdbserver_state.mem_buf->data;
}

static void hostio_reply_with_data(const void *buf, size_t n)
{
    g_string_printf(gdbserver_state.str_buf, "F%zx;", n);
    gdb_memtox(gdbserver_state.str_buf, buf, n);
    gdb_put_packet_binary(gdbserver_state.str_buf->str,
                          gdbserver_state.str_buf->len, true);
}

void gdb_handle_v_file_open(GArray *params, void *user_ctx)
{
    const char *filename = get_filename_param(params, 0);
    uint64_t flags = gdb_get_cmd_param(params, 1)->val_ull;
    uint64_t mode = gdb_get_cmd_param(params, 2)->val_ull;

#ifdef CONFIG_LINUX
    int fd = do_guest_openat(cpu_env(gdbserver_state.g_cpu), 0, filename,
                             flags, mode, false);
#else
    int fd = open(filename, flags, mode);
#endif
    if (fd < 0) {
        g_string_printf(gdbserver_state.str_buf, "F-1,%d", errno);
    } else {
        g_string_printf(gdbserver_state.str_buf, "F%d", fd);
    }
    gdb_put_strbuf();
}

void gdb_handle_v_file_close(GArray *params, void *user_ctx)
{
    int fd = gdb_get_cmd_param(params, 0)->val_ul;

    if (close(fd) == -1) {
        g_string_printf(gdbserver_state.str_buf, "F-1,%d", errno);
        gdb_put_strbuf();
        return;
    }

    gdb_put_packet("F00");
}

void gdb_handle_v_file_pread(GArray *params, void *user_ctx)
{
    int fd = gdb_get_cmd_param(params, 0)->val_ul;
    size_t count = gdb_get_cmd_param(params, 1)->val_ull;
    off_t offset = gdb_get_cmd_param(params, 2)->val_ull;

    size_t bufsiz = MIN(count, BUFSIZ);
    g_autofree char *buf = g_try_malloc(bufsiz);
    if (buf == NULL) {
        gdb_put_packet("E12");
        return;
    }

    ssize_t n = pread(fd, buf, bufsiz, offset);
    if (n < 0) {
        g_string_printf(gdbserver_state.str_buf, "F-1,%d", errno);
        gdb_put_strbuf();
        return;
    }
    hostio_reply_with_data(buf, n);
}

void gdb_handle_v_file_readlink(GArray *params, void *user_ctx)
{
    const char *filename = get_filename_param(params, 0);

    g_autofree char *buf = g_try_malloc(BUFSIZ);
    if (buf == NULL) {
        gdb_put_packet("E12");
        return;
    }

#ifdef CONFIG_LINUX
    ssize_t n = do_guest_readlink(filename, buf, BUFSIZ);
#else
    ssize_t n = readlink(filename, buf, BUFSIZ);
#endif
    if (n < 0) {
        g_string_printf(gdbserver_state.str_buf, "F-1,%d", errno);
        gdb_put_strbuf();
        return;
    }
    hostio_reply_with_data(buf, n);
}

void gdb_handle_query_xfer_exec_file(GArray *params, void *user_ctx)
{
    uint32_t pid = gdb_get_cmd_param(params, 0)->val_ul;
    uint32_t offset = gdb_get_cmd_param(params, 1)->val_ul;
    uint32_t length = gdb_get_cmd_param(params, 2)->val_ul;

    GDBProcess *process = gdb_get_process(pid);
    if (!process) {
        gdb_put_packet("E00");
        return;
    }

    CPUState *cpu = gdb_get_first_cpu_in_process(process);
    if (!cpu) {
        gdb_put_packet("E00");
        return;
    }

    TaskState *ts = get_task_state(cpu);
    if (!ts || !ts->bprm || !ts->bprm->filename) {
        gdb_put_packet("E00");
        return;
    }

    size_t total_length = strlen(ts->bprm->filename);
    if (offset > total_length) {
        gdb_put_packet("E00");
        return;
    }
    if (offset + length > total_length) {
        length = total_length - offset;
    }

    g_string_printf(gdbserver_state.str_buf, "l%.*s", length,
                    ts->bprm->filename + offset);
    gdb_put_strbuf();
}

int gdb_target_sigtrap(void)
{
    return TARGET_SIGTRAP;
}