使用者介面

我敢說,現代計算系統中 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