Forward an invocation of a variadic function in C

前端 未结 12 2431
悲哀的现实
悲哀的现实 2020-11-22 06:41

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 %         


        
相关标签:
12条回答
  • 2020-11-22 07:22

    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;
    }
    
    0 讨论(0)
  • 2020-11-22 07:23

    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.

    0 讨论(0)
  • 2020-11-22 07:25

    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");
    }
    
    0 讨论(0)
  • 2020-11-22 07:26

    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.

    0 讨论(0)
  • 2020-11-22 07:26

    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;
    }
    
    0 讨论(0)
  • 2020-11-22 07:31

    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.

    0 讨论(0)
提交回复
热议问题