[英]Forward an invocation of a variadic function in C
在 C 中,是否可以轉發可變參數函數的調用? 就像在,
int my_printf(char *fmt, ...) {
fprintf(stderr, "Calling printf with fmt %s", fmt);
return SOMEHOW_INVOKE_LIBC_PRINTF;
}
在這種情況下,以上述方式轉發調用顯然不是絕對必要的(因為您可以以其他方式記錄調用,或使用 vfprintf),但我正在處理的代碼庫要求包裝器執行一些實際工作,並且不會'沒有(也不能添加)類似於 vfprintf 的輔助函數。
[更新:根據迄今為止提供的答案,似乎有些混亂。 .]用另一種方式來表達這個問題:一般來說,你能不能在包裝一些任意的可變參數函數。]
如果您沒有類似於vfprintf
的函數,該函數采用va_list
而不是可變數量的參數,您就不能這樣做。 看 http://c-faq.com/varargs/handoff.html 。
例子:
void myfun(const char *fmt, va_list argp) {
vfprintf(stderr, fmt, argp);
}
不是直接的,但是可變參數函數與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 樣式伴侶作為庫的一部分提供。 從您的問題中可以看出,它可以證明對您的用戶有用。
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形式(如果存在的話)。
幾乎,使用<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
情況下直接調用可變參數函數。
由於實際上不可能以良好的方式轉發此類調用,因此我們通過使用原始堆棧幀的副本設置新堆棧幀來解決此問題。 然而,這是非常不可移植的,並且會做出各種假設,例如代碼使用幀指針和“標准”調用約定。
這個頭文件允許為 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;
}
gcc 提供了一個可以做到這一點的擴展:__ __builtin_apply
和 relatives。 請參閱 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;
}
文檔中有一些警告,它在更復雜的情況下可能不起作用。 並且您必須對參數的最大大小進行硬編碼(這里我使用了 1000)。 但對於涉及用 C 語言或匯編語言剖析堆棧的其他方法,它可能是一種合理的替代方法。
使用 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;
}
無法轉發此類函數調用,因為您可以檢索原始堆棧元素的唯一位置是my_print()
。 包裝這樣的調用的常用方法是有兩個函數,一個只是將參數轉換為各種varargs
結構,另一個實際操作這些結構。 使用這樣的雙功能模型,您可以(例如)通過使用va_start()
初始化my_printf()
的結構來包裝printf()
va_start()
,然后將它們傳遞給vfprintf()
。
抱歉,您的話題錯了,但是:
元問題是,C語言中的varargs接口從一開始就已被破壞。 這是對緩沖區溢出和無效內存訪問的邀請,因為如果沒有顯式的結束信號(沒人真正出於懶惰而使用),就無法找到參數列表的末尾。 而且它始終依賴於深奧的實現特定的宏,而重要的va_copy()宏僅在某些體系結構上受支持。
是的,你可以做到,但它有點難看,你必須知道參數的最大數量。 此外,如果您使用的架構中的參數不是像 x86(例如 PowerPC)那樣在堆棧上傳遞,您將必須知道是否使用了“特殊”類型(double、floats、altivec 等),以及因此,請相應地處理它們。 很快就會很痛苦,但如果您使用的是 x86 或者原始函數具有明確定義且有限的邊界,則它可以工作。 它仍然是一個 hack ,將它用於調試目的。 不要圍繞它構建軟件。 無論如何,這是一個關於 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;
}
出於某種原因,您不能將浮點數與 va_arg 一起使用,gcc 說它們被轉換為雙精度數但程序崩潰了。 僅此一項就表明該解決方案是一種黑客行為,並且沒有通用的解決方案。 在我的示例中,我假設參數的最大數量為 8,但您可以增加該數量。 包裝函數也只使用整數,但它與其他“普通”參數的工作方式相同,因為它們總是轉換為整數。 目標函數將知道它們的類型,但您的中間包裝器不需要。 包裝器也不需要知道正確數量的參數,因為目標函數也知道它。 為了做有用的工作(除了記錄調用),你可能必須知道兩者。
不確定這是否有助於回答 OP 的問題,因為我不知道為什么在包裝函數中使用類似於 vfprintf 的輔助函數的限制適用。 我認為這里的關鍵問題是轉發可變參數列表而不解釋它們是困難的。 可能的是執行格式化(使用類似於 vfprintf 的輔助函數:vsnprintf)並將格式化輸出轉發到帶有可變參數的包裝函數(即不修改包裝函數的定義)。 所以,我們開始:
#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;
}
我在這里遇到了這個解決方案。
編輯:修復了 egmont 指出的錯誤
基本上有三種選擇。
一種是不傳遞它,而是使用目標函數的可變參數實現而不傳遞省略號。 另一種是使用可變參數宏。 第三個選項是我缺少的所有東西。
我通常選擇選項一,因為我覺得這真的很容易處理。 選項二有一個缺點,因為調用可變參數宏有一些限制。
下面是一些示例代碼:
#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;
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.