On 21/01/2026 19:13, Samuel Thibault wrote:
Michael Kelly, le mer. 21 janv. 2026 19:01:02 +0000, a ecrit:
Both my test code and the same stress-ng invocation work correctly on 32 bit
without any error reports.
Ok, so we can probably focus on x86_64-only code.

This bug in the signal handling code does actually affect hurd-i386 as well. Samuel mentioned that there are fewer uses of the sse instructions and registers within 32 bit code. Such instructions are certainly used within many of the 64 bit Hurd RPC implementations but seemingly not in those of 32 bit which accounts for why the problem hasn't been seen using my stress-ng test case on 32 bit. There is however the general possibility that a signal handler has code that modifies xmm registers. In that case the same situation could arise in user code where the xmm register values might change unaccountably as a result of the signal processing. I have attached a revised test case that fails whilst running on 32 or 64 bit when sufficient instances (~6) of the program are run concurrently.

The problem actually arises with the following sequence:

1) Signal message received by the signal thread, sets up signal handler stack and 'struct sigcontext' for the chosen thread which is then resumed to handle the signal.

2) 2nd signal is received by the signal thread and chooses the same thread as 1) for handling. The associated 'struct hurd_sigstate' cannot handle the signal immediately so 'ss->pending' is updated to include the signal (SIGALRM).

3) When the signal handling in 1) completes, __sigreturn spots the pending signal and triggers the signal handling code again through the call to __msg_sig_post with signal 0 as a special case to post_all_pending_signals(). This runs in the current thread.

4) The signal handler stack is set up using the same code as in 1) but in this instance there is now a pre-existing ss->context within _hurd_setup_sighandler in trampoline.c. The code however unconditionally refetches the floating point context via thread_get_state and overwrites the original state preserved during handling of the first signal.

I was able to fix the testcases and indeed the stress-ng error reports by not refetching the floating point state at times when 'ss->context' wasn't NULL on entry. If this seems a viable method for solving this issue then I can tidy up what I've done and submit a patch for review.

The signal handling code seems very tricky to me and there's more than a chance that I've misunderstood or misinterpreted parts of it. I don't fully understand how the call to  __msg_sig_post() works. The comment after the call to this within sigreturn.c (x86_64) describes how the call doesn't return in the case that a signal was handled and that is indeed true. I'm guessing that somehow the __mach_msg starts waiting for a reply but then gets suspended and the stack adjusted to run the handler again. It all seems exceedingly complex.

Regards,

Mike.

#include <errno.h>
#include <error.h>
#include <pthread.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#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)

static void
alarm_handler(int signum)
{
  uint8_t wbuf[XSTATE_BUFFER_SIZE];
  uint8_t val = 0x32;

  for (int i = 0; i < XSTATE_BUFFER_SIZE; i++)
    wbuf[i] = val++;

  SET_XSTATE(wbuf);
}

static void*
sigraise_func(void*)
{
  sigset_t set;

  sigemptyset(&set);
  sigaddset(&set, SIGALRM);
  int err = pthread_sigmask(SIG_BLOCK, &set, NULL);
  if (err)
    error(1, err, "pthread_sigmask");

  while (1)
    {
      usleep(100);
      kill(getpid(), SIGALRM);
    }

  return NULL;
}

int
main()
{
  struct sigaction action;
  action.sa_handler = alarm_handler;
  if (sigemptyset(&action.sa_mask))
    error(1, errno, "sigemptyset");
  action.sa_flags = 0;

  if (sigaction(SIGALRM, &action, NULL))
    error(1, errno, "sigaction");

  pthread_t sigraise_thread;
  int err = pthread_create(&sigraise_thread, NULL, sigraise_func, NULL);
  if (err)
    error(1, err, "pthread_create");

  unsigned long iters = 0;
  
  while (1)
    {
      uint8_t rbuf[XSTATE_BUFFER_SIZE];
      uint8_t wbuf[XSTATE_BUFFER_SIZE];
      uint8_t val = 0x23;

      for (int i = 0; i < XSTATE_BUFFER_SIZE; i++)
	wbuf[i] = val++;

      SET_XSTATE(wbuf);
      GET_XSTATE(rbuf);

      for (int i = 0; i < XSTATE_BUFFER_SIZE; i++)
	{
	  if (rbuf[i] != wbuf[i])
	    fprintf(stderr, "%ld: xmm0 comparison failed: %x: %x\n", iters, rbuf[i], wbuf[i]);
	}

      iters++;
    }

  return 0;
}

Reply via email to