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
This commit is contained in:
Peter Maydell 2020-04-20 22:22:06 +01:00
parent ab546bd238
commit 3986a1721e

View file

@ -299,85 +299,96 @@ void cpu_loop(CPUARMState *env)
env->eabi = 1; env->eabi = 1;
/* system call */ /* system call */
if (env->thumb) { if (env->thumb) {
/* FIXME - what to do if get_user() fails? */ /* Thumb is always EABI style with syscall number in r7 */
get_user_code_u16(insn, env->regs[15] - 2, env); n = env->regs[7];
n = insn & 0xff;
} else { } 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? */ /* FIXME - what to do if get_user() fails? */
get_user_code_u32(insn, env->regs[15] - 4, env); get_user_code_u32(insn, env->regs[15] - 4, env);
n = insn & 0xffffff; n = insn & 0xffffff;
} if (n == 0) {
/* zero immediate: EABI, syscall number in r7 */
if (n == 0 || n >= ARM_SYSCALL_BASE || env->thumb) {
/* linux syscall */
if (env->thumb || n == 0) {
n = env->regs[7]; n = env->regs[7];
} else { } 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; env->eabi = 0;
} }
if ( n > ARM_NR_BASE) { }
switch (n) {
case ARM_NR_cacheflush: if (n > ARM_NR_BASE) {
/* nop */ switch (n) {
break; case ARM_NR_cacheflush:
case ARM_NR_set_tls: /* nop */
cpu_set_tls(env, env->regs[0]); break;
env->regs[0] = 0; case ARM_NR_set_tls:
break; cpu_set_tls(env, env->regs[0]);
case ARM_NR_breakpoint: env->regs[0] = 0;
env->regs[15] -= env->thumb ? 2 : 4; break;
goto excp_debug; case ARM_NR_breakpoint:
case ARM_NR_get_tls: env->regs[15] -= env->thumb ? 2 : 4;
env->regs[0] = cpu_get_tls(env); goto excp_debug;
break; case ARM_NR_get_tls:
default: env->regs[0] = cpu_get_tls(env);
if (n < 0xf0800) { break;
/* default:
* Syscalls 0xf0000..0xf07ff (or 0x9f0000.. if (n < 0xf0800) {
* 0x9f07ff in OABI numbering) are defined /*
* to return -ENOSYS rather than raising * Syscalls 0xf0000..0xf07ff (or 0x9f0000..
* SIGILL. Note that we have already * 0x9f07ff in OABI numbering) are defined
* removed the 0x900000 prefix. * to return -ENOSYS rather than raising
*/ * SIGILL. Note that we have already
qemu_log_mask(LOG_UNIMP, * removed the 0x900000 prefix.
"qemu: Unsupported ARM syscall: 0x%x\n", */
n); qemu_log_mask(LOG_UNIMP,
env->regs[0] = -TARGET_ENOSYS; "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 { } else {
/* Otherwise SIGILL */ info._sifields._sigfault._addr -= 4;
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);
} }
break; queue_signal(env, info.si_signo,
} QEMU_SI_FAULT, &info);
} 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;
} }
break;
} }
} else { } 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; break;