linux-user: Move syscall error detection into safe_syscall_base

The current api from safe_syscall_base() is to return -errno, which is
the interface provided by *some* linux kernel abis.  The wrapper macro,
safe_syscall(), detects error, stores into errno, and returns -1, to
match the api of the system syscall().

For those kernel abis that do not return -errno natively, this leads
to double syscall error detection.  E.g. Linux ppc64, which sets the
SO flag for error.

Simplify the usage from C by moving the error detection into assembly,
and usage from assembly by providing a C helper with which to set errno.

Reviewed-by: Warner Losh <imp@bsdimp.com>
Signed-off-by: Richard Henderson <richard.henderson@linaro.org>
This commit is contained in:
Richard Henderson 2021-11-15 14:08:52 +01:00
parent b9d2af3c62
commit a3310c0397
10 changed files with 147 additions and 87 deletions

View File

@ -22,15 +22,12 @@
* first argument an 'int *' to the signal_pending flag, the * first argument an 'int *' to the signal_pending flag, the
* second one the system call number (as a 'long'), and all further * second one the system call number (as a 'long'), and all further
* arguments being syscall arguments (also 'long'). * arguments being syscall arguments (also 'long').
* We return a long which is the syscall's return value, which
* may be negative-errno on failure. Conversion to the
* -1-and-errno-set convention is done by the calling wrapper.
*/ */
safe_syscall_base: safe_syscall_base:
.cfi_startproc .cfi_startproc
/* The syscall calling convention isn't the same as the /* The syscall calling convention isn't the same as the
* C one: * C one:
* we enter with x0 == *signal_pending * we enter with x0 == &signal_pending
* x1 == syscall number * x1 == syscall number
* x2 ... x7, (stack) == syscall arguments * x2 ... x7, (stack) == syscall arguments
* and return the result in x0 * and return the result in x0
@ -60,16 +57,21 @@ safe_syscall_base:
safe_syscall_start: safe_syscall_start:
/* if signal_pending is non-zero, don't do the call */ /* if signal_pending is non-zero, don't do the call */
ldr w10, [x9] ldr w10, [x9]
cbnz w10, 0f cbnz w10, 2f
svc 0x0 svc 0x0
safe_syscall_end: safe_syscall_end:
/* code path for having successfully executed the syscall */ /* code path for having successfully executed the syscall */
cmp x0, #-4096
b.hi 0f
ret ret
0: /* code path setting errno */
0: neg w0, w0
b safe_syscall_set_errno_tail
/* code path when we didn't execute the syscall */ /* code path when we didn't execute the syscall */
mov x0, #-TARGET_ERESTARTSYS 2: mov w0, #TARGET_ERESTARTSYS
ret b safe_syscall_set_errno_tail
.cfi_endproc
.cfi_endproc
.size safe_syscall_base, .-safe_syscall_base .size safe_syscall_base, .-safe_syscall_base

View File

