17

我继承了一个用 8051 汇编语言编写的 10K 行程序,需要进行一些更改。不幸的是,它是按照意大利面条代码的最佳传统编写的。该程序——作为单个文件编写——是一个由 CALL 和 LJMP 语句组成的迷宫(总共大约 1200 条),如果子程序可以被识别为子程序,则它们具有多个入口和/或出口点。所有变量都是全局的。有评论;有些是正确的。没有现有的测试,也没有重构预算。

应用程序的一些背景知识:代码控制当前在国际上部署的自动售货应用程序中的通信中心。它同时处理两个串行流(在单独的通信处理器的帮助下),并且可以与多达四个不同的物理设备进行通信,每个设备来自不同的供应商。其中一个设备的制造商最近进行了更改(“是的,我们进行了更改,但软件完全一样!”)导致某些系统配置不再工作,并且对不更改它不感兴趣(无论它是什么他们没有改变)。

该程序最初是由另一家公司编写的,然后转让给我的客户,然后在九年前由另一位顾问进行了修改。原始公司和顾问都不能作为资源使用。

根据对其中一条串行总线上的流量的分析,我想出了一个 hack,它似乎有效,但它很丑陋,并且没有解决根本原因。如果我对该程序有更好的了解,我相信我可以解决实际问题。我还有大约一周的时间冻结代码以支持月底发货日期。

原始问题:我需要对程序有足够的了解才能在不损坏的情况下进行更改。有没有人开发出处理这种混乱的技术?

我在这里看到了一些很棒的建议,但时间有限。然而,我将来可能有另一个机会去追求一些更复杂的行动方案。

4

11 回答 11

17

首先,我会尝试与那些最初开发代码或至少在我之前维护它的人取得联系,希望能获得足够的信息来对代码有一个基本的了解,这样你就可以开始添加有用的注释它。

也许您甚至可以让某人描述代码中最重要的 API(包括它们的签名、返回值和用途)。如果全局状态被函数修改,这也应该明确。同样,开始区分函数和过程,以及输入/输出寄存器。

您应该向您的雇主非常清楚地表明此信息是必需的,如果他们不相信您,请让他们在您描述您应该做什么以及您必须如何做的同时在此代码前与您坐下来它(逆向工程)。在这种情况下,拥有具有计算和编程背景的雇主实际上会有所帮助!

如果您的雇主没有这样的技术背景,请让他带另一位程序员/同事向他解释您的步骤,这样做实际上会向他表明您对此是认真和诚实的,因为这是一个真正的问题-不仅仅是从你的角度来看(确保有知道这个“项目”的同事)。

如果可用且可行,我也会非常清楚地表明,与以前的开发人员/维护人员(如果他们不再为您的公司工作,也就是说)签订合同(或至少联系)以帮助记录此代码将是一个预先- 在短时间内实际改进代码并确保将来可以更轻松地维护它的必要条件。

强调这整个情况是由于以前的软件开发过程中的缺陷造成的,这些步骤将有助于改进代码库。因此,当前形式的代码库是一个日益严重的问题,现在为解决这个问题所做的一切都是对未来的投资。

这本身对于帮助他们评估和理解你的情况也很重要:做你现在应该做的事情绝非易事,他们应该知道这件事——如果只是为了明确他们的期望(例如关于截止日期和复杂性)任务)。

另外,我个人会开始为那些我理解的部分添加单元测试,这样我就可以慢慢开始重构/重写一些代码。

换句话说,好的文档和源代码注释是一回事,但拥有一个全面的测试套件是另一回事,如果没有任何已建立的测试关键功能的方法,没有人可以实际期望修改不熟悉的代码库。

鉴于代码为 10K,我还将考虑将子例程分解为单独的文件以使组件更易于识别,最好使用访问包装器而不是全局变量以及直观的文件名。

此外,我会考虑通过降低复杂性来进一步提高源代码可读性的步骤,让子例程具有多个入口点(甚至可能是不同的参数签名?)看起来是一种不必要地混淆代码的可靠方法。

同样,巨大的子例程也可以重构为更小的子例程,以帮助提高可读性。

因此,我首先要做的事情之一是确定那些使深入了解代码库变得非常复杂的事情,然后重新处理这些部分,例如通过将具有多个入口点的巨大子例程拆分为不同的而是相互调用的子例程。如果由于性能原因或调用开销而无法做到这一点,请改用宏。

此外,如果这是一个可行的选择,我会考虑使用更高级的语言逐步重写部分代码,或者通过使用 C 的子集,或者至少通过相当多地使用汇编宏来帮助标准化代码基础,也有助于本地化潜在的错误。

如果在 C 中进行增量重写是一个可行的选择,那么一种可能的开始方法是将所有明显的函数转换为 C 函数,其主体在开始时是从汇编文件中复制/粘贴的,因此您最终会使用 C具有大量内联汇编的功能。

就个人而言,我也会尝试在模拟器/模拟器中运行代码以轻松单步执行代码并希望开始理解最重要的构建块(同时检查寄存器和堆栈的使用情况),应该是一个带有内置调试器的好的 8051 模拟器如果您确实必须自己做这件事,则可以使用。

