C_and_CPP 板


LINE

對於編譯器全端的訓練, 剩下 debugger (好啦, linker 嚴格來說還沒完全練過一遍), debugger 是我覺得最難的, 我有想過目標設定在 dos debug 那種即可 (無 symbol debugger), 但還是不知道怎麼開始, 甚至去看 msdos 的 source code, 裡頭有 debug, 不過是用組合語言寫的, 決定先撤退。 和我之前學習的主題一樣, 曾經挑戰多次, 但都無功而返。突然這幾天又被我想到 debugger 這個主題, 再次找了一些資料, 終於找到有關 linux debugger 開發的資料, 幸運的是還有簡體中文翻譯 - 开发一个 Linux 调试器, 原文是: Writing a Linux Debugger。 是不是在 linux 我倒是沒那麼在意, debugger 都好, 但如果是 linux debugger 那更好。 這個門檻就低一點, 站在巨人的肩膀, 使用 linux/ptrace 來寫 debugger, 不用和硬體 debug 暫存器搏鬥。不過 ptrace 也不是那麼好學就是, 我曾經挑戰多次, 一樣無功而返。 ptrace 和 debugger 一起學習很適合, 之前學習 ptrace 就只想做一件事情, 改變程式的變數值印出來, 這不就是 debugger 做的事情嗎! 這篇算是這個教學文的學習指引, 也就是學習這系列文的學習文, 有點繞舌, 如果 Writing a Linux Debugger 有難倒你, 希望我這篇文章能幫助你學習這個主題。 先把程式編譯起來, 需要額外 2 個 library, libelfin, linenoise。 descent@debian-vm:minidbg$ ls ext/ libelfin linenoise 需要自己 git clone 出來: libelfin 需要切到 fbreg branch origin https://github.com/TartanLlama/libelfin.git (fetch) origin https://github.com/TartanLlama/libelfin.git (push) descent@debian-vm:libelfin$ git branch * fbreg linenoise origin https://github.com/antirez/linenoise.git (fetch) origin https://github.com/antirez/linenoise.git (push) 編譯 minidbg cmake . make make VERBOSE=1 # 可以看到編譯指令 編譯好 minidbg 之後會再需要一個 debug 程式, 寫個 hello world 吧! 編譯之後, 然後執行 ./minidbg h 會發現: list 1 error message Hello worlddescent@debian-vm:minidbg$ ./minidbg h terminate called after throwing an instance of 'dwarf::format_error' what(): unknown compilation unit version 5 Aborted 這是因為該程式使用的 libelfin 只支援 DWARFv4, gcc 11 是使用 DWARFv5, 改用 gcc-gdwarf-4 h.c -o h 來編譯要除錯的程式即可, 這樣就會使用 DWARFv4 的版本; 不過 source code 是用 -gdwarf-2 來編譯測試程式。 而為了搭配設定中斷點, 最後我使用 gcc h.c -no-pie -gdwarf-2 -o h 來編譯要除錯的程式。 minidbg 使用範例請參考 list 2。 list 2. minidbg 0 minidbg h 1 minidbg> break main 2 Set breakpoint at address 0x1148 3 minidbg> cont 4 hello, 10 5 Got signal Unknown signal -911728400 6 minidbg> cont 作者 Sy Brand 有說明 -no-pie 的影響, 並介紹了 personality()。大概原因是這樣, 用 objdump 看到的 main 位址很有可能不是被載入執行的位址, 如果是這樣, 設置的中斷點就可能不是預期的位置, 而 execl 不是自己寫的, 我們不知道 execl 是不是真的把 main load 到 objdump 看到的位址 (這衍生另外一個問題, execl 把程式 load 到那個位址, 有辦法查得到嗎?), 所以才有 -no-pie 或是 personality(), 我自己嘗試過在 linux 把 elf 執行檔 load 起來並執行, 但沒有成功, 卡在一些地方, 我的目標是想把 elf 執行檔 load 任何位址都可以執行, 但其中有部份我還沒克服, 所以止步到某個步驟, 在 linux 要克服蠻多問題, 在 bare-metal 環境我是有成功, uefi loader 載入 os kernel 就是這麼做的。 Sy Brand 有提到可以看 /proc/[pid]/maps 的第一行位址來當做載入位址。 使用 gdb 測試, gdb 不管有沒有 -no-pie, 都可以正確設定中斷點, gdb 找得到執行檔被真正載入的位址, 我用 strace (strace -o g.txt gdb abcdef) 觀察 gdb, 可惜沒找出什麼方向, gdb 在執行 run 指令之後才會呼叫 ptrace。 (gdb) b main Breakpoint 1 at 0x1172: file h.c, line 11. gdb 設定中斷點一樣是顯示在 0x1172, 但最後卻可以正確設定在 0x555555555172 的位址。 //child personality(ADDR_NO_RANDOMIZE); execute_debugee(prog); 如果不用 -no-pie 編譯, elf 執行檔會被載入到某個未知位址, 我想了一個很特別的方式, 找出 elf 被載入到的 main 位址, list 22 會印出 main addree, 就可以得知被載入 的真正位址, 和 objdump (list 23) 果然不同。 可以看到, 被載入到 0x555555555163, 而不是 0x1163。 流程是這樣: 執行到 list 22 L12 會卡在 while(1), 使用 kill -s SIGSTOP 240757 stop list 22 的執行檔, 這時候 minidbg 又可以繼續執行, list 25 L6 就可以看到 main 開始的 8 byte data。 我本來一開始是沒有設計 list 22 L12 while(1), cont 讓程式跑完再使用 memory read 指令, 但是只讀到 0xffffffffffffffff, 可能為了資安問題, 這個 process 被整個清掉了。所以才用了這麼迂迴的手法。 list 22 h.c 1 #include <stdio.h> 2 int abc123 = 5; 3 4 void func123() 5 { 6 int mn789 = 8; 7 printf("hello c, abc123: %d, mn789: %d\n", abc123, mn789); 8 } 9 int main(int argc, char *argv[]) 10 { 11 printf("main: %p\n", main); 12 while(1); 13 func123(); 14 return abc123; 15 } list 23 objdump -d h 1 0000000000001163 <main>: 2 int main(int argc, char *argv[]) 3 { 4 1163: 55 push %rbp 5 1164: 48 89 e5 mov %rsp,%rbp 6 1167: 48 83 ec 10 sub $0x10,%rsp list 25 md 1 descent@debian64:minidbg$ ./minidbg h 2 h child pid: 240757 3 minidbg> c 4 regs.rip: 7ffff7fd5090 5 main: 0x555555555163 6 minidbg> m r 0x555555555163 7 0x55 0x48 0x89 0xe5 0x48 0x83 0xec 0x10 8 minidbg> 分析 elf 有個 readelf 工具, 類似地, 觀察 dwarf 也有一個 dwarfdump 工具, readelf 算熟悉, dwarfdump 就很陌生了, 我今天 (20220630) 才第一次安裝這個工具, 和 elf 不同的是, dwarf 的資訊是壓縮過的, 所以無法用 hexdump 來直接觀察 dwarf, 還是借助 dwarfdump 會比較容易。對於 elf 我會使用 readelf, hexdump 來交叉比對, dwarf 看起來就難纏多了。 fig 3 的圖應該是我看過最具象化 elf 的圖了。初步比較, 我覺得 dwarf 比較複雜。 [elf101-64_pages-to-jpg-0001] fig 3. from https://github.com/corkami/pics/raw/master/binary/elf101/ elf101-64.pdf dwarf 比我想的難很多, 之前的基礎派不太上用場, 大概是重新學習的難度。 在快速掃過 10 篇文章之後, 我慢慢有了怎麼開始學習的計畫: 在初步階段, 需要用 objdump 來觀察到設定的位址, 然後使用 break 指令來設定中斷點, 先不要把 dwarf 搞進來, 光使用 ptrace 就夠複雜了。 對於 dwarf 安排了另外的學習方式, 我已經很擅長拆開整個複雜的東西來學習, 畢竟主要還是要學習 debugger, 沒有 dwarf 還是可以作到的, 只要會用 ptrace 即可。 這部份就是 Writing a Linux Debugger Part 3: Registers and memory 之前的部份, 之後就會開始講到 dwarf, 會變得很複雜, 先放一邊。 測試了「設定中斷點」和「繼續執行」和「讀暫存器」以及「讀寫記憶體位址」 這些指令。和 list 2 不同的是, 我增加了縮寫, break 打 b 就可以, cont 打 c 就可以, 類似 gdb 的指令。 測試時需要 list 3 的資訊, 才能做這個驗證, main 位址在 0x401122, 所以 list 5 L3 就是把中斷點設定在 main。另外我把 h 的 pid 印出來, ps 可以看到 h 的狀態是 t+, 處於停止或是被追蹤的狀態。 再來打 c 讓 h 停在 main, 程式起來是停在 main, 要怎麼驗證? 我想到的方式是看 cs:rip, 所以來看一下 rip, list 5 L8 的指令, 顯示的是 0x401123, 差了 1, 不知道是怎麼回事? 可能是這樣, main 現在被換成 0xcc (int 3), 所以 rip 指向 0x401123 是下一個要執行的位址。如果我說錯, 請打我臉 (不是真的打我臉)。 好, 看起來真的停在中斷點上, 再來看記憶體的內容, 要看哪裡, 看 main 好了, list 5 L6 的指令下了之後可以看到 20ec8348e58948cc, 有沒發現和 list 3 L5 ~ L6, L7 很像, 除了 0x55 變成 0xcc, 也說明之前下的中斷點真的把 0x55 改成 0xcc 了。 list 3. objdump -Sd h 1 0000000000401122 <main>: 2 #include <stdio.h> 3 int main(int argc, char *argv[]) 4 { 5 401122: 55 push %rbp 6 401123: 48 89 e5 mov %rsp,%rbp 7 401126: 48 83 ec 20 sub $0x20,%rsp 8 40112a: 89 7d ec mov %edi,-0x14(%rbp) 9 40112d: 48 89 75 e0 mov %rsi,-0x20(%rbp) 10 int a; 11 a = 5; 12 401131: c7 45 fc 05 00 00 00 movl $0x5,-0x4(%rbp) 13 printf("hello c\n"); 14 401138: 48 8d 3d c5 0e 00 00 lea 0xec5(%rip),%rdi # 402004 <_IO_stdin_used+0x4> 15 40113f: e8 ec fe ff ff callq 401030 <puts@plt> 16 return a; 17 401144: 8b 45 fc mov -0x4(%rbp),%eax 18 } 19 401147: c9 leaveq 20 401148: c3 retq 21 401149: 0f 1f 80 00 00 00 00 nopl 0x0(%rax) list 5 minidbg 測試過程 1 descent@debian64:minidbg$ ./minidbg h 2 h child pid: 231719 3 minidbg> b 0x401122 4 Set breakpoint at address 0x401122 5 minidbg> c 6 minidbg> m r 0x401122 7 20ec8348e58948cc 8 minidbg> r r rip 9 401123 有了使用上的理解, 之後看相關的程式碼, 就可以知道這是怎麼辦到的, 揭開除錯器神秘的面紗。Writing a Linux Debugger 系列文一次解除我 debugger/ptrace 2 個疑惑。 最後在我寫下這篇文章時, 我對 dwarfdump 的內容以達「略懂」, 沒有一開始覺得那麼難了, 也覺得 dwarf 很了不起, 為了 debug, 紀錄了相當多的資訊。 ref: ‧ LCTT 是“Linux 中国”(https://linux.cn/)的翻译组,负责从国外优秀媒体翻 译 Linux 相关的技术、资讯、杂文等内容。 ‧ DWARF, 调试信息存储格式 ‧ dwarf2调试信息格式——chapter1,2 ‧ Dwarf2 Exception Handler HOWTO ‧ Writing a Debugger ‧ 中文試譯:How debuggers work: Part 3 – Debugging information blog 原文: https://descent-incoming.blogspot.com/2022/07/debugger.html -- 紙上得來終覺淺,絕知此事要躬行。 --



