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

# 11.9. Kommandozeilenparameter

Übersetzt von Fabian Ruch.

Unser hex-Programm wird nützlicher, wenn es die Dateinamen der Ein- und Ausgabedatei über die Kommandozeile einlesen kann, d.h., wenn es Kommandozeilenparameter verarbeiten kann. Aber... Wo sind die?

Bevor ein UNIX®-System ein Programm ausführt, legt es einige Daten auf dem Stack ab (`push`) und springt dann an das `_start`-Label des Programms. Ja, ich sagte springen, nicht aufrufen. Das bedeutet, dass auf die Daten zugegriffen werden kann, indem `[esp+offset]` ausgelesen wird oder die Daten einfach vom Stack genommen werden (`pop`).

Der Wert ganz oben auf dem Stack enthält die Zahl der Kommandozeilenparameter. Er wird traditionell `argc` wie "argument count" genannt.

Die Kommandozeilenparameter folgen einander, alle `argc`. Von diesen wird üblicherweise als `argv` wie "argument value(s)" gesprochen. So erhalten wir `argv[0]`, `argv[1]`, `...` und `argv[argc-1]`. Dies sind nicht die eigentlichen Parameter, sondern Zeiger (Pointer) auf diese, d.h., Speicheradressen der tatsächlichen Parameter. Die Parameter selbst sind durch NULL beendete Zeichenketten.

Der `argv`-Liste folgt ein NULL-Zeiger, was einfach eine `0` ist. Es gibt noch mehr, aber dies ist erst einmal genug für unsere Zwecke.

> Anmerkung: Falls Sie von der MS-DOS®-Programmierumgebung kommen, ist der größte Unterschied die Tatsache, dass jeder Parameter eine separate Zeichenkette ist. Der zweite Unterschied ist, dass es praktisch keine Grenze gibt, wie viele Parameter vorhanden sein können.

Ausgerüstet mit diesen Kenntnissen, sind wir beinahe bereit für eine weitere Version von `hex.asm`. Zuerst müssen wir jedoch noch ein paar Zeilen zu `system.inc` hinzufügen:

Erstens benötigen wir zwei neue Einträge in unserer Liste mit den Systemaufrufnummern:

    %define	SYS_open	5
    %define	SYS_close	6

Zweitens fügen wir zwei neue Makros am Ende der Datei ein:

    %macro	sys.open	0
    	system	SYS_open
    %endmacro

    %macro	sys.close	0
    	system	SYS_close
    %endmacro

Und hier ist schließlich unser veränderter Quelltext:

    %include	'system.inc'

    %define	BUFSIZE	2048

    section	.data
    fd.in	dd	stdin
    fd.out	dd	stdout
    hex	db	'0123456789ABCDEF'

    section .bss
    ibuffer	resb	BUFSIZE
    obuffer	resb	BUFSIZE

    section	.text
    align 4
    err:
    	push	dword 1		; return failure
    	sys.exit

    align 4
    global	_start
    _start:
    	add	esp, byte 8	; discard argc and argv[0]

    	pop	ecx
    	jecxz	.init		; no more arguments

    	; ECX contains the path to input file
    	push	dword 0		; O_RDONLY
    	push	ecx
    	sys.open
    	jc	err		; open failed

    	add	esp, byte 8
    	mov	[fd.in], eax

    	pop	ecx
    	jecxz	.init		; no more arguments

    	; ECX contains the path to output file
    	push	dword 420	; file mode (644 octal)
    	push	dword 0200h | 0400h | 01h
    	; O_CREAT | O_TRUNC | O_WRONLY
    	push	ecx
    	sys.open
    	jc	err

    	add	esp, byte 12
    	mov	[fd.out], eax

    .init:
    	sub	eax, eax
    	sub	ebx, ebx
    	sub	ecx, ecx
    	mov	edi, obuffer

    .loop:
    	; read a byte from input file or stdin
    	call	getchar

    	; convert it to hex
    	mov	dl, al
    	shr	al, 4
    	mov	al, [hex+eax]
    	call	putchar

    	mov	al, dl
    	and	al, 0Fh
    	mov	al, [hex+eax]
    	call	putchar

    	mov	al, ' '
    	cmp	dl, 0Ah
    	jne	.put
    	mov	al, dl

    .put:
    	call	putchar
    	cmp	al, dl
    	jne	.loop
    	call	write
    	jmp	short .loop

    align 4
    getchar:
    	or	ebx, ebx
    	jne	.fetch

    	call	read

    .fetch:
    	lodsb
    	dec	ebx
    	ret

    read:
    	push	dword BUFSIZE
    	mov	esi, ibuffer
    	push	esi
    	push	dword [fd.in]
    	sys.read
    	add	esp, byte 12
    	mov	ebx, eax
    	or	eax, eax
    	je	.done
    	sub	eax, eax
    	ret

    align 4
    .done:
    	call	write		; flush output buffer

    	; close files
    	push	dword [fd.in]
    	sys.close

    	push	dword [fd.out]
    	sys.close

    	; return success
    	push	dword 0
    	sys.exit

    align 4
    putchar:
    	stosb
    	inc	ecx
    	cmp	ecx, BUFSIZE
    	je	write
    	ret

    align 4
    write:
    	sub	edi, ecx	; start of buffer
    	push	ecx
    	push	edi
    	push	dword [fd.out]
    	sys.write
    	add	esp, byte 12
    	sub	eax, eax
    	sub	ecx, ecx	; buffer is empty now
    	ret

