debugging custom filters for unhandled exceptions

转自http://www.debuginfo.com/articles/easywindbg.html#startupmessages

Updated: 09.10.2005

Introduction
Debugging custom filters
Somebody is overwriting my filter!
Enforcing your own filter
Sample code
Conclusion

Introduction

When our application crashes on the customer's site, we need as much information about the problem as possible. System tools like Dr. Watson can help to collect the necessary information, but their effectiveness depends on the configuration of the target system. If we do not want to depend on system configuration, custom filter for unhandled exceptions is a good solution. With the help of the custom filter, we can get notified about unhandled exceptions in the application, create detailed crash report, and sometimes even automatically send it to developers for investigation.

Unfortunately, custom filters for unhandled exceptions are not easy to debug. In addition, sometimes it can be difficult to ensure that our filter is properly registered, because other components of the application (including some system DLLs) might want to register their own filters. In this article, I will show how to overcome these problems and make our filters debuggable and reliable.

Debugging custom filters

So we have implemented a custom filter for unhandled exceptions (here is an example) and registered it using SetUnhandledExceptionFilter function. We simulate an unhandled exception, and our filter is invoked, but it looks like it has a bug – say, it does not properly create the minidump. We want to debug the filter, so we run the application under debugger and set a breakpoint at the beginning of the filter function. The exception is raised again, but there is a surprise – the breakpoint in our filter is not hit, and instead the debugger reports a second chance exception.

Second chance exception dialog

Pressing “Continue” does not help. What happened? The answer is simple – custom filters for unhandled exceptions are not called at all when the application is running under debugger. A bit later we will determine exactly why it happens, but now lets look for alternative ways of debugging our filter.

KB173652 offers some help, describing two possible approaches. The first approach is pretty simple, and can be used in nearly all situations where we cannot run the application under debugger – use tracing. It means that our old good friends – OutputDebugString, TRACE, ATLTRACE and other members of this family – can help us again.

Another approach is to modify our application (for debugging purposes only!) to execute the filter from __except clause:

 

__try
{
    FaultyFunc();
}
__except( MyCustomFilter( GetExceptionInformation() ) )
{
    _tprintf( _T("Exception handled./n") );
}

 

(see complete example here)

Filters called inside __except clauses are not skipped when the application is running under debugger. When FaultyFunc raises an exception, breakpoint in our filter will be hit and we will be able to debug the filter. An obvious limitation of this approach is that it is intrusive – we have to write additional code only to debug the filter.

There is also another intrusive approach to debugging filters (much less intrusive, though) – modify the filter to show a message box (or print a console message and then sleep) when it is called. Then we can run the application (but this time not under debugger), simulate an unhandled exception, and wait until the filter gets called and displays the message box. After that we can attach a debugger to the application, set breakpoint in the filter, and dismiss the message box. Our breakpoint will be hit, and we will be able to debug the filter. An example of this approach can be found here.

But is it really necessary to write additional code only to make debugging of the filter possible? May be there is a non-intrusive way of debugging a custom filter? Yes, there is one, and I am going to describe it now.

At the beginning, lets explore how the operating system calls custom filters for unhandled exceptions. If we look at the call stack of the first thread of any Win32 application, we can see that execution always starts at kernel32!BaseProcessStart function. This function receives a pointer to the application's main entry point (usually mainCRTStartup or WinMainCRTStartup in C++ applications) and calls it inside of __try..__except block:

 

VOID BaseProcessStart( PPROCESS_START_ROUTINE pfnStartAddr )
{
    __try
    {
        ExitThread( (pfnStartAddr)() );
    }
    __except( UnhandledExceptionFilter( GetExceptionInformation()) )
    {
        ExitProcess( GetExceptionCode() );
    }
}

 

(this code is a bit simplified; more complete code can be found in Figure 7 of this article)

Execution of all other threads in the process starts with a similar function – kernel32!BaseThreadStart:

 

VOID BaseThreadStart( PTHREAD_START_ROUTINE pfnStartAddr, PVOID pParam )
{
    __try
    {
        ExitThread( (pfnStartAddr)(pParam) );
    }
    __except( UnhandledExceptionFilter(GetExceptionInformation()) )
    {
        ExitProcess( GetExceptionCode() );
    }
}

 

