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

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

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

[更新:根据目前提供的答案,似乎有一些困惑。用另一种方式来表达这个问题:一般来说,你可以在不修改函数定义的情况下包装任意的变进函数吗?


当前回答

最好的办法是

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

其他回答

使用函数:

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

    va_start(va, fmt);
    ret = vfprintf(stderr, fmt, va);
    va_end(va);
    return ret;
}

最好的办法是

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

如果可以使用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

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或汇编语言分析堆栈的方法的合理替代方案。

差不多,使用<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,就无法直接调用变进函数。