I have been trying to write some error-protection clauses for identifying problems in a dll which is provided to us by an third party. There may be problems in this dll (memory exceptions, floating point errors, etc), and it is advantageous to be able to identify these errors without access to the source code.
I have something put together from various SEH error handling routines, but although it works, there are several... inconsistencies with it. I'm trying to isolate each one, and I'm going to ask a question on each one individually.
This one is to do with the GetExceptionCode, used in the SEH __try/__except clause to identify the error. It doesn't seem to do so reliably.
This is a clear divide-by-zero case:
#include <float.h> // defines of _EM_OVERFLOW, etc.
#include <string.h> // strncpy_s & strncat_s
#include <stdlib.h> // malloc
#include <excpt.h> // EXCEPTION_EXECUTE_HANDLER
#include <iostream> // cout
#include <bitset> // bitset
#include <conio.h> // _kbhit
#pragma fenv_access (on)
const unsigned int SERIOUS_FP_EXCEPTIONS = _EM_DENORMAL | _EM_ZERODIVIDE | _EM_INVALID;
const unsigned int MINOR_FP_EXCEPTIONS = _EM_OVERFLOW | _EM_UNDERFLOW | _EM_INEXACT;
int main(int argc, char[])
{
double numerator = 1.0;
double denominator = 0.0;
double result = 0.0;
unsigned int _previous_floating_point_control;
_controlfp_s(&_previous_floating_point_control, 0, 0);
_controlfp_s(nullptr, MINOR_FP_EXCEPTIONS, _MCW_EM);
__try {
result = numerator / denominator;
_controlfp_s(NULL, _previous_floating_point_control, _MCW_EM);
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
std::cout << "_EM_INEXACT = " << std::bitset<32>(_EM_INEXACT) << std::endl;
std::cout << "_EM_UNDERFLOW = " << std::bitset<32>(_EM_UNDERFLOW) << std::endl;
std::cout << "_EM_OVERFLOW = " << std::bitset<32>(_EM_OVERFLOW) << std::endl;
std::cout << "_EM_ZERODIVIDE = " << std::bitset<32>(_EM_ZERODIVIDE) << std::endl;
std::cout << "_EM_INVALID = " << std::bitset<32>(_EM_INVALID) << std::endl;
std::cout << "_EM_DENORMAL = " << std::bitset<32>(_EM_DENORMAL) << std::endl;
std::cout << "_EM_AMBIGUOUS = " << std::bitset<32>(_EM_AMBIGUOUS) << std::endl;
std::cout << std::endl;
std::cout << " divide-by-zero" << std::endl;
std::cout << " |" << std::endl;
std::cout << " ambiguous code? underflow" << std::endl;
std::cout << " | : |" << std::endl;
std::cout << " v v v" << std::endl;
std::cout << "Exception code = " << std::bitset<32>(GetExceptionCode()) << std::endl;
std::cout << " ^ ^ ^ ^" << std::endl;
std::cout << " | : : |" << std::endl;
std::cout << " denormal number inexact number" << std::endl;
std::cout << " : |" << std::endl;
std::cout << " overflow" << std::endl;
std::cout << " |" << std::endl;
std::cout << " invalid number" << std::endl;
if (GetExceptionCode() & _EM_ZERODIVIDE)
std::cout << "ERROR! Divide By Zero!" << std::endl;
else
std::cout << "No divide by zero found here!" << std::endl;
_controlfp_s(NULL, _previous_floating_point_control, _MCW_EM);
}
std::cout << "result = " << result << std::endl;
while (!_kbhit()) // Wait until a key is pressed to close console.
{ }
}
And this prints the following:
_EM_INEXACT = 00000000000000000000000000000001
_EM_UNDERFLOW = 00000000000000000000000000000010
_EM_OVERFLOW = 00000000000000000000000000000100
_EM_ZERODIVIDE = 00000000000000000000000000001000
_EM_INVALID = 00000000000000000000000000010000
_EM_DENORMAL = 00000000000010000000000000000000
_EM_AMBIGUOUS = 10000000000000000000000000000000
divide-by-zero
|
ambiguous code? underflow
| : |
v v v
Exception code = 11000000000000000000001010110101
^ ^ ^ ^
| : : |
denormal number inexact number
: |
overflow
|
invalid number
No divide by zero found here!
result = 0
It has identified a problem (great), but hasn't diagnosed it quite correctly.
Worse still, when the clause is replaced with a call to a dll which is missing a dependency, I get:
f.p. exceptions
denormal number |
| _|_
v / \
11000000011011010000000001111110
^^ ^ ^ ^^
|| | | ||
\________________/
unknown codes
A similar result is returned in the case of a SIGSEV error (segmentation fault). This means that we're misdiagnosing other problems as floating point exceptions.
So my questions are:
- Is this general approach correct, or am I misunderstanding something fundamental?
- Why is this not picking up the simple case of a divide-by-zero? Is it hardware dependent?
- Can I find out what the rest of the error bits are coming from GetExceptionCode() - that would be really useful.
PS: Please don't comment or reply to say that I should check whether the denominator is 0 - I know, and I do this in all the code I have control over.
You will need something along the lines of