1

Context

I'm trying to change the FPU rounding mode behaviour in a running process, using a signal handler. Although I would prefer a portable solution, the platform I'm targetting in priority is Linux/x86.

Using fesetround() seemed like a good solution, except it doesn't have any effect after the signal handler returns. As far as I understand, at each context change, the Linux kernel saves the hardware context of the currently running process, and restores the context of the process scheduled to run.

Although this context handling normally happens in kernel space, I understand from various resources that in the case of signals handling, this context is stored in user space and can be accessed. I tried to use this to modify the FPU registers and set the desired rounding mode.

Attempted solution & problems

Below is the code I use to do this. init() is called at the beginning of the program to setup the signal handler, and a USR1 signal is sent to trigger the rounding mode switch.

All this kind of works insofar as calling fegetround() after the signal handler execution correctly returns FE_TOWARDZERO. However, computation results don't seem to be affected.

Calling fesetround( fegetround() ) after the signal handler execution however seems to alter the FPU configuration and causes computation results to actually change. All this seems to hint at another layer indirection, as though I correctly altered the hardware context, but it isn't coherent with the actual FPU state.

Question

Can anybody explain why this isn't working and possibly what could be attempted to correct the problem?


#include <signal.h>
#include <fenv.h>
#pragma STDC FENV_ACCESS ON

void changeRoundingMode (ucontext_t * ucontext)
{
  /* This doesn't seem to work since the hardware context is restored
     to its old value immediately after the signal handler return */
  fesetround (FE_TOWARDZERO);


  /* try and modify the context */
  {
    /* http://www.website.masmforum.com/tutorials/fptute/fpuchap1.htm#cword
       Rounding mode is encoded in bits 10 & 11 (RC = Rounding Control field) */
    __uint16_t cwd = ucontext->uc_mcontext.fpregs->cwd;

    /* Clear bits 10 & 11 */
    const __uint16_t clearRC = 0xf3ff;
    cwd &= clearRC;

    /* And set them to the desired value
         11 -> TOWARDZERO in this case */
    cwd += 0x0c00;

    ucontext->uc_mcontext.fpregs->cwd = cwd;
  }
}

void signalHandler (int signum, siginfo_t * siginfo, void * ucontext)
{
  changeRoundingMode (ucontext);
}

void init ()
{
  struct sigaction new_action, old_action;

  new_action.sa_sigaction = verrou_signalHandler;
  sigemptyset (&new_action.sa_mask);
  new_action.sa_flags = SA_RESTART | SA_SIGINFO;

  sigaction (SIGUSR1, 0, &old_action);
  if (old_action.sa_handler != SIG_IGN)
    sigaction (SIGUSR1, &new_action, 0);
}
Community
  • 1
  • 1
François Févotte
  • 19,520
  • 4
  • 51
  • 74
  • 2
    In general, the only things safe to do in a signal handler are a) aborting the program and b) setting a volatile flag of type sig_atomic_t. Anything else will potentially fatally compromise your program (maybe in a library you called). – Deduplicator Apr 02 '14 at 16:52
  • Yes, I know this. I'm trying to do this to instrument programs I have not written myself. I possibly don't even have the source code to recompile them; all I want to do is periodically change the rounding mode to see whether this badly affects the results of said programs. Aborting the program doesn't help me with this, and setting a volatile flag can only be of interest if I have access to other parts of the code which use it. – François Févotte Apr 02 '14 at 16:57
  • How are you installing your signal handler in the program for which you don't have source code? – Peter Apr 02 '14 at 20:00
  • I use `LD_PRELOAD` to load my code into the process, and then use a debugger (`gdb` in my case) to set up a breakpoint in `main()` and call my `init()` function from there. – François Févotte Apr 03 '14 at 05:29

0 Answers0