繁体   English   中英

解开汇编语言意粉代码

[英]Unravelling Assembly Language Spaghetti Code

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

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

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

基于对其中一条串行总线上的流量的分析,我想出了一个看似有效的黑客攻击,但它很难看并且没有解决根本原因。 如果我对该计划有更好的理解,我相信我可以解决实际问题。 在代码被冻结之前,我还有大约一周的时间来支持月末发货日期。

原始问题:我需要很好地理解程序,以便在不破坏的情况下进行更改。 有没有人开发过处理这种乱七八糟的技术?

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

首先,我会尝试与那些最初开发代码或至少在我之前维护代码的人取得联系,希望获得足够的信息以便对代码进行基本的理解,以便您可以开始添加有用的注释它。

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

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

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

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

强调整个情况是由于先前软件开发过程中的缺点,并且这些步骤将有助于改进代码库。 因此,当前形式的代码库是一个日益严重的问题,现在无论如何处理这个问题都是对未来的投资。

这本身对于帮助他们评估和了解你的情况也很重要:做你现在应该做的事情远非微不足道,他们应该知道 - 如果只是为了直截了当地设定他们的期望(例如关于截止日期和复杂性任务)。

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

换句话说,良好的文档和源代码注释是一回事,但拥有一个全面的测试套件是另一个重要的事情,没有任何人可以在没有任何测试关键功能的既定方法的情况下修改不熟悉的代码库。

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

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

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

因此,我要做的第一件事就是确定那些使得代码库变得非常复杂然后重新编写这些部分的东西,例如将具有多个入口点的巨大子例程拆分为不同的东西。相反调用的子例程。 如果由于性能原因或调用开销而无法完成此操作,请改用宏。

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

如果C中的增量重写是一个可行的选项,一种可能的入门方法是将所有明显的函数转换为C函数,这些函数的主体在开始时从汇编文件中复制/粘贴,因此最终得到C具有大量内联汇编的函数。

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

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

也许,你甚至可以找到一个很好的开源80851模拟器,可以很容易地修改,也可以自动提供一个完整的调用图,只是做一个快速搜索,我发现gsim51 ,但显然有其他几个选项,各种专有的选项。

如果我在你的情况下,我甚至会考虑外包修改我的工具以简化使用这个源代码的工作,即许多sourceforge项目接受捐赠,也许你可以和你的雇主讨论赞助这样的修改。

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

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

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

如果软件需要某些I / O硬件和外设,您甚至可能希望编写相应的硬件仿真循环以在仿真器中运行该软件。

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

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

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

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

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

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

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

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

祝好运

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

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

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

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

请记住Hofstadter定律: 即使考虑到Hofstadter定律,它也总是比您预期的要长

祝好运。

您对这段代码运行的硬件平台有多了解?

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

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

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

现场有不同版本的硬件吗?

确保您具有要测试的所有不同硬件变体。

不要在模拟器上浪费你的时间 - 这是非常难以使用的,你必须对硬件做出很多假设。 获得一个In Circuit Emulator(ICE)并在硬件上运行。

该软件是用汇编语言编写的,原因是你需要找出原因。 即 - 内存约束 - 速度限制

可能有一个原因,这个代码是一团糟

看看链接文件:

XDATA SPACE,IDATA SPACE和CODE SPACE:

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

原作者可能已经优化它以适应可用的内存空间。

如果是这种情况, 您需要与原始开发人员交谈以了解他的所作所为

我对8052软件有一些非常类似的问题。 所以公司继承了这样一个野兽,代码ROM满(64K字节),大约1.5兆的装配意大利面模块和两个3000行PL / M模块组成了这个编码怪物。 该软件的原始开发人员已经死了(这并不意味着没有人,但实际上没有人能够理解它),编译这些软件的编译器来自运行在MDS-70仿真器上的80年代中期,以及几个关键的模块处于这些编译器的极限。 就像添加一个全局符号一样,链接器会崩溃。 再向ASM文件添加一个符号,编译器就会崩溃。

那么如何才能开始削减它?

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

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

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

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

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

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

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

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

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

其他方面提到的方法很适用。 我刚刚概述了这些内容,以便对在真正可怕的情况下可以做些什么有所了解。 我希望我当时只有10K行代码来处理...

您不需要特殊的预算来进行重构和测试 - 它们可以节省您的资金并让您更快地工作 - 实现目标。 这是你应该使用的技术来添加对遗留代码的继承,因为这是最简单的方法,没有“没有破坏”。

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

这是为数不多的几次,我建议你将软技能用于工作,并向你的PM /经理/ CXO提供你重写的理由,以及这项工作所带来的时间/成本节省

把它切成碎片。

我会说IanW的答案(只是将其打印出来并继续跟踪)可能是最好的。 也就是说,我有一个略微偏离墙的想法:

尝试通过可以重建C代码的解析器运行代码(可能是二进制代码)(如果可以找到8051的代码)。 也许它会识别一些你不能(轻松)的例程。

也许它会有所帮助。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM