小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

用gdb觀察函數(shù)調(diào)用過程中棧上的那些事兒

 昵稱15515903 2014-02-11

用gdb觀察函數(shù)調(diào)用過程中棧上的那些事兒

“參數(shù)從右到左入?!保熬植孔兞吭跅I戏峙淇臻g”,聽的耳朵都起繭子了。
最近做項(xiàng)目涉及C和匯編互相調(diào)用,寫代碼的時(shí)候才發(fā)現(xiàn)沒真正弄明白。
自己寫了個(gè)最簡單的函數(shù),用gdb跟蹤了調(diào)用過程,才多少懂了一點(diǎn)。

參考資料:(感謝liigo和eno_rez兩位作者)

http://blog.csdn.net/liigo/archive/2006/12/23/1456938.aspx

http://blog.csdn.net/eno_rez/archive/2008/03/08/2158682.aspx

  1. int add(int x, int y)
  2. {
  3.     int a = 0;
  4.     a = x;
  5.     a += y;
  6.     return a;
  7. }
  8. int main(int argc, char *argv[])
  9. {
  10.     int x, y, result;
  11.     x = 0×12;
  12.     y = 0×34;
  13.     result = add(x, y);
  14.     return 0;
  15. }

編譯:(Fedora6, gcc 4.1.2)
[test]$ gcc -g -Wall -o stack stack.c

反匯編:
這里的匯編的格式是AT&T匯編,它的格式和我們熟悉的匯編格式不太一樣,尤其要注意源操作數(shù)和目的操作數(shù)的順序是反過來的
[test]$ objdump -d stack > stack.dump
[test]$ cat stack.dump

……
08048354 <add>:
 8048354:       55                      push   %ebp  ;保存調(diào)用者的幀指針
 8048355:       89 e5                   mov    %esp,%ebp  ;把當(dāng)前的棧指針作為本函數(shù)的幀指針
 8048357:       83 ec 10                sub    $0×10,%esp  ;調(diào)整棧指針,為局部變量保留空間
 804835a:       c7 45 fc 00 00 00 00    movl   $0×0,0xfffffffc(%ebp)  ;把a(bǔ)置0。ebp-4的位置是第一個(gè)局部變量
 8048361:       8b 45 08                mov    0×8(%ebp),%eax  ;把參數(shù)x保存到eax。ebp+8的位置是最后一個(gè)入棧的參數(shù),也就是第一個(gè)參數(shù)
 8048364:       89 45 fc                mov    %eax,0xfffffffc(%ebp)  ;把eax賦值給變量a
 8048367:       8b 45 0c                mov    0xc(%ebp),%eax  ;把參數(shù)y保存到eax。ebp+C的位置是倒數(shù)第二個(gè)入棧的參數(shù),也就是第二個(gè)參數(shù)
 804836a:       01 45 fc                add    %eax,0xfffffffc(%ebp)  ;a+=y
 804836d:       8b 45 fc                mov    0xfffffffc(%ebp),%eax  ;把a(bǔ)的值作為返回值,保存到eax
 8048370:       c9                      leave 
 8048371:       c3                      ret   

08048372 <main>:
 8048372:       8d 4c 24 04             lea    0×4(%esp),%ecx  ;????
 8048376:       83 e4 f0                and    $0xfffffff0,%esp  ;把棧指針16字節(jié)對(duì)齊
 8048379:       ff 71 fc                pushl  0xfffffffc(%ecx)  ;????
 804837c:       55                      push   %ebp  ;保存調(diào)用者的幀指針
 804837d:       89 e5                   mov    %esp,%ebp  ;把當(dāng)前的棧指針作為本函數(shù)的幀指針
 804837f:       51                      push   %ecx  ;????
 8048380:       83 ec 18                sub    $0×18,%esp  ;調(diào)整棧指針,為局部變量保留空間
 8048383:       c7 45 f0 12 00 00 00    movl   $0×12,0xfffffff0(%ebp)  ;x=0×12。ebp-16是局部變量x
 804838a:       c7 45 f4 34 00 00 00    movl   $0×34,0xfffffff4(%ebp)  ;y=0×34。ebp-12是局部變量y
 8048391:       8b 45 f4                mov    0xfffffff4(%ebp),%eax  ;y保存到eax
 8048394:       89 44 24 04             mov    %eax,0×4(%esp)  ;y作為最右邊的參數(shù)首先入棧
 8048398:       8b 45 f0                mov    0xfffffff0(%ebp),%eax  ;x保存到eax
 804839b:       89 04 24                mov    %eax,(%esp)  ;x第二個(gè)入棧
 804839e:       e8 b1 ff ff ff          call   8048354 <add>  ;調(diào)用add
 80483a3:       89 45 f8                mov    %eax,0xfffffff8(%ebp)  ;把保存在eax的add的返回值,賦值給位于ebp-8的第三個(gè)局部變量result。注意這條指令的地址,就是add的返回地址
 80483a6:       b8 00 00 00 00          mov    $0×0,%eax  ;0作為main的返回值,保存到eax
 80483ab:       83 c4 18                add    $0×18,%esp  ;恢復(fù)棧指針,也就是討論stdcall和cdecl的時(shí)候總要提到的“調(diào)用者清?!?BR> 80483ae:       59                      pop    %ecx  ;
 80483af:       5d                      pop    %ebp  ;
 80483b0:       8d 61 fc                lea    0xfffffffc(%ecx),%esp  ;
 80483b3:       c3                      ret   
 80483b4:       90                      nop   
