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

猜你喜欢

转载自blog.csdn.net/thisinnocence/article/details/79314006