11.7 编写 UNIX® 过滤程序

  过滤程序是 UNIX® 中一种常见的应用程序, 它从标准输入 stdin 读入数据, 然后进行相关处理, 最后将结果写到标准输出 stdout

  在本节中, 我们将编写一个简单的过滤程序, 从而学习如何从标准输入 stdin 和标准输出 stdout 进行读写。 这个过滤程序将按字节把输入转换成16进制的数字, 并在每个数字的后面添加一个空格。

%include   'system.inc'

section .data
hex db  '0123456789ABCDEF'
buffer  db  0, 0, ' '

section .text
global  _start
_start:
    ; read a byte from stdin
    push    dword 1
    push    dword buffer
    push    dword stdin
    sys.read
    add esp, byte 12
    or  eax, eax
    je  .done

    ; convert it to hex
    movzx   eax, byte [buffer]
    mov edx, eax
    shr dl, 4
    mov dl, [hex+edx]
    mov [buffer], dl
    and al, 0Fh
    mov al, [hex+eax]
    mov [buffer+1], al

    ; print it
    push    dword 3
    push    dword buffer
    push    dword stdout
    sys.write
    add esp, byte 12
    jmp short _start

.done:
    push    dword 0
    sys.exit

  在数据段, 我们建立一个叫做 hex 的数组, 它包含了按照升序排列的16进制数字。 在这个数组的后面, 是一个输入输出都会用到的缓存。 缓存的头两个字节被初始化设置为 0。 这里,我们将输出两个16进制数字( 第一个字节也同样是我们读取输入的地方 )。 第三个字节是空格。

  代码段由四部分组成: 读入一字节, 转换成16进制数字, 输出结果, 结束程序。

  为了读入一字节的数据, 我们命令系统从标准输入 stdin 中读出一个字节的数据, 然后将它存储在缓存 buffer 的第一个字节中。 系统将把返回值存放在寄存器 EAX 中。 返回值如果为 1 则代表有数据输入, 如果为 0, 则表示没有数据输入。 因此, 我们检查 EAX 中的数值,如果它为 0 我们的程序将跳转至 .done, 否则我们的程序将继续执行操作。

注意: 出于简单实现程序基本功能的目的, 我们忽略了针对某些可能发生的错误的处理。

  16进制转换程序首先从缓存 buffer 中读出一字节的值, 并将其写入 EAX, 或者, 更确切地说, 这部分是将数据写入 AL, 并把 EAX 的其他部分清零。 同时, 也将数据复制到 EDX 中, 因为我们需要独立转换高4位和低4位的数据。 最后, 把结果存放在和缓存的头两个字节中。

  下面, 我们将让系统将缓存中的这三个字节, 就是两个16进制数字和一个空格, 输出到标准输出 stdout 中。 然后我们的程序将跳转至程序开始处,处理下一个字节。

  一旦没有输入, 我们将命令系统终止程序, 返回0。 通常情况下, 使用0作为返回值代表着程序已经执行成功。

  接下来, 将程序保存到名为 hex.asm 的文件中, 然后输入以下内容 (符号 ^D 代表在按下控制键的同时, 按下键盘 D):

% nasm -f elf hex.asm
% ld -s -o hex hex.o
% ./hex
Hello, World!
48 65 6C 6C 6F 2C 20 57 6F 72 6C 64 21 0A Here I come!
48 65 72 65 20 49 20 63 6F 6D 65 21 0A ^D %

注意: 如果你是从 MS-DOS® 上转到 UNIX 的程序员, 你会很奇怪为什么每行结束的时候没有使用 0D 0A 而是使用了 0A。 这是因为 UNIX 使用的不是 cr/lf, 而是换行符号, 也就是是16进制数字 0A 所代表的字符来表示换行。

  我们能让它看起来更好吗?当然,很明显的一个问题, 当我们转换了一行文字后,我们的输入不再处在行开始的位置。 我们可以修改它,让它在每个 0A 后输出一个新行, 而不是输出一个空格:

%include   'system.inc'

section .data
hex db  '0123456789ABCDEF'
buffer  db  0, 0, ' '

section .text
global  _start
_start:
    mov cl, ' '

.loop:
    ; read a byte from stdin
    push    dword 1
    push    dword buffer
    push    dword stdin
    sys.read
    add esp, byte 12
    or  eax, eax
    je  .done

    ; convert it to hex
    movzx   eax, byte [buffer]
    mov [buffer+2], cl
    cmp al, 0Ah
    jne .hex
    mov [buffer+2], al

.hex:
    mov edx, eax
    shr dl, 4
    mov dl, [hex+edx]
    mov [buffer], dl
    and al, 0Fh
    mov al, [hex+eax]
    mov [buffer+1], al

    ; print it
    push    dword 3
    push    dword buffer
    push    dword stdout
    sys.write
    add esp, byte 12
    jmp short .loop

.done:
    push    dword 0
    sys.exit

  我们将空格存储在寄存器 CL 中。 这样做不会导致问题, 因为与 Microsoft® Windows® 不同, UNIX 系统调用, 除返回值之外, 并不修改其它寄存器。

  所以我们只需要把数值送入 CL 一次即可。 因此我们添加了一个新的标签 .loop, 在下一个字节处理的时候, 跳转到那里,而不是回到 _start。 我们也同样增加了一个 .hex 标签, 这样对第三个字节, 我们既可以赋值为空格, 又可以换行符号。

  如果你想在 hex.asm 中反映这些变化,请输入:

% nasm -f elf hex.asm
% ld -s -o hex hex.o
% ./hex
Hello, World!
48 65 6C 6C 6F 2C 20 57 6F 72 6C 64 21 0A
Here I come!
48 65 72 65 20 49 20 63 6F 6D 65 21 0A
^D %

  这样看起来就好了一些。 但是程序的效率还不高! 我们针对一个字节, 进行了两次系统调用 (一次是读取, 另一次是输出)

本文档和其它文档可从这里下载:ftp://ftp.FreeBSD.org/pub/FreeBSD/doc/.

如果对于FreeBSD有问题,请先阅读文档,如不能解决再联系<questions@FreeBSD.org>.
关于本文档的问题请发信联系 <doc@FreeBSD.org>.