C de varyasyon fonksiyonunun çağrılması


189

C'de, değişken bir fonksiyonun çağrılmasını iletmek mümkün müdür? De olduğu gibi,

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

Çağrıyı yukarıdaki şekilde yönlendirmek bu durumda kesinlikle gerekli değildir (çünkü çağrıları başka yollarla kaydedebilir veya vfprintf kullanabilirsiniz), ancak üzerinde çalıştığım kod tabanı, sarıcı için bazı gerçek işler yapmayı gerektirir ve vfprintf'e benzer bir yardımcı fonksiyona sahip değildir (ve ekleyemezsiniz).

[Güncelleme: Şimdiye kadar verilen cevaplara dayalı bir karışıklık var gibi görünüyor. Soruyu başka bir şekilde ifade etmek için: genel olarak, bu işlevin tanımını değiştirmeden bazı rastgele değişken fonksiyonlar sarabilirsiniz .]


1
harika soru - Neyse ki va_list alan bir vFunc aşırı yük ekleyebilirsiniz. Gönderdiğiniz için teşekkürler.
Gishu

Yanıtlar:


157

Sizin bir işlev benzer yoksa vfprintfbir sürer va_listyerine bağımsız değişken bir dizi, bunu yapamaz . Görmekhttp://c-faq.com/varargs/handoff.html .

Misal:

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

5
Sorunun ne kadar önemli olduğuna bağlı olarak, satır içi montaj tarafından çağırma hala bu olasılığı sunuyor.
filip

60

Doğrudan değil, ancak varyasyon fonksiyonların bir varargsstil alternatif fonksiyonu ile çiftler halinde gelmesi yaygındır (ve standart kütüphanede neredeyse evrensel olarak bulacaktır) . örneğin printf/vprintf

V ... işlevleri, uygulamaya genellikle derleyiciye özgü 'makro büyü' ile yapılan bir va_list parametresi alır, ancak v ... stil işlevinin böyle bir değişken işlevden çağrılmasının işe yarayacağı garanti edilir:

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

Bu size aradığınız etkiyi vermelidir.

Değişken bir kütüphane işlevi yazmayı düşünüyorsanız, va_list stili bir arkadaşı da kütüphanenin bir parçası olarak kullanılabilir hale getirmeyi düşünmelisiniz. Sorunuzdan da görebileceğiniz gibi, kullanıcılarınız için yararlı olabilir.


Değişkeni başka bir işleve va_copygeçirmeden önce aramanız gerektiğinden eminim myargs. Lütfen MSC39-C'ye bakınız , burada yaptığınız şey tanımsız davranıştır.
aviggiano

2
@aviggiano: Kural " Belirsiz bir değeri olan bir va_arg()üzerinde arama va_list", bunu yapmam çünkü asla va_argbenim arama işlevinde kullanmıyorum . Değer arasında myargs(bu durumda) çağrısından sonra vprintfedilir belirsiz (bizi yok varsayarak va_arg). Standart myargs" va_enddaha fazla atıfta bulunmadan önce makroya geçirilecek" diyor ; tam da bunu yapıyorum. Bu argümanları kopyalamak zorunda değilim çünkü çağıran fonksiyonda onlarla yineleme niyetim yok.
CB Bailey

1
Haklısın. Linux kılavuz sayfasında onaylar. Eğer apkullanan bir işleve va_arg(ap,type)sonra değerini apbu işlevin geri sonra tanımlanmamış.
aviggiano

48

C99 değişken değişkenli makroları destekler ; derleyicinize bağlı olarak, istediğinizi yapan bir makro bildirebilirsiniz:

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

Bununla birlikte, genel olarak, en iyi çözüm, varsa, sarmaya çalıştığınız işlevin va_list formunu kullanmaktır .


12

Neredeyse, mevcut tesisleri kullanarak <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;
}

vprintfDüz değil , sürümü kullanmanız gerekeceğini unutmayın printf. Bu durumda kullanmadan doğrudan değişken bir işlevi çağırmanın bir yolu yoktur va_list.


1
Teşekkürler, ancak sorudan: "Üzerinde çalıştığım kod temeli [...] vfprintf'e benzer bir yardımcı fonksiyona sahip değil (ve ekleyemez)."
Patrick

11

Bu tür çağrıları güzel bir şekilde yönlendirmek gerçekten mümkün olmadığından, orijinal yığın çerçevesinin bir kopyasıyla yeni bir yığın çerçevesi oluşturarak bu sorunu çözdük. Bununla birlikte, bu son derece ölçülemez ve her türlü varsayımı yapar , örneğin, kod çerçeve işaretçileri ve 'standart' çağrı kurallarını kullanır.

Bu başlık dosyası x86_64 ve i386 (GCC) için varyasyon fonksiyonlarının kaydırılmasına izin verir. Kayan noktalı argümanlar için işe yaramaz, ancak bunları desteklemek için ileriye dönük olmalıdır.

#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

