在COM程序中保存寄存器状态

时间:2015-03-28 18:10:30

标签: c assembly dos cpu-registers

我反汇编了一个简单的DOS .COM程序,并且有一些代码可以保存和恢复寄存器值

PUSH AX ; this is the first instruction
PUSH CX
....
POP CX
POP AX
MOV AX, 0x00 0x4C
INT 21 // call DOS interrupt 21 => END

这与C程序中的函数序言和结语非常相似。但是编译器会自动添加序言,上面的程序是用汇编语言手动编写的,所以程序员在这段代码中负责保存和恢复值。

我的问题是如果我无意中忘记在我的程序中保存一些寄存器会发生什么?

如果我故意在HEX编辑器中将这些指令替换为NOP怎么办?这会导致程序崩溃吗?为什么被调用函数负责在堆栈上保存外部上下文?从我的观点来看,如果我使用第三方库和编写得不好的代码可能会破坏我的程序执行,那么应该以某种方式调用函数以防止出现问题。

3 个答案:

答案 0 :(得分:2)

在调用另一个函数之前使调用函数保存所有工作寄存器的一个问题是有时函数在不知情的情况下被中断(即硬件中断)。例如,在DOS中,有一个讨厌的54毫秒计时器滴答声。每秒18次,硬件中断会将控制从执行的任何代码传输到计时器滴答处理程序。除非您的程序专门禁用了中断,否则会自动发生这种情况。

计时器刻度处理程序将保存它将要使用的所有寄存器,完成其工作,然后在返回之前恢复它保存的寄存器。

当然,你可以说中断处理程序很特殊,但为什么呢?即使8086上的寄存器很少(AX,BX,CX,DX,SI,DI,Flags - 我忘记了什么吗?我故意没有包含段寄存器),使一个函数保存整个状态在转移控制之前意味着您正在使用大量不必要的堆栈空间和执行周期来保存,因为它们可能被修改。但是如果被调用的函数只负责保存它使用的寄存器,并且它只使用AX和CX,那么它只能保存这两个寄存器。它使代码更小更快,堆栈空间使用更少。

当你开始讨论深层次的调用层次结构时,推送8个寄存器而不是2个寄存器之间的区别很快就会增加。

考虑x86-64及其64个通用寄存器。你真的认为在调用另一个函数之前,应该强制函数保存所有64个寄存器,即使被调用函数只使用其中两个函数吗?保存64个64位寄存器需要512字节的堆栈空间。而不是保存两个只需要16个字节的寄存器。

现在用汇编语言编写东西的主要目的是编写比编译器编写的代码更快更小的代码。一个指导原则是不要做比你更多的工作。这意味着您需要知道汇编语言函数正在使用哪些寄存器,并在进入时保存这些寄存器并在退出时恢复它们。

答案 1 :(得分:1)

如果您不想忘记忘记pushpop的内容,我建议您坚持使用更高级别的语言。

在汇编程序中,如果函数是您自己的,那么您应该保存并恢复函数中使用的所有寄存器,除了那些从函数返回输出的寄存器。如果其他人编写了该函数,请查阅其文档。如果有疑问,请在调用函数之前/之后保存/恢复寄存器(除了那些应该返回值的寄存器)。

答案 2 :(得分:1)

由于 DOS终止功能不依赖于任何注册设置(AX除外)进行操作(*),所发布的代码中的推送/弹出似乎多余的。但是,您应该知道程序员可能已将这些值推送到本地使用它们!因此,在HEX编辑器中用NOP替换这两个推送肯定是一个坏主意。但是,您可以用NOP替换两个弹出窗口,因为在程序中的那一点,由于(*),不需要恢复AX / CX以及平衡堆栈。

由于你的问题是关于在程序级别上保存寄存器,答案必须是推送/弹出寄存器以保存它们是没用的。如果您无意中忘记在程序中保存一些寄存器,那么不会发生任何不好的事情。