As we can see, if any of the application's threads raises an exception and does not handle it, the control will be passed to kernel32!UnhandledExceptionFilter function. This function offers the last chance processing for all unhandled exceptions. I will not provide the whole list of actions performed by kernel32!UnhandledExceptionFilter (it deserves an article of its own; many details can be found here), but here are the most important steps:

 

  • Check if the exception was raised because of an attempt to write into a read-only memory page inside .rsrc section, and correct the problem by making the memory page writeable.
  • If the application is running under debugger, do not handle the exception and pass control to the debugger.
  • Call the registered custom filter for unhandled exceptions.
  • If the filter chose not to handle the exception, launch the registered just-in-time debugger to debug the application.

 

Here is the pseudocode of the relevant parts of kernel32!UnhandledExceptionFilter:

 

LONG UnhandledExceptionFilter( EXCEPTION_POINTERS* pep )
{
    DWORD rv;

    EXCEPTION_RECORD* per = pep->ExceptionRecord;

    
    // Check for read-only resource access

    if( ( per->ExceptionCode == EXCEPTION_ACCESS_VIOLATION ) && 
         ( per->ExceptionInformation[0] != 0 ) )
    {
        rv = BasepCheckForReadOnlyResource( per->ExceptionInformation[1] );

        if( rv == EXCEPTION_CONTINUE_EXECUTION )
            return EXCEPTION_CONTINUE_EXECUTION;
    }


    // Is the process running under debugger ? 

    DWORD DebugPort = 0;

    rv = NtQueryInformationProcess( GetCurrentProcess(), ProcessDebugPort, 
                                    &DebugPort, sizeof( DebugPort ), 0 );

    if( ( rv >= 0 ) && ( DebugPort != 0 ) )
    {
        // Yes, it is -> Pass exception to the debugger
        return EXCEPTION_CONTINUE_SEARCH; 
    }


    // Is custom filter for unhandled exceptions registered ?

    if( BasepCurrentTopLevelFilter != 0 )
    {
        // Yes, it is -> Call the custom filter

        rv = (BasepCurrentTopLevelFilter)(pep);

        if( rv == EXCEPTION_EXECUTE_HANDLER )
            return EXCEPTION_EXECUTE_HANDLER;

        if( rv == EXCEPTION_CONTINUE_EXECUTION )
            return EXCEPTION_CONTINUE_EXECUTION;
    }


    // Proceed to other tasks (check error mode, start JIT debugger, etc.)
    ...

}

 

Look! The function uses NtQueryInformationProcess to determine whether the application is running under debugger, and whether it should skip calling the custom filter. If we modify the value returned by NtQueryInformationProcess (DebugPort), we can “ask” it to continue and call our filter, even when the application is running under debugger.

So the solution for our problem looks simple – run the application under debugger as usual, but instead of setting a breakpoint in our custom filter, set the breakpoint in kernel32!UnhandledExceptionFilter function. When the breakpoint is hit, we should step through the function until the call to NtQueryInformationProcess returns, and modify the value returned by NtQueryInformationProcess (in 3rd parameter) to zero – to make it look like the application is not really running under debugger.

The only problem is that we don't have the source code of kernel32.dll, so we have to step through disassembly. Now lets take a look at the disassembly of kernel32!UnhandledExceptionFilter function, to familiarize ourselves with its layout and see where we should go and what we should modify.

In Windows 2000 SP4, the relevant parts of the disassembly look like this:

 

// Setup the stack frame 

7c51bd2b   push    ebp
7c51bd2c   mov     ebp,esp
7c51bd2e   push    0xff
7c51bd30   push    0x7c505788
7c51bd35   push    0x7c4ff0b4
7c51bd3a   mov     eax,fs:[00000000]
7c51bd40   push    eax
7c51bd41   mov     fs:[00000000],esp
7c51bd48   push    ecx
7c51bd49   push    ecx
7c51bd4a   sub     esp,0x2d8
7c51bd50   push    ebx
7c51bd51   push    esi
7c51bd52   push    edi
7c51bd53   mov     [ebp-0x18],esp

