从 rsp 反汇编 sub 和不同的功能 returns
Disassembly sub from rsp and different function returns
我目前正在使用 gcc、gdb 和汇编并试图理解它们。我已经复习了一些教程并掌握了一些关键点。
所以我决定使用一个小的 .c 文件,看了一下结果,有些事情不是很清楚。
这是文件:
#include <stdio.h>
void func1(){
int x = 8;
int y = x + 5;
}
void func2(){
int x = 12;
}
void func3(){
int x = 10+20;
}
void func4(){
int x;
x = 1;
}
void func5(){
int x;
int y;
x = 2;
y = 1;
}
void func6(){
int x;
int y;
x=15;
y=6;
y += x;
}
int main(int argc, char *argv[]) {
func1();
func2();
func3();
func4();
func5();
func6();
return 20;
}
这些是反汇编结果:
Dump of assembler code for function main:
0x0000000100000f60 <+0> : push %rbp
0x0000000100000f61 <+1> : mov %rsp,%rbp
0x0000000100000f64 <+4> : sub [=12=]x10,%rsp
0x0000000100000f68 <+8> : movl [=12=]x0,-0x4(%rbp)
0x0000000100000f6f <+15>: mov %edi,-0x8(%rbp)
0x0000000100000f72 <+18>: mov %rsi,-0x10(%rbp)
0x0000000100000f76 <+22>: callq 0x100000ed0 <func1>
0x0000000100000f7b <+27>: callq 0x100000ef0 <func2>
0x0000000100000f80 <+32>: callq 0x100000f00 <func3>
0x0000000100000f85 <+37>: callq 0x100000f10 <func4>
0x0000000100000f8a <+42>: callq 0x100000f20 <func5>
0x0000000100000f8f <+47>: callq 0x100000f40 <func6>
0x0000000100000f94 <+52>: mov [=12=]x14,%eax
0x0000000100000f99 <+57>: add [=12=]x10,%rsp
0x0000000100000f9d <+61>: pop %rbp
0x0000000100000f9e <+62>: retq
Dump of assembler code for function func1:
0x0000000100000ed0 <+0> : push %rbp
0x0000000100000ed1 <+1> : mov %rsp,%rbp
0x0000000100000ed4 <+4> : movl [=12=]x8,-0x4(%rbp)
0x0000000100000edb <+11>: mov -0x4(%rbp),%eax
0x0000000100000ede <+14>: add [=12=]x5,%eax
0x0000000100000ee3 <+19>: mov %eax,-0x8(%rbp)
0x0000000100000ee6 <+22>: pop %rbp
0x0000000100000ee7 <+23>: retq
0x0000000100000ee8 <+24>: nopl 0x0(%rax,%rax,1)
Dump of assembler code for function func2:
0x0000000100000ef0 <+0> : push %rbp
0x0000000100000ef1 <+1> : mov %rsp,%rbp
0x0000000100000ef4 <+4> : movl [=12=]xc,-0x4(%rbp)
0x0000000100000efb <+11>: pop %rbp
0x0000000100000efc <+12>: retq
0x0000000100000efd <+13>: nopl (%rax)
Dump of assembler code for function func3:
0x0000000100000f00 <+0> : push %rbp
0x0000000100000f01 <+1> : mov %rsp,%rbp
0x0000000100000f04 <+4> : movl [=12=]x1e,-0x4(%rbp)
0x0000000100000f0b <+11>: pop %rbp
0x0000000100000f0c <+12>: retq
0x0000000100000f0d <+13>: nopl (%rax)
Dump of assembler code for function func4:
0x0000000100000f10 <+0> : push %rbp
0x0000000100000f11 <+1> : mov %rsp,%rbp
0x0000000100000f14 <+4> : movl [=12=]x1,-0x4(%rbp)
0x0000000100000f1b <+11>: pop %rbp
0x0000000100000f1c <+12>: retq
0x0000000100000f1d <+13>: nopl (%rax)
Dump of assembler code for function func5:
0x0000000100000f20 <+0> : push %rbp
0x0000000100000f21 <+1> : mov %rsp,%rbp
0x0000000100000f24 <+4> : movl [=12=]x2,-0x4(%rbp)
0x0000000100000f2b <+11>: movl [=12=]x1,-0x8(%rbp)
0x0000000100000f32 <+18>: pop %rbp
0x0000000100000f33 <+19>: retq
0x0000000100000f34 <+20>: data16 data16 nopw %cs:0x0(%rax,%rax,1)
Dump of assembler code for function func6:
0x0000000100000f40 <+0> : push %rbp
0x0000000100000f41 <+1> : mov %rsp,%rbp
0x0000000100000f44 <+4> : movl [=12=]xf,-0x4(%rbp)
0x0000000100000f4b <+11>: movl [=12=]x6,-0x8(%rbp)
0x0000000100000f52 <+18>: mov -0x4(%rbp),%eax
0x0000000100000f55 <+21>: mov -0x8(%rbp),%ecx
0x0000000100000f58 <+24>: add %eax,%ecx
0x0000000100000f5a <+26>: mov %ecx,-0x8(%rbp)
0x0000000100000f5d <+29>: pop %rbp
0x0000000100000f5e <+30>: retq
0x0000000100000f5f <+31>: nop
我编译这个:
gcc -o example example.c
有几点不太清楚:
- 如果所有函数都以相同的方式结束(在代码中,例如 returns void)为什么
- func1 以 nopl 0x0(%rax,%rax,1)
结尾
- func2 & func3 & func4 以 nopl (%rax)
结尾
- func6 以 nop
结束
- func5 以 data16 data16 nopw %cs:0x0(%rax,%rax,1).
结尾
- data16 data16 nopw %cs:0x0(%rax,%rax,1) 到底是什么意思?
- 主要有
- 低于 $0x10,%rsp
- 加 $0x10,%rsp
- 这些是为方法中的局部变量分配内存吗?如果是这样,为什么它们总是四舍五入到 0x10、0x20、0x30...这不是有点浪费吗?
所有这些nopl 0x0(%rax,%rax,1)
等指令都是nop
指令的变体。它们用于确保函数的长度是 16 字节的倍数。您可能会问为什么他们不直接使用多个 0x90
(nop
) 指令。答案是,如果正在执行这些 nop,那么执行一个长的多字节 nop(如 data16 data16 nopw %cs:0x0(%rax,%rax,1)
或 nopl (%rax)
)比执行多个短 nop 稍微快一些。 Nops 出现在函数内部时可能会被执行;当编译器想要对齐跳转目标以提高性能时,就会生成这样的代码。 nops 由不知道哪些 nops 可能被执行以及哪些 nops 不会被执行的汇编程序生成,因为这通常是不可确定的。
关于堆栈的部分:你是在没有优化的情况下编译,你不应该问没有优化生成的奇怪代码。编译器被指示在没有优化的情况下编译时不聪明,那么为什么你期望它保存 space?
函数以 retq
语句结束。反汇编中显示的操作码只是实际执行的垃圾,但可能会在现代预测 CPU 中预先执行(并丢弃)。您可以放心地忽略它们。有针对具有 "branch delay" 的其他 CPU 的说明,但 x86 没有此功能。
retq
和下一个 16 字节边界之间的间隙是自由的,让函数从偶数地址开始。这允许更快的执行。
data16
可能意味着有一个 16 位数据与反汇编程序已知的任何操作码都不匹配。无视即可,不影响执行
x86 架构允许访问任何地址而不考虑对齐。但是访问未对齐的变量可能需要一个以上的总线周期来访问内存。堆栈点 rsp
的对齐保证对 uint64_t 的访问只会导致一个总线周期。
我目前正在使用 gcc、gdb 和汇编并试图理解它们。我已经复习了一些教程并掌握了一些关键点。
所以我决定使用一个小的 .c 文件,看了一下结果,有些事情不是很清楚。
这是文件:
#include <stdio.h>
void func1(){
int x = 8;
int y = x + 5;
}
void func2(){
int x = 12;
}
void func3(){
int x = 10+20;
}
void func4(){
int x;
x = 1;
}
void func5(){
int x;
int y;
x = 2;
y = 1;
}
void func6(){
int x;
int y;
x=15;
y=6;
y += x;
}
int main(int argc, char *argv[]) {
func1();
func2();
func3();
func4();
func5();
func6();
return 20;
}
这些是反汇编结果:
Dump of assembler code for function main:
0x0000000100000f60 <+0> : push %rbp
0x0000000100000f61 <+1> : mov %rsp,%rbp
0x0000000100000f64 <+4> : sub [=12=]x10,%rsp
0x0000000100000f68 <+8> : movl [=12=]x0,-0x4(%rbp)
0x0000000100000f6f <+15>: mov %edi,-0x8(%rbp)
0x0000000100000f72 <+18>: mov %rsi,-0x10(%rbp)
0x0000000100000f76 <+22>: callq 0x100000ed0 <func1>
0x0000000100000f7b <+27>: callq 0x100000ef0 <func2>
0x0000000100000f80 <+32>: callq 0x100000f00 <func3>
0x0000000100000f85 <+37>: callq 0x100000f10 <func4>
0x0000000100000f8a <+42>: callq 0x100000f20 <func5>
0x0000000100000f8f <+47>: callq 0x100000f40 <func6>
0x0000000100000f94 <+52>: mov [=12=]x14,%eax
0x0000000100000f99 <+57>: add [=12=]x10,%rsp
0x0000000100000f9d <+61>: pop %rbp
0x0000000100000f9e <+62>: retq
Dump of assembler code for function func1:
0x0000000100000ed0 <+0> : push %rbp
0x0000000100000ed1 <+1> : mov %rsp,%rbp
0x0000000100000ed4 <+4> : movl [=12=]x8,-0x4(%rbp)
0x0000000100000edb <+11>: mov -0x4(%rbp),%eax
0x0000000100000ede <+14>: add [=12=]x5,%eax
0x0000000100000ee3 <+19>: mov %eax,-0x8(%rbp)
0x0000000100000ee6 <+22>: pop %rbp
0x0000000100000ee7 <+23>: retq
0x0000000100000ee8 <+24>: nopl 0x0(%rax,%rax,1)
Dump of assembler code for function func2:
0x0000000100000ef0 <+0> : push %rbp
0x0000000100000ef1 <+1> : mov %rsp,%rbp
0x0000000100000ef4 <+4> : movl [=12=]xc,-0x4(%rbp)
0x0000000100000efb <+11>: pop %rbp
0x0000000100000efc <+12>: retq
0x0000000100000efd <+13>: nopl (%rax)
Dump of assembler code for function func3:
0x0000000100000f00 <+0> : push %rbp
0x0000000100000f01 <+1> : mov %rsp,%rbp
0x0000000100000f04 <+4> : movl [=12=]x1e,-0x4(%rbp)
0x0000000100000f0b <+11>: pop %rbp
0x0000000100000f0c <+12>: retq
0x0000000100000f0d <+13>: nopl (%rax)
Dump of assembler code for function func4:
0x0000000100000f10 <+0> : push %rbp
0x0000000100000f11 <+1> : mov %rsp,%rbp
0x0000000100000f14 <+4> : movl [=12=]x1,-0x4(%rbp)
0x0000000100000f1b <+11>: pop %rbp
0x0000000100000f1c <+12>: retq
0x0000000100000f1d <+13>: nopl (%rax)
Dump of assembler code for function func5:
0x0000000100000f20 <+0> : push %rbp
0x0000000100000f21 <+1> : mov %rsp,%rbp
0x0000000100000f24 <+4> : movl [=12=]x2,-0x4(%rbp)
0x0000000100000f2b <+11>: movl [=12=]x1,-0x8(%rbp)
0x0000000100000f32 <+18>: pop %rbp
0x0000000100000f33 <+19>: retq
0x0000000100000f34 <+20>: data16 data16 nopw %cs:0x0(%rax,%rax,1)
Dump of assembler code for function func6:
0x0000000100000f40 <+0> : push %rbp
0x0000000100000f41 <+1> : mov %rsp,%rbp
0x0000000100000f44 <+4> : movl [=12=]xf,-0x4(%rbp)
0x0000000100000f4b <+11>: movl [=12=]x6,-0x8(%rbp)
0x0000000100000f52 <+18>: mov -0x4(%rbp),%eax
0x0000000100000f55 <+21>: mov -0x8(%rbp),%ecx
0x0000000100000f58 <+24>: add %eax,%ecx
0x0000000100000f5a <+26>: mov %ecx,-0x8(%rbp)
0x0000000100000f5d <+29>: pop %rbp
0x0000000100000f5e <+30>: retq
0x0000000100000f5f <+31>: nop
我编译这个:
gcc -o example example.c
有几点不太清楚:
- 如果所有函数都以相同的方式结束(在代码中,例如 returns void)为什么
- func1 以 nopl 0x0(%rax,%rax,1) 结尾
- func2 & func3 & func4 以 nopl (%rax) 结尾
- func6 以 nop 结束
- func5 以 data16 data16 nopw %cs:0x0(%rax,%rax,1). 结尾
- data16 data16 nopw %cs:0x0(%rax,%rax,1) 到底是什么意思?
- 主要有
- 低于 $0x10,%rsp
- 加 $0x10,%rsp
- 这些是为方法中的局部变量分配内存吗?如果是这样,为什么它们总是四舍五入到 0x10、0x20、0x30...这不是有点浪费吗?
所有这些nopl 0x0(%rax,%rax,1)
等指令都是nop
指令的变体。它们用于确保函数的长度是 16 字节的倍数。您可能会问为什么他们不直接使用多个 0x90
(nop
) 指令。答案是,如果正在执行这些 nop,那么执行一个长的多字节 nop(如 data16 data16 nopw %cs:0x0(%rax,%rax,1)
或 nopl (%rax)
)比执行多个短 nop 稍微快一些。 Nops 出现在函数内部时可能会被执行;当编译器想要对齐跳转目标以提高性能时,就会生成这样的代码。 nops 由不知道哪些 nops 可能被执行以及哪些 nops 不会被执行的汇编程序生成,因为这通常是不可确定的。
关于堆栈的部分:你是在没有优化的情况下编译,你不应该问没有优化生成的奇怪代码。编译器被指示在没有优化的情况下编译时不聪明,那么为什么你期望它保存 space?
函数以
retq
语句结束。反汇编中显示的操作码只是实际执行的垃圾,但可能会在现代预测 CPU 中预先执行(并丢弃)。您可以放心地忽略它们。有针对具有 "branch delay" 的其他 CPU 的说明,但 x86 没有此功能。retq
和下一个 16 字节边界之间的间隙是自由的,让函数从偶数地址开始。这允许更快的执行。data16
可能意味着有一个 16 位数据与反汇编程序已知的任何操作码都不匹配。无视即可,不影响执行x86 架构允许访问任何地址而不考虑对齐。但是访问未对齐的变量可能需要一个以上的总线周期来访问内存。堆栈点
rsp
的对齐保证对 uint64_t 的访问只会导致一个总线周期。