Leiten Sie einen Aufruf einer variadischen Funktion in C weiter

188

Ist es in C möglich, den Aufruf einer variadischen Funktion weiterzuleiten? Wie in,

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

Das Weiterleiten des Aufrufs auf die oben beschriebene Weise ist in diesem Fall offensichtlich nicht unbedingt erforderlich (da Sie Aufrufe auf andere Weise protokollieren oder vfprintf verwenden könnten), aber für die Codebasis, an der ich arbeite, muss der Wrapper tatsächlich arbeiten, und dies ist nicht der Fall Ich habe (und kann nicht hinzugefügt) eine Hilfsfunktion, die vfprintf ähnelt.

[Update: Es scheint einige Verwirrung zu geben, basierend auf den Antworten, die bisher geliefert wurden. Um die Frage anders auszudrücken: Können Sie im Allgemeinen eine beliebige variable Funktion einschließen, ohne die Definition dieser Funktion zu ändern? ]

Patrick
quelle
1
tolle Frage - zum Glück kann ich eine vFunc-Überladung hinzufügen, die eine va_list benötigt. Danke fürs Schreiben.
Gishu

Antworten:

156

Wenn Sie keine analoge Funktion haben vfprintf, die eine va_liststatt einer variablen Anzahl von Argumenten verwendet, können Sie dies nicht tun . Sehenhttp://c-faq.com/varargs/handoff.html .

Beispiel:

void myfun(const char *fmt, va_list argp) {
    vfprintf(stderr, fmt, argp);
}
Adam Rosenfield
quelle
5
Je nachdem, wie wichtig das Problem ist, bietet der Aufruf durch Inline-Assembly diese Möglichkeit weiterhin.
Filip
59

Nicht direkt, aber es ist üblich (und in der Standardbibliothek ist dies fast überall der Fall), dass verschiedene Funktionen paarweise mit einer varargsalternativen Stilfunktion geliefert werden. zB printf/vprintf

Die v ... -Funktionen verwenden einen va_list-Parameter, dessen Implementierung häufig mit compilerspezifischer 'Makromagie' erfolgt. Sie können jedoch sicher sein, dass das Aufrufen der v ... style-Funktion von einer variadischen Funktion wie dieser aus funktioniert:

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

Dies sollte Ihnen den Effekt geben, den Sie suchen.

Wenn Sie eine variable Bibliotheksfunktion schreiben möchten, sollten Sie auch in Betracht ziehen, einen Begleiter im va_list-Stil als Teil der Bibliothek verfügbar zu machen. Wie Sie Ihrer Frage entnehmen können, kann sie sich für Ihre Benutzer als nützlich erweisen.

CB Bailey
quelle
Ich bin mir ziemlich sicher, dass Sie aufrufen müssen, va_copybevor Sie die myargsVariable an eine andere Funktion übergeben. Weitere Informationen finden Sie in MSC39-C , wo angegeben wird, dass das, was Sie tun, undefiniertes Verhalten ist.
Aviggiano
2
@aviggiano: Die Regel wird „Rufen Sie nicht va_arg()auf eine , va_listdie einen unbestimmten Wert hat“, weiß ich nicht tun, weil ich nie verwenden va_argin meinem Aufruf Funktion. Der Wert von myargsnach dem Aufruf (in diesem Fall) an vprintfist unbestimmt (vorausgesetzt, dass dies bei uns der Fall ist va_arg). Der Standard besagt, dass myargs" va_endvor jeder weiteren Bezugnahme auf [es] an das Makro übergeben werden soll "; Das ist genau das, was ich tue. Ich muss diese Argumente nicht kopieren, da ich nicht beabsichtige, sie in der aufrufenden Funktion zu durchlaufen.
CB Bailey
1
Du hast recht. Die Linux- Manpage bestätigt dies. Wenn apan eine Funktion übergeben wird, die verwendet, va_arg(ap,type)ist der Wert von apnach der Rückgabe dieser Funktion undefiniert.
Aviggiano
47

C99 unterstützt Makros mit verschiedenen Argumenten . Abhängig von Ihrem Compiler können Sie möglicherweise ein Makro deklarieren, das das tut, was Sie wollen:

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

Im Allgemeinen ist es jedoch die beste Lösung, die Form va_list der Funktion zu verwenden, die Sie umbrechen möchten, falls eine vorhanden ist.

Commodore Jaeger
quelle
11

Fast mit den Einrichtungen 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;
}

Beachten Sie, dass Sie die vprintfVersion anstelle von normal verwenden müssen printf. In dieser Situation gibt es keine Möglichkeit, eine Variadic-Funktion direkt ohne Verwendung aufzurufen va_list.

Greg Hewgill
quelle
1
Danke, aber von der Frage: "Die Codebasis, an der ich [...] arbeite, hat keine Hilfsfunktion, die vfprintf ähnelt (und kann sie auch nicht hinzugefügt haben)."
Patrick
11

Da es nicht wirklich möglich ist, solche Aufrufe auf nette Weise weiterzuleiten, haben wir dies umgangen, indem wir einen neuen Stapelrahmen mit einer Kopie des ursprünglichen Stapelrahmens eingerichtet haben. Dies ist jedoch höchst unportabel und setzt alle möglichen Annahmen voraus , z. B. dass der Code Frame-Zeiger und die Standardaufrufkonventionen verwendet.

Diese Header-Datei ermöglicht das Umschließen verschiedener Funktionen für x86_64 und i386 (GCC). Es funktioniert nicht für Gleitkomma-Argumente, sollte aber einfach zu erweitern sein, um diese zu unterstützen.

