x64 可变参数原理完全解析

问题引子

众所周知,在 X64 下,函数参数都是尽量通过寄存器来传递的(浮点数使用专用的浮点数寄存器 xmm, 其他参数使用通用寄存器),只有 abi 里规定的这些寄存器用完之后,才使用栈来传递参数。

stackoverflow 上有这个问题,标准里说makecontext的可变参数都必须是int类型,然后发现 makecontext源码里有如下一段注释说明。

 /* Handle arguments.
  
       The standard says the parameters must all be int values.  This is
       an historic accident and would be done differently today.  For
       x86-64 all integer values are passed as 64-bit values and
       therefore extending the API to copy 64-bit values instead of
       32-bit ints makes sense.  It does not break existing
       functionality and it does not violate the standard which says
       that passing non-int values means undefined behavior.  */

注释说明里,提到可变参数的整形都是当作 64bit 来处理的,就来深入探究一下x64可变参数的实现原理,包括可变参数当中含有浮点数的情况。

实验代码

实验的代码如下

#include <stdio.h>
#include <stdarg.h>
int f(int x, float y, short a, double b, ...)
{
    va_list ap;
    va_start(ap, b);

    char cc = va_arg(ap, int);
    float dd = va_arg(ap, double);
    int ee = va_arg(ap, int);
    double ff = va_arg(ap, double);
    int last = va_arg(ap, int);

    printf("%d  %f  %d  %lf  %d\n", (int)cc, dd, ee, ff, last);

    for(int i = 0; i < last; ++i)
    {
        int tmp = va_arg(ap, int);
        printf("%d\n", tmp);
    }

    va_end(ap);
}

int main()
{
    f(1, 2.2f, 3, 4.4,             5, 6.6f, 7, 8.8, 3, 10, 11, 12);
    return 0;
}

va_list 结构

typedef struct {
unsigned int gp_offset;
unsigned int fp_offset;
void *overflow_arg_area;
void *reg_save_area;
} va_list[1];

g 表示通用寄存器,f 表示浮点数寄存器。

reg_save_area指向栈上的一个特殊位置,这个位置位于栈中间。这里提供6 + 8个位置(前 6 个位置用来保存通用寄存器传递的参数,每个 8 个字节;后8个位置用来保存xmm寄存器传递的浮点数参数,每个16字节)。所有通过寄存器传递的可变参数最后都会复制到这里。比如第5,6两个参数(可变参数)通过通用寄存器传递。那么将占用第5,6两个位置,前4个位置将不会被使用。如果没有可变参数是通过寄存器传递(即可变参数之前已经有不少于 6 个确定的参数),那么这 6 个位置依然存在(占用空间),只是不被使用。

gp_offset表示第一个前边提到的6个位置中保存的可变参数相对于overflow_arg_area的偏移量。如果从第3个数开始是可变参数,那么偏移就是2 * 8 == 16,即下图当中的 0x10.

overflow_arg_area 表示通过栈传递的参数的起始地址。确定的参数和可变参数一起,前边 6 个通过寄存器传递。后边的都是通过栈传递。最后栈传递的参数最先入栈,第 7 个参数最后入栈,拥有最低的地址。所以overflow_arg_area指向第 7 个参数。

x64 下,浮点数通过 xmm寄存器来传递。xmm浮点数寄存器传递的可变参数最终也会复制到栈上。fp_offset表示复制到栈上的可变浮点数参数的起始地址偏离reg_save_area的大小,上边因为中间刚好隔着 6 个8字节的通用寄存器传递的参数。所以如果从第 3 个浮点数(不算非浮点数)开始,那么就是 8 * 6 + 2 * 16 = 80,即下图当中的0x50.

原理粗解

所以原理就是通过 reg_save_area + gp_offset 访问通过通用寄存器传递的可变参数,这些可变参数会复制到栈中间。每访问一个,gp_offset = gp_offset + 8.通过比较 gp_offset 是否大于 0x2f47,来判断是否耗尽这块区域。因为这个区域最大偏移是8 * 5 = 40,其实用不小于 40的数来进行 above判断比较皆可,源代码里使用47,和具体的实现相关。