// Call BasepCheckForReadOnlyResource, if necessary

7c51bd56   mov     esi,[ebp+0x8]
7c51bd59   mov     eax,[esi]
7c51bd5b   cmp     dword ptr [eax],0xc0000005
7c51bd61   jnz KERNEL32!UnhandledExceptionFilter+0x53 (7c51bd8a)
7c51bd63   xor     ebx,ebx
7c51bd65   cmp     [eax+0x14],ebx
7c51bd68   jz KERNEL32!UnhandledExceptionFilter+0x55 (7c51bd8c)
7c51bd6a   push    dword ptr [eax+0x18]
7c51bd6d   call KERNEL32!BasepCheckForReadOnlyResource (7c51bc52)
7c51bd72   cmp     eax,0xffffffff
7c51bd75   jnz KERNEL32!UnhandledExceptionFilter+0x55 (7c51bd8c)
7c51bd77   or      eax,eax
7c51bd79   mov     ecx,[ebp-0x10]
7c51bd7c   mov     fs:[00000000],ecx
7c51bd83   pop     edi
7c51bd84   pop     esi
7c51bd85   pop     ebx
7c51bd86   leave
7c51bd87   ret     0x4

// Call NtQueryInformationProcess to determine whether the process is being debugged

7c51bd8a   xor     ebx,ebx
7c51bd8c   mov     [ebp-0x38],ebx
7c51bd8f   push    ebx
7c51bd90   push    0x4
7c51bd92   lea     eax,[ebp-0x38]
7c51bd95   push    eax
7c51bd96   push    0x7
7c51bd98   call    KERNEL32!GetCurrentProcess (7c4fe0c8)
7c51bd9d   push    eax
7c51bd9e   call dword ptr [KERNEL32!_imp__NtQueryInformationProcess (7c4e10b8)]
7c51bda4   cmp     eax,ebx
7c51bda6   jl KERNEL32!UnhandledExceptionFilter+0x7a (7c51bdb1)

// Check the value returned by NtQueryInformationProcess (DebugPort) 
// 
// If you want to call the custom filter even when the application is running 
// under debugger, set [ebp-0x38] to zero
// 

1--> 7c51bda8   cmp     [ebp-0x38],ebx
7c51bdab   jne KERNEL32!UnhandledExceptionFilter+0x2c3 (7c51bfda)

// Call the custom filter

7c51bdb1   mov eax,[KERNEL32!BasepCurrentTopLevelFilter (7c54144c)]
7c51bdb6   cmp     eax,ebx
7c51bdb8   jz KERNEL32!UnhandledExceptionFilter+0x98 (7c51bdc7)
7c51bdba   push    esi
2--> 7c51bdbb   call    eax
7c51bdbd   cmp     eax,0x1
7c51bdc0   jz KERNEL32!UnhandledExceptionFilter+0x2e1 (7c51bd79)
7c51bdc2   cmp     eax,0xffffffff
7c51bdc5   jz KERNEL32!UnhandledExceptionFilter+0x2e1 (7c51bd79)

 

We should set our breakpoint at the beginning of the function. Then we should step through the function's disassembly until we reach the line marked with “1-->”, and set to zero the value stored at EBP-0x38 (in VS debugger, you can enter “EBP-0x38, x” into Watch window to obtain the address). After we have set the value, we should step again until the line marked with “2-->”, where our filter is about to be called. Press F11, and we are inside our filter! (Of course, nobody prevents us from optimizing this process with a couple of additional breakpoints)

What about Windows XP and Windows Server 2003? The disassembly of kernel32!UnhandledExceptionFilter function looks very similar to the one on Windows 2000. Recent service packs introduce some additional steps to the logic of this function (we will talk about some of them later in this article), but our approach remains the same – modify the value returned by NtQueryInformationProcess, then step until we reach the call to our filter, and step into it.

Somebody is overwriting my filter!

Now we know how to debug our filter. But sometimes we can end up in a situation when our filter is not called at all. Why could it happen? Of course, the first suspect is the call to SetUnhandledExceptionFilter function – if it is not called properly, our filter is not registered. But it is difficult to miss this call, right?

