[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#868902: Floating-point exception happens when exception is disabled



Package: linux
Version: 4.9.30-2+deb9u2


Bug description:

While testing glibc on a powerpc64le machine with debian stretch, I
noticed that a test case (from the glibc test case suite) fails
intermittently. The test case in glibc [1] is rather long, so I reduced it
to a minimum that still reproduces the problem and I attached it to this
bug report (see fenv.c).


Steps to reproduce:

With the attached test case, there's no need to build the whole of glibc
(which would be needed to test the original test case). So, in order to
reproduce this bug, I just compiled the attached test case with:

$ gcc fenv.c -O0 -lm -o fenv

Since the test case only fails intermittently, it has to be executed many
times before the failure can be seen.  On my machine, I used the following
command:

$ while [ 1 ]; do \
    ./fenv &> ./fenv.log; \
    if [ $? -ne 0 ]; then break; fi; \
  done

This command stops when the test case fails.  The log file, fenv.log
(also attached), can be examined for the error message, which will contain
an error message similar to the following snippet:

  Test: after fedisableexcept (FE_UNDERFLOW) processes will not abort
        when feraiseexcept (FE_UNDERFLOW) is called.
    Fail: Process exited abnormally with status 8.


Reproducing with upstream sources:

I reproduced this bug with the kernel provided by debian stretch (as
reported in the pseudo-header), but I also reproduced it with other
releases from kernel.org:

v4.10 (release tag)
v4.11 (release tag)
v4.12-rc4 (tag)

Since the 5th release candidate for 4.12, the test case miraculously
healed - ;) - and I tracked it down to a commit [2] by Breno Leitao,
Gustavo Romero, Anton Blanchard, and Michael Ellerman.

This problem does not reproduce with v4.12.


Backporting:

I backported (only locally) the patch [2] to v4.10, and it indeed fixes the
problem.


Additional information:

My system is running as a KVM guest with Debian Stretch on a powerpc64le
host machine.  The output of the following commands might be useful:

$ uname -a
Linux debian 4.9.0-3-powerpc64le #1 SMP Debian 4.9.30-2+deb9u2 (2017-06-26) ppc64le GNU/Linux

$ /lib/powerpc64le-linux-gnu/libc.so.6 
GNU C Library (Debian GLIBC 2.24-11) stable release version 2.24, by Roland McGrath et al.
Copyright (C) 2016 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
Compiled by GNU CC version 6.3.0 20170516.
Available extensions:
	crypt add-on version 2.1 by Michael Glad and others
	GNU Libidn by Simon Josefsson
	Native POSIX Threads Library by Ulrich Drepper et al
	BIND-8.2.3-T5B
libc ABIs: UNIQUE IFUNC


References:

[1]
https://sourceware.org/git/?p=glibc.git;a=blob;f=math/test-fenv.c;h=b24b3a1e31ad0769c54d10e2eb0f230eaa7662d6;hb=HEAD

[2]
https://git.kernel.org/pub/scm/linux/kernel/git/powerpc/linux.git/commit/?id=1195892c091a15cc862f4e202482a3
/* Copyright (C) 1997-2017 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
   <http://www.gnu.org/licenses/>.  */

#ifndef _GNU_SOURCE
# define _GNU_SOURCE
#endif

#include <complex.h>
#include <math.h>
#include <float.h>
#include <fenv.h>

#include <errno.h>
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/resource.h>

static int count_errors;

#if FE_ALL_EXCEPT
/* Test that program aborts with no masked interrupts */
static void
feexcp_nomask_test (const char *flag_name, int fe_exc)
{
  int status;
  pid_t pid;

  if (feenableexcept (fe_exc) == -1)
    {
      printf ("Test: not testing feenableexcept, it isn't implemented.\n");
      return;
    }

  printf ("Test: after feenableexcept (%s) processes will abort\n",
	  flag_name);
  printf ("      when feraiseexcept (%s) is called.\n", flag_name);
  pid = fork ();
  if (pid == 0)
    {
      fedisableexcept (FE_ALL_EXCEPT);
      feenableexcept (fe_exc);
      feraiseexcept (fe_exc);
      exit (2);
    }
  else if (pid < 0)
    {
      if (errno != ENOSYS)
	{
	  printf ("  Fail: Could not fork.\n");
	  ++count_errors;
	}
      else
	printf ("  `fork' not implemented, test ignored.\n");
    }
  else {
    if (waitpid (pid, &status, 0) != pid)
      {
	printf ("  Fail: waitpid call failed.\n");
	++count_errors;
      }
    else if (WIFSIGNALED (status) && WTERMSIG (status) == SIGFPE)
      printf ("  Pass: Process received SIGFPE.\n");
    else
      {
	printf ("  Fail: Process didn't receive signal and exited with status %d.\n",
		status);
	++count_errors;
      }
  }
}

