VC++ Exception Handling differ on x86 and x64 for IBPP / Firebird client
Asked Answered
K

2

7

I am hacking around with IBPP on Visual Studio 2015 / VC++. IBPP is a c++ wrapper for the firebird / interbase API. IBPP, a C++ Client Interface to Firebird Server

Part of this package is a little test-suite, you may download it here: ibpp-2-5-3-1-src.zip

To start with the test-suite you will find a simple batchfile to compile it under

x:...\ibpp-2-5-3-1-src\tests\vs2005\simplest-build.bat

It compiles fine with the native x86 and x64 toolchains of vc++ 2015.

Before compiling you need to edit lines 84 to 86 of

x:...\ibpp-2-5-3-1-src\tests\tests.cpp

const char* DbName = "x:/ibpptest/test.fdb";    // FDB extension (GDB is hacked by Windows Me/XP "System Restore")
const char* BkName = "x:/ibpptest/test.fbk";
const std::string ServerName = ""; //"localhost";   // Change to "" for local protocol / embedded

Please keep mind to create the directory x:\ibpptest\.

Furthermore you need to download the fblient files which are not available on its own but as part of the entire server archive. Get these both files: 32-bit Embedded and 64-bit Embedded .

For simplifying create two directories besides x:\...\ibpp-2-5-3-1-src\tests\vs2005\:

x:\...\ibpp-2-5-3-1-src\tests\vs2015x86\
x:\...\ibpp-2-5-3-1-src\tests\vs2015x84\

and copy x:\...\ibpp-2-5-3-1-src\tests\vs2005\simplest-build.bat into them. Now copy the fbclient files (32 Bit to x86, 64 Bit to x64) in these directories:

intl/*
udf/*
fbembed.dll
firebird.msg
ib_util.dll
icudt30.dll
icuin30.dll
icuuc30.dll
msvcp80.dll
msvcr80.dll

Now you can compile and start tests.exe. The x86 binary generates some errors in Test 6, that's OK because you are using the embedded version of the fblient files. The x64 binary will end up in a Windows program failure screen. This happens in Test3 when the test suite activates an exception:

try
{
    #if defined(IBPP_WINDOWS) && defined(_DEBUG)
        OutputDebugString(_("An exception will now get logged in the debugger: this is expected.\n"));
    #endif
    st1->ExecuteImmediate(  "CREATE SYNTAX ERROR(X, Y) AS "
                            "SELECT ERRONEOUS FROM MUSTFAIL M" );
}
catch(IBPP::SQLException& e)
{
    //~ std::cout<< e.what();
    if (e.EngineCode() != 335544569)
    {
        _Success = false;
        printf(_("The error code returned by the engine during a\n"
            "voluntary statement syntax error is unexpected.\n"));
    }
}

In the x86 binary this exception was caught as expected but in the x64 binary it will not. Does anybody knows how to achive similar exception handeling in the x64 binary?

Thanks in advance for any help!

Kuehl answered 19/3, 2017 at 17:31 Comment(6)
which exception is thrown in x64?Bushtit
The exception unknown software exception. Eventlog says: Exception code: 0xc0000409, Fault offset: 0x0000000000043bf7Kuehl
Does it also happen in debug? You might want to take a look at #21765385Bushtit
Yes, it does...Kuehl
Then you can see the whole stack and see what is going wrong. For us it would be interesting to see the stack as well.Bushtit
0xc0000409 is very, very nasty and cannot be caught. Filing a bug with the project is surely the best way to get ahead.Deafen
K
1

After a lot of more research I found the cause. I was wrong beliving that the exception handling in x86 and x64 differ. The IBPP library contains a bug (since 10 years!) which only happens in the x64/windows szenario when the firebird library reports an (complex) error condition to the caller.

So what happens is:

The test programm calls the IBPP libarary. The IBPP library calls the firebird API / library. The firebird library reports its results of calls in a array[20] of longs, they call it "ISC_STATUS vector". The IBPP library checks this results and in a case of an error it throws an exception. The test program catches such exceptions and reports them to the use.

So far, so good. But the bug is, that IBPP defines ISC_STATUS as an array[20] of longs as firebird it also did until v2.0 - which only support x86 windows. Beginning with v2.1 firebird supports x64 windows an defines ISC_STATUS as an array of intptr_t which leads to "long long" on windows x64 LLP64 compilers and to long on LP64 linux compilers - both are 64 bit wide. On ILP32 windows and linux x86 compilers intptr_t is 32 bit wide. IBPP doesn't closed the gap to firebird and stays on its definition of ISC_STATUS as long - which leads to a 32 bit wide data type on LLP64 windows x64 systems (but to 64 bit on linux, as gcc on linux uses a LP64 system, so the bug only happens on windows).

So the firebird x64 API reports up to 20 status integers to the "by reference parameter" array[20] of 64 Bit integers. The FBPP library passes an array[20] of 32 Bit integers. When the firebird API stores values in second half of the array it overwrites memory of the caller. In this case the next bytes after the ISC_STATUS vector are occupied by a c++ string object. Both, the status array and the string a part of the IBS (interbase status) class. Many of the IBPP functions often instantiating a local object of this class to manage the firebird API results and a error descriping string. When the functions exits the framework cleans up such local objects and tries to free the string but its metadata in memory was overwritten by the firebird API.

So the cleanup of the local IBS object leads to an unknown software exceptions which overdrives the exceptions thrown by the IBPP framework. And this exception is not catched by the test program but by windows / dr. watson.

I fixed the definition of ISC_STATUS from "long" to "intptr_t" and all works as expected.

Thank you all for your hints.

Kuehl answered 28/3, 2017 at 14:23 Comment(0)
T
1

Caveat: I used the Visual Studio 2017 environment to run the simplest-build.bat file.

It appears from the evidence I provide below that there is a fork or other compiling difference between the 32 bit and 64 bit versions of the Firebird service that causes this problem.

Solution: The EngineCode() member function does not exist in the 64 bit version. You must use the exception's what() member function as shown in the commented out line inside the Test3() catch block. If you desire to use EngineCode information you will have to parse it out of the what() string, because that contains all of the information that was originally provided as individual data members in the IBPP::SQLExceptionImpl class for 32 bit.

try
{
    #if defined(IBPP_WINDOWS) && defined(_DEBUG)
        OutputDebugString(_("An exception will now get logged in the debugger: this is expected.\n"));
    #endif
    st1->ExecuteImmediate(  "CREATE SYNTAX ERROR(X, Y) AS "
                            "SELECT ERRONEOUS FROM MUSTFAIL M" );
}
catch(IBPP::SQLException& e)
{
    //~ std::cout<< e.what();

    printf(e.what());

    //if (e.EngineCode() != 335544569)
    //{
    //  _Success = false;
    //  printf(_("The error code returned by the engine during a\n"
    //      "voluntary statement syntax error is unexpected.\n"));
    //}
}

Result of what() call.

*** IBPP::SQLException ***
Context: Statement::ExecuteImmediate( CREATE SYNTAX ERROR(X, Y) AS SELECT ERRONEOUS FROM MUSTFAIL M )
Message: isc_dsql_execute_immediate failed

SQL Message : -104
can't format message 13:896 -- message file C:\WINDOWS\SYSTEM32\firebird.msg not found

Engine Code    : 335544569
Engine Message :
Dynamic SQL Error
SQL error code = -104
Token unknown - line 1, column 8
SYNTAX

Puzzle: statement.cpp shows the use of IBPP::SQLExceptionImpl and other ...ExceptionImpl usage, so I have to believe this source code is the 32 bit branch. If this is supposed to be a common branch for both 32 and 64 bit, then I don't see how it can work.

There are two header files that define exception classes. I believe that _ibbp.h was used to compile the 32 bit client and ibbp.h was used for the 64 bit client. I came to these conclusions by changing the catch clause in Test3() to see what possible exceptions could be happening. The ...ExceptionImpl classes would only compile with the 32 bit client dlls and they would not compile with the 64 bit dlls.

From _ibpp.h (32bit fbclient dlls recognize these classes. 64bit fbclient dlls do not.)

///////////////////////////////////////////////////////////////////////////////
//
//  Implementation of the "hidden" classes associated with their public
//  counterparts. Their private data and methods can freely change without
//  breaking the compatibility of the DLL. If they receive new public methods,
//  and those methods are reflected in the public class, then the compatibility
//  is broken.
//
///////////////////////////////////////////////////////////////////////////////

//
// Hidden implementation of Exception classes.
//

/*
                        std::exception
                                |
                        IBPP::Exception
                    /                 \
                    /                   \
IBPP::LogicException    ExceptionBase    IBPP::SQLException
        |        \         /   |     \     /
        |   LogicExceptionImpl |   SQLExceptionImpl
        |                      |
    IBPP::WrongType            |
            \               |
            IBPP::WrongTypeImpl
*/

