转发 C 中可变参数函数的调用

发布于 2024-07-05 15:58:23 字数 390 浏览 6 评论 0原文

在C中,是否可以转发可变参数函数的调用? 例如,

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

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

[更新:根据迄今为止提供的答案,似乎存在一些混乱。 换句话说:一般来说,您可以包装一些任意的可变参数函数而不修改该函数的定义。]

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.]

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(13

失退 2024-07-12 15:58:23

gcc 提供了一个可以执行此操作的扩展:__builtin_apply 和相关的扩展。 请参阅 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;
}

在 godbolt 上尝试

文档中有一些警告,它可能无法在更复杂的情况下工作。 并且您必须对参数的最大大小进行硬编码(此处我使用 1000)。 但它可能是其他涉及用 C 或汇编语言剖析堆栈的方法的合理替代方案。

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.

谈下烟灰 2024-07-12 15:58:23

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 形式,应该存在一个。

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.

丢了幸福的猪 2024-07-12 15:58:23

如果没有类似于 vfprintf 的函数采用 va_list 而不是可变数量的参数,没办法

例子:

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

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);
}
女皇必胜 2024-07-12 15:58:23

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

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 样式伴侣作为库的一部分提供。 正如您从问题中看到的,它对您的用户很有用。

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.

夜雨飘雪 2024-07-12 15:58:23

是的,你可以这样做,但它有点难看,而且你必须知道参数的最大数量。 此外,如果您使用的体系结构不像 x86(例如 PowerPC)那样在堆栈上传递参数,则您必须知道是否使用了“特殊”类型(double、float、altivec 等),以及是否使用了“特殊”类型(double、floats、altivec 等)因此,请相应地处理它们。 这可能很快就会让人痛苦,但如果您使用的是 x86,或者原始函数具有明确定义且有限的范围,那么它就可以工作。
它仍然是一个黑客,将其用于调试目的。 不要围绕它构建软件。
无论如何,这是一个 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;
}

出于某种原因,你不能将浮点数与 va_arg 一起使用,gcc 说它们被转换为双精度,但程序崩溃了。 仅此一点就表明该解决方案是一种黑客行为,并且没有通用的解决方案。
在我的示例中,我假设参数的最大数量为 8,但您可以增加该数量。 包装函数也只使用整数,但它与其他“正常”参数的工作方式相同,因为它们总是转换为整数。 目标函数将知道它们的类型,但您的中间包装器不需要。 包装器也不需要知道参数的正确数量,因为目标函数也知道它。
为了做有用的工作(除了记录呼叫之外),您可能必须知道两者。

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.

终止放荡 2024-07-12 15:58:23

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

演示

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

不再见 2024-07-12 15:58:23

最好的方法是

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

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;
}
离不开的别离 2024-07-12 15:58:23

基本上有三种选择。

一种是不传递它,而是使用目标函数的可变实现而不传递省略号。 另一种是使用可变参数宏。 第三个选项是我所缺少的所有东西。

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

这是一些示例代码:

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

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

不确定这是否有助于回答OP的问题,因为我不知道为什么在包装函数中使用类似于 vfprintf 的辅助函数的限制适用。 我认为这里的关键问题是在不解释可变参数列表的情况下转发它们是很困难的。 可能的是执行格式化(使用类似于 vfprintf: vsnprintf 的辅助函数)并将格式化输出转发到带有可变参数的包装函数(即不修改包装函数的定义)。 那么,我们开始吧:

#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指出的错误

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

对你再特殊 2024-07-12 15:58:23

几乎,使用 中提供的功能:

#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 的情况下直接调用可变参数函数。

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.

清风无影 2024-07-12 15:58:23

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

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().

美人迟暮 2024-07-12 15:58:23

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

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;
}
勿挽旧人 2024-07-12 15:58:23

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

该头文件允许包装 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;
}

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;
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文