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

# 11.5. Portablen Code erzeugen

Übersetzt von Hagen Kühl.

Portabilität ist im Allgemeinen keine Stärke der Assembler-Programmierung. Dennoch ist es, besonders mit nasm, möglich Assembler-Programme für verschiedene Plattformen zu schreiben. Ich selbst habe bereits Assembler-Bibliotheken geschrieben die auf so unterschiedlichen Systemen wie Windows® und FreeBSD übersetzt werden können.

Das ist um so besser möglich, wenn Ihr Code auf zwei Plattformen laufen soll , die, obwohl sie verschieden sind, auf ähnlichen Architekturen basieren.

Beispielsweise ist FreeBSD ein UNIX®, während Linux UNIX-artig ist. Ich habe bisher nur drei Unterschiede zwischen beiden (aus Sicht eines Assembler-Programmierers) erwähnt: Die Aufruf-Konvention, die Funktionsnummern und die Art der Übergabe von Rückgabewerten.

## 11.5.1. Mit Funktionsnummern umgehen

In vielen Fällen sind die Funktionsnummern die selben. Allerdings kann man auch wenn sie es nicht sind leicht mit diesem Problem umgehen: Anstatt die Nummern in Ihrem Code zu verwenden, benutzen Sie Konstanten, die Sie abhängig von der Zielarchitektur unterschiedlich definieren:

    %ifdef	LINUX
    %define	SYS_execve	11
    %else
    %define	SYS_execve	59
    %endif

## 11.5.2. Umgang mit Konventionen

Sowohl die Aufrufkonvention, als auch die Rückgabewerte (das `errno` Problem) kann man mit Hilfe von Makros lösen:

    %ifdef	LINUX

    %macro	system	0
    	call	kernel
    %endmacro

    align 4
    kernel:
    	push	ebx
    	push	ecx
    	push	edx
    	push	esi
    	push	edi
    	push	ebp

    	mov	ebx, [esp+32]
    	mov	ecx, [esp+36]
    	mov	edx, [esp+40]
    	mov	esi, [esp+44]
    	mov	ebp, [esp+48]
    	int	80h

    	pop	ebp
    	pop	edi
    	pop	esi
    	pop	edx
    	pop	ecx
    	pop	ebx

    	or	eax, eax
    	js	.errno
    	clc
    	ret

    .errno:
    	neg	eax
    	stc
    	ret

    %else

    %macro	system	0
    	int	80h
    %endmacro

    %endif

## 11.5.3. Umgang mit anderen Portabilitätsangelegenheiten

Die oben genannte Lösung funktioniert in den meisten Fällen, wenn man Code schreibt, der zwischen FreeBSD und Linux portierbar sein soll. Allerdings sind die Unterschiede bei einigen Kernel-Diensten tiefgreifender.

In diesem Fällen müssen Sie zwei verschiedene Handler für diese Systemaufrufe schreiben und bedingte Assemblierung benutzen, um diese zu übersetzen. Glücklicherweise wird der größte Teil Ihres Codes nicht den Kernel aufrufen und Sie werden deshalb nur wenige solcher bedingten Abschnitte benötigen.

## 11.5.4. Eine Bibliothek benutzen

Sie können Portabilitätsprobleme im Hauptteil ihres Codes komplett vermeiden, indem Sie eine Bibliothek für Systemaufrufe schreiben. Erstellen Sie eine Bibliothek für FreeBSD, eine für Linux und weitere für andere Betriebssysteme.

Schreiben Sie in ihrer Bibliothek eine gesonderte Funktion (oder Prozedur, falls Sie die traditionelle Assembler-Terminologie bevorzugen) für jeden Systemaufruf. Verwenden Sie dabei die C-Aufrufkonvention um Parameter zu übergeben, aber verwenden Sie weiterhin `EAX`, für die Aufrufnummer. In diesem Fall kann ihre FreeBSD-Bibliothek sehr einfach sein, da viele scheinbar unterschiedliche Funktionen als Label für denselben Code implementiert sein können:

    sys.open:
    sys.close:
    [etc...]
    	int	80h
    	ret

Ihre Linux-Bibliothek wird mehr verschiedene Funktionen benötigen, aber auch hier können Sie Systemaufrufe, welche die Anzahl an Parametern akzeptieren zusammenfassen:

    sys.exit:
    sys.close:
    [etc... one-parameter functions]
    	push	ebx
    	mov	ebx, [esp+12]
    	int	80h
    	pop	ebx
    	jmp	sys.return

    ...

    sys.return:
    	or	eax, eax
    	js	sys.err
    	clc
    	ret

    sys.err:
    	neg	eax
    	stc
    	ret

Der Bibliotheks-Ansatz mag auf den ersten Blick unbequem aussehen, weil Sie eine weitere Datei erzeugen müssen von der Ihr Code abhängt. Aber er hat viele Vorteile: Zum einen müssen Sie die Bibliothek nur einmal schreiben und können sie dann in allen Ihren Programmen verwenden. Sie können sie sogar von anderen Assembler-Programmierern verwenden lassen, oder eine die von jemand anderem geschrieben wurde verwenden. Aber der vielleicht größte Vorteil ist, dass Ihr Code sogar von anderen Programmierer auf andere Systeme portiert werden kann, einfach indem man eine neue Bibliothek schreibt, völlig ohne Änderungen an Ihrem Code.

Falls Ihnen der Gedanke eine Bibliothek zu nutzen nicht gefällt, können Sie zumindest all ihre Systemaufrufe in einer gesonderten Assembler-Datei ablegen und diese mit Ihrem Hauptprogramm zusammen binden. Auch hier müssen alle, die ihr Programm portieren, nur eine neue Objekt-Datei erzeugen und an Ihr Hauptprogramm binden.

## 11.5.5. Eine Include-Datei verwenden

Wenn Sie ihre Software als (oder mit dem) Quelltext ausliefern, können Sie Makros definieren und in einer getrennten Datei ablegen, die Sie ihrem Code beilegen.

Porter Ihrer Software schreiben dann einfach eine neue Include-Datei. Es ist keine Bibliothek oder eine externe Objekt-Datei nötig und Ihr Code ist portabel, ohne dass man ihn editieren muss.

> Anmerkung: Das ist der Ansatz den wir in diesem Kapitel verwenden werden. Wir werden unsere Include-Datei `system.inc` nennen und jedesmal, wenn wir einen neuen Systemaufruf verwenden, den entsprechenden Code dort einfügen.

Wir können unsere `system.inc` beginnen indem wir die Standard-Dateideskriptoren deklarieren:

    %define	stdin	0
    %define	stdout	1
    %define	stderr	2

Als Nächstes erzeugen wir einen symbolischen Namen für jeden Systemaufruf:

    %define	SYS_nosys	0
    %define	SYS_exit	1
    %define	SYS_fork	2
    %define	SYS_read	3
    %define	SYS_write	4
    ; [etc...]

Wir fügen eine kleine, nicht globale Prozedur mit langem Namen ein, damit wir den Namen nicht aus Versehen in unserem Code wiederverwenden:

    section	.text
    align 4
    access.the.bsd.kernel:
    	int	80h
    	ret

Wir erzeugen ein Makro, das ein Argument erwartet, die Systemaufruf-Nummer:

    %macro	system	1
    	mov	eax, %1
    	call	access.the.bsd.kernel
    %endmacro

Letztlich erzeugen wir Makros für jeden Systemaufruf. Diese Argumente erwarten keine Argumente.

    %macro	sys.exit	0
    	system	SYS_exit
    %endmacro

    %macro	sys.fork	0
    	system	SYS_fork
    %endmacro

    %macro	sys.read	0
    	system	SYS_read
    %endmacro

    %macro	sys.write	0
    	system	SYS_write
    %endmacro

    ; [etc...]

Fahren Sie fort, geben das in Ihren Editor ein und speichern es als `system.inc`. Wenn wir Systemaufrufe besprechen, werden wir noch Ergänzungen in dieser Datei vornehmen.

Zurück | Zum Anfang | Weiter
Rückgabewerte | Nach oben | Unser erstes Programm

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