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
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 = \
$(interface-headers) \
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_regs sc_fpregs;
int sc_fpexcsr; /* FPSR including exception bits. */
struct i386_xfloat_state *xstate;
};
/* Traditional BSD names for some members. */

View File

@ -21,6 +21,8 @@
#include <stdlib.h>
#include <string.h>
#include <cpuid.h>
/* This is run on the thread stack after restoring it, to be able to
unlock SS off sigstack. */
static void
@ -123,10 +125,32 @@ __sigreturn (struct sigcontext *scp)
if (scp->sc_onstack)
ss->sigaltstack.ss_flags &= ~SS_ONSTACK;
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));
#ifdef i386_XFLOAT_STATE
if ((scp->xstate) && (scp->xstate->initialized))
{
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

View File

@ -26,7 +26,11 @@
#include "hurdfault.h"
#include <intr-msg.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. */
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 *sigsp;
struct sigcontext *scp;
vm_size_t xstate_size;
struct
{
union
@ -145,6 +150,14 @@ _hurd_setup_sighandler (struct hurd_sigstate *ss, const struct sigaction *action
struct hurd_userlink link;
ucontext_t ucontext;
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;
#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))
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.
We may need to reset the SP (the `uesp' slot) to avoid clobbering an
interrupted RPC frame. */
@ -196,14 +220,21 @@ _hurd_setup_sighandler (struct hurd_sigstate *ss, const struct sigaction *action
#endif
}
/* Push the arguments to call `trampoline' on the stack. */
sigsp -= sizeof (*stackframe);
#ifdef __x86_64__
/* Align SP at 16 bytes. Coupled with the fact that sigreturn_addr is
16-byte aligned within the stackframe struct, this ensures that it ends
up on a 16-byte aligned address, as required by the ABI. */
sigsp = (void *) ((uintptr_t) sigsp & ~15UL);
#endif
/* Push the arguments to call `trampoline' on the stack.
* The extended state might have a variable size depending on the platform,
* so we dynamically allocate it on the stack frame.*/
sigsp -= sizeof (*stackframe) + xstate_size;
/* Align SP at 64 bytes. This is needed for two reasons:
* - sigreturn_addr is 16-byte aligned within the stackframe
* 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;
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,
&state->basic, sizeof (state->basic));
/* 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));
scp->xstate = NULL;
#ifdef i386_XFLOAT_STATE
if (xstate_size > 0)
{
mach_msg_type_number_t got = (xstate_size / sizeof (int));
ok = (! __thread_get_state (ss->thread, i386_XFLOAT_STATE,
(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. */
stackframe->signo = signo;

View File

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

View File

@ -20,6 +20,8 @@
#include <hurd/msg.h>
#include <stdlib.h>
#include <cpuid.h>
/* This is run on the thread stack after restoring it, to be able to
unlock SS off sigstack. */
void
@ -116,10 +118,32 @@ __sigreturn (struct sigcontext *scp)
if (scp->sc_onstack)
ss->sigaltstack.ss_flags &= ~SS_ONSTACK;
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));
#ifdef i386_XFLOAT_STATE
if ((scp->xstate) && (scp->xstate->initialized))
{
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
altstack (by unlocking sigstate). Note that unless an altstack is used,