……

有一點(diǎn)值得注意的是main在調(diào)用add之前把參數(shù)壓棧的過程。
它用的不是push指令,而是另一種方法。
在main入口調(diào)整棧指針的時(shí)候,也就是位于8048380的這條指令 sub $0×18,%esp
不但象通常函數(shù)都要做的那樣給局部變量預(yù)留了空間,還順便把調(diào)用add的兩個(gè)參數(shù)的空間也預(yù)留出來了。
然后把參數(shù)壓棧的時(shí)候,用的是mov指令。
我不太明白這種方法有什么好處。
另外一個(gè)不明白的就是main入口的四條指令8048372、8048376、8048379、804837f,還有與之對(duì)應(yīng)的main返回之前的指令。
貌似main對(duì)esp要求16字節(jié)對(duì)齊,所以先把原來的esp壓棧,然后強(qiáng)行把esp的低4位清0。等到返回之前再從棧里恢復(fù)原來的esp

準(zhǔn)備工作都做好了,現(xiàn)在開始gdb
對(duì)gdb不太熟悉的同學(xué)要注意一點(diǎn),stepi命令執(zhí)行之后顯示出來的源代碼行或者指令地址,都是即將執(zhí)行的指令,而不是剛剛執(zhí)行完的指令。
我在每個(gè)stepi后面都加了注釋,就是剛執(zhí)行過的指令。

[test]$ gdb -q stack
(gdb) break main
Breakpoint 1 at 0×8048383: file stack.c, line 11.
gdb并沒有把斷點(diǎn)設(shè)置在main的第一條指令,而是設(shè)置在了調(diào)整棧指針為局部變量保留空間之后

(gdb) run
Starting program: /home/brookmill/test/stack
Breakpoint 1, main () at stack.c:11
11              x = 0×12;
(gdb) stepi    // 注釋: movl   $0×12,0xfffffff0(%ebp)
12              y = 0×34;
(gdb) stepi    // 注釋: movl   $0×34,0xfffffff4(%ebp)
13              result = add(x, y);
(gdb) info registers esp
esp            0xbf8df8ac       0xbf8df8ac
(gdb) info registers ebp
ebp            0xbf8df8c8       0xbf8df8c8
(gdb) x/12 0xbf8df8a0
0xbf8df8a0:     0×002daff4      0×002d9220      0xbf8df8d8      0×080483e9
0xbf8df8b0:     0×001ca8d5      0xbf8df96c      0×00000012      0×00000034
0xbf8df8c0:     0×001903d0      0xbf8df8e0      0xbf8df938      0×001b4dec

這就是傳說中的棧。在main準(zhǔn)備調(diào)用add之前,先看看這里有些什么東東
0xbf8df8c8(ebp)保存的是上一層函數(shù)的幀指針:0xbf8df938,距離這里有112字節(jié)
0xbf8df8cc(ebp+4)保存的是main的返回地址0×001b4dec
0xbf8df8b8(ebp-16)是局部變量x,已經(jīng)賦值0×12;
0xbf8df8bc(ebp-12)是局部變量y,已經(jīng)賦值0×34;
0xbf8df8c0(ebp-8)是局部變量result。值得注意的是,因?yàn)槲覀儧]有給result賦值,這里是一個(gè)不確定的值。局部變量如果不顯式的初始化,初始值不一定是0。

