用户界面

我敢说,现代计算系统中 80%的处理不需要用户交互,例如 Linux,OSX 和 Windows 的内核代码。对于那些做的人来说,有两个基本因素是通过键盘( 指点设备 )和控制台进行交互。本系列中的这个示例和其他示例都是基于基于文本的控制台(VT100 仿真)和键盘。

就其本身而言,这个例子非常简单,但它是更复杂算法的重要组成部分。

Subrtx.asm

        STDIN    equ        0
       STDOUT    equ        1

     SYS_READ    equ        0
    SYS_WRITE    equ        1

    global  gets, strlen, print, atoq

            section .text

由于这仅适用于键盘,因此错误概率几乎为零。我想大多数情况下,程序将能够考虑缓冲区大小来规避缓冲区溢出,但由于间接性,这无法保证。

; =============================================================================
; Accept canonical input from operator for a maximum of EDX bytes and replace
; terminating CR with NULL.

;        ENTER: RSI = Pointer to input buffer
;               EDX = Maximum number of characters

;        LEAVE: EAX = Number of characters entered
;               R11 = Modified by syscall, all others preserved.

;        FLAGS:  ZF = Null entry, NZ otherwise.
; _____________________________________________________________________________

     gets:  push    rcx
            push    rdi

            xor     eax, eax            ; RAX = SYS_READ
            mov     edi, eax            ; RDI = STDIN
            syscall

; TODO:    Should probably do some error trapping here, especially for
;            buffer overrun, but I'll see if it becomes an issue over time.

            dec     eax                 ; Bump back to CR
            mov     byte [rsi+rax], 0   ; Replace it with NULL

            pop     rdi
            pop     rcx
            ret

首先,这是为了避免编写或手动计算 write(2) 的字符串长度的需要。然后我决定加入一个分隔符,现在它可以用来扫描任何字符(0 - FF)。这打开了自动换行文本的可能性,例如,标签 strlen 有点用词不当,因为人们通常会认为结果将是可见字符的数量。

; =============================================================================
; Determine length, including terminating character EOS. Result may include
; VT100 escape sequences.

;        ENTER: RDI = Pointer to ASCII string.
;               RCX   Bits 31 - 08 = Max chars to scan (1 - 1.67e7)
;                           07 - 00 = Terminating character (0 - FF)

;        LEAVE: RAX = Pointer to next string (optional).

;        FLAGS:  ZF = Terminating character found, NZ otherwise (overrun).
; _____________________________________________________________________________

   strlen:  push    rcx                 ; Preserve registers used by proc so
            push    rdi                 ; it's non-destructive except for RAX.

            mov      al, cl             ; Byte to scan for in AL.
            shr     ecx, 8              ; Shift max count into bits 23 - 00

; NOTE: Probably should check direction flag here, but I always set and
;       reset DF in the process that is using it.

            repnz   scasb               ; Scan for AL or until ECX = 0
            mov     rax, rdi            ; Return pointer to EOS + 1

            pop     rdi                 ; Original pointer for proglogue
            jz      $ + 5               ; ZF indicates EOS was found
            mov     rax, rdi            ; RAX = RDI, NULL string
            pop     rcx

            ret

此过程的目的是简化调用过程中的循环设计。

; =============================================================================
; Display an ASCIIZ string on console that may have embedded VT100 sequences.

;        ENTER: RDI = Points to string

;        LEAVE: RAX = Number of characters displayed, including EOS
;                   = Error code if SF
;               RDI = Points to byte after EOS.
;               R11 = Modified by syscall all others preserved

;        FLAGS:  ZF = Terminating NULL was not found. NZ otherwise
;                SF = RAX is negated syscall error code.
;______________________________________________________________________________

    print:  push    rsi
            push    rdx
            push    rcx

            mov     ecx, -1 << 8        ; Scan for NULL
            call    strlen
            push    rax                 ; Preserve point to next string
            sub     rax, rdi            ; EAX = End pntr - Start pntr
            jz      .done

     ; size_t = write (int STDOUT, char *, size_t length)

            mov     edx, eax            ; RDX = length
            mov     rsi, rdi            ; RSI = Pointer
            mov     eax, SYS_WRITE
            mov     edi, eax            ; RDI = STDOUT
            syscall
            or      rax, rax            ; Sets SF if syscall error
            
; NOTE:    This procedure is intended for console, but in the event STDOUT is
;        redirected by some means, EAX may return error code from syscall.

    .done:  pop     rdi                 ; Retrieve pointer to next string.
            pop     rcx
            pop     rdx
            pop     rsi

            ret

最后是一个如何使用这些功能的例子。

Generic.asm

global  _start

    extern  print, gets, atoq

    SYS_EXIT  equ   60
         ESC  equ   27

       BSize  equ   96

            section .rodata
   Prompt:  db  ESC, '[2J'      ; VT100 clear screen
            db  ESC, '[4;11H'   ;   "   Position cursor to line 4 column 11
            db  'ASCII -> INT64 (binary, octal, hexidecimal, decimal), '
            db  'Packed & Unpacked BCD and floating point conversions'
            db  10, 10, 0, 9, 9, 9, '=> ', 0
            db  10, 9, 'Bye'
            db  ESC, '[0m'      ; VT100 Reset console
            db  10, 10, 0

            section .text
   _start:  pop    rdi
            mov    rsi, rsp
            and    rsp, byte 0xf0       ; Align stack on 16 byte boundary.

            call   main
            mov    rdi, rax             ; Copy return code into ARG0

            mov    eax, SYS_EXIT
            syscall

; int main ( int argc, char *args[] )

     main:  enter   BSize, 0            ; Input buffer on stack
            mov     edi, Prompt
            call    print
            lea     rsi, [rbp-BSize]    ; Establish pointer to input buffer
            mov     edx, BSize          ; Max size for read(2)

    .Next:  push    rdi                 ; Save pointer to "=> "
            call    print
            call    gets
            jz      .done

            call    atoq                ; Convert string pointed to by RSI 

            pop     rdi                 ; Restore pointer to prompt
            jmp     .Next

    .done:  call    print               ; RDI already points to "Bye"
            xor     eax, eax
            leave
            ret

Makefile 文件

OBJECTS = Subrtx.o Generic.o

Generic : $(OBJECTS)
    ld -oGeneric $(OBJECTS)
    readelf -WS Generic
    
Generic.o : Generic.asm
     nasm -g -felf64 Generic.asm
     
Subrtx.o : Subrtx.asm
    nasm -g -felf64 Subrtx.asm

clean:
    rm -f $(OBJECTS) Generic