C + + exception handling (1)

Source: Internet
Author: User

Exception (Exception) is a new feature in C + +, which provides a way to structure errors in a structured manner so that the program can easily separate exception handling from the wrong program, and in use, its syntax is fairly concise enough to make the illusion that its underlying implementation should be simple, But that's not the truth. It is precisely because of its grammatical simplicity does not prescribe too much detail, thus leaving the compiler enough space to play its own, so in different operating systems, under different compilers, it is very different implementation. This article describes how Windows and Visual C + + is based on SEH to implement exception handling on C + +, speaking very detailed, although it has been written for a long time, but the original rational things are not outdated, interested can be read a bit.

As for how the Linux under the GCC How to do, the online flooded with a variety of documents, many but also miscellaneous, I am here to simply take me these days to see, think, understand, do not understand, do a simple summary, welcome to correct.

what happened after the exception was thrown?

According to the C + + standard, if the exception is thrown after it is not caught (catch) within the current function, it will continue to toss along the call chain of the function until the entire call chain is completed, or a corresponding catch is found in a function. If the call chain does not find the corresponding catch, then Std::terminate () will be called, the function by default is to abort the program, and if the corresponding catch is found, it will enter the catch code block, the corresponding operation.

Catch in the program part of the code has a special name:Landing pad (not very accurate), from the start of the exception to the execution of Landing pad code in the middle of the entire process is called the stack unwind, the process contains two stages:

1) Starting with the function of throwing an exception, find the landing pad on the function before the call chain.

2) If the landing pad is not found, then the program abort, otherwise, note the location of the landing pad, and then back to throw the abnormal function there to start, one frame at a frame to clean up the call chain functions inside the local variables, until the landing pad where the function.

In short, the thing that stack unwind normally do is start with the function that throws the exception, follow the call chain up to find the function where the catch is, and start by throwing the exception, cleaning up the local variables that have already been created in the stack frames on the call chain.

void Func1 () {  cs A;//stack unwind when it is destructor.  throw 3;} void Func2 () {  cs B;  Func1 ();} void Func3 () {  cs C;  Try   {    func2 ();  }  catch (int)  {    //before entering here, Func1, FUNC2 has been unwind.  }}

As can be seen, the process of unwind can be simply regarded as the inverse process of a function call, the process is implemented by a dedicated stack unwind library, on the Intel platform, it is part of the Itanium ABI interface, and is independent of the specific language, provided by the system implementation, Any upper language can implement its own exception handling on the basis of this interface, and GCC is based on this interface to implement C + + exception handling.

Itanium C + + ABI

The Itanium ABI defines a series of functions and corresponding data structures to establish the process and framework for the entire exception handling, including the following:

_unwind_raiseexception,_unwind_resume,_unwind_deleteexception,_unwind_getgr,_unwind_setgr,_unwind_getip,_ Unwind_setip,_unwind_getregionstart,_unwind_getlanguagespecificdata,_unwind_forcedunwind

The _unwind_raiseexception () function is used for stack unwind, which is called when the user executes the throw, and the main function is to start with the current function and invoke a call personality routine for each function frame on the call chain. function (__gxx_personality_v0), which is defined and provided by the upper-level language, _unwind_raiseexception () rebuilds the current call to the function stack internally and then passes it to personality routine, It is primarily responsible for doing two things:

1) Check that the current function contains a corresponding catch to handle the exception thrown above.

2) Clear out the local variables on the call stack.

Obviously, we can find that the two things that personality routine do correspond to the two phases one by one that the stack unwind is going through, so it can be said that the stack unwind is mainly personality routine To complete, it is equivalent to a callback.

_unwind_reason_code (*__personality_routine)        (int version,         _unwind_action actions,         UInt64 Exceptionclass,         struct _unwind_exception *exceptionobject,         struct _unwind_context *context);

Note the second parameter above, which is used to tell personality routine which stage is currently in the stack unwind, and the other parameters are used primarily to pass information related to the exception and the context of the current function. The stack unwind, which has been said before, contains two stages, specifically to the function on the call chain, that is, each function will be traversed two times by personality routine in the process of unwind.

The following pseudo-code shows the approximate implementation of the _unwind_raiseexception (), which is a summary of the preceding:

