GDB查看堆棧局部變量


GDB查看堆棧局部變量

參數從右到左入棧局部變量在棧上分配空間,聽的耳朵都起繭子了。最近做項目涉及C和匯編互相調用,寫代碼的時候才發現沒真正弄明白。自己寫了個最簡單的函數,用gdb跟蹤了調用過程,才多少懂了一點。

參考資料:

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

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

int add(int x, int y)

{

    int a = 0;

    a = x;

    a += y;

    return a;

}

int main(int argc, char *argv[])

{

    int x, y, result;

    x = 0x12;

    y = 0x34;

    result = add(x, y);

    return 0;

}

編譯:(Fedora6, gcc 4.1.2)

[test]$ gcc -g -Wall -o stack stack.c

反匯編:

這里的匯編的格式是AT&T匯編,它的格式和我們熟悉的匯編格式不太一樣,尤其要注意源操作數和目的操作數的順序是反過來的

[test]$ objdump -d stack > stack.dump

[test]$ cat stack.dump

......

08048354 :

 8048354:       55                      push   %ebp  ;保存調用者的幀指針

 8048355:       89 e5                   mov    %esp,%ebp  ;把當前的棧指針作為本函數的幀指針

 8048357:       83 ec 10                sub    $0x10,%esp  ;調整棧指針,為局部變量保留空間

 804835a:       c7 45 fc 00 00 00 00    movl   $0x0,0xfffffffc(%ebp)  ;a0ebp-4的位置是第一個局部變量

 8048361:       8b 45 08                mov    0x8(%ebp),%eax  ;把參數x保存到eaxebp+8的位置是最后一個入棧的參數,也就是第一個參數

 8048364:       89 45 fc                mov    %eax,0xfffffffc(%ebp)  ;eax賦值給變量a

 8048367:       8b 45 0c                mov    0xc(%ebp),%eax  ;把參數y保存到eaxebp+C的位置是倒數第二個入棧的參數,也就是第二個參數

 804836a:       01 45 fc                add    %eax,0xfffffffc(%ebp)  ;a+=y

 804836d:       8b 45 fc                mov    0xfffffffc(%ebp),%eax  ;a的值作為返回值,保存到eax

 8048370:       c9                      leave 

 8048371:       c3                      ret   

08048372 :

 8048372:       8d 4c 24 04             lea    0x4(%esp),%ecx  ;????

 8048376:       83 e4 f0                and    $0xfffffff0,%esp  ;把棧指針16字節對齊

 8048379:       ff 71 fc                pushl  0xfffffffc(%ecx)  ;????

 804837c:       55                      push   %ebp  ;保存調用者的幀指針

 804837d:       89 e5                   mov    %esp,%ebp  ;把當前的棧指針作為本函數的幀指針

 804837f:       51                      push   %ecx  ;????

 8048380:       83 ec 18                sub    $0x18,%esp  ;調整棧指針,為局部變量保留空間

 8048383:       c7 45 f0 12 00 00 00    movl   $0x12,0xfffffff0(%ebp)  ;x=0x12ebp-16是局部變量x

 804838a:       c7 45 f4 34 00 00 00    movl   $0x34,0xfffffff4(%ebp)  ;y=0x34ebp-12是局部變量y

 8048391:       8b 45 f4                mov    0xfffffff4(%ebp),%eax  ;y保存到eax

 8048394:       89 44 24 04             mov    %eax,0x4(%esp)  ;y作為最右邊的參數首先入棧

 8048398:       8b 45 f0                mov    0xfffffff0(%ebp),%eax  ;x保存到eax

 804839b:       89 04 24                mov    %eax,(%esp)  ;x第二個入棧

 804839e:       e8 b1 ff ff ff          call   8048354   ;調用add

 80483a3:       89 45 f8                mov    %eax,0xfffffff8(%ebp)  ;把保存在eaxadd的返回值,賦值給位於ebp-8的第三個局部變量result。注意這條指令的地址,就是add的返回地址

 80483a6:       b8 00 00 00 00          mov    $0x0,%eax  ;0作為main的返回值,保存到eax

 80483ab:       83 c4 18                add    $0x18,%esp  ;恢復棧指針,也就是討論stdcallcdecl的時候總要提到的調用者清棧

 80483ae:       59                      pop    %ecx  ;

 80483af:       5d                      pop    %ebp  ;

 80483b0:       8d 61 fc                lea    0xfffffffc(%ecx),%esp  ;

 80483b3:       c3                      ret   

 80483b4:       90                      nop   

......

有一點值得注意的是main在調用add之前把參數壓棧的過程。

它用的不是push指令,而是另一種方法。

main入口調整棧指針的時候,也就是位於8048380的這條指令 sub $0x18,%esp

不但象通常函數都要做的那樣給局部變量預留了空間,還順便把調用add的兩個參數的空間也預留出來了。

然后把參數壓棧的時候,用的是mov指令。

我不太明白這種方法有什么好處。

另外一個不明白的就是main入口的四條指令804837280483768048379804837f,還有與之對應的main返回之前的指令。