@ -27,9 +27,6 @@
* first argument an 'int *' to the signal_pending flag, the * first argument an 'int *' to the signal_pending flag, the
* second one the system call number (as a 'long'), and all further * second one the system call number (as a 'long'), and all further
* arguments being syscall arguments (also 'long'). * arguments being syscall arguments (also 'long').
* We return a long which is the syscall's return value, which
* may be negative-errno on failure. Conversion to the
* -1-and-errno-set convention is done by the calling wrapper.
*/ */
safe_syscall_base: safe_syscall_base:
.fnstart .fnstart
@ -46,7 +43,7 @@ safe_syscall_base:
.cfi_rel_offset lr, 20 .cfi_rel_offset lr, 20
/* The syscall calling convention isn't the same as the C one: /* The syscall calling convention isn't the same as the C one:
* we enter with r0 == *signal_pending * we enter with r0 == &signal_pending
* r1 == syscall number * r1 == syscall number
* r2, r3, [sp+0] ... [sp+12] == syscall arguments * r2, r3, [sp+0] ... [sp+12] == syscall arguments
* and return the result in r0 * and return the result in r0
@ -74,17 +71,29 @@ safe_syscall_start:
/* if signal_pending is non-zero, don't do the call */ /* if signal_pending is non-zero, don't do the call */
ldr r12, [r8] /* signal_pending */ ldr r12, [r8] /* signal_pending */
tst r12, r12 tst r12, r12
bne 1f bne 2f
swi 0 swi 0
safe_syscall_end: safe_syscall_end:
/* code path for having successfully executed the syscall */ /* code path for having successfully executed the syscall */
cmp r0, #-4096
neghi r0, r0
bhi 1f
pop { r4, r5, r6, r7, r8, pc } pop { r4, r5, r6, r7, r8, pc }
1:
/* code path when we didn't execute the syscall */ /* code path when we didn't execute the syscall */
ldr r0, =-TARGET_ERESTARTSYS 2: mov r0, #TARGET_ERESTARTSYS
pop { r4, r5, r6, r7, r8, pc }
/* code path setting errno */
1: pop { r4, r5, r6, r7, r8, lr }
.cfi_adjust_cfa_offset -24
.cfi_restore r4
.cfi_restore r5
.cfi_restore r6
.cfi_restore r7
.cfi_restore r8
.cfi_restore lr
b safe_syscall_set_errno_tail
.fnend .fnend
.cfi_endproc .cfi_endproc
.size safe_syscall_base, .-safe_syscall_base .size safe_syscall_base, .-safe_syscall_base

View File

@ -20,9 +20,6 @@
* first argument an 'int *' to the signal_pending flag, the * first argument an 'int *' to the signal_pending flag, the
* second one the system call number (as a 'long'), and all further * second one the system call number (as a 'long'), and all further
* arguments being syscall arguments (also 'long'). * arguments being syscall arguments (also 'long').
* We return a long which is the syscall's return value, which
* may be negative-errno on failure. Conversion to the
* -1-and-errno-set convention is done by the calling wrapper.
*/ */
safe_syscall_base: safe_syscall_base:
.cfi_startproc .cfi_startproc
@ -41,7 +38,7 @@ safe_syscall_base:
/* The syscall calling convention isn't the same as the C one: /* The syscall calling convention isn't the same as the C one:
* we enter with 0(%esp) == return address * we enter with 0(%esp) == return address
* 4(%esp) == *signal_pending * 4(%esp) == &signal_pending
* 8(%esp) == syscall number * 8(%esp) == syscall number
* 12(%esp) ... 32(%esp) == syscall arguments * 12(%esp) ... 32(%esp) == syscall arguments
* and return the result in eax * and return the result in eax
@ -70,11 +67,13 @@ safe_syscall_start:
/* if signal_pending is non-zero, don't do the call */ /* if signal_pending is non-zero, don't do the call */
mov 4+16(%esp), %eax /* signal_pending */ mov 4+16(%esp), %eax /* signal_pending */
cmpl $0, (%eax) cmpl $0, (%eax)
jnz 1f jnz 2f
mov 8+16(%esp), %eax /* syscall number */ mov 8+16(%esp), %eax /* syscall number */
int $0x80 int $0x80
safe_syscall_end: safe_syscall_end:
/* code path for having successfully executed the syscall */ /* code path for having successfully executed the syscall */
cmp $-4095, %eax
jae 0f
pop %ebx pop %ebx
.cfi_remember_state .cfi_remember_state
.cfi_adjust_cfa_offset -4 .cfi_adjust_cfa_offset -4
@ -89,12 +88,28 @@ safe_syscall_end:
.cfi_adjust_cfa_offset -4 .cfi_adjust_cfa_offset -4
.cfi_restore ebp .cfi_restore ebp
ret ret
1:
/* code path when we didn't execute the syscall */
.cfi_restore_state .cfi_restore_state
mov $-TARGET_ERESTARTSYS, %eax
jmp safe_syscall_end
.cfi_endproc
0: neg %eax
jmp 1f
/* code path when we didn't execute the syscall */
2: mov $TARGET_ERESTARTSYS, %eax
/* code path setting errno */
1: pop %ebx
.cfi_adjust_cfa_offset -4
.cfi_restore ebx
pop %edi
.cfi_adjust_cfa_offset -4
.cfi_restore edi
pop %esi
.cfi_adjust_cfa_offset -4
.cfi_restore esi
pop %ebp
.cfi_adjust_cfa_offset -4
.cfi_restore ebp
jmp safe_syscall_set_errno_tail
.cfi_endproc
.size safe_syscall_base, .-safe_syscall_base .size safe_syscall_base, .-safe_syscall_base

