首页 > 解决方案 > 我们如何在编写大型程序时跟踪不同寄存器的值

问题描述

这里我代表了一个 msdos 代码的快照,在这种类型的代码块中,我们经常会遇到非常失望的情况,我们可以清楚地看到在第 26 行的行号处,我们将 DS:SI 的值存储到 ES:DI 中,但是我们可以清楚地看到寄存器 SI 或其内容不在第 26 行附近。我在理解汇编语言编码时经常遇到这种情况。

    01 PUSH    CS
    02 POP     DS
    03 PUSH    CS
    04 POP     ES
    05 ASSUME  DS:DOSGROUP,ES:DOSGROUP
    06 MOV     AX,OFFSET DOSGROUP:INITBLOCK
    07 ADD     AX,0Fh                  ; round to a paragraph
    08 MOV     CL,4
    09 SHR     AX,CL
    10 MOV     DI,DS
    11 ADD     DI,AX
    12 INC     DI
    13 MOV     [CurrentPDB],DI
    14 PUSH    BP
    15 PUSH    DX              ; Save COMMAND address
    16 MOV     AX,[ENDMEM]
    17 MOV     DX,DI

    18 invoke    SETMEM          ; Basic Header
    19 ASSUME  DS:NOTHING,ES:NOTHING
    20 PUSH    CS
    21 POP     DS
    23 ASSUME  DS:DOSGROUP
    24 MOV     DI,PDB_JFN_Table
    25 XOR     AX,AX
    26 STOSW              ;<--------------------here
    27 STOSB

我们可以清楚地看到 SI 的值在第 26 行附近没有。这类问题的补救措施是什么。我们是对程序编码时流动的所有寄存器的值进行硬拷贝,还是回到代码中找到 SI(或任何特定寄存器)的值,然后在我们的编码中调整它的值。

标签: assemblyx86-16

解决方案


要回答标题问题:是的,使用注释来描述哪个逻辑“变量”将位于代码块的哪个寄存器中。并记录每个功能的输入/输出/破坏。就像;;; input: ds:si pointer to a 0-terminated string在一些假设的函数中一样(不是这个)。在临时函数中,再次在您正在计算的地方发表评论。

如果您正在阅读其他人的文档记录不佳的代码,您可以在查看它以查看是否有任何更改某个寄存器之后,在块的顶部添加此类注释。(当存在您不知道它们破坏了哪些寄存器的函数调用时,这很重要。使用标准调用约定会使这变得容易得多,因为您知道要假定哪些寄存器被破坏了。)


正如 Jester 指出的那样,这是stos,不是movs,所以它不读DS:SI。它仅将 AX 和 AL 存储到ES:DI英特尔文档)。然而,这段代码看起来有问题:它设置DS但不是ES在此之前,就好像它期望使用 STOS DS:DI(它没有)。

也许它在实践中有效,因为实际上SETMEM并没有clobber ,或者将其设置为这段代码想要的值。但是从调用到之后,看起来这段代码预计会被破坏。ESassume ES:NOTHINGSETMEMSETMEMES

我假设此代码来自您一直在查看的 DOS 1.0,因此可能ES实际上仍然等于CS来自此块顶部的推送/弹出,幸运或其他原因。

在这种情况下,在调试器中单步执行可能有助于理解它。 我认为 BOCHS 的内置调试器可以让您在任何地方设置断点,即使在操作系统的代码中,它们甚至可以在禁用中断的情况下工作。


无论如何,当事情变得复杂时,使用注释是有限制的。

这就是为什么在现实生活中我们将长距离/大规模优化/常量传播留给编译器(它们在方面非常出色),并且大多只担心热循环的 asm 微优化(编译器并不总是很好) .


推荐阅读