这也将帮助您提出初始化序列和主循环结构以及调用图。

也许,您甚至可以找到一个很好的开源 80851 模拟器,它可以轻松修改以自动提供完整的调用图,只需快速搜索,我就找到了 gsim51,但显然还有其他几个选项,以及各种专有选项。

如果我处于你的情况,我什至会考虑将修改我的工具的工作外包以简化使用此源代码的工作,即许多 sourceforge 项目接受捐赠,也许你可以说服你的雇主赞助这样的修改。

如果不是经济上的,也许你提供相应的补丁?

如果您已经在使用专有产品,您甚至可以与该软件的制造商交谈并详细说明您的要求,并询问他们是否愿意以这种方式改进该产品,或者他们是否至少可以公开一个界面以允许客户进行此类定制(某种形式的内部 API,甚至可能是简单的胶水脚本)。

如果他们没有回应,请表明您的雇主已经考虑使用不同的产品一段时间了,并且您是唯一坚持使用该特定产品的人...... ;-)

如果软件需要特定的 I/O 硬件和外围设备,您甚至可能需要考虑编写相应的硬件仿真循环以在仿真器中运行该软件。

最终,我知道我个人更喜欢定制其他软件来帮助我理解这样一个意大利面条代码怪物的过程,而不是手动单步执行代码并自己玩模拟器,不管我能喝多少加仑咖啡得到。

从开源 8051 模拟器中获取可用的调用图应该不会比说一个周末(最多)花费更长的时间,因为这主要意味着寻找 CALL 操作码并记录它们的地址(位置和目标),以便将所有内容都转储到存档以备日后检查。

访问仿真器的内部实际上也是进一步检查代码的好方法,例如为了找到操作码的重复模式(比如 20-50+),这可能会被考虑到独立的函数/过程中,这实际上可能有助于进一步降低代码库的大小和复杂性。

下一步可能是检查堆栈和寄存器的使用情况。并确定使用的函数参数的类型/大小,以及它们的取值范围——以便您可以构思相应的单元测试。

使用 dot/graphviz 之类的工具来可视化初始化序列的结构和主循环本身,与手动完成所有这些工作相比,这将是一种纯粹的乐趣。

此外,您实际上最终会得到有用的数据和文档,从长远来看,这些数据和文档可以作为更好的文档的基础。

于 2009-06-11T21:23:00.877 回答
7

恐怕这种问题没有灵丹妙药。我发现唯一的解决方案是打印出 ASM 文件,然后去某个安静的地方并在脑海中逐行模拟运行程序(同时在记事本上写入寄存器和内存位置的内容)。一段时间后,您会发现这并不像您预期​​的那么长。准备好花很多时间做这件事,喝几加仑的咖啡。一段时间后,您将了解它在做什么,并且可以考虑更改。

8051是否有任何未使用的IO端口?如果确实如此,并且您无法确定何时调用某些例程,则添加代码以将这些备用端口发送为高电平或低电平。然后在程序运行时用示波器观察这些端口。

祝你好运

于 2009-06-11T21:04:13.573 回答
6

