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

来源:互联网 发布:本科毕业论文 知乎 编辑:程序博客网 时间:2024/05/18 15:28

一次汇编分析的经历

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

寄存器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   %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 我们知道:

  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,于是栈变成了这样

内容 地址 上一帧的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 <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,于是栈变成了这样
内容 地址 上一帧的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调试过程

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) 
0 0
原创粉丝点击