Thunk

From vegard.wiki
Jump to navigation Jump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.

It is possible in C to "bake" one or more arguments into a function pointer that takes no arguments, thus solving the problem of libraries that take a callback function without a data pointer.

Below is one possible implementation for x86-64 that bakes a single argument into a thunk. Error handling may need to be adjusted for specific use cases.

Definition

#include <sys/mman.h>
#include <stdlib.h>

#ifndef PAGE_SIZE
#define PAGE_SIZE 4096
#endif

typedef void (*unthunked_fn)(void *);
typedef void (*thunked_fn)(void);

thunked_fn make_thunk(unthunked_fn fn, void *data)
{
        void *ptr = mmap(NULL, PAGE_SIZE,
                PROT_READ | PROT_WRITE | PROT_EXEC,
                MAP_PRIVATE | MAP_ANONYMOUS,
                -1, 0);
        if (ptr == MAP_FAILED)
                return NULL;

        struct {
                unsigned char movabs_rdi[10];
                unsigned char movabs_rax[10];
                unsigned char jmpq[2];
        } *page = ptr;

        /* movabs $..., %rdi */
        page->movabs_rdi[0] = 0x48;
        page->movabs_rdi[1] = 0xbf;
        for (unsigned int i = 0; i < 8; ++i)
                page->movabs_rdi[2 + i] = (unsigned long) data >> (8 * i);

        /* movabs $..., %rax */
        page->movabs_rax[0] = 0x48;
        page->movabs_rax[1] = 0xb8;
        for (unsigned int i = 0; i < 8; ++i)
                page->movabs_rax[2 + i] = (unsigned long) fn >> (8 * i);

        /* jmpq %rax */
        page->jmpq[0] = 0xff;
        page->jmpq[1] = 0xe0;

        /* Optional; only needed for CPUs with W^X */
        mprotect(ptr, PAGE_SIZE, PROT_READ | PROT_EXEC);

        return (thunked_fn) page;
}

void free_thunk(thunked_fn fn)
{
        munmap((void *) fn, PAGE_SIZE);
}

Usage

#include <stdio.h>

static void print_str(void *data)
{
        printf("print_str: %s\n", (const char *) data);
}

int main(int argc, char *argv[])
{
        thunked_fn fn = make_thunk(print_str, "Hello world!");

        /* Prints "Hello world!" */
        fn();

        return 0;
}

See also