寄存器、ia32、AT&T汇编、intel汇编、x86架构、x64架构
为了想知道为什么i++ ++i不是原子操作
编写两个程序,调用objdump -d
对比汇编代码
int main()
{
int i = 0;
int a = i++;
return 0;
}
00000000004005dc :
4005dc: 55 push %rbp
4005dd: 48 89 e5 mov %rsp,%rbp
4005e0: c7 45 f8 00 00 00 00 movl $0x0,-0x8(%rbp)
4005e7: 8b 45 f8 mov -0x8(%rbp),%eax
4005ea: 89 45 fc mov %eax,-0x4(%rbp)
4005ed: 83 45 f8 01 addl $0x1,-0x8(%rbp)
4005f1: b8 00 00 00 00 mov $0x0,%eax
4005f6: c9 leaveq
4005f7: c3 retq
int main()
{
int i = 0;
int a = ++i;
return 0;
}
00000000004005dc :
4005dc: 55 push %rbp
4005dd: 48 89 e5 mov %rsp,%rbp
4005e0: c7 45 f8 00 00 00 00 movl $0x0,-0x8(%rbp)
4005e7: 83 45 f8 01 addl $0x1,-0x8(%rbp)
4005eb: 8b 45 f8 mov -0x8(%rbp),%eax
4005ee: 89 45 fc mov %eax,-0x4(%rbp)
4005f1: b8 00 00 00 00 mov $0x0,%eax
4005f6: c9 leaveq
4005f7: c3 retq
两坨汇编的不同之处:
4005e7: 8b 45 f8 mov -0x8(%rbp),%eax
4005ea: 89 45 fc mov %eax,-0x4(%rbp)
4005ed: 83 45 f8 01 addl $0x1,-0x8(%rbp)
4005e7: 83 45 f8 01 addl $0x1,-0x8(%rbp)
4005eb: 8b 45 f8 mov -0x8(%rbp),%eax
4005ee: 89 45 fc mov %eax,-0x4(%rbp)
of course 我们知道:
所以addl一个在前一个在后
另外,一行i++汇编出这么多条指令导致i++必然不是原子
当然不能这么简单的放过这段代码,前面后面那一坨子东西是干嘛的?
(以下都是个人理解,诸君请自便)
要解释以上几个疑点就不得不研究一下栈结构及函数调用了
在上面的例子中,调用的函数是main,比较特殊,我们先把它当成一个普通被调用的函数。
我们看一下进程内存布局
栈是由高地址向低地址增长
栈帧就是rbp到rsp之间的内存块,rbp的地址是高于rsp的
一个栈帧对应c代码中的一个函数
int func()
{
...code...
}
现在来看一下我们的main函数的栈布局
最开始的时候main的栈里是空的,
我们先执行push %rbp
,于是栈变成了这样
内容 | 地址 |
---|---|
上一帧的rbp | rsp |
之后mov %rsp,%rbp
,于是rbp由原来的栈底跳到了栈顶变成了新的栈底
内容 | 地址 |
---|---|
上一帧的rbp | rsp rbp |
movl $0x0,-0x8(%rbp)
通过这句话和源码的对照
int i = 0;
我们确定i处在rbp-8处
类似推理,a处在rbp-4处
于是栈应该是这样的
内容 | 地址 |
---|---|
上一帧的rbp | rbp rsp |
int a | rbp-4 |
int i | rbp-8 |
但是rsp没变啊,为什么呢,因为这个函数调用是叶子节点,没必要把rsp移动了
这个分析的不太爽,我们再来一个
int test(int a)
{
return 0;
}
int main()
{
int a = 1;
test(9);
return 0;
}
00000000004005dc <_Z4testi>:
4005dc: 55 push %rbp
4005dd: 48 89 e5 mov %rsp,%rbp
4005e0: 89 7d fc mov %edi,-0x4(%rbp)
4005e3: b8 00 00 00 00 mov $0x0,%eax
4005e8: c9 leaveq
4005e9: c3 retq
00000000004005ea :
4005ea: 55 push %rbp
4005eb: 48 89 e5 mov %rsp,%rbp
4005ee: 48 83 ec 10 sub $0x10,%rsp
4005f2: c7 45 fc 01 00 00 00 movl $0x1,-0x4(%rbp)
4005f9: bf 09 00 00 00 mov $0x9,%edi
4005fe: e8 d9 ff ff ff callq 4005dc <_Z4testi>
400603: b8 00 00 00 00 mov $0x0,%eax
400608: c9 leaveq
400609: c3 retq
因为有函数调用,所以这是必须为a分配空间
int a = 0;
sub $0x10,%rsp
push %rbp
,于是栈变成了这样内容 | 地址 |
---|---|
上一帧的rbp | rsp |
- 之后mov %rsp,%rbp
,于是rbp由原来的栈底跳到了栈顶变成了新的栈底
内容 | 地址 |
---|---|
上一帧的rbp | rsp rbp |
- 之后sub $0x10,%rsp
,于是rsp下移16字节(有没有感觉很奇怪,为啥移动16字节呢,a只有4字节啊,哦,对了,64位cpu,这就是传说中的字节对齐吧)
内容 | 地址 |
---|---|
上一帧的rbp | rbp |
int a | |
12字节 | rsp |
- movl $0x0,-0x4(%rbp)
,a被赋值为0
内容 | 地址 |
---|---|
上一帧的rbp | rbp |
int a | |
12字节 | rsp |
- mov $0x9,%edi
, 把立即数9传入寄存器edi,作为test的调用参数
内容 | 地址 |
---|---|
上一帧的rbp | rbp |
int a | |
12字节 | rsp |
- callq 4005dc <_Z4testi>
,把返回地址压入栈顶,把指令指针rip跳转到test的开头处
内容 | 地址 |
---|---|
上一帧的rbp | rbp |
int a | |
12字节 | |
0x400603(mov $0x0,%eax) | rsp |
- test 中的push %rbp
内容 | 地址 |
---|---|
上一帧的rbp | |
int a | |
12字节 | |
0x400603(mov $0x0,%eax) | rsp |
上一帧的rbp | rbp |
- test 中的mov %rsp,%rbp
内容 | 地址 |
---|---|
上一帧的rbp | |
int a | |
12字节 | |
0x400603(mov $0x0,%eax) | |
上一帧的rbp | rbp rsp |
假设test不是叶子节点,那么rsp应该还得往下移动
内容 | 地址 |
---|---|
上一帧的rbp | |
int a | |
12字节 | |
0x400603(mov $0x0,%eax) | |
上一帧的rbp | rbp |
data | |
data | rsp |
leaveq
rsp跳转到rbp 然后出栈值放入 rbp内容 | 地址 |
---|---|
上一帧的rbp | rbp |
int a | |
12字节 | |
0x400603(mov $0x0,%eax) | rsp |
- retq
出栈,将出栈的值 0x400603(mov $0x0,%eax) 赋值给 rip
内容 | 地址 |
---|---|
上一帧的rbp | rbp |
int a | |
12字节 |
这样我们就恢复了在main中的栈,并且从0x400603(mov $0x0,%eax)继续执行
[email protected]:~/projects/test$ gdb a.out
GNU gdb (GDB) SUSE (7.0-0.4.16)
Copyright (C) 2009 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-suse-linux".
For bug reporting instructions, please see:
...
Reading symbols from /data/home/wangli/projects/test/a.out...done.
(gdb) display /i $pc
(gdb) b main
Breakpoint 1 at 0x4005ee
(gdb) r
Starting program: /data/home/wangli/projects/test/a.out
Failed to read a valid object file image from memory.
Missing separate debuginfo for /usr/lib64/libstdc++.so.6
Try: zypper install -C "debuginfo(build-id)=e907b88d15f5e1312d1ae0c7c61f8da92745738b"
Missing separate debuginfo for /lib64/libgcc_s.so.1
Try: zypper install -C "debuginfo(build-id)=3f06bcfc74f9b01780d68e89b8dce403bef9b2e3"
[Thread debugging using libthread_db enabled]
Breakpoint 1, 0x00000000004005ee in main ()
1: x/i $pc
0x4005ee : sub $0x10,%rsp
(gdb) info registers rip rdi rsp rbp
rip 0x4005ee 0x4005ee
rdi 0x1 1
rsp 0x7fffffffe370 0x7fffffffe370
rbp 0x7fffffffe370 0x7fffffffe370
(gdb) si
0x00000000004005f2 in main ()
1: x/i $pc
0x4005f2 : movl $0x1,-0x4(%rbp)
(gdb) info registers rip rdi rsp rbp
rip 0x4005f2 0x4005f2
rdi 0x1 1
rsp 0x7fffffffe360 0x7fffffffe360
rbp 0x7fffffffe370 0x7fffffffe370
(gdb) si
0x00000000004005f9 in main ()
1: x/i $pc
0x4005f9 : mov $0x9,%edi
(gdb) info registers rip rdi rsp rbp
rip 0x4005f9 0x4005f9
rdi 0x1 1
rsp 0x7fffffffe360 0x7fffffffe360
rbp 0x7fffffffe370 0x7fffffffe370
(gdb) si
0x00000000004005fe in main ()
1: x/i $pc
0x4005fe : callq 0x4005dc <_Z4testi>
(gdb) info registers rip rdi rsp rbp
rip 0x4005fe 0x4005fe
rdi 0x9 9
rsp 0x7fffffffe360 0x7fffffffe360
rbp 0x7fffffffe370 0x7fffffffe370
(gdb) si
0x00000000004005dc in test(int) ()
1: x/i $pc
0x4005dc <_Z4testi>: push %rbp
(gdb) info registers rip rdi rsp rbp
rip 0x4005dc 0x4005dc
rdi 0x9 9
rsp 0x7fffffffe358 0x7fffffffe358
rbp 0x7fffffffe370 0x7fffffffe370
(gdb) x/3xg 0x7fffffffe358
0x7fffffffe358: 0x0000000000400603 0x00007fffffffe440
0x7fffffffe368: 0x0000000100000000
(gdb) si
0x00000000004005dd in test(int) ()
1: x/i $pc
0x4005dd <_Z4testi+1>: mov %rsp,%rbp
(gdb) info registers rip rdi rsp rbp
rip 0x4005dd 0x4005dd
rdi 0x9 9
rsp 0x7fffffffe350 0x7fffffffe350
rbp 0x7fffffffe370 0x7fffffffe370
(gdb) x/3xg 0x7fffffffe350
0x7fffffffe350: 0x00007fffffffe370 0x0000000000400603
0x7fffffffe360: 0x00007fffffffe440
(gdb) si
0x00000000004005e0 in test(int) ()
1: x/i $pc
0x4005e0 <_Z4testi+4>: mov %edi,-0x4(%rbp)
(gdb) info registers rip rdi rsp rbp
rip 0x4005e0 0x4005e0
rdi 0x9 9
rsp 0x7fffffffe350 0x7fffffffe350
rbp 0x7fffffffe350 0x7fffffffe350
(gdb) si
0x00000000004005e3 in test(int) ()
1: x/i $pc
0x4005e3 <_Z4testi+7>: mov $0x0,%eax
(gdb) si
0x00000000004005e8 in test(int) ()
1: x/i $pc
0x4005e8 <_Z4testi+12>: leaveq
(gdb) info registers rip rdi rsp rbp
rip 0x4005e8 0x4005e8
rdi 0x9 9
rsp 0x7fffffffe350 0x7fffffffe350
rbp 0x7fffffffe350 0x7fffffffe350
(gdb) si
0x00000000004005e9 in test(int) ()
1: x/i $pc
0x4005e9 <_Z4testi+13>: retq
(gdb) info registers rip rdi rsp rbp
rip 0x4005e9 0x4005e9
rdi 0x9 9
rsp 0x7fffffffe358 0x7fffffffe358
rbp 0x7fffffffe370 0x7fffffffe370
(gdb) si
0x0000000000400603 in main ()
1: x/i $pc
0x400603 : mov $0x0,%eax
(gdb) info registers rip rdi rsp rbp
rip 0x400603 0x400603
rdi 0x9 9
rsp 0x7fffffffe360 0x7fffffffe360
rbp 0x7fffffffe370 0x7fffffffe370
(gdb)