gdb tui 安装_GDB 单步调试汇编
之前在看匯編的時候一直是肉眼看GCC -S的結(jié)果,缺點是很不直觀,無法實時的看到寄存器的值,所以研究了下如何用GDB調(diào)試匯編。當(dāng)然,寫這篇文章更重要的一個目的是半年沒有寫博客了,博客要長草了。^_^
調(diào)試匯編的需求有幾點:
- 能夠單步進行匯編調(diào)試。
- 能夠?qū)崟r看到寄存器值的變化。
- 能夠看到源代碼和對應(yīng)匯編的關(guān)系。
下面分享下用GDB實現(xiàn)上面的3點需求:
單步進行匯編調(diào)試
使用si和ni。與s與n的區(qū)別在于:s與n是C語言級別的單步調(diào)試,si與ni是匯編級別的單步調(diào)試。
能夠?qū)崟r看到寄存器值的變化。
使用gdb時增加-tui選項,打開gdb后運行l(wèi)ayout regs命令。注意最好加上-tui,否則很大可能會出現(xiàn)花屏現(xiàn)象。
能夠看到源代碼和對應(yīng)匯編的關(guān)系
在gdb中運行set disassemble-next-line on,表示自動反匯編后面要執(zhí)行的代碼。
可以清晰的看出int c=sum(x,y);與下面紅框內(nèi)的匯編指令成對應(yīng)關(guān)系。
如果大家不想用這么原始的方式,可以給GDB安裝插件或者使用emacs達到上面的目的,推薦兩篇文章:
- GDB 從裸奔到穿戴整齊
- GDB實用插件(peda, gef, gdbinit)全解
最后以一個小例子結(jié)束:
int sum(int x,int y){return x+y; }int main(){int x=10;int y=20;int c=sum(x,y);return 0; }gcc版本4.4.7,默認的優(yōu)化選項。
我們單步調(diào)試下這段代碼對應(yīng)的匯編:
設(shè)置斷點
注意如果想要把斷點設(shè)置在匯編指令層次函數(shù)的開頭,應(yīng)該使用b *fun而不是b func,這里我們把斷點設(shè)置在b *main
分配棧幀
0x0000000000400489 <main+0>: 55 push %rbp 0x000000000040048a <main+1>: 48 89 e5 mov %rsp,%rbp 0x000000000040048d <main+4>: 48 83 ec 10 sub $0x10,%rsp%rbp和%rsp表示的是當(dāng)前棧幀的棧底和棧頂。其中%rbp是被調(diào)用者需要保存的寄存器。sub $0x10,%rsp表示為main函數(shù)分配棧幀空間。
注意這里分配了16字節(jié)的棧空間,會有4字節(jié)用不上,我個人猜測跟gcc匯編產(chǎn)生的cfi_def_cfa_offset 16有關(guān),這個沒有深究。
int x=10
0x0000000000400491 <main+8>: c7 45 f4 0a 00 00 00 movl $0xa,-0xc(%rbp)將x的值放到棧中
int y=20
0x0000000000400498 <main+15>: c7 45 f8 14 00 00 00 movl $0x14,-0x8(%rbp)將y的值放到棧中
sum函數(shù)調(diào)用
0x000000000040049f <main+22>: 8b 55 f8 mov -0x8(%rbp),%edx0x00000000004004a2 <main+25>: 8b 45 f4 mov -0xc(%rbp),%eax0x00000000004004a5 <main+28>: 89 d6 mov %edx,%esi0x00000000004004a7 <main+30>: 89 c7 mov %eax,%edi0x00000000004004a9 <main+32>: e8 c6 ff ff ff callq 0x400474 <sum>將x與y分別賦值到%esi和%edi中,其中%edi和%esi被規(guī)定用來傳遞函數(shù)的第一個和第二個參數(shù)。(一個疑問是為什么不能直接mov -0x8(%rbp),%esi呢?)
callq會將下一條指令的地址壓入棧中,并跳到sum函數(shù)的第一條指令。
進入sum函數(shù)
0x0000000000400474 <sum+0>: 55 push %rbp 0x0000000000400475 <sum+1>: 48 89 e5 mov %rsp,%rbp 0x0000000000400478 <sum+4>: 89 7d fc mov %edi,-0x4(%rbp) 0x000000000040047b <sum+7>: 89 75 f8 mov %esi,-0x8(%rbp)同main函數(shù)一樣,首先將%rbp保存,然后從%edi和%esi中取出函數(shù)參數(shù)。
求和
0x000000000040047e <sum+10>: 8b 45 f8 mov -0x8(%rbp),%eax 0x0000000000400481 <sum+13>: 8b 55 fc mov -0x4(%rbp),%edx 0x0000000000400484 <sum+16>: 8d 04 02 lea (%rdx,%rax,1),%eax將x和y相加,這里用到的是lea指令,關(guān)于lea指令介紹參考LEA instruction? ,這里不贅述了。
將返回值放到%eax中,%rax寄存器規(guī)定存放函數(shù)的返回值。像GO語言如果函數(shù)可以有多個返回值的話,返回值是放到棧中。
sum函數(shù)收尾
0x0000000000400487 <sum+19>: c9 leaveq 0x0000000000400488 <sum+20>: c3 retq我們先看下現(xiàn)在的棧:
(這里不知道為什么沒有sub xx,$rsp,我猜測是gcc發(fā)現(xiàn)這個最后一次函數(shù)調(diào)用,之后不會有棧的增長只會有棧的回退,所以用%rsp和%rbp的結(jié)果是一樣的。簡單驗證了下,應(yīng)該是這樣)。
在函數(shù)結(jié)束時首先需要回收當(dāng)前函數(shù)的棧幀、恢復(fù)保存過的寄存器、恢復(fù)%rip的值,即返回地址。
leaveq指令相當(dāng)于:
mov %rbp,%rsp pop %rbp作用是釋放(deallocate)當(dāng)前函數(shù)的棧幀并恢復(fù)被保存的寄存器的值。由此我們也可以看出%rbp的作用:記住%rsp應(yīng)該回退的位置,否則函數(shù)結(jié)束時%rsp不知道該回退到哪。
retq指令相當(dāng)于:
pop %rip將上面保存過的callq的下一條指令地址恢復(fù)到%rip中。
接收函數(shù)返回值
0x00000000004004ae <main+37>: 89 45 fc mov %eax,-0x4(%rbp)將%eax的值放入到main函數(shù)的棧幀中。
return 0
0x00000000004004b1 <main+40>: b8 00 00 00 00 mov $0x0,%eax同上面sum函數(shù)一樣。
main函數(shù)收尾
0x00000000004004b6 <main+45>: c9 leaveq 0x00000000004004b7 <main+46>: c3 retq如果上面%rsp和%rbp指向同一內(nèi)存區(qū)域看起來不太直觀的話,看下現(xiàn)在main函數(shù)即將結(jié)束時的棧空間:
同上面sum函數(shù)的解釋一樣,不再贅述。
程序運行成功退出。
總結(jié)
以上是生活随笔為你收集整理的gdb tui 安装_GDB 单步调试汇编的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: c java开发对比_编程语言:Java
- 下一篇: 几张一模一样的照片_两张一模一样的照片看