現(xiàn)在開始調(diào)用add
(gdb) stepi    // 注釋: mov    0xfffffff4(%ebp),%eax
0×08048394      13              result = add(x, y);
(gdb) stepi    // 注釋: mov    %eax,0×4(%esp)
0×08048398      13              result = add(x, y);
(gdb) x/12 0xbf8df8a0
0xbf8df8a0:     0×002daff4      0×002d9220      0xbf8df8d8      0×080483e9
0xbf8df8b0:     0×00000034      0xbf8df96c      0×00000012      0×00000034
0xbf8df8c0:     0×001903d0      0xbf8df8e0      0xbf8df938      0×001b4dec
y首先被壓棧,在0xbf8df8b0

(gdb) stepi    // 注釋: mov    0xfffffff0(%ebp),%eax
0×0804839b      13              result = add(x, y);
(gdb) stepi    // 注釋: mov    %eax,(%esp)
0×0804839e      13              result = add(x, y);
(gdb) x/12 0xbf8df8a0
0xbf8df8a0:     0×002daff4      0×002d9220      0xbf8df8d8      0×00000012
0xbf8df8b0:     0×00000034      0xbf8df96c      0×00000012      0×00000034
0xbf8df8c0:     0×001903d0      0xbf8df8e0      0xbf8df938      0×001b4dec
x第二個(gè)進(jìn)棧,在0xbf8df8ac

