李望 原创作品转载请注明出处 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-10000290
来源:互联网 发布:本科毕业论文 知乎 编辑:程序博客网 时间:2024/05/18 15:28
一次汇编分析的经历
关键词(为了搜索引擎优化,为了点击量)
寄存器、ia32、AT&T汇编、intel汇编、x86架构、x64架构
背景
为了想知道为什么i++ ++i不是原子操作
测试方法
编写两个程序,调用objdump -d
对比汇编代码
程序1
int main(){ int i = 0; int a = i++; return 0;}
程序1的汇编代码
00000000004005dc <main>:4005dc: 55 push %rbp4005dd: 48 89 e5 mov %rsp,%rbp4005e0: c7 45 f8 00 00 00 00 movl $0x0,-0x8(%rbp)4005e7: 8b 45 f8 mov -0x8(%rbp),%eax4005ea: 89 45 fc mov %eax,-0x4(%rbp)4005ed: 83 45 f8 01 addl $0x1,-0x8(%rbp)4005f1: b8 00 00 00 00 mov $0x0,%eax4005f6: c9 leaveq 4005f7: c3 retq
程序2
int main(){ int i = 0; int a = ++i; return 0;}
程序2的汇编代码
00000000004005dc <main>:4005dc: 55 push %rbp4005dd: 48 89 e5 mov %rsp,%rbp4005e0: 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),%eax4005ee: 89 45 fc mov %eax,-0x4(%rbp)4005f1: b8 00 00 00 00 mov $0x0,%eax4005f6: c9 leaveq 4005f7: c3 retq
两坨汇编的不同之处:
4005e7: 8b 45 f8 mov -0x8(%rbp),%eax4005ea: 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),%eax4005ee: 89 45 fc mov %eax,-0x4(%rbp)
of course 我们知道:
- i++ 是先取值再自增
- ++i 是先自增再取值
所以addl一个在前一个在后
另外,一行i++汇编出这么多条指令导致i++必然不是原子
细节分析
当然不能这么简单的放过这段代码,前面后面那一坨子东西是干嘛的?
疑点
- rbp rsp eax是什么
- movl是什么
- $0x0是什么
- -0x8(%rbp)是什么
- push %rbp是干嘛
释疑
(以下都是个人理解,诸君请自便)
- gcc使用at&t汇编 通用寄存器寄存器rbp rsp eax (64位)
- rsp 相当于32位cpu中的 esp(Stack Pointer) 一般存放栈顶指针
- rbp 相当于32位cpu中的 rbp(Base Pointer) 一般存放栈帧的基址
- eax 累加寄存器
- at&t mov的语法是 mov 源地 目的,和intel汇编正好相反,movl的l是指定long。如果是mov,会根据前后操作数判断要mov的长度。(不权威,具体细节请自行google)
- $+数字 是取立即数
- -0x8(%rbp)表示rbp中存放的地址再减8字节
栈的结构及函数调用
要解释以上几个疑点就不得不研究一下栈结构及函数调用了
在上面的例子中,调用的函数是main,比较特殊,我们先把它当成一个普通被调用的函数。
我们看一下进程内存布局
栈是由高地址向低地址增长
栈帧
栈帧就是rbp到rsp之间的内存块,rbp的地址是高于rsp的
找一张32位的图
一个栈帧对应c代码中的一个函数
int func(){ ...code...}
现在来看一下我们的main函数的栈布局
最开始的时候main的栈里是空的,
我们先执行push %rbp
,于是栈变成了这样
之后mov %rsp,%rbp
,于是rbp由原来的栈底跳到了栈顶变成了新的栈底
movl $0x0,-0x8(%rbp)
通过这句话和源码的对照
int i = 0;
我们确定i处在rbp-8处
类似推理,a处在rbp-4处
于是栈应该是这样的
但是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 <main>: 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
- 最开始的时候main的栈里是空的,
我们先执行push %rbp
,于是栈变成了这样
- 之后mov %rsp,%rbp
,于是rbp由原来的栈底跳到了栈顶变成了新的栈底
- 之后sub $0x10,%rsp
,于是rsp下移16字节(有没有感觉很奇怪,为啥移动16字节呢,a只有4字节啊,哦,对了,64位cpu,这就是传说中的字节对齐吧)
- movl $0x0,-0x4(%rbp)
,a被赋值为0
- mov $0x9,%edi
, 把立即数9传入寄存器edi,作为test的调用参数
- callq 4005dc <_Z4testi>
,把返回地址压入栈顶,把指令指针rip跳转到test的开头处
- test 中的push %rbp
- test 中的mov %rsp,%rbp
假设test不是叶子节点,那么rsp应该还得往下移动
- 我们直接跳到
leaveq
rsp跳转到rbp 然后出栈值放入 rbp
- retq
出栈,将出栈的值 0x400603(mov $0x0,%eax) 赋值给 rip
这样我们就恢复了在main中的栈,并且从0x400603(mov $0x0,%eax)继续执行
附:gdb调试过程
64bit_wangli@10.1.152.80:~/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 <http://gnu.org/licenses/gpl.html>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:<http://www.gnu.org/software/gdb/bugs/>...Reading symbols from /data/home/wangli/projects/test/a.out...done.(gdb) display /i $pc(gdb) b mainBreakpoint 1 at 0x4005ee(gdb) rStarting 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.6Try: zypper install -C "debuginfo(build-id)=e907b88d15f5e1312d1ae0c7c61f8da92745738b"Missing separate debuginfo for /lib64/libgcc_s.so.1Try: zypper install -C "debuginfo(build-id)=3f06bcfc74f9b01780d68e89b8dce403bef9b2e3"[Thread debugging using libthread_db enabled]Breakpoint 1, 0x00000000004005ee in main ()1: x/i $pc0x4005ee <main+4>: sub $0x10,%rsp(gdb) info registers rip rdi rsp rbprip 0x4005ee 0x4005ee <main+4>rdi 0x1 1rsp 0x7fffffffe370 0x7fffffffe370rbp 0x7fffffffe370 0x7fffffffe370(gdb) si0x00000000004005f2 in main ()1: x/i $pc0x4005f2 <main+8>: movl $0x1,-0x4(%rbp)(gdb) info registers rip rdi rsp rbprip 0x4005f2 0x4005f2 <main+8>rdi 0x1 1rsp 0x7fffffffe360 0x7fffffffe360rbp 0x7fffffffe370 0x7fffffffe370(gdb) si0x00000000004005f9 in main ()1: x/i $pc0x4005f9 <main+15>: mov $0x9,%edi(gdb) info registers rip rdi rsp rbprip 0x4005f9 0x4005f9 <main+15>rdi 0x1 1rsp 0x7fffffffe360 0x7fffffffe360rbp 0x7fffffffe370 0x7fffffffe370(gdb) si0x00000000004005fe in main ()1: x/i $pc0x4005fe <main+20>: callq 0x4005dc <_Z4testi>(gdb) info registers rip rdi rsp rbprip 0x4005fe 0x4005fe <main+20>rdi 0x9 9rsp 0x7fffffffe360 0x7fffffffe360rbp 0x7fffffffe370 0x7fffffffe370(gdb) si0x00000000004005dc in test(int) ()1: x/i $pc0x4005dc <_Z4testi>: push %rbp(gdb) info registers rip rdi rsp rbprip 0x4005dc 0x4005dc <test(int)>rdi 0x9 9rsp 0x7fffffffe358 0x7fffffffe358rbp 0x7fffffffe370 0x7fffffffe370(gdb) x/3xg 0x7fffffffe3580x7fffffffe358: 0x0000000000400603 0x00007fffffffe4400x7fffffffe368: 0x0000000100000000(gdb) si0x00000000004005dd in test(int) ()1: x/i $pc0x4005dd <_Z4testi+1>: mov %rsp,%rbp(gdb) info registers rip rdi rsp rbprip 0x4005dd 0x4005dd <test(int)+1>rdi 0x9 9rsp 0x7fffffffe350 0x7fffffffe350rbp 0x7fffffffe370 0x7fffffffe370(gdb) x/3xg 0x7fffffffe3500x7fffffffe350: 0x00007fffffffe370 0x00000000004006030x7fffffffe360: 0x00007fffffffe440(gdb) si0x00000000004005e0 in test(int) ()1: x/i $pc0x4005e0 <_Z4testi+4>: mov %edi,-0x4(%rbp)(gdb) info registers rip rdi rsp rbprip 0x4005e0 0x4005e0 <test(int)+4>rdi 0x9 9rsp 0x7fffffffe350 0x7fffffffe350rbp 0x7fffffffe350 0x7fffffffe350(gdb) si0x00000000004005e3 in test(int) ()1: x/i $pc0x4005e3 <_Z4testi+7>: mov $0x0,%eax(gdb) si0x00000000004005e8 in test(int) ()1: x/i $pc0x4005e8 <_Z4testi+12>: leaveq (gdb) info registers rip rdi rsp rbprip 0x4005e8 0x4005e8 <test(int)+12>rdi 0x9 9rsp 0x7fffffffe350 0x7fffffffe350rbp 0x7fffffffe350 0x7fffffffe350(gdb) si0x00000000004005e9 in test(int) ()1: x/i $pc0x4005e9 <_Z4testi+13>: retq (gdb) info registers rip rdi rsp rbprip 0x4005e9 0x4005e9 <test(int)+13>rdi 0x9 9rsp 0x7fffffffe358 0x7fffffffe358rbp 0x7fffffffe370 0x7fffffffe370(gdb) si0x0000000000400603 in main ()1: x/i $pc0x400603 <main+25>: mov $0x0,%eax(gdb) info registers rip rdi rsp rbprip 0x400603 0x400603 <main+25>rdi 0x9 9rsp 0x7fffffffe360 0x7fffffffe360rbp 0x7fffffffe370 0x7fffffffe370(gdb)
- 李望 原创作品转载请注明出处 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-10000290
- MOOC课程作业http://mooc.study.163.com/course/USTC-1000002006
- 新手lbw + 《软件工程(C编码实践篇)》MOOC课程作业http://mooc.study.163.com/course/USTC-1000002006
- 软件工程学习理解与心得《软件工程(C编码实践篇)》MOOC课程http://mooc.study.163.com/course/USTC-1000002006
- S3C2450自动升级[原创作品,转载请注明出处]
- S3C2450自动升级[原创作品,转载请注明出处]
- Linux内核分析MOOC课程汇编分析
- 《LINUX 内核分析》MOOC课程总结
- 尊重原创,转载请注明出处
- sas proc sql 基础入门 (原创作品,转载请注明出处 )
- 转载请注明出处
- MOOC课程"Linux内核分析"之作业一
- MOOC课程"Linux内核分析"之作业二
- 《Linux内核分析》MOOC课程第一次实验作业
- 《Linux内核分析》MOOC课程第二次实验作业
- 《Linux内核分析》MOOC课程第三次实验作业
- 《Linux内核分析》MOOC课程第四次实验作业
- 《Linux内核分析》MOOC课程第五次实验作业
- 动态规划(一)
- Cpp_函数缺省参数
- 常用开发功能的实现
- poj 1112 uva1627
- iOS-UI-基本控件之UIView
- 李望 原创作品转载请注明出处 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-10000290
- 1052. Linked List Sorting (25)
- android布局实践(二)login界面
- 决定QGraphiciItem图形项自身坐标系统零点的最关键的地方
- Cpp_内联函数
- HDOJ 饭卡
- sqlserver字段添加注释方法
- iOS-UI-基本控件之UISwitch
- zstu 2532 hdu 1466 计算直线的交点数