En C, est-il possible de transmettre l'invocation d'une fonction variadique? Un péché,
int my_printf(char *fmt, ...) {
fprintf(stderr, "Calling printf with fmt %s", fmt);
return SOMEHOW_INVOKE_LIBC_PRINTF;
}
Transférer l'invocation de la manière ci-dessus n'est évidemment pas strictement nécessaire dans ce cas (puisque vous pouvez enregistrer les invocations d'une autre manière, ou utiliser vfprintf), mais la base de code sur laquelle je travaille nécessite que le wrapper effectue un travail réel, et ne le fait pas Je n'ai pas (et je n'ai pas pu ajouter) une fonction d'assistance semblable à vfprintf.
[Mise à jour: il semble y avoir une certaine confusion sur la base des réponses fournies jusqu'à présent. Pour formuler la question d'une autre manière: en général, pouvez-vous envelopper une fonction variadique arbitraire sans modifier la définition de cette fonction .]
Réponses:
Si vous n'avez pas de fonction analogue à
vfprintf
celle qui prend unva_list
au lieu d'un nombre variable d'arguments, vous ne pouvez pas le faire . Voirhttp://c-faq.com/varargs/handoff.html .Exemple:
void myfun(const char *fmt, va_list argp) { vfprintf(stderr, fmt, argp); }
la source
myfun("")
n'est pas pris en charge - existe-t-il une solution pour y remédier? (MSVC imprime: trop peu d'arguments pour l'appel)...
. Votre solution de contournement peut sembler fonctionner avec votre compilateur particulier, mais il s'agit toujours d'un comportement non défini selon la norme du langage.Pas directement, mais il est courant (et vous trouverez presque universellement le cas dans la bibliothèque standard) que les fonctions variadiques viennent par paires avec une
varargs
fonction alternative de style. par exempleprintf
/vprintf
Les fonctions v ... prennent un paramètre va_list, dont l'implémentation est souvent faite avec une `` macro magic '' spécifique au compilateur, mais vous êtes assuré que l'appel de la fonction v ... style à partir d'une fonction variadique comme celle-ci fonctionnera:
#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; }
Cela devrait vous donner l'effet que vous recherchez.
Si vous envisagez d'écrire une fonction de bibliothèque variadique, vous devriez également envisager de rendre disponible un compagnon de style va_list dans le cadre de la bibliothèque. Comme vous pouvez le voir sur votre question, cela peut s'avérer utile pour vos utilisateurs.
la source
va_copy
avant de passer lamyargs
variable à une autre fonction. Veuillez consulter MSC39-C , où il indique que ce que vous faites est un comportement non défini.va_arg()
sur unva_list
qui a une valeur indéterminée", je ne le fais pas car je ne l'utilise jamaisva_arg
dans ma fonction d'appel. La valeur demyargs
après l'appel (dans ce cas) àvprintf
est indéterminée (en supposant que c'est le casva_arg
). La norme dit quemyargs
"doit être passé à lava_end
macro avant toute référence ultérieure à [elle]"; c'est exactement ce que je fais. Je n'ai pas besoin de copier ces arguments car je n'ai pas l'intention de les parcourir dans la fonction appelante.ap
est passé à une fonction qui utiliseva_arg(ap,type)
alors la valeur deap
n'est pas définie après le retour de cette fonction.C99 prend en charge les macros avec des arguments variadiques ; en fonction de votre compilateur, vous pourrez peut-être déclarer une macro qui fait ce que vous voulez:
#define my_printf(format, ...) \ do { \ fprintf(stderr, "Calling printf with fmt %s\n", format); \ some_other_variadac_function(format, ##__VA_ARGS__); \ } while(0)
En général, cependant, la meilleure solution est d'utiliser la forme va_list de la fonction que vous essayez d'encapsuler, s'il en existe une.
la source
Presque, en utilisant les installations disponibles dans
<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; }
Notez que vous devrez utiliser la
vprintf
version plutôt que la version standardprintf
. Il n'y a pas moyen d'appeler directement une fonction variadique dans cette situation sans utiliserva_list
.la source
Comme il n'est pas vraiment possible de transférer de tels appels d'une manière agréable, nous avons contourné ce problème en configurant un nouveau cadre de pile avec une copie du cadre de pile d'origine. Cependant, ceci est hautement non portable et fait toutes sortes d'hypothèses , par exemple que le code utilise des pointeurs de trame et les conventions d'appel «standard».
Ce fichier d'en-tête permet d'encapsuler des fonctions variadiques pour x86_64 et i386 (GCC). Cela ne fonctionne pas pour les arguments à virgule flottante, mais devrait être simple à étendre pour les prendre en charge.
#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
À la fin, vous pouvez encapsuler les appels comme ceci:
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; }
la source
Utilisez 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; }
la source
Il n'y a aucun moyen de transférer de tels appels de fonction car le seul emplacement où vous pouvez récupérer des éléments de pile bruts se trouve
my_print()
. La manière habituelle d'encapsuler des appels comme ça est d'avoir deux fonctions, une qui convertit simplement les arguments dans les différentesvarargs
structures, et une autre qui opère réellement sur ces structures. En utilisant un tel modèle à double fonction, vous pouvez (par exemple) encapsulerprintf()
en initialisant les structures dansmy_printf()
withva_start()
, puis les transmettre àvfprintf()
.la source
gcc propose une extension qui peut faire cela:
__builtin_apply
et parents. Voir Construire des appels de fonction dans le manuel de gcc.Un exemple:
#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; }
Essayez-le sur godbolt
Il y a quelques avertissements dans la documentation que cela pourrait ne pas fonctionner dans des situations plus compliquées. Et vous devez coder en dur une taille maximale pour les arguments (ici j'ai utilisé 1000). Mais cela pourrait être une alternative raisonnable aux autres approches qui impliquent de disséquer la pile en C ou en langage d'assemblage.
la source
Oui, vous pouvez le faire, mais c'est un peu moche et vous devez connaître le nombre maximal d'arguments. De plus si vous êtes sur une architecture où les arguments ne sont pas passés sur la pile comme le x86 (par exemple, PowerPC), vous devrez savoir si des types "spéciaux" (double, floats, altivec etc.) sont utilisés et si alors, traitez-les en conséquence. Cela peut être pénible rapidement mais si vous êtes sur x86 ou si la fonction d'origine a un périmètre bien défini et limité, cela peut fonctionner. Ce sera toujours un hack , utilisez-le à des fins de débogage. Ne construisez pas votre logiciel autour de cela. Quoi qu'il en soit, voici un exemple de travail sur 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; }
Pour une raison quelconque, vous ne pouvez pas utiliser de flottants avec va_arg, gcc dit qu'ils sont convertis en double mais le programme plante. Cela seul démontre que cette solution est un hack et qu'il n'y a pas de solution générale. Dans mon exemple, j'ai supposé que le nombre maximum d'arguments était de 8, mais vous pouvez augmenter ce nombre. La fonction encapsulée n'utilisait également que des entiers, mais elle fonctionne de la même manière avec d'autres paramètres «normaux» puisqu'ils sont toujours convertis en entiers. La fonction cible connaîtra leurs types mais votre wrapper intermédiaire n'en a pas besoin. L'encapsuleur n'a pas non plus besoin de connaître le bon nombre d'arguments puisque la fonction cible le saura également. Pour faire un travail utile (sauf simplement enregistrer l'appel), vous devrez probablement connaître les deux.
la source
Il existe essentiellement trois options.
La première consiste à ne pas la transmettre mais à utiliser l'implémentation variadique de votre fonction cible et à ne pas transmettre les ellipses. L'autre consiste à utiliser une macro variadique. La troisième option est tout ce qui me manque.
Je choisis généralement l'option 1 car j'ai l'impression que c'est vraiment facile à gérer. La deuxième option présente un inconvénient car il existe certaines limitations à l'appel des macros variadiques.
Voici un exemple de code:
#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"); }
la source
La meilleure façon de procéder est
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; }
la source
Je ne sais pas si cela aide à répondre à la question de OP car je ne sais pas pourquoi la restriction d'utilisation d'une fonction d'assistance semblable à vfprintf dans la fonction wrapper s'applique. Je pense que le problème clé ici est qu'il est difficile de transmettre la liste d'arguments variadiques sans les interpréter. Ce qui est possible, c'est d'effectuer le formatage (en utilisant une fonction d'assistance semblable à vfprintf: vsnprintf) et de transmettre la sortie formatée à la fonction encapsulée avec des arguments variadiques (c'est-à-dire ne pas modifier la définition de la fonction encapsulée). Alors, on y va:
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; }
Je suis tombé sur cette solution ici .
la source
ret = printf("%s", formatted);
, sinon si la chaîne formatée contient un,%
il commence à le remplacer par les déchets trouvés sur la pile.args
(c'est-à-direva_end(args); va_start(args, fmt);
) entre les deuxvsnprintf()
appels.