Linux C variadic可變參數:va_list 在x86和x64下的區別與實現原理

仔細研究了下va_list的實現原理。通過看對應的彙編代碼實現和Intel ABI手冊,發現:

  • 在x86平臺下,va_list可變傳參是通過棧來進行;
  • 在x64平臺下,va_list可變傳參是是默認的calling convention;

這是Inter官方的Linux ABI文檔,詳細說明了可變參的底層實現,以及相關的使用約定。

The va_list type is an array containing a single element of one structure containing the necessary information to implement the va_arg macro.
The va_start macro initializes the structure as follows:

  • reg_save_area The element points to the start of the register save area.
  • overflow_arg_area This pointer is used to fetch arguments passed on the stack.
    It is initialized with the address of the first argument passed on the stack, if
    any, and then always updated to point to the start of the next argument on the stack.
  • gp_offset The element holds the offset in bytes from reg_save_area to the place where the next available general purpose argument register is saved.
    In case all argument registers have been exhausted, it is set to the value 48 (6 ∗ 8).
  • fp_offset The element holds the offset in bytes from reg_save_area to the place where the next available floating point argument register is saved. In case all argument registers have been exhausted, it is set to the value 304 (6 ∗ 8 + 16 ∗ 16).

下面通過一個例子結合對應的彙編代碼詳細說明:

#include <stdio.h>
#include <stdarg.h>

#if __GNUC__
    #if __x86_64__
        #define ENVIRONMENT64
    #else
        #define ENVIRONMENT32
    #endif
#endif

#if defined(ENVIRONMENT64)
long add(long num, ...)
{
    long sum = 0, i, tmp;
    /*
    (gdb) pt va_list
    type = struct __va_list_tag {
    unsigned int gp_offset;
    unsigned int fp_offset;
    void *overflow_arg_area;
    void *reg_save_area;
    } [1]
    */
    va_list va;

    printf("ENVIRONMENT64\n");

    /*
    (gdb) p va
    $2 = {{
    gp_offset = 8,
    fp_offset = 48,
    overflow_arg_area = 0x7fffffffe520,
    reg_save_area = 0x7fffffffe460
    }}

    (gdb) x/6g va.reg_save_area  // 這裏g表示一次打印8字節
    0x7fffffffe460: 0x0000000000000001  0x0000000000000002
    0x7fffffffe470: 0x0000000000000003  0x0000000000000004
    0x7fffffffe480: 0x0000000000000005  0x0000000000000006
    (gdb) x/2g va.overflow_arg_area
    0x7fffffffe520: 0x0000000000000007  0x0000000000000008
    */
    va_start(va, num);
    for (i = 0; i < num; i++) {
        tmp = va_arg(va, int);
        sum += tmp;
        printf("%ld ", tmp);
    }
    printf("\n");
    va_end(va);
    return sum;
}
#endif

#if defined(ENVIRONMENT32)
long add(long num, ...)
{
    long sum = 0, i, tmp;
    long *arg = &num + 1;

    printf("ENVIRONMENT32\n");
    for (i = 0; i < num; i++)
    {
        tmp = arg[i];
        sum += tmp;
        printf("%ld ", tmp);
    }
    printf("\n");
    return sum;
}
#endif

int main()
{
    /* ENVIRONMENT32, 使用棧來傳參
    0x080484f5 <+9>:     movl   $0x8,0x1c(%esp)
    0x080484fd <+17>:    movl   $0x7,0x18(%esp)
    0x08048505 <+25>:    movl   $0x6,0x14(%esp)
    0x0804850d <+33>:    movl   $0x5,0x10(%esp)
    0x08048515 <+41>:    movl   $0x4,0xc(%esp)
    0x0804851d <+49>:    movl   $0x3,0x8(%esp)
    0x08048525 <+57>:    movl   $0x2,0x4(%esp)
    0x0804852d <+65>:    movl   $0x7,(%esp)
    0x08048534 <+72>:    call   0x804847d <add>
    */

    /* ENVIRONMENT64, 使用寄存器+棧(默認calling convention)
    0x0000000000400711 <+8>:     movl   $0x8,0x8(%rsp)
    0x0000000000400719 <+16>:    movl   $0x7,(%rsp)
    0x0000000000400720 <+23>:    mov    $0x6,%r9d
    0x0000000000400726 <+29>:    mov    $0x5,%r8d
    0x000000000040072c <+35>:    mov    $0x4,%ecx
    0x0000000000400731 <+40>:    mov    $0x3,%edx
    0x0000000000400736 <+45>:    mov    $0x2,%esi
    0x000000000040073b <+50>:    mov    $0x7,%edi
    0x0000000000400740 <+55>:    mov    $0x0,%eax
    0x0000000000400745 <+60>:    callq  0x4005bd <add>
    */

    long sum = add(7, 2, 3, 4, 5, 6, 7, 8);
    printf("sum is %ld\n", sum);
    return 0;
}

函數運行如下。如果用的 Ubuntu 64位需要安裝下32位的庫:

  • sudo apt-get install gcc-multilib
  • sudo apt-get install g++-multilib
root@ubuntu:/media/psf/Home/iLearn/learn_valist# gcc main.c
root@ubuntu:/media/psf/Home/iLearn/learn_valist# ./a.out
ENVIRONMENT64
2 3 4 5 6 7 8
sum is 35
root@ubuntu:/media/psf/Home/iLearn/learn_valist# gcc main.c -m32
root@ubuntu:/media/psf/Home/iLearn/learn_valist# ./a.out
ENVIRONMENT32
2 3 4 5 6 7 8
sum is 35
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章