C++ Logo

std-proposals

Advanced search

[std-proposals] Standard Library assistance in logging of all exceptions

From: Frederick Virchanza Gotham <cauldwell.thomas_at_[hidden]>
Date: Wed, 28 Feb 2024 16:08:56 +0000
Recently in my job I took over the coding of a very big Software
Development Kit (SDK) written in C++ for x86_64 MS-Windows using
MS-Visual Studio, as the previous maintainer left the company
suddenly, and so I'm sort of reverse-engineering the SDK trying to
figure out the very complicated class hierarchy and how it's all
supposed to work.

The SDK is a DLL file loaded into a .NET program written in C#. The
SDK is not supposed to throw an exception that propagates up to the C#
program, but currently this is happening and so I have to investigate
it.

In the C# program, when they catch the exception, the only info they
can see is "External component has thrown an exception". I've tried
all sorts of techniques and improvisations in the C# program to catch
an "std::exception" or "char*" thrown from the SDK, but the
information is already lost once the exception has reached C#. The C#
program can't see a stack trace either.

I searched the SDK source code for all instances of "throw" and I
found about 200 matches. I could have replaced all of these 'throw'
statements with a preprocessor macro that does logging, but this
wouldn't aid in logging exceptions thrown from the standard library
(nor thrown from another DLL). It would also mean editing dozens of
files that I don't want to edit.

So to try figure out how Microsoft does exceptions, I wrote a simple function:

    void Func(void) { throw "blue bananas"; }

and I got Visual Studio to compile this function to x86_64 assembler,
and I saw this instruction:

    call _CxxThrowException

So I looked up '_CxxThrowException' and saw that it resides in
"vcruntime410.dll". So in order to log all exceptions, all I had to do
was provide my own implementation of this function as follows:

    void _CxxThrowException(void *pObj, ThrowInfo const *pType)
    {
        // Log the exception along with a stack trace here

        // Next call the original _CxxThrowException function
       Original_CxxThrowException(pObj, pType);
    }

I got this working, and so now when the C# program catches the
exception from C++, it's able to ask the DLL to see the exception log
to see what was thrown (along with a stack trace). When it comes to
the GNU g++ compiler, a quick web search shows me that people have
been able to do the same thing by providing an implementation of
'__cxa_throw'.

I wonder would it be useful if the Standard Library were to allow us
to portably do something like this? For example let's say we had:

    namespace std {
        using throw_handler = void (*)(void const *, type_info const
&) noexcept;
        throw_handler set_throw( throw_handler f ) noexcept;
    }

And so then in our own C++ program we could do:

    void MyLogger(void const *const p, type_info const &ti) noexcept
    {
        // Log error text as well as a stack trace here
    }

    int main(void)
    {
        std::set_throw( MyLogger );
    }

So now every time an exception is thrown, the 'throw handler' is
called before the exception is thrown. What would also help here is a
function that can tell you if a given type_info represents a type that
is derived from another type, and adjusts the 'this' pointer
accordingly:

    namespace std {
        template<Base>
        Base *adjust_to_base(void *p, std::type_info const &ti) noexcept;
    }

and so then we could do the following in our logger function:

    void MyLogger(void const *const p, type_info const &ti)
    {
        std::exception const *const p =
std::adjust_to_base<std::exception>(p, ti);

        if ( nullptr == p ) return; // if it's not derived from std::exception

        char const *const text = p->what();

         // Log the 'text' here along with stack trace
    }

The performance penalty incurred by this new feature would be
negligible, as inside a function such as '_CxxThrowException' or
'__cxa_throw', it would just be a quick check:

   if ( throw_handler ) throw_handler( p, ti );

This would be just a handful of CPU cycles -- which isn't of concern
in the context of throwing an exception.

Received on 2024-02-28 16:09:09