Analysis of Windows Structured Exception Handling

Recently, I have been troubled by a problem, that is, the written programs always crash for no reason. In some places, they know that there may be a problem, but in some places, there is no way to know what the problem is. What is even more difficult is that our program needs to serve customers 7x24 . Although it does not require real-time precision and zero errors, it cannot always be disconnected and lost data. Therefore, just by dealing with this problem, I found some solutions, how to capture access to illegal memory addresses or 0 divided by a number . Therefore, I encountered this structured exception handling . Today, I will give a brief introduction, so that when you encounter a related problem, you can first know the cause of the problem, and then how to solve it. Without further ado, let's get to the point.


## What is structured exception handling

Structured exception handling ( structured exception handling , hereinafter abbreviated as SEH ) is introduced into the operating system as a system mechanism, which has nothing to do with the language itself. Using SEH in our own programs allows us to concentrate on developing key functions, and uniformly handle possible exceptions in the program, making the program more concise and more readable.

Using SHE does not mean that errors that may appear in the code can be completely ignored, but we can separate the software workflow and software exception handling, and focus on important and urgent work first, and then deal with this may encounter various problems. kind of wrong important non-urgent questions (not urgent, but definitely important)

When SEH is used in a program , it becomes compiler dependent. The burden caused by it is mainly borne by the compiler. For example, the compiler will generate some tables to support the SEH data structure, and also provide a callback function.

Note:
Do not confuse SHE and C++ exception handling. C++ exception handling is expressed in the form of using the keywords catch and throw . The form of this SHE is different. In Windows Visual C++, it is implemented by the compiler and the SHE of the operating system.

Among all the mechanisms provided by the Win32 operating system, the most widely used undocumented mechanism is probably SHE . Mention SHE and words like *__try , __finally*, and *__except* may come to mind . SHE actually contains two functions: termination handling and exception handling


## Terminate processing

The termination handler ensures that no matter how a block of code (the protected code) exits, another block of code (the termination handler) is always called and executed. Its syntax is as follows:

__try
{
    //Guarded body
    //...
}
__finally
{
    //Terimnation handler
    //...
}

The **__try and __finally** keywords mark the two parts of the termination handler. The cooperative work of the operating system and the compiler ensures that no matter how the protected code part exits (whether it is a normal exit or an abnormal exit), the termination program will be called, that is, the **__finally** code block can be executed.


Normal and abnormal exit of try block

The try block may exit unnaturally due to return , goto , exception, etc., or it may exit naturally due to successful execution. But no matter how the try block is exited, the contents of the finally block will be executed.

int Func1()
{
    cout << __FUNCTION__ << endl;
    int nTemp = 0;
    __try{
        //正常执行
        nTemp = 22;
        cout << "nTemp = " << nTemp << endl;
    }
    __finally{
        //结束处理
        cout << "finally nTemp = " << nTemp << endl;
    }
    return nTemp;
}

int Func2()
{
    cout << __FUNCTION__ << endl;
    int nTemp = 0;
    __try{
        //非正常执行
        return 0;
        nTemp = 22;
        cout << "nTemp = " << nTemp << endl;
    }
    __finally{
        //结束处理
        cout << "finally nTemp = " << nTemp << endl;
    }
    return nTemp;
}

The result is as follows:

Func1
nTemp = 22  //正常执行赋值
finally nTemp = 22  //结束处理块执行

Func2
finally nTemp = 0   //结束处理块执行

As can be seen from the above example, the premature execution of the return statement can be prevented by using a termination handler . When the return statement view exits the try block, the compiler will let the finally code block execute before it. When accessing a variable through a semaphore in multi-threaded programming, if an exception occurs, whether the semaphore can be successfully used, so that the thread will not occupy a semaphore all the time. When the finally block is executed, the function returns.

