Monday, July 19, 2010

Forwarding invocation of variadic function in C

I had been brooding over how to do the RPC calls for variadic functions for some time now. Although marshalling any given variadic isn't really possible due to a lack of general method for obtaining argument count and sizes (see my weekly report #9, issues section), for commonly used stdio.h variadics such as printf and scanf, the arguments are well-defined by the format string so it should be possible to manually marshal these.

I'll be writing a seperate blog post about how I went about doing that, but for now I want to talk about a sub-problem of this: given a number of arguments, how do you forward these to a variadic function? A re-statement could be, how to "dynamically" invoke a variadic function?

Looking this up in the net, I've found these two discussions to be the most relevant:

The second link mentions a library called FFCALL which can be used to pass parameters to variadics dynamically, and this probably is the ideal way of doing things.  

I may have found another method for this - so far as I've seen it works on x86 and ARM. It's based on the assumption that the last mandatory parameter and all the variadic parameters reside continuously in the memory, as well as lots of terrible coding practices, but it should be illustrative enough.

What I'm doing is basically copying a fixed number of bytes from the memory region (=stack) where the variadic parameters are located into a buffer, then passing this buffer to another variadic function which is called with a fixed number of arguments (I picked doubles since they are larger and will allocate more space on the called function's stack). The function then calls memcpy to overwrite its variadic args section with the passed buffer, and afterwards can call the stdarg macros to obtain the variadic arguments, or pass them to something like vprintf.

Here's the code:


#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>

void accepter(char *fmt, char *ptr, ...);
void forwarder(char *fmt, ...);

void forwarder(char *fmt, ...)
{
    double d = 9.1;
    char *buf = (char*) malloc(10*sizeof(double));
    memcpy(buf, (void*)((unsigned int)(&fmt)+sizeof(char*)), 80);
    FILE *o = fopen("tmp", "wb");
    fwrite(buf, 10, sizeof(double), o);
    fclose(o);
    // call function with 10 double arguments to open up stack space
    accepter(fmt, buf, d, d, d, d, d, d, d, d, d, d);
    free(buf);
}

void accepter(char *fmt, char *ptr, ...)
{
    memcpy((void*)((unsigned int)(&ptr)+sizeof(char*)), ptr, 10*sizeof(double));
    va_list ap;
    va_start(ap, ptr);
    vprintf(fmt, ap);
    va_end(ap);
}

int main()
{
    printf("Testing...\n");
    double d = 65.98;
    forwarder("%d %d %d ermm %s and more params! %x %f %x %x \n", 1, 2, 3, "hello world", 199, d , 0xdeadbeef, 0xbeefdead);
    return 0;
}

2 comments:

  1. Another way of solving this could be by wrapping your marshalled arguments as libffi arguments and do the call using libffi (disclaimer: I've never worked with libffi so this is all just based on its manual).

    ReplyDelete
  2. Same is possible with another library called FFCALL as I mentioned in the post, but I wanted to see if there was another way around without introducing additional libraries. I've observed some problem with passing doubles on the ARM which I guess is related to alignment later, so I may have to switch to libraries instead.

    ReplyDelete