_unwind_raiseexception (Exception) {    bool found = false;    while (1)     {         //establishes the context of the previous function         : Build_context ();         if (!context) break;         Found = Personality_routine (exception, context, SEARCH);         if (found or reach the end) break;     }    while (found)    {        context = Build_context ();        if (!context) break;        Personality_routine (exception, context, unwind);        if (reach_catch_function) break;    }}

The functions in the ABI use two custom data structures to pass in some internal information.

struct _unwind_context;struct _unwind_exception {  uint64     exception_class;  _UNWIND_EXCEPTION_CLEANUP_FN Exception_cleanup;  UInt64     private_1;  UInt64     private_2;};

According to the introduction of the interface, _unwind_context is a transparent structure to the caller, used to represent the context of the program runtime, mainly the value of some registers, function return address, etc., it is defined and created by the interface implementation, but I do not find its definition in the interface, only in the GCC In the source code to find a copy of its definition.

struct _unwind_context{  void *reg[dwarf_frame_registers+1];  void *CFA;  void *ra;  void *lsda;  struct dwarf_eh_bases bases;  _unwind_word args_size;};

As the _unwind_exception, as the name implies, it is used within the unwind library to represent an exception.

C + + ABI.

Based on the Itanium ABI described earlier, the compiler level also defines a series of ABI to interact with. When we write "Throw xxx" in the code, the compiler assigns a data structure to represent the exception, which has a header, which is defined as follows:


{ Std::type_info * exceptiontype; void (*exceptiondestructor) (void *); Unexpected_handler Unexpectedhandler; Terminate_handler Terminatehandler; __cxa_exception * nextexception; int Handlercount; int handlerswitchvalue; const char * Actionrecord; const char * languagespecificdata; void * catchtemp; void * adjustedptr; _unwind_exception Unwindheader;};

Note the last of these variables: _unwind_exception Unwindheader, which is the internal structure of the interface mentioned in the previous Itanium interface. When the user throw an exception, the compiler helps us to call the corresponding function to assign the following structure:

Where _cxa_exception is the head, exception_obj is "throw xxx" in the xxx, these two parts in memory is continuous. The exception object is created by the function __cxa_allocate_exception () and finally destroyed by __cxa_free_exception (). When we throw an exception in the program, the compiler does the following for us:

1) Call the __cxa_allocate_exception function and assign an exception object.

2) Call the __cxa_throw function, which initializes the exception object.

3) __cxa_throw () invokes the _unwind_raiseexception () in the Itanium ABI to begin the unwind.

4) _unwind_raiseexception () calls personality routine when the function on the call chain is unwind.

5) If the exception can be handled (with a corresponding catch), then personality routine will clean up the function on the call chain in turn.

6) _unwind_raiseexception () transfers control to the corresponding catch code.

7) Unwind completed, the user code continues to execute.

From the perspective of C + +, a complete exception-handling process is complete, of course, the omission of a lot of detail, of which the most mysterious perhaps is personality routine, it is how to know the current unwind function has a corresponding catch statement it? How do you know how to clean up local variables in this function? Concrete implementation here first not detailed, only need to understand, in fact, it does not know, only the compiler know, so in the compile-time compiler will establish some table entries to save the corresponding information, so that the personality routine can be at runtime through these pre-established information for the corresponding query.

the process of viewing unwind from source code

