C에서 가변 함수의 호출을 전달하십시오.


189

C에서는 가변 함수의 호출을 전달할 수 있습니까? 에서처럼

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

위와 같은 방법으로 호출을 전달하는 것이 반드시 필요한 것은 아닙니다 (다른 방법으로 호출을 기록하거나 vfprintf를 사용할 수 있기 때문에). 작업중 인 코드베이스는 래퍼가 실제 작업을 수행해야하며 vfprintf와 유사한 도우미 함수가 없습니다 (추가 할 수 없습니다).

[업데이트 : 지금까지 제공된 답변에 따라 약간의 혼동이있는 것 같습니다. 다른 방법으로 질문을 표현하려면 : 일반적으로 함수의 정의를 수정하지 않고 임의의 가변 함수를 래핑 할 수 있습니다 .]


1
멋진 질문-다행히도 va_list를 사용하는 vFunc 과부하를 추가 할 수 있습니다. 게시 해 주셔서 감사합니다.
Gishu

답변:


157

에 당신이 함수와 유사한이없는 경우 vfprintf그이 걸리는 va_list대신 가변 인자의, 당신은 그것을 할 수 없습니다 . 보다http://c-faq.com/varargs/handoff.html .

예:

void myfun(const char *fmt, va_list argp) {
    vfprintf(stderr, fmt, argp);
}

5
문제가 얼마나 중요한지에 따라 인라인 어셈블리에 의한 호출은 여전히 ​​이러한 가능성을 제공합니다.
Filip

60

그러나 직접적이지는 않지만 가변성 함수가 varargs스타일 대체 함수 와 쌍을 이루는 것이 일반적입니다 (그리고 표준 라이브러리에서 거의 보편적으로 찾을 수 있습니다) . 예 printf/vprintf

v ... 함수는 va_list 매개 변수를 사용하며, 구현은 종종 컴파일러 특정 '매크로 매직'으로 수행되지만 다음과 같이 variadic 함수에서 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;
}

이것은 당신이 찾고있는 효과를 줄 것입니다.

variadic 라이브러리 함수 작성을 고려중인 경우 va_list 스타일 컴패니언을 라이브러리의 일부로 사용 가능하게하는 것도 고려해야합니다. 질문에서 알 수 있듯이 사용자에게 유용 할 수 있습니다.


변수를 다른 함수에 va_copy전달하기 전에 호출해야한다고 확신합니다 myargs. MSC39-C를 참조하십시오 . 여기서 수행중인 작업은 정의되지 않은 동작입니다.
aviggiano

2
@aviggiano : 규칙은 "호출하지 마십시오되는 va_arg()A의 va_list, 내가 사용하지 않기 때문에 그렇게하지 않는 불확정 값이 그" va_arg내 전화 기능에 있습니다. myargs(이 경우) 호출 후가 vprintf되는 부정 (그것이 우리를 수행한다고 가정 va_arg). 표준에 따르면 myargs" va_end[it]에 대한 추가 참조 전에 매크로 로 전달해야합니다 "; 정확히 내가하는 일입니다. 호출 함수에서 반복 할 의도가 없으므로 해당 인수를 복사 할 필요가 없습니다.
CB Bailey

1
네 말이 맞아 리눅스 매뉴얼 페이지는 것을 확인합니다. 경우에 ap사용하는 함수에 전달 된 va_arg(ap,type)다음의 값 ap이 함수의 복귀 후 미정된다.
aviggiano

48

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 형식 을 사용하는 것입니다.


12

거의 다음에서 사용할 수있는 기능을 사용합니다 <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;
}

vprintfplain 대신 버전 을 사용해야합니다 printf. 이 상황에서는을 사용하지 않고 가변 함수를 직접 호출 할 수있는 방법이 없습니다 va_list.


1
감사합니다. 그러나 질문에서 : "[...]에서 작업중인 코드베이스에는 vfprintf와 유사한 도우미 기능이 없습니다 (추가 할 수 없습니다)."
Patrick

11

