aboutsummaryrefslogtreecommitdiff
path: root/linux-user/arm
diff options
context:
space:
mode:
authorPeter Maydell <peter.maydell@linaro.org>2020-04-20 22:22:06 +0100
committerPeter Maydell <peter.maydell@linaro.org>2020-05-21 20:00:18 +0100
commit3986a1721e10aebe0dc2c17f262ebf067e7414df (patch)
treea3b520fa21afdc1637d13dd34f44dee3788212ad /linux-user/arm
parentab546bd23856866411ff1b2ffaedabdc360e69df (diff)
downloadqemu-3986a1721e10aebe0dc2c17f262ebf067e7414df.zip
qemu-3986a1721e10aebe0dc2c17f262ebf067e7414df.tar.gz
qemu-3986a1721e10aebe0dc2c17f262ebf067e7414df.tar.bz2
linux-user/arm: Fix identification of syscall numbers
Our code to identify syscall numbers has some issues: * for Thumb mode, we never need the immediate value from the insn, but we always read it anyway * bad immediate values in the svc insn should cause a SIGILL, but we were abort()ing instead (via "goto error") We can fix both these things by refactoring the code that identifies the syscall number to more closely follow the kernel COMPAT_OABI code: * for Thumb it is always r7 * for Arm, if the immediate value is 0, then this is an EABI call with the syscall number in r7 * otherwise, we XOR the immediate value with 0x900000 (ARM_SYSCALL_BASE for QEMU; __NR_OABI_SYSCALL_BASE in the kernel), which converts valid syscall immediates into the desired value, and puts all invalid immediates in the range 0x100000 or above * then we can just let the existing "value too large, deliver SIGILL" case handle invalid numbers, and drop the 'goto error' Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Edgar E. Iglesias <edgar.iglesias@xilinx.com> Message-id: 20200420212206.12776-5-peter.maydell@linaro.org
Diffstat (limited to 'linux-user/arm')
-rw-r--r--linux-user/arm/cpu_loop.c143
1 files changed, 77 insertions, 66 deletions
diff --git a/linux-user/arm/cpu_loop.c b/linux-user/arm/cpu_loop.c
index df8b7b3..13629ee 100644
--- a/linux-user/arm/cpu_loop.c
+++ b/linux-user/arm/cpu_loop.c
@@ -299,85 +299,96 @@ void cpu_loop(CPUARMState *env)
env->eabi = 1;
/* system call */
if (env->thumb) {
- /* FIXME - what to do if get_user() fails? */
- get_user_code_u16(insn, env->regs[15] - 2, env);
- n = insn & 0xff;
+ /* Thumb is always EABI style with syscall number in r7 */
+ n = env->regs[7];
} else {
+ /*
+ * Equivalent of kernel CONFIG_OABI_COMPAT: read the
+ * Arm SVC insn to extract the immediate, which is the
+ * syscall number in OABI.
+ */
/* FIXME - what to do if get_user() fails? */
get_user_code_u32(insn, env->regs[15] - 4, env);
n = insn & 0xffffff;
- }
-
- if (n == 0 || n >= ARM_SYSCALL_BASE || env->thumb) {
- /* linux syscall */
- if (env->thumb || n == 0) {
+ if (n == 0) {
+ /* zero immediate: EABI, syscall number in r7 */
n = env->regs[7];
} else {
- n -= ARM_SYSCALL_BASE;
+ /*
+ * This XOR matches the kernel code: an immediate
+ * in the valid range (0x900000 .. 0x9fffff) is
+ * converted into the correct EABI-style syscall
+ * number; invalid immediates end up as values
+ * > 0xfffff and are handled below as out-of-range.
+ */
+ n ^= ARM_SYSCALL_BASE;
env->eabi = 0;
}
- if ( n > ARM_NR_BASE) {
- switch (n) {
- case ARM_NR_cacheflush:
- /* nop */
- break;
- case ARM_NR_set_tls:
- cpu_set_tls(env, env->regs[0]);
- env->regs[0] = 0;
- break;
- case ARM_NR_breakpoint:
- env->regs[15] -= env->thumb ? 2 : 4;
- goto excp_debug;
- case ARM_NR_get_tls:
- env->regs[0] = cpu_get_tls(env);
- break;
- default:
- if (n < 0xf0800) {
- /*
- * Syscalls 0xf0000..0xf07ff (or 0x9f0000..
- * 0x9f07ff in OABI numbering) are defined
- * to return -ENOSYS rather than raising
- * SIGILL. Note that we have already
- * removed the 0x900000 prefix.
- */
- qemu_log_mask(LOG_UNIMP,
- "qemu: Unsupported ARM syscall: 0x%x\n",
- n);
- env->regs[0] = -TARGET_ENOSYS;
+ }
+
+ if (n > ARM_NR_BASE) {
+ switch (n) {
+ case ARM_NR_cacheflush:
+ /* nop */
+ break;
+ case ARM_NR_set_tls:
+ cpu_set_tls(env, env->regs[0]);
+ env->regs[0] = 0;
+ break;
+ case ARM_NR_breakpoint:
+ env->regs[15] -= env->thumb ? 2 : 4;
+ goto excp_debug;
+ case ARM_NR_get_tls:
+ env->regs[0] = cpu_get_tls(env);
+ break;
+ default:
+ if (n < 0xf0800) {
+ /*
+ * Syscalls 0xf0000..0xf07ff (or 0x9f0000..
+ * 0x9f07ff in OABI numbering) are defined
+ * to return -ENOSYS rather than raising
+ * SIGILL. Note that we have already
+ * removed the 0x900000 prefix.
+ */
+ qemu_log_mask(LOG_UNIMP,
+ "qemu: Unsupported ARM syscall: 0x%x\n",
+ n);
+ env->regs[0] = -TARGET_ENOSYS;
+ } else {
+ /*
+ * Otherwise SIGILL. This includes any SWI with
+ * immediate not originally 0x9fxxxx, because
+ * of the earlier XOR.
+ */
+ info.si_signo = TARGET_SIGILL;
+ info.si_errno = 0;
+ info.si_code = TARGET_ILL_ILLTRP;
+ info._sifields._sigfault._addr = env->regs[15];
+ if (env->thumb) {
+ info._sifields._sigfault._addr -= 2;
} else {
- /* Otherwise SIGILL */
- info.si_signo = TARGET_SIGILL;
- info.si_errno = 0;
- info.si_code = TARGET_ILL_ILLTRP;
- info._sifields._sigfault._addr = env->regs[15];
- if (env->thumb) {
- info._sifields._sigfault._addr -= 2;
- } else {
- info._sifields._sigfault._addr -= 4;
- }
- queue_signal(env, info.si_signo,
- QEMU_SI_FAULT, &info);
+ info._sifields._sigfault._addr -= 4;
}
- break;
- }
- } else {
- ret = do_syscall(env,
- n,
- env->regs[0],
- env->regs[1],
- env->regs[2],
- env->regs[3],
- env->regs[4],
- env->regs[5],
- 0, 0);
- if (ret == -TARGET_ERESTARTSYS) {
- env->regs[15] -= env->thumb ? 2 : 4;
- } else if (ret != -TARGET_QEMU_ESIGRETURN) {
- env->regs[0] = ret;
+ queue_signal(env, info.si_signo,
+ QEMU_SI_FAULT, &info);
}
+ break;
}
} else {
- goto error;
+ ret = do_syscall(env,
+ n,
+ env->regs[0],
+ env->regs[1],
+ env->regs[2],
+ env->regs[3],
+ env->regs[4],
+ env->regs[5],
+ 0, 0);
+ if (ret == -TARGET_ERESTARTSYS) {
+ env->regs[15] -= env->thumb ? 2 : 4;
+ } else if (ret != -TARGET_QEMU_ESIGRETURN) {
+ env->regs[0] = ret;
+ }
}
}
break;