creating va_list dynamically in GCC - can it be done?

后端 未结 5 2056
暖寄归人
暖寄归人 2021-01-12 03:28

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 pas

5条回答
  •  广开言路
    2021-01-12 04:04

    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.

提交回复
热议问题