aboutsummaryrefslogtreecommitdiff
path: root/de_DE.ISO8859-1/books/developers-handbook/x86/chapter.sgml
diff options
context:
space:
mode:
Diffstat (limited to 'de_DE.ISO8859-1/books/developers-handbook/x86/chapter.sgml')
-rw-r--r--de_DE.ISO8859-1/books/developers-handbook/x86/chapter.sgml6100
1 files changed, 0 insertions, 6100 deletions
diff --git a/de_DE.ISO8859-1/books/developers-handbook/x86/chapter.sgml b/de_DE.ISO8859-1/books/developers-handbook/x86/chapter.sgml
deleted file mode 100644
index 524015a05b..0000000000
--- a/de_DE.ISO8859-1/books/developers-handbook/x86/chapter.sgml
+++ /dev/null
@@ -1,6100 +0,0 @@
-<?xml version="1.0" encoding="iso-8859-1" standalone="no"?>
-<!--
- The FreeBSD Documentation Project
- The FreeBSD German Documentation Project
-
- This file is automatically generated. Please do not make commits
- to this file. Updates should be sent to the author :
-
- G. Adam Stanislav (adam@redprince.net)
-
- This chapter is an exception to our general rule, and the author
- retains the copyright. Among other things, this means that this
- chapter should not be included in any printed version of the
- Developer's Handbook without Adam's explicit permission.
-
- Eventually we will have to replace this chapter or convince the
- author to assign us the copyright. For now, it is valuable
- content so it should stay.
-
- $FreeBSD$
- $FreeBSDde: de-docproj/books/developers-handbook/x86/chapter.sgml,v 1.24 2010/12/15 19:03:52 bcr Exp $
- basiert auf: 1.19
--->
-
-<chapter id="x86">
- <title>x86-Assembler-Programmierung</title>
-
- <para><emphasis>Dieses Kapitel wurde geschrieben von
- &a.stanislav;.</emphasis></para>
-
- <sect1 id="x86-intro">
- <sect1info>
- <authorgroup>
- <author>
- <firstname>Hagen</firstname>
- <surname>Kühl</surname>
- <contrib>Übersetzt von </contrib>
- </author>
- </authorgroup>
- </sect1info>
-
- <title>Synopsis</title>
-
- <para>Assembler-Programmierung unter &unix; ist höchst
- undokumentiert. Es wird allgemein angenommen, dass niemand sie
- jemals benutzen will, da &unix;-Systeme auf verschiedenen
- Mikroprozessoren laufen, und man deshalb aus Gründen der
- Portabilität alles in C schreiben sollte.</para>
-
- <para>In Wirklichkeit ist die Portabilität von C
- größtenteils ein Mythos. Auch C-Programme müssen
- angepasst werden, wenn man sie von einem &unix; auf ein anderes
- portiert, egal auf welchem Prozessor jedes davon läuft.
- Typischerweise ist ein solches Programm voller Bedingungen, die
- unterscheiden für welches System es kompiliert wird.</para>
-
- <para>Sogar wenn wir glauben, dass jede &unix;-Software in C, oder
- einer anderen High-Level-Sprache geschrieben werden sollte,
- brauchen wir dennoch Assembler-Programmierer: Wer sonst sollte
- den Abschnitt der C-Bibliothek schreiben, die auf den Kernel
- zugreift?</para>
-
- <para>In diesem Kapitel möchte ich versuchen zu zeigen, wie
- man Assembler-Sprache verwenden kann, um &unix;-Programme,
- besonders unter FreeBSD, zu schreiben.</para>
-
- <para>Dieses Kapitel erklärt nicht die Grundlagen der
- Assembler-Sprache. Zu diesem Thema gibt es bereits genug Quellen
- (einen vollständigen Online-Kurs finden Sie in Randall
- Hydes <ulink url="http://webster.cs.ucr.edu/">Art of Assembly
- Language</ulink>; oder falls Sie ein gedrucktes Buch bevorzugen,
- können Sie einen Blick auf Jeff Duntemanns <ulink
- url="http://www.int80h.org/cgi-bin/isbn?isbn=0471375233">Assembly
- Language Step-by-Step</ulink> werfen). Jedenfalls sollte jeder
- Assembler-Programmierer nach diesem Kapitel schnell und
- effizient Programme für FreeBSD schreiben
- können.</para>
-
- <para>Copyright &copy; 2000-2001 G. Adam Stanislav. All rights
- reserved.</para>
- </sect1>
-
- <sect1 id="x86-the-tools">
- <sect1info>
- <authorgroup>
- <author>
- <firstname>Hagen</firstname>
- <surname>Kühl</surname>
- <contrib>Übersetzt von </contrib>
- </author>
- </authorgroup>
- </sect1info>
-
- <title>Die Werkzeuge</title>
-
- <sect2 id="x86-the-assembler">
- <title>Der Assembler</title>
-
- <para>Das wichtigste Werkzeug der Assembler-Programmierung ist
- der Assembler, diese Software übersetzt Assembler-Sprache
- in Maschinencode.</para>
-
- <para>Für FreeBSD stehen zwei verschiedene Assembler zur
- Verfügung. Der erste ist
- <citerefentry><refentrytitle>as</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
- der die traditionelle &unix;-Assembler-Sprache verwendet.
- Dieser ist Teil des Systems.</para>
-
- <para>Der andere ist
- <application>/usr/ports/devel/nasm</application>. Dieser
- benutzt die Intel-Syntax und sein Vorteil ist, dass es Code
- fü viele Vetriebssysteme übersetzen kann. Er muss
- gesondert installiert werden, aber ist völlig
- frei.</para>
-
- <para>In diesem Kapitel wird die
- <application>nasm</application>-Syntax verwendet. Einerseits
- weil es die meisten Assembler-Programmierer, die von anderen
- Systemen zu FreeBSD kommen, leichter verstehen werden. Und
- offen gesagt, weil es das ist, was ich gewohnt bin.</para>
- </sect2>
-
- <sect2 id="x86-the-linker">
- <title>Der Linker</title>
-
- <para>Die Ausgabe des Assemblers muss, genau wie der Code jedes
- Compilers, gebunden werden, um eine ausführbare Datei zu
- bilden.</para>
-
- <para>Der Linker
- <citerefentry><refentrytitle>ld</refentrytitle><manvolnum>1</manvolnum></citerefentry>
- ist der Standard und Teil von FreeBSD. Er funktioniert mit dem
- Code beider Assembler.</para>
- </sect2>
- </sect1>
-
- <sect1 id="x86-system-calls">
- <sect1info>
- <authorgroup>
- <author>
- <firstname>Hagen</firstname>
- <surname>Kühl</surname>
- <contrib>Übersetzt von </contrib>
- </author>
- </authorgroup>
- </sect1info>
-
- <title>Systemaufrufe</title>
-
- <sect2 id="x86-default-calling-convention">
- <title>Standard-Aufrufkonvention</title>
-
- <para>Standardmäßig benutzt der FreeBSD-Kernel die
- C-Aufrufkonvention. Weiterhin wird, obwohl auf den Kernel
- durch <function role="opcode">int 80h</function> zugegriffen
- wird, angenommen, dass das Programm eine Funktion aufruft, die
- <function role="opcode">int 80h</function> verwendet, anstatt
- <function role="opcode">int 80h</function> direkt
- aufzurufen.</para>
-
- <para>Diese Konvention ist sehr praktisch und der
- &microsoft;-Konvention von <acronym>&ms-dos;</acronym>
- überlegen. Warum? Weil es die &unix;-Konvention jedem
- Programm, egal in welcher Sprache es geschrieben ist, erlaubt
- auf den Kernel zuzugreifen.</para>
-
- <para>Ein Assembler-Programm kann das ebenfalls. Beispielsweise
- könnten wir eine Datei öffnen:</para>
-
- <programlisting>kernel:
- int 80h ; Call kernel
- ret
-
-open:
- push dword mode
- push dword flags
- push dword path
- mov eax, 5
- call kernel
- add esp, byte 12
- ret</programlisting>
-
- <para>Das ist ein sehr sauberer und portabler Programmierstil.
- Wenn Sie das Programm auf ein anderes &unix; portieren, das
- einen anderen Interrupt oder eie andere Art der
- Parameterübergabe verwendet, müssen sie nur die
- Prozedur kernel ändern.</para>
-
- <para>Aber Assembler-Programmierer lieben es Taktzyklen zu
- schinden. Das obige Beispiel benötigt eine <function
- role="opcode">call/ret</function>-Kombination. Das können
- wir entfernen, indem wir einen weiteren Parameter mit
- <function role="opcode">push</function> übergeben:</para>
-
- <programlisting>open:
- push dword mode
- push dword flags
- push dword path
- mov eax, 5
- push eax ; Or any other dword
- int 80h
- add esp, byte 16</programlisting>
-
- <para>Die Konstante <constant>5</constant>, die wir in <varname
- role="register">EAX</varname> ablegen, identifiziert die
- Kernel-Funktion, die wir aufrufen. In diesem Fall ist das
- <function role="syscall">open</function>.</para>
- </sect2>
-
- <sect2 id="x86-alternate-calling-convention">
- <title>Alternative Aufruf-Konvention</title>
-
- <para>FreeBSD ist ein extrem flexibles System. Es bietet noch
- andere Wege, um den Kernel aufzurufen. Damit diese
- funktionieren muss allerdings die Linux-Emulation installiert
- sein.</para>
-
- <para>Linux ist ein &unix;-artiges System. Allerdings verwendet
- dessen Kernel die gleiche Systemaufruf-Konvention, bei der
- Parameter in Registern abgelegt werden, wie
- <acronym>&ms-dos;</acronym>. Genau wie bei der
- &unix;-Konvention wird die Nummer der Funktion in <varname
- role="register">EAX</varname> abgelegt. Allerdings werden die
- Parameter nicht auf den Stack gelegt, sondern in die Register
- <varname role="register">EBX, ECX, EDX, ESI, EDI,
- EBP</varname>:</para>
-
- <programlisting>open:
- mov eax, 5
- mov ebx, path
- mov ecx, flags
- mov edx, mode
- int 80h</programlisting>
-
- <para>Diese Konvention hat einen großen Nachteil
- gegenüber der von &unix;, was die
- Assembler-Programmierung angeht: Jedesmal, wenn Sie einen
- Kernel-Aufruf machen, müssen Sie die Register <function
- role="opcode">push</function>en und sie später <function
- role="opcode">pop</function>en. Das macht Ihren Code
- unförmiger und langsamer. Dennoch lässt FreeBSD
- ihnen die Wahl.</para>
-
- <para>Wenn Sie sich für die Linux-Konvention entscheiden,
- müssen Sie es das System wissen lassen. Nachdem ihr
- Programm übersetzt und gebunden wurde, müssen Sie
- die ausführbare Datei kennzeichnen:</para>
-
- <screen>&prompt.user;
- <userinput>brandelf -t Linux
- <replaceable>filename</replaceable></userinput></screen>
- </sect2>
-
- <sect2 id="x86-use-geneva">
- <title>Welche Konvention Sie verwenden sollten</title>
-
- <para>Wenn Sie speziell für FreeBSD programmieren, sollten
- Sie die &unix;-Konvention verwenden: Diese ist schneller, Sie
- können globale Variablen in Registern ablegen, Sie
- müssen die ausführbare Datei nicht kennzeichnen und
- Sie erzwingen nicht die Installation der Linux-Emulation auf
- dem Zielsystem.</para>
-
- <para>Wenn Sie portablen Programmcode erzeugen wollen, der auch
- unter Linux funktioniert, wollen Sie den FreeBSD-Nutzern
- vielleicht dennoch den effizientesten Programmcode bieten, der
- möglich ist. Ich werde Ihnen zeigen, wie Sie das
- erreichen können, nachdem ich die Grundlagen erklärt
- habe.</para>
- </sect2>
-
- <sect2 id="x86-call-numbers">
- <title>Aufruf-Nummern</title>
-
- <para>Um dem Kernel mitzuteilen welchen Dienst Sie aufrufen,
- legen Sie dessen Nummer in <varname
- role="register">EAX</varname> ab. Natürlich müssen
- Sie dazu wissen welche Nummer die Richtige ist.</para>
-
- <sect3 id="x86-the-syscalls-file">
- <title>Die Datei <filename>syscalls</filename></title>
-
- <para>Die Nummer der Funktionen sind in der Datei
- <filename>syscalls</filename> aufgeführt. Mittels
- <command>locate syscalls</command> finden Sie diese in
- verschiedenen Formaten, die alle auf die gleiche Weise aus
- <filename>syscalls.master</filename> erzeugt werden.</para>
-
- <para>Die Master-Datei für die
- &unix;-Standard-Aufrufkonvention finden sie unter
- <filename>/usr/src/sys/kern/syscalls.master</filename>.
- Falls Sie die andere Konvention, die im
- Linux-Emulations-Modus implementiert ist, verwenden
- möchten, lesen Sie bitte
- <filename>/usr/src/sys/i386/linux/syscalls.master</filename>.</para>
-
- <note><para>FreeBSD und Linux unterscheiden sich nicht nur in
- den Aufrufkonventionen, sie haben teilweise auch
- verschiedene Nummern für die gleiche
- Funktion.</para></note>
-
- <para><filename>syscalls.master</filename> beschreibt, wie der
- Aufruf gemacht werden muss:</para>
-
- <programlisting>0 STD NOHIDE { int nosys(void); } syscall nosys_args int
-1 STD NOHIDE { void exit(int rval); } exit rexit_args void
-2 STD POSIX { int fork(void); }
-3 STD POSIX { ssize_t read(int fd, void *buf, size_t nbyte); }
-4 STD POSIX { ssize_t write(int fd, const void *buf, size_t nbyte); }
-5 STD POSIX { int open(char *path, int flags, int mode); }
-6 STD POSIX { int close(int fd); }
-etc...</programlisting>
-
- <para>In der ersten Spalte steht die Nummer, die in <varname
- role="register">EAX</varname> abgelegt werden muss.</para>
-
- <para>Die Spalte ganz rechts sagt uns welche Parameter wir
- <function role="opcode">push</function>en müssen. Die
- Reihenfolge ist dabei <emphasis>von rechts nach
- links</emphasis>.</para>
-
- <informalexample><para>Um beispielsweise eine Datei mittels
- <function>open</function> zu öffnen, müssen wir
- zuerst den <varname>mode</varname> auf den Stack <function
- role="opcode">push</function>en, danach die
- <varname>flags</varname>, dann die Adresse an der der
- <varname>path</varname> gespeichert
- ist.</para></informalexample>
-
- </sect3>
- </sect2>
- </sect1>
-
- <sect1 id="x86-return-values">
- <sect1info>
- <authorgroup>
- <author>
- <firstname>Hagen</firstname>
- <surname>Kühl</surname>
- <contrib>Übersetzt von </contrib>
- </author>
- </authorgroup>
- </sect1info>
-
- <title>Rückgabewerte</title>
-
- <para>Ein Systemaufruf wäre meistens nicht sehr
- nützlich, wenn er nicht irgendeinen Wert zurückgibt:
- Beispielsweise den Dateideskriptor einer geöffneten Datei,
- die Anzahl an Bytes die in einen Puffer gelesen wurde, die
- Systemzeit, etc.</para>
-
- <para>Außerdem muss Sie das System informieren, falls ein
- Fehler auftritt: Wenn eine Datei nicht existiert, die
- Systemressourcen erschöpft sind, wir ein ungültiges
- Argument übergeben haben, etc.</para>
-
- <sect2 id="x86-man-pages">
- <title>Manualpages</title>
-
- <para>Der herkömmliche Ort, um nach Informationen über
- verschiedene Systemaufrufe unter &unix;-Systemen zu suchen,
- sind die Manualpages. FreeBSD beschreibt seine Systemaufrufe
- in Abschnitt 2, manchmal auch Abschnitt 3.</para>
-
- <para>In
- <citerefentry><refentrytitle>open</refentrytitle><manvolnum>2</manvolnum></citerefentry>
- steht beispielsweise:</para>
-
- <blockquote><para>Falls erfolgreich, gibt
- <function>open()</function> einen nicht negativen Integerwert,
- als Dateideskriptor bezeichnet, zurück. Es gibt
- <varname>-1</varname> im Fehlerfall zurück und setzt
- <varname>errno</varname> um den Fehler
- anzuzeigen.</para></blockquote>
-
- <para>Ein Assembler-Programmierer, der neu bei &unix; und
- FreeBSD ist, wird sich sofort fragen: Wo finde ich
- <varname>errno</varname> und wie erreiche ich es?</para>
-
- <note><para>Die Information der Manualpage bezieht sich auf
- C-Programme. Der Assembler-Programmierer benötigt
- zusätzliche Informationen.</para></note>
- </sect2>
-
- <sect2 id="x86-where-return-values">
- <title>Wo sind die Rückgabewerde?</title>
-
- <para>Leider gilt: Es kommt darauf an... Für die meisten
- Systemaufrufe liegt er in <varname
- role="register">EAX</varname>, aber nicht für alle. Eine
- gute Daumenregel, wenn man zum ersten Mal mit einem
- Systemaufruf arbeitet, ist in <varname
- role="register">EAX</varname> nach dem Rückgabewert zu
- suchen. Wenn er nicht dort ist, sind weitere Untersuchungen
- nötig.</para>
-
- <note><para>Mir ist ein Systemaufruf bekannt, der den
- Rückgabewert in <varname role="register">EDX</varname>
- ablegt: <function role="syscall">SYS_fork</function> Alle
- anderen mit denen ich bisher gearbeitet habe verwenden
- <varname role="register">EAX</varname>. Allerdings habe ich
- noch nicht mit allen gearbeitet.</para></note>
-
- <tip><para>Wenn Sie die Antwort weder hier, noch irgendwo anders
- finden, studieren Sie den Quelltext von
- <application>libc</application> und sehen sich an, wie es mit
- dem Kernel zusammenarbeitet.</para></tip>
- </sect2>
-
- <sect2 id="x86-where-errno">
- <title>Wo ist <varname>errno</varname>?</title>
-
- <para>Tatsächlich, nirgendwo...</para>
-
- <para><varname>errno</varname> ist ein Teil der Sprache C, nicht
- des &unix;-Kernels. Wenn man direkt auf Kernel-Dienste
- zugreift, wird der Fehlercode in <varname
- role="register">EAX</varname> zurückgegeben, das selbe
- Register in dem der Rückgabewert, bei einem erfolgreichen
- Aufruf landet.</para>
-
- <para>Das macht auch Sinn. Wenn kein Fehler auftritt, gibt es
- keinen Fehlercode. Wenn ein Fehler auftritt, gibt es keinen
- Rückgabewert. Ein einziges Register kann also beides
- enthalten.</para>
- </sect2>
-
- <sect2 id="x86-how-to-know-error">
- <title>Feststellen, dass ein Fehler aufgetreten ist</title>
-
- <para>Wenn Sie die Standard FreeBSD-Aufrufkonvention verwenden
- wird das <varname role="register">carry flag</varname>
- gelöscht wenn der Aufruf erfolgreich ist und gesetzt wenn
- ein Fehler auftritt.</para>
-
- <para>Wenn Sie den Linux-Emulationsmodus verwenden ist der
- vorzeichenbehaftete Wert in <varname
- role="register">EAX</varname> nicht negativ, bei einem
- erfolgreichen Aufruf. Wenn ein Fehler auftritt ist der Wert
- negativ, also <varname>-errno</varname>.</para>
- </sect2>
- </sect1>
-
- <sect1 id="x86-portable-code">
- <sect1info>
- <authorgroup>
- <author>
- <firstname>Hagen</firstname>
- <surname>Kühl</surname>
- <contrib>Übersetzt von </contrib>
- </author>
- </authorgroup>
- </sect1info>
-
- <title>Portablen Code erzeugen</title>
-
- <para>Portabilität ist im Allgemeinen keine Stärke der
- Assembler-Programmierung. Dennoch ist es, besonders mit
- <application>nasm</application>, 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.</para>
-
- <para>Das ist um so besser möglich, wenn Ihr Code auf zwei
- Plattformen laufen soll , die, obwohl sie verschieden sind, auf
- ähnlichen Architekturen basieren.</para>
-
- <para>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.</para>
-
- <sect2 id="x86-deal-with-function-numbers">
- <title>Mit Funktionsnummern umgehen</title>
-
- <para>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:</para>
-
- <programlisting>%ifdef LINUX
-%define SYS_execve 11
-%else
-%define SYS_execve 59
-%endif</programlisting>
- </sect2>
-
- <sect2 id="x86-deal-with-geneva">
- <title>Umgang mit Konventionen</title>
-
- <para>Sowohl die Aufrufkonvention, als auch die
- Rückgabewerte (das <varname>errno</varname> Problem) kann
- man mit Hilfe von Makros lösen:</para>
-
- <programlisting>%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</programlisting>
- </sect2>
-
- <sect2 id="x86-deal-with-other-portability">
- <title>Umgang mit anderen
- Portabilitätsangelegenheiten</title>
-
- <para>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.</para>
-
- <para>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.</para>
- </sect2>
-
- <sect2 id="x86-portable-library">
- <title>Eine Bibliothek benutzen</title>
-
- <para>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.</para>
-
- <para>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
- <varname role="register">EAX</varname>, 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:</para>
-
- <programlisting>sys.open:
-sys.close:
-[etc...]
- int 80h
- ret</programlisting>
-
- <para>Ihre Linux-Bibliothek wird mehr verschiedene Funktionen
- benötigen, aber auch hier können Sie Systemaufrufe,
- welche die Anzahl an Parametern akzeptieren
- zusammenfassen:</para>
-
- <programlisting>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</programlisting>
-
- <para>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.</para>
-
- <para>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.</para>
- </sect2>
-
- <sect2 id="x86-portable-include">
- <title>Eine Include-Datei verwenden</title>
-
- <para>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.</para>
-
- <para>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.</para>
-
- <note>
- <para>Das ist der Ansatz den wir in diesem Kapitel verwenden
- werden. Wir werden unsere Include-Datei
- <filename>system.inc</filename> nennen und jedesmal, wenn
- wir einen neuen Systemaufruf verwenden, den entsprechenden
- Code dort einfügen.</para>
- </note>
-
- <para>Wir können unsere <filename>system.inc</filename>
- beginnen indem wir die Standard-Dateideskriptoren
- deklarieren:</para>
-
- <programlisting>%define stdin 0
-%define stdout 1
-%define stderr 2</programlisting>
-
- <para>Als Nächstes erzeugen wir einen symbolischen Namen
- für jeden Systemaufruf:</para>
-
- <programlisting>%define SYS_nosys 0
-%define SYS_exit 1
-%define SYS_fork 2
-%define SYS_read 3
-%define SYS_write 4
-; [etc...]</programlisting>
-
- <para>Wir fügen eine kleine, nicht globale Prozedur mit
- langem Namen ein, damit wir den Namen nicht aus Versehen in
- unserem Code wiederverwenden:</para>
-
- <programlisting>section .text
-align 4
-access.the.bsd.kernel:
- int 80h
- ret</programlisting>
-
- <para>Wir erzeugen ein Makro, das ein Argument erwartet, die
- Systemaufruf-Nummer:</para>
-
- <programlisting>%macro system 1
- mov eax, %1
- call access.the.bsd.kernel
-%endmacro</programlisting>
-
- <para>Letztlich erzeugen wir Makros für jeden Systemaufruf.
- Diese Argumente erwarten keine Argumente.</para>
-
- <programlisting>%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...]</programlisting>
-
- <para>Fahren Sie fort, geben das in Ihren Editor ein und
- speichern es als <filename>system.inc</filename>. Wenn wir
- Systemaufrufe besprechen, werden wir noch Ergänzungen in
- dieser Datei vornehmen.</para>
-
- </sect2>
- </sect1>
-
- <sect1 id="x86-first-program">
- <sect1info>
- <authorgroup>
- <author>
- <firstname>Hagen</firstname>
- <surname>Kühl</surname>
- <contrib>Übersetzt von </contrib>
- </author>
- </authorgroup>
- </sect1info>
-
- <title>Unser erstes Programm</title>
-
- <para>Jetzt sind wir bereit für unser erstes Programm, das
- übliche <application>Hello, World!</application></para>
-
- <programlisting> 1: %include 'system.inc'
- 2:
- 3: section .data
- 4: hello db 'Hello, World!', 0Ah
- 5: hbytes equ $-hello
- 6:
- 7: section .text
- 8: global _start
- 9: _start:
-10: push dword hbytes
-11: push dword hello
-12: push dword stdout
-13: sys.write
-14:
-15: push dword 0
-16: sys.exit</programlisting>
-
- <para>Hier folgt die Erklärung des Programms: Zeile 1
- fügt die Definitionen ein, die Makros und den Code aus
- <filename>system.inc</filename>.</para>
-
- <para>Die Zeilen 3 bis 5 enthalten die Daten: Zeile 3 beginnt den
- Datenabschnitt/das Datensegment. Zeile 4 enthält die
- Zeichenkette "Hello, World!", gefolgt von einem Zeilenumbruch
- (<constant>0Ah</constant>). Zeile 5 erstellt eine Konstante, die
- die Länge der Zeichenkette aus Zeile 4 in Bytes
- enthält.</para>
-
- <para>Die Zeilen 7 bis 16 enthalten den Code. Beachten Sie
- bitte, dass FreeBSD das Dateiformat <emphasis>elf</emphasis>
- für diese ausführbare Datei verwendet, bei dem jedes
- Programm mit dem Label <varname>_start</varname> beginnt (oder,
- um genau zu sein, wird dies vom Linker erwartet). Diese Label
- muss global sein.</para>
-
- <para>Die Zeilen 10 bis 13 weisen das System an
- <varname>hbytes</varname> Bytes der Zeichenkette
- <varname>hello</varname> nach <varname>stdout</varname> zu
- schreiben.</para>
-
- <para>Die Zeilen 15 und 16 weisen das System an das Programm mit
- dem Rückgabewert <constant>0</constant> zu beenden. Der
- Systemaufruf <function role="syscall">SYS_exit</function> kehrt
- niemals zurück, somit endet das Programm hier.</para>
-
- <note>
- <para>Wenn Sie von <acronym>&ms-dos;</acronym>-Assembler zu
- &unix; gekommen sind, sind Sie es vielleicht gewohnt direktauf
- die Video-Hardware zu schreiben. Unter FreeBSD müssen Sie
- sich darum keine Gedanken machen, ebenso bei jeder anderen Art
- von &unix;. Soweit es Sie betrifft schreiben Sie in eine Datei
- namens <filename>stdout</filename>. Das kann der Bildschirm,
- oder ein <application>telnet</application>-Terminal, eine
- wirkliche Datei, oder die Eingabe eines anderen Programms
- sein. Es liegt beim System herauszufinden, welches davon es
- tatsächlich ist.</para>
- </note>
-
- <sect2 id="x86-assemble-1">
- <title>Den Code assemblieren</title>
-
- <para>Geben Sie den Code (außer den Zeilennummern) in
- einen Editor ein und speichern Sie ihn in einer Datei namens
- <filename>hello.asm</filename>. Um es zu assemblieren
- benötigen Sie <application>nasm</application>.</para>
-
- <sect3 id="x86-get-nasm">
- <title><application>nasm</application> installieren</title>
-
- <para>Wenn Sie <application>nasm</application> noch nicht
- installiert haben geben Sie folgendes ein:</para>
-
- <screen>&prompt.user; <userinput>su</userinput>
-Password:<userinput><replaceable>your root password</replaceable></userinput>
-&prompt.root; <userinput>cd /usr/ports/devel/nasm</userinput>
-&prompt.root; <userinput>make install</userinput>
-&prompt.root; <userinput>exit</userinput>
-&prompt.user;</screen>
-
- <para>Sie können auch <userinput>make install
- clean</userinput> anstatt <userinput>make
- install</userinput> eingeben, wenn Sie den Quelltext von
- <application>nasm</application> nicht behalten
- möchten.</para>
-
- <para>Auf jeden Fall wird FreeBSD
- <application>nasm</application> automatisch aus dem Internet
- herunterladen, es kompilieren und auf Ihrem System
- installieren.</para>
-
- <note>
- <para>Wenn es sich bei Ihrem System nicht um FreeBSD
- handelt, müssen Sie <application>nasm</application>
- von dessen <ulink
- url="https://sourceforge.net/projects/nasm">Homepage</ulink>
- herunterladen. Sie können es aber dennoch verwenden
- um FreeBSD code zu assemblieren.</para>
- </note>
-
- <para>Nun können Sie den Code assemblieren, binden und
- ausführen:</para>
-
- <screen>&prompt.user; <userinput>nasm -f elf hello.asm</userinput>
-&prompt.user; <userinput>ld -s -o hello hello.o</userinput>
-&prompt.user; <userinput>./hello</userinput>
-Hello, World!
-&prompt.user;</screen>
- </sect3>
- </sect2>
- </sect1>
-
- <sect1 id="x86-unix-filters">
- <sect1info>
- <authorgroup>
- <author>
- <firstname>Hagen</firstname>
- <surname>Kühl</surname>
- <contrib>Übersetzt von </contrib>
- </author>
- </authorgroup>
- </sect1info>
-
- <title>&unix;-Filter schreiben</title>
-
- <para>Ein häufiger Typ von &unix;-Anwendungen ist ein Filter
- &mdash; ein Programm, das Eingaben von
- <filename>stdin</filename> liest, sie verarbeitet und das
- Ergebnis nach <filename>stdout</filename> schreibt.</para>
-
- <para>In diesem Kapitel möchten wir einen einfachen Filter
- entwickeln und lernen, wie wir von <filename>stdin</filename>
- lesen und nach <filename>stdout</filename> schreiben. Dieser
- Filter soll jedes Byte seiner Eingabe in eine hexadezimale Zahl
- gefolgt von einem Leerzeichen umwandeln.</para>
-
- <programlisting>%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</programlisting>
-
- <para>Im Datenabschnitt erzeugen wir ein Array mit Namen
- <varname>hex</varname>. 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 <constant>0</constant> 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.</para>
-
- <para>Der Code-Abschnitt besteht aus vier Teilen: Das Byte lesen,
- es in eine hexadezimale Zahl umwandeln, das Ergebnis schreiben
- und letztendlich das Programm verlassen.</para>
-
- <para>Um das Byte zu lesen, bitten wir das System ein Byte von
- <filename>stdin</filename> zu lesen und speichern es im ersten
- Byte von <varname>buffer</varname>. Das System gibt die Anzahl
- an Bytes, die gelesen wurden, in <varname
- role="register">EAX</varname> zurück. Diese wird
- <constant>1</constant> sein, wenn eine Eingabe empfangen wird
- und <constant>0</constant>, wenn keine Eingabedaten mehr
- verfügbar sind. Deshalb überprüfen wir den Wert
- von <varname role="register">EAX</varname>. Wenn dieser
- <constant>0</constant> ist, springen wir zu
- <varname>.done</varname>, ansonsten fahren wir fort.</para>
-
- <note>
- <para>Zu Gunsten der Einfachheit ignorieren wir hier die
- Möglichkeit eines Fehlers.</para>
- </note>
-
- <para>Die Umwandlungsroutine in eine Hexadezimalzahl liest das
- Byte aus <varname>buffer</varname> in <varname
- role="register">EAX</varname>, oder genaugenommen nur in
- <varname role="register">AL</varname>, wobei die übrigen
- Bits von <varname role="register">EAX</varname> auf null gesetzt
- werden. Außerdem kopieren wir das Byte nach <varname
- role="register">EDX</varname>, 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.</para>
-
- <para>Als Nächstes bitten wir das System die drei Bytes in
- den Puffer zu schreiben, also die zwei hexadezimalen Ziffern und
- das Leerzeichen nach <filename>stdout</filename>. Danach
- springen wir wieder an den Anfang des Programms und verarbeiten
- das nächste Byte.</para>
-
- <para>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.</para>
-
- <para>Fahren Sie fort und speichern Sie den Code in eine Datei
- namens <filename>hex.asm</filename>. Geben Sie danach folgendes
- ein (<userinput>^D</userinput> bedeutet, dass Sie die
- Steuerungstaste drücken und dann <userinput>D</userinput>
- eingeben, während Sie Steuerung gedrückt
- halten):</para>
-
- <screen>&prompt.user; <userinput>nasm -f elf hex.asm</userinput>
-&prompt.user; <userinput>ld -s -o hex hex.o</userinput>
-&prompt.user; <userinput>./hex</userinput>
-<userinput>Hello, World!</userinput>
-48 65 6C 6C 6F 2C 20 57 6F 72 6C 64 21 0A <userinput>Here I come!</userinput>
-48 65 72 65 20 49 20 63 6F 6D 65 21 0A <userinput>^D</userinput> &prompt.user;</screen>
-
- <note>
- <para>Wenn Sie von <acronym>&ms-dos;</acronym> zu &unix;
- wechseln, wundern Sie sich vielleicht, warum jede Zeile mit
- <constant>0A</constant> an Stelle von <constant>0D
- 0A</constant> endet. Das liegt daran, dass &unix; nicht die
- CR/LF-Konvention, sondern die "new line"-Konvention verwendet,
- welches hexadezimal als <constant>0A</constant> dargestellt
- wird.</para>
- </note>
-
- <para>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
- <constant>0A</constant> auszugeben:</para>
-
- <programlisting>%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</programlisting>
-
- <para>Wir haben das Leerzeichen im Register <varname
- role="register">CL</varname> 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.</para>
-
- <para>Das bedeutet, dass wir <varname role="register">CL</varname>
- nur einmal setzen müssen. Dafür haben wir ein neues
- Label <varname>.loop</varname> eingefügt, zu dem wir an
- Stelle von <varname>_start</varname> springen, um das
- nächste Byte einzulesen. Außerdem haben wir das Label
- <varname>.hex</varname> eingefügt, somit können wir
- wahlweise ein Leerzeichen oder einen Zeilenumbruch im dritten
- Byte von <varname>buffer</varname> ablegen.</para>
-
- <para>Nachdem Sie <filename>hex.asm</filename> entsprechend der
- Neuerungen geändert haben, geben Sie Folgendes ein:</para>
-
- <screen>&prompt.user; <userinput>nasm -f elf hex.asm</userinput>
-&prompt.user; <userinput>ld -s -o hex hex.o</userinput>
-&prompt.user; <userinput>./hex</userinput>
-<userinput>Hello, World!</userinput>
-48 65 6C 6C 6F 2C 20 57 6F 72 6C 64 21 0A
-<userinput>Here I come!</userinput>
-48 65 72 65 20 49 20 63 6F 6D 65 21 0A
-<userinput>^D</userinput> &prompt.user;</screen>
-
- <para>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).</para>
- </sect1>
-
- <sect1 id="x86-buffered-io">
- <sect1info>
- <authorgroup>
- <author>
- <firstname>Hagen</firstname>
- <surname>Kühl</surname>
- <contrib>Übersetzt von </contrib>
- </author>
- </authorgroup>
- </sect1info>
-
- <title>Gepufferte Eingabe und Ausgabe</title>
-
- <para>Wir können die Effizienz unseres Codes erhöhen,
- indem wir die Ein- und Ausgabe puffern. Wir erzeugen einen
- Eingabepuffer und lesen dann eine Folge von Bytes auf einmal.
- Danach holen wir sie Byte für Byte aus dem Puffer.</para>
-
- <para>Wir erzeugen ebenfalls einen Ausgabepuffer. Darin speichern
- wir unsere Ausgabe bis er voll ist. Dann bitten wir den Kernel
- den Inhalt des Puffers nach <filename>stdout</filename> zu
- schreiben.</para>
-
- <para>Diese Programm endet, wenn es keine weitere Eingaben gibt.
- Aber wir müssen den Kernel immernoch bitten den Inhalt des
- Ausgabepuffers ein letztes Mal nach <filename>stdout</filename>
- zu schreiben, denn sonst würde ein Teil der Ausgabe zwar im
- Ausgabepuffer landen, aber niemals ausgegeben werden. Bitte
- vergessen Sie das nicht, sonst fragen Sie sich später warum
- ein Teil Ihrer Ausgabe verschwunden ist.</para>
-
- <programlisting>%include 'system.inc'
-
-%define BUFSIZE 2048
-
-section .data
-hex db '0123456789ABCDEF'
-
-section .bss
-ibuffer resb BUFSIZE
-obuffer resb BUFSIZE
-
-section .text
-global _start
-_start:
- sub eax, eax
- sub ebx, ebx
- sub ecx, ecx
- mov edi, obuffer
-
-.loop:
- ; read a byte from 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
- 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 stdin
- 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
- 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 stdout
- sys.write
- add esp, byte 12
- sub eax, eax
- sub ecx, ecx ; buffer is empty now
- ret</programlisting>
-
- <para>Als dritten Abschnitt im Quelltext haben wir
- <varname>.bss</varname>. Dieser Abschnitt wird nicht in unsere
- ausführbare Datei eingebunden und kann daher nicht
- initialisiert werden. Wir verwenden <function
- role="opcode">resb</function> anstelle von <function
- role="opcode">db</function>. Dieses reserviert einfach die
- angeforderte Menge an uninitialisiertem Speicher zu unserer
- Verwendung.</para>
-
- <para>Wir nutzen, die Tatsache, dass das System die Register nicht
- verändert: Wir benutzen Register, wo wir anderenfalls
- globale Variablen im Abschnitt <varname>.data</varname>
- verwenden müssten. Das ist auch der Grund, warum die
- &unix;-Konvention, Parameter auf dem Stack zu übergeben,
- der von Microsoft, hierfür Register zu verwenden,
- überlegen ist: Wir können Register für unsere
- eigenen Zwecke verwenden.</para>
-
- <para>Wir verwenden <varname role="register">EDI</varname> und
- <varname role="register">ESI</varname> als Zeiger auf das
- nächste zu lesende oder schreibende Byte. Wir verwenden
- <varname role="register">EBX</varname> und <varname
- role="register">ECX</varname>, um die Anzahl der Bytes in den
- beiden Puffern zu zählen, damit wir wissen, wann wir die
- Ausgabe an das System übergeben, oder neue Eingabe vom
- System entgegen nehmen müssen.</para>
-
- <para>Lassen Sie uns sehen, wie es funktioniert:</para>
-
- <screen>&prompt.user; <userinput>nasm -f elf hex.asm</userinput>
-&prompt.user; <userinput>ld -s -o hex hex.o</userinput>
-&prompt.user; <userinput>./hex</userinput>
-<userinput>Hello, World!</userinput>
-<userinput>Here I come!</userinput>
-48 65 6C 6C 6F 2C 20 57 6F 72 6C 64 21 0A
-48 65 72 65 20 49 20 63 6F 6D 65 21 0A
-<userinput>^D</userinput> &prompt.user;</screen>
-
- <para>Nicht was Sie erwartet haben? Das Programm hat die Ausgabe
- nicht auf dem Bildschirm ausgegeben bis sie
- <userinput>^D</userinput> gedrückt haben. Das kann man
- leicht zu beheben indem man drei Zeilen Code einfügt,
- welche die Ausgabe jedesmal schreiben, wenn wir einen
- Zeilenumbruch in <constant>0A</constant> umgewandelt haben. Ich
- habe die betreffenden Zeilen mit &gt; markiert (kopieren Sie die
- &gt; bitte nicht mit in Ihre
- <filename>hex.asm</filename>).</para>
-
- <programlisting>%include 'system.inc'
-
-%define BUFSIZE 2048
-
-section .data
-hex db '0123456789ABCDEF'
-
-section .bss
-ibuffer resb BUFSIZE
-obuffer resb BUFSIZE
-
-section .text
-global _start
-_start:
- sub eax, eax
- sub ebx, ebx
- sub ecx, ecx
- mov edi, obuffer
-
-.loop:
- ; read a byte from 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, 0Ah
-> 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 stdin
- 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
- 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 stdout
- sys.write
- add esp, byte 12
- sub eax, eax
- sub ecx, ecx ; buffer is empty now
- ret</programlisting>
-
- <para>Lassen Sie uns jetzt einen Blick darauf werfen, wie es
- funktioniert.</para>
-
- <screen>&prompt.user; <userinput>nasm -f elf hex.asm</userinput>
-&prompt.user; <userinput>ld -s -o hex hex.o</userinput>
-&prompt.user; <userinput>./hex</userinput>
-<userinput>Hello, World!</userinput>
-48 65 6C 6C 6F 2C 20 57 6F 72 6C 64 21 0A
-<userinput>Here I come!</userinput>
-48 65 72 65 20 49 20 63 6F 6D 65 21 0A
-<userinput>^D</userinput> &prompt.user;</screen>
-
- <para>Nicht schlecht für eine 644 Byte große
- Binärdatei, oder?</para>
-
- <note>
- <para>Dieser Ansatz für gepufferte Ein- und Ausgabe
- enthält eine Gefahr, auf die ich im Abschnitt <link
- linkend="x86-buffered-dark-side">Die dunkle Seite des
- Buffering</link> eingehen werde.</para>
- </note>
-
- <sect2 id="x86-ungetc">
- <title>Ein Zeichen ungelesen machen</title>
-
- <warning>
- <para>Das ist vielleicht ein etwas fortgeschrittenes Thema,
- das vor allem für Programmierer interessant ist, die
- mit der Theorie von Compilern vertraut sind. Wenn Sie
- wollen, können Sie <link linkend="x86-command-line">zum
- nächsten Abschnitt springen</link> und das hier
- vielleicht später lesen.</para>
- </warning>
-
- <para>Unser Beispielprogramm benötigt es zwar nicht, aber
- etwas anspruchsvollere Filter müssen häufig
- vorausschauen. Mit anderen Worten, sie müssen wissen was
- das nächste Zeichen ist (oder sogar mehrere Zeichen).
- Wenn das nächste Zeichen einen bestimmten Wert hat, ist
- es Teil des aktuellen Tokens, ansonsten nicht.</para>
-
- <para>Zum Beispiel könnten Sie den Eingabestrom für
- eine Text-Zeichenfolge parsen (z.B. wenn Sie einen Compiler
- einer Sprache implementieren): Wenn einem Buchstaben ein
- anderer Buchstabe oder vielleicht eine Ziffer folgt, ist er
- ein Teil des Tokens, das Sie verarbeiten. Wenn ihm ein
- Leerzeichen folgt, oder ein anderer Wert, ist er nicht Teil
- des aktuellen Tokens.</para>
-
- <para>Das führt uns zu einem interessanten Problem: Wie
- kann man ein Zeichen zurück in den Eingabestrom geben,
- damit es später noch einmal gelesen werden kann?</para>
-
- <para>Eine mögliche Lösung ist, das Zeichen in einer
- Variable zu speichern und ein Flag zu setzen. Wir können
- <function>getchar</function> so anpassen, dass es das Flag
- überprüft und, wenn es gesetzt ist, das Byte aus der
- Variable anstatt dem Eingabepuffer liest und das Flag
- zurück setzt. Aber natürlich macht uns das
- langsamer.</para>
-
- <para>Die Sprache C hat eine Funktion
- <function>ungetc()</function> für genau diesen Zweck.
- Gibt es einen schnellen Weg, diese in unserem Code zu
- implementieren? Ich möchte Sie bitten nach oben zu
- scrollen und sich die Prozedur <function>getchar</function>
- anzusehen und zu versuchen eine schöne und schnelle
- Lösung zu finden, bevor Sie den nächsten Absatz
- lesen. Kommen Sie danach hierher zurück und schauen sich
- meine Lösung an.</para>
-
- <para>Der Schlüssel dazu ein Zeichen an den Eingabestrom
- zurückzugeben, liegt darin, wie wir das Zeichen
- bekommen:</para>
-
- <para>Als erstes überprüfen wir, ob der Puffer leer
- ist, indem wir den Wert von <varname
- role="register">EBX</varname> testen. Wenn er null ist, rufen
- wir die Prozedur <function>read</function> auf.</para>
-
- <para>Wenn ein Zeichen bereit ist verwenden wir <function
- role="opcode">lodsb</function>, dann verringern wir den Wert
- von <varname role="register">EBX</varname>. Die Anweisung
- <function role="opcode">lodsb</function> ist letztendlich
- identisch mit:</para>
-
- <programlisting> mov al, [esi]
- inc esi</programlisting>
-
- <para>Das Byte, welches wir abgerufen haben, verbleibt im Puffer
- bis <function>read</function> zum nächsten Mal aufgerufen
- wird. Wir wissen nicht wann das passiert, aber wir wissen,
- dass es nicht vor dem nächsten Aufruf von
- <function>getchar</function> passiert. Daher ist alles was wir
- tun müssen um das Byte in den Strom "zurückzugeben"
- ist den Wert von <varname role="register">ESI</varname> zu
- verringern und den von <varname role="register">EBX</varname>
- zu erhöhen:</para>
-
- <programlisting>ungetc:
- dec esi
- inc ebx
- ret</programlisting>
-
- <para>Aber seien Sie vorsichtig! Wir sind auf der sicheren Seite,
- solange wir immer nur ein Zeichen im Voraus lesen. Wenn wir
- mehrere kommende Zeichen betrachten und
- <function>ungetc</function> mehrmals hintereinander aufrufen,
- wird es meistens funktionieren, aber nicht immer (und es wird
- ein schwieriger Debug). Warum?</para>
-
- <para>Solange <function>getchar</function>
- <function>read</function> nicht aufrufen muss, befinden sich
- alle im Voraus gelesenen Bytes noch im Puffer und
- <function>ungetc</function> arbeitet fehlerfrei. Aber sobald
- <function>getchar</function> <function>read</function> aufruft
- verändert sich der Inhalt des Puffers.</para>
-
- <para>Wir können uns immer darauf verlassen, dass
- <function>ungetc</function> auf dem zuletzt mit
- <function>getchar</function> gelesenen Zeichen korrekt
- arbeitet, aber nicht auf irgendetwas, das davor gelesen
- wurde.</para>
-
- <para>Wenn Ihr Programm mehr als ein Byte im Voraus lesen soll,
- haben Sie mindestens zwei Möglichkeiten:</para>
-
- <para>Die einfachste Lösung ist, Ihr Programm so zu
- ändern, dass es immer nur ein Byte im Voraus liest, wenn
- das möglich ist.</para>
-
- <para>Wenn Sie diese Möglichkeit nicht haben, bestimmen Sie
- zuerst die maximale Anzahl an Zeichen, die Ihr Programm auf
- einmal an den Eingabestrom zurückgeben muss. Erhöhen
- Sie diesen Wert leicht, nur um sicherzugehen, vorzugsweise auf
- ein Vielfaches von 16&mdash;damit er sich schön
- ausrichtet. Dann passen Sie den <varname>.bss</varname>
- Abschnitt Ihres Codes an und erzeugen einen kleinen
- Reserver-Puffer, direkt vor ihrem Eingabepuffer, in etwa
- so:</para>
-
- <programlisting>section .bss
- resb 16 ; or whatever the value you came up with
- ibuffer resb BUFSIZE
- obuffer resb BUFSIZE</programlisting>
-
- <para>Außerdem müssen Sie <function>ungetc</function>
- anpassen, sodass es den Wert des Bytes, das zurückgegeben
- werden soll, in <varname role="register">AL</varname>
- übergibt:</para>
-
- <programlisting>ungetc:
- dec esi
- inc ebx
- mov [esi], al
- ret</programlisting>
-
- <para>Mit dieser Änderung können Sie sicher
- <function>ungetc</function> bis zu 17 Mal hintereinander
- gqapaufrufen (der erste Aufruf erfolgt noch im Puffer, die
- anderen 16 entweder im Puffer oder in der Reserve).</para>
- </sect2>
- </sect1>
-
- <sect1 id="x86-command-line">
- <sect1info>
- <authorgroup>
- <author>
- <firstname>Fabian</firstname>
- <surname>Ruch</surname>
- <contrib>Übersetzt von </contrib>
- </author>
- </authorgroup>
- </sect1info>
-
- <title>Kommandozeilenparameter</title>
-
- <para>Unser <application>hex</application>-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?</para>
-
- <para>Bevor ein &unix;-System ein Programm ausführt, legt es
- einige Daten auf dem Stack ab (<function
- role="opcode">push</function>) und springt dann an das
- <varname>_start</varname>-Label des Programms. Ja, ich sagte
- springen, nicht aufrufen. Das bedeutet, dass auf die Daten
- zugegriffen werden kann, indem <varname>[esp+offset]</varname>
- ausgelesen wird oder die Daten einfach vom Stack genommen werden
- (<function role="opcode">pop</function>).</para>
-
- <para>Der Wert ganz oben auf dem Stack enthält die Zahl der
- Kommandozeilenparameter. Er wird traditionell
- <varname>argc</varname> wie "argument count" genannt.</para>
-
- <para>Die Kommandozeilenparameter folgen einander, alle
- <varname>argc</varname>. Von diesen wird üblicherweise als
- <varname>argv</varname> wie "argument value(s)" gesprochen. So
- erhalten wir <varname>argv[0]</varname>,
- <varname>argv[1]</varname>, <varname>...</varname> und
- <varname>argv[argc-1]</varname>. 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.</para>
-
- <para>Der <varname>argv</varname>-Liste folgt ein NULL-Zeiger, was
- einfach eine <constant>0</constant> ist. Es gibt noch mehr, aber
- dies ist erst einmal genug für unsere Zwecke.</para>
-
- <note>
- <para>Falls Sie von der
- <acronym>&ms-dos;</acronym>-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.</para>
- </note>
-
- <para>Ausgerüstet mit diesen Kenntnissen, sind wir beinahe
- bereit für eine weitere Version von
- <filename>hex.asm</filename>. Zuerst müssen wir jedoch
- noch ein paar Zeilen zu <filename>system.inc</filename>
- hinzufügen:</para>
-
- <para>Erstens benötigen wir zwei neue Einträge in unserer
- Liste mit den Systemaufrufnummern:</para>
-
- <programlisting>%define SYS_open 5
-%define SYS_close 6</programlisting>
-
- <para>Zweitens fügen wir zwei neue Makros am Ende der Datei
- ein:</para>
-
- <programlisting>%macro sys.open 0
- system SYS_open
-%endmacro
-
-%macro sys.close 0
- system SYS_close
-%endmacro</programlisting>
-
- <para>Und hier ist schließlich unser veränderter
- Quelltext:</para>
-
- <programlisting>%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</programlisting>
-
- <para>In unserem <varname>.data</varname>-Abschnitt befinden
- sich nun die zwei neuen Variablen <varname>fd.in</varname> und
- <varname>fd.out</varname>. Hier legen wir die Dateideskriptoren
- der Ein- und Ausgabedatei ab.</para>
-
- <para>Im <varname>.text</varname>-Abschnitt haben wir die
- Verweise auf <varname>stdin</varname> und
- <varname>stdout</varname> durch <varname>[fd.in]</varname> und
- <varname>[fd.out]</varname> ersetzt.</para>
-
- <para>Der <varname>.text</varname>-Abschnitt beginnt nun mit
- einer einfachen Fehlerbehandlung, welche nur das Programm mit
- einem Rückgabewert von <constant>1</constant> beendet. Die
- Fehlerbehandlung befindet sich vor <varname>_start</varname>,
- sodass wir in geringer Entfernung von der Stelle sind, an der
- der Fehler auftritt.</para>
-
- <para>Selbstverständlich beginnt die
- Programmausführung immer noch bei
- <varname>_start</varname>. Zuerst entfernen wir
- <varname>argc</varname> und <varname>argv[0]</varname> vom
- Stack: Sie sind für uns nicht von Interesse (sprich, in
- diesem Programm).</para>
-
- <para>Wir nehmen <varname>argv[1]</varname> vom Stack und legen
- es in <varname role="register">ECX</varname> ab. Dieses Register
- ist besonders für Zeiger geeignet, da wir mit <function
- role="opcode">jecxz</function> NULL-Zeiger verarbeiten
- können. Falls <varname>argv[1]</varname> 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 <varname>stdin</varname> und Schreiben nach
- <varname>stdout</varname>. Falls wir die Eingabedatei nicht
- öffnen können (z.B. sie ist nicht vorhanden), springen
- wir zur Fehlerbehandlung und beenden das Programm.</para>
-
- <para>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
- <varname>stdout</varname>. 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.</para>
-
- <para>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
- <varname>[fd.in]</varname> und
- <varname>[fd.out]</varname>.</para>
-
- <para>Unsere Binärdatei ist nun kolossale 768 Bytes
- groß.</para>
-
- <para>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:</para>
-
- <itemizedlist>
- <listitem>
- <para>Die Fehlerbehandlung eine Warnung auf
- <varname>stderr</varname> ausgeben lassen.</para>
- </listitem>
-
- <listitem>
- <para>Den <function>Lese</function>- und
- <function>Schreib</function>funkionen eine Fehlerbehandlung
- hinzufügen.</para>
- </listitem>
-
- <listitem>
- <para>Schließen von <varname>stdin</varname>, sobald wir
- eine Eingabedatei öffnen, von <varname>stdout</varname>,
- sobald wir eine Ausgabedatei öffnen.</para>
- </listitem>
-
- <listitem>
- <para>Hinzufügen von Kommandozeilenschaltern wie zum
- Beispiel <parameter>-i</parameter> und
- <parameter>-o</parameter>, sodass wir die Ein- und
- Ausgabedatei in irgendeiner Reihenfolge angeben oder
- vielleicht von <varname>stdin</varname> lesen und in eine
- Datei schreiben können.</para>
- </listitem>
-
- <listitem>
- <para>Ausgeben einer Gebrauchsanweisung, falls die
- Kommandozeilenparameter fehlerhaft sind.</para>
- </listitem>
- </itemizedlist>
-
- <para>Ich beabsichtige, diese Verbesserungen dem Leser als
- Übung zu hinterlassen: Sie wissen bereits alles, das Sie
- wissen müssen, um die Verbesserungen
- durchzuführen.</para>
- </sect1>
-
- <sect1 id="x86-environment">
- <sect1info>
- <authorgroup>
- <author>
- <firstname>Fabian</firstname>
- <surname>Ruch</surname>
- <contrib>Übersetzt von </contrib>
- </author>
- </authorgroup>
- </sect1info>
-
- <title>Die &unix;-Umgebung</title>
-
- <para>Ein entscheidendes Konzept hinter &unix; ist die Umgebung,
- die durch <emphasis>Umgebungsvariablen</emphasis> festgelegt
- wird. Manche werden vom System gesetzt, andere von Ihnen und
- wieder andere von der <application>shell</application> oder
- irgendeinem Programm, das ein anderes lädt.</para>
-
- <sect2 id="x86-find-environment">
- <title>Umgebungsvariablen herausfinden</title>
-
- <para>Ich sagte vorher, dass wenn ein Programm mit der
- Ausführung beginnt, der Stack <varname>argc</varname>
- gefolgt vom durch NULL beendeten <varname>argv</varname>-Array
- und etwas Anderem enthält. Das "etwas Andere" ist die
- <emphasis>Umgebung</emphasis> oder, um genauer zu sein, ein
- durch NULL beendetes Array von Zeigern auf
- <emphasis>Umgebungsvariablen</emphasis>. Davon wird oft als
- <varname>env</varname> gesprochen.</para>
-
- <para>Der Aufbau von <varname>env</varname> entspricht dem von
- <varname>argv</varname>, eine Liste von Speicheradressen gefolgt
- von NULL (<constant>0</constant>). In diesem Fall gibt es kein
- <varname>"envc"</varname>&mdash;wir finden das Ende heraus,
- indem wir nach dem letzten NULL suchen.</para>
-
- <para>Die Variablen liegen normalerweise in der Form
- <varname>name=value</varname> vor, aber manchmal kann der
- <varname>=value</varname>-Teil fehlen. Wir müssen diese
- Möglichkeit in Betracht ziehen.</para>
- </sect2>
-
- <sect2 id="x86-webvar">
- <title>webvars</title>
-
- <para>Ich könnte Ihnen einfach etwas Code zeigen, der die
- Umgebung in der Art vom &unix;-Befehl
- <application>env</application> ausgibt. Aber ich dachte, dass es
- interessanter sei, ein einfaches CGI-Werkzeug in Assembler zu
- schreiben.</para>
-
- <sect3 id="x86-cgi">
- <title>CGI: Ein kurzer Überblick</title>
-
- <para>Ich habe eine <ulink
- url="http://www.whizkidtech.redprince.net/cgi-bin/tutorial">detaillierte
- <acronym>CGI</acronym>-Anleitung</ulink> auf meiner Webseite,
- aber hier ist ein sehr kurzer Überblick über
- <acronym>CGI</acronym>:</para>
-
- <itemizedlist>
- <listitem>
- <para>Der Webserver kommuniziert mit dem
- <acronym>CGI</acronym>-Programm, indem er
- <emphasis>Umgebungsvariablen</emphasis> setzt.</para>
- </listitem>
-
- <listitem>
- <para>Das <acronym>CGI</acronym>-Programm schreibt seine
- Ausgabe auf <filename>stdout</filename>. Der Webserver
- liest von da.</para>
- </listitem>
-
- <listitem>
- <para>Die Ausgabe muss mit einem
- <acronym>HTTP</acronym>-Kopfteil gefolgt von zwei
- Leerzeilen beginnen.</para>
- </listitem>
-
- <listitem>
- <para>Das Programm gibt dann den
- <acronym>HTML</acronym>-Code oder was für einen
- Datentyp es auch immer verarbeitet
- aus.</para>
- </listitem>
-
- <listitem>
- <note>
- <para>Während bestimmte
- <emphasis>Umgebungsvariablen</emphasis> Standardnamen
- benutzen, unterscheiden sich andere, abhängig vom
- Webserver. Dies macht <application>webvars</application>
- zu einem recht nützlichen Werkzeug.</para>
- </note>
- </listitem>
- </itemizedlist>
- </sect3>
-
- <sect3 id="x86-webvars-the-code">
- <title>Der Code</title>
-
- <para>Unser <application>webvars</application>-Programm muss
- also den <acronym>HTTP</acronym>-Kopfteil gefolgt von etwas
- <acronym>HTML</acronym>-Auszeichnung versenden. Dann muss es
- die <emphasis>Umgebungsvariablen</emphasis> eine nach der
- anderen auslesen und sie als Teil der
- <acronym>HTML</acronym>-Seite versenden.</para>
-
- <para>Nun der Code. Ich habe Kommentare und Erklärungen
- direkt in den Code eingefügt:</para>
-
- <programlisting>
-;;;;;;; webvars.asm ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;
-; Copyright (c) 2000 G. Adam Stanislav
-; All rights reserved.
-;
-; Redistribution and use in source and binary forms, with or without
-; modification, are permitted provided that the following conditions
-; are met:
-; 1. Redistributions of source code must retain the above copyright
-; notice, this list of conditions and the following disclaimer.
-; 2. Redistributions in binary form must reproduce the above copyright
-; notice, this list of conditions and the following disclaimer in the
-; documentation and/or other materials provided with the distribution.
-;
-; THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
-; ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-; IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-; ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
-; FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
-; OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
-; HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
-; LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
-; OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
-; SUCH DAMAGE.
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;
-; Version 1.0
-;
-; Started: 8-Dec-2000
-; Updated: 8-Dec-2000
-;
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-%include 'system.inc'
-
-section .data
-http db 'Content-type: text/html', 0Ah, 0Ah
- db '&lt;?xml version="1.0" encoding="utf-8"?&gt;', 0Ah
- db '&lt;!DOCTYPE html PUBLIC "-//W3C/DTD XHTML Strict//EN" '
- db '"DTD/xhtml1-strict.dtd"&gt;', 0Ah
- db '&lt;html xmlns="http://www.w3.org/1999/xhtml" '
- db 'xml.lang="en" lang="en"&gt;', 0Ah
- db '&lt;head&gt;', 0Ah
- db '&lt;title&gt;Web Environment&lt;/title&gt;', 0Ah
- db '&lt;meta name="author" content="G. Adam Stanislav" /&gt;', 0Ah
- db '&lt;/head&gt;', 0Ah, 0Ah
- db '&lt;body bgcolor="#ffffff" text="#000000" link="#0000ff" '
- db 'vlink="#840084" alink="#0000ff"&gt;', 0Ah
- db '&lt;div class="webvars"&gt;', 0Ah
- db '&lt;h1&gt;Web Environment&lt;/h1&gt;', 0Ah
- db '&lt;p&gt;The following &lt;b&gt;environment variables&lt;/b&gt; are defined '
- db 'on this web server:&lt;/p&gt;', 0Ah, 0Ah
- db '&lt;table align="center" width="80" border="0" cellpadding="10" '
- db 'cellspacing="0" class="webvars"&gt;', 0Ah
-httplen equ $-http
-left db '&lt;tr&gt;', 0Ah
- db '&lt;td class="name"&gt;&lt;tt&gt;'
-leftlen equ $-left
-middle db '&lt;/tt&gt;&lt;/td&gt;', 0Ah
- db '&lt;td class="value"&gt;&lt;tt&gt;&lt;b&gt;'
-midlen equ $-middle
-undef db '&lt;i&gt;(undefined)&lt;/i&gt;'
-undeflen equ $-undef
-right db '&lt;/b&gt;&lt;/tt&gt;&lt;/td&gt;', 0Ah
- db '&lt;/tr&gt;', 0Ah
-rightlen equ $-right
-wrap db '&lt;/table&gt;', 0Ah
- db '&lt;/div&gt;', 0Ah
- db '&lt;/body&gt;', 0Ah
- db '&lt;/html&gt;', 0Ah, 0Ah
-wraplen equ $-wrap
-
-section .text
-global _start
-_start:
- ; First, send out all the http and xhtml stuff that is
- ; needed before we start showing the environment
- push dword httplen
- push dword http
- push dword stdout
- sys.write
-
- ; Now find how far on the stack the environment pointers
- ; are. We have 12 bytes we have pushed before "argc"
- mov eax, [esp+12]
-
- ; We need to remove the following from the stack:
- ;
- ; The 12 bytes we pushed for sys.write
- ; The 4 bytes of argc
- ; The EAX*4 bytes of argv
- ; The 4 bytes of the NULL after argv
- ;
- ; Total:
- ; 20 + eax * 4
- ;
- ; Because stack grows down, we need to ADD that many bytes
- ; to ESP.
- lea esp, [esp+20+eax*4]
- cld ; This should already be the case, but let's be sure.
-
- ; Loop through the environment, printing it out
-.loop:
- pop edi
- or edi, edi ; Done yet?
- je near .wrap
-
- ; Print the left part of HTML
- push dword leftlen
- push dword left
- push dword stdout
- sys.write
-
- ; It may be tempting to search for the '=' in the env string next.
- ; But it is possible there is no '=', so we search for the
- ; terminating NUL first.
- mov esi, edi ; Save start of string
- sub ecx, ecx
- not ecx ; ECX = FFFFFFFF
- sub eax, eax
-repne scasb
- not ecx ; ECX = string length + 1
- mov ebx, ecx ; Save it in EBX
-
- ; Now is the time to find '='
- mov edi, esi ; Start of string
- mov al, '='
-repne scasb
- not ecx
- add ecx, ebx ; Length of name
-
- push ecx
- push esi
- push dword stdout
- sys.write
-
- ; Print the middle part of HTML table code
- push dword midlen
- push dword middle
- push dword stdout
- sys.write
-
- ; Find the length of the value
- not ecx
- lea ebx, [ebx+ecx-1]
-
- ; Print "undefined" if 0
- or ebx, ebx
- jne .value
-
- mov ebx, undeflen
- mov edi, undef
-
-.value:
- push ebx
- push edi
- push dword stdout
- sys.write
-
- ; Print the right part of the table row
- push dword rightlen
- push dword right
- push dword stdout
- sys.write
-
- ; Get rid of the 60 bytes we have pushed
- add esp, byte 60
-
- ; Get the next variable
- jmp .loop
-
-.wrap:
- ; Print the rest of HTML
- push dword wraplen
- push dword wrap
- push dword stdout
- sys.write
-
- ; Return success
- push dword 0
- sys.exit</programlisting>
-
- <para>Dieser Code erzeugt eine 1.396-Byte große
- Binärdatei. Das meiste davon sind Daten, d.h., die
- <acronym>HTML</acronym>-Auszeichnung, die wir versenden
- müssen.</para>
-
- <para>Assemblieren Sie es wie immer:</para>
-
- <screen>&prompt.user; <userinput>nasm -f elf webvars.asm</userinput>
-&prompt.user; <userinput>ld -s -o webvars webvars.o</userinput></screen>
-
- <para>Um es zu benutzen, müssen Sie
- <filename>webvars</filename> auf Ihren Webserver hochladen.
- Abhängig von Ihrer Webserver-Konfiguration, müssen
- Sie es vielleicht in einem speziellen
- <filename>cgi-bin</filename>-Verzeichnis ablegen oder es mit
- einer <filename>.cgi</filename>-Dateierweiterung
- versehen.</para>
-
- <para>Schließlich benötigen Sie Ihren Webbrowser,
- um sich die Ausgabe anzusehen. Um die Ausgabe auf meinem
- Webserver zu sehen, gehen Sie bitte auf <ulink
- url="http://www.int80h.org/webvars/"><filename>http://www.int80h.org/webvars/</filename></ulink>.
- Falls Sie neugierig sind, welche zusätzlichen Variablen
- in einem passwortgeschützten Webverzeichnis vorhanden
- sind, gehen Sie auf <ulink
- url="http://www.int80h.org/private/"><filename>http://www.int80h.org/private/</filename></ulink>
- unter Benutzung des Benutzernamens <userinput>asm</userinput>
- und des Passworts <userinput>programmer</userinput>.</para>
- </sect3>
- </sect2>
- </sect1>
-
- <sect1 id="x86-files">
- <sect1info>
- <authorgroup>
- <author>
- <firstname>Paul</firstname>
- <surname>Keller</surname>
- <contrib>Übersetzt von </contrib>
- </author>
- <author>
- <firstname>Fabian</firstname>
- <surname>Borschel</surname>
- </author>
- </authorgroup>
- </sect1info>
-
- <title>Arbeiten mit Dateien</title>
-
- <para>Wir haben bereits einfache Arbeiten mit Dateien gemacht:
- Wir wissen wie wir sie öffnen und schliessen, oder wie
- man sie mit Hilfe von Buffern liest und schreibt. Aber &unix;
- bietet viel mehr Funktionalität wenn es um Dateien geht.
- Wir werden einige von ihnen in dieser Sektion untersuchen und
- dann mit einem netten Datei Konvertierungs Werkzeug
- abschliessen.</para>
-
- <para>In der Tat, Lasst uns am Ende beginnen, also mit dem Datei
- Konvertierungs Werkzeug. Es macht Programmieren immer einfacher,
- wenn wir bereits am Anfang wissen was das End Produkt bezwecken
- soll.</para>
-
- <para>Eines der ersten Programme die ich für &unix; schrieb
- war <ulink url="ftp://ftp.int80h.org/unix/tuc/"><application>
- tuc</application></ulink>, ein Text-Zu-&unix; Datei Konvertierer.
- Es konvertiert eine Text Datei von einem anderen Betriebssystem
- zu einer &unix; Text Datei. Mit anderen Worten, es ändert
- die verschiedenen Arten von Zeilen Begrenzungen zu der Zeilen
- Begrenzungs Konvention von &unix;. Es speichert die Ausgabe in
- einer anderen Datei. Optional konvertiert es eine &unix; Text
- Datei zu einer <acronym>DOS</acronym> Text Datei.</para>
-
- <para>Ich habe <application>tuc</application> sehr oft benutzt,
- aber nur von irgendeinem anderen <acronym>OS</acronym> nach
- &unix; zu konvertieren, niemals anders herum. Ich habe mir immer
- gewünscht das die Datei einfach überschrieben wird
- anstatt das ich die Ausgabe in eine andere Datei senden muss.
- Meistens, habe ich diesen Befehl verwendet:</para>
-
- <screen>&prompt.user; <userinput>tuc <replaceable>myfile tempfile</replaceable></userinput>
-&prompt.user; <userinput>mv <replaceable>tempfile myfile</replaceable></userinput></screen>
-
- <para>Es wäre schö ein <application>ftuc</application>
- zu haben, also, <emphasis>fast tuc</emphasis>, und es so zu
- benutzen:</para>
-
- <screen>&prompt.user; <userinput>ftuc <replaceable>myfile</replaceable></userinput></screen>
-
- <para>In diesem Kapitel werden wir dann, <application>ftuc
- </application> in Assembler schreiben (das Original
- <application>tuc</application> ist in C), und verschiedene
- Datei-Orientierte Kernel Dienste in dem Prozess studieren.</para>
-
- <para>Auf erste Sicht, ist so eine Datei Konvertierung sehr
- simpel: Alles was du zu tun hast, ist die Wagenrückläufe
- zu entfernen, richtig?</para>
-
- <para>Wenn du mit ja geantwortet hast, denk nochmal darüber
- nach: Dieses Vorgehen wird die meiste Zeit funktionieren
- (zumindest mit <acronym>MSDOS</acronym> Text Dateien), aber
- gelegentlich fehlschlagen.</para>
-
- <para>Das Problem ist das nicht alle &unix; Text Dateien ihre
- Zeilen mit einer Wagen Rücklauf / Zeilenvorschub Sequenz
- beenden. Manche benutzen Wagenrücklauf ohne Zeilenvorschub.
- Andere kombinieren mehrere leere Zeilen in einen einzigen
- Wagenrücklauf gefolgt von mehreren Zeilenvorschüben.
- Und so weiter.</para>
-
- <para>Ein Text Datei Konvertierer muss dann also in der Lage sein
- mit allen möglichen Zeilenenden umzugehen:</para>
-
- <itemizedlist>
- <listitem>
- <para>Wagenrücklauf / Zeilenvorschub</para>
- </listitem>
-
- <listitem>
- <para>Wagenrücklauf</para>
- </listitem>
-
- <listitem>
- <para>Zeilenvorschub / Wagenrücklauf</para>
- </listitem>
-
- <listitem>
- <para>Zeilenvorschub</para>
- </listitem>
- </itemizedlist>
-
- <para>Es sollte außerdem in der Lage sein mit Dateien
- umzugehen die irgendeine Art von Kombination der oben stehenden
- Möglichkeiten verwendet. (z.B., Wagenrücklauf gefolgt
- von mehreren Zeilenvorschüben).</para>
-
- <sect2 id="x86-finite-state-machine">
- <title>Endlicher Zustandsautomat</title>
-
- <para>Das Problem wird einfach gelöst in dem man eine
- Technik benutzt die sich <emphasis>Endlicher
- Zustandsautomat</emphasis> nennt, ursprünglich wurde sie
- von den Designern digitaler elektronischer Schaltkreise
- entwickelt. Eine <emphasis>Endlicher Zustandsautomat</emphasis>
- ist ein digitaler Schaltkreis dessen Ausgabe nicht nur von der
- Eingabe abhängig ist sondern auch von der vorherigen
- Eingabe, d.h., von seinem Status. Der Mikroprozessor ist ein
- Beispiel für einen <emphasis>Endlichen Zustandsautomaten
- </emphasis>: Unser Assembler Sprach Code wird zu
- Maschinensprache übersetzt in der manche Assembler Sprach
- Codes ein einzelnes Byte produzieren, während andere
- mehrere Bytes produzieren. Da der Microprozessor die Bytes
- einzeln aus dem Speicher liest, ändern manche nur seinen
- Status anstatt eine Ausgabe zu produzieren. Wenn alle Bytes
- eines OP Codes gelesen wurden, produziert der Mikroprozessor
- eine Ausgabe, oder ändert den Wert eines Registers,
- etc.</para>
-
- <para>Aus diesem Grund, ist jede Software eigentlich nur eine
- Sequenz von Status Anweisungen für den Mikroprozessor.
- Dennoch, ist das Konzept eines <emphasis>Endlichen
- Zustandsautomaten</emphasis> auch im Software Design sehr
- hilfreich.</para>
-
- <para>Unser Text Datei Konvertierer kann als
- <emphasis>Endlicher Zustandsautomat</emphasis> mit 3
- möglichen Stati desgined werden. Wir könnten diese
- von 0-2 benennen, aber es wird uns das Leben leichter machen
- wenn wir ihnen symbolische Namen geben:</para>
-
- <itemizedlist>
- <listitem>
- <para><symbol>ordinary</symbol></para>
- </listitem>
-
- <listitem>
- <para><symbol>cr</symbol></para>
- </listitem>
-
- <listitem>
- <para><symbol>lf</symbol></para>
- </listitem>
- </itemizedlist>
-
- <para>Unser Programm wird in dem <symbol>ordinary</symbol> Status
- starten. Während dieses Status, hängt die Aktion des
- Programms von seiner Eingabe wie folgt ab:</para>
-
- <itemizedlist>
- <listitem>
- <para>Wenn die Eingabe etwas anderes als ein
- Wagenrücklauf oder einem Zeilenvorschub ist, wird die
- Eingabe einfach nur an die Ausgabe geschickt. Der Status
- bleibt unverändert.</para>
- </listitem>
-
- <listitem>
- <para>Wenn die Eingabe ein Wagenrücklauf ist, wird der
- Status auf <symbol>cr</symbol> gesetzt. Die Eingabe wird
- dann verworfen, d.h., es entsteht keine Ausgabe.</para>
- </listitem>
-
- <listitem>
- <para>Wenn die Eingabe ein Zeilenvorschub ist, wird der
- Status auf <symbol>lf</symbol> gesetzt. Die Eingabe wird
- dann verworfen.</para>
- </listitem>
- </itemizedlist>
-
- <para>Wann immer wir in dem <symbol>cr</symbol> Status sind,
- ist das weil die letzte Eingabe ein Wagenrücklauf war,
- welcher nicht verarbeitet wurde. Was unsere Software in
- diesem Status macht hängt von der aktuellen Eingabe
- ab:</para>
-
- <itemizedlist>
- <listitem>
- <para>Wenn die Eingabe irgendetwas anderes als ein
- Wagenrücklauf oder ein Zeilenvorschub ist, dann gib
- einen Zeilenvorschub aus, dann gib die Eingabe aus und
- dann ändere den Status zu
- <symbol>ordinary</symbol>.</para>
- </listitem>
-
- <listitem>
- <para>Wenn die Eingabe ein Wagenrücklauf ist, haben
- wir zwei (oder mehr) Wagenrückläufe in einer
- Reihe. Wir verwerfen die Eingabe, wir geben einen
- Zeilenvorschub aus und lassen den Status
- unverändert.</para>
- </listitem>
-
- <listitem>
- <para>Wenn die Eingabe ein Zeilenvorschub ist, geben wir
- den Zeilenvorschub aus und ändern den Status zu
- <symbol>ordinary</symbol>. Achte darauf, dass das nicht
- das gleiche wie in dem Fall oben drüber ist &ndash;
- würden wir versuchen beide zu kombinieren,
- würden wir zwei Zeilenvorschübe anstatt einen
- ausgeben.</para>
- </listitem>
- </itemizedlist>
-
- <para>Letztendlich, sind wir in dem <symbol>lf</symbol> Status
- nachdem wir einen Zeilenvorschub empfangen haben der nicht
- nach einem Wagenrücklauf kam. Das wird passieren wenn
- unsere Datei bereits im &unix; Format ist, oder jedesmal wenn
- mehrere Zeilen in einer Reihe durch einen einzigen
- Wagenrücklauf gefolgt von mehreren Zeilenvorschüben
- ausgedrückt wird, oder wenn die Zeile mit einer
- Zeilenvorschub / Wagenrücklauf Sequenz endet. Wir
- sollten mit unserer Eingabe in diesem Status folgendermaßen
- umgehen:</para>
-
- <itemizedlist>
- <listitem>
- <para>Wenn die Eingabe irgendetwas anderes als ein
- Wagenrücklauf oder ein Zeilenvorschub ist, geben wir
- einen Zeilenvorschub aus, geben dann die Eingabe aus und
- ändern dann den Status zu <symbol>ordinary</symbol>.
- Das ist exakt die gleiche Aktion wie in dem
- <symbol>cr</symbol> Status nach dem Empfangen der selben
- Eingabe.</para>
- </listitem>
-
- <listitem>
- <para>Wenn die Eingabe ein Wagenrücklauf ist, verwerfen
- wir die Eingabe, geben einen Zeilenvorschub aus und
- ändern dann den Status zu <symbol>ordinary</symbol>.
- </para>
- </listitem>
-
- <listitem>
- <para>Wenn die Eingabe ein Zeilenvorschub ist, geben wir den
- Zeilenvorschub aus und lassen den Status unverändert.
- </para>
- </listitem>
- </itemizedlist>
-
- <sect3 id="x86-final-state">
- <title>Der Endgültige Status</title>
-
- <para>Der obige <emphasis>Endliche Zustandsautomat</emphasis>
- funktioniert für die gesamte Datei, aber lässt die
- Möglichkeit das die letzte Zeile ignoriert wird. Das
- wird jedesmal passieren wenn die Datei mit einem einzigen
- Wagenrücklauf oder einem einzigen Zeilenvorschub endet.
- Daran habe ich nicht gedacht als ich
- <application>tuc</application> schrieb, nur um festzustellen,
- daß das letzte Zeilenende gelegentlich weggelassen
- wird.</para>
-
- <para>Das Problem wird einfach dadurch gelöst, indem man
- den Status überprüft nachdem die gesamte Datei
- verarbeitet wurde. Wenn der Status nicht
- <symbol>ordinary</symbol> ist, müssen wir nur den
- letzten Zeilenvorschub ausgeben.</para>
-
- <note>
- <para>Nachdem wir unseren Algorithmus nun als einen
- <emphasis>Endlichen Zustandsautomaten</emphasis> formuliert
- haben, könnten wir einfach einen festgeschalteten
- digitalen elektronischen Schaltkreis (einen "Chip")
- designen, der die Umwandlung für uns übernimmt.
- Natürlich wäre das sehr viel teurer, als ein
- Assembler Programm zu schreiben.</para>
- </note>
- </sect3>
-
- <sect3 id="x86-tuc-counter">
- <title>Der Ausgabe Zähler</title>
-
- <para>Weil unser Datei Konvertierungs Programm
- möglicherweise zwei Zeichen zu einem kombiniert,
- müssen wir einen Ausgabe Zähler verwenden. Wir
- initialisieren den Zähler zu <constant>0</constant>
- und erhöhen ihn jedes mal wenn wir ein Zeichen an die
- Ausgabe schicken. Am Ende des Programms, wird der
- Zähler uns sagen auf welche Grösse wir die Datei
- setzen müssen.</para>
- </sect3>
- </sect2>
-
- <sect2 id="x86-software-fsm">
- <title>Implementieren von EZ als Software</title>
-
- <para>Der schwerste Teil beim arbeiten mit einer
- <emphasis>Endlichen Zustandsmaschine</emphasis> ist das
- analysieren des Problems und dem ausdrücken als eine
- <emphasis>Endliche Zustandsmaschine</emphasis>. That geschafft,
- schreibt sich die Software fast wie von selbst.</para>
-
- <para>In eine höheren Sprache, wie etwa C, gibt es mehrere
- Hauptansätze. Einer wäre ein <function
- role="statement">switch</function> Angabe zu verwenden die
- auswählt welche Funktion genutzt werden soll. Zum
- Beispiel,</para>
-
- <programlisting>
- switch (state) {
- default:
- case REGULAR:
- regular(inputchar);
- break;
- case CR:
- cr(inputchar);
- break;
- case LF:
- lf(inputchar);
- break;
- }
- </programlisting>
-
- <para>Ein anderer Ansatz ist es ein Array von Funktions Zeigern
- zu benutzen, etwa wie folgt:</para>
-
- <programlisting>
- (output[state])(inputchar);
- </programlisting>
-
- <para>Noch ein anderer ist es aus <varname>state</varname> einen
- Funktions Zeiger zu machen und ihn zu der entsprechenden
- Funktion zeigen zu lassen:</para>
-
- <programlisting>
- (*state)(inputchar);
- </programlisting>
-
- <para>Das ist der Ansatz den wir in unserem Programm verwenden
- werden, weil es in Assembler sehr einfach und schnell geht.
- Wir werden einfach die Adresse der Prozedur in <varname
- role="register">EBX</varname> speichern und dann einfach das
- ausgeben:</para>
-
- <programlisting>
- call ebx
- </programlisting>
-
- <para>Das ist wahrscheinlich schneller als die Adresse im Code
- zu hardcoden weil der Mikroprozessor die Adresse nicht aus dem
- Speicher lesen muss&mdash;es ist bereits in einer der Register
- gespeichert. Ich sagte <emphasis>wahrscheinlich</emphasis>
- weil durch das Cachen neuerer Mikroprozessoren beide Varianten
- in etwa gleich schnell sind.</para>
- </sect2>
-
- <sect2 id="memory-mapped-files">
- <title>Speicher abgebildete Dateien</title>
-
- <para>Weil unser Programm nur mit einzelnen Dateien
- funktioniert, können wir nicht den Ansatz verwedenden der
- zuvor funktioniert hat, d.h., von einer Eingabe Datei zu lesen
- und in eine Ausgabe Datei zu schreiben.</para>
-
- <para>&unix; erlaubt es uns eine Datei, oder einen Bereich einer
- Datei, in den Speicher abzubilden. Um das zu tun, müssen
- wir zuerst eine Datei mit den entsprechenden Lese/Schreib
- Flags öffnen. Dann benutzen wir den <function
- role="syscall">mmap</function> system call um sie in den
- Speicher abzubilden. Ein Vorteil von <function
- role="syscall">mmap</function> ist, das es automatisch mit
- virtuellem Speicher arbeitet: Wir können mehr von der
- Datei im Speicher abbilden als wir überhaupt
- physikalischen Speicher zur Verfügung haben, noch immer
- haben wir aber durch normale OP Codes wie <function
- role="opcode">mov</function>, <function
- role="opcode">lods</function>, und <function
- role="opcode">stos</function> Zugriff darauf. Egal welche
- Änderungen wir an dem Speicherabbild der Datei vornehmen,
- sie werden vom System in die Datei geschrieben. Wir
- müssen die Datei nicht offen lassen: So lange sie
- abgebildet bleibt, können wir von ihr lesen und in sie
- schreiben.</para>
-
- <para>Ein 32-bit Intel Mikroprozessor kann auf bis zu vier
- Gigabyte Speicher zugreifen &ndash; physisch oder virtuell.
- Das FreeBSD System erlaubt es uns bis zu der Hälfte
- für die Datei Abbildung zu verwenden.</para>
-
- <para>Zur Vereinfachung, werden wir in diesem Tutorial nur
- Dateien konvertieren die in ihrere Gesamtheit im Speicher
- abgebildet werden können. Es gibt wahrscheinlich nicht
- all zu viele Text Dateien die eine Grösse von zwei
- Gigabyte überschreiben. Falls unser Programm doch auf
- eine trifft, wird es einfach eine Meldung anzeigen mit dem
- Vorschlag das originale <application>tuc</application> statt
- dessen zu verwenden.</para>
-
- <para>Wenn du deine Kopie von
- <filename>syscalls.master</filename> überprüfst,
- wirst du zwei verschiedene Systemaufrufe
- finden die sich <function role="syscall">mmap</function>
- nennen. Das kommt von der Entwicklung von &unix;: Es gab das
- traditionelle <acronym>BSD</acronym> <function
- role="syscall">mmap</function>, Systemaufruf 71. Dieses wurde
- durch das <acronym>&posix;</acronym> <function
- role="syscall">mmap</function> ersetzt, Systemaufruf 197. Das
- FreeBSD System unterstützt beide, weil ältere
- Programme mit der originalen <acronym>BSD</acronym> Version
- geschrieben wurden. Da neue Software die
- <acronym>&posix;</acronym> Version nutzt, werden wir diese
- auch verwenden.</para>
-
- <para>Die <filename>syscalls.master</filename> Datei zeigt die
- <acronym>&posix;</acronym> Version wie folgt:</para>
-
- <programlisting>
-197 STD BSD { caddr_t mmap(caddr_t addr, size_t len, int prot, \
- int flags, int fd, long pad, off_t pos); }
- </programlisting>
-
- <para>Das weicht etwas von dem ab was
- <citerefentry><refentrytitle>mmap</refentrytitle>
- <manvolnum>2</manvolnum></citerefentry> sagt. Das ist weil
- <citerefentry><refentrytitle>mmap</refentrytitle>
- <manvolnum>2</manvolnum></citerefentry> die C Version
- beschreibt.</para>
-
- <para>Der Unterschiede liegt in dem <varname>long pad</varname>
- Argument, welches in der C Version nicht vorhanden ist. Wie
- auch immer, der FreeBSD Systemaufruf fügt einen 32-bit
- Block ein nachdem es ein 64-Bit Argument auf den Stack
- ge<function role="opcode">push</function>t hat. In diesem
- Fall, ist <varname>off_t</varname> ein 64-Bit Wert.</para>
-
- <para>Wenn wir fertig sind mit dem Arbeiten einer im Speicher
- abgebildeten Datei, entfernen wir das Speicherabbild mit dem
- <function role="syscall">munmap</function> Systemaufruf:</para>
-
- <tip>
- <para>Für eine detailliert Behandlung von <function
- role="syscall">mmap</function>, sieh in W. Richard Stevens'
- <ulink url="http://www.int80h.org/cgi-bin/isbn?isbn=0130810819">
- Unix Network Programming, Volume 2, Chapter 12</ulink>
- nach.</para>
- </tip>
- </sect2>
-
- <sect2 id="x86-file-size">
- <title>Feststellen der Datei Grösse</title>
-
- <para>Weil wir <function role="syscall">mmap</function> sagen
- müssen wie viele Bytes von Datei wir im Speicher abbilden
- wollen und wir außerdem die gesamte Datei abbilden wollen,
- müssen wir die Grösse der Datei feststellen.</para>
-
- <para>Wir können den <function
- role="syscall">fstat</function> Systemaufruf verwenden um alle
- Informationen über eine geöffnete Datei zu erhalten
- die uns das System geben kann. Das beinhaltet die Datei
- Grösse.</para>
-
- <para>Und wieder, zeigt uns <filename>syscalls.master</filename>
- zwei Versionen von <function role="syscall">fstat</function>,
- eine traditionelle (Systemaufruf 62), und eine
- <acronym>&posix;</acronym> (Systemaufruf 189) Variante.
- Natürlich, verwenden wir die <acronym>&posix;</acronym>
- Version:</para>
-
- <programlisting>
-189 STD POSIX { int fstat(int fd, struct stat *sb); }
- </programlisting>
-
- <para>Das ist ein sehr unkomplizierter Aufruf: Wir
- übergeben ihm die Adresse einer
- <structname>stat</structname> Structure und den Deskriptor
- einer geöffneten Datei. Es wird den Inhalt der
- <structname>stat</structname> Struktur ausfüllen.</para>
-
- <para>Ich muss allerdings sagen, das ich versucht habe die
- <structname>stat</structname> Struktur in dem
- <varname>.bss</varname> Bereich zu deklarieren, und
- <function role="syscall">fstat</function> mochte es nicht:
- Es setzte das Carry Flag welches einen Fehler anzeigt.
- Nachdem ich den Code veränderte so dass er die Struktur
- auf dem Stack anlegt, hat alles gut funktioniert.</para>
- </sect2>
-
- <sect2 id="x86-ftruncate">
- <title>Ändern der Dateigrösse</title>
-
- <para>Dadurch das unser Programm
- Wagenrücklauf/Zeilenvorschub-Sequenzen in einfache
- Zeilenvorschübe zusammenfassen könnte, könnte
- unsere Ausgabe kleiner sein als unsere Eingabe. Und da wir die
- Ausgabe in dieselbe Datei um, aus der wir unsere Eingabe
- erhalten, müssen wir eventuell die Dateigrösse
- anpassen.</para>
-
- <para>Der Systemaufruf <function
- role="syscall">ftruncate</function> erlaubt uns, dies zu tun.
- Abgesehen von dem etwas unglücklich gewählten Namen
- <function role="syscall">ftruncate</function> können wir
- mit dieser Funktion eine Datei vergrössern, oder
- verkleinern.</para>
-
- <para>Und ja, wir werden zwei Versionen von <function
- role="syscall">ftruncate</function> in
- <filename>syscalls.master</filename> finden, eine ältere
- (130) und eine neuere (201). Wir werden die neuere Version
- verwenden:</para>
-
- <programlisting>
-201 STD BSD { int ftruncate(int fd, int pad, off_t length); }
- </programlisting>
-
- <para>Beachten Sie bitte, dass hier wieder <varname>int
- pad</varname> verwendet wird.</para>
- </sect2>
-
- <sect2 id="x86-ftuc">
- <title>ftuc</title>
-
- <para>Wir wissen jetzt alles nötige, um
- <application>ftuc</application> zu schreiben. Wir beginnen,
- indem wir ein paar neue Zeilen der Datei
- <filename>system.inc</filename> hinzufügen. Als erstes
- definieren wir irgendwo am Anfang der Datei einige Konstanten
- und Strukturen:</para>
-
- <programlisting>
-;;;;;;; open flags
-%define O_RDONLY 0
-%define O_WRONLY 1
-%define O_RDWR 2
-
-;;;;;;; mmap flags
-%define PROT_NONE 0
-%define PROT_READ 1
-%define PROT_WRITE 2
-%define PROT_EXEC 4
-;;
-%define MAP_SHARED 0001h
-%define MAP_PRIVATE 0002h
-
-;;;;;;; stat structure
-struc stat
-st_dev resd 1 ; = 0
-st_ino resd 1 ; = 4
-st_mode resw 1 ; = 8, size is 16 bits
-st_nlink resw 1 ; = 10, ditto
-st_uid resd 1 ; = 12
-st_gid resd 1 ; = 16
-st_rdev resd 1 ; = 20
-st_atime resd 1 ; = 24
-st_atimensec resd 1 ; = 28
-st_mtime resd 1 ; = 32
-st_mtimensec resd 1 ; = 36
-st_ctime resd 1 ; = 40
-st_ctimensec resd 1 ; = 44
-st_size resd 2 ; = 48, size is 64 bits
-st_blocks resd 2 ; = 56, ditto
-st_blksize resd 1 ; = 64
-st_flags resd 1 ; = 68
-st_gen resd 1 ; = 72
-st_lspare resd 1 ; = 76
-st_qspare resd 4 ; = 80
-endstruc
- </programlisting>
-
- <para>Wir definieren die neuen Systemaufrufe:</para>
-
- <programlisting>
-%define SYS_mmap 197
-%define SYS_munmap 73
-%define SYS_fstat 189
-%define SYS_ftruncate 201
- </programlisting>
-
- <para>Wir fügen die Makros hinzu:</para>
-
- <programlisting>
-%macro sys.mmap 0
- system SYS_mmap
-%endmacro
-
-%macro sys.munmap 0
- system SYS_munmap
-%endmacro
-
-%macro sys.ftruncate 0
- system SYS_ftruncate
-%endmacro
-
-%macro sys.fstat 0
- system SYS_fstat
-%endmacro
- </programlisting>
-
- <para>Und hier ist unser Code:</para>
-
- <programlisting>
-;;;;;;; Fast Text-to-Unix Conversion (ftuc.asm) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;;
-;; Started: 21-Dec-2000
-;; Updated: 22-Dec-2000
-;;
-;; Copyright 2000 G. Adam Stanislav.
-;; All rights reserved.
-;;
-;;;;;;; v.1 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-%include 'system.inc'
-
-section .data
- db 'Copyright 2000 G. Adam Stanislav.', 0Ah
- db 'All rights reserved.', 0Ah
-usg db 'Usage: ftuc filename', 0Ah
-usglen equ $-usg
-co db "ftuc: Can't open file.", 0Ah
-colen equ $-co
-fae db 'ftuc: File access error.', 0Ah
-faelen equ $-fae
-ftl db 'ftuc: File too long, use regular tuc instead.', 0Ah
-ftllen equ $-ftl
-mae db 'ftuc: Memory allocation error.', 0Ah
-maelen equ $-mae
-
-section .text
-
-align 4
-memerr:
- push dword maelen
- push dword mae
- jmp short error
-
-align 4
-toolong:
- push dword ftllen
- push dword ftl
- jmp short error
-
-align 4
-facerr:
- push dword faelen
- push dword fae
- jmp short error
-
-align 4
-cantopen:
- push dword colen
- push dword co
- jmp short error
-
-align 4
-usage:
- push dword usglen
- push dword usg
-
-error:
- push dword stderr
- sys.write
-
- push dword 1
- sys.exit
-
-align 4
-global _start
-_start:
- pop eax ; argc
- pop eax ; program name
- pop ecx ; file to convert
- jecxz usage
-
- pop eax
- or eax, eax ; Too many arguments?
- jne usage
-
- ; Open the file
- push dword O_RDWR
- push ecx
- sys.open
- jc cantopen
-
- mov ebp, eax ; Save fd
-
- sub esp, byte stat_size
- mov ebx, esp
-
- ; Find file size
- push ebx
- push ebp ; fd
- sys.fstat
- jc facerr
-
- mov edx, [ebx + st_size + 4]
-
- ; File is too long if EDX != 0 ...
- or edx, edx
- jne near toolong
- mov ecx, [ebx + st_size]
- ; ... or if it is above 2 GB
- or ecx, ecx
- js near toolong
-
- ; Do nothing if the file is 0 bytes in size
- jecxz .quit
-
- ; Map the entire file in memory
- push edx
- push edx ; starting at offset 0
- push edx ; pad
- push ebp ; fd
- push dword MAP_SHARED
- push dword PROT_READ | PROT_WRITE
- push ecx ; entire file size
- push edx ; let system decide on the address
- sys.mmap
- jc near memerr
-
- mov edi, eax
- mov esi, eax
- push ecx ; for SYS_munmap
- push edi
-
- ; Use EBX for state machine
- mov ebx, ordinary
- mov ah, 0Ah
- cld
-
-.loop:
- lodsb
- call ebx
- loop .loop
-
- cmp ebx, ordinary
- je .filesize
-
- ; Output final lf
- mov al, ah
- stosb
- inc edx
-
-.filesize:
- ; truncate file to new size
- push dword 0 ; high dword
- push edx ; low dword
- push eax ; pad
- push ebp
- sys.ftruncate
-
- ; close it (ebp still pushed)
- sys.close
-
- add esp, byte 16
- sys.munmap
-
-.quit:
- push dword 0
- sys.exit
-
-align 4
-ordinary:
- cmp al, 0Dh
- je .cr
-
- cmp al, ah
- je .lf
-
- stosb
- inc edx
- ret
-
-align 4
-.cr:
- mov ebx, cr
- ret
-
-align 4
-.lf:
- mov ebx, lf
- ret
-
-align 4
-cr:
- cmp al, 0Dh
- je .cr
-
- cmp al, ah
- je .lf
-
- xchg al, ah
- stosb
- inc edx
-
- xchg al, ah
- ; fall through
-
-.lf:
- stosb
- inc edx
- mov ebx, ordinary
- ret
-
-align 4
-.cr:
- mov al, ah
- stosb
- inc edx
- ret
-
-align 4
-lf:
- cmp al, ah
- je .lf
-
- cmp al, 0Dh
- je .cr
-
- xchg al, ah
- stosb
- inc edx
-
- xchg al, ah
- stosb
- inc edx
- mov ebx, ordinary
- ret
-
-align 4
-.cr:
- mov ebx, ordinary
- mov al, ah
- ; fall through
-
-.lf:
- stosb
- inc edx
- ret
- </programlisting>
-
- <warning>
- <para>Verwenden Sie dieses Programm nicht mit Dateien, die
- sich auf Datenträgern befinden, welche mit
- <acronym>&ms-dos;</acronym> oder &windows; formatiert
- wurden. Anscheinend gibt es im Code von FreeBSD einen
- subtilen Bug, wenn <function role="syscall">mmap</function>
- auf solchen Datenträgern verwendet wird: Wenn die Datei
- eine bestimmte Grösse überschreitet, füllt
- <function role="syscall">mmap</function> den Speicher mit
- lauter Nullen, und überschreibt damit anschliessend den
- Dateiinhalt.</para>
- </warning>
- </sect2>
- </sect1>
-
- <sect1 id="x86-one-pointed-mind">
- <sect1info>
- <authorgroup>
- <author>
- <firstname>Daniel</firstname>
- <surname>Seuffert</surname>
- <contrib>Übersetzt von </contrib>
- </author>
- </authorgroup>
- </sect1info>
-
- <title>One-Pointed Mind</title>
-
- <para>Als ein Zen-Schüler liebe ich die Idee eines
- fokussierten Bewußtseins: Tu nur ein Ding zur gleichen
- Zeit, aber mache es richtig.</para>
-
- <para>Das ist ziemlich genau die gleiche Idee, welche &unix;
- richtig funktionieren lässt. Während eine typische
- &windows;-Applikation versucht alles Vorstellbare zu tun (und
- daher mit Fehler durchsetzt ist), versucht eine
- &unix;-Applikation nur eine Funktion zu erfüllen und das
- gut.</para>
-
- <para>Der typische &unix;-Nutzer stellt sich sein eigenes System
- durch Shell-Skripte zusammen, die er selbst schreibt, und welche
- die Vorteile bestehender Applikationen dadurch kombinieren,
- indem sie die Ausgabe eines Programmes als Eingabe in ein
- anderes Programm durch eine Pipe übergeben.</para>
-
- <para>Wenn Sie ihre eigene &unix;-Software schreiben, ist es
- generell eine gute Idee zu betrachten, welcher Teil der
- Problemlösung durch bestehende Programme bewerkstelligt
- werden kann. Man schreibt nur die Programme selbst, für die
- keine vorhandene Lösung existiert.</para>
-
- <sect2 id="x86-csv">
- <title>CSV</title>
-
- <para>Ich will dieses Prinzip an einem besonderen Beispiel
- aus der realen Welt demonstrieren, mit dem ich kürzlich
- konfrontiert wurde:</para>
-
- <para>Ich mußte jeweils das elfte Feld von jedem
- Datensatz aus einer Datenbank extrahieren, die ich von einer
- Webseite heruntergeladen hatte. Die Datenbank war eine
- <acronym>CSV</acronym>-Datei, d.h. eine Liste von
- <emphasis>Komma-getrennten Werten</emphasis>. Dies ist ein
- ziemlich gewöhnliches Format für den Code-Austausch
- zwischen Menschen, die eine unterschiedliche
- Datenbank-Software nutzen.</para>
-
- <para>Die erste Zeile der Datei enthält eine Liste der
- Felder durch Kommata getrennt. Der Rest der Datei enthält
- die einzelnen Datensätze mit durch Kommata getrennten
- Werten in jeder Zeile.</para>
-
- <para>Ich versuchte <application>awk</application> unter
- Nutzung des Kommas als Trenner. Da aber einige Zeilen durch in
- Bindestriche gesetzte Kommata getrennt waren, extrahierte
- <application>awk</application> das falsche Feld aus diesen
- Zeilen.</para>
-
- <para>Daher mußte ich meine eigene Software schreiben,
- um das elfte Feld aus der <acronym>CSV</acronym>-Datei
- auszulesen. Aber durch Anwendung der &unix;-Philosophie
- mußte ich nur einen einfachen Filter schreiben, das
- Folgende tat:</para>
-
- <itemizedlist>
- <listitem>
- <para>Entferne die erste Zeile aus der Datei.</para>
- </listitem>
-
- <listitem>
- <para>Ändere alle Kommata ohne Anführungszeichen
- in einen anderen Buchstaben.</para>
- </listitem>
-
- <listitem>
- <para>Entferne alle Anführungszeichen.</para>
- </listitem>
- </itemizedlist>
-
- <para>Streng genommen könnte ich
- <application>sed</application> benutzen, um die erste Zeile
- der Datei zu entfernen, aber das zu Bewerkstelligen war in
- meinem Programm sehr einfach, also entschloss ich mich dazu
- und reduzierte dadurch die Größe der
- Pipeline.</para>
-
- <para>Unter Berücksichtigung aller Faktoren kostete mich
- das Schreiben dieses Programmes ca. 20 Minuten. Das Schreiben
- eines Programmes, welches jeweils das elfte Feld aus einer
- <acronym>CSV</acronym>-Datei extrahiert hätte wesentlich
- länger gedauert und ich hätte es nicht
- wiederverwenden können, um ein anderes Feld aus irgendeiner
- anderen Datenbank zu extrahieren.</para>
-
- <para>Diesmal entschied ich mich dazu, etwas mehr Arbeit zu
- investieren, als man normalerweise für ein typisches
- Tutorial verwenden würde:</para>
-
- <itemizedlist>
- <listitem>
- <para>Es parst die Kommandozeilen nach Optionen.</para>
- </listitem>
-
- <listitem>
- <para>Es zeigt die richtige Nutzung an, falls es ein
- falsches Argument findet.</para>
- </listitem>
-
- <listitem>
- <para>Es gibt vernünftige Fehlermeldungen aus.</para>
- </listitem>
- </itemizedlist>
-
- <para>Hier ist ein Beispiel für seine Nutzung:</para>
-
- <screen>Usage: csv [-t&lt;delim&gt;] [-c&lt;comma&gt;] [-p] [-o &lt;outfile&gt;] [-i &lt;infile&gt;]</screen>
-
- <para>Alle Parameter sind optional und können in beliebiger
- Reihenfolge auftauchen.</para>
-
- <para>Der <parameter>-t</parameter>-Parameter legt fest, was
- zu die Kommata zu ersetzen sind. Der <constant>tab</constant>
- ist die Vorgabe hierfür. Zum Beispiel wird
- <parameter>-t;</parameter> alle unquotierten Kommata mit
- Semikolon ersetzen.</para>
-
- <para>Ich brauche die <parameter>-c</parameter>-Option nicht,
- aber sie könnte zukünftig nützlich sein. Sie
- ermöglicht mir festzulegen, daß ich einen anderen
- Buchstaben als das Kommata mit etwas anderem ersetzen
- möchte. Zum Beispiel wird der Parameter
- <parameter>-c@</parameter> alle @-Zeichen ersetzen
- (nützlich, falls man eine Liste von Email-Adressen in
- Nutzername und Domain aufsplitten will).</para>
-
- <para>Die <parameter>-p</parameter>-Option erhält die
- erste Zeile, d.h. die erste Zeile der Datei wird nicht
- gelöscht. Als Vorgabe löschen wir die erste Zeile,
- weil die <acronym>CSV</acronym>-Datei in der ersten Zeile
- keine Daten, sondern Feldbeschreibungen enthält.</para>
-
- <para>Die Parameter <parameter>-i</parameter>- und
- <parameter>-o</parameter>-Optionen erlauben es mir, die
- Ausgabe- und Eingabedateien festzulegen. Vorgabe sind
- <filename>stdin</filename> und <filename>stdout</filename>,
- also ist es ein regulärer &unix;-Filter.</para>
-
- <para>Ich habe sichergestellt, daß sowohl <parameter>-i
- filename</parameter> und <parameter>-ifilename</parameter>
- akzeptiert werden. Genauso habe ich dafür Sorge getragen,
- daß sowohl Eingabe- als auch Ausgabedateien festgelegt
- werden können.</para>
-
- <para>Um das elfte Feld jeden Datensatzes zu erhalten kann ich
- nun folgendes eingeben:</para>
-
- <screen>&prompt.user; <userinput>csv '-t;' <replaceable>data.csv</replaceable> | awk '-F;' '{print $11}'</userinput></screen>
-
- <para>Der Code speichert die Optionen (bis auf die
- Dateideskriptoren) in <varname role="register">EDX</varname>:
- Das Kommata in <varname role="register">DH</varname>, den
- neuen Feldtrenner in <varname role="register">DL</varname> und
- das Flag für die <parameter>-p</parameter>-Option in dem
- höchsten Bit von <varname role="register">EDX</varname>.
- Ein kurzer Abgleich des Zeichens wird uns also eine schnelle
- Entscheidung darüber erlauben, was zu tun ist.</para>
-
- <para>Hier ist der Code:</para>
-
- <programlisting>
-;;;;;;; csv.asm ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;
-; Convert a comma-separated file to a something-else separated file.
-;
-; Started: 31-May-2001
-; Updated: 1-Jun-2001
-;
-; Copyright (c) 2001 G. Adam Stanislav
-; All rights reserved.
-;
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
-%include 'system.inc'
-
-%define BUFSIZE 2048
-
-section .data
-fd.in dd stdin
-fd.out dd stdout
-usg db 'Usage: csv [-t&lt;delim&gt;] [-c&lt;comma&gt;] [-p] [-o &lt;outfile&gt;] [-i &lt;infile&gt;]', 0Ah
-usglen equ $-usg
-iemsg db "csv: Can't open input file", 0Ah
-iemlen equ $-iemsg
-oemsg db "csv: Can't create output file", 0Ah
-oemlen equ $-oemsg
-
-section .bss
-ibuffer resb BUFSIZE
-obuffer resb BUFSIZE
-
-section .text
-align 4
-ierr:
- push dword iemlen
- push dword iemsg
- push dword stderr
- sys.write
- push dword 1 ; return failure
- sys.exit
-
-align 4
-oerr:
- push dword oemlen
- push dword oemsg
- push dword stderr
- sys.write
- push dword 2
- sys.exit
-
-align 4
-usage:
- push dword usglen
- push dword usg
- push dword stderr
- sys.write
- push dword 3
- sys.exit
-
-align 4
-global _start
-_start:
- add esp, byte 8 ; discard argc and argv[0]
- mov edx, (',' &lt;&lt; 8) | 9
-
-.arg:
- pop ecx
- or ecx, ecx
- je near .init ; no more arguments
-
- ; ECX contains the pointer to an argument
- cmp byte [ecx], '-'
- jne usage
-
- inc ecx
- mov ax, [ecx]
-
-.o:
- cmp al, 'o'
- jne .i
-
- ; Make sure we are not asked for the output file twice
- cmp dword [fd.out], stdout
- jne usage
-
- ; Find the path to output file - it is either at [ECX+1],
- ; i.e., -ofile --
- ; or in the next argument,
- ; i.e., -o file
-
- inc ecx
- or ah, ah
- jne .openoutput
- pop ecx
- jecxz usage
-
-.openoutput:
- push dword 420 ; file mode (644 octal)
- push dword 0200h | 0400h | 01h
- ; O_CREAT | O_TRUNC | O_WRONLY
- push ecx
- sys.open
- jc near oerr
-
- add esp, byte 12
- mov [fd.out], eax
- jmp short .arg
-
-.i:
- cmp al, 'i'
- jne .p
-
- ; Make sure we are not asked twice
- cmp dword [fd.in], stdin
- jne near usage
-
- ; Find the path to the input file
- inc ecx
- or ah, ah
- jne .openinput
- pop ecx
- or ecx, ecx
- je near usage
-
-.openinput:
- push dword 0 ; O_RDONLY
- push ecx
- sys.open
- jc near ierr ; open failed
-
- add esp, byte 8
- mov [fd.in], eax
- jmp .arg
-
-.p:
- cmp al, 'p'
- jne .t
- or ah, ah
- jne near usage
- or edx, 1 &lt;&lt; 31
- jmp .arg
-
-.t:
- cmp al, 't' ; redefine output delimiter
- jne .c
- or ah, ah
- je near usage
- mov dl, ah
- jmp .arg
-
-.c:
- cmp al, 'c'
- jne near usage
- or ah, ah
- je near usage
- mov dh, ah
- jmp .arg
-
-align 4
-.init:
- sub eax, eax
- sub ebx, ebx
- sub ecx, ecx
- mov edi, obuffer
-
- ; See if we are to preserve the first line
- or edx, edx
- js .loop
-
-.firstline:
- ; get rid of the first line
- call getchar
- cmp al, 0Ah
- jne .firstline
-
-.loop:
- ; read a byte from stdin
- call getchar
-
- ; is it a comma (or whatever the user asked for)?
- cmp al, dh
- jne .quote
-
- ; Replace the comma with a tab (or whatever the user wants)
- mov al, dl
-
-.put:
- call putchar
- jmp short .loop
-
-.quote:
- cmp al, '"'
- jne .put
-
- ; Print everything until you get another quote or EOL. If it
- ; is a quote, skip it. If it is EOL, print it.
-.qloop:
- call getchar
- cmp al, '"'
- je .loop
-
- cmp al, 0Ah
- je .put
-
- call putchar
- jmp short .qloop
-
-align 4
-getchar:
- or ebx, ebx
- jne .fetch
-
- call read
-
-.fetch:
- lodsb
- dec ebx
- ret
-
-read:
- jecxz .read
- call write
-
-.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:
- jecxz .ret ; nothing to 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:
- ret</programlisting>
-
- <para>Vieles daraus ist aus <filename>hex.asm</filename>
- entnommen worden. Aber es gibt einen wichtigen Unterschied:
- Ich rufe nicht länger <function>write</function> auf,
- wann immer ich eine Zeilenvorschub ausgebe. Nun kann der Code
- sogar interaktiv genutzt werden.</para>
-
- <para>Ich habe eine bessere Lösung gefunden für das
- Interaktivitätsproblem seit ich mit dem Schreiben dieses
- Kapitels begonnen habe. Ich wollte sichergehen, daß jede
- Zeile einzeln ausgegeben werden kann, falls erforderlich. Aber
- schlussendlich gibt es keinen Bedarf jede Zeile einzeln
- auszugeben, falls nicht-interaktiv genutzt.</para>
-
- <para>Die neue Lösung besteht darin, die Funktion
- <function>write</function> jedesmal aufzurufen, wenn ich den
- Eingabepuffer leer vorfinde. Auf diesem Wege liest das
- Programm im interaktiven Modus eine Zeile aus der Tastatur des
- Nutzers, verarbeitet sie und stellt fest, ob deren
- Eingabepuffer leer ist, dann leert es seine Ausgabe und liest
- die nächste Zeile.</para>
-
- <sect3 id="x86-buffered-dark-side">
- <title>Die dunkle Seite des Buffering</title>
-
- <para>Diese Änderung verhindert einen mysteriösen
- Aufhänger in einem speziellen Fall. Ich bezeichne dies
- als die <emphasis>dunkle Seite des Buffering</emphasis>,
- hauptsächlich, weil es eine nicht offensichtliche
- Gefahr darstellt.</para>
-
- <para>Es ist unwahrscheinlich, daß dies mit dem
- <application>csv</application>-Programm oben geschieht aber
- lassen Sie uns einen weiteren Filter betrachten: Nehmen wir
- an ihre Eingabe sind rohe Daten, die Farbwerte darstellen,
- wie z.B. die Intensität eines Pixel mit den Farben
- <emphasis>rot</emphasis>, <emphasis>grün</emphasis> und
- <emphasis>blau</emphasis>. Unsere Ausgabe wird der negative
- Wert unserer Eingabe sein.</para>
-
- <para>Solch ein Filter würde sehr einfach zu schreiben
- sein. Der größte Teil davon würde so
- aussehen wie all die anderen Filter, die wir bisher
- geschrieben haben, daher beziehe ich mich nur auf den Kern
- der Prozedur:</para>
-
- <programlisting>.loop:
- call getchar
- not al ; Create a negative
- call putchar
- jmp short .loop</programlisting>
-
- <para>Da dieser Filter mit rohen Daten arbeitet ist es
- unwahrscheinlich, daß er interaktiv genutzt werden
- wird.</para>
-
- <para>Aber das Programm könnte als
- Bildbearbeitungssoftware tituliert werden. Wenn es nicht
- <function>write</function> vor jedem Aufruf von
- <function>read</function> durchführt, ist die
- Möglichkeit gegeben, das es sich aufhängt.</para>
-
- <para>Dies könnte passieren:</para>
-
- <procedure>
- <step>
- <para>Der Bildeditor wird unseren Filter laden mittels der
- C-Funktion <function>popen()</function>.</para>
- </step>
-
- <step>
- <para>Er wird die erste Zeile von Pixeln laden aus einer
- Bitmap oder Pixmap.</para>
- </step>
-
- <step>
- <para>Er wird die erste Zeile von Pixeln geschrieben in
- die <emphasis>Pipe</emphasis>, welche zur Variable
- <varname>fd.in</varname> unseres Filters
- führt.</para>
- </step>
-
- <step>
- <para>Unser Filter wird jeden Pixel auslesen von der
- Eingabe, in in seinen negativen Wert umkehren und ihn in
- den Ausgabepuffer schreiben.</para>
- </step>
-
- <step>
- <para>Unser Filter wird die Funktion
- <function>getchar</function> aufrufen, um das
- nächste Pixel abzurufen.</para>
- </step>
-
- <step>
- <para>Die Funktion <function>getchar</function> wird einen
- leeren Eingabepuffer vorfinden und daher die Funktion
- <function>read</function> aufrufen.</para>
- </step>
-
- <step>
- <para><function>read</function> wird den Systemaufruf
- <function role="syscall">SYS_read</function>
- starten.</para>
- </step>
-
- <step>
- <para>Der <emphasis>Kernel</emphasis> wird unseren Filter
- unterbrechen, bis der Bildeditor mehr Daten zur Pipe
- sendet.</para>
- </step>
-
- <step>
- <para>Der Bildedior wird aus der anderen Pipe lesen,
- welche verbunden ist mit <varname>fd.out</varname>
- unseres Filters, damit er die erste Zeile des
- auszugebenden Bildes setzen kann
- <emphasis>bevor</emphasis> er uns die zweite Zeile der
- Eingabe einliest.</para>
- </step>
-
- <step>
- <para>Der <emphasis>Kernel</emphasis> unterbricht den
- Bildeditor, bis er eine Ausgabe unseres Filters
- erhält, um ihn an den Bildeditor
- weiterzureichen.</para>
- </step>
- </procedure>
-
- <para>An diesem Punkt wartet unser Filter auf den
- Bildeditor, daß er ihm mehr Daten zur Verarbeitung
- schicken möge. Gleichzeitig wartet der Bildeditor
- darauf, daß unser Filter das Resultat der Berechnung
- ersten Zeile sendet. Aber das Ergebnis sitzt in unserem
- Ausgabepuffer.</para>
-
- <para>Der Filter und der Bildeditor werden fortfahren bis in
- die Ewigkeit aufeinander zu warten (oder zumindest bis sie
- per kill entsorgt werden). Unsere Software hat den eine
- <link linkend="secure-race-conditions">Race Condition</link>
- erreicht.</para>
-
- <para>Das Problem tritt nicht auf, wenn unser Filter seinen
- Ausgabepuffer leert <emphasis>bevor</emphasis> er vom
- <emphasis>Kernel</emphasis> mehr Eingabedaten
- anfordert.</para>
- </sect3>
- </sect2>
- </sect1>
-
- <sect1 id="x86-fpu">
- <sect1info>
- <authorgroup>
- <author>
- <firstname>Fabian</firstname>
- <surname>Borschel</surname>
- <contrib>Übersetzt von </contrib>
- </author>
- </authorgroup>
- </sect1info>
-
- <title>Die <acronym>FPU</acronym> verwenden</title>
-
- <para>Seltsamerweise erwähnt die meiste Literatur zu
- Assemblersprachen nicht einmal die Existenz der
- <acronym>FPU</acronym>, oder
- <emphasis>floating point unit</emphasis>
- (Fließkomma-Recheneinheit), geschweige denn, daß
- auf die Programmierung mit dieser eingegangen wird.</para>
-
- <para>Dabei kann die Assemblerprogrammierung gerade bei
- hoch optimiertem <acronym>FPU</acronym>-Code, der
- <emphasis>nur</emphasis> mit einer Assemblersprache realisiert
- werden kann, ihre große Stärke ausspielen.</para>
-
- <sect2 id="x86-fpu-organization">
- <title>Organisation der <acronym>FPU</acronym></title>
-
- <para>Die <acronym>FPU</acronym> besteht aus 8 80&ndash;bit
- Fließkomma-Registern. Diese sind in Form eines
- Stacks organisiert&mdash;Sie können einen Wert durch
- den Befehl <function>push</function> auf dem
- <acronym>TOS</acronym> (<emphasis>top of stack</emphasis>)
- ablegen, oder durch <function>pop</function> von diesem
- holen.</para>
-
- <para>Da also die Befehle <function role="opcode">push</function>
- und <function role="opcode">pop</function> schon verwendet
- werden, kann es keine op-Codes in Assemblersprache mit diesen
- Namen geben.</para>
-
- <para>Sie können mit einen Wert auf dem
- <acronym>TOS</acronym> ablegen, indem Sie
- <function role="opcode">fld</function>, <function
- role="opcode">fild</function>, und <function
- role="opcode">fbld</function> verwenden. Mit weiteren op-Codes
- lassen sich <emphasis>Konstanten</emphasis>&mdash;wie z.B.
- <emphasis>Pi</emphasis>&mdash;auf dem <acronym>TOS</acronym>
- ablegen.</para>
-
- <para>Analog dazu können Sie einen Wert
- holen, indem Sie <function role="opcode">fst</function>,
- <function role="opcode">fstp</function>, <function
- role="opcode">fist</function>, <function
- role="opcode">fistp</function>, und <function
- role="opcode">fbstp</function> verwenden. Eigentlich
- holen (<function>pop</function>) nur die op-Codes, die auf
- <emphasis>p</emphasis> enden, einen Wert, während die
- anderen den Wert irgendwo speichern (<function>store</function>)
- ohne ihn vom <acronym>TOS</acronym> zu entfernen.</para>
-
- <para>Daten können zwischen dem <acronym>TOS</acronym>
- und dem Hauptspeicher als 32&ndash;bit, 64&ndash;bit oder
- 80&ndash;bit <emphasis>real</emphasis>, oder als 16&ndash;bit,
- 32&ndash;bit oder 64&ndash;bit <emphasis>Integer</emphasis>,
- oder als 80&ndash;bit <emphasis>packed decimal</emphasis>
- übertragen werden.</para>
-
- <para>Das 80&ndash;bit <emphasis>packed decimal</emphasis>-Format
- ist ein Spezialfall des
- <emphasis>binary coded decimal</emphasis>-Formates,
- welches üblicherweise bei der Konvertierung zwischen der
- <acronym>ASCII</acronym>- und
- <acronym>FPU</acronym>-Darstellung von Daten verwendet wird.
- Dieses erlaubt die Verwendung von 18 signifikanten
- Stellen.</para>
-
- <para>Unabhängig davon, wie Daten im Speicher dargestellt
- werden, speichert die <acronym>FPU</acronym> ihre Daten immer
- im 80&ndash;bit <emphasis>real</emphasis>-Format in den
- Registern.</para>
-
- <para>Ihre interne Genauigkeit beträgt mindestens 19
- Dezimalstellen. Selbst wenn wir also Ergebnisse im
- <acronym>ASCII</acronym>-Format mit voller 18&ndash;stelliger
- Genauigkeit darstellen lassen, werden immer noch korrekte
- Werte angezeigt.</para>
-
- <para>Des weiteren können mathematische Operationen auf
- dem <acronym>TOS</acronym> ausgeführt werden: Wir
- können dessen <emphasis>Sinus</emphasis> berechnen, wir
- können ihn <emphasis>skalieren</emphasis> (z.B.
- können wir ihn mit dem Faktor 2 Multiplizieren oder
- Dividieren), wir können dessen
- <emphasis>Logarithmus</emphasis> zur Basis 2 nehmen, und viele
- weitere Dinge.</para>
-
- <para>Wir können auch <acronym>FPU</acronym>-Register
- <emphasis>multiplizieren</emphasis>,
- <emphasis>dividieren</emphasis>, <emphasis>addieren</emphasis>
- und <emphasis>subtrahieren</emphasis>, sogar einzelne
- Register mit sich selbst.</para>
-
- <para>Der offizielle Intel op-Code für den
- <acronym>TOS</acronym> ist <varname
- role="register">st</varname> und für die
- <emphasis>Register</emphasis> <varname
- role="register">st(0)</varname>&ndash; <varname
- role="register">st(7)</varname>. <varname
- role="register">st</varname> und <varname
- role="register">st(0)</varname> beziehen sich dabei auf das
- gleiche Register.</para>
-
- <para>Aus welchen Gründen auch immer hat sich der
- Originalautor von <application>nasm</application> dafür
- entschieden, andere op-Codes zu verwenden, nämlich
- <varname role="register">st0</varname>&ndash; <varname
- role="register">st7</varname>. Mit anderen Worten, es gibt
- keine Klammern, und der <acronym>TOS</acronym> ist immer
- <varname role="register">st0</varname>, niemals einfach nur
- <function role="opcode">st</function>.</para>
-
- <sect3 id="x86-fpu-packed-decimal">
- <title>Das Packed Decimal-Format</title>
-
- <para>Das <emphasis>packed decimal</emphasis>-Format verwendet
- 10 Bytes (80 Bits) zur Darstellung von 18 Ziffern. Die so
- dargestellte Zahl ist immer ein
- <emphasis>Integer</emphasis>.</para>
-
- <tip>
- <para>Sie können durch Multiplikation des
- <acronym>TOS</acronym> mit Potenzen von 10 die einzelnen
- Dezimalstellen verschieben.</para>
- </tip>
-
- <para>Das höchste Bit des höchsten Bytes (Byte 9)
- ist das <emphasis>Vorzeichenbit</emphasis>:
- Wenn es gesetzt ist, ist die Zahl
- <emphasis>negativ</emphasis>, ansonsten
- <emphasis>positiv</emphasis>. Die restlichen Bits dieses
- Bytes werden nicht verwendet bzw. ignoriert.</para>
-
- <para>Die restlichen 9 Bytes enthalten die 18 Ziffern der
- gespeicherten Zahl: 2 Ziffern pro Byte.</para>
-
- <para>Die <emphasis>signifikantere Ziffer</emphasis> wird in
- der <emphasis>oberen Hälfte</emphasis> (4 Bits) eines
- Bytes gespeichert, die andere in der
- <emphasis>unteren Hälfte</emphasis>.</para>
-
- <para>Vielleicht würden Sie jetzt annehmen, das
- <constant>-1234567</constant> auf die folgende Art im
- Speicher abgelegt wird (in hexadezimaler Notation):</para>
-
- <programlisting>80 00 00 00 00 00 01 23 45 67</programlisting>
-
- <para>Dem ist aber nicht so! Bei Intel werden alle Daten im
- <emphasis>little&ndash;endian</emphasis>-Format gespeichert,
- auch das <emphasis>packed decimal</emphasis>-Format.</para>
-
- <para>Dies bedeutet, daß <constant>-1234567</constant>
- wie folgt gespeichert wird:</para>
-
- <programlisting>67 45 23 01 00 00 00 00 00 80</programlisting>
-
- <para>Erinnern Sie sich an diesen Umstand, bevor Sie sich
- aus lauter Verzweiflung die Haare
- ausreißen.</para>
-
- <note>
- <para>Das lesenswerte Buch&mdash;falls Sie es finden
- können&mdash;ist Richard Startz' <ulink
- url="http://www.int80h.org/cgi-bin/isbn?isbn=013246604X">
- 8087/80287/80387 for the IBM PC &amp; Compatibles</ulink>.
- Obwohl es anscheinend die Speicherung der <emphasis>packed
- decimal</emphasis> im little&ndash;endian-Format für
- gegeben annimmt. Ich mache keine Witze über meine
- Verzweiflung, als ich den Fehler im unten stehenden Filter
- gesucht habe, <emphasis>bevor</emphasis> mir einfiel,
- daß ich einfach mal versuchen sollte, das
- little&ndash;endian-Format, selbst für diesen Typ von
- Daten, anzuwenden.</para>
- </note>
- </sect3>
- </sect2>
-
- <sect2 id="x86-pinhole-photography">
- <title>Ausflug in die Lochblendenphotographie</title>
-
- <para>Um sinnvolle Programme zu schreiben, müssen wir nicht
- nur unsere Programmierwerkzeuge beherrschen, sondern auch das
- Umfeld, für das die Programme gedacht sind.</para>
-
- <para>Unser nächster Filter wird uns dabei helfen, wann
- immer wir wollen, eine <emphasis>Lochkamera</emphasis> zu
- bauen. Wir brauchen also etwas Hintergrundwissen über
- die <emphasis>Lochblendenphotographie</emphasis>, bevor wir
- weiter machen können.</para>
-
- <sect3 id="x86-camera">
- <title>Die Kamera</title>
-
- <para>Die einfachste Form, eine Kamera zu beschreiben, ist
- die eines abgeschlossenen, lichtundurchlässigen
- Raumes, in dessen Abdeckung sich ein kleines Loch
- befindet.</para>
-
- <para>Die Abdeckung ist normalerweise fest (z.B. eine
- Schachtel), manchmal jedoch auch flexibel (z.B. ein Balgen).
- Innerhalb der Kamera ist es sehr dunkel. Nur durch ein
- kleines Loch kann Licht von einem einzigen Punkt aus in den
- Raum eindringen (in manchen Fällen sind es mehrere
- Löcher). Diese Lichtstrahlen kommen von einem Bild,
- einer Darstellung von dem was sich außerhalb der
- Kamera, vor dem kleinen Loch, befindet.</para>
-
- <para>Wenn ein lichtempfindliches Material (wie z.B. ein Film)
- in der Kamera angebracht wird, so kann dieses das Bild
- einfangen.</para>
-
- <para>Das Loch enthält häufig eine
- <emphasis>Linse</emphasis>, oder etwas linsenartiges,
- häufig auch einfach <emphasis>Objektiv</emphasis>
- genannt.</para>
- </sect3>
-
- <sect3 id="x86-the-pinhole">
- <title>Die Lochblende</title>
-
- <para>Streng genommen ist die Linse nicht notwendig: Die
- ursprünglichen Kameras verwendeten keine Linse, sondern
- eine <emphasis>Lochblende</emphasis>. Selbst heutzutage
- werden noch <emphasis>Lochblenden</emphasis> verwendet,
- zum einen, um die Funktionsweise einer Kamera zu erlernen,
- und zum anderen, um eine spezielle Art von Bildern zu
- erzeugen.</para>
-
- <para>Das Bild, das von einer <emphasis>Lochblende</emphasis>
- erzeugt wird, ist überall scharf. Oder unscharf. Es
- gibt eine ideale Größe für eine Lochblende:
- Wenn sie größer oder kleiner ist, verliert das
- Bild seine Schärfe.</para>
- </sect3>
-
- <sect3 id="x86-focal-length">
- <title>Brennweite</title>
-
- <para>Dieser ideale Lochblendendurchmesser ist eine Funktion
- der Quadratwurzel der <emphasis>Brennweite</emphasis>,
- welche dem Abstand der Lochblende von dem Film
- entspricht.</para>
-
- <programlisting> D = PC * sqrt(FL)</programlisting>
-
- <para>Hier ist <varname>D</varname> der ideale Durchmesser der
- Lochblende, <varname>FL</varname> die Brennweite und
- <constant>PC</constant> eine Konstante der Brennweite. Nach
- Jay Bender hat die Konstante den Wert
- <constant>0.04</constant>, nach Kenneth Connors
- <constant>0.037</constant>. Andere Leute
- haben andere Werte vorgeschlagen. Des weiteren gelten diese
- Werte nur für Tageslicht: Andere Arten von
- Licht benötigen andere konstante Werte, welche nur
- durch Experimente bestimmt werden können.</para>
- </sect3>
-
- <sect3 id="x86-f-number">
- <title>Der f&ndash;Wert</title>
-
- <para>Der f&ndash;Wert ist eine sehr nützliche
- Größe, die angibt, wieviel Licht den Film erreicht.
- Ein Belichtungsmesser kann dies messen, um z.B. für
- einen Film mit einer Empfindlichkeit von f5.6 eine
- Belichtungsdauer von 1/1000 Sekunden auszurechnen.</para>
-
- <para>Es spielt keine Rolle, ob es eine 35&ndash;mm- oder
- eine 6x9cm-Kamera ist, usw. Solange wir den f&ndash;Wert
- kennen, können wir die benötigte Belichtungszeit
- berechnen.</para>
-
- <para>Der f&ndash;Wert läßt sich einfach wie folgt
- berechnen:</para>
-
- <programlisting> F = FL / D</programlisting>
-
- <para>Mit anderen Worten, der f&ndash;Wert ergibt sich aus
- der Brennweite (FL), dividiert durch den Durchmesser (D) der
- Lochblende. Ein großer f&ndash;Wert impliziert also
- entweder eine kleine Lochblende, oder eine große
- Brennweite, oder beides. Je größer also der
- f&ndash;Wert ist, um so länger muß die
- Belichtungszeit sein.</para>
-
- <para>Des weiteren sind der Lochblendendurchmesser und die
- Brennweite eindimensionale Meßgrößen,
- während der Film und die Lochblende an sich
- zweidimensionale Objekte darstellen. Das bedeutet, wenn
- man für einen f&ndash;Wert <varname>A</varname>
- eine Belichtungsdauer <varname>t</varname> bestimmt hat,
- dann ergibt sich daraus für einen f&ndash;Wert
- <varname>B</varname> eine Belichtungszeit von:</para>
-
- <programlisting> t * (B / A)&#178;</programlisting>
- </sect3>
-
- <sect3 id="x86-normalized-f-number">
- <title>Normalisierte f&ndash;Werte</title>
-
- <para>Während heutige moderne Kameras den Durchmesser
- der Lochblende, und damit deren f&ndash;Wert, weich und
- schrittweise verändern können, war dies
- früher nicht der Fall.</para>
-
- <para>Um unterschiedliche f&ndash;Werte einstellen zu
- können, besaßen Kameras typischerweise eine
- Metallplatte mit Löchern unterschiedlichen
- Durchmessers als Lochblende.</para>
-
- <para>Die Durchmesser wurden entsprechend obiger Formel
- gewählt, daß der resultierende f&ndash;Wert
- ein fester Standardwert war, der für alle Kameras
- verwendet wurde. Z.B. hat eine sehr alte Kodak Duaflex
- IV Kamera in meinem Besitz drei solche Löcher
- für die f&ndash;Werte 8, 11 und 16.</para>
-
- <para>Eine neuere Kamera könnte f&ndash;Werte wie 2.8,
- 4, 5.6, 8, 11, 16, 22, und 32 (und weitere) besitzen.
- Diese Werte wurden nicht zufällig ausgewählt:
- Sie sind alle vielfache der Quadratwurzel aus 2, wobei
- manche Werte gerundet wurden.</para>
- </sect3>
-
- <sect3 id="x86-f-stop">
- <title>Der f&ndash;Stopp</title>
-
- <para>Eine typische Kamera ist so konzipiert, daß die
- Nummernscheibe bei den normalisierten f&ndash;Werten
- einrastet. Die Nummernscheibe <emphasis>stoppt</emphasis>
- an diesen Positionen. Daher werden diese Positionen auch
- f&ndash;Stopps genannt.</para>
-
- <para>Da die f&ndash;Werte bei jedem Stopp vielfache der
- Quadratwurzel aus 2 sind, verdoppelt die Drehung der
- Nummernscheibe um einen Stopp die für die gleiche
- Belichtung benötigte Lichtmenge. Eine Drehung
- um 2 Stopps vervierfacht die benötigte Belichtungszeit.
- Eine Drehung um 3 Stopps verachtfacht sie, etc.</para>
- </sect3>
- </sect2>
-
- <sect2 id="x86-pinhole-software">
- <title>Entwurf der Lochblenden-Software</title>
-
- <para>Wir können jetzt festlegen, was genau unsere
- Lochblenden-Software tun soll.</para>
-
- <sect3 id="xpinhole-processing-input">
- <title>Verarbeitung der Programmeingaben</title>
-
- <para>Da der Hauptzweck des Programms darin besteht, uns
- beim Entwurf einer funktionierenden Lochkamera zu
- helfen, wird die <emphasis>Brennweite</emphasis>
- die Programmeingabe sein. Dies ist etwas, das wir ohne
- zusätzliche Programme feststellen können:
- Die geeignete Brennweite ergibt sich aus der
- Größe des Films und der Art des Fotos, ob
- dieses ein "normales" Bild, ein Weitwinkelbild oder
- ein Telebild sein soll.</para>
-
- <para>Die meisten bisher geschriebenen Programme arbeiteten
- mit einzelnen Zeichen, oder Bytes, als Eingabe: Das
- <application>hex</application>-Programm konvertierte
- einzelne Bytes in hexadezimale Werte, das
- <application>csv</application>-Programm ließ entweder
- einzelne Zeichen unverändert, löschte oder
- veränderte sie, etc.</para>
-
- <para>Das Programm <application>ftuc</application> verwendete
- einen Zustandsautomaten, um höchstens zwei gleichzeitig
- eingegebene Bytes zu verarbeiten.</para>
-
- <para>Das <application>pinhole</application>-Programm dagegen
- kann nicht nur mit einzelnen Zeichen arbeiten, sondern
- muß mit größeren syntaktischen Einheiten
- zurrecht kommen.</para>
-
- <para>Wenn wir z.B. möchten, daß unser Programm den
- Lochblendendurchmesser (und weitere Werte, die wir
- später noch diskutieren werden) für die Brennweiten
- <constant>100 mm</constant>, <constant>150 mm</constant> und
- <constant>210 mm</constant> berechnet, wollen wir etwa
- folgendes eingeben:</para>
-
- <screen><userinput>100, 150, 210</userinput></screen>
-
- <para>Unser Programm muß mit der gleichzeitigen Eingabe
- von mehr als nur einem einzelnen Byte zurecht kommen. Wenn
- es eine <constant>1</constant> erkennt, muß es wissen,
- daß dies die erste Stelle einer dezimalen Zahl ist.
- Wenn es eine <constant>0</constant>, gefolgt von einer
- weiteren <constant>0</constant> sieht, muß es wissen,
- daß dies zwei unterschiedliche Stellen mit der
- gleichen Zahl sind.</para>
-
- <para>Wenn es auf das erste Komma trifft, muß es wissen,
- daß die folgenden Stellen nicht mehr zur ersten
- Zahl gehören. Es muß die Stellen der ersten
- Zahl in den Wert <constant>100</constant> konvertieren
- können. Und die Stellen der zweiten Zahl müssen
- in den Wert <constant>150</constant> konvertiert werden.
- Und die Stellen der dritten Zahl müssen in den Wert
- <constant>210</constant> konvertiert werden.</para>
-
- <para>Wir müssen festlegen, welche Trennsymbole
- zulässig sind: Sollen die Eingabewerte durch Kommas
- voneinander getrennt werden? Wenn ja, wie sollen zwei
- Zahlen behandelt werden, die durch ein anderes Zeichen
- getrennt sind?</para>
-
- <para>Ich persönlich mag es einfach. Entweder etwas ist
- eine Zahl, dann wird es verarbeitet, oder es ist keine
- Zahl, dann wird es verworfen. Ich mag es nicht, wenn sich der
- Computer bei der <emphasis>offensichtlichen</emphasis>
- Eingabe eines zusätzlichen Zeichens beschwert.
- Duh!</para>
-
- <para>Zusätzlich erlaubt es mir, die Monotonie des
- Tippens zu durchbrechen, und eine Anfrage anstelle einer
- simplen Zahl zu stellen:</para>
-
- <screen><userinput>Was ist der beste Lochblendendurchmesser
- bei einer Brennweite von 150?</userinput></screen>
-
- <para>Es gibt keinen Grund dafür, die Ausgabe mehrerer
- Fehlermeldungen aufzuteilen:</para>
-
- <screen>Syntax error: Was
-Syntax error: ist
-Syntax error: der
-Syntax error: beste</screen>
-
- <para>Et cetera, et cetera, et cetera.</para>
-
- <para>Zweitens mag ich das <constant>#</constant>-Zeichen, um
- Kommentare zu markieren, die ab dem Zeichen bis zum Ende der
- jeweiligen Zeile gehen. Dies verlangt nicht viel
- Programmieraufwand, und ermöglicht es mir, Eingabedateien
- für meine Programme als ausführbare Skripte zu
- handhaben.</para>
-
- <para>In unserem Fall müssen wir auch entscheiden, in
- welchen Einheiten die Dateneingabe erfolgen soll: Wir
- wählen <emphasis>Millimeter</emphasis>, da die meisten
- Photographen die Brennweite in dieser Einheit messen.</para>
-
- <para>Letztendlich müssen wir noch entscheiden, ob wir die
- Verwendung des dezimalen Punktes erlauben (in diesem Fall
- müssen wir berücksichtigen, daß in vielen
- Ländern der Welt das dezimale <emphasis>Komma</emphasis>
- verwendet wird).</para>
-
- <para>In unserem Fall würde das Zulassen eines dezimalen
- Punktes/Kommas zu einer fälschlicherweise angenommenen,
- höheren Genauigkeit führen: Der Unterschied
- zwischen den Brennweiten <constant>50</constant> und
- <constant>51</constant> ist fast nicht wahrnehmbar. Die
- Zulassung von Eingaben wie <constant>50.5</constant> ist
- also keine gute Idee. Beachten Sie bitte, das dies meine
- Meinung ist. In diesem Fall bin ich der Autor des Programmes.
- Bei Ihren eigenen Programmen müssen Sie selbst solche
- Entscheidungen treffen.</para>
- </sect3>
-
- <sect3 id="x86-pinhole-options">
- <title>Optionen anbieten</title>
-
- <para>Das wichtigste, was wir zum Bau einer Lochkamera
- wissen müssen, ist der Durchmesser der Lochblende. Da
- wir scharfe Bilder schießen wollen, werden wir obige
- Formel für die Berechnung des korrekten Durchmessers zu
- gegebener Brennweite verwenden. Da Experten mehrere
- Werte für die <constant>PC</constant>-Konstante
- anbieten, müssen wir uns hier für einen Wert
- entscheiden.</para>
-
- <para>In der Programmierung unter &unix; ist es üblich,
- zwei Hauptvarianten anzubieten, um Parameter an Programme zu
- übergeben, und des weiteren eine Standardeinstellung
- für den Fall zu haben, das der Benutzer gar keine
- Parameter angibt.</para>
-
- <para>Warum zwei Varianten, Parameter anzugeben?</para>
-
- <para>Ein Grund ist, eine (relativ) <emphasis>feste</emphasis>
- Einstellung anzubieten, die automatisch bei jedem
- Programmaufruf verwendet wird, ohne das wir diese
- Einstellung immer und immer wieder mit angeben
- müssen.</para>
-
- <para>Die feste Einstellung kann in einer Konfigurationsdatei
- gespeichert sein, typischerweise im Heimatverzeichnis des
- Benutzers. Die Datei hat üblicherweise denselben Namen
- wie das zugehörige Programm, beginnt jedoch mit einem
- Punkt. Häufig wird <emphasis>"rc"</emphasis> dem
- Dateinamen hinzugefügt. Unsere Konfigurationsdatei
- könnte also <filename>~/.pinhole</filename> oder
- <filename>~/.pinholerc</filename> heißen. (Die
- Zeichenfolge <filename>~/</filename> steht für das
- Heimatverzeichnis des aktuellen Benutzers.)</para>
-
- <para>Konfigurationsdateien werden häufig von Programmen
- verwendet, die viele konfigurierbare Parameter besitzen.
- Programme, die nur eine (oder wenige) Parameter anbieten,
- verwenden häufig eine andere Methode: Sie erwarten die
- Parameter in einer <emphasis>Umgebungsvariablen</emphasis>.
- In unserem Fall könnten wir eine Umgebungsvariable mit
- dem Namen <varname>PINHOLE</varname> benutzen.</para>
-
- <para>Normalerweise verwendet ein Programm entweder die eine,
- oder die andere der beiden obigen Methoden. Ansonsten
- könnte ein Programm verwirrt werden, wenn eine
- Konfigurationsdatei das eine sagt, die Umgebungsvariable
- jedoch etwas anderes.</para>
-
- <para>Da wir nur <emphasis>einen</emphasis> Parameter
- unterstützen müssen, verwenden wir die zweite
- Methode, und benutzen eine Umgebungsvariable mit dem
- Namen <varname>PINHOLE</varname>.</para>
-
- <para>Der andere Weg erlaubt uns, <emphasis>ad hoc</emphasis>
- Entscheidungen zu treffen: <emphasis>"Obwohl ich
- normalerweise einen Wert von 0.039 verwende, will ich dieses
- eine Mal einen Wert von 0.03872 anwenden."</emphasis>
- Mit anderen Worten, dies erlaubt uns, die Standardeinstellung
- außer Kraft zu setzen.</para>
-
- <para>Diese Art der Auswahl wird häufig über
- Kommandozeilenparameter gemacht.</para>
-
- <para>Schließlich braucht ein Programm
- <emphasis>immer</emphasis> eine
- <emphasis>Standardeinstellung</emphasis>. Der Benutzer
- könnte keine Parameter angeben. Vielleicht weiß
- er auch gar nicht, was er einstellen sollte. Vielleicht will
- er es "einfach nur ausprobieren". Vorzugsweise wird die
- Standardeinstellung eine sein, die die meisten Benutzer
- sowieso wählen würden. Somit müssen diese
- keine zusätzlichen Parameter angeben, bzw. können
- die Standardeinstellung ohne zusätzlichen Aufwand
- benutzen.</para>
-
- <para>Bei diesem System könnte das Programm
- widersprüchliche Optionen vorfinden, und auf die
- folgende Weise reagieren:</para>
-
- <procedure>
- <step>
- <para>Wenn es eine <emphasis>ad
- hoc</emphasis>-Einstellung vorfindet (z.B. ein
- Kommandozeilenparameter), dann sollte es diese
- Einstellung annehmen. Es muß alle vorher
- festgelegten sowie die standardmäßige
- Einstellung ignorieren.</para>
- </step>
-
- <step>
- <para><emphasis>Andererseits</emphasis>, wenn es eine
- festgelegte Option (z.B. eine Umgebungsvariable)
- vorfindet, dann sollte es diese akzeptieren und die
- Standardeinstellung ignorieren.</para>
- </step>
-
- <step>
- <para><emphasis>Ansonsten</emphasis> sollte es die
- Standardeinstellung verwenden.</para>
- </step>
- </procedure>
-
- <para>Wir müssen auch entscheiden, welches
- <emphasis>Format</emphasis> unsere
- <constant>PC</constant>-Option haben soll.</para>
-
- <para>Auf den ersten Blick scheint es einleuchtend, das
- Format <varname>PINHOLE=0.04</varname> für die
- Umgebungsvariable, und <parameter>-p0.04</parameter>
- für die Kommandozeile zu verwenden.</para>
-
- <para>Dies zuzulassen wäre eigentlich eine
- Sicherheitslücke. Die <constant>PC</constant>-Konstante
- ist eine sehr kleine Zahl. Daher würden wir unsere
- Anwendung mit verschiedenen, kleinen Werten für
- <constant>PC</constant> testen. Aber was würde
- passieren, wenn jemand das Programm mit einem sehr
- großen Wert aufrufen würde?</para>
-
- <para>Es könnte abstürzen, weil wir das Programm
- nicht für den Umgang mit großen Werten
- entworfen haben.</para>
-
- <para>Oder wir investieren noch weiter Zeit in das Programm,
- so daß dieses dann auch mit großen Zahlen
- umgehen kann. Wir könnten dies machen, wenn wir
- kommerzielle Software für computertechnisch
- unerfahrene Benutzer schreiben würden.</para>
-
- <para>Oder wir könnten auch sagen <emphasis>"Pech gehabt!
- Der Benutzer sollte es besser wissen."</emphasis></para>
-
- <para>Oder wir könnten es für den Benutzer
- unmöglich machen, große Zahlen einzugeben. Dies
- ist die Variante, die wir verwenden werden: Wir nehmen einen
- <emphasis>impliziten 0.</emphasis>-Präfix an.</para>
-
- <para>Mit anderen Worten, wenn der Benutzer den Wert
- <constant>0.04</constant> angeben will, so muß er
- entweder <parameter>-p04</parameter> als Parameter angeben,
- oder <varname>PINHOLE=04</varname> als Variable in seiner
- Umgebung definieren. Falls der Benutzer
- <parameter>-p9999999</parameter> angibt, so wird dies als
- <constant>0.9999999</constant> interpretiert&mdash;zwar
- immer noch sinnlos, aber zumindest sicher.</para>
-
- <para>Zweitens werden viele Benutzer einfach die Konstanten
- von Bender oder Connors benutzen wollen. Um es diesen
- Benutzern einfacher zu machen, werden wir
- <parameter>-b</parameter> als <parameter>-p04</parameter>,
- und <parameter>-c</parameter> als
- <parameter>-p037</parameter> interpretieren.</para>
- </sect3>
-
- <sect3 id="x86-pinhole-output">
- <title>Die Ausgabe</title>
-
- <para>Wir müssen festlegen, was und in welchem Format
- unsere Anwendung Daten ausgeben soll.</para>
-
- <para>Da wir als Eingabe beliebig viele Brennweiten
- erlauben, macht es Sinn, die Ergebnisse in Form
- einer traditionellen Datenbank&ndash;Ausgabe darzustellen,
- bei der zeilenweise zu jeder Brennweite der
- zugehörige berechnete Wert, getrennt durch ein
- <constant>tab</constant>-Zeichen, ausgegeben wird.</para>
-
- <para>Optional sollten wir dem Benutzer die Möglichkeit
- geben, die Ausgabe in dem schon beschriebenen
- <acronym>CSV</acronym>-Format festzulegen. In diesem Fall
- werden wir zu Beginn der Ausgabe eine Zeile einfügen,
- in der die Beschreibungen der einzelnen Felder, durch
- Kommas getrennt, aufgelistet werden, gefolgt von der Ausgabe
- der Daten wie schon beschrieben, wobei das
- <constant>tab</constant>-Zeichen durch ein
- <constant>Komma</constant> ersetzt wird.</para>
-
- <para>Wir brauchen eine Kommandozeilenoption für das
- <acronym>CSV</acronym>-Format. Wir können nicht
- <parameter>-c</parameter> verwenden, da diese Option bereits
- für <emphasis>verwende Connors Konstante</emphasis>
- steht. Aus irgendeinem seltsamen Grund bezeichnen
- viele Webseiten <acronym>CSV</acronym>-Dateien als
- <emphasis>"Excel Kalkulationstabelle"</emphasis> (obwohl das
- <acronym>CSV</acronym>-Format älter ist als Excel). Wir
- werden daher <parameter>-e</parameter> als Schalter für
- die Ausgabe im <acronym>CSV</acronym>-Format
- verwenden.</para>
-
- <para>Jede Zeile der Ausgabe wird mit einer Brennweite
- beginnen. Dies mag auf den ersten Blick überflüssig
- erscheinen, besonders im interaktiven Modus: Der Benutzer
- gibt einen Wert für die Brennweite ein, und das Programm
- wiederholt diesen.</para>
-
- <para>Der Benutzer kann jedoch auch mehrere Brennweiten
- in einer Zeile angeben. Die Eingabe kann auch aus einer Datei,
- oder aus der Ausgabe eines anderen Programmes, kommen. In
- diesen Fällen sieht der Benutzer die Eingabewerte
- überhaupt nicht.</para>
-
- <para>Ebenso kann die Ausgabe in eine Datei umgelenkt werden,
- was wir später noch untersuchen werden, oder sie
- könnte an einen Drucker geschickt werden, oder auch
- als Eingabe für ein weiteres Programm dienen.</para>
-
- <para>Es macht also wohl Sinn, jede Zeile mit einer durch den
- Benutzer eingegebenen Brennweite beginnen zu lassen.</para>
-
- <para>Halt! Nicht, wie der Benutzer die Daten eingegeben hat.
- Was passiert, wenn der Benutzer etwas wie folgt
- eingibt:</para>
-
- <screen><userinput>00000000150</userinput></screen>
-
- <para>Offensichtlich müssen wir die führenden Nullen
- vorher abschneiden.</para>
-
- <para>Wir müssen also die Eingabe des Benutzers
- sorgfältig prüfen, diese dann in der
- <acronym>FPU</acronym> in die binäre Form konvertieren,
- und dann von dort aus ausgeben.</para>
-
- <para>Aber...</para>
-
- <para>Was ist, wenn der Benutzer etwas wie folgt eingibt:</para>
-
- <screen><userinput>17459765723452353453534535353530530534563507309676764423</userinput></screen>
-
- <para>Ha! Das packed decimal-Format der <acronym>FPU</acronym>
- erlaubt uns die Eingabe einer 18&ndash;stelligen
- Zahl. Aber der Benutzer hat mehr als 18 Stellen eingegeben.
- Wie gehen wir damit um?</para>
-
- <para>Wir <emphasis>könnten</emphasis> unser Programm
- so modifizieren, daß es die ersten 18 Stellen liest,
- der <acronym>FPU</acronym> übergibt, dann weitere
- 18 Stellen liest, den Inhalt des <acronym>TOS</acronym>
- mit einem Vielfachen von 10, entsprechend der Anzahl der
- zusätzlichen Stellen multipliziert, und dann beide
- Werte mittels <function>add</function> zusammen
- addiert.</para>
-
- <para>Ja, wir könnten das machen. Aber in
- <emphasis>diesem</emphasis> Programm wäre es
- unnötig (in einem anderen wäre es vielleicht
- der richtige Weg): Selbst der Erdumfang in Millimetern ergibt
- nur eine Zahl mit 11 Stellen. Offensichtlich können wir
- keine Kamera dieser Größe bauen (jedenfalls jetzt
- noch nicht).</para>
-
- <para>Wenn der Benutzer also eine so große Zahl eingibt,
- ist er entweder gelangweilt, oder er testet uns, oder er
- versucht, in das System einzudringen, oder er spielt&mdash;
- indem er irgendetwas anderes macht als eine Lochkamera
- zu entwerfen.</para>
-
- <para>Was werden wir tun?</para>
-
- <para>Wir werden ihn ohrfeigen, gewissermaßen:</para>
-
- <screen>17459765723452353453534535353530530534563507309676764423 ??? ??? ??? ??? ???</screen>
-
- <para>Um dies zu erreichen, werden wir einfach alle
- führenden Nullen ignorieren. Sobald wir eine Ziffer
- gefunden haben, die nicht Null ist, initialisieren wir
- einen Zähler mit <constant>0</constant> und
- beginnen mit drei Schritten:</para>
-
- <procedure>
- <step>
- <para>Sende die Ziffer an die Ausgabe.</para>
- </step>
-
- <step>
- <para>Füge die Ziffer einem Puffer hinzu, welchen wir
- später benutzen werden, um den packed decimal-Wert
- zu erzeugen, den wir an die
- <acronym>FPU</acronym> schicken können.</para>
- </step>
-
- <step>
- <para>Erhöhe den Zähler um eins.</para>
- </step>
- </procedure>
-
- <para>Während wir diese drei Schritte wiederholen,
- müssen wir auf zwei Bedingungen achten:</para>
-
- <itemizedlist>
- <listitem>
- <para>Wenn der Zähler den Wert 18 übersteigt,
- hören wir auf, Ziffern dem Puffer hinzuzufügen.
- Wir lesen weiterhin Ziffern und senden sie an die
- Ausgabe.</para>
- </listitem>
-
- <listitem>
- <para>Wenn, bzw. <emphasis>falls</emphasis>, das
- nächste Eingabezeichen keine Zahl ist, sind wir mit
- der Bearbeitung der Eingabe erst einmal fertig.</para>
-
- <para>Übrigends können wir einfach Zeichen, die
- keine Ziffern sind, verwerfen, solange sie kein
- <constant>#</constant>-Zeichen sind, welches wir an den
- Eingabestrom zurückgeben müssen. Dieses Zeichen
- markiert den Beginn eines Kommentars. An dieser Stelle
- muß die Erzeugung der Ausgabe fertig sein, und wir
- müssen mit der Suche nach weiteren Eingabedaten
- fortfahren.</para>
- </listitem>
- </itemizedlist>
-
- <para>Es bleibt immer noch eine Möglichkeit
- unberücksichtigt: Wenn der Benutzer eine Null (oder
- mehrere) eingibt, werden wir niemals eine von Null
- verschiedene Zahl vorfinden.</para>
-
- <para>Wir können solch einen Fall immer anhand des
- Zählerstandes feststellen, welcher dann
- immer bei <constant>0</constant> bleibt. In diesem Fall
- müssen wir einfach eine <constant>0</constant> an
- die Ausgabe senden, und anschließend dem Benutzer
- erneut eine "Ohrfeige" verpassen:</para>
-
- <screen>0 ??? ??? ??? ??? ???</screen>
-
- <para>Sobald wir die Brennweite ausgegeben, und die
- Gültigkeit dieser Eingabe verifiziert haben,
- (größer als <constant>0</constant> und kleiner
- als 18 Zahlen) können wir den Durchmesser der
- Lochblende berechnen.</para>
-
- <para>Es ist kein Zufall, daß
- <emphasis>Lochblende</emphasis> das Wort
- <emphasis>Loch</emphasis> enthält. In der Tat ist eine
- Lochblende buchstäblich eine
- <emphasis>Loch Blende</emphasis>, also eine Blende, in die
- mit einer Nadel vorsichtig ein kleines Loch gestochen
- wird.</para>
-
- <para>Daher ist eine typische Lochblende sehr klein. Unsere
- Formel liefert uns das Ergebnis in Millimetern. Wir werden
- dieses mit <constant>1000</constant> multiplizieren, so
- daß die Ausgabe in <constant>Mikrometern</constant>
- erfolgt.</para>
-
- <para>An dieser Stelle müssen wir auf eine weitere Falle
- achten: <emphasis>Zu hohe Genauigkeit.</emphasis></para>
-
- <para>Ja, die <acronym>FPU</acronym> wurde für
- mathematische Berechnungen mit hoher Genauigkeit entworfen.
- Unsere Berechnungen hier erfordern jedoch keine solche
- mathematische Genauigkeit. Wir haben es hier mit Physik zu
- tun (Optik, um genau zu sein).</para>
-
- <para>Angenommen, wir wollten aus eine Lastkraftwagen eine
- Lochkamera bauen (wir wären dabei nicht die
- ersten, die das versuchen würden!). Angenommen, die
- Länge des Laderaumes beträgt <constant>12</constant>
- Meter lang, so daß wir eine Brennweite von
- <constant>12000</constant> hätten. Verwenden wir
- Benders Konstante, so erhalten wir durch Multiplizieren
- von <constant>0.04</constant> mit der Quadratwurzel aus
- <constant>12000</constant> einen Wert von
- <constant>4.381780460</constant> Millimetern, oder
- <constant>4381.780460</constant> Micrometern.</para>
-
- <para>So oder so ist das Rechenergebnis absurd präzise.
- Unser Lastkraftwagen ist nicht <emphasis>genau</emphasis>
- <constant>12000</constant> Millimeter lang. Wir haben
- diese Länge nicht mit einer so hohen Genauigkeit
- gemessen, weswegen es falsch wäre zu behaupten,
- unser Lochblendendurchmesser müsse exakt
- <constant>4.381780460</constant> Millimeter sein. Es
- reicht vollkommen aus, wenn der Durchmesser
- <constant>4.4</constant> Millimeter beträgt.</para>
-
- <note>
- <para>Ich habe in obigem Beispiel das Rechenergebnis "nur"
- auf 10 Stellen genau angegeben. Stellen Sie sich vor,
- wie absurd es wäre, die vollen uns zur Verfügung
- stehenden, 18 Stellen anzugeben!</para>
- </note>
-
- <para>Wir müssen also die Anzahl der signifikanten Stellen
- beschränken. Eine Möglichkeit wäre, die
- Mikrometer durch eine ganze Zahl darzustellen. Unser
- Lastkraftwaren würde dann eine Lochblende mit einem
- Durchmesser von <constant>4382</constant> Mikrometern
- benötigen. Betrachten wir diesen Wert, dann stellen wir
- fest, das <constant>4400</constant> Mikrometer, oder
- <constant>4.4</constant> Millimeter, immer noch genau
- genug ist.</para>
-
- <para>Zusätzlich können wir noch, unabhängig von
- der Größe eines Rechenergebnisses, festlegen,
- daß wir nur vier signifikante Stellen anzeigen wollen
- (oder weniger). Leider bietet uns die <acronym>FPU</acronym>
- nicht die Möglichkeit, das Ergebnis automatisch bis
- auf eine bestimmte Stelle zu runden (sie sieht die Daten ja
- nicht als Zahlen, sondern als binäre Daten an).</para>
-
- <para>Wir müssen also selber einen Algorithmus entwerfen,
- um die Anzahl der signifikanten Stellen zu reduzieren.</para>
-
- <para>Hier ist meiner (ich denke er ist peinlich&mdash;wenn
- Ihnen ein besserer Algorithmus einfällt, verraten sie
- ihn mir <emphasis>bitte</emphasis>):</para>
-
- <procedure>
- <step>
- <para>Initialisiere einen Zähler mit
- <constant>0</constant>.</para>
- </step>
-
- <step>
- <para>Solange die Zahl größer oder gleich
- <constant>10000</constant> ist, dividiere die Zahl durch
- <constant>10</constant>, und erhöhe den Zähler
- um eins.</para>
- </step>
-
- <step>
- <para>Gebe das Ergebnis aus.</para>
- </step>
-
- <step>
- <para>Solange der Zähler größer als
- <constant>0</constant> ist, gebe eine
- <constant>0</constant> aus, und reduziere den Zähler
- um eins.</para>
- </step>
- </procedure>
-
- <note>
- <para>Der Wert <constant>10000</constant> ist nur für
- den Fall, daß Sie <emphasis>vier</emphasis>
- signifikante Stellen haben wollen. Für eine andere
- Anzahl signifikanter Stellen müssen Sie den Wert
- <constant>10000</constant> mit <constant>10</constant>,
- hoch der Anzahl der gewünschten signifikanten Stellen,
- ersetzen.</para>
- </note>
-
- <para>Wir können so den Lochblendendurchmesser, auf vier
- signifikante Stellen gerundet, ausgeben.</para>
-
- <para>An dieser Stellen kennen wir nun die <emphasis>Brennweite
- </emphasis> und den
- <emphasis>Lochblendendurchmesser</emphasis>. Wir haben also
- jetzt genug Informationen, um den
- <emphasis>f&ndash;Wert</emphasis> zu bestimmen.</para>
-
- <para>Wir werden den f&ndash;Wert, auf vier signifikante
- Stellen gerundet, ausgeben. Es könnte passieren,
- daß diese vier Stellen recht wenig aussagen. Um die
- Aussagekraft des f&ndash;Wertes zu erhöhen, könnten
- wir den nächstliegenden, <emphasis>normalisierten
- f&ndash;Wert</emphasis> bestimmen, also z.B. das
- nächstliegende Vielfache der Quadratwurzel aus 2.</para>
-
- <para>Wir erreichen dies, indem wir den aktuellen f&ndash;Wert
- mit sich selbst multiplizieren, so daß wir dessen
- Quadrat (<function>square</function>) erhalten.
- Anschließend berechnen wir den Logarithmus zur Basis 2
- von dieser Zahl. Dies ist sehr viel einfacher, als direkt den
- Logarithmus zur Basis der Quadratwurzel aus 2 zu berechnen!
- Wir runden dann das Ergebnis auf die nächstliegende
- ganze Zahl. Genau genommen können wir mit Hilfe der
- <acronym>FPU</acronym> diese Berechnung beschleunigen: Wir
- können den op-Code
- <function role="opcode">fscale</function> verwenden, um eine
- Zahl um 1 zu "skalieren", was dasselbe ist, wie eine Zahl
- mittels <function role="opcode">shift</function> um eine
- Stelle nach links zu verschieben. Am Ende berechnen wir noch
- die Quadratwurzel aus allem, und erhalten dann den
- nächstliegenden, normalisierten f&ndash;Wert.</para>
-
- <para>Wenn das alles jetzt viel zu kompliziert wirkt&mdash;oder
- viel zu aufwendig&mdash;wird es vielleicht klarer, wenn man
- den Code selber betrachtet. Wir benötigen insgesamt
- 9 op-Codes:</para>
-
- <programlisting>fmul st0, st0
- fld1
- fld st1
- fyl2x
- frndint
- fld1
- fscale
- fsqrt
- fstp st1</programlisting>
-
- <para>Die erste Zeile,
- <function role="opcode">fmul st0, st0</function>, quadriert
- den Inhalt des <acronym>TOS</acronym> (Top Of Stack, was
- dasselbe ist wie <varname role="register">st</varname>, von
- <application>nasm</application> auch
- <varname role="register">st0</varname> genannt). Die Funktion
- <function role="opcode">fld1</function> fügt eine
- <constant>1</constant> dem <acronym>TOS</acronym>
- hinzu.</para>
-
- <para>Die nächste Zeile, <function role="opcode">fld
- st1</function>, legt das Quadrat auf dem
- <acronym>TOS</acronym> ab. An diesem Punkt befindet sich das
- Quadrat sowohl in <varname role="register">st</varname> als
- auch in <varname role="register">st(2)</varname> (es wird
- sich gleich zeigen, warum wir eine zweite Kopie auf dem
- Stack lassen.) <varname role="register">st(1)</varname>
- enthält die <constant>1</constant>.</para>
-
- <para>Im nächsten Schritt, <function
- role="opcode">fyl2x</function>, wird der Logarithmus von
- <varname role="register">st</varname> zur Basis 2 berechnet,
- und anschließend mit <varname
- role="register">st(1)</varname> multipliziert. Deshalb haben
- wir vorher die <constant>1</constant> in <varname
- role="register">st(1)</varname> abgelegt.</para>
-
- <para>An dieser Stelle enthält
- <varname role="register">st</varname> den gerade berechneten
- Logarithmus, und <varname role="register">st(1)</varname>
- das Quadrat des aktuellen f&ndash;Wertes, den wir für
- später gespeichert haben.</para>
-
- <para><function role="opcode">frndint</function> rundet den
- <acronym>TOS</acronym> zur nächstliegenden ganzen Zahl.
- <function role="opcode">fld1</function> legt eine
- <constant>1</constant> auf dem Stack ab.
- <function role="opcode">fscale</function> shiftet die
- <constant>1</constant> auf dem <acronym>TOS</acronym> um
- <varname role="register">st(1)</varname> Stellen, wodurch im
- Endeffekt eine 2 in <varname role="register">st(1)</varname>
- steht.</para>
-
- <para>Schließlich berechnet
- <function role="opcode">fsqrt</function> die Quadratwurzel
- des Rechenergebnisses, also des nächstliegenden,
- normalisierten f&ndash;Wertes.</para>
-
- <para>Wir haben nun den nächstliegenden, normalisierten
- f&ndash;Wert auf dem <acronym>TOS</acronym> liegen, den auf
- den Logarithmus zur Basis 2 gerundeten, nächstliegenden
- ganzzahligen Wert in <varname
- role="register">st(1)</varname>, und das Quadrat des
- aktuellen f&ndash;Wertes in <varname
- role="register">st(2)</varname>. Wir speichern den Wert
- für eine spätere Verwendung in <varname
- role="register">st(2)</varname>.</para>
-
- <para>Aber wir brauchen den Inhalt von
- <varname role="register">st(1)</varname> gar nicht mehr. Die
- letzte Zeile, <function role="opcode">fstp st1</function>,
- platziert den Inhalt von <varname role="register">st</varname>
- in <varname role="register">st(1)</varname>, und erniedrigt
- den Stackpointer um eins. Dadurch ist der Inhalt von
- <varname role="register">st(1)</varname> jetzt
- <varname role="register">st</varname>, der Inhalt von
- <varname role="register">st(2)</varname> jetzt
- <varname role="register">st(1)</varname> usw. Der neue
- <varname role="register">st</varname> speichert jetzt den
- normalisierten f&ndash;Wert. Der neue
- <varname role="register">st(1)</varname> speichert das
- Quadrat des aktuellen f&ndash;Wertes für die
- Nachwelt.</para>
-
- <para>Jetzt können wir den normalisierten f&ndash;Wert
- ausgeben. Da er normalisiert ist, werden wir ihn nicht auf
- vier signifikante Stellen runden, sondern stattdessen
- mit voller Genauigkeit ausgeben.</para>
-
- <para>Der normalisierte f&ndash;Wert ist nützlich, solange
- er so klein ist, daß wir ihn auf einem Photometer
- wiederfinden können. Ansonsten brauchen wir eine andere
- Methode, um die benötigten Belichtungsdaten zu
- bestimmen.</para>
-
- <para>Wir haben weiter oben eine Formel aufgestellt, über
- die wir einen f&ndash;Wert mit Hilfe eines anderen
- f&ndash;Wertes und den zugehörigen Belichtungsdaten
- bestimmen können.</para>
-
- <para>Jedes Photometer, das ich jemals gesehen habe, konnte
- die benötigte Belichtungszeit für f5.6 berechnen.
- Wir werden daher einen
- <emphasis>"f5.6 Multiplizierer"</emphasis> berechnen, der
- uns den Faktor angibt, mit dem wir die bei f5.6 gemessene
- Belichtungszeit für unsere Lochkamera multiplizieren
- müssen.</para>
-
- <para>Durch die Formel wissen wir, daß dieser Faktor
- durch Dividieren unseres f&ndash;Wertes (der aktuelle Wert,
- nicht der normalisierte) durch <constant>5.6</constant>
- und anschließendes Quadrieren, berechnen
- können.</para>
-
- <para>Mathematisch äquivalent dazu wäre, wenn wir
- das Quadrat unseres f&ndash;Wertes durch das Quadrat von
- <constant>5.6</constant> dividieren würden.</para>
-
- <para>Numerisch betrachtet wollen wir nicht zwei Zahlen
- quadrieren, wenn es möglich ist, nur
- eine Zahl zu quadrieren. Daher wirkt die erste Variante
- auf den ersten Blick besser.</para>
-
- <para>Aber...</para>
-
- <para><constant>5.6</constant> ist eine
- <emphasis>Konstante</emphasis>. Wir müssen nicht
- wertvolle Rechenzeit der <acronym>FPU</acronym> verschwenden.
- Es reicht aus, daß wir die Quadrate der einzelnen
- f&ndash;Werte durch den konstanten Wert
- <constant>5.6&#178;</constant> dividieren. Oder wir
- können den jeweiligen f&ndash;Wert durch
- <constant>5.6</constant> dividieren, und dann das Ergebnis
- quadrieren. Zwei Möglichkeiten, die gleich
- erscheinen.</para>
-
- <para>Aber das sind sie nicht!</para>
-
- <para>Erinnern wir uns an die Grundlagen der Photographie
- weiter oben, dann wissen wir, daß sich die
- Konstante <constant>5.6</constant> aus dem 5-fachen der
- Quadratwurzel aus 2 ergibt. Eine
- <emphasis>irrationale</emphasis> Zahl. Das Quadrat dieser
- Zahl ist <emphasis>exakt</emphasis>
- <constant>32</constant>.</para>
-
- <para><constant>32</constant> ist nicht nur eine ganze Zahl,
- sondern auch ein Vielfaches von 2. Wir brauchen also
- gar nicht das Quadrat eines f&ndash;Wertes durch
- <constant>32</constant> zu teilen. Wir müssen lediglich
- mittels <function role="opcode">fscale</function> den
- f&ndash;Wert um fünf Stellen nach rechts shiften.
- Aus Sicht der <acronym>FPU</acronym> müssen wir also
- <function role="opcode">fscale</function> mit
- <varname role="register">st(1)</varname>, welcher gleich
- <constant>-5</constant> ist, auf den f&ndash;Wert anwenden.
- Dies ist <emphasis>sehr viel schneller</emphasis> als die
- Division.</para>
-
- <para>Jetzt wird es auch klar, warum wir das Quadrat des
- f&ndash;Wertes ganz oben auf dem Stack der
- <acronym>FPU</acronym> gespeichert haben. Die Berechnung
- des f5.6 Multiplizierers ist die einfachste Berechnung
- des gesamten Programmes! Wir werden das Ergebnis auf vier
- signifikante Stellen gerundet ausgeben.</para>
-
- <para>Es gibt noch eine weitere nützliche Zahl, die wir
- berechnen können: Die Anzahl der Stopps, die unser
- f&ndash;Wert von f5.6 entfernt ist. Dies könnte
- hilfreich sein, wenn unser f&ndash;Wert außerhalb des
- Meßbereiches unseres Photometers liegt, wir aber eine
- Blende haben, bei der wir unterschiedliche Geschwindigkeiten
- einstellen können, und diese Blende Stopps
- benutzt.</para>
-
- <para>Angenommen, unser f&ndash;Wert ist 5 Stopps von f5.6
- entfernt, und unser Photometer sagt uns, daß wir eine
- Belichtungszeit von 1/1000 Sek. einstellen sollen. Dann
- können wir unsere Blende auf die Geschwindigkeit 1/1000
- einstellen, und unsere Skala um 5 Stopps verschieben.</para>
-
- <para>Diese Rechnung ist ebenfalls sehr einfach. Alles, was
- wir tun müssen, ist, den Logarithmus des f5.6
- Multiplizierers, den wir schon berechnet haben (wobei wir
- dessen Wert vor der Rundung nehmen müssen) zur Basis 2
- zu nehmen. Wir runden dann das Ergebnis zur nächsten
- ganzen Zahl hin, und geben dies aus. Wir müssen uns
- nicht darum kümmern, ob wir mehr als vier signifikante
- Stellen haben: Das Ergebnis besteht
- höchstwahrscheinlich nur aus einer oder zwei
- Stellen.</para>
- </sect3>
- </sect2>
-
- <sect2 id="x86-fpu-optimizations">
- <title>FPU Optimierungen</title>
-
- <para>In Assemblersprache können wir den Code für die
- <acronym>FPU</acronym> besser optimieren, als in einer der
- Hochsprachen, inklusive C.</para>
-
- <para>Sobald eine C-Funktion die Berechnung einer
- Fließkommazahl durchführen will, lädt sie erst
- einmal alle benötigten Variablen und Konstanten in die
- Register der <acronym>FPU</acronym>. Dann werden die
- Berechnungen durchgeführt, um das korrekte Ergebnis zu
- erhalten. Gute C-Compiler können diesen Teil des Codes
- sehr gut optimieren.</para>
-
- <para>Das Ergebnis wird "zurückgegeben", indem dieses auf
- dem <acronym>TOS</acronym> abgelegt wird. Vorher wird
- aufgeräumt. Sämtliche Variablen und Konstanten, die
- während der Berechnung verwendet wurden, werden dabei
- aus der <acronym>FPU</acronym> entfernt.</para>
-
- <para>Was wir im vorherigen Abschnitt selber getan haben, kann so
- nicht durchgeführt werden: Wir haben das Quadrat des
- f&ndash;Wertes berechnet, und das Ergebnis für eine
- weitere Berechnung mit einer anderen Funktion auf dem Stack
- behalten.</para>
-
- <para>Wir <emphasis>wußten</emphasis>, daß wir
- diesen Wert später noch einmal brauchen würden. Wir
- wußten auch, daß auf dem Stack genügend Platz
- war (welcher nur Platz für 8 Zahlen bietet), um den Wert
- dort zu speichern.</para>
-
- <para>Ein C-Compiler kann nicht wissen, ob ein Wert auf dem
- Stack in naher Zukunft noch einmal gebraucht wird.</para>
-
- <para>Natürlich könnte der C-Programmierer dies wissen.
- Aber die einzige Möglichkeit, die er hat, ist, den Wert
- im verfügbaren Speicher zu halten.</para>
-
- <para>Das bedeutet zum einen, daß der Wert mit der
- <acronym>FPU</acronym>-internen, 80-stelligen
- Genauigkeit in einer normalen C-Variable vom Typ
- <emphasis>double</emphasis> (64 Bit) oder vom Typ
- <emphasis>single</emphasis> (32 Bit) gespeichert wird.</para>
-
- <para>Dies bedeutet außerdem, daß der Wert aus dem
- <acronym>TOS</acronym> in den Speicher verschoben werden
- muß, und später wieder zurück. Von allen
- Operationen mit der <acronym>FPU</acronym> ist der Zugriff
- auf den Speicher die langsamste.</para>
-
- <para>Wann immer also mit der <acronym>FPU</acronym> in einer
- Assemblersprache programmiert wird, sollte nach
- Möglichkeiten gesucht werden, Zwischenergebnisse auf dem
- Stack der <acronym>FPU</acronym> zu lassen.</para>
-
- <para>Wir können mit dieser Idee noch einen Schritt weiter
- gehen! In unserem Programm verwenden wir eine
- <emphasis>Konstante</emphasis> (die wir <constant>PC</constant>
- genannt haben).</para>
-
- <para>Es ist unwichtig, wieviele Lochblendendurchmesser wir
- berechnen: 1, 10, 20, 1000, wir verwenden immer dieselbe
- Konstante. Daher können wir unser Programm so optimieren,
- daß diese Konstante immer auf dem Stack belassen
- wird.</para>
-
- <para>Am Anfang unseres Programmes berechnen wir die oben
- erwähnte Konstante. Wir müssen die Eingabe für
- jede Dezimalstelle der Konstanten durch <constant>10</constant>
- dividieren.</para>
-
- <para>Multiplizieren geht sehr viel schneller als Dividieren.
- Wir teilen also zu Beginn unseres Programmes
- <constant>1</constant> durch <constant>10</constant>, um
- <constant>0.1</constant> zu erhalten, was wir auf dem Stack
- speichern: Anstatt daß wir nun für jede einzelne
- Dezimalstelle die Eingabe wieder durch <constant>10</constant>
- teilen, multiplizieren wir sie stattdessen mit
- <constant>0.1</constant>.</para>
-
- <para>Auf diese Weise geben wir <constant>0.1</constant> nicht
- direkt ein, obwohl wir dies könnten. Dies hat einen Grund:
- Während <constant>0.1</constant> durch nur eine einzige
- Dezimalstelle dargestellt werden kann, wissen wir nicht,
- wieviele <emphasis>binäre</emphasis> Stellen benötigt
- werden. Wir überlassen die Berechnung des binären
- Wertes daher der <acronym>FPU</acronym>, mit dessen eigener,
- hoher Genauigkeit.</para>
-
- <para>Wir verwenden noch weitere Konstanten: Wir multiplizieren
- den Lochblendendurchmesser mit <constant>1000</constant>, um
- den Wert von Millimeter in Micrometer zu konvertieren. Wir
- vergleichen Werte mit <constant>10000</constant>, wenn wir
- diese auf vier signifikante Stellen runden wollen. Wir
- behalten also beide Konstanten, <constant>1000</constant>
- und <constant>10000</constant>, auf dem Stack. Und
- selbstverständlich verwenden wir erneut die gespeicherte
- <constant>0.1</constant>, um Werte auf vier signifikante
- Stellen zu runden.</para>
-
- <para>Zu guter letzt behalten wir <constant>-5</constant> noch
- auf dem Stack. Wir brauchen diesen Wert, um das Quadrat des
- f&ndash;Wertes zu skalieren, anstatt diesen durch
- <constant>32</constant> zu teilen. Es ist kein Zufall, daß
- wir diese Konstante als letztes laden. Dadurch wird diese
- Zahl die oberste Konstante auf dem Stack. Wenn später
- das Quadrat des f&ndash;Wertes skaliert werden muß,
- befindet sich die <constant>-5</constant> in
- <varname role="register">st(1)</varname>, also genau da, wo
- die Funktion <function role="opcode">fscale</function> diesen
- Wert erwartet.</para>
-
- <para>Es ist üblich, einige Konstanten per Hand zu erzeugen,
- anstatt sie aus dem Speicher zu laden. Genau das machen wir
- mit der <constant>-5</constant>:</para>
-
- <programlisting>
- fld1 ; TOS = 1
- fadd st0, st0 ; TOS = 2
- fadd st0, st0 ; TOS = 4
- fld1 ; TOS = 1
- faddp st1, st0 ; TOS = 5
- fchs ; TOS = -5</programlisting>
-
- <para>Wir können all diese Optimierungen in einer Regel
- zusammenfassen: <emphasis>Behalte wiederverwendbare Werte auf
- dem Stack!</emphasis></para>
-
- <tip>
- <para><emphasis>&postscript;</emphasis> ist eine
- Stack-orientierte Programmiersprache. Es gibt weit mehr
- Bücher über &postscript;, als über die
- Assemblersprache der <acronym>FPU</acronym>: Werden Sie
- in &postscript; besser, dann werden Sie auch automatisch
- in der Programmierung der <acronym>FPU</acronym>
- besser.</para>
- </tip>
- </sect2>
-
- <sect2 id="x86-pinhole-the-code">
- <title><application>pinhole</application>&mdash;Der Code</title>
-
- <programlisting>
-;;;;;;; pinhole.asm ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;
-; Find various parameters of a pinhole camera construction and use
-;
-; Started: 9-Jun-2001
-; Updated: 10-Jun-2001
-;
-; Copyright (c) 2001 G. Adam Stanislav
-; All rights reserved.
-;
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
-%include 'system.inc'
-
-%define BUFSIZE 2048
-
-section .data
-align 4
-ten dd 10
-thousand dd 1000
-tthou dd 10000
-fd.in dd stdin
-fd.out dd stdout
-envar db 'PINHOLE=' ; Exactly 8 bytes, or 2 dwords long
-pinhole db '04,', ; Bender's constant (0.04)
-connors db '037', 0Ah ; Connors' constant
-usg db 'Usage: pinhole [-b] [-c] [-e] [-p &lt;value&gt;] [-o &lt;outfile&gt;] [-i &lt;infile&gt;]', 0Ah
-usglen equ $-usg
-iemsg db "pinhole: Can't open input file", 0Ah
-iemlen equ $-iemsg
-oemsg db "pinhole: Can't create output file", 0Ah
-oemlen equ $-oemsg
-pinmsg db "pinhole: The PINHOLE constant must not be 0", 0Ah
-pinlen equ $-pinmsg
-toobig db "pinhole: The PINHOLE constant may not exceed 18 decimal places", 0Ah
-biglen equ $-toobig
-huhmsg db 9, '???'
-separ db 9, '???'
-sep2 db 9, '???'
-sep3 db 9, '???'
-sep4 db 9, '???', 0Ah
-huhlen equ $-huhmsg
-header db 'focal length in millimeters,pinhole diameter in microns,'
- db 'F-number,normalized F-number,F-5.6 multiplier,stops '
- db 'from F-5.6', 0Ah
-headlen equ $-header
-
-section .bss
-ibuffer resb BUFSIZE
-obuffer resb BUFSIZE
-dbuffer resb 20 ; decimal input buffer
-bbuffer resb 10 ; BCD buffer
-
-section .text
-align 4
-huh:
- call write
- push dword huhlen
- push dword huhmsg
- push dword [fd.out]
- sys.write
- add esp, byte 12
- ret
-
-align 4
-perr:
- push dword pinlen
- push dword pinmsg
- push dword stderr
- sys.write
- push dword 4 ; return failure
- sys.exit
-
-align 4
-consttoobig:
- push dword biglen
- push dword toobig
- push dword stderr
- sys.write
- push dword 5 ; return failure
- sys.exit
-
-align 4
-ierr:
- push dword iemlen
- push dword iemsg
- push dword stderr
- sys.write
- push dword 1 ; return failure
- sys.exit
-
-align 4
-oerr:
- push dword oemlen
- push dword oemsg
- push dword stderr
- sys.write
- push dword 2
- sys.exit
-
-align 4
-usage:
- push dword usglen
- push dword usg
- push dword stderr
- sys.write
- push dword 3
- sys.exit
-
-align 4
-global _start
-_start:
- add esp, byte 8 ; discard argc and argv[0]
- sub esi, esi
-
-.arg:
- pop ecx
- or ecx, ecx
- je near .getenv ; no more arguments
-
- ; ECX contains the pointer to an argument
- cmp byte [ecx], '-'
- jne usage
-
- inc ecx
- mov ax, [ecx]
- inc ecx
-
-.o:
- cmp al, 'o'
- jne .i
-
- ; Make sure we are not asked for the output file twice
- cmp dword [fd.out], stdout
- jne usage
-
- ; Find the path to output file - it is either at [ECX+1],
- ; i.e., -ofile --
- ; or in the next argument,
- ; i.e., -o file
-
- or ah, ah
- jne .openoutput
- pop ecx
- jecxz usage
-
-.openoutput:
- push dword 420 ; file mode (644 octal)
- push dword 0200h | 0400h | 01h
- ; O_CREAT | O_TRUNC | O_WRONLY
- push ecx
- sys.open
- jc near oerr
-
- add esp, byte 12
- mov [fd.out], eax
- jmp short .arg
-
-.i:
- cmp al, 'i'
- jne .p
-
- ; Make sure we are not asked twice
- cmp dword [fd.in], stdin
- jne near usage
-
- ; Find the path to the input file
- or ah, ah
- jne .openinput
- pop ecx
- or ecx, ecx
- je near usage
-
-.openinput:
- push dword 0 ; O_RDONLY
- push ecx
- sys.open
- jc near ierr ; open failed
-
- add esp, byte 8
- mov [fd.in], eax
- jmp .arg
-
-.p:
- cmp al, 'p'
- jne .c
- or ah, ah
- jne .pcheck
-
- pop ecx
- or ecx, ecx
- je near usage
-
- mov ah, [ecx]
-
-.pcheck:
- cmp ah, '0'
- jl near usage
- cmp ah, '9'
- ja near usage
- mov esi, ecx
- jmp .arg
-
-.c:
- cmp al, 'c'
- jne .b
- or ah, ah
- jne near usage
- mov esi, connors
- jmp .arg
-
-.b:
- cmp al, 'b'
- jne .e
- or ah, ah
- jne near usage
- mov esi, pinhole
- jmp .arg
-
-.e:
- cmp al, 'e'
- jne near usage
- or ah, ah
- jne near usage
- mov al, ','
- mov [huhmsg], al
- mov [separ], al
- mov [sep2], al
- mov [sep3], al
- mov [sep4], al
- jmp .arg
-
-align 4
-.getenv:
- ; If ESI = 0, we did not have a -p argument,
- ; and need to check the environment for "PINHOLE="
- or esi, esi
- jne .init
-
- sub ecx, ecx
-
-.nextenv:
- pop esi
- or esi, esi
- je .default ; no PINHOLE envar found
-
- ; check if this envar starts with 'PINHOLE='
- mov edi, envar
- mov cl, 2 ; 'PINHOLE=' is 2 dwords long
-rep cmpsd
- jne .nextenv
-
- ; Check if it is followed by a digit
- mov al, [esi]
- cmp al, '0'
- jl .default
- cmp al, '9'
- jbe .init
- ; fall through
-
-align 4
-.default:
- ; We got here because we had no -p argument,
- ; and did not find the PINHOLE envar.
- mov esi, pinhole
- ; fall through
-
-align 4
-.init:
- sub eax, eax
- sub ebx, ebx
- sub ecx, ecx
- sub edx, edx
- mov edi, dbuffer+1
- mov byte [dbuffer], '0'
-
- ; Convert the pinhole constant to real
-.constloop:
- lodsb
- cmp al, '9'
- ja .setconst
- cmp al, '0'
- je .processconst
- jb .setconst
-
- inc dl
-
-.processconst:
- inc cl
- cmp cl, 18
- ja near consttoobig
- stosb
- jmp short .constloop
-
-align 4
-.setconst:
- or dl, dl
- je near perr
-
- finit
- fild dword [tthou]
-
- fld1
- fild dword [ten]
- fdivp st1, st0
-
- fild dword [thousand]
- mov edi, obuffer
-
- mov ebp, ecx
- call bcdload
-
-.constdiv:
- fmul st0, st2
- loop .constdiv
-
- fld1
- fadd st0, st0
- fadd st0, st0
- fld1
- faddp st1, st0
- fchs
-
- ; If we are creating a CSV file,
- ; print header
- cmp byte [separ], ','
- jne .bigloop
-
- push dword headlen
- push dword header
- push dword [fd.out]
- sys.write
-
-.bigloop:
- call getchar
- jc near done
-
- ; Skip to the end of the line if you got '#'
- cmp al, '#'
- jne .num
- call skiptoeol
- jmp short .bigloop
-
-.num:
- ; See if you got a number
- cmp al, '0'
- jl .bigloop
- cmp al, '9'
- ja .bigloop
-
- ; Yes, we have a number
- sub ebp, ebp
- sub edx, edx
-
-.number:
- cmp al, '0'
- je .number0
- mov dl, 1
-
-.number0:
- or dl, dl ; Skip leading 0's
- je .nextnumber
- push eax
- call putchar
- pop eax
- inc ebp
- cmp ebp, 19
- jae .nextnumber
- mov [dbuffer+ebp], al
-
-.nextnumber:
- call getchar
- jc .work
- cmp al, '#'
- je .ungetc
- cmp al, '0'
- jl .work
- cmp al, '9'
- ja .work
- jmp short .number
-
-.ungetc:
- dec esi
- inc ebx
-
-.work:
- ; Now, do all the work
- or dl, dl
- je near .work0
-
- cmp ebp, 19
- jae near .toobig
-
- call bcdload
-
- ; Calculate pinhole diameter
-
- fld st0 ; save it
- fsqrt
- fmul st0, st3
- fld st0
- fmul st5
- sub ebp, ebp
-
- ; Round off to 4 significant digits
-.diameter:
- fcom st0, st7
- fstsw ax
- sahf
- jb .printdiameter
- fmul st0, st6
- inc ebp
- jmp short .diameter
-
-.printdiameter:
- call printnumber ; pinhole diameter
-
- ; Calculate F-number
-
- fdivp st1, st0
- fld st0
-
- sub ebp, ebp
-
-.fnumber:
- fcom st0, st6
- fstsw ax
- sahf
- jb .printfnumber
- fmul st0, st5
- inc ebp
- jmp short .fnumber
-
-.printfnumber:
- call printnumber ; F number
-
- ; Calculate normalized F-number
- fmul st0, st0
- fld1
- fld st1
- fyl2x
- frndint
- fld1
- fscale
- fsqrt
- fstp st1
-
- sub ebp, ebp
- call printnumber
-
- ; Calculate time multiplier from F-5.6
-
- fscale
- fld st0
-
- ; Round off to 4 significant digits
-.fmul:
- fcom st0, st6
- fstsw ax
- sahf
-
- jb .printfmul
- inc ebp
- fmul st0, st5
- jmp short .fmul
-
-.printfmul:
- call printnumber ; F multiplier
-
- ; Calculate F-stops from 5.6
-
- fld1
- fxch st1
- fyl2x
-
- sub ebp, ebp
- call printnumber
-
- mov al, 0Ah
- call putchar
- jmp .bigloop
-
-.work0:
- mov al, '0'
- call putchar
-
-align 4
-.toobig:
- call huh
- jmp .bigloop
-
-align 4
-done:
- call write ; flush output buffer
-
- ; close files
- push dword [fd.in]
- sys.close
-
- push dword [fd.out]
- sys.close
-
- finit
-
- ; return success
- push dword 0
- sys.exit
-
-align 4
-skiptoeol:
- ; Keep reading until you come to cr, lf, or eof
- call getchar
- jc done
- cmp al, 0Ah
- jne .cr
- ret
-
-.cr:
- cmp al, 0Dh
- jne skiptoeol
- ret
-
-align 4
-getchar:
- or ebx, ebx
- jne .fetch
-
- call read
-
-.fetch:
- lodsb
- dec ebx
- clc
- ret
-
-read:
- jecxz .read
- call write
-
-.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 .empty
- sub eax, eax
- ret
-
-align 4
-.empty:
- add esp, byte 4
- stc
- ret
-
-align 4
-putchar:
- stosb
- inc ecx
- cmp ecx, BUFSIZE
- je write
- ret
-
-align 4
-write:
- jecxz .ret ; nothing to 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:
- ret
-
-align 4
-bcdload:
- ; EBP contains the number of chars in dbuffer
- push ecx
- push esi
- push edi
-
- lea ecx, [ebp+1]
- lea esi, [dbuffer+ebp-1]
- shr ecx, 1
-
- std
-
- mov edi, bbuffer
- sub eax, eax
- mov [edi], eax
- mov [edi+4], eax
- mov [edi+2], ax
-
-.loop:
- lodsw
- sub ax, 3030h
- shl al, 4
- or al, ah
- mov [edi], al
- inc edi
- loop .loop
-
- fbld [bbuffer]
-
- cld
- pop edi
- pop esi
- pop ecx
- sub eax, eax
- ret
-
-align 4
-printnumber:
- push ebp
- mov al, [separ]
- call putchar
-
- ; Print the integer at the TOS
- mov ebp, bbuffer+9
- fbstp [bbuffer]
-
- ; Check the sign
- mov al, [ebp]
- dec ebp
- or al, al
- jns .leading
-
- ; We got a negative number (should never happen)
- mov al, '-'
- call putchar
-
-.leading:
- ; Skip leading zeros
- mov al, [ebp]
- dec ebp
- or al, al
- jne .first
- cmp ebp, bbuffer
- jae .leading
-
- ; We are here because the result was 0.
- ; Print '0' and return
- mov al, '0'
- jmp putchar
-
-.first:
- ; We have found the first non-zero.
- ; But it is still packed
- test al, 0F0h
- jz .second
- push eax
- shr al, 4
- add al, '0'
- call putchar
- pop eax
- and al, 0Fh
-
-.second:
- add al, '0'
- call putchar
-
-.next:
- cmp ebp, bbuffer
- jb .done
-
- mov al, [ebp]
- push eax
- shr al, 4
- add al, '0'
- call putchar
- pop eax
- and al, 0Fh
- add al, '0'
- call putchar
-
- dec ebp
- jmp short .next
-
-.done:
- pop ebp
- or ebp, ebp
- je .ret
-
-.zeros:
- mov al, '0'
- call putchar
- dec ebp
- jne .zeros
-
-.ret:
- ret</programlisting>
-
- <para>Der Code folgt demselben Aufbau wie alle anderen Filter,
- die wir bisher gesehen haben, bis auf eine Kleinigkeit:</para>
-
- <blockquote>
- <para>Wir nehmen nun nicht mehr an, daß das Ende
- der Eingabe auch das Ende der nötigen Arbeit bedeutet,
- etwas, das wir für <emphasis>zeichenbasierte</emphasis>
- Filter automatisch angenommen haben.</para>
-
- <para>Dieser Filter verarbeitet keine Zeichen. Er verarbeitet
- eine <emphasis>Sprache</emphasis> (obgleich eine sehr
- einfache, die nur aus Zahlen besteht).</para>
-
- <para>Wenn keine weiteren Eingaben vorliegen, kann das zwei
- Ursachen haben:</para>
-
- <itemizedlist>
- <listitem>
- <para>Wir sind fertig und können aufhören. Dies
- ist dasselbe wie vorher.</para>
- </listitem>
-
- <listitem>
- <para>Das Zeichen, das wir eingelesen haben, war eine Zahl.
- Wir haben diese am Ende unseres <acronym>ASCII</acronym>
- &ndash;zu&ndash;float Kovertierungspuffers gespeichert.
- Wir müssen nun den gesamten Pufferinhalt in eine
- Zahl konvertieren, und die letzte Zeile unserer Ausgabe
- ausgeben.</para>
- </listitem>
- </itemizedlist>
-
- <para>Aus diesem Grund haben wir unsere <function>getchar
- </function>- und <function>read</function>-Routinen
- so angepaßt, daß sie das
- <varname role="register">carry flag</varname>
- <emphasis>clear</emphasis> immer dann zurückgeben, wenn
- wir ein weiteres Zeichen aus der Eingabe lesen, und das
- <varname role="register">carry flag</varname>
- <emphasis>set</emphasis> immer dann zurückgeben, wenn
- es keine weiteren Eingabedaten gibt.</para>
-
- <para>Selbstverständlich verwenden wir auch hier die
- Magie der Assemblersprache! Schauen Sie sich
- <function>getchar</function> näher an. Dieses gibt
- <emphasis>immer</emphasis> das
- <varname role="register">carry flag</varname>
- <emphasis>clear</emphasis> zurück.</para>
-
- <para>Dennoch basiert der Hauptteil unseres Programmes auf dem
- <varname role="register">carry flag</varname>, um diesem eine
- Beendigung mitzuteilen&mdash;und es funktioniert.</para>
-
- <para>Die Magie passiert in <function>read</function>. Wann
- immer weitere Eingaben durch das System zur Verfügung
- stehen, ruft diese Funktion <function>getchar</function>
- auf, welche ein weiteres Zeichen aus dem Eingabepuffer
- einliest, und anschließend das
- <varname role="register">carry flag</varname>
- <emphasis>clear</emphasis>t.</para>
-
- <para>Wenn aber <function>read</function> keine weiteren
- Eingaben von dem System bekommt, ruft dieses
- <emphasis>nicht</emphasis> <function>getchar</function>
- auf. Stattdessen addiert der op-Code
- <function role="opcode">add esp, byte 4</function>
- <constant>4</constant> zu
- <varname role="register">ESP</varname> hinzu,
- <emphasis>setzt</emphasis> das
- <varname role="register">carry flag</varname>, und
- springt zurück.</para>
-
- <para>Wo springt diese Funktion hin? Wann immer ein Programm
- den op-Code <function role="opcode">call</function>
- verwendet, <function role="opcode">push</function>t der
- Mikroprozessor die Rücksprungandresse, d.h. er
- speichert diese ganz oben auf dem Stack (nicht auf dem
- Stack der <acronym>FPU</acronym>, sondern auf dem Systemstack,
- der sich im Hauptspeicher befindet). Wenn ein Programm den
- op-Code <function role="opcode">ret</function> verwendet,
- <function role="opcode">pop</function>t der
- Mikroprozessor den Rückgabewert von dem Stack, und
- springt zu der Adresse, die dort gespeichert wurde.</para>
-
- <para>Da wir aber <constant>4</constant> zu
- <varname role="register">ESP</varname> hinzuaddiert haben
- (welches das Register der Stackzeiger ist), haben wir
- effektiv dem Mikroprzessor eine kleine
- <emphasis>Amnesie</emphasis> verpaßt: Dieser erinnert
- sich nun nicht mehr daran, daß
- <function>getchar</function> durch
- <function>read</function> aufgerufen wurde.</para>
-
- <para>Und da <function>getchar</function> nichts vor dem
- Aufruf von <function>read</function> auf dem Stack abgelegt
- hat, enthält der Anfang des Stacks nun die
- Rücksprungadresse von der Funktion, die
- <function>getchar</function> aufgerufen hat. Soweit es den
- Aufrufer betrifft, hat dieser <function>getchar</function>
- ge<function role="opcode">call</function>t, welche mit
- einem gesetzten
- <varname role="register">carry flag</varname>
- <function role="opcode">ret</function>urned.</para>
- </blockquote>
-
- <para>Des weiteren wird die Routine <function>bcdload</function>
- bei einem klitzekleinen Problem zwischen der Big&ndash;Endian-
- und Little&ndash;Endian-Codierung aufgerufen.</para>
-
- <para>Diese konvertiert die Textrepräsentation einer
- Zahl in eine andere Textrepräsentation: Der Text wird
- in der Big&ndash;Endian-Codierung gespeichert, die
- <emphasis>packed decimal</emphasis>-Darstellung jedoch in der
- Little&ndash;Endian-Codierung.</para>
-
- <para>Um dieses Problem zu lösen haben wir vorher den
- op-Code <function>std</function> verwendet. Wir machen diesen
- Aufruf später mittels <function>cld</function> wieder
- rückgängig: Es ist sehr wichtig, daß wir keine
- Funktion mittels <function>call</function> aufrufen, die von
- einer Standardeinstellung des
- <emphasis>Richtungsflags</emphasis> abhängig ist,
- während <function>std</function> ausgeführt
- wird.</para>
-
- <para>Alles weitere in dem Programm sollte leicht zu verstehen
- sein, vorausgesetzt, daß Sie das gesamte vorherige
- Kapitel gelesen haben.</para>
-
- <para>Es ist ein klassisches Beispiel für das Sprichwort,
- daß das Programmieren eine Menge Denkarbeit, und nur
- ein wenig Programmcode benötigt. Sobald wir uns über
- jedes Detail im klaren sind, steht der Code fast schon
- da.</para>
- </sect2>
-
- <sect2 id="x86-pinhole-using">
- <title>Das Programm <application>pinhole</application>
- verwenden</title>
-
- <para>Da wir uns bei dem Programm dafür entschieden haben,
- alle Eingaben, die keine Zahlen sind, zu ignorieren (selbst
- die in Kommentaren), können wir jegliche
- <emphasis>textbasierten Eingaben</emphasis> verarbeiten. Wir
- <emphasis>müssen</emphasis> dies nicht tun, wir
- <emphasis>könnten</emphasis> aber.</para>
-
- <para>Meiner bescheidenen Meinung nach wird ein Programm durch
- die Möglichkeit, anstatt einer strikten Eingabesyntax
- textbasierte Anfragen stellen zu können, sehr viel
- benutzerfreundlicher.</para>
-
- <para>Angenommen, wir wollten eine Lochkamera für einen
- 4x5 Zoll Film bauen. Die standardmäßige Brennweite
- für diesen Film ist ungefähr 150mm.
- Wir wollen diesen Wert <emphasis>optimieren</emphasis>, so
- daß der Lochblendendurchmesser eine möglichst
- runde Zahl ergibt. Lassen Sie uns weiter annehmen, daß
- wir zwar sehr gut mit Kameras umgehen können, dafür
- aber nicht so gut mit Computern. Anstatt das wir nun eine
- Reihe von Zahlen eingeben, wollen wir lieber ein paar
- <emphasis>Fragen</emphasis> stellen.</para>
-
- <para>Unsere Sitzung könnte wie folgt aussehen:</para>
-
- <screen>&prompt.user; <userinput>pinhole
-
-Computer,
-
-Wie groß müßte meine Lochblende bei einer Brennweite
-von 150 sein?</userinput>
-150 490 306 362 2930 12
-<userinput>Hmmm... Und bei 160?</userinput>
-160 506 316 362 3125 12
-<userinput>Laß uns bitte 155 nehmen.</userinput>
-155 498 311 362 3027 12
-<userinput>Ah, laß uns 157 probieren...</userinput>
-157 501 313 362 3066 12
-<userinput>156?</userinput>
-156 500 312 362 3047 12
-<userinput>Das ist es! Perfekt! Vielen Dank!
-^D</userinput></screen>
-
- <para>Wir haben herausgefunden, daß der
- Lochblendendurchmesser bei einer Brennweite von 150 mm
- 490 Mikrometer, oder 0.49 mm ergeben würde. Bei einer fast
- identischen Brennweite von 156 mm würden wir
- einen Durchmesser von genau einem halben Millimeter
- bekommen.</para>
- </sect2>
-
- <sect2 id="x86-pinhole-scripting">
- <title>Skripte schreiben</title>
-
- <para>Da wir uns dafür entschieden haben, das Zeichen
- <constant>#</constant> als den Anfang eines Kommentares zu
- interpretieren, können wir unser
- <application>pinhole</application>-Programm auch als
- <emphasis>Skriptsprache</emphasis> verwenden.</para>
-
- <para>Sie haben vielleicht schon einmal
- <application>shell</application><emphasis>-Skripte</emphasis>
- gesehen, die mit folgenden Zeichen begonnen haben:</para>
-
- <programlisting>#! /bin/sh</programlisting>
-
- <para>...oder...</para>
-
- <programlisting>#!/bin/sh</programlisting>
-
- <para>... da das Leerzeichen hinter dem <function>#!</function>
- optional ist.</para>
-
- <para>Wann immer &unix; eine Datei ausführen soll, die mit
- einem <function>#!</function> beginnt, wird angenommen, das
- die Datei ein Skript ist. Es fügt den Befehl an das Ende
- der ersten Zeile an, und versucht dann, dieses
- auszuführen.</para>
-
- <para>Angenommen, wir haben unser Programm
- <application>pinhole</application> unter
- <application>/usr/local/bin/</application> installiert, dann
- können wir nun Skripte schreiben, um unterschiedliche
- Lochblendendurchmesser für mehrere Brennweiten
- zu berechnen, die normalerweise mit 120er Filmen verwendet
- werden.</para>
-
- <para>Das Skript könnte wie folgt aussehen:</para>
-
- <programlisting>#! /usr/local/bin/pinhole -b -i
-# Find the best pinhole diameter
-# for the 120 film
-
-### Standard
-80
-
-### Wide angle
-30, 40, 50, 60, 70
-
-### Telephoto
-100, 120, 140</programlisting>
-
- <para>Da ein 120er Film ein Film mittlerer Größe ist,
- könnten wir die Datei <application>medium</application>
- nennen.</para>
-
- <para>Wir können die Datei ausführbar machen und dann
- aufrufen, als wäre es ein Programm:</para>
-
- <screen>&prompt.user; <userinput>chmod 755 medium</userinput>
-&prompt.user; <userinput>./medium</userinput></screen>
-
- <para>&unix; wird den letzten Befehl wie folgt
- interpretieren:</para>
-
- <screen>&prompt.user; <userinput>/usr/local/bin/pinhole -b -i ./medium</userinput></screen>
-
- <para>Es wird den Befehl ausführen und folgendes
- ausgeben:</para>
-
- <screen>80 358 224 256 1562 11
-30 219 137 128 586 9
-40 253 158 181 781 10
-50 283 177 181 977 10
-60 310 194 181 1172 10
-70 335 209 181 1367 10
-100 400 250 256 1953 11
-120 438 274 256 2344 11
-140 473 296 256 2734 11</screen>
-
- <para>Lassen Sie uns nun das folgende eingeben:</para>
-
- <screen>&prompt.user; <userinput>./medium -c</userinput></screen>
-
- <para>&unix; wird dieses wie folgt behandeln:</para>
-
- <screen>&prompt.user; <userinput>/usr/local/bin/pinhole -b -i ./medium -c</userinput></screen>
-
- <para>Dadurch erhält das Programm zwei
- widersprüchliche Optionen: <parameter>-b</parameter> und
- <parameter>-c</parameter> (Verwende Benders Konstante und
- verwende Connors Konstante). Wir haben unser Programm so
- geschrieben, daß später eingelesene Optionen die
- vorheringen überschreiben&mdash;unser Programm wird also
- Connors Konstante für die Berechnungen verwenden:</para>
-
- <screen>80 331 242 256 1826 11
-30 203 148 128 685 9
-40 234 171 181 913 10
-50 262 191 181 1141 10
-60 287 209 181 1370 10
-70 310 226 256 1598 11
-100 370 270 256 2283 11
-120 405 296 256 2739 11
-140 438 320 362 3196 12</screen>
-
- <para>Wir entscheiden uns am Ende doch für Benders
- Konstante. Wir wollen die Ergebnisse im CSV-Format in
- einer Datei speichern:</para>
-
- <screen>&prompt.user; <userinput>./medium -b -e &gt; bender</userinput>
-&prompt.user; <userinput>cat bender</userinput>
-focal length in millimeters,pinhole diameter in microns,F-number,normalized F-number,F-5.6 multiplier,stops from F-5.6
-80,358,224,256,1562,11
-30,219,137,128,586,9
-40,253,158,181,781,10
-50,283,177,181,977,10
-60,310,194,181,1172,10
-70,335,209,181,1367,10
-100,400,250,256,1953,11
-120,438,274,256,2344,11
-140,473,296,256,2734,11
-&prompt.user;</screen>
- </sect2>
- </sect1>
-
- <sect1 id="x86-caveats">
- <sect1info>
- <authorgroup>
- <author>
- <firstname>Daniel</firstname>
- <surname>Seuffert</surname>
- <contrib>Übersetzt von </contrib>
- </author>
- </authorgroup>
- </sect1info>
-
- <title>Vorsichtsmassnahmen</title>
-
- <para>Assembler-Programmierer, die aufwuchsen mit
- <acronym>&ms-dos;</acronym> und windows &windows; neigen oft
- dazu Shotcuts zu verwenden. Das Lesen der Tastatur-Scancodes und
- das direkte Schreiben in den Grafikspeicher sind zwei klassische
- Beispiele von Gewohnheiten, die unter
- <acronym>&ms-dos;</acronym> nicht verpönt sind, aber nicht
- als richtig angesehen werden.</para>
-
- <para>Warum dies? Sowohl das <acronym>PC-BIOS</acronym> als auch
- <acronym>&ms-dos;</acronym> sind notorisch langsam bei der
- Ausführung dieser Operationen.</para>
-
- <para>Sie mögen versucht sein ähnliche Angewohnheiten
- in der &unix;-Umgebung fortzuführen. Zum Beispiel habe ich
- eine Webseite gesehen, welche erklärt, wie man auf einem
- beliebten &unix;-Ableger die Tastatur-Scancodes
- verwendet.</para>
-
- <para>Das ist generell eine <emphasis>sehr schlechte
- Idee</emphasis> in einer &unix;-Umgebung! Lassen Sie mich
- erklären warum.</para>
-
- <sect2 id="x86-protected">
- <title>&unix; ist geschützt</title>
-
- <para>Zum Einen mag es schlicht nicht möglich sein.
- &unix; läuft im Protected Mode. Nur der Kernel und
- Gerätetreiber dürfen direkt auf die Hardware
- zugreifen. Unter Umständen erlaubt es Ihnen ein
- bestimmter &unix;-Ableger Tastatur-Scancodes auszulesen, aber
- ein wirkliches &unix;-Betriebssystem wird dies zu verhindern
- wissen. Und falls eine Version es Ihnen erlaubt wird es eine
- andere nicht tun, daher kann eine sorgfältig erstellte
- Software über Nacht zu einem überkommenen
- Dinosaurier werden.</para>
- </sect2>
-
- <sect2 id="x86-abstraction">
- <title>&unix; ist eine Abstraktion</title>
-
- <para>Aber es gibt einen viel wichtigeren Grund, weshalb Sie
- nicht versuchen sollten, die Hardware direkt anzusprechen
- (natürlich nicht, wenn Sie einen Gerätetreiber
- schreiben), selbst auf den &unix;-ähnlichen Systemen, die
- es Ihnen erlauben:</para>
-
- <para><emphasis>&unix; ist eine Abstraktion!</emphasis></para>
-
- <para>Es gibt einen wichtigen Unterschied in der
- Design-Philosophie zwischen <acronym>&ms-dos;</acronym> und
- &unix;. <acronym>&ms-dos;</acronym> wurde entworfen als
- Einzelnutzer-System. Es läuft auf einem Rechner mit einer
- direkt angeschlossenen Tastatur und einem direkt
- angeschlossenem Bildschirm. Die Eingaben des Nutzers kommen
- nahezu immer von dieser Tastatur. Die Ausgabe Ihres Programmes
- erscheint fast immer auf diesem Bildschirm.</para>
-
- <para>Dies ist NIEMALS garantiert unter &unix;. Es ist sehr
- verbreitet für ein &unix;, daß der Nutzer seine
- Aus- und Eingaben kanalisiert und umleitet:</para>
-
- <screen>&prompt.user; <userinput>program1 | program2 | program3 &gt; file1</userinput></screen>
-
- <para>Falls Sie eine Anwendung
- <application>program2</application> geschrieben haben, kommt
- ihre Eingabe nicht von der Tastatur, sondern von der Ausgabe
- von <application>program1</application>. Gleichermassen geht
- Ihre Ausgabe nicht auf den Bildschirm, sondern wird zur
- Eingabe für <application>program3</application>, dessen
- Ausgabe wiederum in <filename>file1</filename> endet.</para>
-
- <para>Aber es gibt noch mehr! Selbst wenn Sie sichergestellt
- haben, daß Ihre Eingabe und Ausgabe zum Terminal kommt
- bzw. gelangt, dann ist immer noch nicht garantiert, daß
- ihr Terminal ein PC ist: Es mag seinen Grafikspeicher nicht
- dort haben, wo Sie ihn erwarten, oder die Tastatur könnte
- keine <acronym>PC</acronym>-ähnlichen Scancodes erzeugen
- können. Es mag ein &macintosh; oder irgendein anderer
- Rechner sein.</para>
-
- <para>Sie mögen nun den Kopf schütteln: Mein
- Programm ist in <acronym>PC</acronym>-Assembler geschrieben,
- wie kann es auf einem &macintosh; laufen? Aber ich habe nicht
- gesagt, daß Ihr Programm auf &macintosh; läuft, nur
- sein Terminal mag ein &macintosh; sein.</para>
-
- <para>Unter &unix; muß der Terminal nicht direkt am
- Rechner angeschlossen sein, auf dem die Software läuft,
- er kann sogar auf einem anderen Kontinent sein oder sogar auf
- einem anderen Planeten. Es ist nicht ungewöhnlich,
- daß ein &macintosh;-Nutzer in Australien sich auf ein
- &unix;-System in Nordamerika (oder sonstwo) mittels
- <application>telnet</application> verbindet. Die Software
- läuft auf einem Rechner während das Terminal sich
- auf einem anderen Rechner befindet: Falls Sie versuchen
- sollten die Scancodes auszulesen werden Sie die falschen
- Eingaben erhalten!</para>
-
- <para>Das Gleiche gilt für jede andere Hardware: Eine
- Datei, welche Sie einlesen, mag auf einem Laufwerk sein, auf
- das Sie keinen direkten Zugriff haben. Eine Kamera, deren
- Bilder Sie auslesen, befindet sich möglicherweise in
- einem Space Shuttle, durch Satelliten mit Ihnen
- verbunden.</para>
-
- <para>Das sind die Gründe, weshalb Sie niemals unter
- &unix; Annahmen treffen dürfen, woher Ihre Daten kommen
- oder gehen. Lassen Sie immer das System den physischen Zugriff
- auf die Hardware regeln.</para>
-
- <note>
- <para>Das sind Vorsichtsmassnahmen, keine absoluten Regeln.
- Ausnahmen sind möglich. Wenn zum Beispiel ein
- Texteditor bestimmt hat, daß er auf einer lokalen
- Maschine läuft, dann mag er die Tastatur-Scancodes
- direkt auslesen, um eine bessere Kontrolle zu
- gewährleisten. Ich erwähne diese
- Vorsichtsmassnahmen nicht, um Ihnen zu sagen, was sie tun
- oder lassen sollen, ich will Ihnen nur bewusst machen,
- daß es bestimmte Fallstricke gibt, die Sie erwarten,
- wenn Sie soeben ihn &unix; von <acronym>&ms-dos;</acronym>
- angelangt sind. Kreative Menschen brechen oft Regeln und das
- ist in Ordnung, solange sie wissen welche Regeln und
- warum.</para>
- </note>
- </sect2>
- </sect1>
-
- <sect1 id="x86-acknowledgements">
- <sect1info>
- <authorgroup>
- <author>
- <firstname>Daniel</firstname>
- <surname>Seuffert</surname>
- <contrib>Übersetzt von </contrib>
- </author>
- </authorgroup>
- </sect1info>
-
- <title>Danksagungen</title>
-
- <para>Dieses Handbuch wäre niemals möglich gewesen
- ohne die Hilfe vieler erfahrener FreeBSD-Programmierer aus
- &a.hackers;. Viele dieser Personen haben geduldig meine Fragen
- beantwortet und mich in die richtige Richtung gewiesen bei
- meinem Versuch, die tieferen liegenden Mechanismen der
- &unix;-Systemprogrammierung zu erforschen im Allgemeinen und bei
- FreeBSD im Besonderen.</para>
-
- <para>Thomas M. Sommers öffnete die Türen für
- mich. Seine <ulink
- url="http://www.codebreakers-journal.com/content/view/262/27/">Wie
- schreibe ich "Hallo Welt" in FreeBSD-Assembler?</ulink> Webseite
- war mein erster Kontakt mit Assembler-Programmierung unter
- FreeBSD.</para>
-
- <para>Jake Burkholder hat die Tür offen gehalten durch das
- bereitwillige Beantworten all meiner Fragen und das
- Zurverfügungstellen von Assembler-Codebeispielen.</para>
-
- <para>Copyright &copy; 2000-2001 G. Adam Stanislav. Alle Rechte
- vorbehalten.</para>
- </sect1>
-</chapter>