汇编语言编程提示和技巧

时间:2011-05-10 14:03:43

标签: assembly x86 nasm

我正在编写自己的“玩具”操作系统,目前我主要在汇编(NASM)中进行操作 - 部分是因为我希望它能帮助我理解x86的反汇编,也因为我我发现它也很有趣!

这是我第一次使用汇编编程的经验 - 我的选择速度比我想象的要快,但是学习任何明显不同的语言我发现我的代码结构相当混乱,因为我试图找出什么模式和我应该使用的约定。

此刻我特别挣扎着:

跟踪寄存器

目前一切都处于16位模式,因此我只有6个通用寄存器可供使用,其中可用于访问存储器的寄存器更少。我继续践踏我自己的寄存器,这反过来意味着我经常交换寄存器以避免这种情况 - 因此我很难跟踪哪些寄存器包含什么值,即使是自由评论。这是正常的吗?我能做些什么来帮助让事情更容易跟踪吗?

例如,我已经开始使用被破坏的寄存器列表来评论我的所有函数:

; ================
; c_lba_chs
; Converts logical block addressing to Cylinder / Head / Selector
;  ax (input, clobbered) - LBA
;  ch (output) - Track number (cylinder)
;  cl (output) - Sector number
;  dh (output) - Head number
; ================

跟踪筹码

在一些情况下,当我用完寄存器时,我已经开始使用堆栈了,但这让事情变得更糟 - 比简单的push call pop序列更复杂以保存寄存器会导致我松动完全跟踪,甚至告诉我是否在堆栈上有正确数量的项目(特别是涉及错误处理时 - 见下文),更不用说它们处于什么样的顺序。我知道必须有更好的方法使用堆栈,我只是看不出它是什么。

处理错误

我一直在使用进位标志和零标志(取决于函数)来向调用者指示错误,例如:

myfn:
    ; Do things
    jz .error
    ; Do more things
    ret

    .error:
        stc
        ret

这是指示错误的正常方式吗?

还有其他任何提示或技巧可以用来更好地组装我的装配吗?

最后是否有良好的资源/精心编写的汇编示例?我遇到The Art of Assembly Language Programming但是它似乎非常关注语言的细节,而不太重视代码的结构。 (还有一些代码示例使用了段,我认为我应该避免使用段。)

我正在使用零段(平面内存模型)来完成所有这些操作,以便在开始使用C时保持简单并使事情变得更容易。

1 个答案:

答案 0 :(得分:6)

别担心,你几乎走在正确的轨道上。作为装配,您可以做您想做的事情,这样您就可以自由决定如何管理您的寄存器和数据。我建议为自己开发一些标准,使用C标准可能不是一个坏主意。我还建议为这样的第一个项目使用不同的汇编语言(例如在qemu上运行的ARM),x86在指令集中有点可怕。但这是一个单独的主题...

如果你愿意,汇编程序通常会让你声明变量,内存名称为:

bob: .word 0x1234

然后从汇编程序(在这里使用ARM asm)

ldr r0,bob
add r0,#1
str r0,bob

暂时使用寄存器,实际数据保存在内存中。这样的模型可以帮助跟踪事物,因为真实数据保存在内存中,用户创建的变量名称就像高级语言一样。 x86使得这更容易,因为您可以对内存执行操作,而不必通过寄存器来处理所有内容。同样,您可以使用堆栈框架管理本地变量,从堆栈中减去一些数字以覆盖该函数的堆栈帧,并在该函数内知道/记住变量joe是堆栈指针+4,ted是堆栈指针+8,等等。可能在代码中使用注释来记住这些内容的位置。记住在返回之前将堆栈指针/帧恢复到其入口点。这种方法有点困难,因为您没有使用变量名称而是数字偏移。但是提供局部变量和递归和/或一些全局内存节省。

用眼睛和手(键盘和鼠标)以人的方式完成这项工作你可能希望将数据保存在寄存器中的时间长度不能超过文本编辑器屏幕上的数据,一目了然变量转到寄存器然后一目了然地返回内存中的变量。程序/编译器当然可以跟踪系统中的内存,远远超过人类。这就是为什么编译器平均产生比人类更好的汇编程序的原因(特定情况下人类可以随时调整或解决问题)。

错误处理,你需要小心使用标志,由于某种原因它不适合我。它可能很好,中断保留标志,你的代码都必须保留或设置标志等。嗯,标志的问题是你必须在函数返回后立即检查/使用该返回值,之前你有一个修改标志的指令。如果你使用一个寄存器,你可以在需要采样或使用该返回值之前选择不修改该返回寄存器以获得更多指令。

我认为这里的底线是,查看编译器用于该指令集的C调用约定规则,以及其他指令集,您将看到强烈的相似性并且有充分的理由。它们易于管理。由于寄存器太少,您可以看到为什么调用约定有时会直接进入所有参数的堆栈,有时也会返回返回值。我被告知Amiga bios为每个bios函数都有一个自定义调用约定,这使得执行紧凑而快速的系统,但乳清试图使用编译器在C中重新创建bios并使用汇编程序包装器附加到函数很困难。我确信没有关于每个功能的良好文档,它是无法管理的。在路上你可能会决定你想要这个便携式,并且可能希望你选择了一个常用的呼叫约定。你仍然想要注释你的代码,说参数1是这个,参数2是那个,等等。另一方面,如果你现在或过去编程过x86汇编程序调用DOS和BIOS调用,你会很满意将每个函数放在一个引用中,并将数据放在每个函数的适当寄存器中。因为有很好的参考资料,所以每个功能都可以自定义。