聊一聊 GDB 調(diào)試程序時(shí)的幾個(gè)實(shí)用命令
一:背景
1. 講故事
用慣了宇宙第一的 Visual Studio 再用其他的開發(fā)工具還是有一點(diǎn)不習(xí)慣,不習(xí)慣在于想用的命令或者面板找不到,總的來說還是各有千秋吧,今天我們來聊一下幾個(gè)在調(diào)試中比較實(shí)用的命令:
- 查看內(nèi)存
- 硬件斷點(diǎn)
- 虛擬內(nèi)存布局
二:命令解讀
1. 查看內(nèi)存
相信大家都知道 Visual Studio 直接提供了 Memory 面板來觀察內(nèi)存布局,但 VSCode 沒有,還需要自己手敲命令來實(shí)現(xiàn),這就比較麻煩了,為了方便先上一段測試代碼。
#include <iostream>
using namespace std;
int main()
{
int a = 10;
int b = 11;
int c = 12;
}
調(diào)試器配的是 GDB,只能用它的 x 命令觀察內(nèi)存,類似 WinDbg 的 d系列命令,我們在 int c=12 處下個(gè)斷點(diǎn),命中后使用 -exec x/40xw $esp 觀察 esp處的內(nèi)存塊,截圖如下:
這里的 x/40xw $esp 是什么意思呢?翻譯成 WinDbg 的術(shù)語就是 dd esp L40 的意思,也就是顯示 40 個(gè) dword 指針單元的內(nèi)存地址。
從內(nèi)存地址上看 a,b 都存放在線程棧上,雖然沒有 VS 便捷,但還是可以用的。
2. 硬件斷點(diǎn)
說實(shí)話到現(xiàn)在都沒搞明白為什么 Visual Studio 不支持硬件斷點(diǎn),其實(shí)是可以做的,熟悉 WinDbg 的朋友都知道有一個(gè) ba 命令就是專門用來設(shè)置硬件斷點(diǎn),硬件斷點(diǎn)牛的地方在于可以對 內(nèi)存地址 的讀寫進(jìn)行監(jiān)控,不過它需要 CPU 的調(diào)試寄存器支持,即 dr0 ~ dr7 。
比如我在 windbg 中對 04ee5000 下一個(gè)讀斷點(diǎn),輸出如下:
eax=04ee5000 ebx=00000000 ecx=7746dfe0 edx=10088020 esi=7746dfe0 edi=7746dfe0
eip=77434e50 esp=0897f804 ebp=0897f830 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
ntdll!DbgBreakPoint:
77434e50 cc int 3
0:014> ba r4 04ee5000
0:014> g
0:014> r dr0
dr0=04ee5000
在 GDB 中也有類似的 硬件斷點(diǎn),即 rwatch 和 awatch 命令,前者用來監(jiān)視讀操作,后者監(jiān)視 讀寫操作,這里我們測試下 awatch 命令,測試代碼如下:
int main()
{
int a = 10;
int b = 11;
a = 15;
int c = 12;
}
接下來在 int b=11 處下斷點(diǎn),通過 x 命令找到 a 所在的內(nèi)存地址,然后使用 awatch 進(jìn)行監(jiān)控,不過有點(diǎn)坑的是 awatch 需要轉(zhuǎn)成具體類型,相當(dāng)于監(jiān)視的范圍寬度,輸出如下:
-exec x/10x $esp+0x4
0xffffd11c: 0x0000000a 0xf7dd4000 0xf7dd4000 0x00000000
0xffffd12c: 0xf7c06ed5 0x00000001 0xffffd1c4 0xffffd1cc
0xffffd13c: 0xffffd154 0xf7dd4000
-exec awatch 0xffffd11c
Cannot watch constant value `0xffffd11c'.
-exec awatch *(int*)0xffffd11c
Hardware access (read/write) watchpoint 3: *(int*)0xffffd11c
-exec c
Continuing.
Hardware access (read/write) watchpoint 3: *(int*)0xffffd11c
Old value = 10
New value = 15
main () at /home/skyfly/code/main.cpp:12
12 int c = 12;
從上面輸出的信息看非常明確,也非常有意思,給 GDB 點(diǎn)一個(gè)贊。
3. 虛擬地址布局
這個(gè)貌似也是 VS 不具有的功能,在 GDB 中得到了支持,相當(dāng)于 WinDBG 中的 !address 命令,觀察虛擬地址布局好處多多,可以看到內(nèi)存的分配情況,比如 stack 是否溢出就能從中觀察得到,在 GDB 中可以使用 i proc mapping 命令,輸出如下:
-exec i proc mapping
process 5142
Mapped address spaces:
Start Addr End Addr Size Offset objfile
0x56555000 0x56556000 0x1000 0x0 /home/skyfly/code/main.out
0x56556000 0x56557000 0x1000 0x1000 /home/skyfly/code/main.out
0x56557000 0x56558000 0x1000 0x2000 /home/skyfly/code/main.out
0x56558000 0x56559000 0x1000 0x2000 /home/skyfly/code/main.out
0x56559000 0x5655a000 0x1000 0x3000 /home/skyfly/code/main.out
0x5655a000 0x5657c000 0x22000 0x0 [heap]
0xf7ac7000 0xf7ac9000 0x2000 0x0
0xf7ac9000 0xf7acb000 0x2000 0x0 /usr/lib32/libgcc_s.so.1
0xf7acb000 0xf7ae1000 0x16000 0x2000 /usr/lib32/libgcc_s.so.1
0xf7ae1000 0xf7ae6000 0x5000 0x18000 /usr/lib32/libgcc_s.so.1
0xf7ae6000 0xf7ae7000 0x1000 0x1c000 /usr/lib32/libgcc_s.so.1
0xf7ae7000 0xf7ae8000 0x1000 0x1d000 /usr/lib32/libgcc_s.so.1
0xf7ae8000 0xf7af2000 0xa000 0x0 /usr/lib32/libm-2.31.so
0xf7af2000 0xf7bb3000 0xc1000 0xa000 /usr/lib32/libm-2.31.so
0xf7bb3000 0xf7bea000 0x37000 0xcb000 /usr/lib32/libm-2.31.so
0xf7bea000 0xf7beb000 0x1000 0x101000 /usr/lib32/libm-2.31.so
0xf7beb000 0xf7bec000 0x1000 0x102000 /usr/lib32/libm-2.31.so
0xf7bec000 0xf7c05000 0x19000 0x0 /usr/lib32/libc-2.31.so
0xf7c05000 0xf7d5d000 0x158000 0x19000 /usr/lib32/libc-2.31.so
0xf7d5d000 0xf7dd1000 0x74000 0x171000 /usr/lib32/libc-2.31.so
0xf7dd1000 0xf7dd2000 0x1000 0x1e5000 /usr/lib32/libc-2.31.so
0xf7dd2000 0xf7dd4000 0x2000 0x1e5000 /usr/lib32/libc-2.31.so
0xf7dd4000 0xf7dd5000 0x1000 0x1e7000 /usr/lib32/libc-2.31.so
0xf7dd5000 0xf7dd8000 0x3000 0x0
0xf7dd8000 0xf7e4d000 0x75000 0x0 /usr/lib32/libstdc++.so.6.0.28
0xf7e4d000 0xf7f4f000 0x102000 0x75000 /usr/lib32/libstdc++.so.6.0.28
0xf7f4f000 0xf7fad000 0x5e000 0x177000 /usr/lib32/libstdc++.so.6.0.28
0xf7fad000 0xf7fb3000 0x6000 0x1d4000 /usr/lib32/libstdc++.so.6.0.28
0xf7fb3000 0xf7fb5000 0x2000 0x1da000 /usr/lib32/libstdc++.so.6.0.28
0xf7fb5000 0xf7fb7000 0x2000 0x0
0xf7fc9000 0xf7fcb000 0x2000 0x0
0xf7fcb000 0xf7fcf000 0x4000 0x0 [vvar]
0xf7fcf000 0xf7fd1000 0x2000 0x0 [vdso]
0xf7fd1000 0xf7fd2000 0x1000 0x0 /usr/lib32/ld-2.31.so
0xf7fd2000 0xf7ff0000 0x1e000 0x1000 /usr/lib32/ld-2.31.so
0xf7ff0000 0xf7ffb000 0xb000 0x1f000 /usr/lib32/ld-2.31.so
0xf7ffc000 0xf7ffd000 0x1000 0x2a000 /usr/lib32/ld-2.31.so
0xf7ffd000 0xf7ffe000 0x1000 0x2b000 /usr/lib32/ld-2.31.so
0xfffdd000 0xffffe000 0x21000 0x0 [stack]
從輸出看,當(dāng)前的 stack 布局段在 0xfffdd000 ~ 0xffffe000 之間,如果發(fā)生了棧溢出就可以看下是不是超過這個(gè)范圍了哈,除了 stack 還可以看到 heap 的段范圍 0x5655a000 ~ 0x5657c000 。
三:總結(jié)
GDB 有很多實(shí)用的命令這里就不逐一介紹了,至少在 Linux 上是霸主一樣的存在,真搞不懂 netcore 的調(diào)試要和 lldb 扯在一塊,簡直是不走尋常路哈 ??????