11.5 建立可移植的代码
一般说来, 可移植性并非汇编语言的长项。 然而, 写出能够在不同平台上执行的汇编代码仍然是可能的事情, 特别是在使用 nasm 的时候。 我曾经写过一个汇编语言函数库, 可以在 Windows® 或 FreeBSD 这样不同的操作系统下进行汇编。
所以, 让你的代码在两种不同但是又基于相似的结构的平台上运行是完全可能的。
比如, FreeBSD 是 UNIX® 操作系统,Linux 是类UNIX 操作系统。 从一个汇编语言程序员的观点来看, 我只说明三个两者不同的地方: 调用方式, 功能号, 以及返回值的传递方式。
11.5.1 功能号的处理
许多情况下, 两个平台下的功能号是相同的。 当然, 即使它们不一样的时候, 问题也一样容易解决。 方法很简单,就是在代码中用常量替代数字, 这样你可以根据不同的系统结构进行不同的声明:
%ifdef LINUX %define SYS_execve 11 %else %define SYS_execve 59 %endif
11.5.2 编程规范的处理
调用规范和返回值( errno
相关的问题) 可以通过使用宏一起得到解决:
%ifdef LINUX %macro system 0 call kernel %endmacro align 4 kernel: push ebx push ecx push edx push esi push edi push ebp mov ebx, [esp+32] mov ecx, [esp+36] mov edx, [esp+40] mov esi, [esp+44] mov ebp, [esp+48] int 80h pop ebp pop edi pop esi pop edx pop ecx pop ebx or eax, eax js .errno clc ret .errno: neg eax stc ret %else %macro system 0 int 80h %endmacro %endif
11.5.3 与移植相关的其他问题处理
以上的方法可以解决 FreeBSD 和 Linux 之间的代码移植过程。 尽管如此, 一些内核服务之间的差异还是很大。
如果那样的话, 你需要针对那些特殊的调用编写代码, 并且使用针对环境的条件汇编。 不过, 幸运的是, 你的代码所进行地大部分工作不是在调用内核, 所以通常情况下, 你只需要在你的代码中增加一些针对环境的条件片段就可以了。
11.5.4 使用函数库
你可以通过为系统调用编写函数库来完全避免你主程序中的移植性问题。 所以,为 FreeBSD 建议一个独立的函数库吧, 为 Linux 建立另外的一个, 再为其他的操作系统建立这样的函数库。
在你的函数库中,为每一个系统调用编写独立的函数。 ( 如果你习惯于传统的汇编语言术语,我们也可以称之为程序) 使用 C 语言传递参数的方式,但是依然使用 EAX
来传递功能号。 如果那样, 你的 FreeBSD 函数库将非常简单, 因为许多看似不同的函数, 实际上都只是同一段代码的不同标签:
sys.open: sys.close: [etc...] int 80h ret
你的 Linux 函数库需要更多彼此不同的函数。 然而, 尽管如此, 仍可以将系统调用按其参数的个数进行分组:
sys.exit: sys.close: [etc... one-parameter functions] push ebx mov ebx, [esp+12] int 80h pop ebx jmp sys.return ... sys.return: or eax, eax js sys.err clc ret sys.err: neg eax stc ret
使用函数库的方法起初会看起来很不方便,因为它需要你去建立一个你的程序所依赖的独立的文件。 但是它又有很多优点:首先, 你只需要编写一次, 就可以在你所有的程序中使用。 甚至你可以让其他的汇编程序员或者其他程序使用。 不过, 使用函数库的最大的好处在于: 仅仅需要增加一个新的函数库, 你的程序就可以被任何人移植到其他系统上了。
如果你对使用函数库没有任何概念, 你至少可以将你所有的系统调用放置在一个独立的汇编语言文件中, 然后将它和你的主程序连接。这里,再次强调, 所有的移植程序的程序员所做的,就是建立一个新的对象文件, 然后连接到你的主程序中。
11.5.5 使用头文件
如果你发布的软件中包含代码, 你可以在你包含代码的地方使用宏, 将它们放置在一个独立的文件中。
移植你的软件的人只需要简单地写一个头文件, 不需要外部的对象文件。 你的程序将不加修改地被移植。
注意: 这个就是我们将在本章中使用的方法。 我们将把我们的头文件命名为 system.inc, 然后在我们使用新的系统调用时增加它。
我们从声明标准文件描述符开始,编写我们的 system.inc :
%define stdin 0 %define stdout 1 %define stderr 2
接下来,为每个系统调用指定符号名:
%define SYS_nosys 0 %define SYS_exit 1 %define SYS_fork 2 %define SYS_read 3 %define SYS_write 4 ; [etc...]
接下来增加一个短小的非全局子程序, 并给它其一个够长的名字, 以避免我们不慎在代码中使用同样的名字。
section .text align 4 access.the.bsd.kernel: int 80h ret
我们建立了一个带有一个参数的宏, 其系统调用号为:
%macro system 1 mov eax, %1 call access.the.bsd.kernel %endmacro
最后, 我们为每一个系统调用建立了一个宏。 这些宏不带有任何参数。
%macro sys.exit 0 system SYS_exit %endmacro %macro sys.fork 0 system SYS_fork %endmacro %macro sys.read 0 system SYS_read %endmacro %macro sys.write 0 system SYS_write %endmacro ; [etc...]
继续, 把它添加到你的编辑器中, 然后把它保存为 system.inc 。 我们将随着讨论得深入,将更多的系统调用添加进来。