貌似mainesp要求16字節對齊,所以先把原來的esp壓棧,然后強行把esp的低4位清0。等到返回之前再從棧里恢復原來的esp

 

准備工作都做好了,現在開始gdb

gdb不太熟悉的同學要注意一點,stepi命令執行之后顯示出來的源代碼行或者指令地址,都是即將執行的指令,而不是剛剛執行完的指令。

我在每個stepi后面都加了注釋,就是剛執行過的指令。

[test]$ gdb -q stack

(gdb) break main

Breakpoint 1 at 0x8048383: file stack.c, line 11.

gdb並沒有把斷點設置在main的第一條指令,而是設置在了調整棧指針為局部變量保留空間之后

(gdb) run

Starting program: /home/brookmill/test/stack

Breakpoint 1, main () at stack.c:11

11              x = 0x12;

(gdb) stepi    // 注釋: movl   $0x12,0xfffffff0(%ebp)

12              y = 0x34;

(gdb) stepi    // 注釋: movl   $0x34,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:     0x002daff4      0x002d9220      0xbf8df8d8      0x080483e9

0xbf8df8b0:     0x001ca8d5      0xbf8df96c      0x00000012      0x00000034

0xbf8df8c0:     0x001903d0      0xbf8df8e0      0xbf8df938      0x001b4dec

這就是傳說中的棧。在main准備調用add之前,先看看這里有些什么東東

0xbf8df8c8(ebp)保存的是上一層函數的幀指針:0xbf8df938,距離這里有112字節

0xbf8df8cc(ebp+4)保存的是main的返回地址0x001b4dec

0xbf8df8b8(ebp-16)是局部變量x,已經賦值0x12;

0xbf8df8bc(ebp-12)是局部變量y,已經賦值0x34;

0xbf8df8c0(ebp-8)是局部變量result。值得注意的是,因為我們沒有給result賦值,這里是一個不確定的值。局部變量如果不顯式的初始化,初始值不一定是0

現在開始調用add

(gdb) stepi    // 注釋: mov    0xfffffff4(%ebp),%eax

0x08048394      13              result = add(x, y);

(gdb) stepi    // 注釋: mov    %eax,0x4(%esp)

0x08048398      13              result = add(x, y);

(gdb) x/12 0xbf8df8a0

0xbf8df8a0:     0x002daff4      0x002d9220      0xbf8df8d8      0x080483e9

0xbf8df8b0:     0x00000034      0xbf8df96c      0x00000012      0x00000034

0xbf8df8c0:     0x001903d0      0xbf8df8e0      0xbf8df938      0x001b4dec

y首先被壓棧,在0xbf8df8b0

(gdb) stepi    // 注釋: mov    0xfffffff0(%ebp),%eax

0x0804839b      13              result = add(x, y);

(gdb) stepi    // 注釋: mov    %eax,(%esp)

0x0804839e      13              result = add(x, y);

(gdb) x/12 0xbf8df8a0

0xbf8df8a0:     0x002daff4      0x002d9220      0xbf8df8d8      0x00000012

0xbf8df8b0:     0x00000034      0xbf8df96c      0x00000012      0x00000034

0xbf8df8c0:     0x001903d0      0xbf8df8e0      0xbf8df938      0x001b4dec

x第二個進棧,在0xbf8df8ac

 

(gdb) stepi    // 注釋: call   8048354

add (x=18, y=52) at stack.c:2

