Assembly Language
Przykłady elf64 dla systemu Linux, które nie używają glibc
Szukaj…
Interfejs użytkownika
Zaryzykowałbym stwierdzenie, że 80% przetwarzania, które odbywa się w nowoczesnych systemach komputerowych, nie wymaga interakcji użytkownika, takich jak kod jądra dla systemów Linux, OSX i Windows. Dla tych, którzy tak robią, są dwie podstawy, którymi są interaktywność za pomocą klawiatury ( urządzenia wskazujące ) i konsoli. Ten przykład i inne z mojej serii dotyczą konsoli tekstowej (emulacja VT100) i klawiatury.
Sam w sobie ten przykład jest bardzo prosty, ale stanowi niezbędny element budujący bardziej złożone algorytmy.
Subrtx.asm
STDIN equ 0
STDOUT equ 1
SYS_READ equ 0
SYS_WRITE equ 1
global gets, strlen, print, atoq
section .text
Ponieważ jest to przeznaczone wyłącznie dla klawiatury, prawdopodobieństwo błędów jest prawie zerowe. Wyobrażam sobie najczęściej, że program będzie w stanie rozważyć rozmiar bufora, aby obejść jego przekroczenie, ale nie jest to gwarantowane z powodu pośredniej.
; =============================================================================
; 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
Po pierwsze, miało to na celu ominięcie potrzeby albo kodowania, albo ręcznego obliczenia długości łańcuchów do zapisu (2). Potem zdecydowałem się zastosować ogranicznik, teraz można go użyć do skanowania dowolnego znaku (0 - FF). Otwiera to na przykład możliwość zawijania tekstu, więc strlen na etykiecie jest nieco mylący, ponieważ ogólnie można by pomyśleć, że wynikiem będzie liczba widocznych znaków.
; =============================================================================
; 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
Celem tej procedury jest uproszczenie projektowania pętli w procedurze wywoływania.
; =============================================================================
; 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
Wreszcie przykład wykorzystania tych funkcji.
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