In order to make the whole mechanism work, the compiler must generate some extra code, and the system must also perform some extra work, so you should avoid using the return statement in the try code block when writing code, because it will affect the performance of the application, for Simple demos are not a big problem. For programs that need to run uninterrupted for a long time, it is better to take it easy. A keyword **__leave** will be mentioned below, which can help us find code with local expansion overhead.

A good rule of thumb: Do not include statements in the termination handler that make the try block exit early, this means removing return, continue, break, goto , etc. statements from the try block and finally block, and placing these statements in the termination handler outside the program. The advantage of this is that there is no need to capture early exits in try blocks, so that the amount of code generated by the compiler is minimal, and the running efficiency and code readability of the program are improved.


#### The cleanup function of finally block and its impact on program structure

In the process of coding, it is necessary to add the need to detect whether the detection function is successfully executed. If it is successful, it needs to be executed. If it is unsuccessful, some additional cleanup work is required, such as releasing memory, closing handles, etc. If there are not many detections, it will have no effect; but if there are many detections and the logical relationship in the software is complex, it often requires a lot of energy to realize tedious detection and judgment. As a result, the program looks complicated in structure, which greatly reduces the readability of the program, and the size of the program continues to increase.

I have a deep understanding of this problem. In the past, when writing VBA to call Word through COM , it was necessary to obtain objects layer by layer, determine whether the object was successfully acquired, perform related operations, and then release the object. After a process, it was originally one or two lines. It takes dozens of lines to write VBA code or C++ (it depends on how many objects are operated).

Here's a method for everyone to see, why some people like scripting languages ​​but not C++.

In order to operate Office more logically and hierarchically , Microsoft divides the application ( Application ) into the following tree structure according to its logical functions

Application(WORD 为例,只列出一部分)
  Documents(所有的文档)
        Document(一个文档)
            ......
  Templates(所有模板)
        Template(一个模板)
            ......
  Windows(所有窗口)
        Window
        Selection
        View
        .....
  Selection(编辑对象)
        Font
        Style
        Range
        ......
  ......

Only by understanding the logical level, we can correctly manipulate Office . For example, if given a VBA statement:

Application.ActiveDocument.SaveAs "c:\abc.doc"

Then, we know that the process of this operation is:

  1. The first step is to get the Application
  2. The second step, get ActiveDocument from Application
  3. The third step, call the Document function SaveAs , the parameter is a string file name.

This is just a minimal VBA code. A slightly more complicated one is as follows, in the selected place, insert a bookmark:

 ActiveDocument.Bookmarks.Add Range:=Selection.Range, Name:="iceman"

The process here is as follows:

  1. Get Application
  2. Get ActiveDocument
  3. Get Selection
  4. Get Range
  5. Get Bookmarks
  6. Call the method Add

Judgment is required when acquiring each object, and error handling, object release, etc. are also required. Here's the pseudo code, it's a bit long to write it all out

#define RELEASE_OBJ(obj) if(obj != NULL) \
                        obj->Realse();