通过reg_save_area + fp_offset访问浮点数寄存器传递的可变参数,这些可变参数也会复制到栈中间。每访问一个,fp_offset = fp_offset + 16.通过比较fp_offset是否大于0xaf175 来判断是否耗尽这块区域。因为这个区域最大偏移是8 * 6 + 16 * 7 = 160,其实用不小于 160 的数来进行above判断比较皆可,和具体的实现相关。

剩下的参数,是本函数的上级调用者传入调用者的栈,通过栈来传递的,通过overflow_arg_area来访问

栈上内存布局

汇编代码(太长不看)

对应的汇编代码和注释如下

gef➤  disassemble /m main
Dump of assembler code for function main:
26      {
=> 0x0000000000400854 <+0>:     push   %rbp
   0x0000000000400855 <+1>:     mov    %rsp,%rbp
​
27          f(1, 2.2f, 3, 4.4,             5, 6.6f, 7, 8.8, 3, 10, 11, 12);
   0x0000000000400858 <+4>:     movsd  0x118(%rip),%xmm2        # 0x400978
   0x0000000000400860 <+12>:    movsd  0x118(%rip),%xmm1        # 0x400980
   0x0000000000400868 <+20>:    movsd  0x118(%rip),%xmm0        # 0x400988
   0x0000000000400870 <+28>:    pushq  $0xc
   0x0000000000400872 <+30>:    pushq  $0xb
   0x0000000000400874 <+32>:    mov    $0xa,%r9d  #通用寄存器传递第 6 个非浮点参数
   0x000000000040087a <+38>:    mov    $0x3,%r8d  #通用寄存器传递第 5 个非浮点参数
   0x0000000000400880 <+44>:    movapd %xmm2,%xmm3
   0x0000000000400884 <+48>:    mov    $0x7,%ecx   #通用寄存器传递第 4 个非浮点参数
   0x0000000000400889 <+53>:    movapd %xmm1,%xmm2
   0x000000000040088d <+57>:    mov    $0x5,%edx   #通用寄存器传递第 3 个非浮点参数
   0x0000000000400892 <+62>:    movapd %xmm0,%xmm1    #xmm 寄存器用来传递浮点数参数。在call 指令前,按顺序从 xmm0 开始,传递从左到右的浮点数参数
   0x0000000000400896 <+66>:    mov    $0x3,%esi  #通用寄存器传递第 2 个非浮点参数
   0x000000000040089b <+71>:    movss  0xed(%rip),%xmm0        # 0x400990
   0x00000000004008a3 <+79>:    mov    $0x1,%edi  #通用寄存器传递第 1 个非浮点参数
   0x00000000004008a8 <+84>:    mov    $0x4,%eax   #eax 传递浮点数参数的个数。在调用者当中更具 eax 当中的值来进行判断是否需要将浮点数寄存器复制到栈当中。
   0x00000000004008ad <+89>:    callq  0x400596 <f>  #当然如果没有浮点数参数,直接 mov $0,%eax 这样就不需要进行复制
   0x00000000004008b2 <+94>:    add    $0x10,%rsp
​
28          return 0;
   0x00000000004008b6 <+98>:    mov    $0x0,%eax
​
29      }
   0x00000000004008bb <+103>:   leaveq
   0x00000000004008bc <+104>:   retq