The process of unwind starts from __cxa_throw (), please see the following source code:

 extern "C" void__cxxabiv1::__cxa_throw (void *obj, Std::type_info *tinfo,void (_glibcxx_cdtor_callabi *dest) (void   *) {PROBE2 (throw, obj, tinfo);   Definitely a primary.   __cxa_refcounted_exception *header = __get_refcounted_exception_header_from_obj (obj);   Header->referencecount = 1;   Header->exc.exceptiontype = Tinfo;   Header->exc.exceptiondestructor = dest;   Header->exc.unexpectedhandler = std::get_unexpected ();   Header->exc.terminatehandler = Std::get_terminate ();   __gxx_init_primary_exception_class (Header->exc.unwindheader.exception_class);   Header->exc.unwindheader.exception_cleanup = __gxx_exception_cleanup;   #ifdef _glibcxx_sjlj_exceptions _unwind_sjlj_raiseexception (&header->exc.unwindheader);   #else _unwind_raiseexception (&header->exc.unwindheader); #endif//Some sort of unwinding error.   Note that terminate is a handler.   __cxa_begin_catch (&header->exc.unwindheader); Std::terminate ();} 

We can see that __cxa_throw finally called _unwind_raiseexception (), and stack unwind started, as mentioned earlier, the unwind is divided into two stages, the search catch and the cleanup call stack, respectively, the corresponding code is as follows:

/* Raise an exception, passing along the given exception object. */_unwind_reason_code_unwind_raiseexception (struct _unwind_exception *exc) {struct _unwind_context this_context, cur  _context;  _unwind_reason_code Code;  Uw_init_context (&this_context);  Cur_context = This_context;  /* Phase 1:search.  Unwind the stack, calling the personality routine with the _UA_SEARCH_PHASE flag set.  Do not modify the stack yet.      */while (1) {_unwind_framestate fs;      Code = uw_frame_state_for (&cur_context, &FS);  if (code = = _urc_end_of_stack)/* hit END of the STACK with no handler found.      */return _urc_end_of_stack;  if (Code! = _urc_no_reason)/* Some error encountered.  ususally the Unwinder doesn ' t diagnose these and merely crashes.      */return _urc_fatal_phase1_error;  /* Unwind successful.  Run The personality routine, if any. */if (fs.personality) {code = (*fs.personality) (1, _ua_search_phase, Exc->exception_class, exc, &cur_context);      if (code = = _urc_handler_found) break;    else if (code! = _urc_continue_unwind) return _urc_fatal_phase1_error;    } uw_update_context (&cur_context, &FS);  }/* indicate to _unwind_resume and associated subroutines, that's not a forced unwind.  Further, note where we found a handler.  */exc->private_1 = 0;  exc->private_2 = Uw_identify_context (&cur_context);  Cur_context = This_context;  Code = _UNWIND_RAISEEXCEPTION_PHASE2 (exc, &cur_context);  if (Code! = _urc_install_context) return code; Uw_install_context (&this_context, &cur_context);} Static _unwind_reason_code_unwind_raiseexception_phase2 (struct _unwind_exception *exc, struct _unwind_con  Text *context) {_unwind_reason_code Code;      while (1) {_unwind_framestate fs;      int Match_handler;      Code = uw_frame_state_for (context, &FS); /* Identify When we ' ve reached the DesignaTed Handler context.      */Match_handler = (uw_identify_context (context) = = exc->private_2? _ua_handler_frame:0);  if (Code! = _urc_no_reason)/* Some error encountered.  Usually the unwinder doesn ' t diagnose these and merely crashes.      */return _urc_fatal_phase2_error;  /* Unwind successful.  Run The personality routine, if any.                    */if (fs.personality) {code = (*fs.personality) (1, _ua_cleanup_phase | Match_handler,        Exc->exception_class, exc, context);        if (code = = _urc_install_context) break;      if (Code! = _urc_continue_unwind) return _urc_fatal_phase2_error;  }/* Don ' t let us unwind past the handler context.      */if (Match_handler) abort ();    Uw_update_context (context, &FS); } return code;

The above two functions correspond to these two phases of the unwind process, and note the following:

Uw_init_context () uw_frame_state_for () Uw_update_context ()

These functions are mainly used to reconstruct the function call site, their implementation involves a lot of detail, here sell a Xiaoguanzi first not detailed, the approximate principle is, for the function on the call chain, their large part of the context can be recovered from the stack, such as EBP, ESP, return address and so on. In order for Unwinder to get this information from the stack, it builds up a number of table entries to record the information about each function that can throw an exception, which will instruct the program to search for things on the stack when it rebuilds the context.

do something interesting.

Say a lot, the following program to write a test briefly review the above-mentioned about exception handling of the approximate process:

#include <iostream>using namespace Std;void test_func3 () {    throw 3;    cout << "Test func3" << Endl;} void Test_func2 () {    cout << "Test Func2" << Endl;    Try    {        test_func3 ();    }    catch (int)    {        cout << "Catch 2" << Endl;    }} void Test_func1 () {    cout << "Test func1" << Endl;    Try    {        test_func2 ();    }    catch (...)    {        cout << "Catch 1" << Endl;    }} int main () {    test_func1 ();    return 0;}

After the above program is running, we can get the next breakpoint in the __gxx_personality_v0.

Breakpoint 2, 0x00dd0a46 in __gxx_personality_v0 () from/usr/lib/libstdc++.so.6 (gdb) bt#0  0x00dd0a46 in __gxx_ Personality_v0 () from/usr/lib/libstdc++.so.6#1  0x00d2af2c in _unwind_raiseexception () from/lib/libgcc_s.so.1#2  0x00dd10e2 in __cxa_throw () from/usr/lib/libstdc++.so.6#3  0x08048979 in Test_func3 () at Exc.cc:6#4  0x080489ac in Test_func2 () @ exc.cc:16#5  0x08048a52 in Test_func1 () at Exc.cc:29#6  0x08048ad1 in Main () at exc . cc:39 (GDB)

From this call stack, we can see what our program did after the exception was thrown. If you feel funny, you can even try to hook off some of these functions to change the behavior of exception handling, a hack technique that is useful at some point, for example, a scenario I'm using today:

We used a third library with a message loop that was placed inside a try/catch.

void Wxentry () {    try    {       call_user_func ();    }    catch (...)    {       unhandled_exception ();    }}

Call_user_func () invokes a series of functions that involve our own code, and at some point our code is thrown out of the ordinary, and we are not capturing it, so wxentry will eventually catch these exceptions and call Unhandled_exception (), the function will call some cleanup function by default, and then abort the program, and when the cleanup function is called, because our code is not behaving properly, in the case of cleanup usually leads to a lot of other weird errors, and finally even get coredump It's also hard to tell what's wrong with our program. So we hope that when our code throws an exception and is not handled by ourselves and finally captured in Wxentry (), we can hit the call stack for the place where we threw the exception. At first we tried to hook the __cxa_throw, that is, whenever someone throws an exception, we put the call stack to fight out, this solution can solve the problem, but the problem is obvious, it affects the execution of all the code thrown out of the efficiency, after all, the collection call stack is relatively time-consuming.

In fact, we don't have to deal with every throw, the key is whether we can identify the exceptions we want to deal with.

In this case, we can precisely, because all the unhandled exception, eventually will be unified on the wxentry, then we just hook personality routine, to see if the current unwind is not wxentry it can be!

#include <execinfo.h> #include <dlfcn.h> #include <cxxabi.h> #include <unwind.h>

#include <iostream>
Using namespace Std;void test_func1 (); static Personality_func gs_gcc_pf = Null;static void Hook_personality_func () {Gs_ GCC_PF = (personality_func) dlsym (Rtld_next, "__gxx_personality_v0");} static int Print_call_stack () {//to do.} extern "C" _unwind_reason_code__gxx_personality_v0 (int version, _unwind_action actions, _unwind _exception_class exception_class, struct _unwind_exception *ue_header, struct _unwind_context *c Ontext) {_unwind_reason_code Code = GS_GCC_PF (version, Actions, Exception_class, ue_header, context); if (_urc_handler_found = = code) {//Find the address of the catch all function//instruction within the current function void* cur_ip = (void*) (_unwind_get IP (context)); Dl_info info; if (Dladdr (CUR_IP, &info)) {if (info.dli_saddr = = &test_func1) {/ /The current function is the target function print_call_stack (); }}} return code;} void Test_func3 () {char* p = New char[2222222222222]; cout << "Test func3" << Endl;} void Test_func2 () {cout << "test Func2" << Endl; try {test_func3 (); } catch (int) {cout << "Catch 2" << Endl; }}void test_func1 () {cout << "test func1" << Endl; try {test_func2 (); } catch (...) {cout << "catch 1" << Endl; }}int Main () {hook_personality_func (); Test_func1 (); return 0;}

In the above code, personality routine returns _urc_handler_found means that the corresponding landing pad is found in the current function frame, and then we try to determine if the function is our target function, If yes, do the appropriate processing immediately. This procedure is obviously better than Hook __cxa_throw, after all, only for a function to do the processing, of course, compared to the original exception, there is a certain loss of efficiency, see How to choose, the pursuit of convenient debug is bound to pay some price.

C + + exception handling (1)

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.