sparc: Fix restartable syscalls (BZ 32173)
The commit 'sparc: Use Linux kABI for syscall return'
(86c5d2cf0ce046279baddc7faa27da71f1a89fde) did not take into account
a subtle sparc syscall kABI constraint. For syscalls that might block
indefinitely, on an interrupt (like SIGCONT) the kernel will set the
instruction pointer to just before the syscall:
arch/sparc/kernel/signal_64.c
476 static void do_signal(struct pt_regs *regs, unsigned long orig_i0)
477 {
[...]
525 if (restart_syscall) {
526 switch (regs->u_regs[UREG_I0]) {
527 case ERESTARTNOHAND:
528 case ERESTARTSYS:
529 case ERESTARTNOINTR:
530 /* replay the system call when we are done */
531 regs->u_regs[UREG_I0] = orig_i0;
532 regs->tpc -= 4;
533 regs->tnpc -= 4;
534 pt_regs_clear_syscall(regs);
535 fallthrough;
536 case ERESTART_RESTARTBLOCK:
537 regs->u_regs[UREG_G1] = __NR_restart_syscall;
538 regs->tpc -= 4;
539 regs->tnpc -= 4;
540 pt_regs_clear_syscall(regs);
541 }
However, on a SIGCONT it seems that 'g1' register is being clobbered after the
syscall returns. Before 86c5d2cf0ce046279, the 'g1' was always placed jus
before the 'ta' instruction which then reloads the syscall number and restarts
the syscall.
On master, where 'g1' might be placed before 'ta':
$ cat test.c
#include <unistd.h>
int main ()
{
pause ();
}
$ gcc test.c -o test
$ strace -f ./t
[...]
ppoll(NULL, 0, NULL, NULL, 0
On another terminal
$ kill -STOP 2262828
$ strace -f ./t
[...]
--- SIGSTOP {si_signo=SIGSTOP, si_code=SI_USER, si_pid=2521813, si_uid=8289} ---
--- stopped by SIGSTOP ---
And then
$ kill -CONT 2262828
Results in:
--- SIGCONT {si_signo=SIGCONT, si_code=SI_USER, si_pid=2521813, si_uid=8289} ---
restart_syscall(<... resuming interrupted ppoll ...>) = -1 EINTR (Interrupted system call)
Where the expected behaviour would be:
$ strace -f ./t
[...]
ppoll(NULL, 0, NULL, NULL, 0) = ? ERESTARTNOHAND (To be restarted if no handler)
--- SIGSTOP {si_signo=SIGSTOP, si_code=SI_USER, si_pid=2521813, si_uid=8289} ---
--- stopped by SIGSTOP ---
--- SIGCONT {si_signo=SIGCONT, si_code=SI_USER, si_pid=2521813, si_uid=8289} ---
ppoll(NULL, 0, NULL, NULL, 0
Just moving the 'g1' setting near the syscall asm is not suffice,
the compiler might optimize it away (as I saw on cancellation.c by
trying this fix). Instead, I have change the inline asm to put the
'g1' setup in ithe asm block. This would require to change the asm
constraint for INTERNAL_SYSCALL_NCS, since the syscall number is not
constant.
Checked on sparc64-linux-gnu.
Reported-by: René Rebe <rene@exactcode.de>
Tested-by: Sam James <sam@gentoo.org>
Reviewed-by: Sam James <sam@gentoo.org>
2024-09-13 14:11:56 +00:00
|
|
|
/* Test if a syscall is correctly restarted.
|
|
|
|
Copyright (C) 2024 Free Software Foundation, Inc.
|
|
|
|
This file is part of the GNU C Library.
|
|
|
|
|
|
|
|
The GNU C Library is free software; you can redistribute it and/or
|
|
|
|
modify it under the terms of the GNU Lesser General Public
|
|
|
|
License as published by the Free Software Foundation; either
|
|
|
|
version 2.1 of the License, or (at your option) any later version.
|
|
|
|
|
|
|
|
The GNU C Library is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
Lesser General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU Lesser General Public
|
|
|
|
License along with the GNU C Library; if not, see
|
|
|
|
<https://www.gnu.org/licenses/>. */
|
|
|
|
|
|
|
|
#include <support/xsignal.h>
|
|
|
|
#include <support/check.h>
|
|
|
|
#include <support/process_state.h>
|
|
|
|
#include <support/xunistd.h>
|
|
|
|
#include <support/xthread.h>
|
|
|
|
#include <sys/wait.h>
|
|
|
|
|
|
|
|
static int
|
|
|
|
check_pid (pid_t pid)
|
|
|
|
{
|
|
|
|
/* Wait until the child has called pause and it blocking on kernel. */
|
|
|
|
support_process_state_wait (pid, support_process_state_sleeping);
|
|
|
|
|
|
|
|
TEST_COMPARE (kill (pid, SIGSTOP), 0);
|
|
|
|
|
|
|
|
/* Adding process_state_tracing_stop ('t') allows the test to work under
|
|
|
|
trace programs such as ptrace. */
|
|
|
|
support_process_state_wait (pid, support_process_state_stopped
|
|
|
|
| support_process_state_tracing_stop);
|
|
|
|
|
|
|
|
TEST_COMPARE (kill (pid, SIGCONT), 0);
|
|
|
|
|
|
|
|
enum support_process_state state
|
|
|
|
= support_process_state_wait (pid, support_process_state_sleeping
|
|
|
|
| support_process_state_zombie);
|
|
|
|
|
|
|
|
TEST_COMPARE (state, support_process_state_sleeping);
|
|
|
|
|
|
|
|
TEST_COMPARE (kill (pid, SIGTERM), 0);
|
|
|
|
|
|
|
|
siginfo_t info;
|
|
|
|
TEST_COMPARE (waitid (P_PID, pid, &info, WEXITED), 0);
|
|
|
|
TEST_COMPARE (info.si_signo, SIGCHLD);
|
|
|
|
TEST_COMPARE (info.si_code, CLD_KILLED);
|
|
|
|
TEST_COMPARE (info.si_status, SIGTERM);
|
|
|
|
TEST_COMPARE (info.si_pid, pid);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void *
|
2024-10-18 11:48:22 +00:00
|
|
|
tf (void *closure)
|
sparc: Fix restartable syscalls (BZ 32173)
The commit 'sparc: Use Linux kABI for syscall return'
(86c5d2cf0ce046279baddc7faa27da71f1a89fde) did not take into account
a subtle sparc syscall kABI constraint. For syscalls that might block
indefinitely, on an interrupt (like SIGCONT) the kernel will set the
instruction pointer to just before the syscall:
arch/sparc/kernel/signal_64.c
476 static void do_signal(struct pt_regs *regs, unsigned long orig_i0)
477 {
[...]
525 if (restart_syscall) {
526 switch (regs->u_regs[UREG_I0]) {
527 case ERESTARTNOHAND:
528 case ERESTARTSYS:
529 case ERESTARTNOINTR:
530 /* replay the system call when we are done */
531 regs->u_regs[UREG_I0] = orig_i0;
532 regs->tpc -= 4;
533 regs->tnpc -= 4;
534 pt_regs_clear_syscall(regs);
535 fallthrough;
536 case ERESTART_RESTARTBLOCK:
537 regs->u_regs[UREG_G1] = __NR_restart_syscall;
538 regs->tpc -= 4;
539 regs->tnpc -= 4;
540 pt_regs_clear_syscall(regs);
541 }
However, on a SIGCONT it seems that 'g1' register is being clobbered after the
syscall returns. Before 86c5d2cf0ce046279, the 'g1' was always placed jus
before the 'ta' instruction which then reloads the syscall number and restarts
the syscall.
On master, where 'g1' might be placed before 'ta':
$ cat test.c
#include <unistd.h>
int main ()
{
pause ();
}
$ gcc test.c -o test
$ strace -f ./t
[...]
ppoll(NULL, 0, NULL, NULL, 0
On another terminal
$ kill -STOP 2262828
$ strace -f ./t
[...]
--- SIGSTOP {si_signo=SIGSTOP, si_code=SI_USER, si_pid=2521813, si_uid=8289} ---
--- stopped by SIGSTOP ---
And then
$ kill -CONT 2262828
Results in:
--- SIGCONT {si_signo=SIGCONT, si_code=SI_USER, si_pid=2521813, si_uid=8289} ---
restart_syscall(<... resuming interrupted ppoll ...>) = -1 EINTR (Interrupted system call)
Where the expected behaviour would be:
$ strace -f ./t
[...]
ppoll(NULL, 0, NULL, NULL, 0) = ? ERESTARTNOHAND (To be restarted if no handler)
--- SIGSTOP {si_signo=SIGSTOP, si_code=SI_USER, si_pid=2521813, si_uid=8289} ---
--- stopped by SIGSTOP ---
--- SIGCONT {si_signo=SIGCONT, si_code=SI_USER, si_pid=2521813, si_uid=8289} ---
ppoll(NULL, 0, NULL, NULL, 0
Just moving the 'g1' setting near the syscall asm is not suffice,
the compiler might optimize it away (as I saw on cancellation.c by
trying this fix). Instead, I have change the inline asm to put the
'g1' setup in ithe asm block. This would require to change the asm
constraint for INTERNAL_SYSCALL_NCS, since the syscall number is not
constant.
Checked on sparc64-linux-gnu.
Reported-by: René Rebe <rene@exactcode.de>
Tested-by: Sam James <sam@gentoo.org>
Reviewed-by: Sam James <sam@gentoo.org>
2024-09-13 14:11:56 +00:00
|
|
|
{
|
|
|
|
pause ();
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
child_mt (void)
|
|
|
|
{
|
|
|
|
/* Let only the created thread to handle signals. */
|
|
|
|
sigset_t set;
|
|
|
|
sigfillset (&set);
|
|
|
|
xpthread_sigmask (SIG_BLOCK, &set, NULL);
|
|
|
|
|
|
|
|
sigdelset (&set, SIGSTOP);
|
|
|
|
sigdelset (&set, SIGCONT);
|
|
|
|
sigdelset (&set, SIGTERM);
|
|
|
|
|
|
|
|
pthread_attr_t attr;
|
|
|
|
xpthread_attr_init (&attr);
|
|
|
|
TEST_COMPARE (pthread_attr_setsigmask_np (&attr, &set), 0);
|
|
|
|
|
|
|
|
xpthread_join (xpthread_create (&attr, tf, NULL));
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
do_test_syscall (bool multithread)
|
|
|
|
{
|
|
|
|
pid_t pid = xfork ();
|
|
|
|
if (pid == 0)
|
|
|
|
{
|
|
|
|
if (multithread)
|
|
|
|
child_mt ();
|
|
|
|
else
|
|
|
|
pause ();
|
|
|
|
_exit (127);
|
|
|
|
}
|
|
|
|
|
|
|
|
check_pid (pid);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
do_test (void)
|
|
|
|
{
|
|
|
|
/* Check for both single and multi thread, since they use different syscall
|
|
|
|
mechanisms. */
|
|
|
|
do_test_syscall (false);
|
|
|
|
do_test_syscall (true);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
#include <support/test-driver.c>
|