6100 lines
186 KiB
XML
6100 lines
186 KiB
XML
<?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>
|