8

my problem with vsprintf is that I can not obtain input arguments directly, I have to first get inputs one by one and save them in void**, then pass this void** to vsprintf(), it is all fine for windows, but when I come to 64bit linux, gcc cannot compile because it is not allowed to convert from void** to va_list, Is there anyone that can give me some help how I should do this under linux?

Can I create va_list dynamically in GCC?

void getInputArgs(char* str, char* format, ...)
{
    va_list args;
    va_start(args, format);
    vsprintf(str, format, args);
    va_end(args);
}  

void process(void)
{
    char s[256];
    double tempValue;
    char * tempString = NULL;
    void ** args_ptr = NULL;
    ArgFormatType format;   //defined in the lib I used in the code
    int numOfArgs = GetNumInputArgs();  // library func used in my code

    if(numOfArgs>1)
    {
        args_ptr = (void**) malloc(sizeof(char)*(numOfArgs-1));
        for(i=2; i<numOfArgs; i++)
        {
            format = GetArgType();    //library funcs

            switch(format)
            {
                case ArgType_double:
                    CopyInDoubleArg(i, TRUE, &tempValue);   //lib func
                    args_ptr[i-2] = (void*) (int)tempValue;    
                    break;

                case ArgType_char:
                    args_ptr[i-2]=NULL;
                    AllocInCharArg(i, TRUE, &tempString);  //lib func
                    args_ptr[i-2]= tempString;
                break;
            }
        }
    }

    getInputArgs(s, formatString, (va_list) args_ptr);   //Here 
           // is the location where gcc cannot compile, 
           // Can I and how if I can create a va_list myself?
}
John Drouhard
  • 1,209
  • 2
  • 12
  • 18
user1558064
  • 867
  • 3
  • 12
  • 28

5 Answers5

6

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.

John Drouhard
  • 1,209
  • 2
  • 12
  • 18
  • 1
    `6 gp registers * 8 bits/register`: you mean bytes / register. And same error on the next line, where XMM registers are 16 *bytes*. (And BTW, getting `__m256` or `__m512` args is possible in variadic functions, and doing so leads gcc to make code that saves `ymm0-7` or `zmm0-7` registers to the stack, not just `xmm0-7`, if `al` != 0) – Peter Cordes Nov 13 '17 at 03:53
  • The Windows 64-bit calling convention is optimized for variadic functions (shadow space allows spilling register args before the stack args to make an array), so I'd assume it's much simpler. Only 4 total args can be passed in registers, not 4 integer and 4 FP. – Peter Cordes Nov 13 '17 at 03:59
  • Shouldn't the line `args->_va_list_ptr = arg_list;` be covered by the `#if` as the member is not present otherwise? And maybe `args->_va_list` should be fille outside of the `#if`? – Gerhardh Aug 15 '18 at 07:46
  • @Gerhardh - Nope - it's correct as is. Look closer at what's going on. If it's being compiled in any environment that isn't Linux 64 bit, it `#define`s `_va_list_ptr` to `_va_list`, which is the actual `va_list` you'd normally use. – John Drouhard Aug 17 '18 at 02:19
3

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

user1558064
  • 867
  • 3
  • 12
  • 28
2

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.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • 1
    The lack of a `stdarg.h` file in `/usr/include` is not an issue; that's not the only place the compiler searches for header files. On my system, for example, `#include ` brings in `/usr/lib/gcc/x86_64-linux-gnu/4.8/include/stdarg.h` (the header is provided by gcc, not by glibc). `gcc -E` should show you where it is on your system. The header on my system has `typedef __builtin_va_list __gnuc_va_list;` and `typedef __gnuc_va_list va_list;`. A quick experiment shows that it's a single-element array type; when I compile with `-m32`, it's a 4-element byte array or a pointer. – Keith Thompson Sep 29 '16 at 19:09
0

Following class works for me:

class VaList
{
    va_list     _arguments;

public:

    explicit inline VaList(const void * pDummy, ...)
    {
        va_start(_arguments, pDummy);
    }

    inline operator va_list &()
    {
        return _arguments;
    }

    inline operator const va_list &() const
    {
        return _arguments;
    }

    inline ~VaList()
    {
        va_end(_arguments);
    }
};

and it can be used like this:

void v(const char * format, const va_list & arguments)
{
    vprintf(format, const_cast<va_list &>(arguments));
}

...

    v("%d\n", VaList("", 1)); // Uses VaList::operator va_list &()
    v("%d %d\n", VaList(nullptr, 2, 3)); // Uses VaList::operator va_list &()
    vprintf("%s %s %s\n", VaList("", "Works", "here", "too!"));

    const VaList args(NULL, 4, 5, "howdy", "there");
    v("%d %d %s %s\n", args); // Uses VaList::operator const va_list &() const

The first dummy parameter can be any kind of pointer, it is only used to compute the address of the following arguments.

The same can of course be done in C too but not so niftily (use pointer instead of reference)!

Simple example of using VaList to construct a dynamic va_list:

static void VectorToVaList(const std::vector<int> & v, va_list & t)
{
    switch (v.size())
    {
        case 1: va_copy(t, VaList("", v[0])); return;
        case 2: va_copy(t, VaList("", v[0], v[1])); return;
        case 3: va_copy(t, VaList("", v[0], v[1], v[2])); return;
        case 4: va_copy(t, VaList("", v[0], v[1], v[2], v[3])); return;
        // etc
    }

    throw std::out_of_range("Out of range vector size!");
}

and usage:

    va_list t;
    VectorToVaList(std::vector<int>{ 1, 2, 3, 4 }, t);
    vprintf("%d %d %d %d\n", t);
