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 p_w_picpath 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>


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章