我从Assembly开始,为了测试,我编写了一个简单的C程序,编译和反汇编它,以便查看参数的传递方式。这是C代码:
#include <stdio.h>
#include <stdlib.h>
void calc (float*a,float*b,float*c,float*d) {
a[0]=1000;
b[0]=100.0;
c[0]=99.9;
d[0]=10000;
}
int main() {
float a[100];
float b[100];
float c[100];
float d[100];
calc(a,b,c,d);
}
这是它的反汇编:
default rel
global calc: function
global main: function
SECTION .text align=1 execute ; section number 1, code
calc: ; Function begin
push rbp ; 0000 _ 55
mov rbp, rsp ; 0001 _ 48: 89. E5
mov qword [rbp-8H], rdi ; 0004 _ 48: 89. 7D, F8
mov qword [rbp-10H], rsi ; 0008 _ 48: 89. 75, F0
mov qword [rbp-18H], rdx ; 000C _ 48: 89. 55, E8
mov qword [rbp-20H], rcx ; 0010 _ 48: 89. 4D, E0
; 0054 _ 90
pop rbp ; 0055 _ 5D
ret ; 0056 _ C3
; calc End of function
main: ; Function begin
push rbp ; 0057 _ 55
mov rbp, rsp ; 0058 _ 48: 89. E5
sub rsp, 1600 ; 005B _ 48: 81. EC, 00000640
lea rcx, [rbp-640H] ; 0062 _ 48: 8D. 8D, FFFFF9C0
lea rdx, [rbp-4B0H] ; 0069 _ 48: 8D. 95, FFFFFB50
lea rsi, [rbp-320H] ; 0070 _ 48: 8D. B5, FFFFFCE0
lea rax, [rbp-190H] ; 0077 _ 48: 8D. 85, FFFFFE70
mov rdi, rax ; 007E _ 48: 89. C7
call calc ; 0081 _ E8, 00000000(rel)
mov eax, 0 ; 0086 _ B8, 00000000
leave ; 008B _ C9
ret ; 008C _ C3
; main End of function
我不明白为什么堆栈上的参数大小不同。第一个是[ebp-8H]
,这是可以理解的,因为它是一个64位的地址,但下一个只有两个字节,在[ebp-10H]
而不是[ebp-16H]
。 />
为什么会这样,最重要的是,当我编写一个带有这些确切参数的汇编程序时,我应该从ebp
使用哪些地址?
答案 0 :(得分:2)
好像我说了很多,但可能你还没有听过,所以需要重复一遍:分析未经优化的代码的反汇编在很大程度上是浪费时间。当禁用优化时,编译器会关注两件事:
未优化的代码混乱,丑陋,令人困惑。它包含许多冗余指令,看起来不像人类会编写的内容,并且与实际应用程序中找到的代码不匹配(在启用优化的情况下编译)。
如果要分析汇编代码,请启用优化程序。
当我们这样做时,我们会看到您的代码编译为:
calc(float*, float*, float*, float*):
mov DWORD PTR [rdi], 0x447a0000
mov DWORD PTR [rsi], 0x42c80000
mov DWORD PTR [rdx], 0x42c7cccd
mov DWORD PTR [rcx], 0x461c4000
ret
main:
xor eax, eax
ret
等等,发生了什么?好吧,优化器看到main
除了返回0之外没有做任何东西(隐式地;甚至没有在代码中表示),所以它将整个函数转换为一个清除指令的指令EAX
注册然后返回。
但是,我们可以告诉我们EAX
中返回了函数的结果。在Unix系统上常见的System V AMD64调用约定中也是如此,在Windows上使用的64位调用约定中也是如此,它甚至可以在所有32位x86调用约定中实现。 。 (在EAX
中返回32位结果;在EDX:EAX
中返回64位结果,其中高位位于EDX
中,低位位于EAX
中。 1}}。)
我们也可以通过查看calc
函数的反汇编来告诉它如何接收它的参数。第一个整数参数在RDI
中传递,第二个传递在RSI
中,第三个传递在RDX
中,第四个传递在RCX
中。根据System V AMD64调用约定,如果有第五个参数,它将在R8
中传递,第六个参数将在R9
中传递。
换句话说,最多六个整数参数在寄存器中传递。之后,任何其他整数参数都会在堆栈上传递。
浮点参数在XMM寄存器(XMM0
到XMM7
)中传递,以便于使用SSE指令。同样,任何额外的浮点参数都会在堆栈上传递。
您尝试在“整数参数”和“内存参数”之间的注释中进行区分,但不存在后者。当您传递指针(或C ++中的引用,哪些编译器根据指针实现)时,您实际上正在传递地址。由于地址只是整数,所以它们就像任何其他整数值寄存器一样传递。
如果在堆栈上传递参数,它们的大小都是8字节(64位),并且一个接一个地传递。第一个与堆栈指针RBP
的偏移量为8。第二个是偏移16,等等。当您查看问题中的代码时,似乎有点混乱,这些代码来自十六进制中表示的偏移量,其中{ {1}}相当于十进制的16,10h
相当于十进制的24。 (为什么第一个参数从偏移量8开始?因为第一个位置18h
被返回指针占用。)
这基本上涵盖了调用约定的基础知识。但坦率地说,分析反汇编是而不是学习调用约定的一种非常好的方法。您不一定会看到更多细节,也无法获得全局视图。你真的需要read the fine manual。如果您讨厌手册,可以在线提供更简洁(和更简化)的各种摘要,例如Wikipedia。