如何调试ILRuntime的PDB符号
解读
国内大多数Unity手游项目把热更新作为刚需,ILRuntime凭借“纯C#热更、无额外原生库”的优势,已成为腾讯、网易、米哈里、莉莉丝等一线厂的主流方案。面试时,面试官真正想确认的是:
- 你是否真的在真机包里调过热更代码,而不是只在Editor里“跑通Demo”;
- 遇到“行号对不上”“断点不命中”这类PDB符号失效问题时,你能否在30分钟内定位并解决,保证线上崩溃可回溯。
因此,回答要围绕“生成→转换→加载→校验→断点命中”五个环节展开,给出可落地的检查清单,而不是背诵官方Wiki。
知识点
- ILRuntime的符号链路:C#源码→Roslyn编译生成Portable PDB→ILRuntimePDBConverter.exe把Portable PDB转成ILRuntime私有格式→*.pdbbytes随DLL一起打包→真机端通过
appdomain.DebugService.Initialize加载。 - 版本一致性校验:DLL与pdbbytes的GUID+时间戳必须完全一致,否则ILRuntime直接拒绝解析符号。
- 调试器端口:ILRuntime采用TCP 56000端口(默认)与VS/Writer调试插件通信,真机Android需
adb forward tcp:56000 tcp:56000,iOS需确保Wi-Fi调试或USB隧道未被防火墙拦截。 - 优化开关:Release模式若开启
Strip Debug Symbols或Code Optimization=Speed,Roslyn会折叠局部变量、行号偏移,导致断点飘移;需要显式指定/debug:portable /optimize-。 - 崩溃回溯:线上包若未预埋
SymbolTool.ConvertFolder的映射文件,Sentry、Bugly只能拿到ILOffset,需要本地用IL2CPP+addr2line二次翻译,否则无法还原C#行号。
答案
我在上线项目中把PDB调试拆成**“编译期-转换期-运行期”**三段验收,保证真机断点命中率100%。
-
编译期:
- 用dotnet build而非Mono编译,命令行显式指定
/p:DebugType=portable /p:Optimize=false
确保生成Portable PDB,且与DLL同目录。 - 在CI里加校验脚本:用
System.Reflection.Metadata.MetadataReader读取DLL的Debug Directory,若GUID为空立即报警,防止Artifact被缓存污染。
- 用dotnet build而非Mono编译,命令行显式指定
-
转换期:
- 把ILRuntimePDBConverter.exe接在打包流水线末尾,参数用
-f强制覆盖,输出命名规则{Assembly}.pdbbytes,并写入MD5到manifest; - 在Android/iOS真机包打出后,立刻做一次冒烟测试:用
DebugService.IsDebuggerConnected断言返回true,若false则检查56000端口是否被其他进程占用。
- 把ILRuntimePDBConverter.exe接在打包流水线末尾,参数用
-
运行期:
- 启动时先判断
Application.isDebugBuild,仅在Development Player执行appdomain.DebugService.Initialize(true),避免Release包误开调试口; - 若断点仍不命中,三步排查:
a)adb logcat -s ILRuntime查看是否出现“PDB symbol mismatch”日志;
b) 用md5sum比对pdbbytes与CI产物;
c) 在VS调试窗口输入~loadby ILRuntimeDebugger 1,观察模块列表是否加载成功。 - 线上崩溃时,提前预埋
SymbolStore.SaveAll()把ILOffset→C#行号映射写到Application.persistentDataPath,回捞后配合addr2line可直接定位到热更代码行,无需重新出包。
- 启动时先判断
按以上流程,我们项目曾在上线前48小时内修复了3起因行号偏移导致的误崩溃,最终Taptap评分从4.3提到4.7。
拓展思考
-
Hybrid调试:当项目部分模块已迁到il2cpp、部分仍在ILRuntime时,可在VS里同时attach两个调试引擎:
- Native端用LLDB看IL2CPP堆栈;
- 热更端用ILRuntime Debugger看C#堆栈;
需要把ILRuntimeDebugger的SymbolReader接口桥接到DbgHelp,实现单窗口混合断点,避免来回切换。
-
增量符号:对于每周两更的敏捷节奏,全量转换PDB会拖慢打包10分钟。可记录上一次Git Commit与本次差异,仅对变更的Type做增量PDB转换,再把增量结果Merge到主pdbbytes,实测可缩短70%时间。
-
安全红线:调试口一旦暴露,黑客可通过56000端口注入
Eval代码,直接执行GM命令。因此上线前必须:- 在U8SDK层把56000端口加入黑名单,防止外网访问;
- 用
#if !RELEASE把DebugService.Initialize完全裁剪,避免通过反射强行开启。