Chuyển tiếp một lời gọi của hàm matrixdic trong C


188

Trong C, có thể chuyển tiếp lời gọi của hàm matrixdic không? Như trong

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

Chuyển tiếp lệnh gọi theo cách trên rõ ràng là không thực sự cần thiết trong trường hợp này (vì bạn có thể ghi nhật ký theo cách khác hoặc sử dụng vfprintf), nhưng cơ sở mã mà tôi đang làm việc yêu cầu trình bao bọc thực hiện một số công việc thực tế và không Không có (và không thể thêm) chức năng của trình trợ giúp gần giống với vfprintf.

[Cập nhật: dường như có một số nhầm lẫn dựa trên các câu trả lời đã được cung cấp cho đến nay. Để diễn đạt câu hỏi theo cách khác: nói chung, bạn có thể bọc một số hàm biến đổi tùy ý mà không sửa đổi định nghĩa của hàm đó .]


1
câu hỏi tuyệt vời - may mắn thay tôi có thể thêm quá tải vFunc cần một va_list. Cảm ơn vì đăng.
Gishu

Câu trả lời:


156

Nếu bạn không có chức năng tương tự như vfprintfvậy, va_listthay vì số lượng đối số thay đổi, bạn không thể làm điều đó . Xemhttp://c-faq.com/varargs/handoff.html .

Thí dụ:

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

5
Tùy thuộc vào mức độ quan trọng của vấn đề, việc gọi bằng cách lắp ráp nội tuyến vẫn cung cấp khả năng này.
filip

59

Không trực tiếp, tuy nhiên nó là phổ biến (và bạn sẽ tìm thấy gần như toàn bộ trường hợp trong thư viện tiêu chuẩn) cho các hàm matrixdic đi kèm theo cặp với một varargschức năng thay thế kiểu. ví dụ printf/vprintf

Các hàm v ... lấy tham số va_list, việc triển khai thường được thực hiện với 'ma thuật macro' cụ thể của trình biên dịch, nhưng bạn được đảm bảo rằng việc gọi hàm v ... từ một hàm matrixdic như thế này sẽ hoạt động:

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

Điều này sẽ cung cấp cho bạn hiệu ứng mà bạn đang tìm kiếm.

Nếu bạn đang xem xét việc viết một hàm thư viện matrixdic, bạn cũng nên xem xét việc cung cấp một kiểu đồng hành kiểu va_list như một phần của thư viện. Như bạn có thể thấy từ câu hỏi của bạn, nó có thể được chứng minh là hữu ích cho người dùng của bạn.


Tôi khá chắc chắn rằng bạn cần phải gọi va_copytrước khi chuyển myargsbiến sang chức năng khác. Vui lòng xem MSC39-C , trong đó tuyên bố rằng những gì bạn đang làm là hành vi không xác định.
aviggiano

2
@aviggiano: Quy tắc là "Đừng gọi va_arg()vào một va_listgiá trị không xác định", tôi không làm điều đó bởi vì tôi không bao giờ sử dụng va_argchức năng gọi điện của mình. Các giá trị của myargssau khi cuộc gọi (trong trường hợp này) để vprintfđược không xác định (giả định rằng nó chúng tôi va_arg). Tiêu chuẩn nói rằng myargs"sẽ được chuyển đến va_endmacro trước khi có bất kỳ tham chiếu nào nữa đến [nó]"; đó chính xác là những gì tôi làm Tôi không cần phải sao chép lập luận đó vì tôi không có ý định lặp qua chúng trong chức năng gọi.
CB Bailey

1
Bạn đúng rồi. Các người đàn ông Linux trang khẳng định rằng. Nếu apđược truyền cho một hàm sử dụng va_arg(ap,type)thì giá trị của apkhông được xác định sau khi trả về hàm đó.
aviggiano

47

C99 hỗ trợ các macro với các đối số biến đổi ; tùy thuộc vào trình biên dịch của bạn, bạn có thể khai báo một macro thực hiện những gì bạn muốn:

#define my_printf(format, ...) \
    do { \
        fprintf(stderr, "Calling printf with fmt %s\n", format); \
        some_other_variadac_function(format, ##__VA_ARGS__); \
    } while(0)

Tuy nhiên, nói chung, giải pháp tốt nhất là sử dụng dạng va_list của hàm bạn đang cố gắng để tồn tại.


11

Hầu như, sử dụng các phương tiện có sẵn trong <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;
}

Lưu ý rằng bạn sẽ cần sử dụng vprintfphiên bản chứ không phải đơn giản printf. Không có cách nào để gọi trực tiếp một hàm matrixdic trong tình huống này mà không sử dụng va_list.


1
Cảm ơn, nhưng từ câu hỏi: "cơ sở mã hóa tôi đang làm việc trên [...] không có (và không thể thêm) chức năng trợ giúp gần giống với vfprintf."
Patrick

11

Vì thực sự không thể chuyển tiếp các cuộc gọi như vậy một cách tốt đẹp, chúng tôi đã khắc phục điều này bằng cách thiết lập khung ngăn xếp mới với một bản sao của khung ngăn xếp ban đầu. Tuy nhiên, điều này rất không khả thi và đưa ra tất cả các loại giả định , ví dụ mã sử dụng các con trỏ khung và các quy ước gọi 'tiêu chuẩn'.

Tệp tiêu đề này cho phép bao bọc các hàm matrixdic cho x86_64 và i386 (GCC). Nó không hoạt động cho các đối số dấu phẩy động, nhưng nên thẳng thắn mở rộng để hỗ trợ các đối số đó.