/* Test that program doesn't abort with exception.  */
static void
feexcp_mask_test (const char *flag_name, int fe_exc)
{
  int status;
  int exception;
  pid_t pid;

  printf ("Test: after fedisableexcept (%s) processes will not abort\n",
	  flag_name);
  printf ("      when feraiseexcept (%s) is called.\n", flag_name);
  pid = fork ();
  if (pid == 0)
    {
      feenableexcept (FE_ALL_EXCEPT);
      exception = fe_exc;
#ifdef FE_INEXACT
      /* The standard allows the inexact exception to be set together with the
	 underflow and overflow exceptions.  So add FE_INEXACT to the set of
	 exceptions to be disabled if we will be raising underflow or
	 overflow.  */
# ifdef FE_OVERFLOW
      if (fe_exc & FE_OVERFLOW)
	exception |= FE_INEXACT;
# endif
# ifdef FE_UNDERFLOW
      if (fe_exc & FE_UNDERFLOW)
	exception |= FE_INEXACT;
# endif
#endif
      fedisableexcept (exception);
      feraiseexcept (fe_exc);
      exit (2);
    }
  else if (pid < 0)
    {
      if (errno != ENOSYS)
	{
	  printf ("  Fail: Could not fork.\n");
	  ++count_errors;
	}
      else
	printf ("  `fork' not implemented, test ignored.\n");
    }
  else {
    if (waitpid (pid, &status, 0) != pid)
      {
	printf ("  Fail: waitpid call failed.\n");
	++count_errors;
      }
    else if (WIFEXITED (status) && WEXITSTATUS (status) == 2)
      printf ("  Pass: Process exited normally.\n");
    else
      {
	printf ("  Fail: Process exited abnormally with status %d.\n",
		status);
	++count_errors;
      }
  }
}

/* Tests for feenableexcept/fedisableexcept/fegetexcept.  */
static void
feenable_test (const char *flag_name, int fe_exc)
{
  feexcp_nomask_test (flag_name, fe_exc);
  feexcp_mask_test (flag_name, fe_exc);
}

static void
fe_single_test (const char *flag_name, int fe_exc)
{
  feenable_test (flag_name, fe_exc);
}
#endif


static void
feenv_tests (void)
{
  /* We might have some exceptions still set.  */
  feclearexcept (FE_ALL_EXCEPT);

#ifdef FE_DIVBYZERO
  fe_single_test ("FE_DIVBYZERO", FE_DIVBYZERO);
#endif
#ifdef FE_INVALID
  fe_single_test ("FE_INVALID", FE_INVALID);
#endif
#ifdef FE_INEXACT
  fe_single_test ("FE_INEXACT", FE_INEXACT);
#endif
#ifdef FE_UNDERFLOW
  fe_single_test ("FE_UNDERFLOW", FE_UNDERFLOW);
#endif
#ifdef FE_OVERFLOW
  fe_single_test ("FE_OVERFLOW", FE_OVERFLOW);
#endif
  fesetenv (FE_DFL_ENV);
}

int
main (void)
{
  feenv_tests ();

  if (count_errors)
    {
      printf ("\n%d errors occurred.\n", count_errors);
      exit (1);
    }
  printf ("\n All tests passed successfully.\n");
  return 0;
}
Test: after feenableexcept (FE_DIVBYZERO) processes will abort
      when feraiseexcept (FE_DIVBYZERO) is called.
  Pass: Process received SIGFPE.
Test: after fedisableexcept (FE_DIVBYZERO) processes will not abort
      when feraiseexcept (FE_DIVBYZERO) is called.
Test: after feenableexcept (FE_DIVBYZERO) processes will abort
      when feraiseexcept (FE_DIVBYZERO) is called.
  Pass: Process received SIGFPE.
Test: after fedisableexcept (FE_DIVBYZERO) processes will not abort
      when feraiseexcept (FE_DIVBYZERO) is called.
  Pass: Process exited normally.
Test: after feenableexcept (FE_INVALID) processes will abort
      when feraiseexcept (FE_INVALID) is called.
  Pass: Process received SIGFPE.
