Переслати виклик варіативної функції в C


189

В C, чи можна переслати виклик варіативної функції? А саме,

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

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

[Оновлення: начебто, існує певна плутанина, заснована на відповідях, що були надані до цього часу. Сформулювати питання іншим способом: загалом, чи можете ви обміняти якусь довільну варіативну функцію, не змінюючи визначення цієї функції .]


1
дивовижне запитання - на щастя, я можу додати перевантаження vFunc, яке займає список va_list. Дякуємо за публікацію
Gishu

Відповіді:


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 ... з подібної варіативної функції буде працювати:

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


Я впевнений, що вам потрібно зателефонувати, va_copyперш ніж передати myargsзмінну в іншу функцію. Будь ласка, дивіться MSC39-C , де йдеться про те, що ви робите це невизначена поведінка.
aviggiano

2
@aviggiano: Правило "Не дзвонити va_arg()на va_listте, що має невизначене значення", я цього не роблю, тому що я ніколи не використовую va_argв своїй функції дзвінків. Значення з myargsпісля виклику (в даному випадку) vprintfє невизначеним (за умови , що вона робить нас va_arg). Стандарт говорить, що myargs"передається va_endмакросу перед будь-яким подальшим посиланням на [це]"; що саме я і роблю. Мені не потрібно копіювати ці аргументи, оскільки я не маю наміру повторювати їх у викликовій функції.
CB Bailey

1
Ти правий. Сторінка man 's 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;
}

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

Чомусь не можна використовувати floats з va_arg, gcc каже, що вони перетворюються в подвійний, але програма виходить з ладу. Це одне лише демонструє, що це рішення є хак і що загального рішення немає. У своєму прикладі я припускав, що максимальна кількість аргументів була 8, але ви можете збільшити це число. Обгорнута функція також використовувала лише цілі числа, але вона працює аналогічно з іншими 'нормальними' параметрами, оскільки вони завжди передаються цілим числам. Цільова функція буде знати їх типи, але вашому посереднику не потрібно. Обгортці також не потрібно знати потрібну кількість аргументів, оскільки цільова функція також знатиме це. Щоб виконувати корисну роботу (за винятком лише реєстрації дзвінка), вам, мабуть, доведеться знати обоє.


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

Спробуйте це на Godbolt

У документації є застереження, що це може не спрацювати у складніших ситуаціях. І вам доведеться жорстко кодувати максимальний розмір аргументів (тут я використав 1000). Але це може бути розумною альтернативою іншим підходам, які передбачають розсічення стека або на C, або на мові складання.


Корисні. Дякую!
rsmoorthy

0

По суті є три варіанти.

Перше - не передавати його, а використовувати різноманітну реалізацію цільової функції, а не передавати еліпси. Інший - використовувати різноманітний макрос. Третій варіант - це всі речі, які мені не вистачає.

Зазвичай я йду з варіантом один, оскільки мені здається, що це дуже просто впоратися. Варіант другий має недолік, оскільки є певні обмеження для виклику різноманітних макросів.

Ось приклад коду:

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

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

0

Не впевнений, чи це допомагає відповісти на запитання ОП, оскільки я не знаю, чому застосовується обмеження щодо використання допоміжної функції, подібної до 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;
}

Я натрапив на це рішення тут .

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.