236

In C, is it possible to forward the invocation of a variadic function? As in,

int my_printf(char *fmt, ...) {
    fprintf(stderr, "Calling printf with fmt %s", fmt);
    return SOMEHOW_INVOKE_LIBC_PRINTF;
}

Forwarding the invocation in the manner above obviously isn't strictly necessary in this case (since you could log invocations in other ways, or use vfprintf), but the codebase I'm working on requires the wrapper to do some actual work, and doesn't have (and can't have added) a helper function akin to vfprintf.

[Update: there seems to be some confusion based on the answers that have been supplied so far. To phrase the question another way: in general, can you wrap some arbitrary variadic function without modifying that function's definition.]

nalzok
  • 14,965
  • 21
  • 72
  • 139
Patrick
  • 2,463
  • 2
  • 16
  • 6
  • 2
    awesome question - fortunately I can add a vFunc overload that takes a va_list. Thanks for posting. – Gishu Jun 03 '09 at 07:16

13 Answers13

175

If there is no function analogous to vfprintf that takes a va_list instead of a variable number of arguments, there is no way.

Example:

void myfun(const char *fmt, va_list argp) {
    vfprintf(stderr, fmt, argp);
}
user16217248
  • 3,119
  • 19
  • 19
  • 37
Adam Rosenfield
  • 390,455
  • 97
  • 512
  • 589
  • 6
    Depending on how crucial the issue, invocation by inline assembly still offers this possibility. – filip Jan 11 '15 at 02:03
  • At least one argument must be passed, `myfun("")` is not supported - is there a solution how to fix it? (MSVC prints: too few arguments for call) – mvorisek Jul 20 '20 at 17:28
  • 1
    @mvorisek: No, there's no well-defined solution to that, as variadic functions are required to take at least one named parameter before the `...`. Your workaround may appear to work with your particular compiler, but it's still Undefined Behavior according to the language standard. – Adam Rosenfield Jul 21 '20 at 15:13
  • [Archived link](https://web.archive.org/web/20120727154400/http://c-faq.com/varargs/handoff.html) as the old one suffered from link rot – Seeseemelk Jul 06 '21 at 11:11
  • @AdamRosenfield This is no longer the case in C23. – Mehdi Charife Jul 12 '23 at 23:25
80

Not directly, however it is common (and you will find almost universally the case in the standard library) for variadic functions to come in pairs with a varargs style alternative function. e.g. printf/vprintf

The v... functions take a va_list parameter, the implementation of which is often done with compiler specific 'macro magic', but you are guaranteed that calling the v... style function from a variadic function like this will work:

#include <stdarg.h>

int m_printf(char *fmt, ...)
{
    int ret;

    /* Declare a va_list type variable */
    va_list myargs;

    /* Initialise the va_list variable with the ... after fmt */

    va_start(myargs, fmt);

    /* Forward the '...' to vprintf */
    ret = vprintf(fmt, myargs);

    /* Clean up the va_list */
    va_end(myargs);

    return ret;
}

This should give you the effect that you are looking for.

If you are considering writing a variadic library function you should also consider making a va_list style companion available as part of the library. As you can see from your question, it can be prove useful for your users.

ingomueller.net
  • 4,097
  • 2
  • 36
  • 33
CB Bailey
  • 755,051
  • 104
  • 632
  • 656
  • I am pretty sure you need to call `va_copy` before passing the `myargs` variable to another function. Please see [MSC39-C](https://www.securecoding.cert.org/confluence/display/c/MSC39-C.+Do+not+call+va_arg()+on+a+va_list+that+has+an+indeterminate+value), where it states that what you are doing is undefined behavior. – aviggiano Feb 26 '16 at 04:23
  • 5
    @aviggiano: The rule is "Do not call `va_arg()` on a `va_list` that has an indeterminate value", I don't do that because I never use `va_arg` in my calling function. The _value_ of `myargs` after the call (in this case) to `vprintf` is _indeterminate_ (assuming that it does us `va_arg`). The standard says that `myargs` "shall be passed to the `va_end` macro prior to any further reference to [it]"; which is exactly what I do. I don't need to copy that args because I have no intention of iterating through them in the calling function. – CB Bailey Feb 26 '16 at 15:16
  • 1
    You are right. The [Linux man](http://linux.die.net/man/3/va_arg) page confirms that. _If `ap` is passed to a function that uses `va_arg(ap,type)` then the value of `ap` is undefined after the return of that function._ – aviggiano Feb 26 '16 at 16:28
59

C99 supports macros with variadic arguments; depending on your compiler, you might be able to declare a macro that does what you want:

#define my_printf(format, ...) \
    do { \
        fprintf(stderr, "Calling printf with fmt %s\n", format); \
        some_other_variadac_function(format, ##__VA_ARGS__); \
    } while(0)

In general, though, the best solution is to use the va_list form of the function you're trying to wrap, should one exist.

Schwern
  • 153,029
  • 25
  • 195
  • 336
Commodore Jaeger
  • 32,280
  • 4
  • 54
  • 44
19

gcc offers an extension that can do this: __builtin_apply and relatives. See Constructing Function Calls in the gcc manual.

An example:

#include <stdio.h>

int my_printf(const char *fmt, ...) {
    void *args = __builtin_apply_args();
    printf("Hello there! Format string is %s\n", fmt);
    void *ret = __builtin_apply((void (*)())printf, args, 1000);
    __builtin_return(ret);
}

int main(void) {
    my_printf("%d %f %s\n", -37, 3.1415, "spam");
    return 0;
}

Try it on godbolt

There are some cautions in the documentation that it might not work in more complicated situations. And you have to hardcode a maximum size for the arguments (here I used 1000). But it might be a reasonable alternative to the other approaches that involve dissecting the stack in either C or assembly language.

Nate Eldredge
  • 48,811
  • 6
  • 54
  • 82
15

As it is not really possible to forward such calls in a nice way, we worked around this by setting up a new stack frame with a copy of the original stack frame. However this is highly unportable and makes all kinds of assumptions, e.g. that the code uses frame pointers and the 'standard' calling conventions.

This header file allows to wrap variadic functions for x86_64 and i386 (GCC). It doesn't work for floating-point arguments, but should be straight forward to extend for supporting those.

#ifndef _VA_ARGS_WRAPPER_H
#define _VA_ARGS_WRAPPER_H
#include <limits.h>
#include <stdint.h>
#include <alloca.h>
#include <inttypes.h>
#include <string.h>

/* This macros allow wrapping variadic functions.
 * Currently we don't care about floating point arguments and
 * we assume that the standard calling conventions are used.
 *
 * The wrapper function has to start with VA_WRAP_PROLOGUE()
 * and the original function can be called by
 * VA_WRAP_CALL(function, ret), whereas the return value will
 * be stored in ret.  The caller has to provide ret
 * even if the original function was returning void.
 */

#define __VA_WRAP_CALL_FUNC __attribute__ ((noinline))

#define VA_WRAP_CALL_COMMON()                                        \
    uintptr_t va_wrap_this_bp,va_wrap_old_bp;                        \
    va_wrap_this_bp  = va_wrap_get_bp();                             \
    va_wrap_old_bp   = *(uintptr_t *) va_wrap_this_bp;               \
    va_wrap_this_bp += 2 * sizeof(uintptr_t);                        \
    size_t volatile va_wrap_size = va_wrap_old_bp - va_wrap_this_bp; \
    uintptr_t *va_wrap_stack = alloca(va_wrap_size);                 \
    memcpy((void *) va_wrap_stack,                                   \
        (void *)(va_wrap_this_bp), va_wrap_size);


#if ( __WORDSIZE == 64 )

/* System V AMD64 AB calling convention */

static inline uintptr_t __attribute__((always_inline)) 
va_wrap_get_bp()
{
    uintptr_t ret;
    asm volatile ("mov %%rbp, %0":"=r"(ret));
    return ret;
}


#define VA_WRAP_PROLOGUE()           \
    uintptr_t va_wrap_ret;           \
    uintptr_t va_wrap_saved_args[7]; \
    asm volatile  (                  \
    "mov %%rsi,     (%%rax)\n\t"     \
    "mov %%rdi,  0x8(%%rax)\n\t"     \
    "mov %%rdx, 0x10(%%rax)\n\t"     \
    "mov %%rcx, 0x18(%%rax)\n\t"     \
    "mov %%r8,  0x20(%%rax)\n\t"     \
    "mov %%r9,  0x28(%%rax)\n\t"     \
    :                                \
    :"a"(va_wrap_saved_args)         \
    );

#define VA_WRAP_CALL(func, ret)            \
    VA_WRAP_CALL_COMMON();                 \
    va_wrap_saved_args[6] = (uintptr_t)va_wrap_stack;  \
    asm volatile (                         \
    "mov      (%%rax), %%rsi \n\t"         \
    "mov   0x8(%%rax), %%rdi \n\t"         \
    "mov  0x10(%%rax), %%rdx \n\t"         \
    "mov  0x18(%%rax), %%rcx \n\t"         \
    "mov  0x20(%%rax),  %%r8 \n\t"         \
    "mov  0x28(%%rax),  %%r9 \n\t"         \
    "mov           $0, %%rax \n\t"         \
    "call             *%%rbx \n\t"         \
    : "=a" (va_wrap_ret)                   \
    : "b" (func), "a" (va_wrap_saved_args) \
    :  "%rcx", "%rdx",                     \
      "%rsi", "%rdi", "%r8", "%r9",        \
      "%r10", "%r11", "%r12", "%r14",      \
      "%r15"                               \
    );                                     \
    ret = (typeof(ret)) va_wrap_ret;

#else

/* x86 stdcall */

static inline uintptr_t __attribute__((always_inline))
va_wrap_get_bp()
{
    uintptr_t ret;
    asm volatile ("mov %%ebp, %0":"=a"(ret));
    return ret;
}

#define VA_WRAP_PROLOGUE() \
    uintptr_t va_wrap_ret;

#define VA_WRAP_CALL(func, ret)        \
    VA_WRAP_CALL_COMMON();             \
    asm volatile (                     \
    "mov    %2, %%esp \n\t"            \
    "call  *%1        \n\t"            \
    : "=a"(va_wrap_ret)                \
    : "r" (func),                      \
      "r"(va_wrap_stack)               \
    : "%ebx", "%ecx", "%edx"   \
    );                                 \
    ret = (typeof(ret))va_wrap_ret;
#endif

#endif

In the end you can wrap calls like this:

int __VA_WRAP_CALL_FUNC wrap_printf(char *str, ...)
{
    VA_WRAP_PROLOGUE();
    int ret;
    VA_WRAP_CALL(printf, ret);
    printf("printf returned with %d \n", ret);
    return ret;
}
coltox
  • 360
  • 3
  • 8
12

Almost, using the facilities available in <stdarg.h>:

#include <stdarg.h>
int my_printf(char *format, ...)
{
   va_list args;
   va_start(args, format);
   int r = vprintf(format, args);
   va_end(args);
   return r;
}

Note that you will need to use the vprintf version rather than plain printf. There isn't a way to directly call a variadic function in this situation without using va_list.

Greg Hewgill
  • 951,095
  • 183
  • 1,149
  • 1,285
  • 2
    Thanks, but from the question: "the codebase I'm working on [...] doesn't have (and can't have added) a helper function akin to vfprintf." – Patrick Sep 29 '08 at 20:53
4

Use vfprintf:

int my_printf(char *fmt, ...) {
    va_list va;
    int ret;

    va_start(va, fmt);
    ret = vfprintf(stderr, fmt, va);
    va_end(va);
    return ret;
}
Greg Rogers
  • 35,641
  • 17
  • 67
  • 94
  • 1
    Thanks, but from the question: "the codebase I'm working on [...] doesn't have (and can't have added) a helper function akin to vfprintf." – Patrick Sep 29 '08 at 20:53
2

There's no way to forward such function calls because the only location where you can retrieve raw stack elements is in my_print(). The usual way to wrap calls like that is to have two functions, one that just converts the arguments into the various varargs structs, and another that actually operates upon those structs. Using such a double-function model, you can (for example) wrap printf() by initializing the structs in my_printf() with va_start(), and then pass them to vfprintf().

John Millikin
  • 197,344
  • 39
  • 212
  • 226
2

Not sure if this helps to answer OP's question since I do not know why the restriction for using a helper function akin to vfprintf in the wrapper function applies. I think the key problem here is that forwarding the variadic argument list without interpreting them is difficult. What is possible, is to perform the formatting (using a helper function akin to vfprintf: vsnprintf) and forward the formatted output to the wrapped function with variadic arguments (i.e. not modifying the definition of the wrapped function). So, here we go:

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

int my_printf(char *fmt, ...)
{
    if (fmt == NULL) {
        /* Invalid format pointer */
        return -1;
    } else {
        va_list args;
        int len;

        /* Initialize a variable argument list */
        va_start(args, fmt);

        /* Get length of format including arguments */
        len = vsnprintf(NULL, 0, fmt, args);

        /* End using variable argument list */
        va_end(args);
        
        if (len < 0) {
            /* vsnprintf failed */
            return -1;
        } else {
            /* Declare a character buffer for the formatted string */
            char formatted[len + 1];

            /* Initialize a variable argument list */
            va_start(args, fmt);
            
            /* Write the formatted output */
            vsnprintf(formatted, sizeof(formatted), fmt, args);
            
            /* End using variable argument list */
            va_end(args);

            /* Call the wrapped function using the formatted output and return */
            fprintf(stderr, "Calling printf with fmt %s", fmt);
            return printf("%s", formatted);
        }
    }
}

int main()
{
    /* Expected output: Test
     * Expected error: Calling printf with fmt Test
     */
    my_printf("Test\n");
    //printf("Test\n");

    /* Expected output: Test
     * Expected error: Calling printf with fmt %s
     */
    my_printf("%s\n", "Test");
    //printf("%s\n", "Test");

    /* Expected output: %s
     * Expected error: Calling printf with fmt %s
     */
    my_printf("%s\n", "%s");
    //printf("%s\n", "%s");

    return 0;
}

I came across this solution here.

Editted: fixed mistakes pointed out by egmont

Bert Regelink
  • 2,696
  • 23
  • 17
1

There are essentially three options.

One is to not pass it on but to use the variadic implementation of your target function and not pass on the ellipses. The other one is to use a variadic macro. The third option is all the stuff i am missing.

I usually go with option one since i feel like this is really easy to handle. Option two has a drawback because there are some limitations to calling variadic macros.

Here is some example code:

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

#define Option_VariadicMacro(f, ...)\
    printf("printing using format: %s", f);\
    printf(f, __VA_ARGS__)

int Option_ResolveVariadicAndPassOn(const char * f, ... )
{
    int r;
    va_list args;

    printf("printing using format: %s", f);
    va_start(args, f);
    r = vprintf(f, args);
    va_end(args);
    return r;
}

void main()
{
    const char * f = "%s %s %s\n";
    const char * a = "One";
    const char * b = "Two";
    const char * c = "Three";
    printf("---- Normal Print ----\n");
    printf(f, a, b, c);
    printf("\n");
    printf("---- Option_VariadicMacro ----\n");
    Option_VariadicMacro(f, a, b, c);
    printf("\n");
    printf("---- Option_ResolveVariadicAndPassOn ----\n");
    Option_ResolveVariadicAndPassOn(f, a, b, c);
    printf("\n");
}
Johannes
  • 6,490
  • 10
  • 59
  • 108
0

Yes you can do it, but it is somewhat ugly and you have to know the maximal number of arguments. Furthermore if you are on an architecture where the arguments aren't passed on the stack like the x86 (for instance, PowerPC), you will have to know if "special" types (double, floats, altivec etc.) are used and if so, deal with them accordingly. It can be painful quickly but if you are on x86 or if the original function has a well defined and limited perimeter, it can work. It still will be a hack, use it for debugging purpose. Do not build you software around that. Anyway, here's a working example on x86:

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

int old_variadic_function(int n, ...)
{
  va_list args;
  int i = 0;

  va_start(args, n);

  if(i++<n) printf("arg %d is 0x%x\n", i, va_arg(args, int));
  if(i++<n) printf("arg %d is %g\n",   i, va_arg(args, double));
  if(i++<n) printf("arg %d is %g\n",   i, va_arg(args, double));

  va_end(args);

  return n;
}

int old_variadic_function_wrapper(int n, ...)
{
  va_list args;
  int a1;
  int a2;
  int a3;
  int a4;
  int a5;
  int a6;
  int a7;
  int a8;

  /* Do some work, possibly with another va_list to access arguments */

  /* Work done */

  va_start(args, n);

  a1 = va_arg(args, int);
  a2 = va_arg(args, int);
  a3 = va_arg(args, int);
  a4 = va_arg(args, int);
  a5 = va_arg(args, int);
  a6 = va_arg(args, int);
  a7 = va_arg(args, int);

  va_end(args);

  return old_variadic_function(n, a1, a2, a3, a4, a5, a6, a7, a8);
}

int main(void)
{
  printf("Call 1: 1, 0x123\n");
  old_variadic_function(1, 0x123);
  printf("Call 2: 2, 0x456, 1.234\n");
  old_variadic_function(2, 0x456, 1.234);
  printf("Call 3: 3, 0x456, 4.456, 7.789\n");
  old_variadic_function(3, 0x456, 4.456, 7.789);
  printf("Wrapped call 1: 1, 0x123\n");
  old_variadic_function_wrapper(1, 0x123);
  printf("Wrapped call 2: 2, 0x456, 1.234\n");
  old_variadic_function_wrapper(2, 0x456, 1.234);
  printf("Wrapped call 3: 3, 0x456, 4.456, 7.789\n");
  old_variadic_function_wrapper(3, 0x456, 4.456, 7.789);

  return 0;
}

For some reason, you can't use floats with va_arg, gcc says they are converted to double but the program crashes. That alone demonstrates that this solution is a hack and that there is no general solution. In my example I assumed that the maximum number of arguments was 8, but you can increase that number. The wrapped function also only used integers but it works the same way with other 'normal' parameters since they always cast to integers. The target function will know their types but your intermediary wrapper doesn't need to. The wrapper also doesn't need to know the right number of arguments since the target function will also know it. To do useful work (except just logging the call), you probably will have to know both though.

user10146
  • 47
  • 3
0

The best way to do this is

static BOOL(__cdecl *OriginalVarArgsFunction)(BYTE variable1, char* format, ...)(0x12345678); //TODO: change address lolz

BOOL __cdecl HookedVarArgsFunction(BYTE variable1, char* format, ...)
{
    BOOL res;

    va_list vl;
    va_start(vl, format);

    // Get variable arguments count from disasm. -2 because of existing 'format', 'variable1'
    uint32_t argCount = *((uint8_t*)_ReturnAddress() + 2) / sizeof(void*) - 2;
    printf("arg count = %d\n", argCount);

    // ((int( __cdecl* )(const char*, ...))&oldCode)(fmt, ...);
    __asm
    {
        mov eax, argCount
        test eax, eax
        je noLoop
        mov edx, vl
        loop1 :
        push dword ptr[edx + eax * 4 - 4]
        sub eax, 1
        jnz loop1
        noLoop :
        push format
        push variable1
        //lea eax, [oldCode] // oldCode - original function pointer
        mov eax, OriginalVarArgsFunction
        call eax
        mov res, eax
        mov eax, argCount
        lea eax, [eax * 4 + 8] //+8 because 2 parameters (format and variable1)
        add esp, eax
    }
    return res;
}
SSpoke
  • 5,656
  • 10
  • 72
  • 124
-2

If it is possible to compile your code using a C++11 or higher compiler, you can use a variadic function template:

#include <stdio.h>

template<typename... Targs>
int my_printf(const char *fmt, Targs... Fargs) {
    fprintf(stderr, "Calling printf with fmt %s", fmt);
    return printf(fmt, Fargs...);;
}

int main() {
    my_printf("test %d\n", 1);
    return 0;
}

Demo

sbrajchuk
  • 48
  • 6
  • The question is about C. C++ is not C, nor even a superset of C. – John Bollinger Feb 07 '23 at 18:45
  • And indeed it **isn't** possible ***in C***, which, again, is what the question is about. I appreciate that your rep is too low to comment on the question (not that that takes very much rep), but if your objective is to alert people that the the answer is different in C++ than in C then a comment on the question would be much more effective. – John Bollinger Feb 07 '23 at 19:23
  • I don't see how you read the OP's update as expanding the scope of the question beyond C. If they meant to ask about C++ then they would have tagged C++ and explicitly said "C++" in the question. They especially would have done that with their update if they felt that answers were missing on that point. On the contrary, the update clarifies that they are asking about (C) variadic functions in general, not limited to `printf`-like functions such as their example. Generally speaking, "write it in C++" is not a useful answer to a question about accomplishing something in C. – John Bollinger Feb 07 '23 at 20:26
  • I think "write it in C++" is a useful answer if it can save your time. – sbrajchuk Feb 08 '23 at 00:23
  • My answer is not an academic study of the strong and weak points of the C language, but a practical solution. If you want to wrap a C variadic function and it has no v... equivalent, you can implement a wrapper function using a C++ variadic function template, ***leaving the rest of the C code unchanged***. This approach can sometimes work and I am sure it can save ***a lot*** of time. – sbrajchuk Feb 08 '23 at 05:47
  • That's just it. For C to call C++, the C++ function must have C linkage. That excludes template functions straight off the bat. So no, C code ***cannot*** call your function, changes or no. There may be some problems that afford a good solution via adding some C++ to an otherwise C program, but this is not one of them. At least, not via the approach described. – John Bollinger Feb 08 '23 at 13:35
  • By "C code," I refer to source files written in the C programming language, typically identified by the ".c" file extension. These are not to be confused with binary files produced by a C compiler. If you use a C++ compiler to compile these C code files, the code within them will have the ability to call variadic template functions. – sbrajchuk Feb 09 '23 at 12:46
  • Code written in C generally is not also written in C++. It takes intent and considerable effort to write a significant body of code exclusively in the shared subset of C and C++. Compiling C source code with a C++ compiler is usually a mistake. But if you're supposing that the code in question is written in the shared subset then that means *your solution is for C++*, not for C, except inasmuch as there may be a rare project that is *in whole* both C and C++. Source file names notwithstanding. – John Bollinger Feb 09 '23 at 13:24