#include <stdlib.h> void hello() { puts("Hello World!"); exit(1); } void func() { void *buffer[1]; } int main() { func(); return 0; }從這個簡單的小程式,我們可以發現:
$ gcc -g -o func func.c接著就祭出 gdb:
$ gdb func GNU gdb (GDB) 7.0-ubuntu ... Reading symbols from /home/jserv/testbed/func...done.先對函式 main() 作反組譯:
(gdb) disassemble main Dump of assembler code for function main: 0x0804843a <main+0>: push %ebp 0x0804843b <main+1>: mov %esp,%ebp 0x0804843d <main+3>: call 0x8048432 <func> 0x08048442 <main+8>: mov $0x0,%eax 0x08048447 <main+13>: pop %ebp 0x08048448 <main+14>: ret End of assembler dump.我們可留意到兩個細節:
(gdb) break func Breakpoint 1 at 0x8048438: file func.c, line 10.儘管函式 func() 的進入點為 "0x08048432",不過 gdb 會停留在函式內首個有效的指令,我們可見該位址是 "0x8048438",對照反組譯輸出:
(gdb) disassemble func Dump of assembler code for function func: 0x08048432 <func+0>: push %ebp 0x08048433 <func+1>: mov %esp,%ebp 0x08048435 <func+3>: sub $0x10,%esp 0x08048438 <func+6>: leave 0x08048439 <func+7>: ret End of assembler dump.設定好中斷點後,執行這個小程式,動態觀察程式的狀態,預期會停在剛剛設定的函式 func():
(gdb) run Starting program: /home/jserv/testbed/func Breakpoint 1, func () at func.c:10 10 }回顧稍早的程式碼列表,預留的 void *buffer[1] 給予我們一個機會去探索 x86 stack 的變化。因為 buffer[0] 實際位於函式 func() 的宣告中,而將索引值往後指呢?來看看:
(gdb) print *(buffer + 1) $1 = (void *) 0xbffff808 (gdb) print *(buffer + 2) $2 = (void *) 0x8048442gdb 告訴我們,*(buffer + 2) 的內含值就是位址 "0x8048442",有沒有覺得很熟悉呢?不就是函式 main() 裡頭,指令 call 的下一個位址嗎?回顧一下反組譯輸出:
(gdb) disassemble main Dump of assembler code for function main: 0x0804843a <main+0>: push %ebp 0x0804843b <main+1>: mov %esp,%ebp 0x0804843d <main+3>: call 0x8048432 <func> 0x08048442 <main+8>: mov $0x0,%eax 0x08048447 <main+13>: pop %ebp 0x08048448 <main+14>: ret End of assembler dump.至此,透過 gdb 動態改變程式執行的範例就剩下臨門一腳,在 gdb 的提示符號打入以下指令,要求將位址對齊到函式 hello():
(gdb) print *(buffer + 2) = (void(*)()) hello $3 = (void *) 0x8048414繼續程式執行,來檢驗我們的想法:
(gdb) c Continuing. Hello World! Program exited with code 01.由上可見:
你好,请问一下,怎么我执行你的代码发现有些不同呢?
NU gdb 6.3.50-20050815 (Apple version gdb-966) (Tue Mar 10 02:43:13 UTC 2009)
Copyright 2004 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-apple-darwin"...Reading symbols for shared libraries ... done
(gdb) disassemble main
Dump of assembler code for function main:
0x00001fe1 : push %ebp
0x00001fe2 : mov %esp,%ebp
0x00001fe4 : sub $0x8,%esp
0x00001fe7 : call 0x1fd9
0x00001fec : mov $0x0,%eax
0x00001ff1 : leave
0x00001ff2 : ret
End of assembler dump.
(gdb) disassemble func
Dump of assembler code for function func:
0x00001fd9 : push %ebp
0x00001fda : mov %esp,%ebp
0x00001fdc : sub $0x18,%esp
0x00001fdf : leave
0x00001fe0 : ret
End of assembler dump.
(gdb) break func
Breakpoint 1 at 0x1fdf: file func.c, line 10.
(gdb) run
Starting program: /Users/karpar/func
Reading symbols for shared libraries ++. done
Breakpoint 1, func () at func.c:10
10 }
(gdb) print *(buffer+1)
$1 = (void *) 0x0
(gdb) print *(buffer+2)
$2 = (void *) 0x0
(gdb)
karpar,
感謝分享 Apple Mac 上執行的狀況,理由很簡單,因為是 i386-apple-darwin :)
軟體的組態與 GNU/Linux 不同,是符合預期的。小弟這邊沒有實際的系統可以分析 Apple Mac 的 Mach-O 格式執行檔。
由 jserv 發表於 December 8, 2009 11:08 PM大概明白了你的意思了,謝謝jserv
由 karpar 發表於 December 9, 2009 11:21 AM不了解您要表達的是什麼?是在探討Stack內容對程式的影響嗎?
如果要以gdb動態改變程式執行,是x86沒有PC暫存器可以直接修改嗎?
還是您有其它想表達的意義?
To Danny,
延伸的意思就是,若在 func() 多加一行:
*(buffer + 2) = (void(*)()) hello
那麼,在 x86 架構上,會導致額外去呼叫 hello(),當然整個 call stack 也會被破壞
由 jserv 發表於 December 15, 2009 12:36 PM博主并没有讲明原理。在IA32的GDB环境下,调用栈从高地址向低地址“生长”,函数内第一个变量的内存地址向回(也就是向栈底位置,即向高地址)偏移该变量的内存长度,再偏移函数参数所占的内存长度后得到的内存地址中存放着该函数的返回地址,将该地址存储的地址值改成你要调用的函数FUNC的指针地址,就可以在该函数结束后自动跳转到FUNC函数执行。
由 adreaman 發表於 December 30, 2009 11:39 PM在OpenSolaris 2009.06中也正常。
GNU gdb 6.3.50_2004-11-23-cvs
Copyright 2004 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-pc-solaris2.11"...
(gdb) disassemble main
Dump of assembler code for function main:
0x08050d08 : push %ebp
0x08050d09 : mov %esp,%ebp
0x08050d0b : sub $0x8,%esp
0x08050d0e : and $0xfffffff0,%esp
0x08050d11 : mov $0x0,%eax
0x08050d16 : add $0xf,%eax
0x08050d19 : add $0xf,%eax
0x08050d1c : shr $0x4,%eax
0x08050d1f : shl $0x4,%eax
0x08050d22 : sub %eax,%esp
0x08050d24 : call 0x8050d00
0x08050d29 : mov $0x0,%eax
0x08050d2e : leave
0x08050d2f : ret
End of assembler dump.
(gdb) break func
Breakpoint 1 at 0x8050d06: file func.c, line 10.
(gdb) disassemble func
Dump of assembler code for function func:
0x08050d00 : push %ebp
0x08050d01 : mov %esp,%ebp
0x08050d03 : sub $0x4,%esp
0x08050d06 : leave
0x08050d07 : ret
End of assembler dump.
(gdb) run
Starting program: /export/home/roy/func
Breakpoint 1, func () at func.c:10
10 }
(gdb) print *(buffer + 1)
$1 = (void *) 0x8047a2c
(gdb) print *(buffer + 2)
$2 = (void *) 0x8050d29
(gdb) print *(buffer + 2) = (void(*)()) hello
$3 = (void *) 0x8050ce0
(gdb) c
Continuing.
Hello World!
Program exited with code 01.