「开发这套系统的公司已经不存在了」「当初负责开发的人已经离职,联系不上了」「服务器里虽然有可执行文件和数据库,却怎么也找不到源代码和文档」── 在中小企业业务系统的咨询中,这种情况绝不罕见。即便如此,系统今天依然在运行,业务也依然依赖着它。
在这种状态下,最糟糕的选择就是因为「搞不清楚状况」而开始摸索式的改造,或是随手对环境做出改动。没有源代码的系统,一旦坏掉,并不能保证还能修好。但另一方面,一开始就断言「什么都做不了,只能全面重建」,同样为时过早。即便没有文档,也存在还原规格的手段;即便没有源代码,有时也能读懂系统内部的构造。本文将按照实务中的实际顺序,整理从「什么都没有」的状态出发,直到建立起运维与保守体系为止的具体步骤。
1. 先说结论
- 最先该做的不是改造,而是「保全现状」。正在运行的生产环境本身就是最重要的资产。应先获取磁盘镜像备份(例如用 Disk2vhd 转换为虚拟机)与数据库备份,并确认能够真正还原之后,再进入下一步。1
- 即使没有文档,规格也是可以还原的。主要材料是对业务使用者的访谈、画面与报表、数据库结构(schema)、日志,以及借助 Process Monitor 对实际行为的观察。2
- 源代码能否还原,很大程度上取决于技术栈。如果是 .NET 编写的,用 ILSpy 之类的反编译工具可以读出相当多内容;而 VB6、C++ 这类原生代码,现实中不能指望能还原多少。34
- 反编译存在法律上的争议点。根据《著作权法》第 30 条之 4 的规定,以调查解析为目的的利用原则上被认为是被允许的,但事先确认合同(使用许可条款)是必须的。5
- 不要等到「完全弄清楚」才行动。应优先掌握「一旦停止就会导致业务中断的部分」以及「近期需要变更的部分」,变更则应做到小而分次、并且可以回退。
- 保守合同以准委托为基本形式。对于内部构造尚不清楚的系统,无法在调查阶段就承诺完成责任(承揽)。应将调查阶段与改造阶段分开签订合同。
2. 为什么会出现「什么都没有」的状态
在考虑应对方式之前,先确认自己公司属于哪种模式,有助于大致判断还留有哪些线索。
| 模式 | 典型经过 | 常常留存下来的东西 |
|---|---|---|
| 开发公司倒闭或退出 | 保守合同到期后放置数年,已联系不上对方 | 交付时的 CD-R、验收单、合同(有时沉睡在文件柜里) |
| 内部开发人员离职 | 由一个人独自开发,始终高度依赖个人经验,随后离职 | 该员工电脑或共享文件夹中的开发环境或源代码片段 |
| 业务转让・并购(M&A) | 系统随业务一并接手,但文档未随之移交 | 转让合同中的清单、联系原公司负责人的渠道 |
| 有源代码但无法信任 | 找到了源代码,但无法保证与正在运行的可执行文件一致 | 用于核对的构建日期与版本信息 |
最后一种模式容易被忽视,但实质上需要按「没有源代码」来同等对待。把旧的源代码当作正本进行改造,结果发现生产环境的可执行文件其实已经包含了数年份的修改,这是典型的事故案例。即使找到了源代码,在把它构建出来并与生产环境的可执行文件核对一致之前,都不应该轻信它。
无论属于哪种模式,先去查找合同、交付单、验收单都是有价值的。如果其中写明了著作权归属或源代码交付义务,就能成为后续交涉与法律梳理的基础。
3. 第一周该做的事 ── 保全现状与盘点
3.1 冻结变更
在调查结束之前,原则上不要对目标服务器、终端做任何改动。「先把操作系统更新一下吧」「把看起来没在用的文件整理一下吧」这类举动往往会成为致命伤。因为既然没有源代码,「坏了就修好」这个选项就不存在。也应考虑把更新程序的应用时机纳入管理之下,以免自动更新(Windows Update、防病毒软件的行为变化)擅自改变环境。
3.2 备份 ── 把环境本身作为资产复制下来
仅靠文件级别的备份是不够的。这类系统的操作系统设置、注册表、运行时环境、部署位置等,全都有可能是「之所以能正常运行」这一原因的一部分,因此需要保全整个磁盘镜像。
实务中常用的工具是 Sysinternals 的 Disk2vhd。它可以在系统联机运行的状态下,通过卷影复制服务(VSS)将其转换为具有一致性的 VHD/VHDX,成为在 Hyper-V 上以虚拟机形式启动的验证环境的基础。1 好处是可以同时推进物理服务器老化对策与验证环境的搭建(不过,OEM 授权的 Windows 有时在许可协议上不允许迁移到虚拟环境,因此需要确认授权类型1)。
与此同时,数据库应使用 DBMS 自带的标准方式进行备份。而最重要的一点是,要实际从备份中还原并确认能够启动。没有做过还原测试的备份,只是一份自以为有效的保险。不过,由于还原后的镜像中会原样保留生产环境的连接设置与计划任务,启动确认务必在与网络隔离的状态下进行(详见第 7 章)。如果不小心在联网状态下启动,原本只是想用来验证的环境就可能更新生产数据库或对接的外部系统。
3.3 盘点 ── 列出正在运行的一切
接下来,机械化地把系统的构成要素全部列出来。这项工作靠的不是敏锐的直觉,而是覆盖的全面性。
| 盘点对象 | 确认手段 | 关注要点 |
|---|---|---|
| 全部可执行文件 | 安装文件夹、Program Files 目录下 | EXE/DLL 的文件版本、更新时间、数字签名 |
| 自动启动的项目 | Sysinternals Autoruns6 | 启动项、服务、常驻进程 |
| 计划任务 | 任务计划程序 | 夜间批处理、按月・按年处理(也要查看执行历史) |
| 数据库 | 连接字符串、ODBC 设置 | 连接目标服务器、schema、是否与其他系统共用 |
| 配置 | INI 文件、注册表、app.config 等 | 路径、连接目标、运行模式切换 |
| 与外部的对接 | 共享文件夹、FTP、邮件发送、外部 API | 对接方及方向(是接收数据,还是发送数据) |
| 账号・证书 | 服务运行账号、证书存储 | 密码与证书的有效期(悄无声息的定时炸弹) |
如果不清楚配置文件或输出目标「到底在哪里」,用 Process Monitor 观测进程对文件、注册表的访问是最快的方法。应用程序实际读写的路径会直接以列表形式呈现出来。2
4. 没有文档,也能还原规格
通过盘点弄清楚「有什么」之后,接下来就是还原「它在做什么」。材料是齐全的。
- 对业务使用者的访谈 ── 最大的一份「文档」,就在每天使用这套系统的人的脑子里。按照日常、月度、年度的业务流程,询问在哪个画面输入什么内容、又会得到什么结果。尤其是年度处理(结算、盘点、年度更新)有时连负责人自己都会忘记,是交接之后第一次执行时容易出事故的环节。
- 画面与报表 ── 用截图和实物样本,把所有画面、所有报表整理成台账。仅凭输入项与输出项之间的对应关系,就能相当程度地看清处理逻辑的骨架。
- 数据库结构(schema)与数据 ── 表定义、约束,以及编码值的实际数据,都是业务规则的化石。像「这个标志位实际上只用到了 3 种取值」这样的观察,能够揭示出画面上看不到的规格。
- 日志与事件日志 ── 如果应用程序有自己的日志,可以从中读出处理流程;而从 Windows 事件日志中,则能读出过去的错误倾向。
- 对实际行为的观测 ── 用 Process Monitor 记录文件、注册表、网络访问,就能在没有源代码的情况下,证实「月末的这项处理会读取这个共享文件夹里的 CSV,并与这台数据库服务器通信」这样的输入输出对应关系。2 不过,Process Monitor 能看到的只是通信对象,看不到具体是哪张表被如何更新的。再往下,就需要借助 DBMS 一侧的跟踪与审计功能(如 SQL Server 的扩展事件),或者通过在处理前后对比数据库内容的方式来确定。
这里重要的是,不要试图对所有功能进行同等程度的文档化。目的不是编写一部百科全书,而是让运维得以持续,因此应优先处理「一旦停止就会导致业务中断的处理」「正在报错的处理」「近期需要变更的部分」,把调查过的范围逐步积累到台账中。
5. 没有源代码时,技术上能做到什么
「能读懂内部构造」到什么程度,几乎完全取决于这套系统是用什么技术构建的。请先从可执行文件的属性和 DLL 构成推断出技术栈,再据此设定合理的预期。
| 技术栈 | 内部构造的可还原程度 | 主要手段 |
|---|---|---|
| .NET(C#、VB.NET)── 常规 IL 格式 | 高 | ILSpy 等反编译工具。Visual Studio 本身也内置了基于 ILSpy 的反编译功能43 |
| .NET ── 以 Native AOT 方式发布 | 低(与原生代码相当) | 由于不包含中间语言(IL)、已转换为原生代码,不能指望反编译工具能还原出 C# 代码 |
| Java | 高 | 同样可以用反编译工具读出来(因为也是中间代码) |
| Web 系统(PHP 等脚本语言) | 源代码往往本就存在于服务器上 | 首先检查服务器内部,价值很大 |
| VB6 | 低 | 机械式地还原出接近原始源代码的形式并不现实,主要以基于行为的解析与部分性的重新实现为主 |
| C / C++(原生) | 低(专业性要求高) | 反汇编、生成伪代码是可行的,但成本高。应聚焦于点状解析,而非整体还原 |
如果是以常规 IL 格式部署的 .NET 应用,情况会相当乐观,反编译得到的 C# 代码对于理解处理逻辑而言已经足够实用。不过,正如官方文档中所明确说明的那样,注释、局部变量名、空白等编译时不需要的信息会丢失,因此不能把它当作原始源代码的替代品,而应定位为「用于理解程序行为的资料」。3 另外也存在例外。如果实施了混淆(obfuscation),解读难度会大幅上升;而以 Native AOT 方式发布的二进制文件不包含 IL,因此「因为是 .NET 写的所以能读懂」这种期待并不成立。在判断技术栈时,不能只看开发语言,还要连同部署形式一并确认。
如果可执行文件旁边还留有 PDB(符号文件),有时甚至可以还原出函数名,乃至(如果嵌入了源代码的话)源代码本身。里面到底包含什么、能期待到什么程度,整理在《PDB(程序数据库)是什么》一文中。
法律注意事项 ── 逆向工程要「先调查清楚」
技术上能做到的事情,和被允许去做的事情,是两回事。包括反编译在内的逆向工程,在著作权法上存在争议点。根据 2018 年(平成 30 年)修订新设的《著作权法》第 30 条之 4(不以享受作品中所表现的思想或感情为目的的利用),以调查解析程序为目的的复制、翻改,在被认定为必要的限度内,原则上被认为是允许的。不过条文中附有但书,规定「对著作权人利益造成不当损害的情形除外」,比如利用解析结果去制作竞品这类情况,评价可能会有所不同。5
此外,如果打包软件或交付物的使用许可合同中包含禁止解析的条款,其效力该如何看待,这一合同层面的争议点依然存在。实施之前,应确认目标软件的许可条款,以及当时签订的开发委托合同(著作权归属条款);拿不准时,请咨询律师。如果交付物的著作权归属本公司,这个问题就会大幅简化。关于合同的解读方式,也可参考《受托开发与运维保守的合同该如何签订》一文。
6. 续用、封装,还是重建
当调查所获得的理解程度与业务方面的实际情况都齐备之后,就该决定方针了。这里同样不应「一上来就全面重建」,也不应「一上来就原封不动地放着不管」,而是用判断表来梳理。
| 选项 | 适合的情形 | 主要风险 |
|---|---|---|
| 原样续用(固定环境・虚拟化) | 使用终止的时间点已能预见,就在数年之内。几乎没有变更需求 | 操作系统与运行时的生命周期、与安全更新之间的两全问题 |
| 封装续用(不动主体,只新开发周边) | 主体稳定,需求集中在新增输入输出或对接方面 | 边界部分变复杂。对主体隐藏规格的依赖依然存在 |
| 部分重建 | 变更需求集中在特定功能上 | 需要维护新旧系统间的一致性。数据的双重管理 |
| 全面重建 | 变更需求多,业务本身已经发生变化,续用成本已经反超 | 遗漏隐藏规格的风险。并行运行与迁移带来的负担 |
判断的坐标轴有四个:剩余使用年限、变更需求的频率与集中程度、停止运行时对业务的影响,以及通过调查所还原出的理解程度。在理解程度较低的情况下就贸然推进全面重建,会遗漏旧系统里那些「没人能解释清楚、但在业务上确实是正确行为」的部分。即便选择重建,第 4 章中所还原出的规格台账也会直接成为需求定义的基础,因此对调查的投入不会白费。迁移时,常规做法是让新旧系统在一定期间内并行运行,并对同一输入所产生的输出(报表、汇总、文件)进行机械化的核对。
另外,针对 VB6、Access 等具体技术的续用与迁移判断,在《VB6 / Access 业务应用的续用与迁移 ── 保留、封装、替换的判断表》中有详细说明;而企业内部 Web 系统对 IE 模式的依赖问题,则在《摆脱 IE 模式依赖系统的实践指引》中有详细探讨。
7. 交接之后的运维与保守体制
如果方针是「暂时继续维持运行」(实际上大多数情况都是如此),遵循以下这几种做法可以减少事故。
- 拥有验证环境 ── 但首次启动务必与网络隔离 ── 把 3.2 中制作的磁盘镜像作为虚拟机启动,就能得到与生产环境配置相同的验证环境。不过,这份镜像中原样保留着生产环境的连接字符串、认证信息、计划任务、自动启动服务。如果在联网状态下启动,夜间批处理可能会重复执行并更新生产数据库,邮件可能会被重新发送,外部 API 也可能被再次调用。首次启动务必拔掉虚拟网卡或在隔离网络中进行,先停止计划任务与自动启动服务,把连接目标改写为验证用之后,再仅在必要范围内允许联网。
- 变更一次一项,且要能够回退 ── 无论是配置变更还是应用 Windows Update,每次只做一件事。保留变更前的镜像,一旦出现问题就切换回去。同时配套记录改动了什么内容(变更台账)。
- 把文档当作「调查过程的副产品」来逐步培养 ── 不要专门立项去写一份完美的文档,而是每次处理故障或进行改造时,把弄清楚的内容追加记录到台账里。运行一年左右,文档就会从业务上重要的部分开始逐渐齐备起来。
- 预先布置监控 ── 存活监控、磁盘剩余空间、错误日志,以及「本应输出的文件却没有输出」这类情况的检测。即使看不见黑盒内部,也能够监控它的入口和出口。
- 合同要把调查和改造分开 ── 委托外部进行保守时,由于无法对内部构造不明的系统的调查工作承诺完成责任,健康的做法是按阶段拆分合同:调查与保守以准委托方式签订,而在规格已经明确之后的具体改造,则以承揽(或成果完成型准委托)方式签订。
8. 总结
- 接手了没有源代码、也没有文档的系统时,应先于任何改造进行现状保全。获取磁盘镜像(Disk2vhd 等)与数据库备份,并确认能够真正还原。
- 对可执行文件、自动启动项、计划任务、配置、对接目标、账号进行盘点,把系统的整体面貌列成清单。
- 规格可以从使用者访谈、画面、报表、数据库结构、日志,以及借助 Process Monitor 对实际行为的观测中还原出来。不必覆盖全部功能,应优先处理对业务影响较大的部分。
- 内部构造能否还原,取决于技术栈。.NET 通过反编译可以读出相当多内容,但不能替代原始源代码。实施之前应确认《著作权法》第 30 条之 4 的立法宗旨,以及许可条款与合同内容。
- 方针应根据剩余使用年限、变更频率、业务影响、理解程度,在「续用・封装・部分重建・全面重建」之间做出判断。无论选择哪条路,调查中还原出的规格都会成为资产。
- 在运维阶段,验证环境、逐项变更、变更台账、对入口与出口的监控,以及以准委托方式签订合同,是基本的做法。
相关文章
- PDB(程序数据库)是什么 ── 理解调试信息、符号与 Source Link
- Process Monitor 实战指南
- VB6 / Access 业务应用的续用与迁移 ── 保留、封装、替换的判断表
- 摆脱 IE 模式依赖系统的实践指引
- 受托开发与运维保守的合同该如何签订 ── 借鉴 IPA「标准交易・合同」学习准委托与承揽的区分使用
- 委托开发 Windows 应用程序前该梳理的事项
相关咨询领域
合同会社小村软件(合同会社小村ソフト)承接的业务包括:对没有留存源代码或文档的业务系统进行现状调查(可执行文件、数据库、实际行为的解析)、从行为中还原规格、梳理续用与迁移方针,直至后续的运维与保守。即便是尚处于「不知道该从何入手」这个阶段的咨询,我们也非常欢迎。
参考链接
-
Microsoft Learn, Disk2vhd v2.02 (Sysinternals)。关于:可以在系统联机运行的状态下,通过 Windows 的卷影复制功能将其转换为具有时间点一致性的 VHD;转换后的 VHD 可以挂载到 Hyper-V 等虚拟机中启动;不得将其挂载到与源系统相同的系统上用于启动;不支持已启用 BitLocker 的卷;以及 OEM 版 Windows 的 P2V 迁移在许可协议上有时是不被允许的。 ↩ ↩2 ↩3
-
Microsoft Learn, Process Monitor (Sysinternals)。关于该工具是一款可以实时监视文件系统、注册表、进程/线程活动的工具。实务中的使用方法,可参考本站的《Process Monitor 实战指南》。 ↩ ↩2 ↩3
-
Microsoft Learn, Generate source code from .NET assemblies while debugging。关于 Visual Studio 的反编译功能是基于开源的 ILSpy(Visual Studio 2019 16.5 及以后版本);生成的源代码会因为空白、注释、局部变量名等编译时不需要的信息丢失,而与原始源代码并不完全相同,应当把它当作理解程序行为的资料,而非替代品来使用;async/await 模式的反编译有时并不完整;以及只会生成 C# 代码。 ↩ ↩2 ↩3
-
ILSpy (icsharpcode/ILSpy)。一款开源的 .NET 程序集浏览器与反编译工具。Microsoft Learn 的文档中,也将其作为 Visual Studio 反编译功能的基础加以引用。 ↩ ↩2
-
e-Gov 法令检索, 著作権法(昭和四十五年法律第四十八号)(日本《著作权法》)第 30 条之 4(不以享受作品中所表现的思想或感情为目的的利用)。这是 2018 年(平成 30 年)修订所完善的灵活权利限制规定之一,以调查、解析程序为目的的利用,一般被认为在被认定为必要的限度内是被允许的。不过,该条但书中「对著作权人利益造成不当损害的情形除外」的适用范围,以及使用许可合同中禁止解析条款的效力,仍需个别探讨(参考:内田・鲛岛法律事务所〈关于程序逆向工程的可行性(平成 30 年著作权法修订)〉)。 ↩ ↩2
-
Microsoft Learn, Autoruns for Windows (Sysinternals)。关于该工具可以全面列出注册在启动项、服务、计划任务等 Windows 自动启动位置的程序。 ↩
相关文章
共享相同标签的最新文章。可以围绕相近的主题进一步加深理解。
如何安全地为没有测试的遗留业务应用做修改 ── 特性化测试与重构实战
为了能安全地修改没有测试的业务应用,本文用 C# 示例讲解固定当前行为的特性化测试(黄金母版法)步骤、创建接缝(seam)的方法,以及不把重构与功能追加混在一起的运营规则。
委托开发 Windows 应用程序前该梳理的事项
在委托外包开发 Windows 应用程序之前,梳理现有软件改造、设备联动、COM/ActiveX、发布与更新、维护等需要注意的要点。
故障处理不止于恢复 ── 写给小型开发团队的 Postmortem(再发防止)实践模板
把故障处理停留在「修复、道歉,就此结束」,同样的故障就会反复发生。本文把 blameless postmortem 翻译给小型团队使用,整理出可在 1 小时内写完的模板、再发防止策略的强度判断表,以及实施的分级(triage)标准。
ADR(Architecture Decision Record)入门 —— 在小规模开发中,用最简方法留住「为什么这样设计」
代码不会告诉你「为什么这样做」。本文讲解如何用 ADR(Architecture Decision Record),以「1 个决定 = 1 个 Markdown 文件」的方式记录设计判断的理由,并附带模板、写与不写的判断表,以及实际案例。
网络驱动器与 UNC 路径的陷阱 ── 业务应用中处理文件服务器(共享文件夹)的实务
本文整理业务应用向共享文件夹输出、监控时的常见故障:驱动器号(Z:)为何在服务中不可见、各执行账户所需的权限、错误 1219,以及 FileSystemWatcher 的注意事项。
相关主题
与本文相近的主题页面。以本文为起点,可以进一步了解相关服务和其他文章。
Windows 技术主题
汇整 KomuraSoft LLC 关于 Windows 开发、故障调查与既有资产活用文章的主题中心。
ActiveX 迁移
整理保留、包装或替换 COM / ActiveX / OCX 资产的阶段性判断的主题页面。
与本主题相关的服务
本文与以下服务页面相关联,欢迎从最接近的入口查看。
Windows 应用程序开发
支持包含常驻处理、设备联动、运行日志与可维护结构的 Windows 桌面应用程序。
技术咨询 & 设计评审
协助梳理设计方向、架构边界、生命周期责任,以及既有 Windows 资产的处理方式。
常见问题
汇总了咨询这一主题时常见的问题。
- 没有源代码的系统,也能委托保守或改造吗?
- 可以委托。不过推进方式会和通常的保守有所不同。现实的做法是,先对正在运行的环境进行保全与备份,然后设立一个通过解析画面、报表、数据库、日志、可执行文件来还原规格的调查阶段,再在该阶段所获得的理解范围内,推进改造或周边功能的开发。由于调查这项工作性质上无法事先承诺成果,因此通常不采用需要承担完成责任的承揽合同,而是以准委托合同的形式推进。
- 对可执行文件进行反编译(逆向工程)是违法的吗?
- 并非一律违法。根据 2018 年(平成 30 年)修订新设的《著作权法》第 30 条之 4,像调查解析程序这样「不以享受作品中所表现的思想或感情为目的的利用」,在被认定为必要的限度内,原则上被认为是允许的。不过,该条排除了「对著作权人利益造成不当损害的情形」,另外,使用许可合同中禁止解析的条款该如何处理,也是仍然存在的争议点。实施之前,请确认许可条款以及委托开发时签订的合同,拿不准时请咨询律师等专业人士。
- 开发公司倒闭了,拿不到源代码,该怎么办?
- 首先应确认过去的合同与交付物。如果开发委托合同中规定了著作权归属或源代码的交付义务,这就能成为获取或使用源代码的依据。如果还能联系到相关人员,也值得尝试交涉获取。不过在实务中,重要的是不要止步于「结果还是没拿到」,建议在假设无法获取的前提下,同步开始对正在运行的环境进行保全与备份,以及从行为和数据库中还原规格。
- 没有文档的系统,应该从什么地方入手?
- 在着手改造之前,首先要做的是保全现状。获取正在运行的生产环境的磁盘镜像与数据库备份,并确认能够还原。接下来,对可执行文件、自动启动项、计划任务、配置、对接目标、账号进行盘点,把系统的整体面貌列成清单。在此基础上,对业务使用者进行访谈,并观察画面、报表、数据库结构(schema),聚焦于业务上重要的部分,逐步将规格文档化,这是常规做法。
作者简介
本文作者的个人简介页面。
Go Komura
小村软件有限公司 代表
以 Windows 软件开发、技术咨询与故障排查为中心,擅长难以复现的故障调查,以及既有资产仍在运行的项目。