After we have verified that our application calls SetUnhandledExceptionFilter properly, we start suspecting that somebody else overwrites our filter. Yes, there can be only one registered filter for the whole process, so any DLL or, for example, a 3rd party control could call SetUnhandledExceptionFilter and register its own filter, disabling ours.

Is there a way to determine whether our filter is registered or not? Yes, there is. The pointer to the currently registered filter is stored in BasepCurrentTopLevelFilter global variable, which is located in kernel32.dll. If we have symbols for kernel32.dll (and symbol server can help us to obtain them), we can use the debugger to see which filter is registered. In Visual Studio debugger, we can use the following expression in Watch window to see the address of the currently registered custom filter:

 

*(unsigned long*){,,kernel32.dll}_BasepCurrentTopLevelFilter, x

 

In WinDbg, 'dds' command is very convenient:

 

> dds kernel32!BasepCurrentTopLevelFilter L1
7c54144c  00401050 MyApp!MyCustomFilter [c:/test/myapp.cpp @ 63]

 

(note that if incremental linking is enabled, BasepCurrentTopLevelFilter can point to a thunk, but even in that case it is easy to identify the real filter)

Unfortunately, this method is not going to work since Windows XP SP2 and Windows Server 2003 SP1, because the pointer to the filter function is stored in encoded form. Yes, if we look at the disassembly of SetUnhandledExceptionFilter function in Windows XP SP2, we can see that the pointer is passed to EncodePointer function, and the resulting encoded value is stored in BasepCurrentTopLevelFilter variable. Fortunately, after exploring the disassembly of ntdll!RtlEncodePointer function (kernel32!EncodePointer is forwarded to ntdll!RtlEncodePointer), it becomes clear that the encoding performed actually means XORing the pointer value with a process-wide cookie. Further examination reveals that the cookie is stored as part of _EPROCESS structure in kernel memory (_EPROCESS.Cookie), and therefore we can use our favourite kernel debugger (I like WinDbg with LiveKd) to display this value. For example:

 

> !process <pid> 0
> dt nt!_EPROCESS <address> Cookie

 

(<address> is the address of _EPROCESS structure, obtained from the output of !process command)

If we don't want to resort to kernel debugger to decode the pointer, we can simply run the application under debugger and set breakpoint at SetUnhandledExceptionFilter to see who is registering custom filters for unhandled exceptions (of course, data breakpoint at kernel32!BasepCurrentTopLevelFilter could be even more reliable source of information, in case if somebody attempts to modify the variable directly).

Here is the output shown by WinDbg when debugging this sample application, after I set a breakpoint at SetUnhandledExceptionFilter using the following command:

 

0:000> bp kernel32!SetUnhandledExceptionFilter "k;g"
0:000> g
ChildEBP RetAddr  
0012f820 780020fd KERNEL32!SetUnhandledExceptionFilter
0012f828 780011b3 msvcrt!__CxxSetUnhandledExceptionFilter+0xb
0012f830 78001e29 msvcrt!_initterm+0xf
0012f83c 780010ec msvcrt!_cinit+0x1a
0012f8dc 77f86215 msvcrt!_CRTDLL_INIT+0xec
0012f8fc 77f86f17 ntdll!LdrpCallInitRoutine+0x14
0012f97c 77f8b845 ntdll!LdrpRunInitializeRoutines+0x1df
0012fc98 77f8c295 ntdll!LdrpInitializeProcess+0x802
0012fd1c 77fa15d3 ntdll!LdrpInitialize+0x207
00000000 00000000 ntdll!KiUserApcDispatcher+0x7
ChildEBP RetAddr  
0012feb8 0041563e KERNEL32!SetUnhandledExceptionFilter
0012fec4 00415946 MyApp!__CxxSetUnhandledExceptionFilter+0xe
0012fed0 00415699 MyApp!_initterm_e+0x26
0012fee4 00412343 MyApp!_cinit+0x29
0012ffc0 7c4e87f5 MyApp!mainCRTStartup+0x133
0012fff0 00000000 KERNEL32!BaseProcessStart+0x3d
ChildEBP RetAddr  
0012fe04 00411b7b KERNEL32!SetUnhandledExceptionFilter
0012fedc 00412380 MyApp!main+0x2b
0012ffc0 7c4e87f5 MyApp!mainCRTStartup+0x170
0012fff0 00000000 KERNEL32!BaseProcessStart+0x3d

 

