Переслать вызов функции с переменным числом в C - PullRequest
167 голосов
/ 30 сентября 2008

В C возможно ли переадресация вызова переменной функции? Как в,

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

Пересылка вызова описанным выше способом, очевидно, не является строго необходимой в этом случае (поскольку вы можете регистрировать вызовы другими способами или использовать vfprintf), но кодовая база, над которой я работаю, требует, чтобы оболочка выполнила некоторую фактическую работу и не имеет (и не может добавить) вспомогательную функцию, похожую на vfprintf.

[Обновление: кажется, что существует некоторая путаница, основанная на ответах, которые были предоставлены до сих пор. Чтобы сформулировать вопрос по-другому: в общем, вы можете обернуть некоторую произвольную переменную функцию без изменения определения этой функции .]

Ответы [ 10 ]

143 голосов
/ 30 сентября 2008

Если у вас нет функции, аналогичной vfprintf, которая принимает va_list вместо переменного числа аргументов, , вы не можете это сделать . См. http://c -faq.com / varargs / handoff.html .

Пример:

void myfun(const char *fmt, va_list argp) {
    vfprintf(stderr, fmt, argp);
}
55 голосов
/ 30 сентября 2008

Непосредственно, однако обычно (и вы найдете это почти повсеместно в стандартной библиотеке), когда функции с переменными числами идут парами с альтернативной функцией в стиле varargs. например printf / vprintf

Функции v ... принимают параметр va_list, реализация которого часто выполняется с помощью специфической для компилятора 'макро-магии', но вы гарантированно, что вызов функции v ... style из функции с переменными параметрами, подобной этой, будет работать :

#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 доступным как часть библиотеки. Как видно из вашего вопроса, он может оказаться полезным для ваших пользователей.

43 голосов
/ 30 сентября 2008

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 , которую вы пытаетесь обернуть, если она существует.

11 голосов
/ 30 сентября 2008

Почти, используя средства, доступные в <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.

10 голосов
/ 27 мая 2013

Поскольку переадресация таких вызовов в действительности невозможна, мы решили эту проблему, настроив новый кадр стека с копией исходного кадра стека. Однако это крайне непереносимо и делает всевозможные предположения , например, что код использует указатели фреймов и «стандартные» соглашения о вызовах.

Этот заголовочный файл позволяет обернуть переменные функции для 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;
}
3 голосов
/ 30 сентября 2008

Использовать 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;
}
2 голосов
/ 30 сентября 2008

Нет способа перенаправить такие вызовы функций, потому что единственное место, где вы можете получить необработанные элементы стека, находится в my_print(). Обычный способ обернуть вызовы подобным образом - это иметь две функции, одна из которых просто преобразует аргументы в различные структуры varargs, а другая - фактически действует на эти структуры. Используя такую ​​двухфункциональную модель, вы можете (например) обернуть printf(), инициализируя структуры в my_printf() с va_start(), а затем передать их vfprintf().

1 голос
/ 30 сентября 2008

Да, вы можете сделать это, но это несколько уродливо, и вы должны знать максимальное количество аргументов. Более того, если вы работаете в архитектуре, в которой аргументы не передаются в стек, например, в x86 (например, PowerPC), вам необходимо знать, используются ли «специальные» типы (double, float, 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;
}

По какой-то причине вы не можете использовать float с va_arg, gcc говорит, что они конвертируются в удвоенные, но программа вылетает. Уже одно это демонстрирует, что это решение является взломом и что нет общего решения. В моем примере я предположил, что максимальное количество аргументов было 8, но вы можете увеличить это число. Обернутая функция также использует только целые числа, но она работает так же, как и другие «нормальные» параметры, поскольку они всегда приводятся к целым числам. Функция назначения будет знать их типы, но ваша промежуточная оболочка не нуждается в этом. Оболочке также не нужно знать правильное количество аргументов, поскольку целевая функция также будет знать это. Чтобы сделать полезную работу (кроме простой регистрации звонка), вам, вероятно, придется знать и то и другое.

1 голос
/ 30 сентября 2008

Извините за не тему темы, но:

Мета-проблема заключается в том, что интерфейс varargs в C с самого начала был существенно разрушен. Это приглашение к переполнению буфера и недопустимому доступу к памяти, потому что конец списка аргументов не может быть найден без явного сигнала завершения (который никто не использует из-за лени). И он всегда опирался на эзотерические макросы, специфичные для реализации, поскольку жизненно важный макрос va_copy () поддерживается только на некоторых архитектурах.

0 голосов
/ 13 февраля 2014

Существуют три основных варианта.

Один из них - не передавать его, а использовать вариативную реализацию целевой функции, а не передавать эллипсы. Другой - использовать макрос с переменным значением. Третий вариант - это все, чего мне не хватает.

Я обычно выбираю первый вариант, потому что чувствую, что с ним действительно легко справиться. Второй вариант имеет недостаток, поскольку существуют некоторые ограничения при вызове variadic-макросов.

Вот пример кода:

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