​
​
​
​
gef➤  disassemble /m f
Dump of assembler code for function f:
4       {
   0x0000000000400596 <+0>:     push   %rbp
   0x0000000000400597 <+1>:     mov    %rsp,%rbp
   0x000000000040059a <+4>:     sub    $0x110,%rsp
   0x00000000004005a1 <+11>:    mov    %edi,-0xf4(%rbp)  #通用寄存器传递的前 2 个参数是命名参数,直接复制到栈的顶部
   0x00000000004005a7 <+17>:    movss  %xmm0,-0xf8(%rbp)
   0x00000000004005af <+25>:    movsd  %xmm1,-0x108(%rbp) #xmm 传递的前两个浮点数参数也是命名参数,直接复制到栈的顶部
   0x00000000004005b7 <+33>:    mov    %rdx,-0xa0(%rbp)
   0x00000000004005be <+40>:    mov    %rcx,-0x98(%rbp)
   0x00000000004005c5 <+47>:    mov    %r8,-0x90(%rbp)
   0x00000000004005cc <+54>:    mov    %r9,-0x88(%rbp)   #通用寄存器传递的后 4 个参数是可变参数,可变参数复制到 自身 栈中一块单独的保存通用寄存器传递的参数特定区域
   0x00000000004005d3 <+61>:    test   %al,%al #调用者负责设置好 eax 的值,表明传递的所有的浮点数参数的个数
   0x00000000004005d5 <+63>:    je     0x4005ef <f+89>
   0x00000000004005d7 <+65>:    movaps %xmm2,-0x60(%rbp)
   0x00000000004005db <+69>:    movaps %xmm3,-0x50(%rbp)  #xmm 传递的剩下的浮点数参数是可变参数,可变参数也复制到 自身 栈上一块单独保存浮点数寄存器的特定区域
   0x00000000004005df <+73>:    movaps %xmm4,-0x40(%rbp)  
   0x00000000004005e3 <+77>:    movaps %xmm5,-0x30(%rbp)
   0x00000000004005e7 <+81>:    movaps %xmm6,-0x20(%rbp)  #
   0x00000000004005eb <+85>:    movaps %xmm7,-0x10(%rbp)
   0x00000000004005ef <+89>:    mov    %esi,%eax           #edi 和 esi 传递的都是命名参数。所以直接复制到栈顶。
   0x00000000004005f1 <+91>:    mov    %ax,-0xfc(%rbp)  #通用寄存器传递的参数和浮点数传递的参数保存在栈上的位置相邻
​
5           va_list ap;
6           va_start(ap, b);
   0x00000000004005f8 <+98>:    movl   $0x10,-0xe8(%rbp) #va_list.gp_offset 第一个通用寄存器传递的可变参数偏离保存的寄存器区域的偏移量
   0x0000000000400602 <+108>:   movl   $0x50,-0xe4(%rbp) #va_list.fp_offset 第一个浮点数寄存器传递的可变参数偏离保存的寄存器区域的偏移量
   0x000000000040060c <+118>:   lea    0x10(%rbp),%rax  #这里是 rbp + 说明是调用者的栈,0x10 刚好跳过保存的调用者的 rbp (当前 rbp 指向旧的 rbp) 和 返回地址
   0x0000000000400610 <+122>:   mov    %rax,-0xe0(%rbp)  #va_list.overflow_arg_area 指向
   0x0000000000400617 <+129>:   lea    -0xb0(%rbp),%rax
   0x000000000040061e <+136>:   mov    %rax,-0xd8(%rbp) #va_list.reg_save_area 指向上一个栈帧上保存的寄存器(保存通用寄存器和浮点数寄存器传递的参数)区
​
7
8           char cc = va_arg(ap, int);
   0x0000000000400625 <+143>:   mov    -0xe8(%rbp),%eax #eax = va_list.gp_offset
   0x000000000040062b <+149>:   cmp    $0x2f,%eax  #因为通用寄存器只有 6 个位置 48 个字节,所以这里计算偏移量是否超过47,超过说明剩下的可变参数不在这个本栈帧内的这个特殊区域
   0x000000000040062e <+152>:   ja     0x400653 <f+189>
   0x0000000000400630 <+154>:   mov    -0xd8(%rbp),%rax #va_list.reg_save_area
   0x0000000000400637 <+161>:   mov    -0xe8(%rbp),%edx
   0x000000000040063d <+167>:   mov    %edx,%edx
   0x000000000040063f <+169>:   add    %rdx,%rax #eax = va_list.gp_offset + va_list.reg_save_area,刚好指向特定区域保存的寄存器传递的参数
   0x0000000000400642 <+172>:   mov    -0xe8(%rbp),%edx
   0x0000000000400648 <+178>:   add    $0x8,%edx
   0x000000000040064b <+181>:   mov    %edx,-0xe8(%rbp) #va_list.gp_offset = va_list.gp_offset + 0x8,偏移量 + 8 指向下一个保存的寄存器参数
   0x0000000000400651 <+187>:   jmp    0x400665 <f+207>
   0x0000000000400653 <+189>:   mov    -0xe0(%rbp),%rax
   0x000000000040065a <+196>:   lea    0x8(%rax),%rdx
   0x000000000040065e <+200>:   mov    %rdx,-0xe0(%rbp)
   0x0000000000400665 <+207>:   mov    (%rax),%eax  #eax 指向保存的寄存器参数,取值
   0x0000000000400667 <+209>:   mov    %al,-0xb5(%rbp) # 复制给栈上的局部变量 cc
​
9           float dd = va_arg(ap, double);
   0x000000000040066d <+215>:   mov    -0xe4(%rbp),%eax #eax = va_list.fp_offset
   0x0000000000400673 <+221>:   cmp    $0xaf,%eax #通用寄存器 6 * 8 浮点数寄存器 8 * 16 一共就是 48 + 128 = 128 个字节,这里大于0xaf(127) 说明剩下的浮点数参数都在上个栈上
   0x0000000000400678 <+226>:   ja     0x40069d <f+263>
   0x000000000040067a <+228>:   mov    -0xd8(%rbp),%rax #va_list.reg_save_area
   0x0000000000400681 <+235>:   mov    -0xe4(%rbp),%edx
   0x0000000000400687 <+241>:   mov    %edx,%edx
   0x0000000000400689 <+243>:   add    %rdx,%rax #rax = va_list.reg_save_area + va_list.fp_offset 刚好指向特殊区域当中的保存的浮点数寄存器
   0x000000000040068c <+246>:   mov    -0xe4(%rbp),%edx
   0x0000000000400692 <+252>:   add    $0x10,%edx 
   0x0000000000400695 <+255>:   mov    %edx,-0xe4(%rbp) #偏移 + 16,指向下一个 va_list.fp_offset = va_list.fp_offset + 16
   0x000000000040069b <+261>:   jmp    0x4006af <f+281>
   0x000000000040069d <+263>:   mov    -0xe0(%rbp),%rax
   0x00000000004006a4 <+270>:   lea    0x8(%rax),%rdx
   0x00000000004006a8 <+274>:   mov    %rdx,-0xe0(%rbp)
   0x00000000004006af <+281>:   movsd  (%rax),%xmm0      #这 3 条指令,先用双精度复制到 xmm0 中转
   0x00000000004006b3 <+285>:   cvtsd2ss %xmm0,%xmm2    #再将双精度转换成单精度,复制给 xmm2 中转
   0x00000000004006b7 <+289>:   movss  %xmm2,-0xbc(%rbp)  #最后赋值给局部变量 dd
​
10          int ee = va_arg(ap, int);
   0x00000000004006bf <+297>:   mov    -0xe8(%rbp),%eax
   0x00000000004006c5 <+303>:   cmp    $0x2f,%eax
   0x00000000004006c8 <+306>:   ja     0x4006ed <f+343>
   0x00000000004006ca <+308>:   mov    -0xd8(%rbp),%rax
   0x00000000004006d1 <+315>:   mov    -0xe8(%rbp),%edx
   0x00000000004006d7 <+321>:   mov    %edx,%edx
   0x00000000004006d9 <+323>:   add    %rdx,%rax
   0x00000000004006dc <+326>:   mov    -0xe8(%rbp),%edx
   0x00000000004006e2 <+332>:   add    $0x8,%edx
   0x00000000004006e5 <+335>:   mov    %edx,-0xe8(%rbp)
   0x00000000004006eb <+341>:   jmp    0x4006ff <f+361>
   0x00000000004006ed <+343>:   mov    -0xe0(%rbp),%rax
   0x00000000004006f4 <+350>:   lea    0x8(%rax),%rdx
   0x00000000004006f8 <+354>:   mov    %rdx,-0xe0(%rbp)
   0x00000000004006ff <+361>:   mov    (%rax),%eax
   0x0000000000400701 <+363>:   mov    %eax,-0xc0(%rbp)
​
11          double ff = va_arg(ap, double);
   0x0000000000400707 <+369>:   mov    -0xe4(%rbp),%eax
   0x000000000040070d <+375>:   cmp    $0xaf,%eax
   0x0000000000400712 <+380>:   ja     0x400737 <f+417>
   0x0000000000400714 <+382>:   mov    -0xd8(%rbp),%rax
   0x000000000040071b <+389>:   mov    -0xe4(%rbp),%edx
   0x0000000000400721 <+395>:   mov    %edx,%edx
   0x0000000000400723 <+397>:   add    %rdx,%rax
   0x0000000000400726 <+400>:   mov    -0xe4(%rbp),%edx
   0x000000000040072c <+406>:   add    $0x10,%edx
   0x000000000040072f <+409>:   mov    %edx,-0xe4(%rbp)
   0x0000000000400735 <+415>:   jmp    0x400749 <f+435>
   0x0000000000400737 <+417>:   mov    -0xe0(%rbp),%rax
   0x000000000040073e <+424>:   lea    0x8(%rax),%rdx
   0x0000000000400742 <+428>:   mov    %rdx,-0xe0(%rbp)
   0x0000000000400749 <+435>:   movsd  (%rax),%xmm0
   0x000000000040074d <+439>:   movsd  %xmm0,-0xc8(%rbp)
​
12          int last = va_arg(ap, int);
   0x0000000000400755 <+447>:   mov    -0xe8(%rbp),%eax
   0x000000000040075b <+453>:   cmp    $0x2f,%eax
   0x000000000040075e <+456>:   ja     0x400783 <f+493>
   0x0000000000400760 <+458>:   mov    -0xd8(%rbp),%rax
   0x0000000000400767 <+465>:   mov    -0xe8(%rbp),%edx
   0x000000000040076d <+471>:   mov    %edx,%edx
   0x000000000040076f <+473>:   add    %rdx,%rax
   0x0000000000400772 <+476>:   mov    -0xe8(%rbp),%edx
   0x0000000000400778 <+482>:   add    $0x8,%edx
   0x000000000040077b <+485>:   mov    %edx,-0xe8(%rbp)
   0x0000000000400781 <+491>:   jmp    0x400795 <f+511>
   0x0000000000400783 <+493>:   mov    -0xe0(%rbp),%rax
   0x000000000040078a <+500>:   lea    0x8(%rax),%rdx
   0x000000000040078e <+504>:   mov    %rdx,-0xe0(%rbp)
   0x0000000000400795 <+511>:   mov    (%rax),%eax
   0x0000000000400797 <+513>:   mov    %eax,-0xcc(%rbp)
​
13
14          printf("%d  %f  %d  %lf  %d\n", (int)cc, dd, ee, ff, last);
   0x000000000040079d <+519>:   cvtss2sd -0xbc(%rbp),%xmm0
   0x00000000004007a5 <+527>:   movsbl -0xb5(%rbp),%eax
   0x00000000004007ac <+534>:   mov    -0xcc(%rbp),%ecx
   0x00000000004007b2 <+540>:   movsd  -0xc8(%rbp),%xmm1
   0x00000000004007ba <+548>:   mov    -0xc0(%rbp),%edx
   0x00000000004007c0 <+554>:   mov    %eax,%esi
   0x00000000004007c2 <+556>:   mov    $0x400958,%edi
   0x00000000004007c7 <+561>:   mov    $0x2,%eax
   0x00000000004007cc <+566>:   callq  0x4004a0 <printf@plt>
​
15
16          for(int i = 0; i < last; ++i)
   0x00000000004007d1 <+571>:   movl   $0x0,-0xb4(%rbp)
   0x00000000004007db <+581>:   jmp    0x400843 <f+685>
   0x000000000040083c <+678>:   addl   $0x1,-0xb4(%rbp)
   0x0000000000400843 <+685>:   mov    -0xb4(%rbp),%eax
   0x0000000000400849 <+691>:   cmp    -0xcc(%rbp),%eax
   0x000000000040084f <+697>:   jl     0x4007dd <f+583>
​
17          {
18              int tmp = va_arg(ap, int);
   0x00000000004007dd <+583>:   mov    -0xe8(%rbp),%eax   #eax = va_list.gp_offset
   0x00000000004007e3 <+589>:   cmp    $0x2f,%eax     #栈上保存的是否耗尽
   0x00000000004007e6 <+592>:   ja     0x40080b <f+629>
   0x00000000004007e8 <+594>:   mov    -0xd8(%rbp),%rax
   0x00000000004007ef <+601>:   mov    -0xe8(%rbp),%edx
   0x00000000004007f5 <+607>:   mov    %edx,%edx
   0x00000000004007f7 <+609>:   add    %rdx,%rax
   0x00000000004007fa <+612>:   mov    -0xe8(%rbp),%edx
   0x0000000000400800 <+618>:   add    $0x8,%edx
   0x0000000000400803 <+621>:   mov    %edx,-0xe8(%rbp)
   0x0000000000400809 <+627>:   jmp    0x40081d <f+647>
   0x000000000040080b <+629>:   mov    -0xe0(%rbp),%rax      #如果耗尽跳转到这里  eax = va_list.overflow_arg_area 指向上个栈帧
   0x0000000000400812 <+636>:   lea    0x8(%rax),%rdx        #
   0x0000000000400816 <+640>:   mov    %rdx,-0xe0(%rbp) #va_list.overflow_arg_area = va_list.overflow_arg_area + 8
   0x000000000040081d <+647>:   mov    (%rax),%eax   #取值
   0x000000000040081f <+649>:   mov    %eax,-0xd0(%rbp)
​
19              printf("%d\n", tmp);
   0x0000000000400825 <+655>:   mov    -0xd0(%rbp),%eax
   0x000000000040082b <+661>:   mov    %eax,%esi
   0x000000000040082d <+663>:   mov    $0x40096d,%edi
   0x0000000000400832 <+668>:   mov    $0x0,%eax
   0x0000000000400837 <+673>:   callq  0x4004a0 <printf@plt>
​
20          }
21
22          va_end(ap);
23      }
   0x0000000000400851 <+699>:   nop
   0x0000000000400852 <+700>:   leaveq
   0x0000000000400853 <+701>:   retq
