Переслать вызов вариационной функции в C

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

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

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

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

Ответ 1

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

Пример:

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

Ответ 2

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

Ответ 3

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

Ответ 4

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

Ответ 5

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

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

Ответ 6

Использовать 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;
}

Ответ 7

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

Ответ 8

Извините за разглашение не по теме, но:

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

Ответ 9

Да, вы можете это сделать, но это несколько уродливо, и вам нужно знать максимальное количество аргументов. Кроме того, если вы находитесь в архитектуре, где аргументы не передаются в стеке, например 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 говорит, что они преобразуются в double, но программа выходит из строя. Это само по себе демонстрирует, что это решение является взломом и что нет общего решения. В моем примере я предположил, что максимальное количество аргументов равно 8, но вы можете увеличить это число. Обернутая функция также использует только целые числа, но она работает так же, как и с другими "нормальными" параметрами, поскольку они всегда отливаются от целых чисел. Целевая функция будет знать их типы, но вашей промежуточной обертке не нужно. Обертке также не нужно знать правильное количество аргументов, так как целевая функция также будет знать это. Чтобы выполнить полезную работу (за исключением только входа в систему), вам, вероятно, придется знать оба.

Ответ 10

Существует по существу три варианта.

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

Обычно я выбираю вариант с тех пор, как чувствую, что это очень легко обрабатывать. Второй вариант имеет недостаток, потому что есть некоторые ограничения для вызова переменных массивов.

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

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