creating va_list dynamically in GCC - can it be done?

There is a way you can do this, but it is specific to gcc on Linux. It does work on Linux (tested) for both 32 and 64 bit builds.

DISCLAIMER: I am not endorsing using this code. It is not portable, it is hackish, and is quite frankly a precariously balanced elephant on a proverbial tightrope. I am merely demonstrating that it is possible to dynamically create a va_list using gcc, which is what the original question was asking.

With that said, the following article details how va_list works with the amd64 ABI: Amd64 and Va_arg.

With knowledge of the internal structure of the va_list struct, we can trick the va_arg macro into reading from a va_list that we construct ourselves:

#if (defined( __linux__) && defined(__x86_64__))
// AMD64 byte-aligns elements to 8 bytes
#define VLIST_CHUNK_SIZE 8
#else
#define VLIST_CHUNK_SIZE 4
#define _va_list_ptr _va_list
#endif

typedef struct  {
    va_list _va_list;
#if (defined( __linux__) && defined(__x86_64__))
    void* _va_list_ptr;
#endif
} my_va_list;

void my_va_start(my_va_list* args, void* arg_list)
{
#if (defined(__linux__) && defined(__x86_64__))
    /* va_args will read from the overflow area if the gp_offset
       is greater than or equal to 48 (6 gp registers * 8 bytes/register)
       and the fp_offset is greater than or equal to 304 (gp_offset +
       16 fp registers * 16 bytes/register) */
    args->_va_list[0].gp_offset = 48;
    args->_va_list[0].fp_offset = 304;
    args->_va_list[0].reg_save_area = NULL;
    args->_va_list[0].overflow_arg_area = arg_list;
#endif
    args->_va_list_ptr = arg_list;
}

void my_va_end(my_va_list* args)
{
    free(args->_va_list_ptr);
}

typedef struct {
    ArgFormatType type; // OP defined this enum for format
    union {
        int i;
        // OTHER TYPES HERE
        void* p;
    } data;
} va_data;

Now, we can generate the va_list pointer (which is the same for both 64 bit and 32 bit builds) using something like your process() method or the following:

void* create_arg_pointer(va_data* arguments, unsigned int num_args) {
    int i, arg_list_size = 0;
    void* arg_list = NULL;

    for (i=0; i < num_args; ++i)
    {
        unsigned int native_data_size, padded_size;
        void *native_data, *vdata;

        switch(arguments[i].type)
        {
            case ArgType_int:
                native_data = &(arguments[i].data.i);
                native_data_size = sizeof(arguments[i]->data.i);
                break;
            // OTHER TYPES HERE
            case ArgType_string:
                native_data = &(arguments[i].data.p);
                native_data_size = sizeof(arguments[i]->data.p);
                break;
            default:
                // error handling
                continue;
        }

        // if needed, pad the size we will use for the argument in the va_list
        for (padded_size = native_data_size; 0 != padded_size % VLIST_CHUNK_SIZE; padded_size++);

        // reallocate more memory for the additional argument
        arg_list = (char*)realloc(arg_list, arg_list_size + padded_size);

        // save a pointer to the beginning of the free space for this argument
        vdata = &(((char *)(arg_list))[arg_list_size]);

        // increment the amount of allocated space (to provide the correct offset and size for next time)
        arg_list_size += padded_size;

        // set full padded length to 0 and copy the actual data into the location
        memset(vdata, 0, padded_size);
        memcpy(vdata, native_data, native_data_size);
    }

    return arg_list;
}

And finally, we can use it:

va_data data_args[2];
data_args[0].type = ArgType_int;
data_args[0].data.i = 42;

data_args[1].type = ArgType_string;
data_args[1].data.p = "hello world";

my_va_list args;
my_va_start(&args, create_arg_pointer(data_args, 2));

vprintf("format string %d %s", args._va_list);

my_va_end(&args);

And there you have it. It works mostly the same as the normal va_start and va_end macros, but lets you pass your own dynamically generated, byte-aligned pointer to be used instead of relying on the calling convention to set up your stack frame.


I have tried using libffi as mentioned somewhere else and it works. Here below is the link , hope it can help others with similar issues. Thanks again for all help I got here!

Link: http://www.atmark-techno.com/~yashi/libffi.html -- simple example given http://www.swig.org/Doc1.3/Varargs.html -- printf() and other examples given


The type of va_list is not void ** or anything similar with 64-bit gcc (on Intel x86/64 machines). On both Mac OS X 10.7.4 and on RHEL 5, there is no header stdarg.h in /usr/include. Consider the following code:

#include <stdarg.h>
#include <stdio.h>
int main(void)
{
    printf("sizeof(va_list) = %zu\n", sizeof(va_list));
    return 0;
}

The output on both RHEL 5 and Mac OS X 10.7 with a 64-bit compilation is:

sizeof(va_list) = 24

With a 32-bit compilation, the output on each platform is:

sizeof(va_list) = 4

(You may take it that I was surprised to find this much discrepancy between the 32-bit and 64-bit versions. I was expecting a value between 12 and 24 for the 32-bit version.)

So, the type is opaque; you can't even find a header that tells you anything about; and it is much bigger than a single pointer on 64-bit machines.

Even if your code works on some machines, it is very, very far from guaranteed to work everywhere.

The GCC 4.7.1 manual does not mention any functions that allow you to build a va_list at runtime.