OS/2 codes: Prevent SIGFPE

OS/2 has a very famous bug. It's releated to SIGFPE. When operating floating point arithmetics, SIGFPE occurs due to an exception such as divide by zero. On normal condition, this should be processed without exceptions.

How can this SIGFPE be prevented ?

First, find the cause of SIGFPE. It is that some DLLs do not restore a FPU control word after it changes the word when loading. So what to have to do is to mask off the FPU control word, again. There are 3 ways for this.

1. _control87()


The first way is to use _control87(). If the executable uses only imported DLLs, then all the DLLs are loaded before a main routine of a program is started. So if we mask off the FPU control word at begining, then all the problem will be solved. Usually, use the following codes.

1
2
3
4
5
6
7
8
9
10
#include <float.h>

int main( void )
{
    _control87( MCW_EM, MCW_EM );

    ....

    return 0;
}


However, this approach has a shortcoming. When some DLLs are load dynamically, they can also alter the FPU word. Then SIGFPE can occur, again.

2. Wrapping functions loading DLLs dynamically


OS/2 API to load a DLL dynamically is DosLoadModule(). So if we provide a wrapper function for it, we can prevent SIGFPE, again.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#define INCL_DOS
#include <os2.h>

#define DosLoadModule MyDosLoadModule

APIRET MyDosLoadModule( PSZ pszName, ULONG cbName, PSZ pszModName,
                                         PHMODULE phmod )
{
    APIRET rc = DosLoadModule( pszName, cbName, pszModName, phmod );

    _control87( MCW_EM, MCW_EM );

    return rc;
}

int main( void )
{
    _control87( MCW_EM, MCW_EM );

    // some routines to load DLLs dynamically
 
    return 0;
}


This code is just a skleton. You should include line 4 in every source file using DosLoadModule(). Or you can use compiler command line option such as -D of gcc for every sources.

However, this approach also have a problem. If you use this, you should recompile all the sources using DosLoadModule(). So if you have a library whose source is not available, this approach is useless.

This is true for other functions such as dlopen() loading DLLs dynamically.

3. Signal handler for SIGFPE


SIGFPE is an exception. So if we register a handler for SIGFPE and modify a control word, then SIGFPE can be ignored safely. The following codes show how to do this.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#define INCL_DOS
#include <os2.h>

#include <stdio.h>
#include <float.h>

// this handler was excerpted from Mozilla repository
static
ULONG _System fpeHandler( PEXCEPTIONREPORTRECORD p1,
                          PEXCEPTIONREGISTRATIONRECORD p2,
                          PCONTEXTRECORD p3,
                          PVOID p4 )             
{
    printf("XCPT no. = %x\n", p1->ExceptionNum );

    switch( p1->ExceptionNum )
    {
        case XCPT_FLOAT_DIVIDE_BY_ZERO :
        {
            unsigned cw = p3->ctx_env[0];
            if ((cw & MCW_EM) != MCW_EM) {
                /* Mask out all floating point exceptions */
                p3->ctx_env[0] |= MCW_EM;
                /* Following two lines set precision to 53 bit mantissa. See jsnum.c */
                p3->ctx_env[0] &= ~MCW_PC;
                p3->ctx_env[0] |= PC_53;

                printf("----- XCPT_CONTINUE_EXECUTION\n");

                return XCPT_CONTINUE_EXECUTION;
            }

            break;
        }
    }

    printf("----- XCPT_CONTINUE_SEARCH\n");

    return XCPT_CONTINUE_SEARCH;
}

int main(void)
{
    float f, f2;
    EXCEPTIONREGISTRATIONRECORD regRec = { 0, };

    // mask on all exceptions
    _control87( 0, MCW_EM );

    regRec.ExceptionHandler = ( ERR )fpeHandler;
    DosSetExceptionHandler( &regRec );

    f2 = 1.5;
    f = .0;
    f = f2 / f;
    printf("f = %f\n", f );

    DosUnsetExceptionHandler( &regRec );

    return 0;
}


However, this also has a defect. You should register this handler at every threads. Like DosLoadModule(), you can wrap thread starter functions such as DosCreateThread() or _beginthread(). But a better way is to provide this in libc. Actually, this task was requested in kLIBC project(See http://trac.netlabs.org/libc/ticket/296) . The best is, of course, to fix those buggy DLLs.

So far, we've found out the ways to prevent SIGFPE. Out of these ways, I recommend Way 1 and Way 3.(2014/09/06) And I wish they will be included in kLIBC ASAP.

// ----- 2016/01/14

Since kLIBC 0.6.6, FPU control word is masked off at startup. See http://trac.netlabs.org/libc/ticket/312. And loading modules dynamically was also enhanced to prevent SIGFPE. See http://trac.netlabs.org/libc/ticket/317.

// ----- 2014/09/10
List of APIs which change FPU CW but do not restore it.

  • WinCreateMsgQueue()
// ----- 2014/10/26
  • DosLoadModule     
  • DosFreeModule
  • DosRead
  • DosWrite
// -----

댓글

이 블로그의 인기 게시물

토렌트: < 왕좌의 게임 > 시즌 1 ~ 시즌 8 완결편 마그넷

토렌트: < 스타워즈 > Ep.1 ~ Ep.6 마그넷

토렌트: NGC < 코스모스 > 우리말 더빙 전편(1편~13편) 마그넷