hurd: save xstate during signal handling

* hurd/Makefile: add new tests
* hurd/test-sig-rpc-interrupted.c: check xstate save and restore in
  the case where a signal is delivered to a thread which is waiting
  for an rpc. This test implements the rpc interruption protocol used
  by the hurd servers. It was so far passing on Debian thanks to the
  local-intr-msg-clobber.diff patch, which is now obsolete.
* hurd/test-sig-xstate.c: check xstate save and restore in the case
  where a signal is delivered to a running thread, making sure that
  the xstate is modified in the signal handler.
* hurd/test-xstate.h: add helpers to test xstate
* sysdeps/mach/hurd/i386/bits/sigcontext.h: add xstate to the
  sigcontext structure.
+ sysdeps/mach/hurd/i386/sigreturn.c: restore xstate from the saved
  context
* sysdeps/mach/hurd/x86/trampoline.c: save xstate if
  supported. Otherwise we fall back to the previous behaviour of
  ignoring xstate.
* sysdeps/mach/hurd/x86_64/bits/sigcontext.h: add xstate to the
  sigcontext structure.
* sysdeps/mach/hurd/x86_64/sigreturn.c: restore xstate from the saved
  context

Signed-off-by: Luca Dariz <luca@orpolo.org>
Signed-off-by: Samuel Thibault <samuel.thibault@ens-lyon.org>
Message-ID: <20250319171118.142163-1-luca@orpolo.org>
This commit is contained in:
Luca Dariz 2025-03-19 18:11:18 +01:00 committed by Samuel Thibault
parent e150ee8709
commit 6d6a6e2dd2
9 changed files with 458 additions and 25 deletions

View File

@ -19,6 +19,11 @@ subdir := hurd
include ../Makeconfig include ../Makeconfig
tests := test-sig-xstate \
test-sig-rpc-interrupted
$(objpfx)test-sig-xstate: $(shared-thread-library)
$(objpfx)test-sig-rpc-interrupted: $(shared-thread-library) $(objdir)/hurd/libhurduser.so
headers = \ headers = \
$(interface-headers) \ $(interface-headers) \
hurd.h \ hurd.h \

View File

