There is no longer anything target specific. Signed-off-by: Richard Henderson <richard.henderson@linaro.org> Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org> Signed-off-by: Alex Bennée <alex.bennee@linaro.org> Message-Id: <20230303025805.625589-29-richard.henderson@linaro.org>
		
			
				
	
	
		
			206 lines
		
	
	
		
			5.0 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			206 lines
		
	
	
		
			5.0 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 * GDB Syscall Handling
 | 
						|
 *
 | 
						|
 * GDB can execute syscalls on the guests behalf, currently used by
 | 
						|
 * the various semihosting extensions.
 | 
						|
 *
 | 
						|
 * Copyright (c) 2003-2005 Fabrice Bellard
 | 
						|
 * Copyright (c) 2023 Linaro Ltd
 | 
						|
 *
 | 
						|
 * SPDX-License-Identifier: LGPL-2.0+
 | 
						|
 */
 | 
						|
 | 
						|
#include "qemu/osdep.h"
 | 
						|
#include "qemu/error-report.h"
 | 
						|
#include "semihosting/semihost.h"
 | 
						|
#include "sysemu/runstate.h"
 | 
						|
#include "gdbstub/user.h"
 | 
						|
#include "gdbstub/syscalls.h"
 | 
						|
#include "trace.h"
 | 
						|
#include "internals.h"
 | 
						|
 | 
						|
/* Syscall specific state */
 | 
						|
typedef struct {
 | 
						|
    char syscall_buf[256];
 | 
						|
    gdb_syscall_complete_cb current_syscall_cb;
 | 
						|
} GDBSyscallState;
 | 
						|
 | 
						|
static GDBSyscallState gdbserver_syscall_state;
 | 
						|
 | 
						|
/*
 | 
						|
 * Return true if there is a GDB currently connected to the stub
 | 
						|
 * and attached to a CPU
 | 
						|
 */
 | 
						|
static bool gdb_attached(void)
 | 
						|
{
 | 
						|
    return gdbserver_state.init && gdbserver_state.c_cpu;
 | 
						|
}
 | 
						|
 | 
						|
static enum {
 | 
						|
    GDB_SYS_UNKNOWN,
 | 
						|
    GDB_SYS_ENABLED,
 | 
						|
    GDB_SYS_DISABLED,
 | 
						|
} gdb_syscall_mode;
 | 
						|
 | 
						|
/* Decide if either remote gdb syscalls or native file IO should be used. */
 | 
						|
int use_gdb_syscalls(void)
 | 
						|
{
 | 
						|
    SemihostingTarget target = semihosting_get_target();
 | 
						|
    if (target == SEMIHOSTING_TARGET_NATIVE) {
 | 
						|
        /* -semihosting-config target=native */
 | 
						|
        return false;
 | 
						|
    } else if (target == SEMIHOSTING_TARGET_GDB) {
 | 
						|
        /* -semihosting-config target=gdb */
 | 
						|
        return true;
 | 
						|
    }
 | 
						|
 | 
						|
    /* -semihosting-config target=auto */
 | 
						|
    /* On the first call check if gdb is connected and remember. */
 | 
						|
    if (gdb_syscall_mode == GDB_SYS_UNKNOWN) {
 | 
						|
        gdb_syscall_mode = gdb_attached() ? GDB_SYS_ENABLED : GDB_SYS_DISABLED;
 | 
						|
    }
 | 
						|
    return gdb_syscall_mode == GDB_SYS_ENABLED;
 | 
						|
}
 | 
						|
 | 
						|
/* called when the stub detaches */
 | 
						|
void gdb_disable_syscalls(void)
 | 
						|
{
 | 
						|
    gdb_syscall_mode = GDB_SYS_DISABLED;
 | 
						|
}
 | 
						|
 | 
						|
void gdb_syscall_reset(void)
 | 
						|
{
 | 
						|
    gdbserver_syscall_state.current_syscall_cb = NULL;
 | 
						|
}
 | 
						|
 | 
						|
