a

<h1>一次汇编分析的经历</h1>

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

<p><strong>寄存器</strong>、<strong>ia32</strong>、<strong>AT&amp;T汇编</strong>、<strong>intel汇编</strong>、<strong>x86架构</strong>、<strong>x64架构</strong></p>

<h3>背景</h3>

<p>为了想知道为什么i++ ++i不是原子操作</p>

<h3>测试方法</h3>

<p>编写两个程序,调用<code>objdump -d</code>对比汇编代码</p>

<h4>程序1</h4>

<pre><code>int main()

{

    int i = 0;

    int a = i++;

    return 0;

}

</code></pre>

<h4>程序1的汇编代码</h4>

<pre><code>00000000004005dc &lt;main&gt;:

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  

</code></pre>

<h4>程序2</h4>

<pre><code>int main()

{

    int i = 0;

    int a = ++i;

    return 0;

}

</code></pre>

<h4>程序2的汇编代码</h4>

<pre><code>00000000004005dc &lt;main&gt;:

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

</code></pre>

<p>两坨汇编的不同之处:</p>

<pre><code>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)

</code></pre>

<p>of course 我们知道:</p>

<ol>

<li>i++ 是先取值再自增</li>

<li>++i 是先自增再取值</li>

</ol>

<p>所以addl一个在前一个在后</p>

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

<h2>细节分析</h2>

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

<h4>疑点</h4>

<ol>

<li>rbp rsp eax是什么</li>

<li>movl是什么</li>

<li>$0x0是什么</li>

<li>-0x8(%rbp)是什么</li>

<li>push %rbp是干嘛</li>

</ol>

<h4>释疑</h4>

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

<ol>

<li>gcc使用at&amp;t汇编 通用寄存器寄存器rbp rsp eax (64位)

<ul>

<li>rsp 相当于32位cpu中的 esp(Stack Pointer) 一般存放栈顶指针</li>

<li>rbp 相当于32位cpu中的 rbp(Base Pointer) 一般存放栈帧的基址</li>

<li>eax 累加寄存器</li>

</ul>

</li>

<li>at&amp;t mov的语法是 mov 源地 目的,和intel汇编正好相反,movl的l是指定long。如果是mov,会根据前后操作数判断要mov的长度。(不权威,具体细节请自行google)</li>

<li>$+数字 是取立即数</li>

<li>-0x8(%rbp)表示rbp中存放的地址再减8字节</li>

</ol>

<h4>栈的结构及函数调用</h4>

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

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

<p>我们看一下进程内存布局</p>

<p><img src="http://s2.sinaimg.cn/middle/67e1c5cc4ae9ad71b3f11&amp;690&amp;690" alt="Mou icon" /></p>

<p>栈是由高地址向低地址增长</p>

<h6>栈帧</h6>

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

<p>找一张32位的图</p>

<p><img src="http://s10.sinaimg.cn/mw690/b5205e1dgx6CotFq37H09&amp;690" alt="Mou icon" /></p>

<p>一个栈帧对应c代码中的一个函数</p>

<pre><code>int func()

{

    ...code...

}

</code></pre>

<p>现在来看一下我们的main函数的栈布局</p>

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

<table border="1">

<thead>

<tr>

<th>内容</th>

<th>地址</th>

</tr>

</thead>

<tbody>

<tr>

<td>上一帧的rbp</td>

<td>rsp</td>

</tr>

</tbody>

</table>

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

<table border="1">

<thead>

<tr>

<th>内容</th>

<th>地址</th>

</tr>

</thead>

<tbody>

<tr>

<td>上一帧的rbp</td>

<td>rsp rbp</td>

</tr>

</tbody>

</table>

<pre><code>movl   $0x0,-0x8(%rbp)

</code></pre>

<p>通过这句话和源码的对照</p>

<pre><code>int i = 0;

</code></pre>

<p>我们确定i处在rbp-8处</p>

<p>类似推理,a处在rbp-4处</p>

<p>于是栈应该是这样的</p>

<table border="1">

<thead>

<tr>

<th>内容</th>

<th>地址</th>

</tr>

</thead>

<tbody>

<tr>

<td>上一帧的rbp</td>

<td>rbp rsp</td>

</tr>

<tr>

<td>int a</td>

<td>rbp-4</td>

</tr>

<tr>

<td>int i</td>

<td>rbp-8</td>

</tr>

</tbody>

</table>

<p>但是rsp没变啊,为什么呢,因为这个函数调用是叶子节点,没必要把rsp移动了</p>

<p>这个分析的不太爽,我们再来一个</p>

<h2>另一个例子</h2>

<h4>代码</h4>

<pre><code>int test(int a)

{

    return 0;

}

int main()

{

    int a = 1;

    test(9);

    return 0;

}

</code></pre>

<h4>汇编</h4>

<pre><code>00000000004005dc &lt;_Z4testi&gt;:

 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 &lt;main&gt;:

 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 &lt;_Z4testi&gt;

 400603:    b8 00 00 00 00          mov    $0x0,%eax

 400608:    c9                      leaveq 

 400609:    c3                      retq 