​

x64可变参数,通用类型的都是按照 8 字节来保存的,所以不存在需要都是整数的说法

总结

1、通过va_arg来获取参数值,广义的整形(char, short...)指定的类型长度不能小于 int,浮点型不能是 float,否则有警告,会进行类型提升,而且查生成的汇编代码也有问题。

warning: ‘char’ is promoted to ‘int’ when passed through ‘...’

warning: ‘float’ is promoted to ‘double’ when passed through ‘...’

2、只有通过寄存器传递的可变参数,才会赋值到栈上这块特殊的区域,毕竟具名参数都复制到了栈顶。多余的参数(6个之外的整形参数,8个之外的浮点参数)都是在 call指令之前入栈,也就是本函数作为被调用者,在调用者的栈上,而且这些直接通过栈传递的整形都是占用 8 个字节,浮点型,无论是单精度还是双精度也是占用 8 个字节。

如果是下边这种方式调用,因为前边已经有了 8 个浮点数参数,14.14f 将通过栈传递。

f(1, 2.2f, 3, 4.4,             5, 6.6f, 7, 8.8, 3, 10, 11, 12, 'x', 'y', 9.9, 10.1, 11.11, 12.12, 13.3f, 14.4f);
 对应的部分反汇编如下
 0x00000000004008e4 <+0>:     push   %rbp
   0x00000000004008e5 <+1>:     mov    %rsp,%rbp
   0x00000000004008e8 <+4>:     movsd  0x158(%rip),%xmm7        # 0x400a48
   0x00000000004008f0 <+12>:    movsd  0x158(%rip),%xmm6        # 0x400a50
   0x00000000004008f8 <+20>:    movsd  0x158(%rip),%xmm5        # 0x400a58
   0x0000000000400900 <+28>:    movsd  0x158(%rip),%xmm4        # 0x400a60
   0x0000000000400908 <+36>:    movsd  0x158(%rip),%xmm3        # 0x400a68
   0x0000000000400910 <+44>:    movsd  0x158(%rip),%xmm2        # 0x400a70
   0x0000000000400918 <+52>:    movsd  0x158(%rip),%xmm1        # 0x400a78
   0x0000000000400920 <+60>:    movsd  0x158(%rip),%xmm0        # 0x400a80
   0x0000000000400928 <+68>:    lea    -0x8(%rsp),%rsp
   0x000000000040092d <+73>:    movsd  %xmm0,(%rsp)                         #13.3f 放到栈上
   0x0000000000400932 <+78>:    movsd  0x14e(%rip),%xmm0        # 0x400a88
   0x000000000040093a <+86>:    lea    -0x8(%rsp),%rsp
   0x000000000040093f <+91>:    movsd  %xmm0,(%rsp)  # 0x400a88             #14.4f 放到栈上
   0x0000000000400944 <+96>:    pushq  $0x79 #虽然是 char 直接占用 8 个字节
   0x0000000000400946 <+98>:    pushq  $0x78
   0x0000000000400948 <+100>:   pushq  $0xc
   0x000000000040094a <+102>:   pushq  $0xb #直接占用 8 个字节
   0x000000000040094c <+104>:   mov    $0xa,%r9d
   0x0000000000400952 <+110>:   mov    $0x3,%r8d
   0x0000000000400958 <+116>:   mov    $0x7,%ecx
   0x000000000040095d <+121>:   mov    $0x5,%edx
   0x0000000000400962 <+126>:   mov    $0x3,%esi
   0x0000000000400967 <+131>:   movss  0x121(%rip),%xmm0        # 0x400a90 #第一个浮点数参数 2.2f 传给寄存器
   0x000000000040096f <+139>:   mov    $0x1,%edi
   0x0000000000400974 <+144>:   mov    $0x8,%eax
   0x0000000000400979 <+149>:   callq  0x400596 <f>
   0x000000000040097e <+154>:   add    $0x30,%rsp
   0x0000000000400982 <+158>:   mov    $0x0,%eax
   0x0000000000400987 <+163>:   leaveq
   0x0000000000400988 <+164>:   retq

之前通过 va_arg获取参数时,如果在调用者的栈上,无论是整形还是浮点型,va_list.overflow_arg_area 都是增加 8 个字节也说明了这一点。

lea    0x8(%rax),%rdx
mov    %rdx,-0xe0(%rbp) #va_list.overflow_arg_area = va_list.overflow_arg_area + 8

x64下,通过栈传递的整形和浮点型参数都是占用 8 个字节,毕竟使用的是 pushq 指令。但是通过寄存器传递的具名参数,复制到栈上,因为是先开辟栈空间,然后再 mov 复制进栈,都是能合并的合并来节省内存。

通过寄存器传递的可变参数最终复制到栈上的特定区域,整形都是 8 个字节,浮点型因为是 xmm 寄存器,都是 16 个字节。

回归

最初的问题,makecontext的源码内部

va_arg (ap, greg_t); #grep_t 是宏定义 为 long long

通过 greg_t类型来获取参数,所以makecontextx64 环境下,其可变参数可以是任意的整形char, short, long, long long

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值