📜 ⬆️ ⬇️

C ++ exception handling under the hood. Part 2

Continuing to translate a series of articles about exception handling in C ++

1 part
3 part

C ++ exceptions under the hood: cute personality


Our trip to the amazing journey of exploring the work of exceptions is far from the end, we still have to learn something called "call frame information" that helps the Unwind library to unfold the stack, and also what the compiler writes in something called LSDA, in which determines which errors the method can handle. And also we have already learned that the majority of magic occurs in a personal function that we have not yet seen in action. Let's summarize what we already know about forwarding and catching errors (or, more precisely, what we already know about how the abandoned will be intercepted):
')

As long as we learned all this, it was the right time to write our own personalized function! Our ABI is used so that when an exception is thrown, print the following:

alloc ex 1 __cxa_throw called no one handled __cxa_throw, terminate! 

Go back to our mycppabi and add something like this:

 void __gxx_personality_v0() { printf("Personality function FTW\n"); } 

Let me remind you, you can see the actual code in my githab repository .

And, of course, when we launch the application, our personal function will be called. We see that on the right track, as well as we know what we want from it. Why not use the correct function definition:

 _Unwind_Reason_Code __gxx_personality_v0 ( int version, _Unwind_Action actions, uint64_t exceptionClass, _Unwind_Exception* unwind_exception, _Unwind_Context* context); 

If we put it in our mycppabi.cpp , we get:

 #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <stdint.h> namespace __cxxabiv1 { struct __class_type_info { virtual void foo() {} } ti; } #define EXCEPTION_BUFF_SIZE 255 char exception_buff[EXCEPTION_BUFF_SIZE]; extern "C" { void* __cxa_allocate_exception(size_t thrown_size) { printf(&"alloc ex %i\n", thrown_size); if (thrown_size > EXCEPTION_BUFF_SIZE) printf("Exception too big"); return &exception_buff; } void __cxa_free_exception(void *thrown_exception); #include <unwind.h> typedef void (*unexpected_handler)(void); typedef void (*terminate_handler)(void); struct __cxa_exception { 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; }; void __cxa_throw(void* thrown_exception, struct type_info *tinfo, void (*dest)(void*)) { printf("__cxa_throw called\n"); __cxa_exception *header = ((__cxa_exception *) thrown_exception - 1); _Unwind_RaiseException(&header->unwindHeader); // __cxa_throw     printf("no one handled __cxa_throw, terminate!\n"); exit(0); } void __cxa_begin_catch() { printf("begin FTW\n"); } void __cxa_end_catch() { printf("end FTW\n"); } _Unwind_Reason_Code __gxx_personality_v0 ( int version, _Unwind_Action actions, uint64_t exceptionClass, _Unwind_Exception* unwind_exception, _Unwind_Context* context) { printf("Personality function FTW!\n"); } } 

Compile and link everything, then run and start to analyze each parameter of this function with a little help from gdb:

 Breakpoint 1, __gxx_personality_v0 (version=1, actions=1, exceptionClass=134514792, unwind_exception=0x804a060, context=0xbffff0f0) 


Well, we have a working (well, at least, linkable) personal function. It does not do very much, so we will further fill it with real behavior and try to make it handle exceptions.

C ++ exceptions under the hood: two-phase pass


We ended the previous chapter by adding a personal function that Unwind can call. She, in general, does nothing, for now. Our ABI already implements half of the work on throwing exceptions and catching them, but the personal function still has to learn how to correctly select the block (landing pad) for error handling. We start this chapter with an attempt to understand the parameters that the __gxx_personality_v0 function accepts and add real behavior to it: we will output "yes, the current stack unit can handle the exception".

We already said that we wanted to spit on the version or class of exception in our mini ABI. Well, let's score for now the context too: we just handle each exception in the first frame of the stack. Do not forget to substitute the try / catch block in the function directly above the call to the method that throws exceptions, otherwise everything will break. It is also worth remembering that the catch block will ignore the exception type. How do we let the _Unwind_ function know that we want to handle an exception?

_Unwind_Reason_Code is the return value of a personal function that says to the Unwind function: did we find the landing pad to handle the error or not. Let us return from our personal function * _URC_HANDLER_FOUND and see what happens:

 alloc ex 1 __cxa_throw called Personality function FTW Personality function FTW no one handled __cxa_throw, terminate! 

