diff options
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.sgml | 6100 |
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 © 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 - µsoft;-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 - — 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 µsoft.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 > markiert (kopieren Sie die - > 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—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>—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 '<?xml version="1.0" encoding="utf-8"?>', 0Ah - db '<!DOCTYPE html PUBLIC "-//W3C/DTD XHTML Strict//EN" ' - db '"DTD/xhtml1-strict.dtd">', 0Ah - db '<html xmlns="http://www.w3.org/1999/xhtml" ' - db 'xml.lang="en" lang="en">', 0Ah - db '<head>', 0Ah - db '<title>Web Environment</title>', 0Ah - db '<meta name="author" content="G. Adam Stanislav" />', 0Ah - db '</head>', 0Ah, 0Ah - db '<body bgcolor="#ffffff" text="#000000" link="#0000ff" ' - db 'vlink="#840084" alink="#0000ff">', 0Ah - db '<div class="webvars">', 0Ah - db '<h1>Web Environment</h1>', 0Ah - db '<p>The following <b>environment variables</b> are defined ' - db 'on this web server:</p>', 0Ah, 0Ah - db '<table align="center" width="80" border="0" cellpadding="10" ' - db 'cellspacing="0" class="webvars">', 0Ah -httplen equ $-http -left db '<tr>', 0Ah - db '<td class="name"><tt>' -leftlen equ $-left -middle db '</tt></td>', 0Ah - db '<td class="value"><tt><b>' -midlen equ $-middle -undef db '<i>(undefined)</i>' -undeflen equ $-undef -right db '</b></tt></td>', 0Ah - db '</tr>', 0Ah -rightlen equ $-right -wrap db '</table>', 0Ah - db '</div>', 0Ah - db '</body>', 0Ah - db '</html>', 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 – - 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—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 – 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<delim>] [-c<comma>] [-p] [-o <outfile>] [-i <infile>]</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<delim>] [-c<comma>] [-p] [-o <outfile>] [-i <infile>]', 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, (',' << 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 << 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–bit - Fließkomma-Registern. Diese sind in Form eines - Stacks organisiert—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>—wie z.B. - <emphasis>Pi</emphasis>—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–bit, 64–bit oder - 80–bit <emphasis>real</emphasis>, oder als 16–bit, - 32–bit oder 64–bit <emphasis>Integer</emphasis>, - oder als 80–bit <emphasis>packed decimal</emphasis> - übertragen werden.</para> - - <para>Das 80–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–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–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>– <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>– <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–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—falls Sie es finden - können—ist Richard Startz' <ulink - url="http://www.int80h.org/cgi-bin/isbn?isbn=013246604X"> - 8087/80287/80387 for the IBM PC & Compatibles</ulink>. - Obwohl es anscheinend die Speicherung der <emphasis>packed - decimal</emphasis> im little–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–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–Wert</title> - - <para>Der f–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–mm- oder - eine 6x9cm-Kamera ist, usw. Solange wir den f–Wert - kennen, können wir die benötigte Belichtungszeit - berechnen.</para> - - <para>Der f–Wert läßt sich einfach wie folgt - berechnen:</para> - - <programlisting> F = FL / D</programlisting> - - <para>Mit anderen Worten, der f–Wert ergibt sich aus - der Brennweite (FL), dividiert durch den Durchmesser (D) der - Lochblende. Ein großer f–Wert impliziert also - entweder eine kleine Lochblende, oder eine große - Brennweite, oder beides. Je größer also der - f–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–Wert <varname>A</varname> - eine Belichtungsdauer <varname>t</varname> bestimmt hat, - dann ergibt sich daraus für einen f–Wert - <varname>B</varname> eine Belichtungszeit von:</para> - - <programlisting> t * (B / A)²</programlisting> - </sect3> - - <sect3 id="x86-normalized-f-number"> - <title>Normalisierte f–Werte</title> - - <para>Während heutige moderne Kameras den Durchmesser - der Lochblende, und damit deren f–Wert, weich und - schrittweise verändern können, war dies - früher nicht der Fall.</para> - - <para>Um unterschiedliche f–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–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–Werte 8, 11 und 16.</para> - - <para>Eine neuere Kamera könnte f–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–Stopp</title> - - <para>Eine typische Kamera ist so konzipiert, daß die - Nummernscheibe bei den normalisierten f–Werten - einrastet. Die Nummernscheibe <emphasis>stoppt</emphasis> - an diesen Positionen. Daher werden diese Positionen auch - f–Stopps genannt.</para> - - <para>Da die f–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—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–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–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— - 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—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–Wert</emphasis> zu bestimmen.</para> - - <para>Wir werden den f–Wert, auf vier signifikante - Stellen gerundet, ausgeben. Es könnte passieren, - daß diese vier Stellen recht wenig aussagen. Um die - Aussagekraft des f–Wertes zu erhöhen, könnten - wir den nächstliegenden, <emphasis>normalisierten - f–Wert</emphasis> bestimmen, also z.B. das - nächstliegende Vielfache der Quadratwurzel aus 2.</para> - - <para>Wir erreichen dies, indem wir den aktuellen f–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–Wert.</para> - - <para>Wenn das alles jetzt viel zu kompliziert wirkt—oder - viel zu aufwendig—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–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–Wertes.</para> - - <para>Wir haben nun den nächstliegenden, normalisierten - f–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–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–Wert. Der neue - <varname role="register">st(1)</varname> speichert das - Quadrat des aktuellen f–Wertes für die - Nachwelt.</para> - - <para>Jetzt können wir den normalisierten f–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–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–Wert mit Hilfe eines anderen - f–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–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–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–Werte durch den konstanten Wert - <constant>5.6²</constant> dividieren. Oder wir - können den jeweiligen f–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–Wertes durch - <constant>32</constant> zu teilen. Wir müssen lediglich - mittels <function role="opcode">fscale</function> den - f–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–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–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–Wert von f5.6 entfernt ist. Dies könnte - hilfreich sein, wenn unser f–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–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–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–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–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>—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 <value>] [-o <outfile>] [-i <infile>]', 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> - –zu–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—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–Endian- - und Little–Endian-Codierung aufgerufen.</para> - - <para>Diese konvertiert die Textrepräsentation einer - Zahl in eine andere Textrepräsentation: Der Text wird - in der Big–Endian-Codierung gespeichert, die - <emphasis>packed decimal</emphasis>-Darstellung jedoch in der - Little–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—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 > 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 > 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 © 2000-2001 G. Adam Stanislav. Alle Rechte - vorbehalten.</para> - </sect1> -</chapter> |