View File

@ -22,9 +22,6 @@
* first argument an 'int *' to the signal_pending flag, the * first argument an 'int *' to the signal_pending flag, the
* second one the system call number (as a 'long'), and all further * second one the system call number (as a 'long'), and all further
* arguments being syscall arguments (also 'long'). * arguments being syscall arguments (also 'long').
* We return a long which is the syscall's return value, which
* may be negative-errno on failure. Conversion to the
* -1-and-errno-set convention is done by the calling wrapper.
*/ */
#if _CALL_ELF == 2 #if _CALL_ELF == 2
safe_syscall_base: safe_syscall_base:
@ -39,7 +36,7 @@ safe_syscall_base:
.L.safe_syscall_base: .L.safe_syscall_base:
.cfi_startproc .cfi_startproc
#endif #endif
/* We enter with r3 == *signal_pending /* We enter with r3 == &signal_pending
* r4 == syscall number * r4 == syscall number
* r5 ... r10 == syscall arguments * r5 ... r10 == syscall arguments
* and return the result in r3 * and return the result in r3
@ -71,21 +68,22 @@ safe_syscall_start:
/* if signal_pending is non-zero, don't do the call */ /* if signal_pending is non-zero, don't do the call */
lwz 12, 0(14) lwz 12, 0(14)
cmpwi 0, 12, 0 cmpwi 0, 12, 0
bne- 0f bne- 2f
sc sc
safe_syscall_end: safe_syscall_end:
/* code path when we did execute the syscall */ /* code path when we did execute the syscall */
ld 14, 16(1) /* restore r14 to its original value */ ld 14, 16(1) /* restore r14 */
bnslr+ bso- 1f
/* syscall failed; return negative errno */
neg 3, 3
blr blr
/* code path when we didn't execute the syscall */ /* code path when we didn't execute the syscall */
0: addi 3, 0, -TARGET_ERESTARTSYS 2: ld 14, 16(1) /* restore r14 */
ld 14, 16(1) /* restore r14 to its original value */ addi 3, 0, TARGET_ERESTARTSYS
blr
/* code path setting errno */
1: b safe_syscall_set_errno_tail
nop /* per abi, for the linker to modify */
.cfi_endproc .cfi_endproc
#if _CALL_ELF == 2 #if _CALL_ELF == 2

View File

@ -23,15 +23,12 @@
* first argument an 'int *' to the signal_pending flag, the * first argument an 'int *' to the signal_pending flag, the
* second one the system call number (as a 'long'), and all further * second one the system call number (as a 'long'), and all further
* arguments being syscall arguments (also 'long'). * arguments being syscall arguments (also 'long').
* We return a long which is the syscall's return value, which
* may be negative-errno on failure. Conversion to the
* -1-and-errno-set convention is done by the calling wrapper.
*/ */
safe_syscall_base: safe_syscall_base:
.cfi_startproc .cfi_startproc
/* /*
* The syscall calling convention is nearly the same as C: * The syscall calling convention is nearly the same as C:
* we enter with a0 == *signal_pending * we enter with a0 == &signal_pending
* a1 == syscall number * a1 == syscall number
* a2 ... a7 == syscall arguments * a2 ... a7 == syscall arguments
* and return the result in a0 * and return the result in a0
@ -62,16 +59,21 @@ safe_syscall_base:
safe_syscall_start: safe_syscall_start:
/* If signal_pending is non-zero, don't do the call */ /* If signal_pending is non-zero, don't do the call */
lw t1, 0(t0) lw t1, 0(t0)
bnez t1, 0f bnez t1, 2f
scall scall
safe_syscall_end: safe_syscall_end:
/* code path for having successfully executed the syscall */ /* code path for having successfully executed the syscall */
li t2, -4096
bgtu a0, t2, 0f
ret ret
0: /* code path setting errno */
0: neg a0, a0
j safe_syscall_set_errno_tail
/* code path when we didn't execute the syscall */ /* code path when we didn't execute the syscall */
li a0, -TARGET_ERESTARTSYS 2: li a0, TARGET_ERESTARTSYS
ret j safe_syscall_set_errno_tail
.cfi_endproc
.cfi_endproc
.size safe_syscall_base, .-safe_syscall_base .size safe_syscall_base, .-safe_syscall_base

