在引导加载程序中使用的正确调用约定是什么?

What is the correct calling convention to use within a bootloader?

我正在尝试编写一个引导加载程序和一个极其原始和基本的内核来学习裸机编码实践和技术。无论如何,我正在使用 NASM 编写引导加载程序。我的代码可以正常工作,但我对要使用的调用约定有疑问。

我正在通过 运行 NASM 编译我的引导程序:nasm bootloader.asm -o bootloader.

在我的汇编代码中,我编写了像 BlDisplayString 这样的函数来通过 BIOS 中断 int 0x10AH = 0x13 显示字符串。我试图通过在 CX, DX, STACK 中传递参数来模拟 __fastcall 调用约定。这是在 16 位代码中使用的正确调用约定标准吗? CPU 未处于保护模式,当我调用这些函数时仍处于实模式。

CPU不在乎,做任何方便和可维护的事情。“正确性”的唯一判断者是你,如果你不想link 到 C 编译器生成的任何代码。

但是,是的,注册参数通常是个好主意,带有调用破坏的 AX、CX、DX。如果您愿意在每个 rep-string 函数之前设置它,让 ES 被调用破坏可能会很方便地避免函数 save/restore 它。

在寄存器中传递与 BIOS int 调用希望它们对齐的参数可能可以在包装代码中保存一些指令。

您甚至可以在每个函数的基础上使用自定义调用约定,但这更难记住/记录。对于仅从一个函数(但该函数中的多个位置)或从一个文件中的几个类似函数调用的本地辅助函数很有用。在注释中,注册输入、输出和破坏的文档(用作没有 save/restore 的临时 space)。

对于不同类型的函数有几个不同的调用约定是 1 个固定约定与每个函数不同的约定之间的中间地带。

在 FLAGS 中返回布尔条件对于 asm 来说很方便,尤其是当你希望你的调用者在它上面分支​​时。或者对于像 memcmp 这样的函数,以 cmp al, dl 或任何让你的调用者分支相等或更大/更小的函数,无论它想要读取哪个 FLAGS。所有这些都没有像 C 函数那样实际生成 + / 0 / - return 值的成本。

CodeGolf.SE Tips for golfing in x86/x64 machine code 上的回答详细介绍了如果您全力以赴编写小代码而不关心函数之间的可维护性或一致性,您可能会做什么。

如果您想将更多代码放入 512 字节的第一阶段引导加载程序,或者放入更少的额外扇区,通常可以节省 一些 字节而不影响可读性。 指令越少通常越容易阅读。 (不过,这并不总是与较小的机器代码大小相同。)

在我的引导加载程序中,调用约定全部由单独的协议组成,每个协议都专门针对相关功能量身定制。这需要尽可能多地保存字节,并塞入很多功能。每个函数都有 a protocol comment 指定输入、输出和更改的寄存器。

我主要看FAT32 loader,因为它其实有几个常规意义上的功能。这些是 read_sector, clust_to_first_sector, clust_next, and check_clust。接下来我将引用其中的两个评论。

这里是read_sector:

                ; Read a sector using Int13.02 or Int13.42
                ;
                ; INP:  dx:ax = sector number within partition
                ;       bx => buffer
                ;       (_LBA) ds = ss
                ; OUT:  If unable to read,
                ;        ! jumps to error instead of returning
                ;       If sector has been read,
                ;        dx:ax = next sector number (has been incremented)
                ;        bx => next buffer (bx = es+word[para_per_sector])
                ;        es = input bx
                ; CHG:  -
read_sector:

这是clust_to_first_sector:

                ; INP:  dx:ax = cluster - 2 (0-based cluster)
                ; OUT:  cx:bx = input dx:ax
                ;       dx:ax = first sector of that cluster
                ; CHG:  -
clust_to_first_sector:

the FSIBOOT entrypoint but this is not exactly a function. There's also the simpler FAT12/FAT16 loader, but this one only has one actual function, read_sector very much like the one I quoted, and one part where a function call was completely inlined into a loop 有另一个协议注释。