#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

Am Ende können Sie Anrufe wie folgt abschließen:

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
quelle
3

Verwenden Sie 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;
}
Greg Rogers
quelle
1
Danke, aber von der Frage: "Die Codebasis, an der ich [...] arbeite, hat keine Hilfsfunktion, die vfprintf ähnelt (und kann sie auch nicht hinzugefügt haben)."
Patrick
2

Es gibt keine Möglichkeit, solche Funktionsaufrufe weiterzuleiten, da sich der einzige Speicherort befindet, an dem Sie Rohstapelelemente abrufen können my_print(). Der übliche Weg, solche Aufrufe zu verpacken, besteht darin, zwei Funktionen zu haben, eine, die nur die Argumente in die verschiedenen varargsStrukturen konvertiert, und eine andere, die diese Strukturen tatsächlich verarbeitet. Mit einem solchen Doppelfunktionsmodell können Sie (zum Beispiel) printf()umbrechen, indem Sie die Strukturen my_printf()mit initialisieren va_start()und dann an übergeben vfprintf().

John Millikin
quelle
Es gibt also keine Möglichkeit, den Aufruf weiterzuleiten, wenn Sie die Implementierung der Funktion, die Sie umbrechen möchten, nicht ändern können.
Patrick
Richtig. Am besten drängen Sie darauf, dass ein Vprintf-Wrapper hinzugefügt wird.
John Millikin
1

Ja, Sie können es tun, aber es ist etwas hässlich und Sie müssen die maximale Anzahl von Argumenten kennen. Wenn Sie sich in einer Architektur befinden, in der die Argumente nicht wie x86 (z. B. PowerPC) auf dem Stapel übergeben werden, müssen Sie außerdem wissen, ob und ob "spezielle" Typen (double, float, altivec usw.) verwendet werden Gehen Sie also entsprechend mit ihnen um. Es kann schnell schmerzhaft sein, aber wenn Sie auf x86 arbeiten oder wenn die ursprüngliche Funktion einen genau definierten und begrenzten Umfang hat, kann es funktionieren. Es wird immer noch ein Hack sein , verwenden Sie es zum Debuggen. Erstellen Sie keine Software dafür. Hier ist ein funktionierendes Beispiel für 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;
}

Aus irgendeinem Grund können Sie Floats nicht mit va_arg verwenden. Laut gcc werden sie in double konvertiert, aber das Programm stürzt ab. Das allein zeigt, dass diese Lösung ein Hack ist und dass es keine allgemeine Lösung gibt. In meinem Beispiel habe ich angenommen, dass die maximale Anzahl von Argumenten 8 beträgt, aber Sie können diese Anzahl erhöhen. Die umschlossene Funktion verwendete ebenfalls nur Ganzzahlen, funktioniert jedoch genauso mit anderen 'normalen' Parametern, da diese immer in Ganzzahlen umgewandelt werden. Die Zielfunktion kennt ihre Typen, Ihr Zwischen-Wrapper muss dies jedoch nicht. Der Wrapper muss auch nicht die richtige Anzahl von Argumenten kennen, da die Zielfunktion dies auch weiß. Um nützliche Arbeit zu leisten (außer nur den Anruf zu protokollieren), müssen Sie wahrscheinlich beide kennen.

user10146
quelle
0

Grundsätzlich gibt es drei Möglichkeiten.

Eine besteht darin, es nicht weiterzugeben, sondern die variable Implementierung Ihrer Zielfunktion zu verwenden und die Ellipsen nicht weiterzugeben. Das andere ist die Verwendung eines variadischen Makros. Die dritte Option ist alles, was mir fehlt.

Normalerweise entscheide ich mich für Option eins, da ich der Meinung bin, dass dies sehr einfach zu handhaben ist. Option zwei hat einen Nachteil, da das Aufrufen verschiedener Makros einige Einschränkungen aufweist.

Hier ist ein Beispielcode:

#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
quelle
0

Der beste Weg, dies zu tun, ist

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
quelle
0

gcc bietet eine Erweiterung, die dies kann: __builtin_applyund Verwandte. Siehe Erstellen von Funktionsaufrufen im gcc-Handbuch.

Ein Beispiel:

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

Probieren Sie es auf Godbolt

In der Dokumentation gibt es einige Vorsichtsmaßnahmen, dass dies in komplizierteren Situationen möglicherweise nicht funktioniert. Und Sie müssen eine maximale Größe für die Argumente fest codieren (hier habe ich 1000 verwendet). Es könnte jedoch eine vernünftige Alternative zu den anderen Ansätzen sein, bei denen der Stapel entweder in C- oder in Assemblersprache zerlegt wird.

Nate Eldredge
quelle
0

Ich bin mir nicht sicher, ob dies zur Beantwortung der Frage von OP beiträgt, da ich nicht weiß, warum die Einschränkung für die Verwendung einer Hilfsfunktion ähnlich vfprintf in der Wrapper-Funktion gilt. Ich denke, das Hauptproblem hierbei ist, dass es schwierig ist, die Liste der variadischen Argumente weiterzuleiten, ohne sie zu interpretieren. Es ist möglich, die Formatierung durchzuführen (unter Verwendung einer Hilfsfunktion ähnlich vfprintf: vsnprintf) und die formatierte Ausgabe mit variadischen Argumenten an die umschlossene Funktion weiterzuleiten (dh die Definition der umschlossenen Funktion nicht zu ändern). Auf geht's:

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

Ich bin hier auf diese Lösung gestoßen .

Bert Regelink
quelle