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

11.9 命令行参数

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

如果我们的 hex 程序能读取通过命令行传给它的输入输出文件名, 也就是如果它能处理命令行参数的话, 那它就更好用了。 但是, 这些参数在哪里呢?

在 UNIX® 系统启动程序之前, 它会将一些数据 push 到堆栈中, 接着跳转到程序的 _start 标签。 是的, 我说的是跳转而不是调用。 这意味着, 这些数据可以通过读取 [esp+offset], 或更简单的 pop 得到。

�顶的值包含了命令行参数的个数。 传统上它叫做 argc, 表示 “argument count”。

接下来是 argc 个命令行参数。 传统上这些称为 argv, 表示 “argument value(s)”。 这样, 我们便得到了 argv[0]argv[1]...argv[argc-1]。 这些并不是实际的参数, 而是指向这些参数的指针, 也就是实际参数的内存地址。 参数本身是以 NUL-结尾的字符串形式存放的。

argv 表以一 NULL 指针结束, 这个指针的值就是 0。 还有一些其它的数据, 但前面这些已经足以让我们达到目的了。

注意: 如果你以前在 MS-DOS® 环境下编程, 现在的主要的区别就是每个参数都在不同的string里头。第二个不同点就是对于参数数量没有实际的限制。

通过这些知识的武装,我们几乎可以立即开始下一个版本的 hex.asm 了。 首先,不论如何,我们需要在 system.inc 增加一些代码:

首先,为我们的系统调用编号清单增加两个新的入口:

%define    SYS_open    5
%define SYS_close   6

接下来在文件尾部增加两个新的宏:

%macro sys.open    0
    system  SYS_open
%endmacro

%macro  sys.close   0
    system  SYS_close
%endmacro

然后就是我们改过的源码:

%include   'system.inc'

%define BUFSIZE 2048

section .data
fd.in   dd  stdin
fd.out  dd  stdout
hex db  '0123456789ABCDEF'

section .bss
ibuffer resb    BUFSIZE
obuffer resb    BUFSIZE

section .text
align 4
err:
    push    dword 1     ; return failure
    sys.exit

align 4
global  _start
_start:
    add esp, byte 8 ; discard argc and argv[0]

    pop ecx
    jecxz   .init       ; no more arguments

    ; ECX contains the path to input file
    push    dword 0     ; O_RDONLY
    push    ecx
    sys.open
    jc  err     ; open failed

    add esp, byte 8
    mov [fd.in], eax

    pop ecx
    jecxz   .init       ; no more arguments

    ; ECX contains the path to output file
    push    dword 420   ; file mode (644 octal)
    push    dword 0200h | 0400h | 01h
    ; O_CREAT | O_TRUNC | O_WRONLY
    push    ecx
    sys.open
    jc  err

    add esp, byte 12
    mov [fd.out], eax

.init:
    sub eax, eax
    sub ebx, ebx
    sub ecx, ecx
    mov edi, obuffer

.loop:
    ; read a byte from input file or stdin
    call    getchar

    ; convert it to hex
    mov dl, al
    shr al, 4
    mov al, [hex+eax]
    call    putchar

    mov al, dl
    and al, 0Fh
    mov al, [hex+eax]
    call    putchar

    mov al, ' '
    cmp dl, 0Ah
    jne .put
    mov al, dl

.put:
    call    putchar
    cmp al, dl
    jne .loop
    call    write
    jmp short .loop

align 4
getchar:
    or  ebx, ebx
    jne .fetch

    call    read

.fetch:
    lodsb
    dec ebx
    ret

read:
    push    dword BUFSIZE
    mov esi, ibuffer
    push    esi
    push    dword [fd.in]
    sys.read
    add esp, byte 12
    mov ebx, eax
    or  eax, eax
    je  .done
    sub eax, eax
    ret

align 4
.done:
    call    write       ; flush output buffer

    ; close files
    push    dword [fd.in]
    sys.close

    push    dword [fd.out]
    sys.close

    ; return success
    push    dword 0
    sys.exit

align 4
putchar:
    stosb
    inc ecx
    cmp ecx, BUFSIZE
    je  write
    ret

align 4
write:
    sub edi, ecx    ; start of buffer
    push    ecx
    push    edi
    push    dword [fd.out]
    sys.write
    add esp, byte 12
    sub eax, eax
    sub ecx, ecx    ; buffer is empty now
    ret

现在我们在 .data 区里头有了两个新的变量 fd.infd.out. 我们把输入输出文件描述符放在这.

我们还在 .text 区里用 [fd.in][fd.out] 替换了指向 stdinstdout 的引用。.

现在,.text 段以一个简单的错误处理程序开始,——这个程序除了退出程序时返回 1 之外啥都不干。 该错误处理程序在 _start 之前,由此我们一旦碰到错误,距离会很近。

很自然, 这个程序还是从 _start 开始执行 首先,我们把 argcargv[0] 从栈中移走:在这个程序里头,他们对我们没意义。

argv[1] 出栈, 放到 ECX 。这个寄存器很适合于指针,就如同我们把 NULL 指针用 jecxz 处理。 如果 argv[1] 不为空,我们尝试打开第一个参数中的文件。否则我们将照常继续程序:从 stdin 中读取,写入 stdin 。 假设我们还是在打开文件时候失败 (比如文件不存在),跳转到错误处理然后退出。

如果一切顺利, 我们现在可以检查第二个参数。假设文件存在,我们就带开输出文件。否则,把输出送到 stdout。 如果在打开输出文件时候失败(比如文件已经存在但是我们没有写权限),那我们就再来一次错误处理。

剩下的代码跟之前一样,除开我们在退出之前关闭了输入和输出,如前所述,我们使用的是 [fd.in] and [fd.out]

然后768 字节大小的可执行文件到手。

  我们是否可以进一步改进?理所当然!每个程序都可以改进。以下是一些我们可以做点啥的想法:

  • 我们的错误处理是否输出信息到 stderr.

  • 把错误处理加入 readwrite 函数。

  • 当我们打开文件用于输入时,关闭 stdin, 反之(输出时)关闭stdout

  • 增加命令行参数,比如 -i-o , 这样我们就能用任何次序列出输入和输出文件,或者从 stdin 读入然后写入某个文件。

  • 当命令行参数不正确的时候,打印一份帮助。

I shall leave these enhancements as an exercise to the reader: You already know everything you need to know to implement them.