背景:web
某个.net 应用以windows服务的形式对外提供服务,而后经过IIs的web应用和该服务进行交互,经过web的方式向用户提供业务。该项目有点年头,目前至关于本身维护,没厂家支持,并且自定义了不少开发在上面,可是有一个棘手的问题是该项目没有提供全部源码的,相似服务程序,使用的相关DLL还进行了混淆加密,因此其内部逻辑经过反编译看源码会很是绕,并且就算反混淆后,代码的组织和命名也会差不少,看起来会很是的累。windows
症状:app
1. 某次为了和生产保持一致,就地升级了测试环境的操做系统(2012 r2 到2016),这个服务死活其不来,还好作了VM的快照。这个程序错误的时候就在日志里记了一条“输入字符串格式不对”,没头没脑,也不包含stack trace ,不知道哪里报的错。后来没有办法,拿快照回退,初步断定为操做系统兼容性问题(注意是.net 4.x 开发的,虽然有点怀疑这个结论)ide
2. 另外的同事为了熟悉这个系统,在新的测试环境,从新部署了一套这个系统(操做系统使用的2019),系统能够运行,没有问题。(因此我很怀疑1 中的兼容性问题,但目前我找不到缘由)工具
3. 恢复快照的测试环境,后又经历过两次服务起不来的问题。有次打了补丁,有次是重启就不行了。可是由于没有太多思路去排错(由于配置什么的都没有变过,关键是也没有什么有用的日志协助排错,并且没技术支持,没源码),后面偷懒直接恢复了快照解决。测试
4. 测试环境由于须要更新补丁,重启,结果服务又挂了。仍是同样的报错加密
尝试解决:spa
实在忍不了,我必须得把这个缘由找出来,否则在生产上出现,真的无法处理,并且到时候确定手忙脚乱。初步的想法是经过调试器直接调试这个服务,而后找到报错的位置,大体按逻辑找到可能出错的位置。从而找出究竟是配置问题、仍是兼容性问题,仍是其余的什么。操作系统
因为是个.net 应用,带调试功能,且能反编译的DNSpy 就属于个人首选工具了。支持直接查看.net 语言编译的exe、dll 文件,并且支持直接调试,并且能够就地对dll、exe的代码进行修改并保存。.net
结果调试一个windows 服务没有你想的简单。当使用DNSpy调试运行一个windows 服务时,因为其不是经过net start 或者windows 服务管理器执行的,因此报这个错误。
这可如何是好,这个服务起来就挂掉,我没有经过attach 到进程的方式来调试啊。还好微软的文章提供了一些思路。https://docs.microsoft.com/en-us/dotnet/framework/windows-services/how-to-debug-windows-service-applications#debugging-tips-for-windows-services
两个选项:
1. 在服务的onstart或者main 里增长sleep ,延长服务执行实际代码的时间,在sleep 没报错的时候,使用调试器attch上去(加sleep 也就一两行代码的改动,应该能够直接使用dnspy搞定,即便混淆过了,可是程序入口这些仍是很容易找到的,所以选择了该方法)
2. 把服务的程序逻辑改写,从新编译成一个console 程序。(没源码,修改较多,看来不太适合个人状况)
Dnspy 打开服务程序的exe文件,定位到onstart 方法,而后点击右键,选择”编辑方法(C#)“
增长一个sleep,我这里大概5秒钟,能够手快的执行完attach,另外在onstart处下断点。
保存修改。而后再DNspy的文件菜单中选择保存模块。这样会把更改写入到新的exe中(注意保留备份)
在服务管理器中启动服务。
使用dnspy 调试--附加到进程
注意点: 这里附加到进程能列出的是dnspy执行帐号下的进程,加入你的服务以其余用户身份执行,可能列举不出来,因此可能要更换dnspy的运行帐号。
后续因为涉及到应用内部的内容,就不写出来了,大概定位到是程序加载某些自生成的配置文件(二进制),而后某些目录的配置文件不知被谁拷贝了一份,名称为xx.yy-复制,致使程序加载报错(多是文件名问题),可是报错内容只有一条“输入格式不正确”,由于程序自己对错误进行了catch ,而后没有记录下面stack信息,调试器里其实能够看到。虽然代码进行了混淆,可是还能够定位到大概的位置。
在 System.Version.VersionResult.SetFailure(ParseFailureKind failure, String argument)
在 System.Version.TryParseComponent(String component, String componentName, VersionResult& result, Int32& parsedComponent)
在 System.Version.TryParseVersion(String version, VersionResult& result)
在 System.Version.Parse(String input)
在 System.Version..ctor(String version)
在 OO.Server.OOProcess.a(String A_0, Version& A_1)
在 OO.Server.OOProcess.b(String A_0)
在 l.c(String A_0)
在 l.j()
在 l.b(Boolean A_0)
在 l.b(Boolean A_0)
在 s4.a(Boolean A_0, Boolean A_1)
在 aje.g()
在 OO.Server.Server.Start()
总结分析:
综合以上多个历史症状,能够得出问题缘由是复制的自动生成的配置文件致使了应用crash,只是服务不重启的时候,这个配置文件不会从新加载,因此不管是升级操做系统、打补丁、重启都是因为间接重启了服务致使配置从新加载,而后触发错误。
更多思考和问题:
这个程序是一个.net 反编译和更改代码相对简单,可是若是服务是个C程序或者C++编译的,如何进行调试呢?如何增长sleep 呢,或者有其余更好的方法进行调试。并且程序自己catch了异常,退出也是相对优雅的退出,不能经过在程序无处理异常时打开调试器的方法。