(gdb) stepi    // 注釋: call   8048354 <add>
add (x=18, y=52) at stack.c:2
2       {

剛剛執(zhí)行了call指令,現(xiàn)在我們進(jìn)入了add函數(shù)
(gdb) info registers esp
esp            0xbf8df8a8       0xbf8df8a8
(gdb) info registers ebp
ebp            0xbf8df8c8       0xbf8df8c8
(gdb) x/12 0xbf8df8a0
0xbf8df8a0:     0×002daff4      0×002d9220      0×080483a3      0×00000012
0xbf8df8b0:     0×00000034      0xbf8df96c      0×00000012      0×00000034
0xbf8df8c0:     0×001903d0      0xbf8df8e0      0xbf8df938      0×001b4dec
現(xiàn)在esp指向0xbf8df8a8,這里保存的是add函數(shù)的返回地址,它是由call指令壓棧的。

(gdb) stepi    // 注釋: push   %ebp
0×08048355      2       {
(gdb) stepi    // 注釋: mov    %esp,%ebp
0×08048357      2       {
(gdb) stepi    // 注釋: sub    $0×10,%esp
3               int a = 0;
(gdb) info registers esp
esp            0xbf8df894       0xbf8df894
(gdb) info registers ebp
ebp            0xbf8df8a4       0xbf8df8a4
(gdb) x/16 0xbf8df890
0xbf8df890:     0×00000000      0×08049574      0xbf8df8a8      0×08048245
0xbf8df8a0:     0×002daff4      0xbf8df8c8      0×080483a3      0×00000012
0xbf8df8b0:     0×00000034      0xbf8df96c      0×00000012      0×00000034
0xbf8df8c0:     0×001903d0      0xbf8df8e0      0xbf8df938      0×001b4dec
剛剛執(zhí)行完的3條指令是函數(shù)入口的定式。
現(xiàn)在我們可以看到,main的棧還是原樣,向下增長之后就是add的棧。
0xbf8df8a4(ebp)保存的是上層函數(shù)main的幀指針
0xbf8df8a8(ebp+4)保存的是返回地址
0xbf8df8ac(ebp+8)保存的是最后一個(gè)入棧的參數(shù)x
0xbf8df8b0(ebp+C)保存的是倒數(shù)第二個(gè)入棧的參數(shù)y
0xbf8df8a0(ebp-4)保存的是局部變量a,現(xiàn)在是一個(gè)不確定值

接下來add函數(shù)就真正開始干活了
(gdb) stepi    // 注釋: movl   $0×0,0xfffffffc(%ebp)
4               a = x;
(gdb) x/16 0xbf8df890
0xbf8df890:     0×00000000      0×08049574      0xbf8df8a8      0×08048245
0xbf8df8a0:     0×00000000      0xbf8df8c8      0×080483a3      0×00000012
0xbf8df8b0:     0×00000034      0xbf8df96c      0×00000012      0×00000034
0xbf8df8c0:     0×001903d0      0xbf8df8e0      0xbf8df938      0×001b4dec
可以看到a被置0了

(gdb) stepi    // 注釋: mov    0×8(%ebp),%eax
0×08048364      4               a = x;
(gdb) stepi    // 注釋: mov    %eax,0xfffffffc(%ebp)
5               a += y;
(gdb) x/16 0xbf8df890
0xbf8df890:     0×00000000      0×08049574      0xbf8df8a8      0×08048245
0xbf8df8a0:     0×00000012      0xbf8df8c8      0×080483a3      0×00000012
0xbf8df8b0:     0×00000034      0xbf8df96c      0×00000012      0×00000034
0xbf8df8c0:     0×001903d0      0xbf8df8e0      0xbf8df938      0×001b4dec
參數(shù)x(ebp+8)的值通過eax賦值給了局部變量a(ebp-4)

(gdb) stepi    // 注釋: mov    0xc(%ebp),%eax
0×0804836a      5               a += y;
(gdb) stepi    // 注釋: add    %eax,0xfffffffc(%ebp)
6               return a;
(gdb) x/16 0xbf8df890
0xbf8df890:     0×00000000      0×08049574      0xbf8df8a8      0×08048245
0xbf8df8a0:     0×00000046      0xbf8df8c8      0×080483a3      0×00000012
0xbf8df8b0:     0×00000034      0xbf8df96c      0×00000012      0×00000034
0xbf8df8c0:     0×001903d0      0xbf8df8e0      0xbf8df938      0×001b4dec
參數(shù)y(ebp+C)的值通過eax加到了局部變量a(ebp-4)

現(xiàn)在要從add返回了。返回之前把局部變量a(ebp-4)保存到eax用作返回值
(gdb) stepi    // 注釋: mov    0xfffffffc(%ebp),%eax
7       }
(gdb) stepi    // 注釋: leave
0×08048371 in add (x=1686688, y=134513616) at stack.c:7
7       }
(gdb) stepi    // 注釋: ret
0×080483a3 in main () at stack.c:13
13              result = add(x, y);

現(xiàn)在我們回到了main,棧現(xiàn)在是這樣的
(gdb) info registers esp
esp            0xbf8df8ac       0xbf8df8ac
(gdb) info registers ebp
ebp            0xbf8df8c8       0xbf8df8c8
(gdb) x/16 0xbf8df890
0xbf8df890:     0×00000000      0×08049574      0xbf8df8a8      0×08048245
0xbf8df8a0:     0×00000046      0xbf8df8c8      0×080483a3      0×00000012
0xbf8df8b0:     0×00000034      0xbf8df96c      0×00000012      0×00000034
0xbf8df8c0:     0×001903d0      0xbf8df8e0      0xbf8df938      0×001b4dec
可以看到,esp和ebp都已經(jīng)恢復(fù)到了調(diào)用add之前的值。
但是,調(diào)用add的兩個(gè)參數(shù)還在棧里(0xbf8df8ac、0xbf8df8b0,都在esp以上)。
也就是說,被調(diào)用的函數(shù)add沒有把它們從棧上清出去,需要調(diào)用方main來清理。這就是著名的“調(diào)用者清?!保琧decl調(diào)用方式的特點(diǎn)之一。

(gdb) stepi    // 注釋: mov    %eax,0xfffffff8(%ebp)
14              return 0;
(gdb) x/16 0xbf8df890
0xbf8df890:     0×00000000      0×08049574      0xbf8df8a8      0×08048245
0xbf8df8a0:     0×00000046      0xbf8df8c8      0×080483a3      0×00000012
0xbf8df8b0:     0×00000034      0xbf8df96c      0×00000012      0×00000034
0xbf8df8c0:     0×00000046      0xbf8df8e0      0xbf8df938      0×001b4dec
從eax得到函數(shù)add的返回值,賦值給了局部變量result(ebp-8)

(gdb) stepi    // 注釋: mov    $0×0,%eax ;把eax置0作為main的返回值
15      }
(gdb) stepi    // 注釋: add    $0×18,%esp ; 調(diào)用者清棧
0×080483ae      15      }
(gdb) continue
Continuing.
Program exited normally.
(gdb) quit
[test]$

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多