INF: How to Trap Integer Divide-By-Zero Exceptions in C

ID Number: Q73153

5.00 5.10 6.00 6.00a 6.00ax | 5.10 6.00 6.00a

MS-DOS | OS/2

Summary:

In Microsoft C versions 5.1, 6.0, 6.0a, and 6.0ax, when performing

integer math, it is possible to generate an exception by attempting to

divide an integer by zero. Since the result of a division by zero

operation is undefined, Intel sets aside an interrupt (Int 0) for the

purpose of indicating that a divide-by-zero exception has occurred. In

the default configuration, the run-time library shipped with Microsoft

language products will issue the following error and terminate the

program:

run-time error R6003

- integer divide by 0

Depending on your program's requirements, this may be undesirable

behavior. If you want to trap this error and handle it in a more

appropriate manner, you need to set up an exception handler for Int 0.

This can be done using the _dos_setvect() run-time function for

MS-DOS, or the DosSetVec() API function for OS/2.

More Information:

The code example below demonstrates how this is done in Microsoft C or

Microsoft QuickC. The run time will install a default Int 0 handler

during execution of the startup code (see CRT0DAT.ASM for specifics).

If you want to install a different handler, you should save the

original interrupt vector so that you can restore it as part of the

cleanup procedure. With the run time under MS-DOS, this is done using

the _dos_getvect() function. Using the OS/2 API function DosSetVec(),

one of the arguments you pass is the address of where you want the

original function pointer stored.

The actual function that handles the error must be declared with the

_interrupt keyword. Doing so causes the compiler to save all the

registers on function entry, restore them when the function

terminates, and issue an "iret" instruction to return to the calling

procedure. For more information on the _interrupt keyword, see the

online help.

Inside the exception handler, you have a couple of choices on how to

handle the error: you can either terminate the process or ignore the

error and continue. If you decide to terminate the process, remember

to call one of the run-time exit routines to shut down the run time

[exit(), _exit(), _cexit(), or _c_exit()]. If you decide to ignore the

error and continue, you must increment CS:IP to skip over the

instruction that caused the error. This is also illustrated below in

the sample program.

Sample Code

-----------

/* Compile options needed for DOS : /G2 /DDOS

Compile options needed for OS/2: /G2 /DOS2

*/

#ifdef OS2

#define INCL_DOSMISC

#include <os2.h>

#endif

#include <stdio.h>

#include <dos.h>

void main(void);

void _far _cdecl _interrupt ZeroDivTrap(unsigned int,

unsigned int, unsigned int, unsigned int,

unsigned int, unsigned int, unsigned int,

unsigned int, unsigned int, unsigned int,

unsigned int, unsigned int, unsigned int);

int arg1 = 10;

int arg2 = 0;

int arg3;

int rc; // return codes...

void main(void)

{

#ifdef DOS

// For MS-DOS, first retrieve the old handler, then set up the new one.

void (_interrupt _far *OldZeroDiv)( void );

OldZeroDiv = _dos_getvect(0x00);

_dos_setvect(0x00, ZeroDivTrap);

#endif

#ifdef OS2

// For OS/2, the API will take care of both for us.

PFN OldZeroDiv;

PFN NewZeroDiv;

rc = DosSetVec(VECTOR_DIVIDE_BY_ZERO,

(PFN)ZeroDivTrap, &OldZeroDiv);

if (rc != 0)

printf("rc: %d - DosSetvec() Failed...\n", rc);

#endif

// Now let's try to generate an error. This should do it:

arg3 = arg1 / arg2;

// Just to make sure we are back where we started, print a message.

printf("Back in the main code...\n");

// Clean up after ourselves.

#ifdef DOS

_dos_setvect(0x00, OldZeroDiv);

#endif

#ifdef OS2

rc = DosSetVec(VECTOR_DIVIDE_BY_ZERO, OldZeroDiv, &NewZeroDiv);

if (rc != 0)

printf("rc: %d - DosSetvec() Failed...\n", rc);

#endif

// All done!

}

void _far _cdecl _interrupt ZeroDivTrap(unsigned int _es,

unsigned int _ds, unsigned int _di, unsigned int _si,

unsigned int _bp, unsigned int _sp, unsigned int _bx,

unsigned int _dx, unsigned int _cx, unsigned int _ax,

unsigned int _ip, unsigned int _cs, unsigned int _flags)

{

char chAddrByte;

printf("This is from inside the Exception Handler...\n");

// If we want to terminate the program, do it now. Remember to

// call one of the exit routines to shut down the run time

// [for example, exit(), _exit(), _cexit(), or _c_exit()].

// On the other hand, if you want to continue (knowing of course

// that the results are incorrect for the divide operation), you

// have to determine the size of the instruction that caused the

// exception and jump past it. This is done by examining the mod

// and r/m bits in the second byte of the op code and incrementing

// the IP register by the required amount. See the MASM or Intel

// docs for more information.

chAddrByte = *(char far *)(((unsigned long)_cs << 16) + _ip + 1);

chAddrByte &= 0xC7; // Mask off unneeded bits.

if (chAddrByte == 6) // If mod = 0 and r/m = 6, then

_ip += 4; // opcode is 4 bytes.

else

switch (chAddrByte >> 6) // Else, we look at mod only.

{

case 0:

case 3:

_ip += 2;

break;

case 1:

_ip += 3;

break;

case 2:

_ip += 4;

break;

}

}

Additional reference words: 5.10 6.00 6.00a 6.00ax 2.0 2.00 2.01 2.5

2.50 2.51