汇编 - 将参数传递给函数调用

Mar*_*ark 0 linux assembly x86-64 abi calling-convention

我目前正在通过反汇编C程序并试图了解它们的作用来进行汇编阅读.

我被困在一个简单的问题:一个简单的你好世界计划.

#include <stdio.h>
#include <stdlib.h>

int main() {
  printf("Hello, world!");
  return(0);
}
Run Code Online (Sandbox Code Playgroud)

当我拆卸主要时:

(gdb) disassemble main
Dump of assembler code for function main:
   0x0000000000400526 <+0>: push   rbp
   0x0000000000400527 <+1>: mov    rbp,rsp
   0x000000000040052a <+4>: mov    edi,0x4005c4
   0x000000000040052f <+9>: mov    eax,0x0
   0x0000000000400534 <+14>:    call   0x400400 <printf@plt>
   0x0000000000400539 <+19>:    mov    eax,0x0  
   0x000000000040053e <+24>:    pop    rbp
   0x000000000040053f <+25>:    ret
Run Code Online (Sandbox Code Playgroud)

我理解前两行:基本指针保存在堆栈上(通过push rbp,这会导致堆栈指针的值减少8,因为它已经"增长")并且堆栈指针的值被保存在基指针中(这样,参数和局部变量可以分别通过正偏移和负偏移轻松到达,而堆栈可以保持"增长").

第三行提出了第一个问题:为什么0x4005c4("Hello,World!"字符串的地址)在edi寄存器中移动而不是在堆栈中移动?printf函数不应该将该字符串的地址作为参数吗?据我所知,函数从堆栈中获取参数(但在这里,看起来参数放在该寄存器中:edi)

在StackOverflow上的另一篇文章中,我读到"printf @ ptl"就像一个调用真正的printf函数的存根函数.我试图反汇编这个功能,但它变得更加混乱:

(gdb) disassemble printf
Dump of assembler code for function __printf:
   0x00007ffff7a637b0 <+0>: sub    rsp,0xd8
   0x00007ffff7a637b7 <+7>: test   al,al
   0x00007ffff7a637b9 <+9>: mov    QWORD PTR [rsp+0x28],rsi
   0x00007ffff7a637be <+14>:    mov    QWORD PTR [rsp+0x30],rdx
   0x00007ffff7a637c3 <+19>:    mov    QWORD PTR [rsp+0x38],rcx
   0x00007ffff7a637c8 <+24>:    mov    QWORD PTR [rsp+0x40],r8
   0x00007ffff7a637cd <+29>:    mov    QWORD PTR [rsp+0x48],r9
   0x00007ffff7a637d2 <+34>:    je     0x7ffff7a6380b <__printf+91>
   0x00007ffff7a637d4 <+36>:    movaps XMMWORD PTR [rsp+0x50],xmm0
   0x00007ffff7a637d9 <+41>:    movaps XMMWORD PTR [rsp+0x60],xmm1
   0x00007ffff7a637de <+46>:    movaps XMMWORD PTR [rsp+0x70],xmm2
   0x00007ffff7a637e3 <+51>:    movaps XMMWORD PTR [rsp+0x80],xmm3
   0x00007ffff7a637eb <+59>:    movaps XMMWORD PTR [rsp+0x90],xmm4
   0x00007ffff7a637f3 <+67>:    movaps XMMWORD PTR [rsp+0xa0],xmm5
   0x00007ffff7a637fb <+75>:    movaps XMMWORD PTR [rsp+0xb0],xmm6
   0x00007ffff7a63803 <+83>:    movaps XMMWORD PTR [rsp+0xc0],xmm7
   0x00007ffff7a6380b <+91>:    lea    rax,[rsp+0xe0]
   0x00007ffff7a63813 <+99>:    mov    rsi,rdi
   0x00007ffff7a63816 <+102>:   lea    rdx,[rsp+0x8]
   0x00007ffff7a6381b <+107>:   mov    QWORD PTR [rsp+0x10],rax
   0x00007ffff7a63820 <+112>:   lea    rax,[rsp+0x20]
   0x00007ffff7a63825 <+117>:   mov    DWORD PTR [rsp+0x8],0x8
   0x00007ffff7a6382d <+125>:   mov    DWORD PTR [rsp+0xc],0x30
   0x00007ffff7a63835 <+133>:   mov    QWORD PTR [rsp+0x18],rax
   0x00007ffff7a6383a <+138>:   mov    rax,QWORD PTR [rip+0x36d70f]        # 0x7ffff7dd0f50
   0x00007ffff7a63841 <+145>:   mov    rdi,QWORD PTR [rax]
   0x00007ffff7a63844 <+148>:   call   0x7ffff7a5b130 <_IO_vfprintf_internal>
   0x00007ffff7a63849 <+153>:   add    rsp,0xd8
   0x00007ffff7a63850 <+160>:   ret    