Test: after fedisableexcept (FE_INVALID) processes will not abort
      when feraiseexcept (FE_INVALID) is called.
Test: after feenableexcept (FE_DIVBYZERO) processes will abort
      when feraiseexcept (FE_DIVBYZERO) is called.
  Pass: Process received SIGFPE.
Test: after fedisableexcept (FE_DIVBYZERO) processes will not abort
      when feraiseexcept (FE_DIVBYZERO) is called.
  Pass: Process exited normally.
Test: after feenableexcept (FE_INVALID) processes will abort
      when feraiseexcept (FE_INVALID) is called.
  Pass: Process received SIGFPE.
Test: after fedisableexcept (FE_INVALID) processes will not abort
      when feraiseexcept (FE_INVALID) is called.
  Pass: Process exited normally.
Test: after feenableexcept (FE_INEXACT) processes will abort
      when feraiseexcept (FE_INEXACT) is called.
  Pass: Process received SIGFPE.
Test: after fedisableexcept (FE_INEXACT) processes will not abort
      when feraiseexcept (FE_INEXACT) is called.
Test: after feenableexcept (FE_DIVBYZERO) processes will abort
      when feraiseexcept (FE_DIVBYZERO) is called.
  Pass: Process received SIGFPE.
Test: after fedisableexcept (FE_DIVBYZERO) processes will not abort
      when feraiseexcept (FE_DIVBYZERO) is called.
  Pass: Process exited normally.
Test: after feenableexcept (FE_INVALID) processes will abort
      when feraiseexcept (FE_INVALID) is called.
  Pass: Process received SIGFPE.
Test: after fedisableexcept (FE_INVALID) processes will not abort
      when feraiseexcept (FE_INVALID) is called.
  Pass: Process exited normally.
Test: after feenableexcept (FE_INEXACT) processes will abort
      when feraiseexcept (FE_INEXACT) is called.
  Pass: Process received SIGFPE.
Test: after fedisableexcept (FE_INEXACT) processes will not abort
      when feraiseexcept (FE_INEXACT) is called.
  Pass: Process exited normally.
Test: after feenableexcept (FE_UNDERFLOW) processes will abort
      when feraiseexcept (FE_UNDERFLOW) is called.
  Pass: Process received SIGFPE.
Test: after fedisableexcept (FE_UNDERFLOW) processes will not abort
      when feraiseexcept (FE_UNDERFLOW) is called.
  Fail: Process exited abnormally with status 8.
Test: after feenableexcept (FE_OVERFLOW) processes will abort
      when feraiseexcept (FE_OVERFLOW) is called.
  Pass: Process received SIGFPE.
Test: after fedisableexcept (FE_OVERFLOW) processes will not abort
      when feraiseexcept (FE_OVERFLOW) is called.
Test: after feenableexcept (FE_DIVBYZERO) processes will abort
      when feraiseexcept (FE_DIVBYZERO) is called.
  Pass: Process received SIGFPE.
Test: after fedisableexcept (FE_DIVBYZERO) processes will not abort
      when feraiseexcept (FE_DIVBYZERO) is called.
  Pass: Process exited normally.
Test: after feenableexcept (FE_INVALID) processes will abort
      when feraiseexcept (FE_INVALID) is called.
  Pass: Process received SIGFPE.
Test: after fedisableexcept (FE_INVALID) processes will not abort
      when feraiseexcept (FE_INVALID) is called.
  Pass: Process exited normally.
Test: after feenableexcept (FE_INEXACT) processes will abort
      when feraiseexcept (FE_INEXACT) is called.
  Pass: Process received SIGFPE.
Test: after fedisableexcept (FE_INEXACT) processes will not abort
      when feraiseexcept (FE_INEXACT) is called.
  Pass: Process exited normally.
Test: after feenableexcept (FE_UNDERFLOW) processes will abort
      when feraiseexcept (FE_UNDERFLOW) is called.
  Pass: Process received SIGFPE.
Test: after fedisableexcept (FE_UNDERFLOW) processes will not abort
      when feraiseexcept (FE_UNDERFLOW) is called.
  Fail: Process exited abnormally with status 8.
Test: after feenableexcept (FE_OVERFLOW) processes will abort
      when feraiseexcept (FE_OVERFLOW) is called.
  Pass: Process received SIGFPE.
Test: after fedisableexcept (FE_OVERFLOW) processes will not abort
      when feraiseexcept (FE_OVERFLOW) is called.
  Pass: Process exited normally.

1 errors occurred.

Reply to: