在C中,是否有可能转发可变函数的调用?如,

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

在这种情况下,以上述方式转发调用显然不是严格必要的(因为您可以以其他方式记录调用,或使用vfprintf),但我正在处理的代码库要求包装器做一些实际工作,并且没有(也不可能添加)类似于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;
}

差不多,使用<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;
}

注意,您需要使用vprintf版本,而不是普通的printf。在这种情况下,如果不使用va_list,就无法直接调用变进函数。


没有办法转发这样的函数调用,因为可以检索原始堆栈元素的唯一位置是my_print()。像这样包装调用的通常方法是有两个函数,一个只是将参数转换为各种varargs结构体,另一个实际操作这些结构体。使用这样的双函数模型,您可以(例如)通过使用va_start()初始化my_printf()中的结构来包装printf(),然后将它们传递给vfprintf()。


如果您没有类似于vfprintf的函数,它接受一个va_list而不是可变数量的参数,那么您就不能这样做。见http://c-faq.com/varargs/handoff.html。

例子:

void myfun(const char *fmt, va_list argp) {
    vfprintf(stderr, fmt, argp);
}

不是直接的,但是变进函数与可变参数样式的替代函数成对出现是很常见的(在标准库中几乎是普遍的情况)。例如printf函数/

v……函数接受一个va_list形参,它的实现通常是通过编译器特定的“宏魔法”来完成的,但是你可以保证调用v…像这样的可变函数的样式函数可以工作:

#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;
}

这应该会给你你正在寻找的效果。

如果你正在考虑编写一个可变值库函数,你还应该考虑将一个va_list样式的同伴作为库的一部分。从你的问题中可以看出,它对你的用户是有用的。


C99支持可变参数宏;取决于你的编译器,你可以声明一个宏来做你想做的事情:

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

不过,一般来说,最好的解决方案是使用您试图包装的函数的va_list形式(如果存在的话)。


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.


由于不可能以一种良好的方式转发这样的调用,我们通过使用原始堆栈框架的副本设置一个新的堆栈框架来解决这个问题。然而,这是高度不可移植的,并做出各种各样的假设,例如,代码使用帧指针和“标准”调用约定。

这个头文件允许包装x86_64和i386 (GCC)的可变函数。它不适用于浮点实参,但应该可以直接扩展以支持浮点实参。

#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

最后,你可以像这样包装调用:

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;
}

基本上有三种选择。

一种是不传递它而是使用目标函数的变进实现而不传递椭圆。另一种方法是使用可变宏。第三个选择是我所缺少的所有东西。

我通常选择第一种,因为我觉得这真的很容易处理。选项二有一个缺点,因为调用可变宏有一些限制。

下面是一些示例代码:

#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");
}

最好的办法是

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;
}

GCC提供了一个可以做到这一点的扩展:__builtin_apply和relatives。参见gcc手册中的构造函数调用。

一个例子:

#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;
}

戴上试试吧

文档中有一些警告,说明它可能不适用于更复杂的情况。你必须硬编码参数的最大大小(这里我使用了1000)。但它可能是其他涉及用C或汇编语言分析堆栈的方法的合理替代方案。


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;
}

我遇到了这个解。

编辑:修正由egmont指出的错误


如果可以使用c++ 11或更高版本的编译器编译代码,则可以使用可变参数函数模板:

#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