Inoltra un'invocazione di una funzione variadica in C


189

In C, è possibile inoltrare l'invocazione di una funzione variadica? Come in,

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

In questo caso ovviamente inoltrare l'invocazione nel modo sopra non è strettamente necessario (poiché è possibile registrare le invocazioni in altri modi o utilizzare vfprintf), ma la base di codice su cui sto lavorando richiede che il wrapper esegua un lavoro effettivo, e non hanno (e non possono aver aggiunto) una funzione di supporto simile a vfprintf.

[Aggiornamento: sembra esserci un po 'di confusione in base alle risposte che sono state fornite finora. Per formulare la domanda in un altro modo: in generale, puoi avvolgere alcune funzioni variadiche arbitrarie senza modificare la definizione di quella funzione .]


1
domanda fantastica - fortunatamente posso aggiungere un sovraccarico di vFunc che richiede un va_list. Grazie per la pubblicazione.
Gishu,

Risposte:


157

Se non si dispone di una funzione analoga a vfprintfquella che accetta un numero va_listanziché un numero variabile di argomenti, non è possibile farlo . Vederehttp://c-faq.com/varargs/handoff.html .

Esempio:

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

5
A seconda di quanto sia cruciale la questione, l'invocazione da parte dell'assemblea in linea offre ancora questa possibilità.
filip

60

Non direttamente, tuttavia è comune (e troverete quasi universalmente il caso nella libreria standard) per le funzioni variadiche che si presentano in coppia con una varargsfunzione alternativa di stile. ad es. printf/vprintf

Le funzioni v ... accettano un parametro va_list, la cui implementazione viene spesso eseguita con la 'macro magia' specifica del compilatore, ma si è certi che la funzione v ... style da una funzione variadica come questa funzionerà:

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

Questo dovrebbe darti l'effetto che stai cercando.

Se stai pensando di scrivere una funzione di libreria variadica, dovresti anche considerare di rendere disponibile un compagno di stile va_list come parte della libreria. Come puoi vedere dalla tua domanda, può essere utile per i tuoi utenti.


Sono abbastanza sicuro che devi chiamare va_copyprima di passare la myargsvariabile a un'altra funzione. Si prega di consultare MSC39-C , dove afferma che ciò che si sta facendo è un comportamento indefinito.
aviggiano,

2
@aviggiano: la regola è "Non chiamare va_arg()su una va_listche ha un valore indeterminato", non lo faccio perché non ho mai utilizzare va_argnella mia funzione di chiamata. Il valore di myargsafter the call (in questo caso) to vprintfè indeterminato (supponendo che ci faccia va_arg). La norma dice che myargs"deve essere passato alla va_endmacro prima di ogni ulteriore riferimento a [essa]"; che è esattamente quello che faccio. Non ho bisogno di copiarlo perché non ho intenzione di scorrere attraverso di loro nella funzione di chiamata.
CB Bailey,

1
Hai ragione. La pagina man di Linux lo conferma. Se apviene passato a una funzione che utilizza, va_arg(ap,type)il valore di apnon è definito dopo il ritorno di quella funzione.
aviggiano,

48

C99 supporta le macro con argomenti variadici ; a seconda del compilatore, potresti essere in grado di dichiarare una macro che fa quello che vuoi:

#define my_printf(format, ...) \
    do { \
        fprintf(stderr, "Calling printf with fmt %s\n", format); \
        some_other_variadac_function(format, ##__VA_ARGS__); \
    } while(0)

In generale, tuttavia, la soluzione migliore è utilizzare la forma va_list della funzione che si sta tentando di racchiudere, se ne esiste una.


12

Quasi, utilizzando le strutture disponibili 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;
}

Nota che dovrai usare la vprintfversione piuttosto che semplice printf. Non esiste un modo per chiamare direttamente una funzione variadica in questa situazione senza usare va_list.


1
Grazie, ma dalla domanda: "la base di codice su cui sto lavorando [...] non ha (e non posso aver aggiunto) una funzione di supporto simile a vfprintf."
Patrick,

11

Poiché non è davvero possibile inoltrare tali chiamate in modo piacevole, abbiamo risolto il problema impostando un nuovo frame di stack con una copia del frame di stack originale. Tuttavia, questo è altamente insostituibile e fa tutti i tipi di ipotesi , ad esempio che il codice utilizza i puntatori di frame e le convenzioni di chiamata "standard".

Questo file di intestazione consente di racchiudere le funzioni variadic per x86_64 e i386 (GCC). Non funziona per argomenti in virgola mobile, ma dovrebbe essere semplice estenderlo per supportarli.

#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

Alla fine puoi concludere le chiamate in questo modo:

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

Usa 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
Grazie, ma dalla domanda: "la base di codice su cui sto lavorando [...] non ha (e non posso aver aggiunto) una funzione di supporto simile a vfprintf."
Patrick,

2

Non c'è modo di inoltrare tali chiamate di funzione perché è l'unica posizione in cui è possibile recuperare gli elementi dello stack non elaborati my_print(). Il solito modo per racchiudere chiamate del genere è quello di avere due funzioni, una che converte semplicemente gli argomenti nelle varie varargsstrutture, e un'altra che opera effettivamente su quelle strutture. Utilizzando tale modello a doppia funzione, è possibile (ad esempio) avvolgere printf()inizializzando le strutture my_printf()con va_start()e quindi passandole a vfprintf().


Quindi non c'è modo di inoltrare l'invocazione se non è possibile modificare l'implementazione della funzione che si desidera avvolgere?
Patrick,

Destra. La tua scommessa migliore è spingere per un wrapper in stile vprintf da aggiungere.
John Millikin,

1

Sì, puoi farlo, ma è un po 'brutto e devi conoscere il numero massimo di argomenti. Inoltre, se utilizzi un'architettura in cui gli argomenti non vengono passati nello stack come x86 (ad esempio, PowerPC), dovrai sapere se vengono utilizzati tipi "speciali" (double, float, altivec ecc.) E se quindi, affrontali di conseguenza. Può essere doloroso rapidamente ma se sei su x86 o se la funzione originale ha un perimetro ben definito e limitato, può funzionare. Sarà comunque un hack , usalo a scopo di debug. Non creare software attorno a quello. Ad ogni modo, ecco un esempio funzionante su 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;
}

