如何调试ILRuntime的PDB符号

解读

国内大多数Unity手游项目把热更新作为刚需,ILRuntime凭借“纯C#热更、无额外原生库”的优势,已成为腾讯、网易、米哈里、莉莉丝等一线厂的主流方案。面试时,面试官真正想确认的是:

  1. 你是否真的在真机包里调过热更代码,而不是只在Editor里“跑通Demo”;
  2. 遇到“行号对不上”“断点不命中”这类PDB符号失效问题时,你能否在30分钟内定位并解决,保证线上崩溃可回溯。
    因此,回答要围绕“生成→转换→加载→校验→断点命中”五个环节展开,给出可落地的检查清单,而不是背诵官方Wiki。

知识点

  1. ILRuntime的符号链路:C#源码→Roslyn编译生成Portable PDB→ILRuntimePDBConverter.exe把Portable PDB转成ILRuntime私有格式→*.pdbbytes随DLL一起打包→真机端通过appdomain.DebugService.Initialize加载。
  2. 版本一致性校验:DLL与pdbbytes的GUID+时间戳必须完全一致,否则ILRuntime直接拒绝解析符号。
  3. 调试器端口:ILRuntime采用TCP 56000端口(默认)与VS/Writer调试插件通信,真机Android需adb forward tcp:56000 tcp:56000,iOS需确保Wi-Fi调试USB隧道未被防火墙拦截。
  4. 优化开关:Release模式若开启Strip Debug SymbolsCode Optimization=Speed,Roslyn会折叠局部变量行号偏移,导致断点飘移;需要显式指定/debug:portable /optimize-
  5. 崩溃回溯:线上包若未预埋SymbolTool.ConvertFolder的映射文件,Sentry、Bugly只能拿到ILOffset,需要本地用IL2CPP+addr2line二次翻译,否则无法还原C#行号。

答案

我在上线项目中把PDB调试拆成**“编译期-转换期-运行期”**三段验收,保证真机断点命中率100%。

  1. 编译期

    • dotnet build而非Mono编译,命令行显式指定
      /p:DebugType=portable /p:Optimize=false
      确保生成Portable PDB,且与DLL同目录。
    • 在CI里加校验脚本:用System.Reflection.Metadata.MetadataReader读取DLL的Debug Directory,若GUID为空立即报警,防止Artifact被缓存污染。
  2. 转换期

    • 把ILRuntimePDBConverter.exe接在打包流水线末尾,参数用-f强制覆盖,输出命名规则{Assembly}.pdbbytes,并写入MD5到manifest;
    • 在Android/iOS真机包打出后,立刻做一次冒烟测试:用DebugService.IsDebuggerConnected断言返回true,若false则检查56000端口是否被其他进程占用。
  3. 运行期

    • 启动时先判断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。

拓展思考

  1. Hybrid调试:当项目部分模块已迁到il2cpp、部分仍在ILRuntime时,可在VS里同时attach两个调试引擎:

    • Native端用LLDB看IL2CPP堆栈;
    • 热更端用ILRuntime Debugger看C#堆栈;
      需要把ILRuntimeDebuggerSymbolReader接口桥接到DbgHelp,实现单窗口混合断点,避免来回切换。
  2. 增量符号:对于每周两更的敏捷节奏,全量转换PDB会拖慢打包10分钟。可记录上一次Git Commit与本次差异,仅对变更的Type增量PDB转换,再把增量结果Merge到主pdbbytes,实测可缩短70%时间。

  3. 安全红线:调试口一旦暴露,黑客可通过56000端口注入Eval代码,直接执行GM命令。因此上线前必须:

    • U8SDK层把56000端口加入黑名单,防止外网访问;
    • #if !RELEASEDebugService.Initialize完全裁剪,避免通过反射强行开启。