#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

Cuối cùng, bạn có thể ngắt các cuộc gọi như thế này:

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

Sử dụng 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
Cảm ơn, nhưng từ câu hỏi: "cơ sở mã hóa tôi đang làm việc trên [...] không có (và không thể thêm) chức năng trợ giúp gần giống với vfprintf."
Patrick

2

Không có cách nào để chuyển tiếp các cuộc gọi chức năng như vậy bởi vì vị trí duy nhất mà bạn có thể truy xuất các phần tử ngăn xếp thô nằm trong my_print(). Cách thông thường để kết thúc các cuộc gọi như vậy là có hai hàm, một hàm chỉ chuyển đổi các đối số thành các varargscấu trúc khác nhau và một hàm khác thực sự hoạt động theo các cấu trúc đó. Sử dụng như một mô hình hai chức năng, bạn có thể (ví dụ) quấn printf()bằng cách khởi tạo các cấu trúc trong my_printf()với va_start(), và sau đó vượt qua họ vfprintf().


Vì vậy, không có cách nào để chuyển tiếp lời gọi nếu bạn không thể sửa đổi việc thực hiện chức năng bạn muốn bọc?
Patrick

Đúng. Đặt cược tốt nhất của bạn là thúc đẩy một trình bao bọc kiểu vprintf được thêm vào.
John Millikin

1

Có bạn có thể làm điều đó, nhưng nó hơi xấu và bạn phải biết số lượng đối số tối đa. Hơn nữa, nếu bạn đang ở trên một kiến ​​trúc nơi các đối số không được truyền trên ngăn xếp như x86 (ví dụ: PowerPC), bạn sẽ phải biết liệu các loại "đặc biệt" (double, float, altivec, v.v.) có được sử dụng không và nếu vì vậy, đối phó với chúng cho phù hợp. Nó có thể bị đau nhanh chóng nhưng nếu bạn ở trên x86 hoặc nếu chức năng ban đầu có chu vi được xác định rõ và giới hạn, nó có thể hoạt động. Nó vẫn sẽ là một hack , sử dụng nó cho mục đích gỡ lỗi. Đừng xây dựng phần mềm cho bạn. Dù sao, đây là một ví dụ hoạt động trên 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;
}

Vì một số lý do, bạn không thể sử dụng float với va_arg, gcc nói rằng chúng được chuyển đổi thành gấp đôi nhưng chương trình gặp sự cố. Điều đó một mình chứng tỏ rằng giải pháp này là một hack và không có giải pháp chung. Trong ví dụ của tôi, tôi giả sử rằng số lượng đối số tối đa là 8, nhưng bạn có thể tăng số lượng đó. Hàm được bọc cũng chỉ sử dụng các số nguyên nhưng nó hoạt động tương tự với các tham số 'bình thường' khác vì chúng luôn truyền sang các số nguyên. Hàm mục tiêu sẽ biết các loại của chúng nhưng trình bao bọc trung gian của bạn không cần. Trình bao bọc cũng không cần biết đúng số lượng đối số vì hàm mục tiêu cũng sẽ biết nó. Để thực hiện công việc hữu ích (ngoại trừ chỉ ghi nhật ký cuộc gọi), có lẽ bạn sẽ phải biết cả hai.


0

Về cơ bản có ba lựa chọn.

Một là không vượt qua nó mà sử dụng triển khai từ khóa của hàm mục tiêu và không truyền vào các hình elip. Một cái khác là sử dụng macro macro. Tùy chọn thứ ba là tất cả những thứ tôi đang thiếu.

Tôi thường đi với tùy chọn một vì tôi cảm thấy như thế này thực sự dễ xử lý. Tùy chọn hai có một nhược điểm vì có một số hạn chế khi gọi macro macro.

Dưới đây là một số mã ví dụ:

#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

Cách tốt nhất để làm điều này là

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

gcc cung cấp một phần mở rộng có thể làm điều này: __builtin_applyvà người thân. Xem Xây dựng các cuộc gọi chức năng trong hướng dẫn gcc.

Một ví dụ:

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

Hãy thử nó trên godbolt

Có một số cảnh báo trong tài liệu rằng nó có thể không hoạt động trong các tình huống phức tạp hơn. Và bạn phải mã hóa kích thước tối đa cho các đối số (ở đây tôi đã sử dụng 1000). Nhưng nó có thể là một sự thay thế hợp lý cho các phương pháp khác liên quan đến việc phân tích ngăn xếp bằng ngôn ngữ C hoặc ngôn ngữ lắp ráp.


0

Không chắc chắn nếu điều này giúp trả lời câu hỏi của OP vì tôi không biết tại sao hạn chế sử dụng chức năng của trình trợ giúp gần giống với vfprintf trong chức năng trình bao bọc được áp dụng. Tôi nghĩ vấn đề chính ở đây là việc chuyển tiếp danh sách đối số từ khóa mà không diễn giải chúng là khó khăn. Điều có thể, là thực hiện định dạng (sử dụng hàm trợ giúp gần giống với vfprintf: vsnprintf) và chuyển tiếp đầu ra được định dạng sang hàm được bao bọc bằng các đối số matrixdic (nghĩa là không sửa đổi định nghĩa của hàm được bao bọc). Vì vậy, ở đây chúng tôi đi:

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

Tôi đã xem qua giải pháp này ở đây .

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.