2       {

 

剛剛執行了call指令,現在我們進入了add函數

(gdb) info registers esp

esp            0xbf8df8a8       0xbf8df8a8

(gdb) info registers ebp

ebp            0xbf8df8c8       0xbf8df8c8

(gdb) x/12 0xbf8df8a0

0xbf8df8a0:     0x002daff4      0x002d9220      0x080483a3      0x00000012

0xbf8df8b0:     0x00000034      0xbf8df96c      0x00000012      0x00000034

0xbf8df8c0:     0x001903d0      0xbf8df8e0      0xbf8df938      0x001b4dec

現在esp指向0xbf8df8a8,這里保存的是add函數的返回地址,它是由call指令壓棧的。

(gdb) stepi    // 注釋: push   %ebp

0x08048355      2       {

(gdb) stepi    // 注釋: mov    %esp,%ebp

0x08048357      2       {

(gdb) stepi    // 注釋: sub    $0x10,%esp

3               int a = 0;

(gdb) info registers esp

esp            0xbf8df894       0xbf8df894

(gdb) info registers ebp

ebp            0xbf8df8a4       0xbf8df8a4

(gdb) x/16 0xbf8df890

0xbf8df890:     0x00000000      0x08049574      0xbf8df8a8      0x08048245

0xbf8df8a0:     0x002daff4      0xbf8df8c8      0x080483a3      0x00000012

0xbf8df8b0:     0x00000034      0xbf8df96c      0x00000012      0x00000034

0xbf8df8c0:     0x001903d0      0xbf8df8e0      0xbf8df938      0x001b4dec

剛剛執行完的3條指令是函數入口的定式。

現在我們可以看到,main的棧還是原樣,向下增長之后就是add的棧。

0xbf8df8a4(ebp)保存的是上層函數main的幀指針

0xbf8df8a8(ebp+4)保存的是返回地址

0xbf8df8ac(ebp+8)保存的是最后一個入棧的參數x

0xbf8df8b0(ebp+C)保存的是倒數第二個入棧的參數y

0xbf8df8a0(ebp-4)保存的是局部變量a,現在是一個不確定值

接下來add函數就真正開始干活了

(gdb) stepi    // 注釋: movl   $0x0,0xfffffffc(%ebp)

4               a = x;

(gdb) x/16 0xbf8df890

0xbf8df890:     0x00000000      0x08049574      0xbf8df8a8      0x08048245

0xbf8df8a0:     0x00000000      0xbf8df8c8      0x080483a3      0x00000012

0xbf8df8b0:     0x00000034      0xbf8df96c      0x00000012      0x00000034

0xbf8df8c0:     0x001903d0      0xbf8df8e0      0xbf8df938      0x001b4dec

可以看到a被置0

(gdb) stepi    // 注釋: mov    0x8(%ebp),%eax

0x08048364      4               a = x;

(gdb) stepi    // 注釋: mov    %eax,0xfffffffc(%ebp)

5               a += y;

(gdb) x/16 0xbf8df890

0xbf8df890:     0x00000000      0x08049574      0xbf8df8a8      0x08048245

0xbf8df8a0:     0x00000012      0xbf8df8c8      0x080483a3      0x00000012

0xbf8df8b0:     0x00000034      0xbf8df96c      0x00000012      0x00000034

0xbf8df8c0:     0x001903d0      0xbf8df8e0      0xbf8df938      0x001b4dec

參數x(ebp+8)的值通過eax賦值給了局部變量a(ebp-4)

(gdb) stepi    // 注釋: mov    0xc(%ebp),%eax

0x0804836a      5               a += y;

(gdb) stepi    // 注釋: add    %eax,0xfffffffc(%ebp)

6               return a;

(gdb) x/16 0xbf8df890

0xbf8df890:     0x00000000      0x08049574      0xbf8df8a8      0x08048245

0xbf8df8a0:     0x00000046      0xbf8df8c8      0x080483a3      0x00000012

0xbf8df8b0:     0x00000034      0xbf8df96c      0x00000012      0x00000034

0xbf8df8c0:     0x001903d0      0xbf8df8e0      0xbf8df938      0x001b4dec

參數y(ebp+C)的值通過eax加到了局部變量a(ebp-4)

現在要從add返回了。返回之前把局部變量a(ebp-4)保存到eax用作返回值

(gdb) stepi    // 注釋: mov    0xfffffffc(%ebp),%eax

7       }

(gdb) stepi    // 注釋: leave

0x08048371 in add (x=1686688, y=134513616) at stack.c:7

7       }

(gdb) stepi    // 注釋: ret

0x080483a3 in main () at stack.c:13

13              result = add(x, y);

現在我們回到了main,棧現在是這樣的

(gdb) info registers esp

esp            0xbf8df8ac       0xbf8df8ac

(gdb) info registers ebp

ebp            0xbf8df8c8       0xbf8df8c8

(gdb) x/16 0xbf8df890

0xbf8df890:     0x00000000      0x08049574      0xbf8df8a8      0x08048245

0xbf8df8a0:     0x00000046      0xbf8df8c8      0x080483a3      0x00000012

0xbf8df8b0:     0x00000034      0xbf8df96c      0x00000012      0x00000034

0xbf8df8c0:     0x001903d0      0xbf8df8e0      0xbf8df938      0x001b4dec

可以看到,espebp都已經恢復到了調用add之前的值。

但是,調用add的兩個參數還在棧里(0xbf8df8ac0xbf8df8b0,都在esp以上)

也就是說,被調用的函數add沒有把它們從棧上清出去,需要調用方main來清理。這就是著名的調用者清棧cdecl調用方式的特點之一。

(gdb) stepi    // 注釋: mov    %eax,0xfffffff8(%ebp)

14              return 0;

(gdb) x/16 0xbf8df890

0xbf8df890:     0x00000000      0x08049574      0xbf8df8a8      0x08048245

0xbf8df8a0:     0x00000046      0xbf8df8c8      0x080483a3      0x00000012

0xbf8df8b0:     0x00000034      0xbf8df96c      0x00000012      0x00000034

0xbf8df8c0:     0x00000046      0xbf8df8e0      0xbf8df938      0x001b4dec

eax得到函數add的返回值,賦值給了局部變量result(ebp-8)

(gdb) stepi    // 注釋: mov    $0x0,%eax ;eax0作為main的返回值

15      }

(gdb) stepi    // 注釋: add    $0x18,%esp ; 調用者清棧

0x080483ae      15      }

(gdb) continue

Continuing.

Program exited normally.

(gdb) quit

[test]$

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM