FreeBSD Developers' Handbook
Zurück | Kapitel 11. x86-Assembler-Programmierung | Weiter

# 11.7. UNIX®-Filter schreiben

Übersetzt von Hagen Kühl.

Ein häufiger Typ von UNIX®-Anwendungen ist ein Filter -- ein Programm, das Eingaben von `stdin` liest, sie verarbeitet und das Ergebnis nach `stdout` schreibt.

In diesem Kapitel möchten wir einen einfachen Filter entwickeln und lernen, wie wir von `stdin` lesen und nach `stdout` schreiben. Dieser Filter soll jedes Byte seiner Eingabe in eine hexadezimale Zahl gefolgt von einem Leerzeichen umwandeln.

    %include	'system.inc'

    section	.data
    hex	db	'0123456789ABCDEF'
    buffer	db	0, 0, ' '

    section	.text
    global	_start
    _start:
    	; read a byte from stdin
    	push	dword 1
    	push	dword buffer
    	push	dword stdin
    	sys.read
    	add	esp, byte 12
    	or	eax, eax
    	je	.done

    	; convert it to hex
    	movzx	eax, byte [buffer]
    	mov	edx, eax
    	shr	dl, 4
    	mov	dl, [hex+edx]
    	mov	[buffer], dl
    	and	al, 0Fh
    	mov	al, [hex+eax]
    	mov	[buffer+1], al

    	; print it
    	push	dword 3
    	push	dword buffer
    	push	dword stdout
    	sys.write
    	add	esp, byte 12
    	jmp	short _start

    .done:
    	push	dword 0
    	sys.exit

Im Datenabschnitt erzeugen wir ein Array mit Namen `hex`. Es enthält die 16 hexadezimalen Ziffern in aufsteigender Reihenfolge. Diesem Array folgt ein Puffer, den wir sowohl für die Ein- als auch für die Ausgabe verwenden. Die ersten beiden Bytes dieses Puffers werden am Anfang auf `0` gesetzt. Dorthin schreiben wir die beiden hexadezimalen Ziffern (das erste Byte ist auch die Stelle an die wir die Eingabe lesen). Das dritte Byte ist ein Leerzeichen.

Der Code-Abschnitt besteht aus vier Teilen: Das Byte lesen, es in eine hexadezimale Zahl umwandeln, das Ergebnis schreiben und letztendlich das Programm verlassen.

Um das Byte zu lesen, bitten wir das System ein Byte von `stdin` zu lesen und speichern es im ersten Byte von `buffer`. Das System gibt die Anzahl an Bytes, die gelesen wurden, in `EAX` zurück. Diese wird `1` sein, wenn eine Eingabe empfangen wird und `0`, wenn keine Eingabedaten mehr verfügbar sind. Deshalb überprüfen wir den Wert von `EAX`. Wenn dieser `0` ist, springen wir zu `.done`, ansonsten fahren wir fort.

> Anmerkung: Zu Gunsten der Einfachheit ignorieren wir hier die Möglichkeit eines Fehlers.

Die Umwandlungsroutine in eine Hexadezimalzahl liest das Byte aus `buffer` in `EAX`, oder genaugenommen nur in `AL`, wobei die übrigen Bits von `EAX` auf null gesetzt werden. Außerdem kopieren wir das Byte nach `EDX`, da wir die oberen vier Bits (Nibble) getrennt von den unteren vier Bits umwandeln müssen. Das Ergebnis speichern wir in den ersten beiden Bytes des Puffers.

Als Nächstes bitten wir das System die drei Bytes in den Puffer zu schreiben, also die zwei hexadezimalen Ziffern und das Leerzeichen nach `stdout`. Danach springen wir wieder an den Anfang des Programms und verarbeiten das nächste Byte.

Wenn die gesamte Eingabe verarbeitet ist, bitten wie das System unser Programm zu beenden und null zurückzuliefern, welches traditionell die Bedeutung hat, dass unser Programm erfolgreich war.