Sonunda şu şekilde çağrıları sarabilirsiniz:

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 kullanın:

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
Teşekkürler, ancak sorudan: "Üzerinde çalıştığım kod temeli [...] vfprintf'e benzer bir yardımcı fonksiyona sahip değil (ve ekleyemez)."
Patrick

2

Ham yığın öğelerini alabileceğiniz tek konum olduğu için bu tür işlev çağrılarını yönlendirmenin bir yolu yoktur my_print(). Çağrıları bu şekilde sarmanın olağan yolu, biri bağımsız değişkenleri çeşitli varargsyapılara dönüştüren ve diğeri de bu yapılar üzerinde çalışan iki işleve sahip olmaktır . Böyle bir çift işlevli model kullanarak, (örneğin) printf()yapıları my_printf()ile başlatabilir va_start()ve sonra bunları aktarabilirsiniz vfprintf().


Yani, sarmak istediğiniz işlevin uygulanmasını değiştiremezseniz çağrıyı iletmenin bir yolu yok mu?
Patrick

Sağ. En iyi bahsiniz vprintf tarzı bir ambalajın eklenmesidir.
John Millikin

1

Evet yapabilirsin, ama bu biraz çirkin ve en fazla sayıda argümanı bilmek zorundasın. Ayrıca, argümanların x86 (örneğin, PowerPC) gibi yığına aktarılmadığı bir mimarideyseniz, "özel" türlerin (double, floats, altivec vb.) Kullanılıp kullanılmadığını ve böylece, onlarla uygun şekilde başa çıkmak. Çabuk acı verici olabilir, ancak x86 kullanıyorsanız veya orijinal işlevin iyi tanımlanmış ve sınırlı bir çevresi varsa, işe yarayabilir. Hala bir hack olacak , hata ayıklama amacıyla kullanın. Yazılımınızı bunun üzerine kurmayın. Her neyse, işte x86 üzerinde çalışan bir örnek:

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

Herhangi bir nedenle va_arg ile şamandıra kullanamazsınız, gcc bunların ikiye dönüştürüldüğünü ancak programın çöktüğünü söylüyor. Bu tek başına bu çözümün bir saldırı olduğunu ve genel bir çözüm olmadığını gösterir. Örneğimde maksimum argüman sayısının 8 olduğunu varsaydım, ancak bu sayıyı artırabilirsiniz. Sarılı işlev yalnızca tamsayılar da kullandı, ancak her zaman tamsayılara aktarıldığından diğer 'normal' parametrelerle aynı şekilde çalışır. Hedef işlev türlerini bilecek, ancak ara paketleyicinizin bilmesine gerek yoktur. Sarıcı ayrıca doğru işlev argümanlarını bilmek zorunda değildir, çünkü hedef işlev bunu da bilir. Yararlı bir iş yapmak için (sadece aramayı kaydetmek hariç), muhtemelen her ikisini de bilmek zorunda kalacaksınız.


1

gcc bunu yapabilen bir uzantı sunar: __builtin_applyve akrabalar. Bkz . Gcc kılavuzundaki İşlev Çağrıları Oluşturma .

Bir örnek:

#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 üzerinde dene

Belgelerde, daha karmaşık durumlarda çalışmayabileceğine dair bazı uyarılar vardır. Ve argümanlar için maksimum boyutu sabit kodlamanız gerekiyor (burada 1000 kullandım). Ancak yığının C veya montaj dilinde parçalara ayrılmasını içeren diğer yaklaşımlara makul bir alternatif olabilir.


Faydalı. Teşekkürler!
rsmoorthy

0

Esasen üç seçenek vardır.

Birincisi, onu aktarmak değil, hedef fonksiyonunuzun varyasyonel uygulamasını kullanmak ve elipsleri geçmek değil. Diğeri varyasyonlu bir makro kullanmaktır. Üçüncü seçenek eksik olduğum tüm şeyler.

Genellikle bir seçenek ile gidiyorum çünkü bu gerçekten kolay bir iş gibi hissediyorum. İkinci seçeneğin bir dezavantajı vardır, çünkü değişken makroları çağırmak için bazı sınırlamalar vardır.

İşte bazı örnek kod:

#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

Bunu yapmanın en iyi yolu

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

Sarıcı işlevinde vfprintf'e benzer bir yardımcı işlev kullanma kısıtlamasının neden geçerli olduğunu bilmediğimden, bu OP'nin sorusunu yanıtlamaya yardımcı olup olmadığından emin değilim. Bence buradaki temel sorun, varyasyon argüman listesini yorumlamadan iletmenin zor olmasıdır. Mümkün olan, biçimlendirmeyi gerçekleştirmek (vfprintf: vsnprintf'e benzer bir yardımcı işlev kullanarak) ve biçimlendirilmiş çıktıyı değişken değişkenlerle sarılmış işleve iletmektir (örneğin, sarılmış işlevin tanımını değiştirmemek). İşte başlıyoruz:

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

Burada bu çözüme rastladım .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.