This patch improves support for semihosting and debugging with the in-built gdbstub for ARM system-mode emulation in big-endian mode (either BE8 or BE32), after the fairly recent changes to allow a single QEMU binary to deal with each of LE, BE8 and BE32 modes in one. It's only currently good for little-endian host systems. The relevant use case is using QEMU as a "bare metal" instruction-set simulator, e.g. for toolchain testing.
For semihosting, the softmmu-semi.h file is overridden with an ARM-specific version that knows about byte-swapping target memory into host order -- including that which has been byte-swapped at load time for BE32 mode. For the gdbstub, we'd like to be able to invoke QEMU from GDB like: (gdb) target remote | arm-qemu-system -cpu=foo [options] /dev/null (gdb) load (gdb) ... which unfortunately bypasses the probing of the loaded ELF file (since it's just /dev/null) to determine whether to use BE8/BE32 mode. So, I added some "virtual" CPUs to set the endian type instead (arm926-be, cortex-a15-be for BE32/BE8 mode respectively), from the reset value of the SCTLR. This is kind of like having a configuration input to the CPU on some hardware board to select endianness, which is a completely legitimate thing to have, even if the implementation as-is is not really ideal from a software-engineering standpoint. It suffices for our current use-case though. Signed-off-by: Julian Brown <jul...@codesourcery.com> --- hw/arm/boot.c | 16 ++++- include/exec/softmmu-arm-semi.h | 148 ++++++++++++++++++++++++++++++++++++++++ target-arm/arm-semi.c | 2 +- target-arm/cpu.c | 49 +++++++++++++ target-arm/gdbstub.c | 42 ++++++++++++ 5 files changed, 255 insertions(+), 2 deletions(-) create mode 100644 include/exec/softmmu-arm-semi.h diff --git a/hw/arm/boot.c b/hw/arm/boot.c index 942416d..644fdaf 100644 --- a/hw/arm/boot.c +++ b/hw/arm/boot.c @@ -894,7 +894,21 @@ static void arm_load_kernel_notify(Notifier *notifier, void *data) entry = info->loader_start + kernel_load_offset; kernel_size = load_image_targphys(info->kernel_filename, entry, info->ram_size - kernel_load_offset); - is_linux = 1; + if (kernel_size > 0) { + is_linux = 1; + } else { + /* We've been launched with a kernel of /dev/null or similar. + * Infer endianness from the reset value of the SCTLR for this + * CPU/board (FIXME: a way of configuring this more nicely). + */ + if (!arm_feature(&cpu->env, ARM_FEATURE_V7) && + (cpu->reset_sctlr & SCTLR_B) != 0) + info->endianness = ARM_ENDIANNESS_BE32; + else if ((cpu->reset_sctlr & SCTLR_EE) != 0) + info->endianness = ARM_ENDIANNESS_BE8; + else + info->endianness = ARM_ENDIANNESS_LE; + } } if (kernel_size < 0) { fprintf(stderr, "qemu: could not load kernel '%s'\n", diff --git a/include/exec/softmmu-arm-semi.h b/include/exec/softmmu-arm-semi.h new file mode 100644 index 0000000..e9ddb21 --- /dev/null +++ b/include/exec/softmmu-arm-semi.h @@ -0,0 +1,148 @@ +/* + * Helper routines to provide target memory access for ARM semihosting + * syscalls in system emulation mode. + * + * Copyright (c) 2007 CodeSourcery, (c) 2016 Mentor Graphics + * + * This code is licensed under the GPL + */ + +#ifndef SOFTMMU_ARM_SEMI_H +#define SOFTMMU_ARM_SEMI_H 1 + +/* In BE32 system mode, the CPU-specific memory_rw_debug method will arrange to + * perform byteswapping on the target memory, so that it appears to the host as + * it appears to the emulated CPU. Memory is read verbatim in BE8 mode. (In + * other words, this function arranges so that BUF has the same format in both + * BE8 and BE32 system mode.) + */ + +static inline int armsemi_memory_rw_debug(CPUState *cpu, target_ulong addr, + uint8_t *buf, int len, bool is_write) +{ + CPUClass *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); +} + +/* In big-endian mode (either BE8 or BE32), values larger than a byte will be + * transferred to/from memory in big-endian format. Assuming we're on a + * little-endian host machine, such values will need to be byteswapped before + * and after the host processes them. + * + * This means that byteswapping will occur *twice* in BE32 mode for + * halfword/word reads/writes. + */ + +static inline bool arm_bswap_needed(CPUARMState *env) +{ +#ifdef HOST_WORDS_BIGENDIAN +#error HOST_WORDS_BIGENDIAN is not supported for ARM semihosting at the moment. +#else + return arm_sctlr_b(env) || (env->uncached_cpsr & CPSR_E) != 0; +#endif +} + +static inline uint64_t softmmu_tget64(CPUArchState *env, target_ulong addr) +{ + uint64_t val; + + armsemi_memory_rw_debug(ENV_GET_CPU(env), addr, (uint8_t *)&val, 8, 0); + if (arm_bswap_needed(env)) { + return bswap64(val); + } else { + return val; + } +} + +static inline uint32_t softmmu_tget32(CPUArchState *env, target_ulong addr) +{ + uint32_t val; + + armsemi_memory_rw_debug(ENV_GET_CPU(env), addr, (uint8_t *)&val, 4, 0); + if (arm_bswap_needed(env)) { + return bswap32(val); + } else { + return val; + } +} + +static inline uint32_t softmmu_tget8(CPUArchState *env, target_ulong addr) +{ + uint8_t val; + armsemi_memory_rw_debug(ENV_GET_CPU(env), addr, &val, 1, 0); + return val; +} + +#define get_user_u64(arg, p) ({ arg = softmmu_tget64(env, p); 0; }) +#define get_user_u32(arg, p) ({ arg = softmmu_tget32(env, p) ; 0; }) +#define get_user_u8(arg, p) ({ arg = softmmu_tget8(env, p) ; 0; }) +#define get_user_ual(arg, p) get_user_u32(arg, p) + +static inline void softmmu_tput64(CPUArchState *env, + target_ulong addr, uint64_t val) +{ + if (arm_bswap_needed(env)) { + val = bswap64(val); + } + cpu_memory_rw_debug(ENV_GET_CPU(env), addr, (uint8_t *)&val, 8, 1); +} + +static inline void softmmu_tput32(CPUArchState *env, + target_ulong addr, uint32_t val) +{ + if (arm_bswap_needed(env)) { + val = bswap32(val); + } + armsemi_memory_rw_debug(ENV_GET_CPU(env), addr, (uint8_t *)&val, 4, 1); +} +#define put_user_u64(arg, p) ({ softmmu_tput64(env, p, arg) ; 0; }) +#define put_user_u32(arg, p) ({ softmmu_tput32(env, p, arg) ; 0; }) +#define put_user_ual(arg, p) put_user_u32(arg, p) + +static void *softmmu_lock_user(CPUArchState *env, + target_ulong addr, target_ulong len, int copy) +{ + uint8_t *p; + /* TODO: Make this something that isn't fixed size. */ + p = malloc(len); + if (p && copy) { + armsemi_memory_rw_debug(ENV_GET_CPU(env), addr, p, len, 0); + } + return p; +} +#define lock_user(type, p, len, copy) softmmu_lock_user(env, p, len, copy) +static char *softmmu_lock_user_string(CPUArchState *env, target_ulong addr) +{ + char *p; + char *s; + uint8_t c; + /* TODO: Make this something that isn't fixed size. */ + s = p = malloc(1024); + if (!s) { + return NULL; + } + do { + armsemi_memory_rw_debug(ENV_GET_CPU(env), addr, &c, 1, 0); + addr++; + *(p++) = c; + } while (c); + return s; +} +#define lock_user_string(p) softmmu_lock_user_string(env, p) +static void softmmu_unlock_user(CPUArchState *env, void *p, target_ulong addr, + target_ulong len) +{ + uint8_t *pc = p; + if (len) { + armsemi_memory_rw_debug(ENV_GET_CPU(env), addr, p, len, 1); + } + free(p); +} + +#define unlock_user(s, args, len) softmmu_unlock_user(env, s, args, len) + +#endif diff --git a/target-arm/arm-semi.c b/target-arm/arm-semi.c index 7cac873..a9cf5f2 100644 --- a/target-arm/arm-semi.c +++ b/target-arm/arm-semi.c @@ -114,7 +114,7 @@ static inline uint32_t set_swi_errno(CPUARMState *env, uint32_t code) return code; } -#include "exec/softmmu-semi.h" +#include "exec/softmmu-arm-semi.h" #endif static target_ulong arm_semi_syscall_len; diff --git a/target-arm/cpu.c b/target-arm/cpu.c index 15a174f..2918b24 100644 --- a/target-arm/cpu.c +++ b/target-arm/cpu.c @@ -32,6 +32,7 @@ #include "sysemu/sysemu.h" #include "sysemu/kvm.h" #include "kvm_arm.h" +#include "exec/cpu-common.h" static void arm_cpu_set_pc(CPUState *cs, vaddr value) { @@ -780,6 +781,17 @@ static void arm926_initfn(Object *obj) cpu->reset_sctlr = 0x00090078; } +/* FIXME: This is likely not the best way to override or specify endianness + * for a CPU/board. + */ +static void arm926_be_initfn(Object *obj) +{ + ARMCPU *cpu = ARM_CPU(obj); + + arm926_initfn(obj); + cpu->reset_sctlr |= SCTLR_B; +} + static void arm946_initfn(Object *obj) { ARMCPU *cpu = ARM_CPU(obj); @@ -1248,6 +1260,17 @@ static void cortex_a15_initfn(Object *obj) define_arm_cp_regs(cpu, cortexa15_cp_reginfo); } +/* FIXME: As arm926_be_initfn, this is likely not the best way to override or + * specify endianness for a CPU/board. + */ +static void cortex_a15_be_initfn(Object *obj) +{ + ARMCPU *cpu = ARM_CPU(obj); + + cortex_a15_initfn(obj); + cpu->reset_sctlr |= SCTLR_EE; +} + static void ti925t_initfn(Object *obj) { ARMCPU *cpu = ARM_CPU(obj); @@ -1442,6 +1465,7 @@ typedef struct ARMCPUInfo { static const ARMCPUInfo arm_cpus[] = { #if !defined(CONFIG_USER_ONLY) || !defined(TARGET_AARCH64) { .name = "arm926", .initfn = arm926_initfn }, + { .name = "arm926-be", .initfn = arm926_be_initfn }, { .name = "arm946", .initfn = arm946_initfn }, { .name = "arm1026", .initfn = arm1026_initfn }, /* What QEMU calls "arm1136-r2" is actually the 1136 r0p2, i.e. an @@ -1461,6 +1485,7 @@ static const ARMCPUInfo arm_cpus[] = { { .name = "cortex-a8", .initfn = cortex_a8_initfn }, { .name = "cortex-a9", .initfn = cortex_a9_initfn }, { .name = "cortex-a15", .initfn = cortex_a15_initfn }, + { .name = "cortex-a15-be", .initfn = cortex_a15_be_initfn }, { .name = "ti925t", .initfn = ti925t_initfn }, { .name = "sa1100", .initfn = sa1100_initfn }, { .name = "sa1110", .initfn = sa1110_initfn }, @@ -1519,6 +1544,27 @@ static gchar *arm_gdb_arch_name(CPUState *cs) return g_strdup("arm"); } +#ifndef CONFIG_USER_ONLY +static int arm_cpu_memory_rw_debug(CPUState *cpu, vaddr address, + uint8_t *buf, int len, bool is_write) +{ + target_ulong addr = address; + ARMCPU *armcpu = ARM_CPU(cpu); + CPUARMState *env = &armcpu->env; + + if (arm_sctlr_b(env)) { + target_ulong i; + for (i = 0; i < len; i++) { + cpu_memory_rw_debug(cpu, (addr + i) ^ 3, &buf[i], 1, is_write); + } + } else { + cpu_memory_rw_debug(cpu, addr, buf, len, is_write); + } + + return 0; +} +#endif + static void arm_cpu_class_init(ObjectClass *oc, void *data) { ARMCPUClass *acc = ARM_CPU_CLASS(oc); @@ -1536,6 +1582,9 @@ static void arm_cpu_class_init(ObjectClass *oc, void *data) cc->has_work = arm_cpu_has_work; cc->cpu_exec_interrupt = arm_cpu_exec_interrupt; cc->dump_state = arm_cpu_dump_state; +#if !defined(CONFIG_USER_ONLY) + cc->memory_rw_debug = arm_cpu_memory_rw_debug; +#endif cc->set_pc = arm_cpu_set_pc; cc->gdb_read_register = arm_cpu_gdb_read_register; cc->gdb_write_register = arm_cpu_gdb_write_register; diff --git a/target-arm/gdbstub.c b/target-arm/gdbstub.c index 04c1208..1e9fe68 100644 --- a/target-arm/gdbstub.c +++ b/target-arm/gdbstub.c @@ -21,6 +21,7 @@ #include "qemu-common.h" #include "cpu.h" #include "exec/gdbstub.h" +#include "exec/softmmu-arm-semi.h" /* Old gdb always expect FPA registers. Newer (xml-aware) gdb only expect whatever the target description contains. Due to a historical mishap @@ -32,10 +33,22 @@ int arm_cpu_gdb_read_register(CPUState *cs, uint8_t *mem_buf, int n) { ARMCPU *cpu = ARM_CPU(cs); CPUARMState *env = &cpu->env; +#ifndef CONFIG_USER_ONLY + bool targ_bigendian = arm_bswap_needed(env); +#endif if (n < 16) { /* Core integer register. */ +#ifdef CONFIG_USER_ONLY return gdb_get_reg32(mem_buf, env->regs[n]); +#else + if (targ_bigendian) { + stl_be_p(mem_buf, env->regs[n]); + } else { + stl_le_p(mem_buf, env->regs[n]); + } + return 4; +#endif } if (n < 24) { /* FPA registers. */ @@ -51,10 +64,28 @@ int arm_cpu_gdb_read_register(CPUState *cs, uint8_t *mem_buf, int n) if (gdb_has_xml) { return 0; } +#ifdef CONFIG_USER_ONLY return gdb_get_reg32(mem_buf, 0); +#else + if (targ_bigendian) { + stl_be_p(mem_buf, 0); + } else { + stl_le_p(mem_buf, 0); + } + return 4; +#endif case 25: /* CPSR */ +#ifdef CONFIG_USER_ONLY return gdb_get_reg32(mem_buf, cpsr_read(env)); +#else + if (targ_bigendian) { + stl_be_p(mem_buf, cpsr_read(env)); + } else { + stl_le_p(mem_buf, cpsr_read(env)); + } + return 4; +#endif } /* Unknown register. */ return 0; @@ -65,8 +96,19 @@ int arm_cpu_gdb_write_register(CPUState *cs, uint8_t *mem_buf, int n) ARMCPU *cpu = ARM_CPU(cs); CPUARMState *env = &cpu->env; uint32_t tmp; +#ifndef CONFIG_USER_ONLY + bool targ_bigendian = arm_bswap_needed(env); +#endif +#ifdef CONFIG_USER_ONLY tmp = ldl_p(mem_buf); +#else + if (targ_bigendian) { + tmp = ldl_be_p(mem_buf); + } else { + tmp = ldl_le_p(mem_buf); + } +#endif /* Mask out low bit of PC to workaround gdb bugs. This will probably cause problems if we ever implement the Jazelle DBX extensions. */ -- 1.9.1