源文件移動後gdb不顯示代碼的原因
問題
我們從一個最簡單的C語言程序開始。源文件main.c在 用戶目錄gdb文件夾下。
florian@florian-pc:~/gdb$ cat main.c
int main()
{
return 0;
};
然後將源文件編譯為main(需要調試選項-g),並將main.c移動到src/main.c下,然後對main進行調試。
florian@florian-pc:~/gdb$ gcc main.c -o main -g
florian@florian-pc:~/gdb$ mv main.c src/main.c
florian@florian-pc:~/gdb$ gdb main
(gdb) b main
Breakpoint 1 at 0x8048397: file main.c, line 3.
(gdb) list
1 main.c: 沒有那個文件或目錄.
in main.c
在gdb中,使用list命令查看源代碼時,無法找到源文件main.c。
探究
由於對DWARF調試格式並不清晰,我本以為使用調試選項編譯的可執行程序內部包含了源文件的內容,這樣無論源碼是否存在,可執行程序都可以被正常調試。但是,從上邊的例子中可以看出,事實並非如此。
我們可以作一個簡單的推測:由於移動源文件的位置後,gdb無法找到源文件的位置,估計可執行文件的調試段內保存的不是源文件的內容,而是路徑信息。
將main.c移動回來,重新編譯,生成目標文件main.o,並查看其段信息。
florian@florian-pc:~/gdb$ mv src/main.c main.c
florian@florian-pc:~/gdb$ gcc -c main.c -o main.o -g
florian@florian-pc:~/gdb$ objdump -s main.o
main.o: file format elf32-i386
Contents of section .text:
0000 5589e5b8 00000000 5dc3 U.......].
……
Contents of section .debug_str:
0000 6d61696e 2e63002f 686f6d65 2f666c6f main.c./home/flo
0010 7269616e 2f676462 00474e55 20432034 rian/gdb.GNU C 4
0020 2e342e35 006d6169 6e00 .4.5.main.
……
我們發現在.debug_str段內,有兩個很明顯的字符串信息。
(1)/home/florian/gdb:看起來很像源文件所在的絕對路徑。
(2)main.c:顯而易見,是源文件的名稱。
驗證
我們再把main.c移動到src目錄下,再次編譯,看看字符串的信息有何變化。
florian@florian-pc:~/gdb$ mv main.c src/main.c
florian@florian-pc:~/gdb$ gcc -c src/main.c -o main.o -g
florian@florian-pc:~/gdb$ objdump -s main.o
main.o: file format elf32-i386
Contents of section .text:
0000 5589e5b8 00000000 5dc3 U.......].
……
Contents of section .debug_str:
0000 2f686f6d 652f666c 6f726961 6e2f6764 /home/florian/gd
0010 6200474e 55204320 342e342e 35007372 b.GNU C 4.4.5.sr
0020 632f6d61 696e2e63 006d6169 6e00 c/main.c.main.
在.debug_str段內的兩個字符串信息發生了變化。
(1)/home/florian/gdb:可見該字符串為執行gcc命令時的當前目錄的絕對路徑。
(2)src/main.c:該字符串為源文件相對於當前目錄的相對路徑。
將兩個目錄合並,便可以得到源文件的絕對路徑:
/home/florian/gdb /src/main.c
結論
由此可見,使用調試選項編譯生成的可執行文件內並非保存了源文件的內容,而是源文件的絕對路徑信息。DWARF調試格式(詳見這裡)定義的其他debug段內保存了二進制代碼與源文件行號的對應關系,這樣gdb的list命令工作時,實際是讀取可執行文件內的行號信息,並將源文件的代碼內容顯示出來。這也是為什麼將源文件移動後,list命令信息無法找到源文件的原因。
從這裡,我們也可以清楚一個事實:當源碼目錄的源文件路徑發生變化後,如果需要對可執行文件進行調試,則必須重新編譯。
類似的問題不僅僅在gdb的list命令中存在,objdump –S命令用於交叉顯示反匯編代碼與源代碼,同樣會受移動源文件的影響。