1

I have the below example. (My actual project is a multi-threaded one and I have the terminate handler set for all of them.) I have a couple of questions here.

  1. My terminate handler doesn't do anything fancy. It just says that an error occured and exits. I read that it i good practice to add the handler. Why is that so and do I really need in this case ?

  2. If I don't have the handler, I get the type of exception thrown. terminate called after throwing an instance of 'char const*' But when I use the handler, I am unable to get it. Even if I use current_exception, I am unable to get the type of exception. (Here its obviously char* but in my case it could be anything so I can't catch correctly. Even if I use catch{...}, the message and type are lost). Is there anyway to get the message. If not message, atleast can I get type of the exception thrown ?

// set_terminate example
#include <iostream>
#include <exception>
#include <cstdlib>
using namespace std;

void myterminate () {
  cerr << "terminate handler called\n";
  abort();  // forces abnormal termination
}

int main (void) {
  //set_terminate (myterminate);
  throw "TEST";  // unhandled exception: calls terminate handler
  return 0;

  • If you catch the exception, the `std::terminate` handler _will not_ be called. [demo](https://godbolt.org/z/7Hc_Dr) – Ted Lyngmo May 14 '20 at 13:55
  • Ya, That is fine. I'm talking about getting the type of unhanded exception – Vinodini Natrajan May 14 '20 at 13:59
  • If you want the type you should handle the exception. I doubt it's good practice to use a `terminate_handler` as a replacement for proper exception handling. Perhaps in some obscure embedded environment where installing a `try`..`catch` is considered way too expensive... but .. no... – Ted Lyngmo May 14 '20 at 14:02
  • Agreed. In my case, its a multi threaded system and I don't want to do a try catch over every method that is used by thread. So I am using the terminate_handler for each of them. If I don't use it, I get the exception type but if I use it, I don't get the type. – Vinodini Natrajan May 14 '20 at 14:07
  • If you install a `terminate_handler` in every thread, why not start the thread function with a `try` and end it with a `catch` to display what you caught instead? If you still want the exception to terminate the application you can rethrow or terminate directly, ~ same effect. You could wrap that up in a function template to not have to type it if you dispatch threads differently in many places. – Ted Lyngmo May 14 '20 at 14:26

2 Answers2

4

If you can live with the portability restriction implied by <cxxabi.h>1, then you might be OK with the backstop() terminate-handler below:

main.cpp

#include <iostream>
#include <exception>
#include <stdexcept>
#include <cstdlib>
#include <cxxabi.h>

void backstop()
{
    auto const ep = std::current_exception();
    if (ep) {
        try {
            int status;
            auto const etype = abi::__cxa_demangle(abi::__cxa_current_exception_type()->name(), 0, 0, &status);
            std::cerr << "Terminating with uncaught exception of type `" << etype << "`";
            std::rethrow_exception(ep);

        } catch(const std::exception& e) {
            std::cerr << " with `what()` = \"" << e.what() << "\"";
        } catch(...) {}
        std::cerr << std::endl;
    }
    std::abort();
}

int main(int argc, char *argv[])
{
    std::set_terminate(backstop);
    if (argc > 1) {
        throw argv[1];
    } else {
        throw std::runtime_error("I am too tired to carry on");
    }
    return 0;
}

This will always report the type of the uncaught exception, and if that type derives from std::exception, it will also report the what() of that exception. E.g.

$ g++ --version
g++ (Ubuntu 9.3.0-10ubuntu2) 9.3.0
...
$ g++ -Wall -Wextra -pedantic main.cpp; ./a.out Whoops!
Terminating with uncaught exception of type `char*`
Aborted (core dumped)

$ clang++ --version
clang version 10.0.0-4ubuntu1
...
$ clang++ -Wall -Wextra -pedantic main.cpp; ./a.out
Terminating with uncaught exception of type `std::runtime_error` with `what()` = "I am too tired to carry on"
Aborted (core dumped)

Note that you might avoid calling set_terminate(backstop) - which conceivably might be countermanded someplace else in a big complicated program - and ensure that any exception escaping the body of main is caught in a function try-block, i.e. replace main with:

int main(int argc, char *argv[]) try
{
    if (argc > 1) {
        throw argv[1];
    } else {
        throw std::runtime_error("I am too tired to carry on");
    }
    return 0;
}
catch(...) {
    backstop();
}

This program will behave just as before.


[1] You'll have at least g++, clang++, icc; you won't have MS C++

Mike Kinghan
  • 55,740
  • 12
  • 153
  • 182
  • 1
    We're talking about terminate handler here so it isn't a problem, but for others finding this code when googling `abi::__cxa_demangle()` note that the returned pointer needs to be passed to `free()`. – Stéphane Jan 08 '21 at 08:20
  • If you don't want to use `` and your project is already using boost, you can get the exception type by placing `boost::typeindex::type_id_runtime(e).pretty_name()` inside the `catch(const std::exception& e)` block instead. – joesdiner Jul 21 '22 at 13:28
0

As your metioned, If you want to let your terminate do something fancy, you could write a terminate handler function and call it as a parameter of std::set_terminate.And I think it might be useful to add a backtrace in this function to give more information.Although the name of functions is mangled by the compiler, but still you could see where this error occurred.

And if you want to show the exception, you can add a try-catch block in terminate-handler function.

By the way, I think it may be necessary to using signal function to deal with segmental default error, by doing this your program is more complete in exception handle.

In utils.h file :

#include <stdexcept>
#include <execinfo.h>
#include <exception>
#include <stdexcept>
#include <cstdlib>
#include <csignal>

// This function is used for handle segmental fault
inline void segfaultHandler(int signal __attribute__((unused)))
{
    void *stackArray[20];
    size_t size = backtrace(stackArray, 10);
    std::cerr << "Segmentation fault! backtrace: ";
    char** backtrace = backtrace_symbols(stackArray, size);
    for (size_t i = 0; i < size; i++)
    {
        std::cerr << "\t" << backtrace[i]
    } 
    abort();
}

// This is terminate handle function
inline void exceptionHandler()
{
    static bool triedThrow = false;
    try
    {
        if(!triedThrow)
        {
            triedThrow = true;
            throw;
        }
    }
    catch( const std::exception &e)
    {
        std::cerr << "Caught unhandled exception: " << e.what();
    }
    catch(...){}
    void *stackArray[20];
    size_t size = backtrace(stackArray, 10);
    std::cerr << "Segmentation fault! backtrace: ";
    char** backtrace = backtrace_symbols(stackArray, size);
    for (size_t i = 0; i < size; i++)
    {
        std::cerr << "\t" << backtrace[i]
    } 
    abort();
}

And in main, Using Mike Kinghan's code for example

#include <iostream>
#include "utils.h"
int main(int argc, char *argv[])
{
    signal(SIGSEGV, segfaultHandler);
    std::set_terminate(exceptionHandler);
    if (argc > 1) {
        throw argv[1];
    } else {
        throw std::runtime_error("I am too tired to carry on");
    }
    return 0;
}
xin king
  • 21
  • 6