当前位置: 首页 > 文档资料 > FreeBSD 开发手册 >

11.5 建立可移植的代码

优质
小牛编辑
129浏览
2023-12-01

一般说来, 可移植性并非汇编语言的长项。 然而, 写出能够在不同平台上执行的汇编代码仍然是可能的事情, 特别是在使用 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 。 我们将随着讨论得深入,将更多的系统调用添加进来。