李望 原创作品转载请注明出处 《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 
: 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 
: 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,比较特殊,我们先把它当成一个普通被调用的函数。

我们看一下进程内存布局

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

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

栈帧

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

找一张32位的图
李望 原创作品转载请注明出处 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-10000290_第2张图片

一个栈帧对应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
  • 最开始的时候main的栈里是空的,
    我们先执行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)继续执行

附:gdb调试过程

[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) 

你可能感兴趣的:(汇编)