View File

@ -20,9 +20,6 @@
* first argument an 'int *' to the signal_pending flag, the * first argument an 'int *' to the signal_pending flag, the
* second one the system call number (as a 'long'), and all further * second one the system call number (as a 'long'), and all further
* arguments being syscall arguments (also 'long'). * arguments being syscall arguments (also 'long').
* We return a long which is the syscall's return value, which
* may be negative-errno on failure. Conversion to the
* -1-and-errno-set convention is done by the calling wrapper.
*/ */
safe_syscall_base: safe_syscall_base:
.cfi_startproc .cfi_startproc
@ -44,9 +41,9 @@ safe_syscall_base:
stg %r1,0(%r15) /* store back chain */ stg %r1,0(%r15) /* store back chain */
stg %r0,8(%r15) /* store eos */ stg %r0,8(%r15) /* store eos */
/* The syscall calling convention isn't the same as the /*
* C one: * The syscall calling convention isn't the same as the C one:
* we enter with r2 == *signal_pending * we enter with r2 == &signal_pending
* r3 == syscall number * r3 == syscall number
* r4, r5, r6, (stack) == syscall arguments * r4, r5, r6, (stack) == syscall arguments
* and return the result in r2 * and return the result in r2
@ -77,14 +74,25 @@ safe_syscall_start:
svc 0 svc 0
safe_syscall_end: safe_syscall_end:
1: lg %r15,0(%r15) /* load back chain */ /* code path for having successfully executed the syscall */
lg %r15,0(%r15) /* load back chain */
.cfi_remember_state .cfi_remember_state
.cfi_adjust_cfa_offset -160 .cfi_adjust_cfa_offset -160
lmg %r6,%r15,48(%r15) /* load saved registers */ lmg %r6,%r15,48(%r15) /* load saved registers */
br %r14
.cfi_restore_state
2: lghi %r2, -TARGET_ERESTARTSYS
j 1b
.cfi_endproc
lghi %r0, -4095 /* check for syscall error */
clgr %r2, %r0
blr %r14 /* return on success */
lcr %r2, %r2 /* create positive errno */
jg safe_syscall_set_errno_tail
.cfi_restore_state
/* code path when we didn't execute the syscall */
2: lg %r15,0(%r15) /* load back chain */
.cfi_adjust_cfa_offset -160
lmg %r6,%r15,48(%r15) /* load saved registers */
lghi %r2, TARGET_ERESTARTSYS
jg safe_syscall_set_errno_tail
.cfi_endproc
.size safe_syscall_base, .-safe_syscall_base .size safe_syscall_base, .-safe_syscall_base

View File