The output looks interesting – we can see not less than three attempts to register a custom filter, and two of them originate from ... CRT library! This brings us to the next part of our discussion – we will try to determine what components of our application might want to install a custom filter. For now, I can identify three categories:

 

  • CRT library
  • .NET runtime
  • 3rd party components

 

Lets approach them one by one, and start with CRT library. It turns out that CRT library relies on a custom filter for unhandled exceptions to implement support for terminate() and related functionality (remember that C++ exceptions are implemented with the help of SEH exceptions). Whenever an unhandled C++ exception is thrown by the application, the custom filter installed by CRT library will catch it and call terminate(). The application can use set_terminate() function to register a terminate handler and get notified about unhandled C++ exceptions. If there is no registered handler, or if the handler returns control, the application is terminated by abort().

Fortunately, the custom filter installed by CRT library is a good citizen (by the way, its name is _CxxUnhandledExceptionFilter), and it does not attempt to handle all possible types of exceptions. If it catches an exception other than “Microsoft C++ Exception” (its code is 0xE06D7363), it calls the previously registered filter, and lets it process the exception.

Filter chaining

When we register a custom filter using SetUnhandledExceptionFilter, the function returns a pointer to the previously registered filter. This pointer gives us the option to call the previous filter from ours, if necessary. Why would we want to do it? For example, if our filter only cares about some specific kinds of exceptions, and does not want to take responsibility of others. A good example of this is the filter installed by CRT library, which handles Microsoft C++ exception (exception code 0xE06D7363) and passes all other exceptions to the previously registered filter. Also, if we want to unregister our filter, we can call SetUnhandledExceptionFilter again and pass it the pointer to the previous filter, thus re-registering it.

In theory, this feature allows an application and its components to implement a chain of custom filters for unhandled exceptions. As long as every filter in the chain plays by the rules and calls the previous filter, every interested component can be notified about unhandled exceptions and react properly. You can find an example of filter chaining here.

In practice, as usual, there are some difficulties. After we have got a pointer to the previous filter, it is not always possible to ensure that the filter can be called safely. What if the filter was registered by a DLL, which is now unloaded? If it is still loaded, is it ready to process the exception? If we don't have the intimate knowledge and control over lifetimes of the application's components, it is probably better not to rely on filter chains, and have only one filter which is responsible for application-wide error handling policy.

Another interesting issue with filters installed by CRT library is that there can be several instances of CRT inside our process. This is because one CRT library is linked with our main executable, and other instances of CRT can be linked with DLLs loaded by our application. Each of them will try to install its own filter for unhandled exceptions, potentially overwriting our filter.

This situation gets especially tricky in the following scenario:

1. Our application registers its custom filter at startup.

2. After a while, the application loads a DLL linked with its own version of CRT, which registers its own filter.

It means that even if we registered our filter successfully, it can be overridden any time the application loads a DLL.

.NET runtime also uses a custom filter (mscorwks!ComUnhandledExceptionFilter). I don't know .NET as well as Win32, but it is clear that at least the following aspects of .NET functionality are implemented with the help of this filter:

 

  • Managed debugger support (the filter notifies the debugger about unhandled managed exceptions)
  • AppDomain.UnhandledException event
  • Just-in-time debugging (managed)

 

And finally, third party components can also install their own filters for unhandled exceptions. Is it good or bad? As always, it depends on the situation. In general, I think that it is not a desirable behavior, because usually the application itself, and not one of its components, should define the policy for error handling and reporting.

Enforcing your own filter

I will reiterate my last sentence: in most cases the application, and not one of its components, should be responsible for application-wide error handling and reporting policy. In particular, it means that if the application registers a filter for unhandled exceptions, nobody else should override it by registering its own filter. How can we achieve it? I don't know a 100% reliable solution, but there are some good opportunities. Lets discuss them.

It's obvious that if we want to make sure that our filter is always registered, we should prevent other parties from registering their filters. API hooking looks like a possible solution – we can hook SetUnhandledExceptionFilter function and reject all attempts to use it after we have registered our filter.