@ -0,0 +1,185 @@
/* Test the state save/restore procedures during signal handling when an
interruptible RPC is 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 <assert.h>
#include <pthread.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <mach/message.h>
#include <mach/gnumach.h>
#include <mach/mach_traps.h>
#include <mach/mig_errors.h>
#include <mach-shortcuts.h>
#include <mach_init.h>
#include <hurd/io.h>
#include <hurd/io_reply.h>
#include <support/check.h>
#include <support/xthread.h>
#include "test-xstate.h"
void handler (int signum, siginfo_t *info, void *context)
{
printf ("signal %d setting a different CPU state\n", signum);
char buf3[XSTATE_BUFFER_SIZE];
memset (buf3, 0x77, XSTATE_BUFFER_SIZE);
SET_XSTATE (buf3);
}
static const mach_msg_type_t RetCodeCheck = {
.msgt_name = (unsigned char) MACH_MSG_TYPE_INTEGER_32,
.msgt_size = 32,
.msgt_number = 1,
.msgt_inline = TRUE,
.msgt_longform = FALSE,
.msgt_deallocate = FALSE,
.msgt_unused = 0
};
/* Helper thread to simulate a proper RPC interruption during dignal handling */
void* fake_interruptor (void *arg)
{
int err;
sigset_t ss;
TEST_COMPARE (sigemptyset (&ss), 0);
TEST_COMPARE (sigaddset (&ss, SIGUSR1), 0);
TEST_COMPARE (sigprocmask (SIG_BLOCK, &ss, NULL), 0);
struct {
mach_msg_header_t Head;
} request;
mach_port_t rxport = *((mach_port_t*)arg);
err = mach_msg (&request.Head, MACH_RCV_MSG, 0, sizeof (request), rxport,
MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
TEST_COMPARE (err, MACH_MSG_SUCCESS);
TEST_COMPARE (request.Head.msgh_bits, 0x1112);
TEST_COMPARE (request.Head.msgh_size, sizeof (request.Head));
TEST_COMPARE (request.Head.msgh_id, 33000);
mig_reply_header_t reply;
reply.Head = request.Head;
reply.Head.msgh_id += 100;
reply.RetCodeType = RetCodeCheck;
reply.RetCode = KERN_SUCCESS;
err = mach_msg (&reply.Head, MACH_SEND_MSG, sizeof (reply), 0, MACH_PORT_NULL,
MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
TEST_COMPARE (err, MACH_MSG_SUCCESS);
return NULL;
}
/* Helper thread to send a signal to the main thread in the middle of
* an interruptible rpc */
void* signal_sender (void *arg)
{
int err;
sigset_t ss;
TEST_COMPARE (sigemptyset (&ss), 0);
TEST_COMPARE (sigaddset (&ss, SIGUSR1), 0);
TEST_COMPARE (sigprocmask (SIG_BLOCK, &ss, NULL), 0);
/* Receive the first request, we won't answer to this. */
struct {
mach_msg_header_t head;
char data[64];
} m1, m2;
mach_port_t rxport = *((mach_port_t*)arg);
memset (&m1, 0, sizeof (m1));
memset (&m2, 0, sizeof (m2));
err = mach_msg (&m1.head, MACH_RCV_MSG, 0, sizeof (m1), rxport,
MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
TEST_COMPARE (err, MACH_MSG_SUCCESS);
/* interrupt the ongoing rpc with a signal, using the
* interruptible rpc protocol */
pthread_t thintr = xpthread_create (NULL, fake_interruptor, arg);
TEST_COMPARE (kill (getpid (), SIGUSR1), 0);
xpthread_join (thintr);
/* Complete the interruption by sending EINTR */
mig_reply_header_t reply;
reply.Head = m1.head;
reply.Head.msgh_id += 100;
reply.RetCodeType = RetCodeCheck;
reply.RetCode = EINTR;
err = mach_msg (&reply.Head, MACH_SEND_MSG, sizeof (reply), 0, MACH_PORT_NULL,
MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
TEST_COMPARE (err, MACH_MSG_SUCCESS);
/* Receive the retried rpc, and check that it has the same payload
* as the first one. Port names might still be different. */
err = mach_msg (&m2.head, MACH_RCV_MSG, 0, sizeof (m2), rxport,
MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
TEST_COMPARE (m1.head.msgh_bits, m2.head.msgh_bits);
TEST_COMPARE (m1.head.msgh_size, m2.head.msgh_size);
TEST_COMPARE (m1.head.msgh_id, m2.head.msgh_id);
TEST_COMPARE_BLOB (m1.data, sizeof (m1.data), m2.data, sizeof (m2.data));
/* And finally make the rpc succeed by sending a valid reply */
err = io_read_reply (m2.head.msgh_remote_port, MACH_MSG_TYPE_MOVE_SEND_ONCE,
KERN_SUCCESS, NULL, 0);
TEST_COMPARE (err, MACH_MSG_SUCCESS);
return NULL;
}
static int do_test (void)
{
#if ! XSTATE_HELPERS_SUPPORTED
FAIL_UNSUPPORTED ("Test not supported on this arch.");
#endif
/* Setup signal handling; we need to handle the signal in the main
* thread, the other ones will explicitely block SIGUSR1. */
struct sigaction act = { 0 };
act.sa_flags = SA_RESTART;
act.sa_sigaction = &handler;
TEST_COMPARE (sigaction (SIGUSR1, &act, NULL), 0);
mach_port_t fakeio;
int err;
err = mach_port_allocate (mach_task_self (), MACH_PORT_RIGHT_RECEIVE, &fakeio);
TEST_COMPARE (err, MACH_MSG_SUCCESS);
err = mach_port_insert_right (mach_task_self (), fakeio, fakeio,
MACH_MSG_TYPE_MAKE_SEND);
TEST_COMPARE (err, MACH_MSG_SUCCESS);
pthread_t thsender = xpthread_create (NULL, signal_sender, &fakeio);
char *buf;
mach_msg_type_number_t n;
TEST_COMPARE (io_read (fakeio, &buf, &n, 1, 2), 0);
xpthread_join (thsender);
return EXIT_SUCCESS;
}
#include <support/test-driver.c>

94
hurd/test-sig-xstate.c Normal file
View File

@ -0,0 +1,94 @@
/* Test the state save/restore procedures during signal handling.
Copyright (C) 2025 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 <assert.h>
#include <pthread.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <mach/message.h>
#include <mach/gnumach.h>
#include <mach/mach_traps.h>
#include <mach-shortcuts.h>
#include <mach_init.h>
#include <hurd/io.h>
#include <hurd/io_reply.h>
#include <support/check.h>
#include <support/xthread.h>
#include "test-xstate.h"
static volatile bool loopflag = true;
void handler (int signum, siginfo_t *info, void *context)
{
char buf3[XSTATE_BUFFER_SIZE];
memset (buf3, 0x77, XSTATE_BUFFER_SIZE);
SET_XSTATE (buf3);
printf ("signal %d setting a different CPU state\n", signum);
loopflag = false;
}
/* Helper thread to send a signal to the main thread */
void* signal_sender (void *arg)
{
sigset_t ss;
assert (! sigemptyset (&ss));
assert (! sigaddset (&ss, SIGUSR1));
assert (! sigprocmask (SIG_BLOCK, &ss, NULL));
TEST_COMPARE (kill (getpid (), SIGUSR1), 0);
return NULL;
}
static int do_test (void)
{
#if ! XSTATE_HELPERS_SUPPORTED
FAIL_UNSUPPORTED ("Test not supported on this arch.");
#endif
struct sigaction act = { 0 };
act.sa_sigaction = &handler;
TEST_COMPARE (sigaction (SIGUSR1, &act, NULL), 0);
pthread_t thsender = xpthread_create (NULL, signal_sender, NULL);
char buf1[XSTATE_BUFFER_SIZE], buf2[XSTATE_BUFFER_SIZE];
memset (buf1, 0x33, XSTATE_BUFFER_SIZE);
SET_XSTATE (buf1);
while (loopflag)
;
GET_XSTATE (buf2);
TEST_COMPARE_BLOB (buf1, sizeof (buf1), buf2, sizeof (buf2));
xpthread_join (thsender);
return EXIT_SUCCESS;
}
#include <support/test-driver.c>

40
hurd/test-xstate.h Normal file
View File

@ -0,0 +1,40 @@
/* Helpers to test XSTATE during signal handling
Copyright (C) 2025 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/>. */
#ifndef _TEST_XSTATE_H
#define _TEST_XSTATE_H
#if defined __x86_64__ || defined __i386__
#define XSTATE_HELPERS_SUPPORTED 1
#define XSTATE_BUFFER_SIZE 16
#define SET_XSTATE(b) do { \
asm volatile ("movups (%0),%%xmm0" :: "r" (b)); \
} while (0)
#define GET_XSTATE(b) do { \
asm volatile ("movups %%xmm0,(%0)" :: "r" (b)); \
} while (0)
#else
#define XSTATE_HELPERS_SUPPORTED 0
#define XSTATE_BUFFER_SIZE 1
#define SET_XSTATE(b)
#endif
#endif /* _TEST_XSTATE_H */

View File

@ -88,6 +88,8 @@ struct sigcontext
struct i386_fp_save sc_fpsave; struct i386_fp_save sc_fpsave;
struct i386_fp_regs sc_fpregs; struct i386_fp_regs sc_fpregs;
int sc_fpexcsr; /* FPSR including exception bits. */ int sc_fpexcsr; /* FPSR including exception bits. */
struct i386_xfloat_state *xstate;
}; };
/* Traditional BSD names for some members. */ /* Traditional BSD names for some members. */

View File

@ -21,6 +21,8 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <cpuid.h>
/* This is run on the thread stack after restoring it, to be able to /* This is run on the thread stack after restoring it, to be able to
unlock SS off sigstack. */ unlock SS off sigstack. */
static void static void
@ -123,10 +125,32 @@ __sigreturn (struct sigcontext *scp)
if (scp->sc_onstack) if (scp->sc_onstack)
ss->sigaltstack.ss_flags &= ~SS_ONSTACK; ss->sigaltstack.ss_flags &= ~SS_ONSTACK;
if (scp->sc_fpused) #ifdef i386_XFLOAT_STATE
/* Restore the FPU state. Mach conveniently stores the state if ((scp->xstate) && (scp->xstate->initialized))
in the format the i387 `frstor' instruction uses to restore it. */ {
asm volatile ("frstor %0" : : "m" (scp->sc_fpsave)); unsigned eax, ebx, ecx, edx;
__cpuid_count(0xd, 0, eax, ebx, ecx, edx);
switch (scp->xstate->fp_save_kind)
{
case 0: // FNSAVE
asm volatile("frstor %0" : : "m" (scp->xstate->hw_state));
break;
case 1: // FXSAVE
asm volatile("fxrstor %0" : : "m" (scp->xstate->hw_state), \
"a" (eax), "d" (edx));
break;
default: // XSAVE, XSAVEOPT, XSAVEC, XSAVES
asm volatile("xrstor %0" : : "m" (scp->xstate->hw_state), \
"a" (eax), "d" (edx));
break;
}
}
else
#endif
if (scp->sc_fpused)
/* Restore the FPU state. Mach conveniently stores the state
in the format the i387 `frstor' instruction uses to restore it. */
asm volatile ("frstor %0" : : "m" (scp->sc_fpsave));
{ {
/* There are convenient instructions to pop state off the stack, so we /* There are convenient instructions to pop state off the stack, so we

View File

@ -26,7 +26,11 @@
#include "hurdfault.h" #include "hurdfault.h"
#include <intr-msg.h> #include <intr-msg.h>
#include <sys/ucontext.h> #include <sys/ucontext.h>
#ifdef __x86_64__
#include <mach/x86_64/mach_i386.h>
#else
#include <mach/i386/mach_i386.h>
#endif
/* Fill in a siginfo_t structure for SA_SIGINFO-enabled handlers. */ /* Fill in a siginfo_t structure for SA_SIGINFO-enabled handlers. */
static void fill_siginfo (siginfo_t *si, int signo, static void fill_siginfo (siginfo_t *si, int signo,
@ -106,6 +110,7 @@ _hurd_setup_sighandler (struct hurd_sigstate *ss, const struct sigaction *action
void firewall (void); void firewall (void);
void *sigsp; void *sigsp;
struct sigcontext *scp; struct sigcontext *scp;
vm_size_t xstate_size;
struct struct
{ {
union union
@ -145,6 +150,14 @@ _hurd_setup_sighandler (struct hurd_sigstate *ss, const struct sigaction *action
struct hurd_userlink link; struct hurd_userlink link;
ucontext_t ucontext; ucontext_t ucontext;
siginfo_t siginfo; siginfo_t siginfo;
#ifdef __x86_64__
char _pad2[56];
#else
char _pad2[20];
#endif
char xstate[];
/* Don't add anything after xstate, as it's dynamically
sized. */
} *stackframe; } *stackframe;
#ifdef __x86_64__ #ifdef __x86_64__
@ -170,6 +183,17 @@ _hurd_setup_sighandler (struct hurd_sigstate *ss, const struct sigaction *action
if (! machine_get_basic_state (ss->thread, state)) if (! machine_get_basic_state (ss->thread, state))
return NULL; return NULL;
/* Initialize the size of the CPU extended state, to be saved during
* signal handling */
#ifdef i386_XFLOAT_STATE
_Static_assert ((sizeof(*stackframe) + sizeof(struct i386_xfloat_state)) % 64 == 0,
"stackframe size must be multiple of 64-byte minus "
"sizeof(struct i386_xfloat_state), please adjust _pad2");
if (__i386_get_xstate_size(__mach_host_self(), &xstate_size))
#endif
xstate_size = 0;
/* Save the original SP in the gratuitous `esp' slot. /* Save the original SP in the gratuitous `esp' slot.
We may need to reset the SP (the `uesp' slot) to avoid clobbering an We may need to reset the SP (the `uesp' slot) to avoid clobbering an
interrupted RPC frame. */ interrupted RPC frame. */
@ -196,14 +220,21 @@ _hurd_setup_sighandler (struct hurd_sigstate *ss, const struct sigaction *action
#endif #endif
} }
/* Push the arguments to call `trampoline' on the stack. */ /* Push the arguments to call `trampoline' on the stack.
sigsp -= sizeof (*stackframe); * The extended state might have a variable size depending on the platform,
#ifdef __x86_64__ * so we dynamically allocate it on the stack frame.*/
/* Align SP at 16 bytes. Coupled with the fact that sigreturn_addr is sigsp -= sizeof (*stackframe) + xstate_size;
16-byte aligned within the stackframe struct, this ensures that it ends
up on a 16-byte aligned address, as required by the ABI. */ /* Align SP at 64 bytes. This is needed for two reasons:
sigsp = (void *) ((uintptr_t) sigsp & ~15UL); * - sigreturn_addr is 16-byte aligned within the stackframe
#endif * struct, and this ensures that it ends up on a 16-byte aligned
* address, as required by the ABI.
* - the XSAVE state needs to be aligned at 64 bytes (on both i386 and
* x86_64), so we align the stackframe also at 64 bytes and add the
* required padding at the end, see the _pad2 field.
*/
sigsp = (void *) ((uintptr_t) sigsp & ~63UL);
stackframe = sigsp; stackframe = sigsp;
if (_hurdsig_catch_memory_fault (stackframe)) if (_hurdsig_catch_memory_fault (stackframe))
@ -248,14 +279,40 @@ _hurd_setup_sighandler (struct hurd_sigstate *ss, const struct sigaction *action
memcpy (&scp->sc_i386_thread_state, memcpy (&scp->sc_i386_thread_state,
&state->basic, sizeof (state->basic)); &state->basic, sizeof (state->basic));
/* struct sigcontext is laid out so that starting at sc_fpkind mimics scp->xstate = NULL;
a struct i386_float_state. */ #ifdef i386_XFLOAT_STATE
_Static_assert (offsetof (struct sigcontext, sc_i386_float_state) if (xstate_size > 0)
% __alignof__ (struct i386_float_state) == 0, {
"sc_i386_float_state layout mismatch"); mach_msg_type_number_t got = (xstate_size / sizeof (int));
ok = machine_get_state (ss->thread, state, i386_FLOAT_STATE,
&state->fpu, &scp->sc_i386_float_state, ok = (! __thread_get_state (ss->thread, i386_XFLOAT_STATE,
sizeof (state->fpu)); (thread_state_t) stackframe->xstate, &got)
&& got == (xstate_size / sizeof (int)));
if (((struct i386_xfloat_state*) stackframe->xstate)->fp_save_kind > 5)
/* We support up to XSAVES */
ok = 0;
if (ok)
{
scp->xstate = (struct i386_xfloat_state*) stackframe->xstate;
assert((uintptr_t)scp->xstate->hw_state % 64 == 0);
}
}
else
#endif
ok = 0;
if (!ok)
{
/* struct sigcontext is laid out so that starting at sc_fpkind mimics
a struct i386_float_state. */
_Static_assert (offsetof (struct sigcontext, sc_i386_float_state)
% __alignof__ (struct i386_float_state) == 0,
"sc_i386_float_state layout mismatch");
ok = machine_get_state (ss->thread, state, i386_FLOAT_STATE,
&state->fpu, &scp->sc_i386_float_state,
sizeof (state->fpu));
}
/* Set up the arguments for the signal handler. */ /* Set up the arguments for the signal handler. */
stackframe->signo = signo; stackframe->signo = signo;

View File

@ -96,6 +96,8 @@ struct sigcontext
struct i386_fp_save sc_fpsave; struct i386_fp_save sc_fpsave;
struct i386_fp_regs sc_fpregs; struct i386_fp_regs sc_fpregs;
int sc_fpexcsr; /* FPSR including exception bits. */ int sc_fpexcsr; /* FPSR including exception bits. */
struct i386_xfloat_state *xstate;
}; };
/* Traditional BSD names for some members. */ /* Traditional BSD names for some members. */

View File

@ -20,6 +20,8 @@
#include <hurd/msg.h> #include <hurd/msg.h>
#include <stdlib.h> #include <stdlib.h>
#include <cpuid.h>
/* This is run on the thread stack after restoring it, to be able to /* This is run on the thread stack after restoring it, to be able to
unlock SS off sigstack. */ unlock SS off sigstack. */
void void
@ -116,10 +118,32 @@ __sigreturn (struct sigcontext *scp)
if (scp->sc_onstack) if (scp->sc_onstack)
ss->sigaltstack.ss_flags &= ~SS_ONSTACK; ss->sigaltstack.ss_flags &= ~SS_ONSTACK;
if (scp->sc_fpused) #ifdef i386_XFLOAT_STATE
/* Restore the FPU state. Mach conveniently stores the state if ((scp->xstate) && (scp->xstate->initialized))
in the format the i387 `frstor' instruction uses to restore it. */ {
asm volatile ("frstor %0" : : "m" (scp->sc_fpsave)); unsigned eax, ebx, ecx, edx;
__cpuid_count(0xd, 0, eax, ebx, ecx, edx);
switch (scp->xstate->fp_save_kind)
{
case 0: // FNSAVE
asm volatile("frstor %0" : : "m" (scp->xstate->hw_state));
break;
case 1: // FXSAVE
asm volatile("fxrstor %0" : : "m" (scp->xstate->hw_state), \
"a" (eax), "d" (edx));
break;
default: // XSAVE, XSAVEOPT, XSAVEC, XSAVES
asm volatile("xrstor %0" : : "m" (scp->xstate->hw_state), \
"a" (eax), "d" (edx));
break;
}
}
else
#endif
if (scp->sc_fpused)
/* Restore the FPU state. Mach conveniently stores the state
in the format the i387 `frstor' instruction uses to restore it. */
asm volatile ("frstor %0" : : "m" (scp->sc_fpsave));
/* Copy the registers onto the user's stack, to be able to release the /* Copy the registers onto the user's stack, to be able to release the
altstack (by unlocking sigstate). Note that unless an altstack is used, altstack (by unlocking sigstate). Note that unless an altstack is used,