bool gdb_handled_syscall(void)
 | 
						|
{
 | 
						|
    if (gdbserver_syscall_state.current_syscall_cb) {
 | 
						|
        gdb_put_packet(gdbserver_syscall_state.syscall_buf);
 | 
						|
        return true;
 | 
						|
    }
 | 
						|
 | 
						|
    return false;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Send a gdb syscall request.
 | 
						|
 *  This accepts limited printf-style format specifiers, specifically:
 | 
						|
 *   %x  - target_ulong argument printed in hex.
 | 
						|
 *   %lx - 64-bit argument printed in hex.
 | 
						|
 *   %s  - string pointer (target_ulong) and length (int) pair.
 | 
						|
 */
 | 
						|
void gdb_do_syscall(gdb_syscall_complete_cb cb, const char *fmt, ...)
 | 
						|
{
 | 
						|
    char *p, *p_end;
 | 
						|
    va_list va;
 | 
						|
 | 
						|
    if (!gdb_attached()) {
 | 
						|
        return;
 | 
						|
    }
 | 
						|
 | 
						|
    gdbserver_syscall_state.current_syscall_cb = cb;
 | 
						|
    va_start(va, fmt);
 | 
						|
 | 
						|
    p = gdbserver_syscall_state.syscall_buf;
 | 
						|
    p_end = p + sizeof(gdbserver_syscall_state.syscall_buf);
 | 
						|
    *(p++) = 'F';
 | 
						|
    while (*fmt) {
 | 
						|
        if (*fmt == '%') {
 | 
						|
            uint64_t i64;
 | 
						|
            uint32_t i32;
 | 
						|
 | 
						|
            fmt++;
 | 
						|
            switch (*fmt++) {
 | 
						|
            case 'x':
 | 
						|
                i32 = va_arg(va, uint32_t);
 | 
						|
                p += snprintf(p, p_end - p, "%" PRIx32, i32);
 | 
						|
                break;
 | 
						|
            case 'l':
 | 
						|
                if (*(fmt++) != 'x') {
 | 
						|
                    goto bad_format;
 | 
						|
                }
 | 
						|
                i64 = va_arg(va, uint64_t);
 | 
						|
                p += snprintf(p, p_end - p, "%" PRIx64, i64);
 | 
						|
                break;
 | 
						|
            case 's':
 | 
						|
                i64 = va_arg(va, uint64_t);
 | 
						|
                i32 = va_arg(va, uint32_t);
 | 
						|
                p += snprintf(p, p_end - p, "%" PRIx64 "/%x" PRIx32, i64, i32);
 | 
						|
                break;
 | 
						|
            default:
 | 
						|
            bad_format:
 | 
						|
                error_report("gdbstub: Bad syscall format string '%s'",
 | 
						|
                             fmt - 1);
 | 
						|
                break;
 | 
						|
            }
 | 
						|
        } else {
 | 
						|
            *(p++) = *(fmt++);
 | 
						|
        }
 | 
						|
    }
 | 
						|
    *p = 0;
 | 
						|
 | 
						|
    va_end(va);
 | 
						|
    gdb_syscall_handling(gdbserver_syscall_state.syscall_buf);
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * GDB Command Handlers
 | 
						|
 */
 | 
						|
 | 
						|
void gdb_handle_file_io(GArray *params, void *user_ctx)
 | 
						|
{
 | 
						|
    if (params->len >= 1 && gdbserver_syscall_state.current_syscall_cb) {
 | 
						|
        uint64_t ret;
 | 
						|
        int err;
 | 
						|
 | 
						|
        ret = get_param(params, 0)->val_ull;
 | 
						|
        if (params->len >= 2) {
 | 
						|
            err = get_param(params, 1)->val_ull;
 | 
						|
        } else {
 | 
						|
            err = 0;
 | 
						|
        }
 | 
						|
 | 
						|
        /* Convert GDB error numbers back to host error numbers. */
 | 
						|
#define E(X)  case GDB_E##X: err = E##X; break
 | 
						|
        switch (err) {
 | 
						|
        case 0:
 | 
						|
            break;
 | 
						|
        E(PERM);
 | 
						|
        E(NOENT);
 | 
						|
        E(INTR);
 | 
						|
        E(BADF);
 | 
						|
        E(ACCES);
 | 
						|
        E(FAULT);
 | 
						|
        E(BUSY);
 | 
						|
        E(EXIST);
 | 
						|
        E(NODEV);
 | 
						|
        E(NOTDIR);
 | 
						|
        E(ISDIR);
 | 
						|
        E(INVAL);
 | 
						|
        E(NFILE);
 | 
						|
        E(MFILE);
 | 
						|
        E(FBIG);
 | 
						|
        E(NOSPC);
 | 
						|
        E(SPIPE);
 | 
						|
        E(ROFS);
 | 
						|
        E(NAMETOOLONG);
 | 
						|
        default:
 | 
						|
            err = EINVAL;
 | 
						|
            break;
 | 
						|
        }
 | 
						|
#undef E
 | 
						|
 | 
						|
        gdbserver_syscall_state.current_syscall_cb(gdbserver_state.c_cpu,
 | 
						|
                                                   ret, err);
 | 
						|
        gdbserver_syscall_state.current_syscall_cb = NULL;
 | 
						|
    }
 | 
						|
 | 
						|
    if (params->len >= 3 && get_param(params, 2)->opcode == (uint8_t)'C') {
 | 
						|
        gdb_put_packet("T02");
 | 
						|
        return;
 | 
						|
    }
 | 
						|
 | 
						|
    gdb_continue();
 | 
						|
}
 |