BOOL InsertBookmarInWord(const string& bookname)
{
    BOOL ret = FALSE;
    IDispatch* pDispApplication = NULL;
    IDispatch* pDispDocument = NULL;
    IDispatch* pDispSelection = NULL;
    IDispatch* pDispRange = NULL;
    IDispatch* pDispBookmarks = NULL;
    HRESULT hr = S_FALSE;

    hr = GetApplcaiton(..., &pDispApplication);
    if (!(SUCCEEDED(hr) || pDispApplication == NULL))
        return FALSE;

    hr = GetActiveDocument(..., &pDispDocument);
    if (!(SUCCEEDED(hr) || pDispDocument == NULL)){
        RELEASE_OBJ(pDispApplication);
        return FALSE;
    }

    hr = GetActiveDocument(..., &pDispDocument);
    if (!(SUCCEEDED(hr) || pDispDocument == NULL)){
        RELEASE_OBJ(pDispApplication);
        return FALSE;
    }

    hr = GetSelection(..., &pDispSelection);
    if (!(SUCCEEDED(hr) || pDispSelection == NULL)){
        RELEASE_OBJ(pDispApplication);
        RELEASE_OBJ(pDispDocument);
        return FALSE;
    }

    hr = GetRange(..., &pDispRange);
    if (!(SUCCEEDED(hr) || pDispRange == NULL)){
        RELEASE_OBJ(pDispApplication);
        RELEASE_OBJ(pDispDocument);
        RELEASE_OBJ(pDispSelection);
        return FALSE;
    }

    hr = GetBookmarks(..., &pDispBookmarks);
    if (!(SUCCEEDED(hr) || pDispBookmarks == NULL)){
        RELEASE_OBJ(pDispApplication);
        RELEASE_OBJ(pDispDocument);
        RELEASE_OBJ(pDispSelection);
        RELEASE_OBJ(pDispRange);
        return FALSE;
    }

    hr = AddBookmark(...., bookname);
    if (!SUCCEEDED(hr)){
        RELEASE_OBJ(pDispApplication);
        RELEASE_OBJ(pDispDocument);
        RELEASE_OBJ(pDispSelection);
        RELEASE_OBJ(pDispRange);
        RELEASE_OBJ(pDispBookmarks);
        return FALSE;
    }
    ret = TRUE;
    return ret;

This is just pseudo-code. Although goto can also be used to reduce lines of code, errors will occur if goto is not used properly. If you are not careful in the following program, goto will go to the place where it should not be obtained.

BOOL InsertBookmarInWord2(const string& bookname)
{
    BOOL ret = FALSE;
    IDispatch* pDispApplication = NULL;
    IDispatch* pDispDocument = NULL;
    IDispatch* pDispSelection = NULL;
    IDispatch* pDispRange = NULL;
    IDispatch* pDispBookmarks = NULL;
    HRESULT hr = S_FALSE;

    hr = GetApplcaiton(..., &pDispApplication);
    if (!(SUCCEEDED(hr) || pDispApplication == NULL))
        goto exit6;

    hr = GetActiveDocument(..., &pDispDocument);
    if (!(SUCCEEDED(hr) || pDispDocument == NULL)){
        goto exit5;
    }

    hr = GetActiveDocument(..., &pDispDocument);
    if (!(SUCCEEDED(hr) || pDispDocument == NULL)){
        goto exit4;
    }

    hr = GetSelection(..., &pDispSelection);
    if (!(SUCCEEDED(hr) || pDispSelection == NULL)){
        goto exit4;
    }

    hr = GetRange(..., &pDispRange);
    if (!(SUCCEEDED(hr) || pDispRange == NULL)){
        goto exit3;
    }

    hr = GetBookmarks(..., &pDispBookmarks);
    if (!(SUCCEEDED(hr) || pDispBookmarks == NULL)){
        got exit2;
    }

    hr = AddBookmark(...., bookname);
    if (!SUCCEEDED(hr)){
        goto exit1;
    }

    ret = TRUE;
exit1:
    RELEASE_OBJ(pDispApplication);
exit2:
    RELEASE_OBJ(pDispDocument);
exit3:
    RELEASE_OBJ(pDispSelection);
exit4:
    RELEASE_OBJ(pDispRange);
exit5:
    RELEASE_OBJ(pDispBookmarks);
exit6:
    return ret;

Here, the method is rewritten through SEH 's termination handler, so it is more clear.

BOOL InsertBookmarInWord3(const string& bookname)
{
    BOOL ret = FALSE;
    IDispatch* pDispApplication = NULL;
    IDispatch* pDispDocument = NULL;
    IDispatch* pDispSelection = NULL;
    IDispatch* pDispRange = NULL;
    IDispatch* pDispBookmarks = NULL;
    HRESULT hr = S_FALSE;

    __try{
        hr = GetApplcaiton(..., &pDispApplication);
        if (!(SUCCEEDED(hr) || pDispApplication == NULL))
            return FALSE;

        hr = GetActiveDocument(..., &pDispDocument);
        if (!(SUCCEEDED(hr) || pDispDocument == NULL)){
            return FALSE;
        }

        hr = GetActiveDocument(..., &pDispDocument);
        if (!(SUCCEEDED(hr) || pDispDocument == NULL)){
            return FALSE;
        }

        hr = GetSelection(..., &pDispSelection);
        if (!(SUCCEEDED(hr) || pDispSelection == NULL)){
            return FALSE;
        }

        hr = GetRange(..., &pDispRange);
        if (!(SUCCEEDED(hr) || pDispRange == NULL)){
            return FALSE;
        }

        hr = GetBookmarks(..., &pDispBookmarks);
        if (!(SUCCEEDED(hr) || pDispBookmarks == NULL)){
            return FALSE;
        }

        hr = AddBookmark(...., bookname);
        if (!SUCCEEDED(hr)){
            return FALSE;
        }

        ret = TRUE;
    }
    __finally{
        RELEASE_OBJ(pDispApplication);
        RELEASE_OBJ(pDispDocument);
        RELEASE_OBJ(pDispSelection);
        RELEASE_OBJ(pDispRange);
        RELEASE_OBJ(pDispBookmarks);
    }
    return ret;

The functions of these functions are the same. You can see that the cleanup functions (RELEASE_OBJ) in InsertBookmarInWord are everywhere, while the cleanup functions in InsertBookmarInWord3 are all concentrated in the finally block. If you read the code, you only need to look at the content of the try block to understand the program flow. These two functions themselves are very small, you can experience the difference between these two functions in detail.


keyword __leave
---

Using the **__leave keyword in a try block will cause the program to jump to the end of the try block, thus naturally entering the finally block. For InsertBookmarInWord3 in the above example, the return in the try block can be completely replaced with __leave**. The difference between the two is that using return will cause try to exit the system prematurely, which will cause local expansion and increase system overhead. If **__leave** is used, the try block will be exited naturally , and the overhead will be much smaller.

BOOL InsertBookmarInWord4(const string& bookname)
{
    BOOL ret = FALSE;
    IDispatch* pDispApplication = NULL;
    IDispatch* pDispDocument = NULL;
    IDispatch* pDispSelection = NULL;
    IDispatch* pDispRange = NULL;
    IDispatch* pDispBookmarks = NULL;
    HRESULT hr = S_FALSE;

    __try{
        hr = GetApplcaiton(..., &pDispApplication);
        if (!(SUCCEEDED(hr) || pDispApplication == NULL))
            __leave;

        hr = GetActiveDocument(..., &pDispDocument);
        if (!(SUCCEEDED(hr) || pDispDocument == NULL))
            __leave;

        hr = GetActiveDocument(..., &pDispDocument);
        if (!(SUCCEEDED(hr) || pDispDocument == NULL))
            __leave;

        hr = GetSelection(..., &pDispSelection);
        if (!(SUCCEEDED(hr) || pDispSelection == NULL))
            __leave;

        hr = GetRange(..., &pDispRange);
        if (!(SUCCEEDED(hr) || pDispRange == NULL))
            __leave;

        hr = GetBookmarks(..., &pDispBookmarks);
        if (!(SUCCEEDED(hr) || pDispBookmarks == NULL))
            __leave;

        hr = AddBookmark(...., bookname);
        if (!SUCCEEDED(hr))
            __leave;

        ret = TRUE;
    }
    __finally{
        RELEASE_OBJ(pDispApplication);
        RELEASE_OBJ(pDispDocument);
        RELEASE_OBJ(pDispSelection);
        RELEASE_OBJ(pDispRange);
        RELEASE_OBJ(pDispBookmarks);
    }
    return ret;
}


## exception handler

Software exceptions are something we don’t want to see, but errors still occur from time to time. For example, the CPU captures problems such as illegal memory access and division by 0. Once such errors are detected, relevant exceptions are thrown, and the operating system will give us the application The program has a chance to see the type of exception, and the runner handles the exception itself. The exception handler structure code is as follows

  __try {
      // Guarded body
    }
    __except ( exception filter ) {
      // exception handler
    }

Pay attention to the keyword **__except**, any try block must be followed by a finally code block or except code block, but there cannot be a finally and an except block at the same time after a try , nor can there be multiple finnaly or except blocks at the same time, but you can nested within each other


## Exception handling basic process

int Func3()
{
    cout << __FUNCTION__ << endl;
    int nTemp = 0;
    __try{
        nTemp = 22;
        cout << "nTemp = " << nTemp << endl;
    }
    __except (EXCEPTION_EXECUTE_HANDLER){
        cout << "except nTemp = " << nTemp << endl;
    }
    return nTemp;
}

int Func4()
{
    cout << __FUNCTION__ << endl;
    int nTemp = 0;
    __try{
        nTemp = 22/nTemp;
        cout << "nTemp = " << nTemp << endl;
    }
    __except (EXCEPTION_EXECUTE_HANDLER){
        cout << "except nTemp = " << nTemp << endl;
    }
    return nTemp;
}

The result is as follows:

Func3
nTemp = 22  //正常执行

Func4
except nTemp = 0 //捕获异常,

The try block in Func3 is just a simple operation, so it will not cause an exception, so the code in the except block will not be executed. The view of the try block in Func4 is divided by 22 by 0, causing the CPU to capture this event and throw it, and the system locates the except block. , to handle the exception, there is an exception filter expression, there are three definitions in the system (defined in Excpt.h of Windows):

1. EXCEPTION_EXECUTE_HANDLER:
    我知道这个异常了,我已经写了代码来处理它,让这些代码执行吧,程序跳转到except块中执行并退出
2. EXCEPTION_CONTINUE_SERCH
    继续上层搜索处理except代码块,并调用对应的异常过滤程序
3. EXCEPTION_CONTINUE_EXECUTION
    返回到出现异常的地方重新执行那条CPU指令本身

Faces are used in two basic ways:

  • Method 1: Use one of the three return values ​​of the filter directly

    __try {
       ……
    }
    __except ( EXCEPTION_EXECUTE_HANDLER ) {
       ……
    }
  • Method 2: Custom filter
    ```
    __try {
    ...
    }
    __except ( MyFilter( GetExceptionCode() ) )
    {
    ...
    }

LONG MyFilter ( DWORD dwExceptionCode )
{
if ( dwExceptionCode == EXCEPTION_ACCESS_VIOLATION )
return EXCEPTION_EXECUTE_HANDLER ;
else
return EXCEPTION_CONTINUE_SEARCH ;
}


<br>

## .NET4.0中捕获SEH异常
---

在.NET 4.0之后,CLR将会区别出一些异常(都是SEH异常),将这些异常标识为破坏性异常(Corrupted State Exception)。针对这些异常,CLR的catch块不会捕捉这些异常,一下代码也没有办法捕捉到这些异常。

try{
//....
}
catch(Exception ex)
{
Console.WriteLine(ex.ToString());
}


因为并不是所有人都需要捕获这个异常,如果你的程序是在4.0下面编译并运行,而你又想在.NET程序里捕捉到SEH异常的话,有两个方案可以尝试:

 - 在托管程序的.config文件里,启用legacyCorruptedStateExceptionsPolicy这个属性,即简化的.config文件类似下面的文件:

App.Config

这个设置告诉CLR 4.0,整个.NET程序都要使用老的异常捕捉机制。

-  在需要捕捉破坏性异常的函数外面加一个HandleProcessCorruptedStateExceptions属性,这个属性只控制一个函数,对托管程序的其他函数没有影响,例如:

[HandleProcessCorruptedStateExceptions]
try{
//....
}
catch(Exception ex)
{
Console.WriteLine(ex.ToString());
}
```

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324673695&siteId=291194637