@ -19,9 +19,6 @@
* first argument an 'int *' to the signal_pending flag, the * first argument an 'int *' to the signal_pending flag, the
* second one the system call number (as a 'long'), and all further * second one the system call number (as a 'long'), and all further
* arguments being syscall arguments (also 'long'). * arguments being syscall arguments (also 'long').
* We return a long which is the syscall's return value, which
* may be negative-errno on failure. Conversion to the
* -1-and-errno-set convention is done by the calling wrapper.
*/ */
safe_syscall_base: safe_syscall_base:
.cfi_startproc .cfi_startproc
@ -35,9 +32,9 @@ safe_syscall_base:
.cfi_adjust_cfa_offset 8 .cfi_adjust_cfa_offset 8
.cfi_rel_offset rbp, 0 .cfi_rel_offset rbp, 0
/* The syscall calling convention isn't the same as the /*
* C one: * The syscall calling convention isn't the same as the C one:
* we enter with rdi == *signal_pending * we enter with rdi == &signal_pending
* rsi == syscall number * rsi == syscall number
* rdx, rcx, r8, r9, (stack), (stack) == syscall arguments * rdx, rcx, r8, r9, (stack), (stack) == syscall arguments
* and return the result in rax * and return the result in rax
@ -68,24 +65,30 @@ safe_syscall_base:
safe_syscall_start: safe_syscall_start:
/* if signal_pending is non-zero, don't do the call */ /* if signal_pending is non-zero, don't do the call */
cmpl $0, (%rbp) cmpl $0, (%rbp)
jnz 1f jnz 2f
syscall syscall
safe_syscall_end: safe_syscall_end:
/* code path for having successfully executed the syscall */ /* code path for having successfully executed the syscall */
cmp $-4095, %rax
jae 0f
pop %rbp pop %rbp
.cfi_remember_state .cfi_remember_state
.cfi_def_cfa_offset 8 .cfi_def_cfa_offset 8
.cfi_restore rbp .cfi_restore rbp
ret ret
1:
/* code path when we didn't execute the syscall */
.cfi_restore_state .cfi_restore_state
mov $-TARGET_ERESTARTSYS, %rax
pop %rbp 0: neg %eax
jmp 1f
/* code path when we didn't execute the syscall */
2: mov $TARGET_ERESTARTSYS, %eax
/* code path setting errno */
1: pop %rbp
.cfi_def_cfa_offset 8 .cfi_def_cfa_offset 8
.cfi_restore rbp .cfi_restore rbp
ret jmp safe_syscall_set_errno_tail
.cfi_endproc .cfi_endproc
.size safe_syscall_base, .-safe_syscall_base .size safe_syscall_base, .-safe_syscall_base

View File

@ -10,6 +10,7 @@ linux_user_ss.add(files(
'main.c', 'main.c',
'mmap.c', 'mmap.c',
'safe-syscall.S', 'safe-syscall.S',
'safe-syscall-error.c',
'signal.c', 'signal.c',
'strace.c', 'strace.c',
'syscall.c', 'syscall.c',

View File

@ -0,0 +1,28 @@
/*
* safe-syscall-error.c: errno setting fragment
* This is intended to be invoked by safe-syscall.S
*
* Written by Richard Henderson <rth@twiddle.net>
* Copyright (C) 2021 Red Hat, Inc.
*
* This work is licensed under the terms of the GNU GPL, version 2 or later.
* See the COPYING file in the top-level directory.
*/
#include "qemu/osdep.h"
#include "hostdep.h"
#include "safe-syscall.h"
#ifdef HAVE_SAFE_SYSCALL
/*
* This is intended to be invoked via tail-call on the error path
* from the assembly in host/arch/safe-syscall.inc.S. This takes
* care of the host specific addressing of errno.
* Return -1 to finalize the return value for safe_syscall_base.
*/
long safe_syscall_set_errno_tail(int value)
{
errno = value;
return -1;
}
#endif

View File

@ -127,21 +127,15 @@
#ifdef HAVE_SAFE_SYSCALL #ifdef HAVE_SAFE_SYSCALL
/* The core part of this function is implemented in assembly */ /* The core part of this function is implemented in assembly */
extern long safe_syscall_base(int *pending, long number, ...); extern long safe_syscall_base(int *pending, long number, ...);
extern long safe_syscall_set_errno_tail(int value);
/* These are defined by the safe-syscall.inc.S file */ /* These are defined by the safe-syscall.inc.S file */
extern char safe_syscall_start[]; extern char safe_syscall_start[];
extern char safe_syscall_end[]; extern char safe_syscall_end[];
#define safe_syscall(...) \ #define safe_syscall(...) \
({ \ safe_syscall_base(&((TaskState *)thread_cpu->opaque)->signal_pending, \
long ret_; \ __VA_ARGS__)
int *psp_ = &((TaskState *)thread_cpu->opaque)->signal_pending; \
ret_ = safe_syscall_base(psp_, __VA_ARGS__); \
if (is_error(ret_)) { \
errno = -ret_; \
ret_ = -1; \
} \
ret_; \
})
#else #else