aku
  • 103
  • 4
  • You already have an argument list from a call to a variadic function. The OP wants to do something like parse a flat string like `"1 2 3 4"` *at run time* into a `va_list` with types and total length that aren't known at compile-time. i.e. manually construct a `va_list`, because you can't let the compiler do it for you at compile time. You're just showing the normal way that you should use if possible, but which isn't usable for dynamic list lengths. – Peter Cordes Aug 15 '18 at 09:01
  • Sure, because it's the only portable way of creating a va_list. You can still do it (pseudodynamically!) by having a factory that has N variants of VaList with varying number of arguments from 1 to N that are pointers to instances of classes with a common base class. Depending what you are using your newly constructed va_list for you can have virtual methods in your classes for example ToString() for printing etc. Practical limitation is that N should not be very large (maybe less than around 10). VaList could be a template class similar to C++ tuple but that is too involved to show here. – aku Aug 15 '18 at 09:43
  • This question is specifically asking for a non-portable efficient way of doing this. Appending to a `va_list` one at a time with a function call might work. But anything that depends on the number of args being known at compile time is *not* what's being asked. A C++ template can't work because it needs everything to be known at compile time. Also in C you'd have to implement virtual functions / polymorphism yourself if you want that. – Peter Cordes Aug 15 '18 at 09:57
  • I added a dynamic example. Certainly you admit that converting the contents of a vector to a va_list is dynamic? We should have va_add functionality in next C++ standard to add elements to an existing va_list to make it easier to construct va_lists. – aku Aug 15 '18 at 10:10
  • Hard-coding every size explicitly hardly counts, but at least tries to answer the question now, and illustrates the limitations of this. Removed my downvote. But note that the number of cases balloons exponentially with the OP's example of each arg being either `double` or `char`; using a `vector` of all the same type is a huge simplification. – Peter Cordes Aug 15 '18 at 10:16
  • That is the language limitation that we have to live with until they add something like va_add. And va_list is typically used in printf type scenarios where the number of arguments is limited. va_list is static in nature so why try to use it in a dynamic scenario to begin with. – aku Aug 15 '18 at 11:15
  • Agreed that the language doesn't support it portably, but as you can see from the existing answers, there are implementation-specific ways. If you need this, you've probably chosen poorly in designing things, i.e. not making enough stuff a compile-time constant (or in choosing C in the first place). But there are multiple Q&As on SO including this one about constructing function arg dynamically, like [asm calling-convention hacks](https://stackoverflow.com/q/49283616), so apparently people do paint themselves into corners like this and want an efficient way out. – Peter Cordes Aug 15 '18 at 11:27
-2

If the problem you're trying to solve is inserting passing arbitrary types to a function in va_list style, then, consider using union:

#include <iostream>
#include <cstdarg>

union ARG
{
    int d;
    char* s;
    double f;
};

int main()
{
    printf("%d %s %f \n", 1, "two", 3.1415 );
    // Output: 1 two 3.141500

    char format[ 1024 ] = "%d %s %f\n";
    ARG args[ 5 ] = { };
    args[ 0 ].d = 1;
    args[ 1 ].s = "two";
    args[ 2 ].f = 3.1415;
    printf( format, args[ 0 ], args[ 1 ], args[ 2 ], args[ 3 ], args[ 4 ] );
    // Output: 1 two 3.141500

    return 0;
}

Some things you'll note about my solution:

  • No attempt is made to produce the correct number of arguments. i.e. I oversupply the arguments, but, most functions will look at the first parameter to determine how to handle the rest (i.e. format)
  • I didn't bother dynamically create the format, but, it is a trivial exercise to build a routine that dynamically populates format and args.

Tested this on: - Ubuntu, g++ - Android NDK

I did some more testing, and, confirmed @PeterCoordes comments about this answer not working for double precision.

Stephen Quan
  • 21,481
  • 4
  • 88
  • 75
  • Interesting idea, but a `union{int;double;}` isn't passed the same way as a `double` in calling conventions like x86-64 System V. Even for variadic functions, a plain `double` is passed in an XMM register but I think this union will get passed in memory. This is also broken for 32-bit code: a `double` takes two 32-bit "arg-passing slots" so the union is twice as big as it should be when the format string is looking for an `int`. – Peter Cordes Dec 02 '19 at 16:23
  • I see you didn't test this; it doesn't compile as C (bad includes and various non-C stuff) or C++ (`arg[3]` is a typo for `args[3]`). (This is a C question). A fixed version of this https://godbolt.org/z/Z9pcHg shows that `args[2]` gets passed in an integer reg (like any not-purely-FP struct/union of that size), not XMM0, so `printf` won't find it for the `%f` conversion. You could also add `-m32` to see that it doesn't work there either. (Godbolt also has an option to run the binary, if looking at the asm isn't convincing.) – Peter Cordes Dec 02 '19 at 16:29
  • Anyway, this is a potentially useful idea, but **only if the callee is written to receive args wrapped in a union**. This makes passing FP args somewhat less efficient. Otherwise the UB really does bite you in most calling conventions, including x86-64 and all 32-bit calling conventions. – Peter Cordes Dec 02 '19 at 16:32
  • @PeterCordes I did run this on g++ on Ubuntu. Sorry, I mistyped when I transcribed the code here. Typos corrected. – Stephen Quan Dec 02 '19 at 21:56
  • 1
    I tested it again on Godbolt. It happened to work because the first printf call dumped the same `xmm0` FP arg to the same place on the stack, and the 2nd passes AL=0 so the FP register-dump array stays unwritten by the 2nd. **If you comment out the first `printf`, it only prints `1 two 0.000000` not `3.1415`**. https://godbolt.org/z/55RwpT. Your testcase only works by pure luck and coincidence. This hack does seem to work if all the args are integer/pointer, though, as long as the union is no larger than an "arg passing slot" (usually register width). – Peter Cordes Dec 02 '19 at 23:25