我知道这听起来很疯狂....但我失业了(我选择了错误的时间告诉大多数合伙人下地狱)并且有一些空闲时间。我愿意看看它。我曾经为苹果 ][ 和原始 PC 编写程序集。如果我可以在模拟器上玩你的代码几个小时,我可以给你一个想法,如果我有机会为你记录它(不运行我计划外的假期)。由于我对 8051 一无所知,这对于像我这样的人来说可能是不可能的,但模拟器看起来很有希望。我不想要任何钱来做这件事。接触 8051 嵌入式开发就足够了。我告诉过你这听起来很疯狂。

于 2009-06-11T22:55:58.487 回答
4

找另一份工作——说真的!如果这本书“有效地使用遗留代码”可能会有所帮助——尽管我认为它将遗留代码称为没有单元测试的代码。

于 2009-06-11T21:01:06.467 回答
4

我做过几次这样的事情。一些建议:

  • 首先查看原理图,这应该可以帮助您了解所需更改会影响哪些端口和引脚。
  • 使用 grep 查找所有调用、分支、跳转和返回。这可以帮助理解流程并识别代码块。
  • 查看复位向量和中断表以识别主要线路。
  • 使用 grep 为所有代码标签和数据引用创建交叉引用(如果您的汇编工具无法为您执行此操作)。

记住霍夫施塔特定律: 它总是比你预期的要长,即使你考虑了霍夫施塔特定律

祝你好运。

于 2009-06-12T14:30:51.607 回答
3

您对运行此代码的硬件平台了解多少?

  • 是否已进入省电模式(Pcon=2)以节省电力如果是,它是如何被唤醒的。(复位或硬件中断)

  • 在进行串行通信之前,您是否必须等待振荡器在上电后稳定

  • 是否已进入睡眠模式 (Pcon=1)

现场是否有不同版本的硬件?

确保您有所有不同的硬件变体进行测试。

不要在模拟器上浪费时间——它很难使用,而且你必须对硬件做出很多假设。让自己拥有一个在线仿真器 (ICE)并在硬件上运行。

该软件是用汇编程序编写的,您需要找出原因。即 - 内存限制 - 速度限制

这段代码乱七八糟可能是有原因的

查看链接文件:

XDATA 空间、IDATA 空间和代码空间:

如果没有空闲代码空间或 Xdata 或 Idata?

原作者可能已经对其进行了优化以适应可用的内存空间。

如果是这种情况,您需要与原始开发人员交谈以了解他做了什么

于 2009-06-11T21:52:25.627 回答
1

重构和测试不需要特别的预算——它们可以节省你的钱,让你工作得更快——去做吧。这是您应该用来向遗留的继承代码添加更改的技术,因为它是在没有“不破坏”的情况下做到这一点的最便宜的方法。

大多数时候,我认为有一个权衡,你可以获得更高的质量以换取花费更多的时间,但是对于你不熟悉的遗留代码,我认为进行测试会更快——你必须先运行代码你发货了,对吧?

于 2009-06-11T21:01:06.123 回答
1

这是我建议您将您的软技能发挥作用的少数几次之一,并向您的 PM/经理/CXO 展示您重写背后的理由,以及此类工作所涉及的时间/成本节省

于 2009-06-11T21:01:59.010 回答
1

把它切成小块。

于 2009-06-11T23:03:51.727 回答
1

我在使用 8052 软件时遇到了一些非常相似的问题。所以公司继承了这样一个野兽,代码 ROM 完整(64Kbytes),大约 1.5 兆的组装意大利面条模块加上两个 3000 行 PL/M 模块组成了这个编码怪物。该软件的原始开发人员早已死去(这并不意味着没有人,但实际上没有人会整体理解它),编译这些软件的编译器来自 80 年代中期,在 MDS-70 仿真器上运行,还有几个关键的模块受到这些编译器的限制。就像再添加一个全局符号,链接器会崩溃。在 ASM 文件中再添加一个符号,编译器就会崩溃。

那么如何开始削减呢?

首先你需要工具。例如,Notepad++ 是一个非常好的东西,因为它可以用于一次跨多个文件进行交叉搜索,非常适合查找哪些模块引用了全局符号。这可能是最关键的元素。

如果可能,请获取您可以在该软件上找到的任何文件。使用这些野兽要解决的最直接的问题是了解它们的大致组成,它们的架构是什么。这通常不包含在软件本身中,即使它被正确注释也是如此。

要自己获得架构,首先您可以尝试构建调用图。它比数据流图更简单,因为通常跨文件调用和跳转比全局变量少。对于这个调用图只考虑全局符号,假设源文件应该是模块(这不一定是真的,但通常它们应该是)。

为此,请使用您的工具进行跨文件搜索,创建一个大列表(例如在 OpenOffice Calc 中),您可以在其中收集在哪个文件中定义了哪个符号,以及哪些文件引用了调用它的这个符号。

然后从绘图仪中偷一些大的(!)纸张,然后开始素描。如果您非常精通某些图形软件,您可能会使用它,但除非是这样,否则它更有可能阻碍您。因此,绘制一个调用图,显示哪个文件调用了哪些其他文件(不显示符号本身,有 50 个左右的文件,您将无法管理它)。

最有可能的结果是意大利面。目标是理顺这一点,使其成为一个没有循环的具有根(将是包含程序入口点的文件)的分层树。在此过程中,您可能会吞食几张纸,以反复拉直野兽。您可能还会发现某些文件非常复杂,以至于如果没有循环就无法表示它们。在这种情况下,很可能单个“模块”以某种方式被分成了两个文件,或者更多的概念模块被纠缠在一起。返回到您的调用列表,并对符号进行分组,以便将有问题的文件分割成较小的独立单元(您还需要检查文件本身以在此处进行本地跳转,以查看您假设的分割是否可行)。

最后,除非您已经在其他地方为自己的利益工作,否则您将获得带有概念模块的分层调用图。从中可以推断出软件的有意架构并进一步工作。

下一个目标是架构。通过您之前制作的地图,您将需要浏览软件,找出它的线程(中断和主程序任务),以及每个模块/源文件的粗略用途。如何做到这一点以及在这里获得什么更多地取决于应用程序域。

当这两个完成后,“休息”就相当简单了。通过这些,您应该基本上知道事情的每个部分应该做什么,因此您知道当您开始处理源文件时您可能会处理什么。重要的是,每当您在源代码中发现“可疑”的东西时,程序似乎会做一些不相关的事情,回到您的架构和调用图,并在必要时进行更正。

其他人提到的方法适用于其余部分。我只是概述了这些,以提供一些关于在真正可怕的情况下可以做什么的见解。我希望那时我只有 10K 行代码要处理......

于 2013-07-08T12:40:15.657 回答
0

我会说 IanW 的答案(只需打印出来并继续跟踪)可能是最好的。也就是说,我有一个有点离谱的想法:

尝试通过可以重构 C 代码(如果你能找到 8051 的代码)的反编译器运行代码(可能是二进制文件)。也许它会识别一些你不能(容易)的例程。

也许会有所帮助。

于 2009-06-11T21:58:21.027 回答