From ibpp.h (both fbclient dll sets recognize these classes)

/* IBPP never return any error codes. It throws exceptions.
 * On database engine reported errors, an IBPP::SQLException is thrown.
 * In all other cases, IBPP throws IBPP::LogicException.
 * Also note that the runtime and the language might also throw exceptions
 * while executing some IBPP methods. A failing new operator will throw
 * std::bad_alloc, IBPP does nothing to alter the standard behaviour.
 *
 *                    std::exception
 *                           |
 *                   IBPP::Exception
 *                 /                 \
 *    IBPP::LogicException    IBPP::SQLException
 *             |
 *      IBPP::WrongType
 */

In all of tests.cpp, the only catch for IBPP::SQLException is in Test3(). Every other catch uses IBPP::Exception.

So this problem will only show up in Test3() when compiling for 64 bit, but I think it would manifest itself whenever IBPP::SQLException is used in a 64 bit implementation.

Tautomer answered 26/3, 2017 at 15:50 Comment(1)
Dear Rex, unfortunately you missed the problem at all. First, the catch block with the what() clause is never reached under the situation descriped here. That was the motivation for my question. Next, you don't understand how the compiler and the includes work. _ibpp.h includes very first ibpp.h - regardless of x86 of x64 target. The includes beginning with an underscore are more private to the library, that ones without are public and must be used by the modules using this library. Nevertheless, thank you for your effort!Kuehl
K
1

After a lot of more research I found the cause. I was wrong beliving that the exception handling in x86 and x64 differ. The IBPP library contains a bug (since 10 years!) which only happens in the x64/windows szenario when the firebird library reports an (complex) error condition to the caller.

So what happens is:

The test programm calls the IBPP libarary. The IBPP library calls the firebird API / library. The firebird library reports its results of calls in a array[20] of longs, they call it "ISC_STATUS vector". The IBPP library checks this results and in a case of an error it throws an exception. The test program catches such exceptions and reports them to the use.

So far, so good. But the bug is, that IBPP defines ISC_STATUS as an array[20] of longs as firebird it also did until v2.0 - which only support x86 windows. Beginning with v2.1 firebird supports x64 windows an defines ISC_STATUS as an array of intptr_t which leads to "long long" on windows x64 LLP64 compilers and to long on LP64 linux compilers - both are 64 bit wide. On ILP32 windows and linux x86 compilers intptr_t is 32 bit wide. IBPP doesn't closed the gap to firebird and stays on its definition of ISC_STATUS as long - which leads to a 32 bit wide data type on LLP64 windows x64 systems (but to 64 bit on linux, as gcc on linux uses a LP64 system, so the bug only happens on windows).

So the firebird x64 API reports up to 20 status integers to the "by reference parameter" array[20] of 64 Bit integers. The FBPP library passes an array[20] of 32 Bit integers. When the firebird API stores values in second half of the array it overwrites memory of the caller. In this case the next bytes after the ISC_STATUS vector are occupied by a c++ string object. Both, the status array and the string a part of the IBS (interbase status) class. Many of the IBPP functions often instantiating a local object of this class to manage the firebird API results and a error descriping string. When the functions exits the framework cleans up such local objects and tries to free the string but its metadata in memory was overwritten by the firebird API.

So the cleanup of the local IBS object leads to an unknown software exceptions which overdrives the exceptions thrown by the IBPP framework. And this exception is not catched by the test program but by windows / dr. watson.

I fixed the definition of ISC_STATUS from "long" to "intptr_t" and all works as expected.

Thank you all for your hints.

Kuehl answered 28/3, 2017 at 14:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.