1. Example analysis EH
So far, I still stay in the range of C and C , but this time I have to talk about the assembly language. Objective: The implementation of Visual C to the Throw and Catch of EH. This article is not a giant, after all, my principle is to pay only attention (C / C ) language itself. However, simply revealing the implementation of EH is helpful to understand and trust EH.
1.1 The only thing we are afraid
When the stack is decont in the throw process, the EH tracks which partial object needs the designer, pre-arranged the call must be called, and the control is given to the correct exception handler. In order to complete the recording and management of EH, the compiler is injecting the data, instructions, and library references in the generated code.
Unfortunately, many programmers (and their manager) hate this implantation behavior causes excessive code expansion. They feel panic, think that EH will weake the value of the program. So, I think that EH touches people's fear of unknown: because there is no clear expression of EH in the source code, they will be the worst estimate.
In order to overcome this fear, let's analyze the EH through a short Visual C code.
1.2 cases 1: baseline version
Generate a new C source file EH.CPP as follows:
Class C
{
PUBLIC:
C ()
{
}
~ C ()
{
}
}
Void F1 ()
{
C x1;
}
int main ()
{
F1 ();
Return 0;
}
Then, create a new Visual C console project and contain EH.CPP as the unique source file. Use the default item properties, but open the "Generate Source / Compilation Mix .asm File" option. Compile the Debug version. On my machine, the resulting EH.EXE is 23,040 bytes.
Open an EH.ASM file, you will find that the F1 () function is very close to it: set the stack frame, call the XL constructor and the destructor, and then reset the stack frame. In particular, you will notice that there is no EH product or record - is not surprising, because the program does not throw or capture any exceptions.
1.3 Example 2: Single Assembly Processing Function
Now change F1 to the following form:
Void F1 ()
{
C x1;
Try
{
}
Catch (char)
{
}
}
Rebate EH.EXE, then pay attention to the file size. On my machine, the size increases from 23,040 bytes to 29,696 bytes. Some heartbeats, EH led to an increase in the size of 29% of the file. But look at the absolute increase, only 6,656 bytes, and most of them come from fixed-size libraries for sale. The remaining small amount is additional to code and data in EH.Obj.
In EH.ASM, you can find a symbol __ $ EHREC $ defined a constant value, which indicates the offset of the stack frame. Each function references the __ $ EHREC $, and the compiler secretly defines a partial "EH Record" record object in its code.
The EH record is temporary: and need to have a permanent static record in the code, where they exist in the stack, generated when the function is entered, and the function exits is disappeared. The compiler increases the EH record (and is maintained by local code) when the function needs to be subjected to local objects.
Incidentally, some functions do not require EH records. See this, increase the second function:
Void F2 ()
{
}
There is no object and exception. Re-compile the program. EH.ASM Displays the F1 () stack to include an EH record as before, but there is no in the stack of F2 (). However, if code is changed to this: Void F2 ()
{
C x2;
F1 ();
}
F2 () Now defines a partial EH record, even if F2 () does not have a TRY block. why? Because F2 () is called F1 (), F1 () may throw an abnormally and terminate F2 (), it is necessary to prepare X2 early.
Conclusion: If a function containing partial objects does not have a clear handling exception, it may pass an exception thrown by others, then the function still needs an EH record and the corresponding maintenance code.
Do you worry about you? As long as the short-circuit exception chain is fine. In our example, the definition of F1 () is changed:
Void f1 () throw ()
{
C x1;
Try
{
}
Catch (char)
{
}
}
Now F1 () promises not to throw it. As a result, F2 () does not need to pass the exception of F1 (), and no EH record is required. You can recompile the program to verify that EH.ASM and finding the code of F2 () no longer mention __ $ EHREC $.
1.4 cases 3: Multiple exception handle functions
EH Record and its support code are not only the only records introduced by compilation. Each processing function of a given TRY block, the compiler also created an entry table. Want to see some, save the current EH.ASM, and expand F1 () to:
Void f1 () throw ()
{
C x1;
Try
{
}
Catch (char)
{
}
Catch (int)
{
}
Catch (long)
{
}
Catch (unsigned)
{
}
}
Re-compile, then compare EH.ASM twice.
(Reminder: The EH.ASM listed below, I have not ignored the unrelated thing, and I have not used omitted number to replace what. The exact label name may not be the same in your system. Do not look at these in the eyes of assembly language analyzers. Code.)
In my EH.ASM, the relevant names, descriptors and comments are as follows:
Public ?? _ r0d @ 8; char `RTTI Type Descriptor '
Public ?? _ rry @ 8; int `RTTI Type Descriptor '
Public ?? _ r0j @ 8; Long `RTTI Type Descriptor '
Public ?? _ r0i @ 8; unsigned int `RTTI Type Descriptor '
_Data segment
?? _ r0d @ 8 DD flat: ?? _7type_info @@ 6b @; char` RTTI Type Descriptor '
DD ...
DB '.d', ...
_Data Ends
_Data segment
?? _ r0h @ 8 DD flat: ?? _7type_info @@ 6b @; int `rtti type descriptor '
DD ...
DB '.h', ...
_Data Ends
_Data segment
?? _ r0j @ 8 DD flat: ?? _7type_info @@ 6b @; long `rtti type descriptor '
DD ...
DB '.j', ...
_Data Ends
_Data segment
?? _ r0i @ 8 DD flat: ?? _7type_info @@ 6b @; unsigned int `RTTI Type Descriptor'dd ...
DB '.i', ...
_Data Ends
(For "RTTI Type Descriptor" and "Type_Info" comment prompt me, Visual C uses the same type name descriptor when EH and RTTI.)
The compiler also generates references to the type descriptor defined in the xData @ x. Each type corresponds to an address that captures this type of exception handler function. This descriptor / processing function is published in the distribution of an EH library code distribution. These are also copied from my EH.ASM, plus comments and charts:
XData $ x segment
$ T214 DD ...
DD ...
DD Flat: $ T217; ---
DD ...;
DD Flat: $ T218; --- | ---
DD 2 dup (...); | | |
ORG $ 4; | | | | |
; | | | |
$ T217 DD ...; <- |
DD ...;
DD ...;
DD ...;
; |
$ T218 DD ...; <------
DD ...
DD ...
DD 04H; # of Handlers
DD Flat: $ T219; ---
ORG $ 4; | |
; |
$ T219 DD ...; <-
DD flat: ?? _ r0d @ 8; Char RTTI Type Descriptor
DD ...
DD Flat: $ 1; catch (char) address
DD ...
DD flat: ?? _ R0H @ 8; int RTTI Type Descriptor
DD ...
DD Flat: $ 1; catch (int) Address
DD ...
DD flat: ?? _ r0j @ 8; Long RTTI Type Descriptor
DD ...
DD Flat: $ 1; catch (long) Address
DD ...
DD flat: ?? _ r0i @ 8; unsigned int RTTI Type Descriptor
DD ...
DD flat: $ 1; catch (unsigned int) Address
XData $ x Ends
The sputum header (code number $ T214, $ T217, and $ T218) is F1 () exclusive, and all exception handler for F1 () is shared. Each entry item of $ T219 is a specific exception handler that belongs to F1 ().
More generally, the compiler generates a division table header for each of the TRY blocks, adds an entry entry for each exception handler. Type descriptors share all divisions of the program. (For example, all CATCH (long) processing functions in the program references the same ?? _ r0j @ 8 type descriptor.)
Summary: To reduce the space overhead of EH, minimize the number of functions in the program, minimize the number of abnormal processing functions in the function, minimize the exception type captured by the exception handler.
1.5 cases: throwing
Follow all things in "Throwing an Abnormality". Change the TRY statement of F1 () to this:
Try
{
Throw 123; // Type 'int' Exception}
Re-compile the program, open EH.ASM, pay attention to new things (I added annotations and charts).
; in these exported names, 'h' is The RTTI Type Descriptor
Code for 'int' - Which Matches the data type of
; Thrown Exception Value 123
PUBLIC __TI1H
PUBLIC __CTA1H
PUBLIC __CT ?? _ R0H @ 84
; EH Library Routine That Actually Throws Exceptions
EXTRN __CXXTHROWEXCEPTION @ 8: Near
; New Static Data Blocks Used by Library
When Throwing 'int' Exception
XData $ x segment
__CT ?? _ R0H @ 84 DD ...; <------
DD flat: ?? _ R0H @ 8; | ?? _ rry @ 8 is rtti 'int
| | TYPE DESCRIPTOR
DD ...;
DD ...;
ORG $ 4; | |
DD ...;
DD ...;
; |
__CTA1H DD ...; <- |
DD flat: __ ct ?? _ rry @ 84; --- | ---
; |
__Ti1h dd ...; | __ti1h is argument passed to
DD ...; | __CXXXTHROWEXCEPTION @ 8
DD ...;
DD flat: __ CTA1H; ---
XData $ x Ends
As with the type descriptor, these new data blocks are shared all programs, for example, all INT anomalous code references __ti1h. Also pay attention: The same type descriptor is referenced by an exception handler and a throw statement.
Turn to F1 (), the relevant parts are as follows:
void f1 () throw ()
; {
; TRY
; {
...
Push $ l224; address of code to adjut stack frame via handler
Dispatch Table. Invoked by __cxxthrowexception @ 8.
...
Throw 123;
PUSH OFFSET FLAT: __ TI1H; Address of Data Area Diagramed
; ABOVE
MOV DWORD PTR $ T213 [EBP], 123; 123 Is The Exception's Value
Lea Eax, DWORD PTR $ T213 [EBP]
Push EAX
Call __cxxthrowexception @ 8; Call Into EH LIBRARY, WHICH IN; Turn Eventually Calls $ 1 L224
And $ l216 a.k.a. 'catch (int) "
}
; // ...
Catch (int)
$ L216:
; {
Mov Eax, $ L182; RETURN TO EH LIBRARY, Which Jumps To $ l182
Ret 0
}
; // ...
$ L182:
; // Call Local-Object Destructors, Clean Up Stack, Return
}
$ L224:; this label referened by 'Try' Code.
Mov Eax, Offset Flat: $ T223; $ T223 IS HANDLER Dispatch Table, What
Had Previously Been Label $ T214
Before we added 'Throw 123'
JMP ___cxxframehandler; Internal Library Routine
When the program is running, __ cxxthrowexception @ 8 (the library function of the EH) calls the address of $ 16, CATCH (int) process. When the process function ends, the program continues to run down the code in the EH library, jump to $ 124, continue down and eventually skip to $ l182. This label is the termination of F1 () and the address of the Cleanup code, which is called X1 destructor. You can verify using a single step under the debugger.
1.6 small knot
All abnormal treatment systems have led to overhead. Unless you are willing to perform code without any abnormal security system, you must agree to pay the speed and space. EH as the language characteristic: The compiler explicitly knows the implementation of the EH and optimizes it accordingly.
In addition to optimization of the compiler, you still have many ways to optimize. In future articles, I will reveal a particular way to minimize the cost of EH. Some methods are based on standard C , depending on the specific implementation of Visual C .