为了能够重复利用现有代码,在代码与代码之间调用,所有程序必须遵守本编程规范。
约定是必须遵守的规定,是程序之间沟通的桥梁,不遵守会导致程序不能与现有的程序库交互。
规范是强烈建议遵守的规定,是为了方便开发者理解代码,使代码易于维护,方便不同开发者使用相同的方法理解不同人编写的代码。
_start
函数,在其中编写用户代码。
每个指令与其操作数使用一个空格隔开。操作数之间使用一个逗号 + 空格(,
)隔开。
MOV ax, bx
所有标签使用下划线分词。除 Calling convention 所规定的特殊前后缀外,一律使用小写字母。
nimshab_miga:
如果注释为自然语言,使用一个分号 + 空格 + 正文,确保首字母大写,且在单词间换行时使用连字符。
如果是汇编对应的 C 代码,使用两个分号 + 正文,确保首字母小写。
; Test running n ; -uclear reactor ; for a while ;;out_h_for_whi ;;le(0, 5); MOV c, 0 MOV d, 5 CALL out_h_for_while
若函数所遵循的调用约定规定要在函数中通过压栈的方式暂存寄存器状态,则分三块结构编写:
遵照如下所示的结构:
; 【FUNC】<参数及返回值说明>==== <函数名>: ; Init regs PUSH xx PUSH xx ; Main logics ............ ; Exit POP xx POP xx RET
如果函数内有分支(如 b1 b2 两个分支),那么遵照如下所示的结构:
; 【FUNC】<参数及返回值说明>==== <函数名>: ; Init regs PUSH xx PUSH xx ; Main logic ............ <函数名缩写>__b1: ............ JMP <函数名缩写>__exit <函数名缩写>__b2: ............ JMP <函数名缩写>__exit <函数名缩写>__exit: POP xx POP xx RET
例如:
汇编
; 【FUNC】d,c==== ; d: b fast_boot start_machine: ; Init regs. PUSH c ; Main logic. ;;if(fast_boot) CMP d, 0 JZ sm__if_fbot JMP sm__if_fbot_el ;;{ sm__if_fbot: ; 快速启动 逻辑 JMP sm__ps_if_fbot ;;} else { sm__if_fbot_el: ; 慢速启动 逻辑 sm__ps_if_fbot: ;;} ; 通知启动成功 逻辑 MOV c, 0x10 MOV d, 0 CALL out_h_for_while ; Exit. POP c RET
C 语言
#include <stdbool.h> void start_machine(bool fast_boot) { if (fast_boot) { // 快速启动 逻辑 } else { // 慢速启动 逻辑 } // 通知启动成功 逻辑 out_h_for_while(0, 0x10); }
函数标记中包含「参数及返回值说明」。它的规则如下:
如果函数没有返回用寄存器列表,则使用下面的语法:
[传参用寄存器列表][,内部使用的寄存器列表]
如果函数有返回用寄存器列表,则使用下面的语法:
[传参用寄存器列表],[内部使用的寄存器列表],[返回用寄存器列表]
如果函数既没有传参用寄存器,也没有内部使用的寄存器和返回用寄存器,则使用下面的语法:
每个非内部函数(与其他程序共享的函数而非自己使用的函数)都需要具备文档。文档需要按顺序阐明:
调用约定有以下几种:
StdCall, StdCallR, QCall, CDecl
_start
函数是例外的,它不遵守任何调用约定。_start
函数不需要做任何额外的事情(如 PUSH 内部用到的寄存器)。
“Standard call”,标准调用。
使用寄存器传参,没有返回值。
采用 StdCall 的函数最好使用 d c b a 的顺序用寄存器保存参数。
采用 StdCall 的函数不加任何后缀。
被调用的函数在所有逻辑开始前必须将自身使用的寄存器通过压栈的方式暂存状态,退出前必须复原自身使用的寄存器的状态。
“Standard call with return(s)”.
使用寄存器传参,使用寄存器返回值。
采用 StdCallR 的函数最好使用 d c b a 的顺序用寄存器先后保存返回值与参数。
采用 StdCallR 的函数必须加后缀 _Rx
。其中 x 为返回值使用的寄存器的列表。如欲编写函数 “get_train_speed” 使用寄存器 d 返回列车的速度,则应当命名为 get_train_speed_Rd
。
被调用的函数在所有逻辑开始前必须将自身使用的寄存器中,除了返回值占用的寄存器通过压栈的方式暂存状态;退出前必须复原自身使用的寄存器,除了返回值占用的寄存器的状态。
若调用方使用了被调用函数返回值占用的寄存器,则在 CALL 函数前必须将函数返回值占用的寄存器通过压栈的方式暂存状态,CALL 后择机复原寄存器状态。
“Quirks call”,「怪诞调用」。本调用约定是为了应对数量较多的返回值。
使用寄存器传参,使用栈返回值。
采用 QCall 的函数最好使用 a b c d 的顺序用寄存器保存自身状态,使用 d c b a 的顺序用寄存器保存参数。
采用 StdCallR 的函数必须加后缀 _Q
。
无论调用方使用是否使用了被调用函数内部使用的寄存器,调用方在 CALL 函数前都必须将被调用函数内部使用的寄存器通过压栈的方式暂存状态,CALL 结束后自行择机复原寄存器状态。
被调用的函数在退出前不复原自身使用的寄存器的状态。
“C declaration”,C 语言的调用约定。本调用约定是为了应对数量较多的参数。
使用栈传参,使用寄存器返回值。
采用 CDecl 的函数必须加前缀 _
。
调用方调用一个 CDecl 函数的流程是:
被调用的函数在退出前不复原自身使用的寄存器的状态,不将参数出栈。
首先要说明的是,微处理器 mod 的硬件极其有限,且与现实世界中的计算机非常不同。这些不同体现在:
是故,给微处理器 mod 编程,比给现实世界中的计算机编程要困难很多。所以才出现了很多「怪异」的事情。如:
等。