See it? We told the advertiser that we had found the handler, and he called the personal function again! What the hell is going on here?

Remember the action parameter? This is how Unwind tells us exactly what it wants, because exception handling occurs in two stages: search and cleanup (or _UA_SEARCH_PHASE and _UA_CLEANUP_PHASE). Let's go back to our recipe for error handling:


Attention should be paid to these two important things:

  1. Running in a two-phase mode for catching exceptions means that we can get the original and full stack trace exceptions (if we were deployed in one pass along with the deletion, we would not have a stack trace, or we would have to keep a copy of it!).
  2. Running _UA_CLEANUP_PHASE and re-calling for each frame, even if we know that the frame can handle an exception, is also very important: a personal function has the ability to call all destructors for objects created in this scope. This is what makes RAII exceptions (Resource Acquisition Is Initialization, Resource Acquisition Is Initialization) safe idiom.

Now we understand how the handler search phase works and we can continue the implementation of our personal function.

C ++ exceptions under the hood: catching our first exception


We have completed the previous chapter, having taught the personal function to respond to the advertiser. It is time to add real behavior __gxx_personality_v9 : we will teach it to process the passage in two phases.

Our personal function will take the form:

 _Unwind_Reason_Code __gxx_personality_v0 ( int version, _Unwind_Action actions, uint64_t exceptionClass, _Unwind_Exception* unwind_exception, _Unwind_Context* context) { if (actions &_UA_SEARCH_PHASE) { printf("Personality function, lookup phase\n"); return _URC_HANDLER_FOUND; } else if (actions & _UA_CLEANUP_PHASE) { printf("Personality function, cleanup\n"); return _URC_INSTALL_CONTEXT; } else { printf("Personality function, error\n"); return _URC_FATAL_PHASE1_ERROR; } } 

Let me remind you: the source code can be found on my github repository .

Run and see what happens:

 alloc ex 1 __cxa_throw called Personality function, lookup phase Personality function, cleanup try_but_dont_catch handled the exception catchit handled the exception 

It works, but something goes wrong: the handler inside the catch / try block never starts! This is due to the fact that the personal function tells the developer to "set context" (i.e. continue execution), but never tells which particular context. In this case, it is likely to continue execution after the landing pad, but this, I believe, is indefinite behavior. Next, we will see how to specify a point, where to continue with the execution of the code (landing pad), using the information available in .gcc_except_table (our old friend, LSDA).

C ++ exceptions under the hood: Unwind information of the current frame


We left our mini-ABI capable of throwing exceptions, and now we are working on catching them. We have implemented a personal function that can detect and listen to an exception, but it is not yet complete: even if it can notify the advertiser when he should stop, she still cannot run the code inside the error handler block. This is better than what we started with, but we still have a long way to go before creating a suitable ABI error handling system. Can we improve our code?

How can we tell the advertiser where our landing pad is so that we can continue executing the code inside our catch block? If we go back to the ABI specification , we’ll find there some context management functions that can be useful:


Let's look at these functions with gdb. On my car:

 Breakpoint 1, __gxx_personality_v0 (version=1, actions=6, exceptionClass=134515400, unwind_exception=0x804a060, context=0xbffff0f0) at mycppabi.cpp:77 84 const uint8_t* lsda = (const uint8_t*)_Unwind_GetLanguageSpecificData(context); 85 uintptr_t ip = _Unwind_GetIP(context) - 1; 86 uintptr_t funcStart = _Unwind_GetRegionStart(context); 87 uintptr_t ipOffset = ip - funcStart; 

