)
mruby 调试器 mrdb 完全使用指南从断点管理到源码级原理含 fluent-bit 仓库内嵌 mruby 上下文【免费下载链接】fluent-bitFast and Lightweight Logs, Metrics and Traces processor for Linux, BSD, OSX and Windows项目地址: https://gitcode.com/GitHub_Trending/fl/fluent-bitmrdbmruby debugger是 mruby 官方随源码树分发的命令行调试器用于对 mruby 脚本.rb与编译后的字节码.mrb进行交互式调试。本文以本仓库lib/nghttp2-1.65.0/third-party/mruby/doc/guides/debugger.md为骨架结合仓库内 mrdb 的完整 C 源码lib/nghttp2-1.65.0/third-party/mruby/mrbgems/mruby-bin-debugger/与命令行测试lib/nghttp2-1.65.0/third-party/mruby/mrbgems/mruby-bin-debugger/bintest/mrdb.rb系统讲解 mrdb 的构建方式、全部交互命令用法并深入到断点编号分配、命令解析表、调试钩子等实现细节。读完本文你将掌握 mrdb 从启动到熟练排查 mruby 脚本问题的完整技能。上下文说明mruby 在本仓库中位于 nghttp2 的third-party目录下而 nghttp2 又是 fluent-bit 的依赖库之一。nghttp2 自身通过src/shrpx_mruby_module_*.h等接口见 shrpx_mruby.h内嵌 mruby 作为扩展脚本引擎因此理解 mrdb 对于排查 nghttp2/fluent-bit 构建链中的 mruby 相关问题同样有参考价值。1. mrdb 概述mrdb 是 mruby 源码树中mruby-bin-debugger这个 mrbgem 提供的调试器命令。它允许开发者在交互式 shell 中控制 mruby 程序的执行流程设置/删除/禁用/启用断点按行号或按方法名单步执行step、运行到下一个断点continue在调试会话中实时求值 mruby 表达式print / eval查看源码与断点信息list / info breakpoints。从 mrbgem 定义文件 mrbgem.rake 可以看到它的两个关键事实spec.build.defines MRB_USE_DEBUG_HOOK—— 构建该 gem 时会自动定义MRB_USE_DEBUG_HOOK宏这是 mrdb 能工作的前提spec.bins %w(mrdb)—— 安装产物就是mrdb这个可执行文件。对应地mrdbconf.h 第 911 行会做编译期检查如果没有定义MRB_USE_DEBUG_HOOK直接报错 mruby-bin-debugger need MRB_USE_DEBUG_HOOK in your build configuration。2. 构建与安装 mrdb2.1 从 mruby 源码树构建mrdb 随 mruby 源码树trunk一起分发。在本仓库中对应源码树位于lib/nghttp2-1.65.0/third-party/mruby/其中包含完整的Makefile、Rakefile、build_config.rb与mrbgems/目录。在 mruby 源码根目录执行make即可完成构建$ cd mruby 源码根目录 # 本仓库中即 lib/nghttp2-1.65.0/third-party/mruby $ make默认情况下make会把调试器相关文件安装到mruby/bin目录下即生成bin/mrdb。2.2 将 mrdb 加入 PATH构建完成后可以把 mrdb 所在目录加入宿主环境 PATH方便全局调用$ echo export PATH\$PATH:MRUBY_ROOT/bin ~/.bashrc $ source ~/.bashrcMRUBY_ROOT指 mruby 源码安装的根目录例如lib/nghttp2-1.65.0/third-party/mruby的绝对路径。2.3 验证安装运行mrdb --version确认安装成功$ mrdb --version mruby 3.3.0 (2024-02-14)该输出对应本仓库内 mruby 树的版本参见 doc/mruby3.3.md 与 NEWS 文件。2.4 mrdb 支持的启动参数从 mrdb.c 的usage()与parse_args()第 86170 行可以看出mrdb 支持的开关如下参数含义源码依据-b加载并执行 RiteBinary.mrb字节码文件case b: args-mrbfile TRUE;-d指定源码目录可多次指定用于定位.mrb对应的.rb源码实现 list 等源码相关功能case d: ... append_srcpath--version打印版本信息mrb_show_version(mrb)--copyright打印版权信息mrb_show_copyright(mrb)若未指定程序文件mrdb 会提示Program file not specified.并退出若文件无法打开则提示Cannot open program file. (...)。解析后剩余的参数会作为脚本参数传给被调试程序args-argc/args-argv。3. 基本操作3.1 调试 mruby 脚本文件.rb直接键入mrdb即进入调试器指定脚本文件的格式为$ mrdb [option] file name例如调试sample.rb$ mrdb sample.rb进入调试器后可用命令汇总如下与原文档命令表一致并补充了源码中登记的全部命令命令描述run执行程序停在第一个断点step单步执行continue继续执行程序可指定目标断点号break设置断点delete删除断点disable禁用断点enable启用断点info breakpoints显示断点列表print对 mruby 表达式求值并打印结果list显示源码help显示帮助quit退出调试器对照 mrdb.c 中的debug_command_list可以发现实际登记的命令比文档表格还多除上述命令外还包含eval等价于 print、info locals查看局部变量、next下一行不进入方法内部。每个命令均登记了最短缩写长度如break的最小长度 1即bdisable的最小长度 3即dis这就是为什么b、c、d、dis、en、h、l、p、q、r、s、n、ev等缩写均可用。该规则被 bintest/mrdb.rb 等测试严格验证如b、br、brea、break合法而bl、breaka会得到 invalid command。3.2 调试 mruby 二进制文件.mrb3.2.1 先用-g编译出带调试信息的字节码注意要调试 mruby 二进制文件必须先用mrbc加-g选项编译脚本否则字节码中缺少行号等调试信息$ mrbc -g sample.rb这一步在测试脚本 bintest/mrdb.rb 中也有体现#{cmd(mrbc)} -g -o ...。3.2.2 用-b启动调试器$ mrdb -b sample.mrb-b使 mrdb 以二进制模式fopen(..., rb)加载.mrb文件见 mrdb.c。此时全部调试器命令同样可用。若希望list命令能显示源码可配合-d指定.rb源码所在目录。4. 断点管理命令详解4.1 break —— 设置断点断点可以按行号或方法名设置break [file:]linenum b [file:]linenum break [class:]method b [class:]method断点从 1 开始按顺序编号被删除的断点编号永远不会被再次分配。可以对同一行号或同一方法设置多个断点。注意break 命令不会校验类名与方法名的合法性会原样登记执行时才检查是否命中。设置成功后调试器会显示当前断点列表。断点信息有两种展示形式breakpoint 断点号 : 文件名.行号 breakpoint 断点号 : [类名,] 方法名从 apibreak.c 看实现mrb_debug_set_break_line()第 189 行与mrb_debug_set_break_method()第 232 行都会检查dbg-next_bpno MAX_BREAKPOINTNOMAX_BREAKPOINTNO MAX_BREAKPOINT * 1024见第 20 行超限则拒绝新增分配断点号后dbg-next_bpno第 220、264 行这正解释了已删除断点的编号不再复用的行为。4.2 continue —— 继续执行continue [N] c [N]N下一个要停下的断点编号。执行时程序会忽略编号小于等于 N-1 的断点直接停在断点 N 处。不带参数时停在下一个按执行顺序遇到的断点。示例(foo.rb:1) continue 3表示恢复执行并在第 3 个断点处停下。4.3 delete —— 删除断点delete [breakpoint-no] d [breakpoint-no]breakpoint-no断点编号可传多个。示例(foo.rb:1) delete # 删除全部断点 (foo.rb:1) delete 1 3 # 删除编号 1 和 3 的断点4.4 disable —— 禁用断点disable [breakpoint-no] dis [breakpoint-no]禁用而非删除断点使其暂时不生效但保留编号与定义。示例(foo.rb:1) disable # 禁用全部断点 (foo.rb:1) disable 1 3 # 禁用编号 1 和 3 的断点4.5 enable —— 启用断点enable [breakpoint-no] e [breakpoint-no]重新启用被禁用的断点(foo.rb:1) enable # 启用全部断点 (foo.rb:1) enable 1 3 # 启用编号 1 和 3 的断点对应的底层 API 分别为 apibreak.c 中的mrb_debug_delete_break[_all]、mrb_debug_disable_break[_all]、mrb_debug_enable_break[_all]第 328、359、377、396、412、431 行其头文件声明见 apibreak.h。4.6 info breakpoints —— 查看断点信息info breakpoints [breakpoint-no] i b [breakpoint-no]不带参数时显示全部断点(sample.rb:1) info breakpoints Num Type Enb What 1 breakpoint y at sample.rb:3 - 文件名.行号 2 breakpoint n in Sample_class:sample_class_method - [类:]方法名 3 breakpoint y in sample_global_methodNum断点编号Type断点类型此处均为breakpointEnb是否启用y启用n禁用What断点位置描述。只查看指定编号(foo.rb:1) info breakpoints 1 3 Num Type Enb What 1 breakpoint y at sample.rb:3 3 breakpoint y in sample_global_method5. 程序执行控制命令详解5.1 run —— 运行程序run r执行程序并停在第一个断点处。5.2 step —— 单步执行step s逐行执行程序。当遇到方法调用或代码块block时会进入其内部并在第一行停下由 C 实现的程序C 函数/扩展会被忽略不会进入。5.3 next —— 越过方法执行源码补充debug_command_list中还登记了nextn命令mrdb.c用于执行下一行但不进入方法内部与 step 形成对照。6. 表达式求值与源码查看命令详解6.1 print / eval —— 表达式求值print [expr] p [expr]exprmruby 表达式必填。显示结果按顺序从 1 开始编号如$1、$2。若表达式求值发生异常会显示异常信息并继续调试不会终止会话。示例(sample.rb:1) print 12 $1 3 (sample.rb:1) print self $2 main异常情形示例(sample.rb:1) print (12 $1 SyntaxError: line 1: syntax error, unexpected $end, expecting )eval命令与print等价文档原文即注明 Same as print command其最小缩写为ev。info locals命令则可用于查看当前作用域的局部变量。6.2 list —— 显示源码list [filename:]first[,last] l [filename]:first[,last]first起始行号last结束行号。分页规则只指定first不指定last显示从first开始的 10 行first与last均不指定显示接下来的 10 行翻页。示例(sample.rb:1) list sample2.rb:5 # 显示 sample2.rb 第 5 行起 10 行 (sample.rb:1) list sample2.rb:6,7 # 显示 sample2.rb 第 67 行list依赖调试信息对于.mrb字节码文件需要mrbc -g编译且用-d指定源码目录才能正确对应回.rb源码对应 apilist.c 的实现其行缓冲区大小LINE_BUF_SIZE同样取自MAX_COMMAND_LINE。7. 其他命令7.1 help —— 帮助help [command] h [command]不带参数显示全部命令列表带参数则显示指定命令的帮助。7.2 quit —— 退出调试器quit q立即终止 mruby 调试器会话。8. 从源码看 mrdb 的实现机制8.1 命令分发debug_command_listmrdb 的核心是一个命令描述表mrdb.cstatic const debug_command debug_command_list[] { {break, NULL, 1, 0, 0, DBGCMD_BREAK, dbgcmd_break}, {continue, NULL, 1, 0, 0, DBGCMD_CONTINUE, dbgcmd_continue}, {delete, NULL, 1, 0, 1, DBGCMD_DELETE, dbgcmd_delete}, {disable, NULL, 3, 0, 1, DBGCMD_DISABLE, dbgcmd_disable}, {enable, NULL, 2, 0, 1, DBGCMD_ENABLE, dbgcmd_enable}, {eval, NULL, 2, 0, 0, DBGCMD_EVAL, dbgcmd_eval}, {help, NULL, 1, 0, 1, DBGCMD_HELP, dbgcmd_help}, {info, breakpoints, 1, 1, 1, DBGCMD_INFO_BREAK, dbgcmd_info_break}, {info, locals, 1, 1, 0, DBGCMD_INFO_LOCAL, dbgcmd_info_local}, {list, NULL, 1, 0, 1, DBGCMD_LIST, dbgcmd_list}, {print, NULL, 1, 0, 0, DBGCMD_PRINT, dbgcmd_print}, {quit, NULL, 1, 0, 0, DBGCMD_QUIT, dbgcmd_quit}, {run, NULL, 1, 0, 1, DBGCMD_RUN, dbgcmd_run}, {step, NULL, 1, 0, 1, DBGCMD_STEP, dbgcmd_step}, {next, NULL, 1, 0, 1, DBGCMD_NEXT, dbgcmd_next}, {NULL} };结构体中的cmd1/cmd2是命令名与子命令名如info的breakpoints/localslen1/len2是最短缩写长度div标记命令是否需要分隔参数id是命令枚举func是具体处理函数dbgcmd_break、dbgcmd_continue等。mrdb 每读入一行命令就查这张表做前缀匹配因此所有命令都支持按最小前缀缩写。8.2 断点编号的单调递增调试上下文mrb_debug_context在 mrdb.c 中初始化dbg-next_bpno 1。之后每新增一个断点编号取自next_bpno并自增apibreak.c删除断点不会回退编号从而保证了编号永不复用的语义也让continue N能够可靠地指代某个断点。8.3 输入行长度上限mrdbconf.h 定义#define MAX_COMMAND_LINE 1024。mrdb 为每行命令分配MAX_COMMAND_LINE1字节缓冲区mrdb.c读取时若超过该长度会提示 command line too long.。这一点被测试用例精确验证bintest/mrdb.rb长度 1023、1024 字节的命令合法1025 字节即报错。8.4 调试钩子MRB_USE_DEBUG_HOOKmrdb 依赖 mruby 虚拟机的调试钩子。MRB_USE_DEBUG_HOOK宏由 mrbgem.rake 在构建该 gem 时自动注入若未定义mrdbconf.h 会直接编译失败。这也是为什么自定义构建 mruby 时若要启用 mrdb必须确保该宏生效。9. 实战示例完整调试一次 mruby 脚本假设有sample.rbdef add(a, b) a b end puts add(1, 2) puts done调试流程示意$ mrdb sample.rb (sample.rb:1) break add # 按方法名设断点进入 add 时停下 (sample.rb:1) break sample.rb:5 # 按文件:行号设断点 (sample.rb:1) info breakpoints # 查看断点列表 Num Type Enb What 1 breakpoint y in add 2 breakpoint y at sample.rb:5 (sample.rb:1) run # 运行停在第一个断点 (sample.rb:3) print a # 求值局部变量 $1 1 (sample.rb:3) list 1,5 # 查看源码 (sample.rb:3) step # 单步进入 (sample.rb:4) continue 2 # 继续并停在断点 2 (sample.rb:5) disable 2 # 禁用断点 2 (sample.rb:5) continue # 继续执行到结束 (sample.rb:6) quit # 退出调试器若改用编译后的字节码调试$ mrbc -g sample.rb # 生成带调试信息的 sample.mrb $ mrdb -b -d . sample.mrb # -b 加载字节码-d 指定源码目录10. 注意事项小结调试.mrb二进制文件前必须用mrbc -g编译否则缺少调试信息。break不校验类名/方法名合法性设置错误的方法断点不会在设置时报错。删除的断点编号不会复用continue N中的 N 是断点编号而非命中次数。每条命令含表达式长度上限为 1024 字符超长会报 command line too long.。print的表达式发生异常不会终止调试会话异常信息会直接打印。构建 mruby-bin-debugger 必须启用MRB_USE_DEBUG_HOOKmrbgem.rake 已自动处理。参考文件索引本文档原文doc/guides/debugger.mdmrdb 主程序命令表、参数解析mrdb.c断点 API 实现apibreak.c、apibreak.h源码列表 APIapilist.c编译期约束与常量定义mrdbconf.hmrbgem 构建定义mrbgem.rake命令行集成测试bintest/mrdb.rb【免费下载链接】fluent-bitFast and Lightweight Logs, Metrics and Traces processor for Linux, BSD, OSX and Windows项目地址: https://gitcode.com/GitHub_Trending/fl/fluent-bit创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考