李望 原创作品转载请注明出处 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-10000290

一次汇编分析的经历

关键词(为了搜索引擎优化,为了点击量)

寄存器ia32AT&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   %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  
程序2
int main()
{
    int i = 0;
    int a = ++i;
    return 0;
}
程序2的汇编代码
00000000004005dc <main>:
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 我们知道:

  1. i++ 是先取值再自增
  2. ++i 是先自增再取值

所以addl一个在前一个在后

另外,一行i++汇编出这么多条指令导致i++必然不是原子

细节分析

当然不能这么简单的放过这段代码,前面后面那一坨子东西是干嘛的?

疑点
  1. rbp rsp eax是什么
  2. movl是什么
  3. $0x0是什么
  4. -0x8(%rbp)是什么
  5. push %rbp是干嘛
释疑

(以下都是个人理解,诸君请自便)

  1. gcc使用at&t汇编 通用寄存器寄存器rbp rsp eax (64位)
    • rsp 相当于32位cpu中的 esp(Stack Pointer) 一般存放栈顶指针
    • rbp 相当于32位cpu中的 rbp(Base Pointer) 一般存放栈帧的基址
    • eax 累加寄存器
  2. at&t mov的语法是 mov 源地 目的,和intel汇编正好相反,movl的l是指定long。如果是mov,会根据前后操作数判断要mov的长度。(不权威,具体细节请自行google)
  3. $+数字 是取立即数
  4. -0x8(%rbp)表示rbp中存放的地址再减8字节
栈的结构及函数调用

要解释以上几个疑点就不得不研究一下栈结构及函数调用了

在上面的例子中,调用的函数是main,比较特殊,我们先把它当成一个普通被调用的函数。

我们看一下进程内存布局

这里写图片描述

栈是由高地址向低地址增长

栈帧

栈帧就是rbp到rsp之间的内存块,rbp的地址是高于rsp的

找一张32位的图
这里写图片描述

一个栈帧对应c代码中的一个函数

int func()
{
    ...code...
}

现在来看一下我们的main函数的栈布局

最开始的时候main的栈里是空的,
我们先执行push %rbp,于是栈变成了这样

内容地址
上一帧的rbprsp

之后mov %rsp,%rbp,于是rbp由原来的栈底跳到了栈顶变成了新的栈底

内容地址
上一帧的rbprsp rbp

movl $0x0,-0x8(%rbp)
通过这句话和源码的对照

int i = 0;

我们确定i处在rbp-8处

类似推理,a处在rbp-4处

于是栈应该是这样的

内容地址
上一帧的rbprbp rsp
int arbp-4
int irbp-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 <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,于是栈变成了这样
内容地址
上一帧的rbprsp

- 之后mov %rsp,%rbp,于是rbp由原来的栈底跳到了栈顶变成了新的栈底

内容地址
上一帧的rbprsp rbp

- 之后sub $0x10,%rsp,于是rsp下移16字节(有没有感觉很奇怪,为啥移动16字节呢,a只有4字节啊,哦,对了,64位cpu,这就是传说中的字节对齐吧)

内容地址
上一帧的rbprbp
int a
12字节rsp

- movl $0x0,-0x4(%rbp),a被赋值为0

内容地址
上一帧的rbprbp
int a
12字节rsp

- mov $0x9,%edi, 把立即数9传入寄存器edi,作为test的调用参数

内容地址
上一帧的rbprbp
int a
12字节rsp

- callq 4005dc <_Z4testi>,把返回地址压入栈顶,把指令指针rip跳转到test的开头处

内容地址
上一帧的rbprbp
int a
12字节
0x400603(mov $0x0,%eax)rsp

- test 中的push %rbp

内容地址
上一帧的rbp
int a
12字节
0x400603(mov $0x0,%eax)rsp
上一帧的rbprbp

- test 中的mov %rsp,%rbp

内容地址
上一帧的rbp
int a
12字节
0x400603(mov $0x0,%eax)
上一帧的rbprbp rsp

假设test不是叶子节点,那么rsp应该还得往下移动

内容地址
上一帧的rbp
int a
12字节
0x400603(mov $0x0,%eax)
上一帧的rbprbp
data
datarsp
  • 我们直接跳到leaveq rsp跳转到rbp 然后出栈值放入 rbp
内容地址
上一帧的rbprbp
int a
12字节
0x400603(mov $0x0,%eax)rsp

- retq 出栈,将出栈的值 0x400603(mov $0x0,%eax) 赋值给 rip

内容地址
上一帧的rbprbp
int a
12字节

这样我们就恢复了在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 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 <main+4>:  sub    $0x10,%rsp
(gdb) info registers rip rdi rsp rbp
rip            0x4005ee 0x4005ee <main+4>
rdi            0x1  1
rsp            0x7fffffffe370   0x7fffffffe370
rbp            0x7fffffffe370   0x7fffffffe370
(gdb) si
0x00000000004005f2 in main ()
1: x/i $pc
0x4005f2 <main+8>:  movl   $0x1,-0x4(%rbp)
(gdb) info registers rip rdi rsp rbp
rip            0x4005f2 0x4005f2 <main+8>
rdi            0x1  1
rsp            0x7fffffffe360   0x7fffffffe360
rbp            0x7fffffffe370   0x7fffffffe370
(gdb) si
0x00000000004005f9 in main ()
1: x/i $pc
0x4005f9 <main+15>: mov    $0x9,%edi
(gdb) info registers rip rdi rsp rbp
rip            0x4005f9 0x4005f9 <main+15>
rdi            0x1  1
rsp            0x7fffffffe360   0x7fffffffe360
rbp            0x7fffffffe370   0x7fffffffe370
(gdb) si
0x00000000004005fe in main ()
1: x/i $pc
0x4005fe <main+20>: callq  0x4005dc <_Z4testi>
(gdb) info registers rip rdi rsp rbp
rip            0x4005fe 0x4005fe <main+20>
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 <test(int)>
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 <test(int)+1>
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 <test(int)+4>
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 <test(int)+12>
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 <test(int)+13>
rdi            0x9  9
rsp            0x7fffffffe358   0x7fffffffe358
rbp            0x7fffffffe370   0x7fffffffe370
(gdb) si
0x0000000000400603 in main ()
1: x/i $pc
0x400603 <main+25>: mov    $0x0,%eax
(gdb) info registers rip rdi rsp rbp
rip            0x400603 0x400603 <main+25>
rdi            0x9  9
rsp            0x7fffffffe360   0x7fffffffe360
rbp            0x7fffffffe370   0x7fffffffe370
(gdb) 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值