</code></pre>

<p>因为有函数调用,所以这是必须为a分配空间</p>

<pre><code>int a = 0;

sub    $0x10,%rsp

</code></pre>

<ul>

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

</ul>

<table border="1">

<thead>

<tr>

<th>内容</th>

<th>地址</th>

</tr>

</thead>

<tbody>

<tr>

<td>上一帧的rbp</td>

<td>rsp</td>

</tr>

</tbody>

</table>

<ul>

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

</ul>

<table border="1">

<thead>

<tr>

<th>内容</th>

<th>地址</th>

</tr>

</thead>

<tbody>

<tr>

<td>上一帧的rbp</td>

<td>rsp rbp</td>

</tr>

</tbody>

</table>

<ul>

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

</ul>

<table border="1">

<thead>

<tr>

<th>内容</th>

<th>地址</th>

</tr>

</thead>

<tbody>

<tr>

<td>上一帧的rbp</td>

<td>rbp</td>

</tr>

<tr>

<td>int a</td>

<td></td>

</tr>

<tr>

<td>12字节</td>

<td>rsp</td>

</tr>

</tbody>

</table>

<ul>

<li><code>movl $0x0,-0x4(%rbp)</code>,a被赋值为0</li>

</ul>

<table border="1">

<thead>

<tr>

<th>内容</th>

<th>地址</th>

</tr>

</thead>

<tbody>

<tr>

<td>上一帧的rbp</td>

<td>rbp</td>

</tr>

<tr>

<td>int a</td>

<td></td>

</tr>

<tr>

<td>12字节</td>

<td>rsp</td>

</tr>

</tbody>

</table>

<ul>

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

</ul>

<table border="1">

<thead>

<tr>

<th>内容</th>

<th>地址</th>

</tr>

</thead>

<tbody>

<tr>

<td>上一帧的rbp</td>

<td>rbp</td>

</tr>

<tr>

<td>int a</td>

<td></td>

</tr>

<tr>

<td>12字节</td>

<td>rsp</td>

</tr>

</tbody>

</table>

<ul>

<li><code>callq 4005dc &lt;_Z4testi&gt;</code>,把返回地址压入栈顶,把指令指针rip跳转到test的开头处</li>

</ul>

<table border="1">

<thead>

<tr>

<th>内容</th>

<th>地址</th>

</tr>

</thead>

<tbody>

<tr>

<td>上一帧的rbp</td>

<td>rbp</td>

</tr>

<tr>

<td>int a</td>

<td></td>

</tr>

<tr>

<td>12字节</td>

<td></td>

</tr>

<tr>

<td>0x400603(mov $0x0,%eax)</td>

<td>rsp</td>

</tr>

</tbody>

</table>

<ul>

<li>test 中的<code>push %rbp</code></li>

</ul>

<table border="1">

<thead>

<tr>

<th>内容</th>

<th>地址</th>

</tr>

</thead>

<tbody>

<tr>

<td>上一帧的rbp</td>

<td></td>

</tr>

<tr>

<td>int a</td>

<td></td>

</tr>

<tr>

<td>12字节</td>

<td></td>

</tr>

<tr>

<td>0x400603(mov $0x0,%eax)</td>

<td>rsp</td>

</tr>

<tr>

<td>上一帧的rbp</td>

<td>rbp</td>

</tr>

</tbody>

</table>

<ul>

<li>test 中的<code>mov %rsp,%rbp</code></li>

</ul>

<table border="1">

<thead>

<tr>

<th>内容</th>

<th>地址</th>

</tr>

</thead>

<tbody>

<tr>

<td>上一帧的rbp</td>

<td></td>

</tr>

<tr>

<td>int a</td>

<td></td>

</tr>

<tr>

<td>12字节</td>

<td></td>

</tr>

<tr>

<td>0x400603(mov $0x0,%eax)</td>

<td></td>

</tr>

<tr>

<td>上一帧的rbp</td>

<td>rbp rsp</td>

</tr>

</tbody>

</table>

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

<table border="1">

<thead>

<tr>

<th>内容</th>

<th>地址</th>

</tr>

</thead>

<tbody>

<tr>

<td>上一帧的rbp</td>

<td></td>

</tr>

<tr>

<td>int a</td>

<td></td>

</tr>

<tr>

<td>12字节</td>

<td></td>

</tr>

<tr>

<td>0x400603(mov $0x0,%eax)</td>

<td></td>

</tr>

<tr>

<td>上一帧的rbp</td>

<td>rbp</td>

</tr>

<tr>

<td>data</td>

<td></td>

</tr>

<tr>

<td>data</td>

<td>rsp</td>

</tr>

</tbody>

</table>

<ul>

<li>我们直接跳到<code>leaveq</code> rsp跳转到rbp 然后出栈值放入 rbp</li>

</ul>

<table border="1">

<thead>

<tr>

<th>内容</th>

<th>地址</th>

</tr>

</thead>

<tbody>

<tr>

<td>上一帧的rbp</td>

<td>rbp</td>

</tr>

<tr>

<td>int a</td>

<td></td>

</tr>

