3

I need to determine if the function has exception handler via static analysis. Initially I thought that if there's SEH handler prolog in the function, then the function should have try /catch. I.e. I thought if x86 function has instructions like:

.text:004125E3                 push    0FFFFFFFFh
.text:004125E5                 push    418540h
.text:004125EA                 mov     eax, large fs:0

there should be try/catch. But after compiling some C++ code samples I've found that these instructions are added even if there's no try/catch, but a call to throw(). Thus it looks like it's impossible to determine if the function actually has the exception hander or not.

On AMD64, it's even more obscure, since there are no exception related instructions. The information about the exception handler seems to be encoded with Exception Directory in PE header. The data in Exception Directory points to function start, function end and Unwind info. I thought that if function doesn't have try/catch there will be no entry in the Exception Directory. But similarly to x86, if the function calls throw() it has the entry in the Exception Directory, even if there's no try/catch in the source code.

So the question is: how does the system know if there's an exception hander for a function and to distinguish the function which have no try/catch from those which have it?

1 Answer 1

1

TL,DR;
Exception handling can cause memory leaks. Your C++ compiler is smart enough to realize the possibility of memory leaks surrounding exception handling and is automatically inserting exception handlers where necessary to prevent memory leaks. This means that there really is a try/catch block where you observed an exception handler, except it is handling nothing and simply re-throwing the exception after cleaning up C++ objects.


how does the system know if there's an exception handler for a function and to distinguish the function which have no try/catch from those which have it

The system doesn't know. In fact, it is impossible for the system to know, so it simply walks down the chain of exception handlers until it finds one that tells the system "exception handled". This means that whenever you see those 3 assembly instructions, there is always an exception handler and I believe that it should always be possible to reverse the exception handler into an equivalent C++ try/catch block.

But as you have observed, not all exception handlers need to be explicitly defined. Your C++ compiler will insert them into your program where necessary for the purpose of object cleanup, because exception handling can cause memory leaks:

void i_leak_memory() {
    void* memory = malloc(512);
    throw std::exception();

    // this line of code will never be executed
    free(memory);
}

void i_handle_exceptions() {
    try {
        i_leak_memory();
    }
    catch(std::exception e) {
        // exception handled
    }
}

In the above code, you will find that memory is never freed due to the interruption caused by the exception. At the exact moment the exception occurs, execution is immediately transferred to the catch in i_handle_exceptions() and after handling the exception, execution continues in i_handle_exceptions(). i_leak_memory() never gets an opportunity to release resources after the exception occurred, and so we end up with a memory leak.

Fortunately, there is a simple solution to this apparent memory leak. Simply replace i_leak_memory() with i_do_not_leak_memory():

void i_do_not_leak_memory() {
    void* memory = 0;

    try {
        memory = malloc(512);
        throw std:exception();
        free(memory);
    }
    catch(std:exception e) {
        free(memory);
        throw;
    }
}

By simply adding a try/catch block to the function, we can give it one last chance at releasing resources in the event of an exception. The catch block does not necessarily need to handle the exception, but it allows us to include special logic that would prevent memory leaks.

With the RAII design of C++, the task of object cleanup is the responsibility of the C++ compiler. When using throw(), the C++ compiler needs to inject special logic into the code (possibly in multiple functions) to prevent memory leaks from occurring. So in your observations, what really happened is that your C++ compiler replaced i_leak_memory() with i_do_not_leak_memory() because if it didn't, the program would be leaking C++ objects whenever an exception occurred.

2
  • (Part 1) Thanks for C++ tour, but I've already known that. The system actually knows if the function has try/catch from unwind info. In case of x86, the prolog actually pushes SEH hander structure to SEH stack, and in such way CRT SEH handler knows where it should go in case exception has happened and if the exception type matches, it will execute the topmost SEH handler, otherwise it will traverse down until it reaches Unhandled exception filter. In x86 SEH handler code is usually a part of the function code. Thus it can been seen "by eyes" in the disassembly. Jul 26, 2021 at 8:34
  • (Part 2) Sometimes SEH hander is placed right after the function. Thus it should be possible to detect (for at least x86) that there's an exception handler and which kind of exception it handles. The problem is how to do it in static. Since the information about unwind info is hard to find and hard to understand. Jul 26, 2021 at 8:38

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.