In unserem `.data`-Abschnitt befinden sich nun die zwei neuen Variablen `fd.in` und `fd.out`. Hier legen wir die Dateideskriptoren der Ein- und Ausgabedatei ab.

Im `.text`-Abschnitt haben wir die Verweise auf `stdin` und `stdout` durch `[fd.in]` und `[fd.out]` ersetzt.

Der `.text`-Abschnitt beginnt nun mit einer einfachen Fehlerbehandlung, welche nur das Programm mit einem Rückgabewert von `1` beendet. Die Fehlerbehandlung befindet sich vor `_start`, sodass wir in geringer Entfernung von der Stelle sind, an der der Fehler auftritt.

Selbstverständlich beginnt die Programmausführung immer noch bei `_start`. Zuerst entfernen wir `argc` und `argv[0]` vom Stack: Sie sind für uns nicht von Interesse (sprich, in diesem Programm).

Wir nehmen `argv[1]` vom Stack und legen es in `ECX` ab. Dieses Register ist besonders für Zeiger geeignet, da wir mit `jecxz` NULL-Zeiger verarbeiten können. Falls `argv[1]` nicht NULL ist, versuchen wir, die Datei zu öffnen, die der erste Parameter festlegt. Andernfalls fahren wir mit dem Programm fort wie vorher: Lesen von `stdin` und Schreiben nach `stdout`. Falls wir die Eingabedatei nicht öffnen können (z.B. sie ist nicht vorhanden), springen wir zur Fehlerbehandlung und beenden das Programm.

Falls es keine Probleme gibt, sehen wir nun nach dem zweiten Parameter. Falls er vorhanden ist, öffnen wir die Ausgabedatei. Andernfalls schreiben wir die Ausgabe nach `stdout`. Falls wir die Ausgabedatei nicht öffnen können (z.B. sie ist zwar vorhanden, aber wir haben keine Schreibberechtigung), springen wir auch wieder in die Fehlerbehandlung.

Der Rest des Codes ist derselbe wie vorher, außer dem Schließen der Ein- und Ausgabedatei vor dem Verlassen des Programms und, wie bereits erwähnt, die Benutzung von `[fd.in]` und `[fd.out]`.

Unsere Binärdatei ist nun kolossale 768 Bytes groß.

Können wir das Programm immer noch verbessern? Natürlich! Jedes Programm kann verbessert werden. Hier finden sich einige Ideen, was wir tun könnten:

- Die Fehlerbehandlung eine Warnung auf `stderr` ausgeben lassen.

- Den `Lese`- und `Schreib`funkionen eine Fehlerbehandlung hinzufügen.

- Schließen von `stdin`, sobald wir eine Eingabedatei öffnen, von `stdout`, sobald wir eine Ausgabedatei öffnen.

- Hinzufügen von Kommandozeilenschaltern wie zum Beispiel `-i` und `-o`, sodass wir die Ein- und Ausgabedatei in irgendeiner Reihenfolge angeben oder vielleicht von `stdin` lesen und in eine Datei schreiben können.

- Ausgeben einer Gebrauchsanweisung, falls die Kommandozeilenparameter fehlerhaft sind.

Ich beabsichtige, diese Verbesserungen dem Leser als Übung zu hinterlassen: Sie wissen bereits alles, das Sie wissen müssen, um die Verbesserungen durchzuführen.

Zurück | Zum Anfang | Weiter
Gepufferte Eingabe und Ausgabe | Nach oben | Die UNIX®-Umgebung

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>.