End of assembler dump.
Run Code Online (Sandbox Code Playgroud)

eax上的两个mov操作(mov eax,0x0)也让我感到烦恼,因为我没有在这里扮演角色(但我更关心我刚刚描述的内容).先感谢您.

Pet*_*des 5

gcc的目标是x86-64 System V ABI,由Windows以外的所有x86-64系统使用(出于各种历史原因).它的调用约定在返回堆栈之前传递寄存器中的前几个args.(另请参阅维基百科此调用约定的基本摘要.)

是的,这与使用堆栈处理所有内容的硬件旧32位调用约定不同.这是一件好事.另请参阅标签wiki以获取ABI文档的更多链接以及大量其他内容.

   0x0000000000400526: push   rbp
   0x0000000000400527: mov    rbp,rsp         # stack-frame boilerplate
   0x000000000040052a: mov    edi,0x4005c4    # first arg
   0x000000000040052f: mov    eax,0x0         # 0 FP args in vector registers
   0x0000000000400534: call   0x400400 <printf@plt>
   0x0000000000400539: mov    eax,0x0         # return 0.  If you'd compiled with optimization, this and the previous mov would be  xor eax,eax
   0x000000000040053e: pop    rbp             # clean up stack frame
   0x000000000040053f: ret
Run Code Online (Sandbox Code Playgroud)

指向静态数据的指针适合32位,这就是它可以mov edi, imm32代替使用的原因movabs rdi, imm64.

浮点args在SSE寄存器(xmm0-xmm7)中传递,甚至传递给var-args函数. al表示向量寄存器中有多少FP args.(注意,C的类型提升规则意味着float变量函数的args总是被提升为double,这就是为什么printf没有任何格式说明符float,仅用于doublelong double).


printf@ptl 就像一个调用真正的printf函数的存根函数.

恩,那就对了.过程链接表条目jmp以动态链接器例程开始,该例程解析符号并修改PLT中的代码以将其jmp直接转换printf为映射libc 定义的地址. printf是一个弱别名__printf,这就是为什么gdb __printf在你要求反汇编之后选择该地址的标签printf.

Dump of assembler code for function __printf:
   0x00007ffff7a637b0 <+0>: sub    rsp,0xd8               # reserve space
   0x00007ffff7a637b7 <+7>: test   al,al                  # check if there were any FP args
   0x00007ffff7a637b9 <+9>: mov    QWORD PTR [rsp+0x28],rsi  # store the integer arg-passing registers to local scratch space
   0x00007ffff7a637be <+14>:    mov    QWORD PTR [rsp+0x30],rdx
   0x00007ffff7a637c3 <+19>:    mov    QWORD PTR [rsp+0x38],rcx
   0x00007ffff7a637c8 <+24>:    mov    QWORD PTR [rsp+0x40],r8
   0x00007ffff7a637cd <+29>:    mov    QWORD PTR [rsp+0x48],r9
   0x00007ffff7a637d2 <+34>:    je     0x7ffff7a6380b <__printf+91>  # skip storing the FP arg-passing regs if there were no FP args
   0x00007ffff7a637d4 <+36>:    movaps XMMWORD PTR [rsp+0x50],xmm0
   0x00007ffff7a637d9 <+41>:    movaps XMMWORD PTR [rsp+0x60],xmm1
   0x00007ffff7a637de <+46>:    movaps XMMWORD PTR [rsp+0x70],xmm2
   0x00007ffff7a637e3 <+51>:    movaps XMMWORD PTR [rsp+0x80],xmm3
   0x00007ffff7a637eb <+59>:    movaps XMMWORD PTR [rsp+0x90],xmm4
   0x00007ffff7a637f3 <+67>:    movaps XMMWORD PTR [rsp+0xa0],xmm5
   0x00007ffff7a637fb <+75>:    movaps XMMWORD PTR [rsp+0xb0],xmm6
   0x00007ffff7a63803 <+83>:    movaps XMMWORD PTR [rsp+0xc0],xmm7
       branch_target_from_test_je:
   0x00007ffff7a6380b <+91>:    lea    rax,[rsp+0xe0]            # some more stuff
Run Code Online (Sandbox Code Playgroud)

因此printf,实现通过将所有arg传递寄存器(除了第一个包含格式字符串的寄存器)存储到本地数组来使var-args处理变得简单.它可以通过它们遍历指针,而不需要类似开关的代码来提取正确的整数或FP arg.它仍然需要跟踪前5个整数和前8个FP args,因为它们与调用者推入堆栈的其余args不相邻.

Windows 64位调用约定的阴影空间通过为函数提供空间来简化这一点,该函数将其寄存器args转储到与堆栈上已有的args相邻的堆栈,但是在每次调用IMO时都不值得浪费32个字节的堆栈.(请参阅我的答案和对其他答案的评论为什么Windows64在x86-64上使用来自所有其他操作系统的不同调用约定?)