对于.NET程序来说,上线前进行代码混淆是一道很常见的保护步骤,可一旦混淆之后,异常定位的难度就会明显加大。要说清楚Agile.NET混淆后堆栈信息难以定位该怎么办,以及这些用来定位的信息平时又该怎么保留,核心就一件事:必须提前把混淆前后的名称映射关系、准确的版本号,还有异常发生时的上下文都给留存好。Agile.NET在混淆过程中,会改动类、方法、字段之类的元数据,最后留在异常堆栈里的,往往也只剩下那些被改得面目全非的名字;幸好它会同时生成一份map文件,专门用来描述混淆实体和原始名称之间的对应关系,这对于后面解释那些被混淆过的调试输出来说,是非常关键的依据。
一、混淆后堆栈信息难以定位怎么办
当线上抛出来的异常堆栈里全是乱七八糟的方法名时,通常不是因为日志本身没有价值,而是因为我们手里还缺少能把它们还原回去的条件。这时不要只盯着堆栈里那些乱码一样的名字发愁,而要把当时发布的混淆包、混淆工具的配置,以及那份对应的map文件放到一起对照着看。
1、先确认是哪一个发布版本
线上的每一条异常,都必须能够对应到具体的版本号、构建号、代码提交号,还有发布的时间。如果连版本信息都没有,就算手里拿着map文件,也很有可能用错映射关系。所以每次发布时,最好都把混淆完的程序包、原始的构建产物、map文件以及相关的配置文件,按照同一个版本编号归档存好。
2、借助map文件把名字还原回去
混淆工具生成的那份map文件,里面一条一条记录的就是原始名称和混淆后名称的对应关系。当我们从线上拿到异常堆栈之后,就可以先按照堆栈里出现的类名、方法名、程序集名称,到对应版本的map文件里面去逐个查找,查到了再回到源码里定位。类似的.NET混淆工具说明也经常强调,反混淆堆栈通常都需要依赖映射文件,才能恢复出原始的调用栈信息。
3、让关键模块保持原名,不要全部重命名
对外暴露的API、用到了反射调用的地方、需要序列化的模型、插件接口、依赖注入的入口、XAML里绑定的名称,还有Web API的Controller等位置,都不建议盲目地全部重命名。Agile.NET的说明里也提到,反射调用在混淆之后,可能会因为方法已经被重命名而失败,这时可以通过声明式的混淆属性,来指定哪些名称是不应该被重命名的。
4、复验问题时要使用同一份构建包
排查线上问题时,应当拿跟当初完全同一份混淆配置和同一个发布包去尝试复现,不要直接用没有混淆的Debug包复现之后就下结论。因为在经过了控制流混淆、字符串加密和方法调用处理之后,异常真正被触发的那个点,在表现上可能会跟未混淆版本不太一样。
二、堆栈定位信息该怎么保留
跟堆栈定位有关的那些信息,应该在发布流程里就当作固定环节保留下来,不能等到线上已经出了故障再去补。map文件、符号文件和异常日志中的关键字段,这三样里只要缺了任何一项,后面的定位速度都会被严重拖慢。
1、仔细保管每次生成的map文件
每次混淆并发布出去以后,都要把当时生成的那份map文件放入一个受控的目录里,只允许开发负责人、安全负责人或者发布负责人访问。因为map文件能够直接还原混淆后的名称,所以它绝对不可以随发布包一起发给客户,也不能放在公开下载的目录里面。
2、把PDB和对应的源码版本也留好
PDB符号文件不一定非要跟着程序一起发布,但在内部用来定位问题的时候,它的作用非常大。建议把PDB文件、源码提交号、构建流水线编号、混淆配置和发布包一并归档,这样不仅能对应上方法名,还能进一步定位到具体的代码行。
3、在日志里写全版本相关的字段
异常日志当中,至少要把应用程序的版本号、程序集版本号、构建号、模块名、所处的环境信息、异常类型、完整的堆栈、用户当时的操作入口,还有请求ID都给保留下来。如果一条日志里只有堆栈而没有版本号,后面就很难判断应该用哪一份map文件来进行还原。
4、把混淆配置也一起保存下来
混淆配置决定了哪些命名会被改掉、哪些规则会被跳过、是否打开了控制流混淆和字符串加密等功能。配置一旦发生过变化,堆栈还原的方式也会跟着变。所以每次发布的时候,都需要把当时的配置文件和map文件放在同一处保管好。
三、上线前怎么验证堆栈是可以定位的
在程序正式发布上线之前,还应该专门抽时间做一次异常定位的演练,不要等到客户环境里真的报了错,才发现map文件根本没存、版本号没有写进去,或者堆栈日志被平台截断了。
1、在测试环境主动制造一次异常
用已经混淆过的Release包,在测试环境里想办法触发一个可以控制的异常,把完整的堆栈记录下来。然后再用跟它匹配的那份map文件和源码版本,从堆栈往回查找,确认最终能不能顺利定位到具体的类、具体的方法,以及大概的代码位置上。
2、检查日志平台会不会截断堆栈
有些日志收集平台会自动截断过长的堆栈,或者只保留异常的主消息,而把内部的Inner Exception丢掉。这时需要确认一下,内部异常、异步调用栈、线程信息以及请求ID,这些在日志里面是不是全部都能查得到。
3、检查归档文件的访问权限
map文件和PDB文件,需要做到让被授权的人员可以快速找到,但又不能被普通用户或外部人员随意下载。权限放得太松会有安全风险,权限控制得太紧又会影响故障出现之后的响应速度。
总结
总的来说,Agile.NET混淆之后堆栈不好定位,以及堆栈定位信息平时该怎么保留,核心就是每次发布时都把map文件、PDB、源码版本号、混淆配置和构建号一并留存好。当线上出现异常回头排查时,先确定出问题的版本,再用对应的map文件把混淆名称还原回来。另外,针对反射、接口、序列化和外部调用相关的代码,还要提前设置好不参与重命名的规则。这样既能保留代码保护的效果,也不会让线上故障定位完全失去方向。