C에서 가변 함수의 호출을 전송합니다.
C 에서는, Variadic 함수의 호출을 전송 할 수 있습니까?예를 들어,
int my_printf(char *fmt, ...) {
fprintf(stderr, "Calling printf with fmt %s", fmt);
return SOMEHOW_INVOKE_LIBC_PRINTF;
}
이 경우 호출을 다른 방법으로 기록하거나 vfprintf를 사용할 수 있기 때문에 위와 같은 방법으로 호출을 전송할 필요는 없습니다.그러나 현재 작업 중인 코드베이스에서는 래퍼가 실제 작업을 수행해야 하며 vfrintf와 같은 도우미 기능이 없거나 추가될 수 없습니다.
[갱신 내용 : 지금까지의 답변에 따라서는 다소 혼동이 있는 것 같습니다.질문을 바꿔 말하면, 일반적으로 함수의 정의를 수정하지 않고 임의의 가변 함수를 랩할 수 있습니다.]
음음 to to to to to to to to to to analogous analogous 와 유사한 vfprintf
은 a a가 필요하다.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 매개 변수를 사용합니다. va_list 매개 변수는 컴파일러 고유의 "various magic"을 사용하여 구현되지만 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는 variadic 인수를 가진 매크로를 지원합니다.컴파일러에 따라서는, 다음의 작업을 실행하는 매크로를 선언할 수 있습니다.
#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 형식을 사용하는 것입니다.
는 이를할 수 확장을 합니다.gcc 는 、 cc는 、 gcc 、 g g g g g 。__builtin_apply
그리고 친척들.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 또는 어셈블리 언어로 해부하는 다른 접근법에 대한 합리적인 대안일 수 있습니다.
이러한 콜을 적절한 방법으로 전송할 수 없기 때문에 원래 스택프레임의 복사본을 사용하여 새로운 스택프레임을 셋업함으로써 이 문제를 해결했습니다.그러나 이것은 매우 휴대하기 어렵고, 예를 들어 코드가 프레임 포인터와 '표준' 호출 규칙을 사용한다는 모든 종류의 가정을 합니다.
이 헤더 파일을 사용하면 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;
}
래퍼 함수의 vfprintf와 유사한 도우미 함수를 사용하는 제한이 적용되는 이유를 모르기 때문에 이것이 OP의 질문에 대한 답변에 도움이 될지는 확실하지 않습니다.여기서 중요한 문제는 다양한 인수 목록을 해석하지 않고 전달하는 것이 어렵다는 것입니다.가능한 것은 포맷(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가 지적한 오류 수정
의,에서 할 수 있는 <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;
}
일반 버전이 아닌 버전을 사용해야 합니다.printf
할 수 있는 va_list
.
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;
}
할 수 있는 는 raw밖에 없기 my_print()
함수를 한 2개의 기능으로 입니다.하나는 인수를 다양한 값으로 변환하는 것입니다.varargs
구조물, 그리고 실제로 그 구조물에서 작동하는 다른 구조물.모델을 하면, 예를 랩을 수 있습니다.printf()
의 my_printf()
va_start()
에 전달한다.vfprintf()
.
네, 할 수 있어요, 하지만 좀 추악하고 당신은 논쟁의 최대 수를 알아야 해요.게다가 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;
}
어떤 이유로 인해 va_arg와 함께 플로트를 사용할 수 없습니다.gcc는 플로트가 더블로 변환되지만 프로그램이 크래쉬합니다.이것만으로도 이 솔루션이 해킹이며 일반적인 솔루션이 없다는 것을 알 수 있습니다.이 예에서는 인수의 최대 수를 8로 상정하고 있습니다만, 그 수를 늘릴 수 있습니다.줄바꿈된 함수는 정수만 사용했지만 항상 정수로 캐스팅되므로 다른 '정규' 매개 변수와 같은 방식으로 작동합니다.대상 함수는 해당 유형을 알 수 있지만 중간 래퍼에서는 알 필요가 없습니다.대상 함수도 이를 인식하기 때문에 래퍼도 올바른 인수 수를 알 필요가 없습니다.유용한 작업(콜 로깅 제외)을 수행하려면 아마도 둘 다 알아야 합니다.
기본적으로 세 가지 옵션이 있습니다.
하나는 전달하지 않고 대상 함수의 가변 구현을 사용하고 생략형을 전달하지 않는 것입니다.다른 하나는 가변 매크로를 사용하는 것입니다.세 번째 옵션은 내가 놓치고 있는 모든 것들이다.
저는 보통 1번 옵션을 선택합니다. 왜냐하면 이게 정말 다루기 쉽다고 느끼기 때문입니다.옵션 2에는 바리에타딕 매크로를 호출하는 데 몇 가지 제한이 있기 때문에 단점이 있습니다.
다음은 코드 예시입니다.
#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;
}
언급URL : https://stackoverflow.com/questions/150543/forward-an-invocation-of-a-variadic-function-in-c
'programing' 카테고리의 다른 글
커스텀 dev/prod 서버에서 vue cli 서비스 기능을 활용하는 방법 (0) | 2022.07.16 |
---|---|
getter가 업데이트되지 않는 것을 수신하는 Vuex 계산 속성 (0) | 2022.07.16 |
C에서 글로벌 변수를 사용하는 것은 언제가 좋습니까? (0) | 2022.07.16 |
Java 앱에서 Windows 서비스를 만드는 방법 (0) | 2022.07.16 |
static_cast와 repretter_cast의 차이점은 무엇입니까? (0) | 2022.07.16 |