Per qualche motivo, non puoi usare float con va_arg, gcc dice che sono convertiti in double ma il programma si arresta in modo anomalo. Questo da solo dimostra che questa soluzione è un hack e che non esiste una soluzione generale. Nel mio esempio ho assunto che il numero massimo di argomenti fosse 8, ma è possibile aumentare quel numero. La funzione wrapping utilizzava anche solo numeri interi, ma funziona allo stesso modo con altri parametri "normali" poiché eseguono sempre il cast su numeri interi. La funzione target conoscerà i loro tipi, ma il wrapper intermedio non è necessario. Inoltre, il wrapper non ha bisogno di conoscere il giusto numero di argomenti poiché anche la funzione target lo saprà. Per fare un lavoro utile (tranne per il semplice logging della chiamata), probabilmente dovrete conoscere entrambi.


1

gcc offre un'estensione che può fare questo: __builtin_applye i parenti. Vedi Costruire chiamate di funzione nel manuale di gcc.

Un esempio:

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

Provalo su godbolt

Ci sono alcune precauzioni nella documentazione che potrebbero non funzionare in situazioni più complicate. E devi hardcodificare una dimensione massima per gli argomenti (qui ho usato 1000). Ma potrebbe essere una ragionevole alternativa agli altri approcci che prevedono la dissezione della pila in linguaggio C o assembly.


Utile. Grazie!
rsmoorthy,

0

Ci sono essenzialmente tre opzioni.

Uno è non trasmetterlo ma usare l'implementazione variadica della funzione target e non passare le ellissi. L'altro è usare una macro variadica. La terza opzione è tutto ciò che mi manca.

Di solito vado con l'opzione uno poiché mi sembra che questo sia davvero facile da gestire. L'opzione due ha uno svantaggio perché ci sono alcune limitazioni nel chiamare macro variadiche.

Ecco un esempio di codice:

#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

Il modo migliore per farlo è

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

Non sono sicuro se questo aiuta a rispondere alla domanda di OP poiché non so perché si applichi la limitazione per l'utilizzo di una funzione di supporto simile a vfprintf nella funzione wrapper. Penso che il problema chiave qui sia che è difficile inoltrare l'elenco degli argomenti variadici senza interpretarli. Ciò che è possibile è eseguire la formattazione (utilizzando una funzione di supporto simile a vfprintf: vsnprintf) e inoltrare l'output formattato alla funzione wrapped con argomenti variabili (ovvero non modificare la definizione della funzione wrapped). Quindi, eccoci qui:

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

Ho trovato questa soluzione qui .

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.