Question

I need to unit test some code where the failure cases raise an exception. In a nutshell, I need to handle the exception by either unwinding a stack frame or making a local jump over the error. Using MSVC is not an option.

The MSDN example for AddVectoredExceptionHandler indicates that I can modify eip and then return EXCEPTION_CONTINUE_EXECUTION to perform a local jump. The obvious question is what to what address to jump. GCC's Label as Value feature seems like it should be just the thing.

The example below works if there is a single return from the erroring function. However, if a second return statement is added, the offset of the jump is suspiciously small and the jump fails. Why?

#include <assert.h>
#include <stdbool.h>
#include <stdio.h>

#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0502
#endif

#define WIN32_LEAN_AND_MEAN
#include <windows.h>

static bool handler_called;
static DWORD handler_eip;

static LONG WINAPI local_handler(struct _EXCEPTION_POINTERS* ExceptionInfo) {
   handler_called = true;
   PCONTEXT Context = ExceptionInfo->ContextRecord;
   Context->Eip = handler_eip;
   return EXCEPTION_CONTINUE_EXECUTION;
}

static void badcall(void) {
   handler_called = false;
   handler_eip = &&fail;

   int value = 100;
   int zero = 0;
   value = value/0;
   printf("not handled.\n");
   assert(false);
   //return; // Uncomment this to break the handler

   fail:
   printf("error handled.\n");
   return;
}

int main(int argc, char* argv[]) {
   void* old_handler = SetUnhandledExceptionFilter(&local_handler);
   badcall();
   SetUnhandledExceptionFilter(old_handler);
   return(0);
}
Was it helpful?

Solution

GCC's dead code elimination seems to be the culprit -- nothing to do with SEH. When the bottom block of the function is not reachable, those statements are eliminated. GCC then arbitrarily creates a label at the location where we took the address.

Since we're manipulating program flow directly, the rules of C tell us that we're not allowed to complain when the compiler does something wacky like this.

Here is a more minimal example:

static void* handler_eip;
static void badcall(void) {
   handler_eip = &&fail;

   int value = 100/0;
   printf("not handled.\n");
   //goto fail;
   return;

   fail:
   printf("error handled.\n");
   return;
}

Note the location of L2 with the goto in place:

_badcall:
    pushl    %ebp
    movl    %esp, %ebp
    subl    $56, %esp
    movl    $L2, _handler_eip
    movl    $100, %eax
    movl    $0, -28(%ebp)
    movl    %eax, %edx
    sarl    $31, %edx
    idivl    -28(%ebp)
    movl    %eax, -12(%ebp)
    movl    $LC0, (%esp)
    call    _puts
    nop
L2:
    movl    $LC1, (%esp)
    call    _puts
    nop
    leave
    ret

And without:

_badcall:
    pushl    %ebp
    movl    %esp, %ebp
    subl    $56, %esp
L2:
    movl    $L2, _handler_eip
    movl    $100, %eax
    movl    $0, -28(%ebp)
    movl    %eax, %edx
    sarl    $31, %edx
    idivl    -28(%ebp)
    movl    %eax, -12(%ebp)
    movl    $LC0, (%esp)
    call    _puts
    nop
    leave
    ret
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top