在C语言中向前调用变量函数
在C语言中,是否可以向前调用变量函数?如:在C语言中向前调用变量函数,c,variadic,C,Variadic,在C语言中,是否可以向前调用变量函数?如: int my_printf(char *fmt, ...) { fprintf(stderr, "Calling printf with fmt %s", fmt); return SOMEHOW_INVOKE_LIBC_PRINTF; } 在这种情况下,以上述方式转发调用显然不是绝对必要的(因为您可以用其他方式记录调用,或者使用vfprintf),但是我正在处理的代码库需要包装器进行一些实际工作,并且没有(也不可能添加)类似于vfp
int my_printf(char *fmt, ...) {
fprintf(stderr, "Calling printf with fmt %s", fmt);
return SOMEHOW_INVOKE_LIBC_PRINTF;
}
在这种情况下,以上述方式转发调用显然不是绝对必要的(因为您可以用其他方式记录调用,或者使用vfprintf),但是我正在处理的代码库需要包装器进行一些实际工作,并且没有(也不可能添加)类似于vfprintf的帮助函数
[更新:根据目前提供的答案,似乎存在一些混乱。换一种说法:一般来说,你能在不修改函数定义的情况下包装任意变量函数。]使用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;
}
几乎可以使用
中提供的设施:
#包括
int my_printf(字符*格式,…)
{
va_列表参数;
va_开始(参数,格式);
int r=vprintf(格式,args);
va_端(args);
返回r;
}
请注意,您将需要使用该版本,而不是普通的
printf
。在这种情况下,如果不使用va_list
就无法直接调用变量函数,因为您可以检索原始堆栈元素的唯一位置是my_print()
。像这样包装调用的通常方法是使用两个函数,一个只将参数转换为各种varargs
结构,另一个实际操作这些结构。使用这种双函数模型,您可以(例如)通过使用va\u start()
初始化my\u printf()
中的结构来包装printf()
,然后将它们传递给vfprintf()
如果您没有类似于vfprintf
的函数,该函数采用va_列表而不是可变数量的参数,您就不能这样做。看
例如:
void myfun(const char*fmt,va_list argp){
vfprintf(标准、fmt、argp);
}
不是直接的,但是变差函数与varargs
样式的替代函数成对出现是很常见的(在标准库中几乎可以找到这种情况)。e、 g.printf
/vprintf
v。。。函数采用一个va_列表参数,该参数的实现通常使用特定于编译器的“宏魔术”来完成,但可以保证调用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;
}
#包括
整数m_printf(字符*fmt,…)
{
int ret;
/*声明一个va_列表类型变量*/
va_列表myargs;
/*使用…after fmt初始化va_列表变量*/
va_启动(myargs,fmt);
/*将“…”转发到vprintf*/
ret=vprintf(fmt,myargs);
/*清理清单*/
va_end(myargs);
返回ret;
}
这会给你带来你想要的效果
如果你正在考虑编写一个变量库函数,你也应该考虑把一个VAYLIST风格的同伴作为库的一部分。从您的问题中可以看出,它对您的用户非常有用。
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形式(如果存在)。是的,您可以这样做,但它有点难看,您必须知道最大参数数。此外,如果您所处的体系结构中的参数没有在堆栈上传递,比如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;
}
#包括
#包括
int旧变量函数(int n,…)
{
va_列表参数;
int i=0;
va_启动(args,n);
如果(i++因为不可能以一种好的方式转发这样的调用,我们通过使用原始堆栈帧的副本设置一个新的堆栈帧来解决这个问题。但是这是非常不可移植的,并且会做出各种假设,例如,代码使用帧指针和“标准”调用约定
此头文件允许包装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
基本上有三种选择
一个是不传递它,而是使用目标函数的可变实现,而不是传递省略号。另一个是使用可变宏。第三个选项是我缺少的所有东西
我通常选择选项一,因为我觉得这很容易处理。选项二有一个缺点,因为调用可变宏有一些限制
下面是一些示例代码:
#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");
}
#包括
#包括
#定义选项\u变量宏(f,…)\
printf(“使用以下格式打印:%s”,f)\
printf(f,_uva_uargs_uu)
int选项_ResolveVariableAndPasson(常量字符*f,…)
{
INTR;
va_列表参数;
printf(“使用以下格式打印:%s”,f);
va_启动(args,f);
r=vprintf(f,args);
va_端(args);
返回r;
}
void main()
{
常量char*f=“%s%s%s\n”;
const char*a=“一”;
const char*b=“两个”;
const char*c=“三”;
printf(“----正常打印----\n”);
printf(f,a,b,c);
printf(“\n”);
printf(“--Option\u VariadicMacro--\n”);
选项(f,a,b,c);;
printf(“\n”);
printf(“----Option\u resolvedicandpasson----\n”);
期权(f,a,b,c);;
printf(“\n”);
}
最好的方法是
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;
}
#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");
}
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;
}
#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;
}
#include <stdio.h>
#include <stdarg.h>
int my_printf(char *fmt, ...)
{
if (fmt == NULL) {
/* Invalid format pointer */
return -1;
} else {
va_list args;
int len;
/* Initialize a variable argument list */
va_start(args, fmt);
/* Get length of format including arguments */
len = vsnprintf(NULL, 0, fmt, args);
/* End using variable argument list */
va_end(args);
if (len < 0) {
/* vsnprintf failed */
return -1;
} else {
/* Declare a character buffer for the formatted string */
char formatted[len + 1];
/* Initialize a variable argument list */
va_start(args, fmt);
/* Write the formatted output */
vsnprintf(formatted, sizeof(formatted), fmt, args);
/* End using variable argument list */
va_end(args);
/* Call the wrapped function using the formatted output and return */
fprintf(stderr, "Calling printf with fmt %s", fmt);
return printf("%s", formatted);
}
}
}
int main()
{
/* Expected output: Test
* Expected error: Calling printf with fmt Test
*/
my_printf("Test\n");
//printf("Test\n");
/* Expected output: Test
* Expected error: Calling printf with fmt %s
*/
my_printf("%s\n", "Test");
//printf("%s\n", "Test");
/* Expected output: %s
* Expected error: Calling printf with fmt %s
*/
my_printf("%s\n", "%s");
//printf("%s\n", "%s");
return 0;
}