<tr>

<td>12字节</td>

<td></td>

</tr>

<tr>

<td>0x400603(mov $0x0,%eax)</td>

<td>rsp</td>

</tr>

</tbody>

</table>

<ul>

<li><code>retq</code> 出栈,将出栈的值 0x400603(mov $0x0,%eax) 赋值给 rip</li>

</ul>

<table border="1">

<thead>

<tr>

<th>内容</th>

<th>地址</th>

</tr>

</thead>

<tbody>

<tr>

<td>上一帧的rbp</td>

<td>rbp</td>

</tr>

<tr>

<td>int a</td>

<td></td>

</tr>

<tr>

<td>12字节</td>

<td></td>

</tr>

</tbody>

</table>

<p>这样我们就恢复了在main中的栈,并且从0x400603(mov $0x0,%eax)继续执行</p>

<h4>附:gdb调试过程</h4>

<pre><code>[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 &lt;http://gnu.org/licenses/gpl.html&gt;

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:

&lt;http://www.gnu.org/software/gdb/bugs/&gt;...

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 &lt;main+4&gt;:  sub    $0x10,%rsp

(gdb) info registers rip rdi rsp rbp

rip            0x4005ee 0x4005ee &lt;main+4&gt;

rdi            0x1  1

rsp            0x7fffffffe370   0x7fffffffe370

rbp            0x7fffffffe370   0x7fffffffe370

(gdb) si

0x00000000004005f2 in main ()

1: x/i $pc

0x4005f2 &lt;main+8&gt;:  movl   $0x1,-0x4(%rbp)

(gdb) info registers rip rdi rsp rbp

rip            0x4005f2 0x4005f2 &lt;main+8&gt;

rdi            0x1  1

rsp            0x7fffffffe360   0x7fffffffe360

rbp            0x7fffffffe370   0x7fffffffe370

(gdb) si

0x00000000004005f9 in main ()

1: x/i $pc

0x4005f9 &lt;main+15&gt;: mov    $0x9,%edi

(gdb) info registers rip rdi rsp rbp

rip            0x4005f9 0x4005f9 &lt;main+15&gt;

rdi            0x1  1

rsp            0x7fffffffe360   0x7fffffffe360

rbp            0x7fffffffe370   0x7fffffffe370

(gdb) si

0x00000000004005fe in main ()

1: x/i $pc

0x4005fe &lt;main+20&gt;: callq  0x4005dc &lt;_Z4testi&gt;

(gdb) info registers rip rdi rsp rbp

rip            0x4005fe 0x4005fe &lt;main+20&gt;

rdi            0x9  9

rsp            0x7fffffffe360   0x7fffffffe360

rbp            0x7fffffffe370   0x7fffffffe370

(gdb) si

0x00000000004005dc in test(int) ()

1: x/i $pc

0x4005dc &lt;_Z4testi&gt;:    push   %rbp

(gdb) info registers rip rdi rsp rbp

rip            0x4005dc 0x4005dc &lt;test(int)&gt;

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 &lt;_Z4testi+1&gt;:  mov    %rsp,%rbp

(gdb) info registers rip rdi rsp rbp

rip            0x4005dd 0x4005dd &lt;test(int)+1&gt;

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 &lt;_Z4testi+4&gt;:  mov    %edi,-0x4(%rbp)

(gdb) info registers rip rdi rsp rbp

rip            0x4005e0 0x4005e0 &lt;test(int)+4&gt;

rdi            0x9  9

rsp            0x7fffffffe350   0x7fffffffe350

rbp            0x7fffffffe350   0x7fffffffe350

(gdb) si

0x00000000004005e3 in test(int) ()

1: x/i $pc

0x4005e3 &lt;_Z4testi+7&gt;:  mov    $0x0,%eax

(gdb) si

0x00000000004005e8 in test(int) ()

1: x/i $pc

0x4005e8 &lt;_Z4testi+12&gt;: leaveq 

(gdb) info registers rip rdi rsp rbp

rip            0x4005e8 0x4005e8 &lt;test(int)+12&gt;

rdi            0x9  9

rsp            0x7fffffffe350   0x7fffffffe350

rbp            0x7fffffffe350   0x7fffffffe350

(gdb) si

0x00000000004005e9 in test(int) ()

1: x/i $pc

0x4005e9 &lt;_Z4testi+13&gt;: retq   

(gdb) info registers rip rdi rsp rbp

rip            0x4005e9 0x4005e9 &lt;test(int)+13&gt;

rdi            0x9  9

rsp            0x7fffffffe358   0x7fffffffe358

rbp            0x7fffffffe370   0x7fffffffe370

(gdb) si

0x0000000000400603 in main ()

1: x/i $pc

0x400603 &lt;main+25&gt;: mov    $0x0,%eax

(gdb) info registers rip rdi rsp rbp

rip            0x400603 0x400603 &lt;main+25&gt;

rdi            0x9  9

rsp            0x7fffffffe360   0x7fffffffe360

rbp            0x7fffffffe370   0x7fffffffe370

(gdb) 

</code></pre>


你可能感兴趣的:(a)