Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to write a zero-cost exception handling in C?

g++ compiler has the feature of zero-cost exception handling. To my understanding, try does nothing, but when exception is thrown, a subroutine for the exception handler is executed. Like this:

void foo() {
    try {
        bar(); // throws.
    } catch (Type exc) {
        baz();
    }
}

In pseudocode (c-stylish) would look like this:

void foo() {
    bar();
    return;
catch1_Type:
    baz();
}

bar() throws. The exception routine does the following:

Ah, the return address is in function foo()! And the return address is in the first try-catch block, and we throw type Type, so the exception handler routine is at address foo+catch1_Type. So cleanup the stack so we end up there!

Now my question: is there any way to implement it in C? (Can be C99 or newer, although I'm interested in C dialect supported by gcc). I know that I can use e.g libunwind for stack examination and traversal, although I have no idea how to get the address of catch1_Type label. It may not be possible.

The exception handler may be a different function, that's equally OK, but then how to get addresses of local variables of stackframe foo in that other function? It also seem to be impossible.

So... is there any way to do it? I would like not to go into assembler with this, but it also is acceptable if everything else fails (although local variables - man, you never know where they are if using different optimization levels).

And to be clear - the purpose of this question is to avoid setjmp/longjmp approach.

EDIT: I've found a quite cool idea, but does not work entirely:

Nested functions in gcc. What they can do?

  • have access to local variables,
  • have possibility to goto local labels in the parent function!
  • can be called by callees of our function, provided that we pass a pointer to the nested function, so it's available by pointer in the callee.

Downside which prevents me from doing anything zero-cost:

  • they're optimized out even at -O0 level if they're unused. Can I do anything about this? If I could, I could get address by symbol name when exception is thrown and it would simply do the job of implementing exceptions which cost nohing when not thrown...
like image 783
Al W Avatar asked Mar 17 '13 18:03

Al W


1 Answers

I've found some time to play with this idea, and I'm very close to finding the solution for my own question. Here are the details:

  • gcc allows nested functions, which have access to parent function's local variables, and can goto labels in the parent function!
  • gcc will not emit the code for the internal function if it's only defined but not referenced. You can define inline no-op function which obtains a pointer to the local function, and call it in the parent function. This will force generating code for the internal function, and it will be zero-cost (at higher optimization levels the inline no-op call will be removed). It is possible that in more recent gcc optimizing out the inline call will prevent generating the code for the internal function though..
  • bad thing is, I see no way to force nested (internal) functions to be global symbols. They're always local, so no possibility to get the address using dlsym.
  • another bad news, if the program is using such nested functions, valgrind is crashing ;) I was able to verify my simple test program, but I was unable to use valgrind to verify that there are no memory violations.

The code that I used for checking has obvious flaw: it's not 'zero-cost', as the global pointer to the exception handling routine must be set during function's execution. If only we could make this compile-time!

Well, after all if one wants to use the internal function properly, like passing a pointer to it into callees, so they can call it in case of exceptions thrown, we could probably have very fast exception handling, much, MUCH faster than setjmp/longjmp...

I'll keep on hacking, maybe I'll find a way (some assembler chunk of code to force GAS registering the function as a personality routine of the parent?).

#include <stdio.h>
#include <dlfcn.h>
#include <stdlib.h>

typedef void (*catch_routine)(void*);

catch_routine g_r = NULL;

void tostr_internal(char* str, int a)
{
    int result = a + 'a';
    if (result < 'a' || result > 'z')
    {
        // handle exception
        if(g_r)
        {
            g_r(&a);
        }
        else
        {
            fprintf(stderr, "Exception not caught!");
            abort();
        }
    }
    else
    {
        str[0] = result;
        str[1] = '\0';
    }
}

char* tostring(int a)
{
    __label__ exhandler;
    char* string = (char*)malloc(2*sizeof(char));

    void personality(void* exid) {
        fprintf(stderr, "Number %d is not a character!\n", *(int*)(exid));
        free(string);
        goto exhandler;
    }
    g_r = personality;

    tostr_internal(string, a);
    return string;

exhandler:
    return NULL;
}

int main(int a, char** b)
{
    int i = 0;

    for(i = 0; i < 10000; i++)
    {
        int trythisbastard = i % 95;
        char* result = tostring(trythisbastard);
        if (result)
        {
            fprintf(stderr, "Number %d is %s\n", trythisbastard, result);
            free(result);
        }
    }

    return 0;
}
like image 59
Al W Avatar answered Nov 15 '22 23:11

Al W