Fahren Sie fort und speichern Sie den Code in eine Datei namens `hex.asm`. Geben Sie danach folgendes ein (`^D` bedeutet, dass Sie die Steuerungstaste drücken und dann `D` eingeben, während Sie Steuerung gedrückt halten):

    % nasm -f elf hex.asm
    % ld -s -o hex hex.o
    % ./hex
    Hello, World!
    48 65 6C 6C 6F 2C 20 57 6F 72 6C 64 21 0A Here I come!
    48 65 72 65 20 49 20 63 6F 6D 65 21 0A ^D %

> Anmerkung: Wenn Sie von MS-DOS® zu UNIX wechseln, wundern Sie sich vielleicht, warum jede Zeile mit `0A` an Stelle von `0D 0A` endet. Das liegt daran, dass UNIX nicht die CR/LF-Konvention, sondern die "new line"-Konvention verwendet, welches hexadezimal als `0A` dargestellt wird.

Können wir das Programm verbessern? Nun, einerseits ist es etwas verwirrend, dass die Eingabe, nachdem wir eine Zeile verarbeitet haben, nicht wieder am Anfang der Zeile beginnt. Deshalb können wir unser Programm anpassen um einen Zeilenumbruch an Stelle eines Leerzeichens nach jedem `0A` auszugeben:

    %include	'system.inc'

    section	.data
    hex	db	'0123456789ABCDEF'
    buffer	db	0, 0, ' '

    section	.text
    global	_start
    _start:
    	mov	cl, ' '

    .loop:
    	; read a byte from stdin
    	push	dword 1
    	push	dword buffer
    	push	dword stdin
    	sys.read
    	add	esp, byte 12
    	or	eax, eax
    	je	.done

    	; convert it to hex
    	movzx	eax, byte [buffer]
    	mov	[buffer+2], cl
    	cmp	al, 0Ah
    	jne	.hex
    	mov	[buffer+2], al

    .hex:
    	mov	edx, eax
    	shr	dl, 4
    	mov	dl, [hex+edx]
    	mov	[buffer], dl
    	and	al, 0Fh
    	mov	al, [hex+eax]
    	mov	[buffer+1], al

    	; print it
    	push	dword 3
    	push	dword buffer
    	push	dword stdout
    	sys.write
    	add	esp, byte 12
    	jmp	short .loop

    .done:
    	push	dword 0
    	sys.exit

Wir haben das Leerzeichen im Register `CL` abgelegt. Das können wir bedenkenlos tun, da UNIX-Systemaufrufe im Gegensatz zu denen von Microsoft® Windows® keine Werte von Registern ändern in denen sie keine Werte zurückliefern.

Das bedeutet, dass wir `CL` nur einmal setzen müssen. Dafür haben wir ein neues Label `.loop` eingefügt, zu dem wir an Stelle von `_start` springen, um das nächste Byte einzulesen. Außerdem haben wir das Label `.hex` eingefügt, somit können wir wahlweise ein Leerzeichen oder einen Zeilenumbruch im dritten Byte von `buffer` ablegen.

Nachdem Sie `hex.asm` entsprechend der Neuerungen geändert haben, geben Sie Folgendes ein:

    % nasm -f elf hex.asm
    % ld -s -o hex hex.o
    % ./hex
    Hello, World!
    48 65 6C 6C 6F 2C 20 57 6F 72 6C 64 21 0A
    Here I come!
    48 65 72 65 20 49 20 63 6F 6D 65 21 0A
    ^D %

Das sieht doch schon besser aus. Aber der Code ist ziemlich ineffizient! Wir führen für jeden einzelne Byte zweimal einen Systemaufruf aus (einen zum Lesen und einen um es in die Ausgabe zu schreiben).

Zurück | Zum Anfang | Weiter
Unser erstes Programm | Nach oben | Gepufferte Eingabe und Ausgabe

Wenn Sie Fragen zu FreeBSD haben, schicken Sie eine E-Mail an <de-bsd-questions@de.FreeBSD.org>.
Wenn Sie Fragen zu dieser Dokumentation haben, schicken Sie eine E-Mail an <de-bsd-translators@de.FreeBSD.org>.