Assembly Language
Linux elf64-Beispiele, die glibc nicht verwenden
Suche…
Benutzeroberfläche
Ich wage zu sagen, dass 80% der Verarbeitung, die in modernen Computersystemen stattfindet, keine Benutzerinteraktion erfordert, wie z. B. Kernelcode für Linux, OSX und Windows. Für diejenigen, die dies tun, gibt es zwei Grundlagen, die Interaktivität über Tastatur ( Zeigegeräte ) und Konsole sind. Dieses Beispiel und andere in meiner Serie beziehen sich auf textbasierte Konsole (VT100-Emulation) und Tastatur.
An sich ist dieses Beispiel sehr einfach, aber es ist ein wesentlicher Baustein für komplexere Algorithmen.
Subrtx.asm
STDIN equ 0
STDOUT equ 1
SYS_READ equ 0
SYS_WRITE equ 1
global gets, strlen, print, atoq
section .text
Da dies ausschließlich für die Tastatur vorgesehen ist, ist die Fehlerwahrscheinlichkeit nahezu unbegrenzt. Ich würde mir am häufigsten vorstellen, dass das Programm die Puffergröße zur Umgehung des Pufferüberlaufs in Betracht ziehen kann, aber dies ist aufgrund der Umleitung nicht garantiert.
; =============================================================================
; 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
Zunächst sollte dadurch die Notwendigkeit umgangen werden, entweder eine Zeichenfolgenlänge für das Schreiben zu codieren oder manuell zu berechnen (2). Dann entschied ich mich für ein Trennzeichen, jetzt kann es nach beliebigen Zeichen (0 - FF) durchsucht werden. Dies eröffnet die Möglichkeit, beispielsweise Text umbrochen zu werden , so dass die Beschriftung strlen ein wenig irreführend ist, da man im Allgemeinen annehmen würde, dass das Ergebnis die Anzahl der sichtbaren Zeichen ist.
; =============================================================================
; 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
Die Absicht dieser Prozedur besteht darin, das Design der Schleife in der aufrufenden Prozedur zu vereinfachen.
; =============================================================================
; 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
Zum Schluss noch ein Beispiel, wie diese Funktionen genutzt werden können.
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