※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 116.89.131.215 (臺灣)
※ 文章網址: https://webptt.com/m.aspx?n=bbs/C_and_CPP/M.1671885594.A.FFC.html
1F:推 hella: 推 12/24 21:16
2F:推 NCKUchemRx: 看了1/2 先推個 12/25 07:08
3F:推 enthos: 我是只到手刻elf header(FORTH),可以執行但格式有錯. 12/26 01:08
真厲害, 難得看到有研究 forth 的, 有沒有相關心得可以分享?
4F:推 SmallHanley: 推 12/26 11:53
5F:推 lc85301: 推 12/27 12:08
6F:推 eopXD: 推推推 12/27 14:18
7F:推 IhateOGC: 太神了 01/01 00:15
※ 編輯: descent (180.217.141.102 臺灣), 01/01/2023 00:26:20
8F:推 handsome616: 謝謝 01/01 18:05







like.gif 您可能會有興趣的文章
icon.png[問題/行為] 貓晚上進房間會不會有憋尿問題
icon.pngRe: [閒聊] 選了錯誤的女孩成為魔法少女 XDDDDDDDDDD
icon.png[正妹] 瑞典 一張
icon.png[心得] EMS高領長版毛衣.墨小樓MC1002
icon.png[分享] 丹龍隔熱紙GE55+33+22
icon.png[問題] 清洗洗衣機
icon.png[尋物] 窗台下的空間
icon.png[閒聊] 双極の女神1 木魔爵
icon.png[售車] 新竹 1997 march 1297cc 白色 四門
icon.png[討論] 能從照片感受到攝影者心情嗎
icon.png[狂賀] 賀賀賀賀 賀!島村卯月!總選舉NO.1
icon.png[難過] 羨慕白皮膚的女生
icon.png閱讀文章
icon.png[黑特]
icon.png[問題] SBK S1安裝於安全帽位置
icon.png[分享] 舊woo100絕版開箱!!
icon.pngRe: [無言] 關於小包衛生紙
icon.png[開箱] E5-2683V3 RX480Strix 快睿C1 簡單測試
icon.png[心得] 蒼の海賊龍 地獄 執行者16PT
icon.png[售車] 1999年Virage iO 1.8EXi
icon.png[心得] 挑戰33 LV10 獅子座pt solo
icon.png[閒聊] 手把手教你不被桶之新手主購教學
icon.png[分享] Civic Type R 量產版官方照無預警流出
icon.png[售車] Golf 4 2.0 銀色 自排
icon.png[出售] Graco提籃汽座(有底座)2000元誠可議
icon.png[問題] 請問補牙材質掉了還能再補嗎?(台中半年內
icon.png[問題] 44th 單曲 生寫竟然都給重複的啊啊!
icon.png[心得] 華南紅卡/icash 核卡
icon.png[問題] 拔牙矯正這樣正常嗎
icon.png[贈送] 老莫高業 初業 102年版
icon.png[情報] 三大行動支付 本季掀戰火
icon.png[寶寶] 博客來Amos水蠟筆5/1特價五折
icon.pngRe: [心得] 新鮮人一些面試分享
icon.png[心得] 蒼の海賊龍 地獄 麒麟25PT
icon.pngRe: [閒聊] (君の名は。雷慎入) 君名二創漫畫翻譯
icon.pngRe: [閒聊] OGN中場影片:失蹤人口局 (英文字幕)
icon.png[問題] 台灣大哥大4G訊號差
icon.png[出售] [全國]全新千尋侘草LED燈, 水草

請輸入看板名稱,例如:BabyMother站內搜尋

TOP