Nowadays, there are two main approaches to API hooking:

 

  • Import Address Table (IAT) hooking
  • Detours-like hooking

 

IAT approach has a problem which makes it unreliable – if the caller obtained the address of the target function (SetUnhandledExceptionFilter in our case) using GetProcAddress function, the call will not go through IAT, and our hook function will not be called.

Detours-like hooking relies on patching the beginning of the target function itself, and therefore it is more reliable – it will not miss calls that can be missed by IAT hooks. The problem with Detours is that it is not publicly available (needs license for commercial use), and implementing a similar, but home grown solution just for hooking SetUnhandledExceptionFilter is not always feasible.

There is one more approach possible, and it is much easier to implement than the previous two. After we have registered our own filter, we can patch the beginning of SetUnhandledExceptionFilter function so that it will not be able to register filters anymore. For example, the following sequence of assembly instructions can be used:

 

xor eax, eax
ret 0x4

 

EnforceFilter example demonstrates this approach.

Sample code

Complete sample code for the article can be found here.

Conclusion

Custom filters for unhandled exceptions are difficult to debug because they are not called when the application is running under debugger. But if we know how the operating system processes unhandled exceptions, we can change the default behavior and make our filter debuggable.

If our application consists of a large set of components, it can be difficult to implement a reliable error handling policy, because multiple components may want to be notified about unhandled exceptions. But if we know how the custom filters for unhahdled exceptions are registered, we can ensure that our filter always prevails.


contact

Have comments, suggestions, or corrections? Feel free to contact us.

Need help with debugging? Consider our debugging services.

### 回答1: Windbg10是一款面向Windows 10的调试工具,它拥有强大的调试功能和精准的代码分析能力,可以帮助我们在开发和调试过程中快速定位和修复问题。 通过Windbg10,我们可以进行内存和寄存器的实时监测,控制线程和进程的调度,查看内存分配和释放状态,分析崩溃信息,并深入了解系统行为和执行路径。同时,Windbg10还支持一系列高级调试命令和脚本语言,可以进行更加复杂和细致的调试操作。 作为一款专业的调试工具,Windbg10的学习曲线可能较为陡峭,需要对汇编语言和操作系统原理有一定的了解。但是,一旦掌握了它的基本用法,就能够有效地提高代码开发和调试效率,为软件开发和测试带来更多便利。 ### 回答2: Windbg10是一个针对Windows 10的调试工具。它是微软官方提供的强大的应用程序调试工具。它可以帮助程序开发人员识别和修复Windows应用程序的错误,并提供精确的调试信息。Windbg10可以用来分析Windows应用程序的崩溃、死锁、性能瓶颈和其他问题。 Windbg10支持多种调试技术,如内核调试、用户模式调试和远程调试。它可以分析内存转储文件,提供精确的错误信息来诊断问题。除此之外,该工具还支持脚本编程,可以用脚本自动化分析和排除问题。 总的来说,Windbg10是一个非常有用的工具,对于开发Windows应用程序的人来说,它是一个必不可少的工具。它可以帮助开发人员更快地找出错误并解决问题,从而提高应用程序的性能和可靠性。虽然Windbg10的使用可能需要一些学习和掌握,但是它的强大和效益使它成为Windows开发的必要工具之一。 ### 回答3: Windbg10是适用于Windows 10的调试工具,它能够帮助开发者对应用程序和操作系统进行调试。Windbg10可以在不同机器上远程调试运行的应用程序,并提供了用户界面和命令行两种不同的操作选择。Windbg10包含多种调试分析工具,例如命令行调试分析器(kd),调试信息文件分析器(dumpbin),性能分析器(perfview)等。通过使用Windbg10,开发者可以运用各种调试命令,例如断点(breakpoints)、单步调试(step)、查看内存(memory inspection)以及观察程序状态(watch variables)等,来定位和解决应用程序和操作系统可能存在的问题,以确保应用程序和操作系统的稳定性和可靠性。Windbg10界面简洁清晰,操作易懂,对于Windows 10开发者而言,是一款必不可少的优秀调试工具。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值