?

Log in

No account? Create an account

fanf

Signal mis-handling

« previous entry | next entry »
30th Mar 2006 | 00:26

One of the things that made the Bourne Shell notorious (apart from the obvious Bournegol) was its memory management. Its allocator just walked up the address space as needed. If it hit the end of its mapped memory, it would SEGV, drop into its signal handler, ask the kernel for more memory, and return. This caused inordinate problems later when Unix was ported to systems with less amenable signal handling. (See OpenSolaris for a slightly cleaned-up Bourne shell.)

Larry Wall's 1987 IOCCC winner goes something like:
    extern int fd[];
    signal(SIGPIPE, start);
    pipe(fd);
    close(fd[0]);
    for(;;)
        write(fd[1],ptr,n);
The write serves only to trigger SIGPIPE, and the signal handler is repeatedly changed in order to implement the flow of control (which the judges called "amazing").

I thought it would be amusing to combine these ideas.
    extern char *prog[];
    extern int pc;
    for(;;)
        ((void(*)(void))dlsym(NULL,prog[pc]))();
This looks like a pretty boring interpreter if you assume that the prog consists entirely of built-in function names. But what if it doesn't? Then dlsym() will return NULL and the program will SEGV. The signal handler can then push a return address on the interpreter stack, change pc to point to the user-defined function definition, and longjmp() back to the loop.

(Aside: you can have symbolic builtins like +-*/ if you choose function names that have the same ELF hash as the symbols you want, then run sed over the object code to change their names to symbols.)

The problem is that dlsym() isn't very portable - certainly not enough for the IOCCC. So we need a better way of constructing a symbol table, and preferably without having to mention the function name loads of times (e.g. as in Larry's program). The problem is that you can't interleave the array initializers with the function definitions (at least not without non-portable hacks like linker sets). But how about this:
    #define BUILTIN(name, code) \
        void name(void) code { #name, name },
    
    BUILTIN(add,{
        int b = pop();
        int a = pop();
        push(a + b);
    }
    
    BUILTIN(sub,{
        int b = pop();
        int a = pop();
        push(a - b);
    }
    
    BUILTIN(exch,{
        int b = pop();
        int a = pop();
        push(b);
        push(a);
    }
    
    struct {
        const char *name;
        void (*code)(void);
    } builtin[] = {
          )))
        { 0,0 }
    };

| Leave a comment | Share

Comments {7}

Ross

from: crazyscot
date: 30th Mar 2006 08:06 (UTC)

*fx: hands fanf a card for egregious abuse of the C pre-processor*

*fx: turns up at fanf's door with a dumper truck full of cards*

Reply | Thread

Tony Finch

from: fanf
date: 30th Mar 2006 11:16 (UTC)

That is no where near the worst thing I have done with cpp.

Reply | Parent | Thread

Ben Hutchings

from: womble2
date: 1st Apr 2006 13:02 (UTC)

I'd be surprised if that worked with a standard preprocessor.

Reply | Parent | Thread

Tony Finch

from: fanf
date: 2nd Apr 2006 22:10 (UTC)

The secret is in the Makefile.

Reply | Parent | Thread

Ben Hutchings

from: womble2
date: 2nd Apr 2006 22:49 (UTC)

Ew!

Reply | Parent | Thread

Simon Tatham

from: simont
date: 30th Mar 2006 08:38 (UTC)

That actually made my eyes water. And this is me saying it. :-)

Reply | Thread

David Cantrell

from: therealdrhyde
date: 15th Jun 2006 10:16 (UTC)

You, sir, are evil. I approve.

Reply | Thread