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 -- 纸上得来终觉浅,绝知此事要躬行。 --
QR Code



※ 发信站: 批踢踢实业坊(ptt.cc), 来自: 116.89.131.215 (台湾)
※ 文章网址: https://webptt.com/cn.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