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

189

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

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

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

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

Патрик
источник
1
удивительный вопрос - к счастью, я могу добавить перегрузку vFunc, которая принимает va_list. Спасибо за публикацию.
Гишу

Ответы:

157

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

Пример:

void myfun(const char *fmt, va_list argp) {
    vfprintf(stderr, fmt, argp);
}
Адам Розенфилд
источник
5
В зависимости от того, насколько важна проблема, вызов встроенной сборкой все еще предлагает такую ​​возможность.
Филип
60

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

CB Bailey
источник
Я уверен, что вам нужно вызвать va_copyперед передачей myargsпеременной в другую функцию. Пожалуйста, смотрите MSC39-C , где говорится, что то, что вы делаете, является неопределенным поведением.
aviggiano
2
@aviggiano: Правило «Не вызывать va_arg()объект va_listс неопределенным значением», я этого не делаю, потому что никогда не использую va_argв своей вызывающей функции. Значение из myargsпосле вызова (в данном случае) vprintfявляется неопределенным (при условии , что она делает нас va_arg). Стандарт гласит, что myargs«должен быть передан va_endмакросу до любой дальнейшей ссылки на [it]»; это именно то, что я делаю. Мне не нужно копировать эти аргументы, потому что я не собираюсь повторять их в вызывающей функции.
CB Bailey
1
Ты прав. Человек Linux страница подтверждает , что. Если apпередается функции, которая использует, va_arg(ap,type)то значение apне определено после возврата этой функции.
aviggiano
48

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

Коммодор егер
источник
12

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

Грег Хьюгилл
источник
1
Спасибо, но из вопроса: «кодовая база, над которой я работаю [...], не имеет (и не могла добавить) вспомогательную функцию, похожую на vfprintf».
Патрик
11

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

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

Используйте 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;
}
Грег Роджерс
источник
1
Спасибо, но из вопроса: «кодовая база, над которой я работаю [...], не имеет (и не могла добавить) вспомогательную функцию, похожую на vfprintf».
Патрик
2

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

Джон Милликин
источник
То есть нет способа переслать вызов, если вы не можете изменить реализацию функции, которую хотите обернуть?
Патрик
Правильно. Лучше всего будет добавить упаковку в стиле vprintf.
Джон Милликин
1

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

user10146
источник
1

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

Попробуй это на Годболте

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

Нейт Элдридж
источник
Полезно. Спасибо!
rsmoorthy
0

По сути, есть три варианта.

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

Я обычно выбираю первый вариант, так как чувствую, что с ним действительно легко справиться. У варианта два есть недостаток, потому что есть некоторые ограничения к вызову 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");
}
Johannes
источник
0

Лучший способ сделать это

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;
}
SSpoke
источник
0

Не уверен, поможет ли это ответить на вопрос OP, так как я не знаю, почему применяется ограничение на использование вспомогательной функции, похожей на vfprintf в функции-обертке. Я думаю, что ключевая проблема здесь заключается в том, что пересылка списка аргументов с переменными параметрами без их интерпретации является сложной задачей. Что возможно, это выполнить форматирование (используя вспомогательную функцию, похожую на vfprintf: vsnprintf) и переслать отформатированный вывод в упакованную функцию с переменными аргументами (т.е. без изменения определения упакованной функции). Итак, поехали:

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

    if (fmt == NULL) {
        /* Invalid format pointer */
        ret = -1;
    } else {
        va_list args;
        int len;

        va_start(args, fmt);

        /* Get length of format including arguments */
        len = vsnprintf(NULL, 0, fmt, args);

        if (len < 0) {
            /* vsnprintf failed */
            ret = -1;
        } else {
            /* Declare a character buffer and write the formatted string */
            char formatted[len + 1];
            vsnprintf(formatted, sizeof(formatted), fmt, args);

            /* Call the wrapped function using the formatted output and return */
            fprintf(stderr, "Calling printf with fmt %s", fmt);
            ret = printf(formatted);
        }

        va_end(args);
    }

    return ret;
}

Я столкнулся с этим решением здесь .

Берт Регелинк
источник