我们如何在编写大型程序时跟踪不同寄存器的值



>在这里我表示了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(或任何特定寄存器)的值,然后在编码中调整其值。

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

如果你正在阅读别人记录不佳的代码,你可以在一个块的顶部添加这样的注释,在查看它以查看是否有任何更改某个寄存器之后。 (当有函数调用时,这并不重要,你不知道它们破坏了哪些寄存器。 使用标准调用约定可以简化得多,因为您知道要假设哪些寄存器被破坏了。


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

也许它在实践中有效,因为SETMEM实际上并没有破坏ES,或者将其设置为此代码无论如何都需要的值。 但是从调用SETMEM后的assume ES:NOTHING来看,这段代码似乎SETMEM会破坏ES

我假设这段代码来自您一直在查看的 DOS 1.0,所以大概ES实际上仍然等于这个块顶部的 push/pop 的CS,运气或其他原因。

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


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

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

最新更新