If we investigate these variables, we will see that _Unwind_GetRegionStart points to the current stack frame (try_but_dont_catch) and that _Unwind_GetIp is the IP to the position where the next frame was called. _Unwind_GetRegionStart points us to the place where the exception was the first time it was passed, this is a bit difficult to explain, let's leave it for later. We also do not see the LSDA pointer here, but we can assume that it is right behind the function code, while _Unwind_GetLanguageSpecificData refers directly to the line after the end of the function.

 _Unwind_GetIP = (void *) 0x804861d _Unwind_GetRegionStart = (void *) 0x8048612 _Unwind_GetLanguageSpecificData = (void *) 0x8048e3c function pointer to try_but_dont_catch = 0x8048612 &<try_but_dont_catch()> (gdb) disassemble /m try_but_dont_catch Dump of assembler code for function try_but_dont_catch(): 10 void try_but_dont_catch() { [...] 11 try { 12 raise(); 0x08048619 <+7>: call 0x80485e8 <raise()> 13 } catch(Fake_Exception&) { 0x08048651 <+63>: call 0x804874a <__cxa_begin_catch()> 0x08048665 <+83>: call 0x804875e <__cxa_end_catch()> 0x0804866a <+88>: jmp 0x804861e <try_but_dont_catch()+12> 14 printf("Caught a Fake_Exception!\n"); 0x08048659 <+71>: movl $0x8048971,(%esp) 0x08048660 <+78>: call 0x80484c0 <puts@plt> 15 } 16 17 printf("try_but_dont_catch handled the exception\n"); 0x0804861e <+12>;: movl $0x8048948,(%esp) 0x08048625 <+19>: call 0x80484c0 <puts@plt> 18 } 0x0804862a <+24>: add $0x24,%esp 

With Unwind, we can now get enough information about the current frame of the stack to determine whether we can handle the exception or not, as well as how we should handle it. The last step we need before we can determine whether we can determine the landing pad or not is that we need to interpret the CFI information at the end of the function. This is part of the DWARF specification and its implementation is somewhat complicated. As in our ABI, we will use the required minimum.

C ++ exceptions under the hood: reading the CFI table


In order to properly handle exceptions, our personal function, which we implement in our ABI, must read LSDA to find out which frame (that is, which function) can handle the exception, and which exception, and also to find out where the landing pad (catch- block) can be found. The LSDA table is specified in the CFI format, and in this chapter we will learn how to read it.

CFI data can be read quite simply, but there are a few pitfalls that need to be taken into account. Two, in fact:

  1. There is very little documentation about .gcc_except_table (in fact, I found only a couple of letters about it), so we will have to watch a lot of source codes, as well as understand the disassembled code.
  2. Although the format itself is not hellishly complex, it uses LEB (Little Endian Base), which makes reading this table not so simple.

As far as I know, most of the DWARF data is encoded in LEB, which is a great idea to confuse programmers, and also to reduce the space for code for encoding ints of arbitrary length. Fortunately, we can count a little here: mostly, the LEB-encoded numbers are read by uint8_t, because we will not deal with large exception tables or something like that.

As always, the current version of the code for this chapter is in the repository .

Let's start analyzing CFI directly with disassembling and see if we can build something to read this data in our personal function. I reassigned the tags to make them more human-friendly. LSDA has three sections, try to define them below:

 .local_frame_entry: .globl __gxx_personality_v0 .section .gcc_except_table,"a",@progbits .align 4 

It's very simple: just a header saying that we are going to use __gxx_personality_v0 as a global one, and also lets the linker know that we are going to define the .gcc_except_table section.

Moving on:

 .local_lsda_1: #   .     .byte 0xff #    landing pads;  0, func's ptr #   (_Unwind_GetRegionStart) .byte 0 #   LSDA:   LLSDATT1  LLSDATTD1  #    LSDA,  .uleb128 .local_lsda_end - .local_lsda_call_site_table_header 

There is already much more information. These tags are very vague, but they follow the pattern. LSDA means a language-specific data zone, L at the beginning means "local", so it is local (for the translated module, .o file). data zone number one. Other labels follow the same pattern, but I did not take up their description. And they, in general, we do not need.

 .local_lsda_call_site_table_header: # Encoding of items in the landing pad table. Again, we don't care. .byte 0x1. # The length of the call site table (ie the landing pads) .uleb128 .local_lsda_call_site_table_end - .local_lsda_call_site_table 

Another boring headline, we go further:

 .local_lsda_call_site_table: .uleb128 .LEHB0-.LFB1 .uleb128 .LEHE0-.LEHB0 .uleb128 .L8-.LFB1 .uleb128 0x1 .uleb128 .LEHB1-.LFB1 .uleb128 .LEHE1-.LEHB1 .uleb128 0 .uleb128 0 .uleb128 .LEHB2-.LFB1 .uleb128 .LEHE2-.LEHB2 .uleb128 .L9-.LFB1 .uleb128 0 .local_lsda_call_site_table_end: 

This is much more interesting, here we see the call table with our own eyes. Somehow, in all these records, we have to find our landing pad. In accordance with some random page on the Internet, the format of each link should correspond to the structure:

 struct lsda_call_site_entry { //  IP  size_t cs_start; //  IP  size_t cs_len; // Landing pad  size_t cs_lp; //     size_t cs_action; }; 

Well, it looks like we are on the right track, however, we still do not know why there are 3 entry points here, when we defined only one langing pad. In any case, we can read a bit: considering the disassembled code, we can determine all CFI values ​​will be less than 128, which means that LEB-coding can be read as uchars. This makes our CFI read code much easier, and now we can see how to use it in our personal function further.

C ++ exceptions under the hood: and suddenly, reflection in C ++


Recall what we have already done: we learned how to forward errors, we wrote a personal function __gxx_personality_v0, which can identify and handle errors, also telling the stack stack when it should stop, but it still does not know how to determine the necessary catch block. We also learned how to read LSDA, now it’s all left to combine!

Let's do something like this and see if we are on the right path (note that this code can only work with uint8, and also probably not portable):

 struct LSDA_Header { uint8_t lsda_start_encoding; uint8_t lsda_type_encoding; uint8_t lsda_call_site_table_length; }; struct LSDA_Call_Site_Header { uint8_t encoding; uint8_t length; }; struct LSDA_Call_Site { LSDA_Call_Site(const uint8_t *ptr) { cs_start = ptr[0]; cs_len = ptr[1]; cs_lp = ptr[2]; cs_action = ptr[3]; } uint8_t cs_start; uint8_t cs_len; uint8_t cs_lp; uint8_t cs_action; }; _Unwind_Reason_Code __gxx_personality_v0 ( int version, _Unwind_Action actions, uint64_t exceptionClass, _Unwind_Exception* unwind_exception, _Unwind_Context* context) { if (actions & _UA_SEARCH_PHASE) { printf("Personality function, lookup phase\n"); return _URC_HANDLER_FOUND; } else if (actions & _UA_CLEANUP_PHASE) { printf("Personality function, cleanup\n"); const uint8_t* lsda = (const uint8_t*) _Unwind_GetLanguageSpecificData(context); LSDA_Header *header = (LSDA_Header*)(lsda); LSDA_Call_Site_Header *cs_header = (LSDA_Call_Site_Header*) (lsda + sizeof(LSDA_Header)); size_t cs_in_table = cs_header->length / sizeof(LSDA_Call_Site); //    cs_table_base  uint8,    //    const uint8_t *cs_table_base = lsda + sizeof(LSDA_Header) + sizeof(LSDA_Call_Site_Header); //    call site  for (size_t i=0; i < cs_in_table; ++i) { const uint8_t *offset = &cs_table_base[i * sizeof(LSDA_Call_Site)]; LSDA_Call_Site cs(offset); printf("Found a CS:\n"); printf("\tcs_start: %i\n", cs.cs_start); printf("\tcs_len: %i\n", cs.cs_len); printf("\tcs_lp: %i\n", cs.cs_lp); printf("\tcs_action: %i\n", cs.cs_action); } uintptr_t ip = _Unwind_GetIP(context); uintptr_t funcStart = _Unwind_GetRegionStart(context); uintptr_t ipOffset = ip - funcStart; return _URC_INSTALL_CONTEXT; } else { printf("Personality function, error\n"); return _URC_FATAL_PHASE1_ERROR; } } } 

Current Code

As you can see (if you run this code) all points in the call table are relative. Relative of what? Start function, of course. This means that if we want to get an EIP (instruction pointer) for a particular landing pad, all we need to do is add: _Unwind_GetRegionStart + LSDA_Call_Site.cs_Ip!

Finally, now we are able to solve our problem: let's change our personal function so that it performs the correct landing pad. Now we need to use another Unwind function to indicate where we want to continue execution from: _Unwind_SetIP . Change our personalized function again to launch the first landing pad.

 const uint8_t *cs_table_base = lsda + sizeof(LSDA_Header) + sizeof(LSDA_Call_Site_Header); for (size_t i=0; i < cs_in_table; ++i) { const uint8_t *offset = &cs_table_base[i * sizeof(LSDA_Call_Site)]; LSDA_Call_Site cs(offset); if (cs.cs_lp) { uintptr_t func_start = _Unwind_GetRegionStart(context); _Unwind_SetIP(context, func_start + cs.cs_lp); break; } } return _URC_INSTALL_CONTEXT; 

Try running this code and watch the wonderful eternal loop. Can you guess what went wrong? The answer is in the next chapter!

C ++ exceptions under the hood: setting context for the landing pad


In the last chapter, we finally wrote an almost working personal function. We can define each frame stack with available landing pads, and then tell Unwind exactly what we want to run. However, we got a small problem: to set the Unwind context to continue execution on the correct landing pad, we must set the current exception to the register. This, in general, means that the landing pad does not want to know which exception should be handled, so he will only say "I can't handle it." Unwind will then say "please try the next landing pad", but our ABI is so simple that he doesn't even have any idea how he should find another landing pad, and just tries to slip the same one. Again and again. We seem to have come up with the most contrived example for while (true)!

Fix the context for the landing pad and slightly improve our ABI:

 #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <stdint.h> namespace __cxxabiv1 { struct __class_type_info { virtual void foo() {} } ti; } #define EXCEPTION_BUFF_SIZE 255 char exception_buff[EXCEPTION_BUFF_SIZE]; extern "C" { void* __cxa_allocate_exception(size_t thrown_size) { printf("alloc ex %i\n", thrown_size); if (thrown_size > EXCEPTION_BUFF_SIZE) printf("Exception too big"); return &exception_buff; } void __cxa_free_exception(void *thrown_exception); #include <unwind.h> typedef void (*unexpected_handler)(void); typedef void (*terminate_handler)(void); struct __cxa_exception { 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; }; void __cxa_throw(void* thrown_exception, struct type_info *tinfo, void (*dest)(void*)) { printf("__cxa_throw called\n"); __cxa_exception *header = ((__cxa_exception *) thrown_exception - 1); _Unwind_RaiseException(&header->unwindHeader); // __cxa_throw never returns printf("no one handled __cxa_throw, terminate!\n"); exit(0); } void __cxa_begin_catch() { printf("begin FTW\n"); } void __cxa_end_catch() { printf("end FTW\n"); } /**********************************************/ /** * The LSDA is a read only place in memory; we'll create a typedef for * this to avoid a const mess later on; LSDA_ptr refers to readonly and * &LSDA_ptr will be a non-const pointer to a const place in memory */ typedef const uint8_t* LSDA_ptr; struct LSDA_Header { /** * Read the LSDA table into a struct; advances the lsda pointer * as many bytes as read */ LSDA_Header(LSDA_ptr *lsda) { LSDA_ptr read_ptr = *lsda; // Copy the LSDA fields start_encoding = read_ptr[0]; type_encoding = read_ptr[1]; ttype = read_ptr[2]; // Advance the lsda pointer *lsda = read_ptr + sizeof(LSDA_Header); } uint8_t start_encoding; uint8_t type_encoding; uint8_t ttype; }; struct LSDA_CS_Header { // Same as other LSDA constructors LSDA_CS_Header(LSDA_ptr *lsda) { LSDA_ptr read_ptr = *lsda; encoding = read_ptr[0]; length = read_ptr[1]; *lsda = read_ptr + sizeof(LSDA_CS_Header); } uint8_t encoding; uint8_t length; }; struct LSDA_CS { // Same as other LSDA constructors LSDA_CS(LSDA_ptr *lsda) { LSDA_ptr read_ptr = *lsda; start = read_ptr[0]; len = read_ptr[1]; lp = read_ptr[2]; action = read_ptr[3]; *lsda = read_ptr + sizeof(LSDA_CS); } // Note start, len and lp would be void*'s, but they are actually relative // addresses: start and lp are relative to the start of the function, len // is relative to start // Offset into function from which we could handle a throw uint8_t start; // Length of the block that might throw uint8_t len; // Landing pad uint8_t lp; // Offset into action table + 1 (0 means no action) // Used to run destructors uint8_t action; }; /**********************************************/ _Unwind_Reason_Code __gxx_personality_v0 ( int version, _Unwind_Action actions, uint64_t exceptionClass, _Unwind_Exception* unwind_exception, _Unwind_Context* context) { if (actions & _UA_SEARCH_PHASE) { printf("Personality function, lookup phase\n"); return _URC_HANDLER_FOUND; } else if (actions & _UA_CLEANUP_PHASE) { printf("Personality function, cleanup\n"); // Pointer to the beginning of the raw LSDA LSDA_ptr lsda = (uint8_t*)_Unwind_GetLanguageSpecificData(context); // Read LSDA headerfor the LSDA LSDA_Header header(&lsda); // Read the LSDA CS header LSDA_CS_Header cs_header(&lsda); // Calculate where the end of the LSDA CS table is const LSDA_ptr lsda_cs_table_end = lsda + cs_header.length; // Loop through each entry in the CS table while (lsda < lsda_cs_table_end) { LSDA_CS cs(&lsda); if (cs.lp) { int r0 = __builtin_eh_return_data_regno(0); int r1 = __builtin_eh_return_data_regno(1); _Unwind_SetGR(context, r0, (uintptr_t)(unwind_exception)); // Note the following code hardcodes the exception type; // we'll fix that later on _Unwind_SetGR(context, r1, (uintptr_t)(1)); uintptr_t func_start = _Unwind_GetRegionStart(context); _Unwind_SetIP(context, func_start + cs.lp); break; } } return _URC_INSTALL_CONTEXT; } else { printf("Personality function, error\n"); return _URC_FATAL_PHASE1_ERROR; } } } 

The current version of the code and a much more detailed description of LSDA .

Finally it works! We should get something like this:

 ./app alloc ex 1 __cxa_throw called Personality function, lookup phase Personality function, cleanup begin FTW Caught a Fake_Exception! end FTW try_but_dont_catch handled the exception catchit handled the exception 

Of course, we deceived Unwind a bit : we told him that he should handle all successive exceptions on the very first catch block. This turns catch (Exception &) into catch (...) and all hell will crush on us if the first function in the frame does not have a catch block. However, we went through the first stage in creating a very simple ABI!

Can we improve our code and make it handle exceptions correctly in the correct frame? Of course!

C ++ exceptions under the hood: several landing pads & gurus' teachings

After a hard road, we finally made a working personal function that allows us to handle errors without the help of lbstdc ++. It randomly handles all errors, but it works! Correct exception handling is a big question that we haven’t answered yet, but if we go back to LSDA, we will see something like this:

 .local_lsda_call_site_table: .uleb128 .LEHB0-.LFB1 .uleb128 .LEHE0-.LEHB0 .uleb128 .L8-.LFB1 .uleb128 0x1 .uleb128 .LEHB1-.LFB1 .uleb128 .LEHE1-.LEHB1 .uleb128 0 .uleb128 0 .uleb128 .LEHB2-.LFB1 .uleb128 .LEHE2-.LEHB2 .uleb128 .L9-.LFB1 .uleb128 0 .local_lsda_call_site_table_end: 

There are three landing pads, even if we write a single try / catch block. What's going on here?

If you look closely at the previous chapter, you will notice that I added some comments in the definition of the LSDA_CS structure:

 struct LSDA_CS { // len  lp   void*'s,   // : start and lp   , len //   //   ,     uint8_t start; //   uint8_t len; // Landing pad uint8_t lp; //  action table + 1 (0  " ") //     uint8_t action; }; 

There are many interesting things here, but first we will look at the structure field by field for this example:

 void foo() { L0: try { do_something(); L1: } catch (const Exception1& ex) { ... } catch (const Exception2& ex) { ... } catch (const ExceptionN& ex) { ... } catch (...) { } L2: } 


The fields we are interested in are start and len: in functions with a lot of try / catch blocks, we can determine whether we should handle exceptions, checking whether the instruction pointer (IP) is worth between the current frame and start and start + len.

This destroys the myth of how a function with several try / catch blocks can handle several exceptions, but we still do not know the answer to the question: why are three objects generated for a single landing pad? Other objects are placed as a likely place for cleaning actions or a landing pad that can be thrown.

Continuation

Source: https://habr.com/ru/post/279151/


All Articles