이러한 호출을 좋은 방법으로 전달하는 것은 실제로 불가능하므로 원래 스택 프레임의 복사본으로 새 스택 프레임을 설정하여이 문제를 해결했습니다. 그러나 이것은 매우 이식성이 없으며 모든 종류의 가정을 만듭니다. 코드가 프레임 포인터와 '표준'호출 규칙을 사용 을합니다.

이 헤더 파일은 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;
}

3

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

1
감사합니다. 그러나 질문에서 : "[...]에서 작업중인 코드베이스에는 vfprintf와 유사한 도우미 기능이 없습니다 (추가 할 수 없습니다)."
Patrick

2

원시 스택 요소를 검색 할 수있는 유일한 위치는에 있기 때문에 이러한 함수 호출을 전달할 방법이 없습니다 my_print(). 이와 같이 호출을 래핑하는 일반적인 방법은 인수를 다양한 varargs구조체 로 변환하는 함수 와 실제로 해당 구조체에서 작동 하는 함수를 갖는 것 입니다. 이러한 이중 기능 모델을 사용하면 (예를 들어) 포장 할 수 printf()있는 구조체를 초기화하여 my_printf()va_start()다음에 전달할 vfprintf().


랩핑하려는 함수의 구현을 수정할 수 없다면 호출을 전달할 방법이 없습니까?
Patrick

권리. 가장 좋은 방법은 vprintf 스타일 래퍼를 추가하는 것입니다.
John Millikin

1

그렇습니다, 그러나 그것은 추악한 일이며 최대한 많은 수의 주장을 알아야합니다. 또한 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와 함께 float를 사용할 수 없으며 gcc는 double로 변환되지만 프로그램이 충돌한다고 말합니다. 이것만으로는이 솔루션이 해킹이며 일반적인 솔루션이 없음을 보여줍니다. 내 예에서는 최대 인수 수가 8이라고 가정했지만 그 수를 늘릴 수 있습니다. 랩핑 된 함수는 정수만 사용했지만 항상 다른 정수로 캐스트되므로 다른 '정상'매개 변수와 동일한 방식으로 작동합니다. 대상 함수는 유형을 알지만 중간 래퍼는 필요하지 않습니다. 래퍼는 또한 대상 함수가이를 알기 때문에 올바른 수의 인수를 알 필요가 없습니다. 유용한 작업을 수행하려면 (통화 기록 만 제외하고) 둘 다 알고 있어야합니다.


1

gcc는이를 수행 할 수있는 확장 기능 __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;
}

Godbolt에서 사용해보십시오

더 복잡한 상황에서는 작동하지 않을 수 있다는주의 사항이 문서에 있습니다. 그리고 인수의 최대 크기를 하드 코딩해야합니다 (여기서는 1000을 사용했습니다). 그러나 스택을 C 또는 어셈블리 언어로 해부하는 다른 방법에 대한 합리적인 대안 일 수 있습니다.


도움이됩니다. 감사!
rsmoorthy

0

본질적으로 세 가지 옵션이 있습니다.

하나는 그것을 전달하지 않고 대상 함수의 가변적 구현을 ​​사용하고 타원을 전달하지 않는 것입니다. 다른 하나는 variadic 매크로를 사용하는 것입니다. 세 번째 옵션은 내가 놓친 모든 것입니다.

나는 이것이 실제로 다루기가 쉽다고 느끼기 때문에 보통 옵션 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");
}

0

가장 좋은 방법은

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

0

래퍼 함수에서 vfprintf와 비슷한 도우미 함수 사용에 대한 제한이 적용되는 이유를 모르기 때문에 이것이 OP의 질문에 대답하는 데 도움이되는지 확실하지 않습니다. 여기서 중요한 문제는 변수를 해석하지 않고 가변성 인수 목록을 전달하는 것이 어렵다는 것입니다. 가능한 방법은 형식화를 수행하고 (vfprintf : vsnprintf와 유사한 도우미 함수 사용) 형식화 된 출력을 가변 인수로 랩핑 된 함수에 전달하는 것입니다 (즉, 랩핑 된 함수의 정의를 수정하지 않음). 그래서 여기에 간다 :

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

이 솔루션을 여기에서 발견했습니다 .

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.