<?xml version="1.0" encoding="iso-8859-1" standalone="no"?>
<!--
     The FreeBSD Documentation Project
     The FreeBSD German Documentation Project

     This file is automatically generated. Please do not make commits
     to this file. Updates should be sent to the author :

     G. Adam Stanislav (adam@redprince.net)

     This chapter is an exception to our general rule, and the author
     retains the copyright. Among other things, this means that this
     chapter should not be included in any printed version of the
     Developer's Handbook without Adam's explicit permission.

     Eventually we will have to replace this chapter or convince the
     author to assign us the copyright. For now, it is valuable
     content so it should stay.

     $FreeBSD$
     $FreeBSDde: de-docproj/books/developers-handbook/x86/chapter.sgml,v 1.24 2010/12/15 19:03:52 bcr Exp $
     basiert auf: 1.19
-->

<chapter id="x86">
  <title>x86-Assembler-Programmierung</title>

  <para><emphasis>Dieses Kapitel wurde geschrieben von
    &a.stanislav;.</emphasis></para>

  <sect1 id="x86-intro">
    <sect1info>
      <authorgroup>
	<author>
	  <firstname>Hagen</firstname>
	  <surname>K�hl</surname>
	  <contrib>�bersetzt von </contrib>
	</author>
      </authorgroup>
    </sect1info>

    <title>Synopsis</title>

    <para>Assembler-Programmierung unter &unix; ist h�chst
      undokumentiert. Es wird allgemein angenommen, dass niemand sie
      jemals benutzen will, da &unix;-Systeme auf verschiedenen
      Mikroprozessoren laufen, und man deshalb aus Gr�nden der
      Portabilit�t alles in C schreiben sollte.</para>

    <para>In Wirklichkeit ist die Portabilit�t von C
      gr��tenteils ein Mythos. Auch C-Programme m�ssen
      angepasst werden, wenn man sie von einem &unix; auf ein anderes
      portiert, egal auf welchem Prozessor jedes davon l�uft.
      Typischerweise ist ein solches Programm voller Bedingungen, die
      unterscheiden f�r welches System es kompiliert wird.</para>

    <para>Sogar wenn wir glauben, dass jede &unix;-Software in C, oder
      einer anderen High-Level-Sprache geschrieben werden sollte,
      brauchen wir dennoch Assembler-Programmierer: Wer sonst sollte
      den Abschnitt der C-Bibliothek schreiben, die auf den Kernel
      zugreift?</para>

    <para>In diesem Kapitel m�chte ich versuchen zu zeigen, wie
      man Assembler-Sprache verwenden kann, um &unix;-Programme,
      besonders unter FreeBSD, zu schreiben.</para>

    <para>Dieses Kapitel erkl�rt nicht die Grundlagen der
      Assembler-Sprache. Zu diesem Thema gibt es bereits genug Quellen
      (einen vollst�ndigen Online-Kurs finden Sie in Randall
      Hydes <ulink url="http://webster.cs.ucr.edu/">Art of Assembly
      Language</ulink>; oder falls Sie ein gedrucktes Buch bevorzugen,
      k�nnen Sie einen Blick auf Jeff Duntemanns <ulink
      url="http://www.int80h.org/cgi-bin/isbn?isbn=0471375233">Assembly
      Language Step-by-Step</ulink> werfen). Jedenfalls sollte jeder
      Assembler-Programmierer nach diesem Kapitel schnell und
      effizient Programme f�r FreeBSD schreiben
      k�nnen.</para>

    <para>Copyright &copy; 2000-2001 G. Adam Stanislav. All rights
      reserved.</para>
  </sect1>

  <sect1 id="x86-the-tools">
    <sect1info>
      <authorgroup>
	<author>
	  <firstname>Hagen</firstname>
	  <surname>K�hl</surname>
	  <contrib>�bersetzt von </contrib>
	</author>
      </authorgroup>
    </sect1info>

    <title>Die Werkzeuge</title>

    <sect2 id="x86-the-assembler">
      <title>Der Assembler</title>

      <para>Das wichtigste Werkzeug der Assembler-Programmierung ist
	der Assembler, diese Software �bersetzt Assembler-Sprache
	in Maschinencode.</para>

      <para>F�r FreeBSD stehen zwei verschiedene Assembler zur
	Verf�gung. Der erste ist
	<citerefentry><refentrytitle>as</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
	der die traditionelle &unix;-Assembler-Sprache verwendet.
	Dieser ist Teil des Systems.</para>

      <para>Der andere ist
	<application>/usr/ports/devel/nasm</application>. Dieser
	benutzt die Intel-Syntax und sein Vorteil ist, dass es Code
	f� viele Vetriebssysteme �bersetzen kann. Er muss
	gesondert installiert werden, aber ist v�llig
	frei.</para>

      <para>In diesem Kapitel wird die
	<application>nasm</application>-Syntax verwendet. Einerseits
	weil es die meisten Assembler-Programmierer, die von anderen
	Systemen zu FreeBSD kommen, leichter verstehen werden. Und
	offen gesagt, weil es das ist, was ich gewohnt bin.</para>
    </sect2>

    <sect2 id="x86-the-linker">
      <title>Der Linker</title>

      <para>Die Ausgabe des Assemblers muss, genau wie der Code jedes
	Compilers, gebunden werden, um eine ausf�hrbare Datei zu
	bilden.</para>

      <para>Der Linker
	<citerefentry><refentrytitle>ld</refentrytitle><manvolnum>1</manvolnum></citerefentry>
	ist der Standard und Teil von FreeBSD. Er funktioniert mit dem
	Code beider Assembler.</para>
    </sect2>
  </sect1>

  <sect1 id="x86-system-calls">
    <sect1info>
      <authorgroup>
	<author>
	  <firstname>Hagen</firstname>
	  <surname>K�hl</surname>
	  <contrib>�bersetzt von </contrib>
	</author>
      </authorgroup>
    </sect1info>

    <title>Systemaufrufe</title>

    <sect2 id="x86-default-calling-convention">
      <title>Standard-Aufrufkonvention</title>

      <para>Standardm��ig benutzt der FreeBSD-Kernel die
	C-Aufrufkonvention. Weiterhin wird, obwohl auf den Kernel
	durch <function role="opcode">int 80h</function> zugegriffen
	wird, angenommen, dass das Programm eine Funktion aufruft, die
	<function role="opcode">int 80h</function> verwendet, anstatt
	<function role="opcode">int 80h</function> direkt
	aufzurufen.</para>

      <para>Diese Konvention ist sehr praktisch und der
	&microsoft;-Konvention von <acronym>&ms-dos;</acronym>
	�berlegen. Warum? Weil es die &unix;-Konvention jedem
	Programm, egal in welcher Sprache es geschrieben ist, erlaubt
	auf den Kernel zuzugreifen.</para>

      <para>Ein Assembler-Programm kann das ebenfalls. Beispielsweise
	k�nnten wir eine Datei �ffnen:</para>

      <programlisting>kernel:
	int	80h	; Call kernel
	ret

open:
	push	dword mode
	push	dword flags
	push	dword path
	mov	eax, 5
	call	kernel
	add	esp, byte 12
	ret</programlisting>

      <para>Das ist ein sehr sauberer und portabler Programmierstil.
	Wenn Sie das Programm auf ein anderes &unix; portieren, das
	einen anderen Interrupt oder eie andere Art der
	Parameter�bergabe verwendet, m�ssen sie nur die
	Prozedur kernel �ndern.</para>

      <para>Aber Assembler-Programmierer lieben es Taktzyklen zu
	schinden. Das obige Beispiel ben�tigt eine <function
	role="opcode">call/ret</function>-Kombination. Das k�nnen
	wir entfernen, indem wir einen weiteren Parameter mit
	<function role="opcode">push</function> �bergeben:</para>

      <programlisting>open:
	push	dword mode
	push	dword flags
	push	dword path
	mov	eax, 5
	push	eax		; Or any other dword
	int	80h
	add	esp, byte 16</programlisting>

      <para>Die Konstante <constant>5</constant>, die wir in <varname
	role="register">EAX</varname> ablegen, identifiziert die
	Kernel-Funktion, die wir aufrufen. In diesem Fall ist das
	<function role="syscall">open</function>.</para>
    </sect2>

    <sect2 id="x86-alternate-calling-convention">
      <title>Alternative Aufruf-Konvention</title>

      <para>FreeBSD ist ein extrem flexibles System. Es bietet noch
	andere Wege, um den Kernel aufzurufen. Damit diese
	funktionieren muss allerdings die Linux-Emulation installiert
	sein.</para>

      <para>Linux ist ein &unix;-artiges System. Allerdings verwendet
	dessen Kernel die gleiche Systemaufruf-Konvention, bei der
	Parameter in Registern abgelegt werden, wie
	<acronym>&ms-dos;</acronym>. Genau wie bei der
	&unix;-Konvention wird die Nummer der Funktion in <varname
	role="register">EAX</varname> abgelegt. Allerdings werden die
	Parameter nicht auf den Stack gelegt, sondern in die Register
	<varname role="register">EBX, ECX, EDX, ESI, EDI,
	EBP</varname>:</para>

      <programlisting>open:
	mov	eax, 5
	mov	ebx, path
	mov	ecx, flags
	mov	edx, mode
	int	80h</programlisting>

      <para>Diese Konvention hat einen gro�en Nachteil
	gegen�ber der von &unix;, was die
	Assembler-Programmierung angeht: Jedesmal, wenn Sie einen
	Kernel-Aufruf machen, m�ssen Sie die Register <function
	role="opcode">push</function>en und sie sp�ter <function
	role="opcode">pop</function>en. Das macht Ihren Code
	unf�rmiger und langsamer. Dennoch l�sst FreeBSD
	ihnen die Wahl.</para>

      <para>Wenn Sie sich f�r die Linux-Konvention entscheiden,
	m�ssen Sie es das System wissen lassen. Nachdem ihr
	Programm �bersetzt und gebunden wurde, m�ssen Sie
	die ausf�hrbare Datei kennzeichnen:</para>

      <screen>&prompt.user;
	<userinput>brandelf -t Linux
	<replaceable>filename</replaceable></userinput></screen>
    </sect2>

    <sect2 id="x86-use-geneva">
      <title>Welche Konvention Sie verwenden sollten</title>

      <para>Wenn Sie speziell f�r FreeBSD programmieren, sollten
	Sie die &unix;-Konvention verwenden: Diese ist schneller, Sie
	k�nnen globale Variablen in Registern ablegen, Sie
	m�ssen die ausf�hrbare Datei nicht kennzeichnen und
	Sie erzwingen nicht die Installation der Linux-Emulation auf
	dem Zielsystem.</para>

      <para>Wenn Sie portablen Programmcode erzeugen wollen, der auch
	unter Linux funktioniert, wollen Sie den FreeBSD-Nutzern
	vielleicht dennoch den effizientesten Programmcode bieten, der
	m�glich ist. Ich werde Ihnen zeigen, wie Sie das
	erreichen k�nnen, nachdem ich die Grundlagen erkl�rt
	habe.</para>
    </sect2>

    <sect2 id="x86-call-numbers">
      <title>Aufruf-Nummern</title>

      <para>Um dem Kernel mitzuteilen welchen Dienst Sie aufrufen,
	legen Sie dessen Nummer in <varname
	role="register">EAX</varname> ab. Nat�rlich m�ssen
	Sie dazu wissen welche Nummer die Richtige ist.</para>

      <sect3 id="x86-the-syscalls-file">
	<title>Die Datei <filename>syscalls</filename></title>

	<para>Die Nummer der Funktionen sind in der Datei
	  <filename>syscalls</filename> aufgef�hrt. Mittels
	  <command>locate syscalls</command> finden Sie diese in
	  verschiedenen Formaten, die alle auf die gleiche Weise aus
	  <filename>syscalls.master</filename> erzeugt werden.</para>

	<para>Die Master-Datei f�r die
	  &unix;-Standard-Aufrufkonvention finden sie unter
	  <filename>/usr/src/sys/kern/syscalls.master</filename>.
	  Falls Sie die andere Konvention, die im
	  Linux-Emulations-Modus implementiert ist, verwenden
	  m�chten, lesen Sie bitte
	  <filename>/usr/src/sys/i386/linux/syscalls.master</filename>.</para>

	<note><para>FreeBSD und Linux unterscheiden sich nicht nur in
	  den Aufrufkonventionen, sie haben teilweise auch
	  verschiedene Nummern f�r die gleiche
	  Funktion.</para></note>

	<para><filename>syscalls.master</filename> beschreibt, wie der
	  Aufruf gemacht werden muss:</para>

	<programlisting>0	STD	NOHIDE	{ int nosys(void); } syscall nosys_args int
1	STD	NOHIDE	{ void exit(int rval); } exit rexit_args void
2	STD	POSIX	{ int fork(void); }
3	STD	POSIX	{ ssize_t read(int fd, void *buf, size_t nbyte); }
4	STD	POSIX	{ ssize_t write(int fd, const void *buf, size_t nbyte); }
5	STD	POSIX	{ int open(char *path, int flags, int mode); }
6	STD	POSIX	{ int close(int fd); }
etc...</programlisting>

	<para>In der ersten Spalte steht die Nummer, die in <varname
	  role="register">EAX</varname> abgelegt werden muss.</para>

	<para>Die Spalte ganz rechts sagt uns welche Parameter wir
	  <function role="opcode">push</function>en m�ssen. Die
	  Reihenfolge ist dabei <emphasis>von rechts nach
	  links</emphasis>.</para>

	<informalexample><para>Um beispielsweise eine Datei mittels
	  <function>open</function> zu �ffnen, m�ssen wir
	  zuerst den <varname>mode</varname> auf den Stack <function
	  role="opcode">push</function>en, danach die
	  <varname>flags</varname>, dann die Adresse an der der
	  <varname>path</varname> gespeichert
	  ist.</para></informalexample>

      </sect3>
    </sect2>
  </sect1>

  <sect1 id="x86-return-values">
    <sect1info>
      <authorgroup>
	<author>
	  <firstname>Hagen</firstname>
	  <surname>K�hl</surname>
	  <contrib>�bersetzt von </contrib>
	</author>
      </authorgroup>
    </sect1info>

    <title>R�ckgabewerte</title>

    <para>Ein Systemaufruf w�re meistens nicht sehr
      n�tzlich, wenn er nicht irgendeinen Wert zur�ckgibt:
      Beispielsweise den Dateideskriptor einer ge�ffneten Datei,
      die Anzahl an Bytes die in einen Puffer gelesen wurde, die
      Systemzeit, etc.</para>

    <para>Au�erdem muss Sie das System informieren, falls ein
      Fehler auftritt: Wenn eine Datei nicht existiert, die
      Systemressourcen ersch�pft sind, wir ein ung�ltiges
      Argument �bergeben haben, etc.</para>

    <sect2 id="x86-man-pages">
      <title>Manualpages</title>

      <para>Der herk�mmliche Ort, um nach Informationen �ber
	verschiedene Systemaufrufe unter &unix;-Systemen zu suchen,
	sind die Manualpages. FreeBSD beschreibt seine Systemaufrufe
	in Abschnitt 2, manchmal auch Abschnitt 3.</para>

      <para>In
	<citerefentry><refentrytitle>open</refentrytitle><manvolnum>2</manvolnum></citerefentry>
	steht beispielsweise:</para>

      <blockquote><para>Falls erfolgreich, gibt
	<function>open()</function> einen nicht negativen Integerwert,
	als Dateideskriptor bezeichnet, zur�ck. Es gibt
	<varname>-1</varname> im Fehlerfall zur�ck und setzt
	<varname>errno</varname> um den Fehler
	anzuzeigen.</para></blockquote>

      <para>Ein Assembler-Programmierer, der neu bei &unix; und
	FreeBSD ist, wird sich sofort fragen: Wo finde ich
	<varname>errno</varname> und wie erreiche ich es?</para>

      <note><para>Die Information der Manualpage bezieht sich auf
	C-Programme. Der Assembler-Programmierer ben�tigt
	zus�tzliche Informationen.</para></note>
    </sect2>

    <sect2 id="x86-where-return-values">
      <title>Wo sind die R�ckgabewerde?</title>

      <para>Leider gilt: Es kommt darauf an... F�r die meisten
	Systemaufrufe liegt er in <varname
	role="register">EAX</varname>, aber nicht f�r alle. Eine
	gute Daumenregel, wenn man zum ersten Mal mit einem
	Systemaufruf arbeitet, ist in <varname
	role="register">EAX</varname> nach dem R�ckgabewert zu
	suchen. Wenn er nicht dort ist, sind weitere Untersuchungen
	n�tig.</para>

      <note><para>Mir ist ein Systemaufruf bekannt, der den
	R�ckgabewert in <varname role="register">EDX</varname>
	ablegt: <function role="syscall">SYS_fork</function> Alle
	anderen mit denen ich bisher gearbeitet habe verwenden
	<varname role="register">EAX</varname>. Allerdings habe ich
	noch nicht mit allen gearbeitet.</para></note>

      <tip><para>Wenn Sie die Antwort weder hier, noch irgendwo anders
	finden, studieren Sie den Quelltext von
	<application>libc</application> und sehen sich an, wie es mit
	dem Kernel zusammenarbeitet.</para></tip>
    </sect2>

    <sect2 id="x86-where-errno">
      <title>Wo ist <varname>errno</varname>?</title>

      <para>Tats�chlich, nirgendwo...</para>

      <para><varname>errno</varname> ist ein Teil der Sprache C, nicht
	des &unix;-Kernels. Wenn man direkt auf Kernel-Dienste
	zugreift, wird der Fehlercode in <varname
	role="register">EAX</varname> zur�ckgegeben, das selbe
	Register in dem der R�ckgabewert, bei einem erfolgreichen
	Aufruf landet.</para>

      <para>Das macht auch Sinn. Wenn kein Fehler auftritt, gibt es
	keinen Fehlercode. Wenn ein Fehler auftritt, gibt es keinen
	R�ckgabewert. Ein einziges Register kann also beides
	enthalten.</para>
    </sect2>

    <sect2 id="x86-how-to-know-error">
      <title>Feststellen, dass ein Fehler aufgetreten ist</title>

      <para>Wenn Sie die Standard FreeBSD-Aufrufkonvention verwenden
	wird das <varname role="register">carry flag</varname>
	gel�scht wenn der Aufruf erfolgreich ist und gesetzt wenn
	ein Fehler auftritt.</para>

      <para>Wenn Sie den Linux-Emulationsmodus verwenden ist der
	vorzeichenbehaftete Wert in <varname
	role="register">EAX</varname> nicht negativ, bei einem
	erfolgreichen Aufruf. Wenn ein Fehler auftritt ist der Wert
	negativ, also <varname>-errno</varname>.</para>
    </sect2>
  </sect1>

  <sect1 id="x86-portable-code">
    <sect1info>
      <authorgroup>
	<author>
	  <firstname>Hagen</firstname>
	  <surname>K�hl</surname>
	  <contrib>�bersetzt von </contrib>
	</author>
      </authorgroup>
    </sect1info>

    <title>Portablen Code erzeugen</title>

    <para>Portabilit�t ist im Allgemeinen keine St�rke der
      Assembler-Programmierung. Dennoch ist es, besonders mit
      <application>nasm</application>, m�glich
      Assembler-Programme f�r verschiedene Plattformen zu
      schreiben. Ich selbst habe bereits Assembler-Bibliotheken
      geschrieben die auf so unterschiedlichen Systemen wie &windows;
      und FreeBSD �bersetzt werden k�nnen.</para>

    <para>Das ist um so besser m�glich, wenn Ihr Code auf zwei
      Plattformen laufen soll , die, obwohl sie verschieden sind, auf
      �hnlichen Architekturen basieren.</para>

    <para>Beispielsweise ist FreeBSD ein &unix;, w�hrend Linux
      &unix;-artig ist. Ich habe bisher nur drei Unterschiede zwischen
      beiden (aus Sicht eines Assembler-Programmierers) erw�hnt:
      Die Aufruf-Konvention, die Funktionsnummern und die Art der
      �bergabe von R�ckgabewerten.</para>

    <sect2 id="x86-deal-with-function-numbers">
      <title>Mit Funktionsnummern umgehen</title>

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

      <programlisting>%ifdef	LINUX
%define	SYS_execve	11
%else
%define	SYS_execve	59
%endif</programlisting>
    </sect2>

    <sect2 id="x86-deal-with-geneva">
      <title>Umgang mit Konventionen</title>

      <para>Sowohl die Aufrufkonvention, als auch die
	R�ckgabewerte (das <varname>errno</varname> Problem) kann
	man mit Hilfe von Makros l�sen:</para>

      <programlisting>%ifdef	LINUX

%macro	system	0
	call	kernel
%endmacro

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

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

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

	or	eax, eax
	js	.errno
	clc
	ret

.errno:
	neg	eax
	stc
	ret

%else

%macro	system	0
	int	80h
%endmacro

%endif</programlisting>
    </sect2>

    <sect2 id="x86-deal-with-other-portability">
      <title>Umgang mit anderen
	Portabilit�tsangelegenheiten</title>

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

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

    <sect2 id="x86-portable-library">
      <title>Eine Bibliothek benutzen</title>

      <para>Sie k�nnen Portabilit�tsprobleme im Hauptteil
	ihres Codes komplett vermeiden, indem Sie eine Bibliothek
	f�r Systemaufrufe schreiben. Erstellen Sie eine
	Bibliothek f�r FreeBSD, eine f�r Linux und weitere
	f�r andere Betriebssysteme.</para>

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

      <programlisting>sys.open:
sys.close:
[etc...]
	int	80h
	ret</programlisting>

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

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

...

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

sys.err:
	neg	eax
	stc
	ret</programlisting>

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

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

    <sect2 id="x86-portable-include">
      <title>Eine Include-Datei verwenden</title>

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

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

      <note>
	<para>Das ist der Ansatz den wir in diesem Kapitel verwenden
	  werden. Wir werden unsere Include-Datei
	  <filename>system.inc</filename> nennen und jedesmal, wenn
	  wir einen neuen Systemaufruf verwenden, den entsprechenden
	  Code dort einf�gen.</para>
      </note>

      <para>Wir k�nnen unsere <filename>system.inc</filename>
	beginnen indem wir die Standard-Dateideskriptoren
	deklarieren:</para>

      <programlisting>%define	stdin	0
%define	stdout	1
%define	stderr	2</programlisting>

      <para>Als N�chstes erzeugen wir einen symbolischen Namen
	f�r jeden Systemaufruf:</para>

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

      <para>Wir f�gen eine kleine, nicht globale Prozedur mit
	langem Namen ein, damit wir den Namen nicht aus Versehen in
	unserem Code wiederverwenden:</para>

      <programlisting>section	.text
align 4
access.the.bsd.kernel:
	int	80h
	ret</programlisting>

      <para>Wir erzeugen ein Makro, das ein Argument erwartet, die
	Systemaufruf-Nummer:</para>

      <programlisting>%macro	system	1
	mov	eax, %1
	call	access.the.bsd.kernel
%endmacro</programlisting>

      <para>Letztlich erzeugen wir Makros f�r jeden Systemaufruf.
	Diese Argumente erwarten keine Argumente.</para>

      <programlisting>%macro	sys.exit	0
	system	SYS_exit
%endmacro

%macro	sys.fork	0
	system	SYS_fork
%endmacro

%macro	sys.read	0
	system	SYS_read
%endmacro

%macro	sys.write	0
	system	SYS_write
%endmacro

; [etc...]</programlisting>

      <para>Fahren Sie fort, geben das in Ihren Editor ein und
	speichern es als <filename>system.inc</filename>. Wenn wir
	Systemaufrufe besprechen, werden wir noch Erg�nzungen in
	dieser Datei vornehmen.</para>

    </sect2>
  </sect1>

  <sect1 id="x86-first-program">
    <sect1info>
      <authorgroup>
	<author>
	  <firstname>Hagen</firstname>
	  <surname>K�hl</surname>
	  <contrib>�bersetzt von </contrib>
	</author>
      </authorgroup>
    </sect1info>

    <title>Unser erstes Programm</title>

    <para>Jetzt sind wir bereit f�r unser erstes Programm, das
      �bliche <application>Hello, World!</application></para>

    <programlisting> 1:	%include	'system.inc'
 2:
 3:	section	.data
 4:	hello	db	'Hello, World!', 0Ah
 5:	hbytes	equ	$-hello
 6:
 7:	section	.text
 8:	global	_start
 9:	_start:
10:	push	dword hbytes
11:	push	dword hello
12:	push	dword stdout
13:	sys.write
14:
15:	push	dword 0
16:	sys.exit</programlisting>

    <para>Hier folgt die Erkl�rung des Programms: Zeile 1
      f�gt die Definitionen ein, die Makros und den Code aus
      <filename>system.inc</filename>.</para>

    <para>Die Zeilen 3 bis 5 enthalten die Daten: Zeile 3 beginnt den
      Datenabschnitt/das Datensegment. Zeile 4 enth�lt die
      Zeichenkette "Hello, World!", gefolgt von einem Zeilenumbruch
      (<constant>0Ah</constant>). Zeile 5 erstellt eine Konstante, die
      die L�nge der Zeichenkette aus Zeile 4 in Bytes
      enth�lt.</para>

    <para>Die Zeilen 7 bis 16 enthalten den Code. Beachten Sie
      bitte, dass FreeBSD das Dateiformat <emphasis>elf</emphasis>
      f�r diese ausf�hrbare Datei verwendet, bei dem jedes
      Programm mit dem Label <varname>_start</varname> beginnt (oder,
      um genau zu sein, wird dies vom Linker erwartet). Diese Label
      muss global sein.</para>

    <para>Die Zeilen 10 bis 13 weisen das System an
      <varname>hbytes</varname> Bytes der Zeichenkette
      <varname>hello</varname> nach <varname>stdout</varname> zu
      schreiben.</para>

    <para>Die Zeilen 15 und 16 weisen das System an das Programm mit
      dem R�ckgabewert <constant>0</constant> zu beenden. Der
      Systemaufruf <function role="syscall">SYS_exit</function> kehrt
      niemals zur�ck, somit endet das Programm hier.</para>

    <note>
      <para>Wenn Sie von <acronym>&ms-dos;</acronym>-Assembler zu
	&unix; gekommen sind, sind Sie es vielleicht gewohnt direktauf
	die Video-Hardware zu schreiben. Unter FreeBSD m�ssen Sie
	sich darum keine Gedanken machen, ebenso bei jeder anderen Art
	von &unix;. Soweit es Sie betrifft schreiben Sie in eine Datei
	namens <filename>stdout</filename>. Das kann der Bildschirm,
	oder ein <application>telnet</application>-Terminal, eine
	wirkliche Datei, oder die Eingabe eines anderen Programms
	sein. Es liegt beim System herauszufinden, welches davon es
	tats�chlich ist.</para>
    </note>

    <sect2 id="x86-assemble-1">
      <title>Den Code assemblieren</title>

      <para>Geben Sie den Code (au�er den Zeilennummern) in
	einen Editor ein und speichern Sie ihn in einer Datei namens
	<filename>hello.asm</filename>. Um es zu assemblieren
	ben�tigen Sie <application>nasm</application>.</para>

      <sect3 id="x86-get-nasm">
	<title><application>nasm</application> installieren</title>

	<para>Wenn Sie <application>nasm</application> noch nicht
	  installiert haben geben Sie folgendes ein:</para>

	<screen>&prompt.user; <userinput>su</userinput>
Password:<userinput><replaceable>your root password</replaceable></userinput>
&prompt.root; <userinput>cd /usr/ports/devel/nasm</userinput>
&prompt.root; <userinput>make install</userinput>
&prompt.root; <userinput>exit</userinput>
&prompt.user;</screen>

	<para>Sie k�nnen auch <userinput>make install
	  clean</userinput> anstatt <userinput>make
	  install</userinput> eingeben, wenn Sie den Quelltext von
	  <application>nasm</application> nicht behalten
	  m�chten.</para>

	<para>Auf jeden Fall wird FreeBSD
	  <application>nasm</application> automatisch aus dem Internet
	  herunterladen, es kompilieren und auf Ihrem System
	  installieren.</para>

	<note>
	  <para>Wenn es sich bei Ihrem System nicht um FreeBSD
	    handelt, m�ssen Sie <application>nasm</application>
	    von dessen <ulink
	    url="https://sourceforge.net/projects/nasm">Homepage</ulink>
	    herunterladen. Sie k�nnen es aber dennoch verwenden
	    um FreeBSD code zu assemblieren.</para>
	</note>

	<para>Nun k�nnen Sie den Code assemblieren, binden und
	  ausf�hren:</para>

	<screen>&prompt.user; <userinput>nasm -f elf hello.asm</userinput>
&prompt.user; <userinput>ld -s -o hello hello.o</userinput>
&prompt.user; <userinput>./hello</userinput>
Hello, World!
&prompt.user;</screen>
      </sect3>
    </sect2>
  </sect1>

  <sect1 id="x86-unix-filters">
    <sect1info>
      <authorgroup>
	<author>
	  <firstname>Hagen</firstname>
	  <surname>K�hl</surname>
	  <contrib>�bersetzt von </contrib>
	</author>
      </authorgroup>
    </sect1info>

    <title>&unix;-Filter schreiben</title>

    <para>Ein h�ufiger Typ von &unix;-Anwendungen ist ein Filter
      &mdash; ein Programm, das Eingaben von
      <filename>stdin</filename> liest, sie verarbeitet und das
      Ergebnis nach <filename>stdout</filename> schreibt.</para>

    <para>In diesem Kapitel m�chten wir einen einfachen Filter
      entwickeln und lernen, wie wir von <filename>stdin</filename>
      lesen und nach <filename>stdout</filename> schreiben. Dieser
      Filter soll jedes Byte seiner Eingabe in eine hexadezimale Zahl
      gefolgt von einem Leerzeichen umwandeln.</para>

    <programlisting>%include	'system.inc'

section	.data
hex	db	'0123456789ABCDEF'
buffer	db	0, 0, ' '

section	.text
global	_start
_start:
	; read a byte from stdin
	push	dword 1
	push	dword buffer
	push	dword stdin
	sys.read
	add	esp, byte 12
	or	eax, eax
	je	.done

	; convert it to hex
	movzx	eax, byte [buffer]
	mov	edx, eax
	shr	dl, 4
	mov	dl, [hex+edx]
	mov	[buffer], dl
	and	al, 0Fh
	mov	al, [hex+eax]
	mov	[buffer+1], al

	; print it
	push	dword 3
	push	dword buffer
	push	dword stdout
	sys.write
	add	esp, byte 12
	jmp	short _start

.done:
	push	dword 0
	sys.exit</programlisting>

    <para>Im Datenabschnitt erzeugen wir ein Array mit Namen
      <varname>hex</varname>. Es enth�lt die 16 hexadezimalen
      Ziffern in aufsteigender Reihenfolge. Diesem Array folgt ein
      Puffer, den wir sowohl f�r die Ein- als auch f�r die
      Ausgabe verwenden. Die ersten beiden Bytes dieses Puffers werden
      am Anfang auf <constant>0</constant> gesetzt. Dorthin schreiben
      wir die beiden hexadezimalen Ziffern (das erste Byte ist auch
      die Stelle an die wir die Eingabe lesen). Das dritte Byte ist
      ein Leerzeichen.</para>

    <para>Der Code-Abschnitt besteht aus vier Teilen: Das Byte lesen,
      es in eine hexadezimale Zahl umwandeln, das Ergebnis schreiben
      und letztendlich das Programm verlassen.</para>

    <para>Um das Byte zu lesen, bitten wir das System ein Byte von
      <filename>stdin</filename> zu lesen und speichern es im ersten
      Byte von <varname>buffer</varname>. Das System gibt die Anzahl
      an Bytes, die gelesen wurden, in <varname
      role="register">EAX</varname> zur�ck. Diese wird
      <constant>1</constant> sein, wenn eine Eingabe empfangen wird
      und <constant>0</constant>, wenn keine Eingabedaten mehr
      verf�gbar sind. Deshalb �berpr�fen wir den Wert
      von <varname role="register">EAX</varname>. Wenn dieser
      <constant>0</constant> ist, springen wir zu
      <varname>.done</varname>, ansonsten fahren wir fort.</para>

    <note>
      <para>Zu Gunsten der Einfachheit ignorieren wir hier die
	M�glichkeit eines Fehlers.</para>
    </note>

    <para>Die Umwandlungsroutine in eine Hexadezimalzahl liest das
      Byte aus <varname>buffer</varname> in <varname
      role="register">EAX</varname>, oder genaugenommen nur in
      <varname role="register">AL</varname>, wobei die �brigen
      Bits von <varname role="register">EAX</varname> auf null gesetzt
      werden. Au�erdem kopieren wir das Byte nach <varname
      role="register">EDX</varname>, da wir die oberen vier Bits
      (Nibble) getrennt von den unteren vier Bits umwandeln
      m�ssen. Das Ergebnis speichern wir in den ersten beiden
      Bytes des Puffers.</para>

    <para>Als N�chstes bitten wir das System die drei Bytes in
      den Puffer zu schreiben, also die zwei hexadezimalen Ziffern und
      das Leerzeichen nach <filename>stdout</filename>. Danach
      springen wir wieder an den Anfang des Programms und verarbeiten
      das n�chste Byte.</para>

    <para>Wenn die gesamte Eingabe verarbeitet ist, bitten wie das
      System unser Programm zu beenden und null zur�ckzuliefern,
      welches traditionell die Bedeutung hat, dass unser Programm
      erfolgreich war.</para>

    <para>Fahren Sie fort und speichern Sie den Code in eine Datei
      namens <filename>hex.asm</filename>. Geben Sie danach folgendes
      ein (<userinput>^D</userinput> bedeutet, dass Sie die
      Steuerungstaste dr�cken und dann <userinput>D</userinput>
      eingeben, w�hrend Sie Steuerung gedr�ckt
      halten):</para>

    <screen>&prompt.user; <userinput>nasm -f elf hex.asm</userinput>
&prompt.user; <userinput>ld -s -o hex hex.o</userinput>
&prompt.user; <userinput>./hex</userinput>
<userinput>Hello, World!</userinput>
48 65 6C 6C 6F 2C 20 57 6F 72 6C 64 21 0A <userinput>Here I come!</userinput>
48 65 72 65 20 49 20 63 6F 6D 65 21 0A <userinput>^D</userinput> &prompt.user;</screen>

    <note>
      <para>Wenn Sie von <acronym>&ms-dos;</acronym> zu &unix;
	wechseln, wundern Sie sich vielleicht, warum jede Zeile mit
	<constant>0A</constant> an Stelle von <constant>0D
	0A</constant> endet. Das liegt daran, dass &unix; nicht die
	CR/LF-Konvention, sondern die "new line"-Konvention verwendet,
	welches hexadezimal als <constant>0A</constant> dargestellt
	wird.</para>
    </note>

    <para>K�nnen wir das Programm verbessern? Nun, einerseits ist
      es etwas verwirrend, dass die Eingabe, nachdem wir eine Zeile
      verarbeitet haben, nicht wieder am Anfang der Zeile beginnt.
      Deshalb k�nnen wir unser Programm anpassen um einen
      Zeilenumbruch an Stelle eines Leerzeichens nach jedem
      <constant>0A</constant> auszugeben:</para>

    <programlisting>%include	'system.inc'

section	.data
hex	db	'0123456789ABCDEF'
buffer	db	0, 0, ' '

section	.text
global	_start
_start:
	mov	cl, ' '

.loop:
	; read a byte from stdin
	push	dword 1
	push	dword buffer
	push	dword stdin
	sys.read
	add	esp, byte 12
	or	eax, eax
	je	.done

	; convert it to hex
	movzx	eax, byte [buffer]
	mov	[buffer+2], cl
	cmp	al, 0Ah
	jne	.hex
	mov	[buffer+2], al

.hex:
	mov	edx, eax
	shr	dl, 4
	mov	dl, [hex+edx]
	mov	[buffer], dl
	and	al, 0Fh
	mov	al, [hex+eax]
	mov	[buffer+1], al

	; print it
	push	dword 3
	push	dword buffer
	push	dword stdout
	sys.write
	add	esp, byte 12
	jmp	short .loop

.done:
	push	dword 0
	sys.exit</programlisting>

    <para>Wir haben das Leerzeichen im Register <varname
      role="register">CL</varname> abgelegt. Das k�nnen wir
      bedenkenlos tun, da &unix;-Systemaufrufe im Gegensatz zu denen
      von &microsoft.windows; keine Werte von Registern �ndern in
      denen sie keine Werte zur�ckliefern.</para>

    <para>Das bedeutet, dass wir <varname role="register">CL</varname>
      nur einmal setzen m�ssen. Daf�r haben wir ein neues
      Label <varname>.loop</varname> eingef�gt, zu dem wir an
      Stelle von <varname>_start</varname> springen, um das
      n�chste Byte einzulesen. Au�erdem haben wir das Label
      <varname>.hex</varname> eingef�gt, somit k�nnen wir
      wahlweise ein Leerzeichen oder einen Zeilenumbruch im dritten
      Byte von <varname>buffer</varname> ablegen.</para>

    <para>Nachdem Sie <filename>hex.asm</filename> entsprechend der
      Neuerungen ge�ndert haben, geben Sie Folgendes ein:</para>

    <screen>&prompt.user; <userinput>nasm -f elf hex.asm</userinput>
&prompt.user; <userinput>ld -s -o hex hex.o</userinput>
&prompt.user; <userinput>./hex</userinput>
<userinput>Hello, World!</userinput>
48 65 6C 6C 6F 2C 20 57 6F 72 6C 64 21 0A
<userinput>Here I come!</userinput>
48 65 72 65 20 49 20 63 6F 6D 65 21 0A
<userinput>^D</userinput> &prompt.user;</screen>

    <para>Das sieht doch schon besser aus. Aber der Code ist ziemlich
      ineffizient! Wir f�hren f�r jeden einzelne Byte
      zweimal einen Systemaufruf aus (einen zum Lesen und einen um es
      in die Ausgabe zu schreiben).</para>
  </sect1>

  <sect1 id="x86-buffered-io">
    <sect1info>
      <authorgroup>
	<author>
	  <firstname>Hagen</firstname>
	  <surname>K�hl</surname>
	  <contrib>�bersetzt von </contrib>
	</author>
      </authorgroup>
    </sect1info>

    <title>Gepufferte Eingabe und Ausgabe</title>

    <para>Wir k�nnen die Effizienz unseres Codes erh�hen,
      indem wir die Ein- und Ausgabe puffern. Wir erzeugen einen
      Eingabepuffer und lesen dann eine Folge von Bytes auf einmal.
      Danach holen wir sie Byte f�r Byte aus dem Puffer.</para>

    <para>Wir erzeugen ebenfalls einen Ausgabepuffer. Darin speichern
      wir unsere Ausgabe bis er voll ist. Dann bitten wir den Kernel
      den Inhalt des Puffers nach <filename>stdout</filename> zu
      schreiben.</para>

    <para>Diese Programm endet, wenn es keine weitere Eingaben gibt.
      Aber wir m�ssen den Kernel immernoch bitten den Inhalt des
      Ausgabepuffers ein letztes Mal nach <filename>stdout</filename>
      zu schreiben, denn sonst w�rde ein Teil der Ausgabe zwar im
      Ausgabepuffer landen, aber niemals ausgegeben werden. Bitte
      vergessen Sie das nicht, sonst fragen Sie sich sp�ter warum
      ein Teil Ihrer Ausgabe verschwunden ist.</para>

    <programlisting>%include	'system.inc'

%define	BUFSIZE	2048

section	.data
hex	db	'0123456789ABCDEF'

section .bss
ibuffer	resb	BUFSIZE
obuffer	resb	BUFSIZE

section	.text
global	_start
_start:
	sub	eax, eax
	sub	ebx, ebx
	sub	ecx, ecx
	mov	edi, obuffer

.loop:
	; read a byte from stdin
	call	getchar

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

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

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

.put:
	call	putchar
	jmp	short .loop

align 4
getchar:
	or	ebx, ebx
	jne	.fetch

	call	read

.fetch:
	lodsb
	dec	ebx
	ret

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

align 4
.done:
	call	write		; flush output buffer
	push	dword 0
	sys.exit

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

align 4
write:
	sub	edi, ecx	; start of buffer
	push	ecx
	push	edi
	push	dword stdout
	sys.write
	add	esp, byte 12
	sub	eax, eax
	sub	ecx, ecx	; buffer is empty now
	ret</programlisting>

    <para>Als dritten Abschnitt im Quelltext haben wir
      <varname>.bss</varname>. Dieser Abschnitt wird nicht in unsere
      ausf�hrbare Datei eingebunden und kann daher nicht
      initialisiert werden. Wir verwenden <function
      role="opcode">resb</function> anstelle von <function
      role="opcode">db</function>. Dieses reserviert einfach die
      angeforderte Menge an uninitialisiertem Speicher zu unserer
      Verwendung.</para>

    <para>Wir nutzen, die Tatsache, dass das System die Register nicht
      ver�ndert: Wir benutzen Register, wo wir anderenfalls
      globale Variablen im Abschnitt <varname>.data</varname>
      verwenden m�ssten. Das ist auch der Grund, warum die
      &unix;-Konvention, Parameter auf dem Stack zu �bergeben,
      der von Microsoft, hierf�r Register zu verwenden,
      �berlegen ist: Wir k�nnen Register f�r unsere
      eigenen Zwecke verwenden.</para>

    <para>Wir verwenden <varname role="register">EDI</varname> und
      <varname role="register">ESI</varname> als Zeiger auf das
      n�chste zu lesende oder schreibende Byte. Wir verwenden
      <varname role="register">EBX</varname> und <varname
      role="register">ECX</varname>, um die Anzahl der Bytes in den
      beiden Puffern zu z�hlen, damit wir wissen, wann wir die
      Ausgabe an das System �bergeben, oder neue Eingabe vom
      System entgegen nehmen m�ssen.</para>

    <para>Lassen Sie uns sehen, wie es funktioniert:</para>

    <screen>&prompt.user; <userinput>nasm -f elf hex.asm</userinput>
&prompt.user; <userinput>ld -s -o hex hex.o</userinput>
&prompt.user; <userinput>./hex</userinput>
<userinput>Hello, World!</userinput>
<userinput>Here I come!</userinput>
48 65 6C 6C 6F 2C 20 57 6F 72 6C 64 21 0A
48 65 72 65 20 49 20 63 6F 6D 65 21 0A
<userinput>^D</userinput> &prompt.user;</screen>

    <para>Nicht was Sie erwartet haben? Das Programm hat die Ausgabe
      nicht auf dem Bildschirm ausgegeben bis sie
      <userinput>^D</userinput> gedr�ckt haben. Das kann man
      leicht zu beheben indem man drei Zeilen Code einf�gt,
      welche die Ausgabe jedesmal schreiben, wenn wir einen
      Zeilenumbruch in <constant>0A</constant> umgewandelt haben. Ich
      habe die betreffenden Zeilen mit &gt; markiert (kopieren Sie die
      &gt; bitte nicht mit in Ihre
      <filename>hex.asm</filename>).</para>

    <programlisting>%include    'system.inc'

%define	BUFSIZE	2048

section	.data
hex	db	'0123456789ABCDEF'

section .bss
ibuffer	resb	BUFSIZE
obuffer	resb	BUFSIZE

section	.text
global	_start
_start:
	sub	eax, eax
	sub	ebx, ebx
	sub	ecx, ecx
	mov	edi, obuffer

.loop:
	; read a byte from stdin
	call	getchar

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

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

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

.put:
	call	putchar
>	cmp	al, 0Ah
>	jne	.loop
>	call	write
	jmp	short .loop

align 4
getchar:
	or	ebx, ebx
	jne	.fetch

	call	read

.fetch:
	lodsb
	dec	ebx
	ret

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

align 4
.done:
	call	write		; flush output buffer
	push	dword 0
	sys.exit

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

align 4
write:
	sub	edi, ecx	; start of buffer
	push	ecx
	push	edi
	push	dword stdout
	sys.write
	add	esp, byte 12
	sub	eax, eax
	sub	ecx, ecx	; buffer is empty now
	ret</programlisting>

    <para>Lassen Sie uns jetzt einen Blick darauf werfen, wie es
      funktioniert.</para>

    <screen>&prompt.user; <userinput>nasm -f elf hex.asm</userinput>
&prompt.user; <userinput>ld -s -o hex hex.o</userinput>
&prompt.user; <userinput>./hex</userinput>
<userinput>Hello, World!</userinput>
48 65 6C 6C 6F 2C 20 57 6F 72 6C 64 21 0A
<userinput>Here I come!</userinput>
48 65 72 65 20 49 20 63 6F 6D 65 21 0A
<userinput>^D</userinput> &prompt.user;</screen>

    <para>Nicht schlecht f�r eine 644 Byte gro�e
      Bin�rdatei, oder?</para>

    <note>
      <para>Dieser Ansatz f�r gepufferte Ein- und Ausgabe
        enth�lt eine Gefahr, auf die ich im Abschnitt <link
        linkend="x86-buffered-dark-side">Die dunkle Seite des
        Buffering</link> eingehen werde.</para>
    </note>

    <sect2 id="x86-ungetc">
      <title>Ein Zeichen ungelesen machen</title>

      <warning>
	<para>Das ist vielleicht ein etwas fortgeschrittenes Thema,
	  das vor allem f�r Programmierer interessant ist, die
	  mit der Theorie von Compilern vertraut sind. Wenn Sie
	  wollen, k�nnen Sie <link linkend="x86-command-line">zum
	  n�chsten Abschnitt springen</link> und das hier
	  vielleicht sp�ter lesen.</para>
      </warning>

      <para>Unser Beispielprogramm ben�tigt es zwar nicht, aber
	etwas anspruchsvollere Filter m�ssen h�ufig
	vorausschauen. Mit anderen Worten, sie m�ssen wissen was
	das n�chste Zeichen ist (oder sogar mehrere Zeichen).
	Wenn das n�chste Zeichen einen bestimmten Wert hat, ist
	es Teil des aktuellen Tokens, ansonsten nicht.</para>

      <para>Zum Beispiel k�nnten Sie den Eingabestrom f�r
	eine Text-Zeichenfolge parsen (z.B. wenn Sie einen Compiler
	einer Sprache implementieren): Wenn einem Buchstaben ein
	anderer Buchstabe oder vielleicht eine Ziffer folgt, ist er
	ein Teil des Tokens, das Sie verarbeiten. Wenn ihm ein
	Leerzeichen folgt, oder ein anderer Wert, ist er nicht Teil
	des aktuellen Tokens.</para>

      <para>Das f�hrt uns zu einem interessanten Problem: Wie
	kann man ein Zeichen zur�ck in den Eingabestrom geben,
	damit es sp�ter noch einmal gelesen werden kann?</para>

      <para>Eine m�gliche L�sung ist, das Zeichen in einer
	Variable zu speichern und ein Flag zu setzen. Wir k�nnen
	<function>getchar</function> so anpassen, dass es das Flag
	�berpr�ft und, wenn es gesetzt ist, das Byte aus der
	Variable anstatt dem Eingabepuffer liest und das Flag
	zur�ck setzt. Aber nat�rlich macht uns das
	langsamer.</para>

      <para>Die Sprache C hat eine Funktion
	<function>ungetc()</function> f�r genau diesen Zweck.
	Gibt es einen schnellen Weg, diese in unserem Code zu
	implementieren? Ich m�chte Sie bitten nach oben zu
	scrollen und sich die Prozedur <function>getchar</function>
	anzusehen und zu versuchen eine sch�ne und schnelle
	L�sung zu finden, bevor Sie den n�chsten Absatz
	lesen. Kommen Sie danach hierher zur�ck und schauen sich
	meine L�sung an.</para>

      <para>Der Schl�ssel dazu ein Zeichen an den Eingabestrom
	zur�ckzugeben, liegt darin, wie wir das Zeichen
	bekommen:</para>

      <para>Als erstes �berpr�fen wir, ob der Puffer leer
	ist, indem wir den Wert von <varname
	role="register">EBX</varname> testen. Wenn er null ist, rufen
	wir die Prozedur <function>read</function> auf.</para>

      <para>Wenn ein Zeichen bereit ist verwenden wir <function
	role="opcode">lodsb</function>, dann verringern wir den Wert
	von <varname role="register">EBX</varname>. Die Anweisung
	<function role="opcode">lodsb</function> ist letztendlich
	identisch mit:</para>

      <programlisting>	mov	al, [esi]
	  inc	esi</programlisting>

      <para>Das Byte, welches wir abgerufen haben, verbleibt im Puffer
	bis <function>read</function> zum n�chsten Mal aufgerufen
	wird. Wir wissen nicht wann das passiert, aber wir wissen,
	dass es nicht vor dem n�chsten Aufruf von
	<function>getchar</function> passiert. Daher ist alles was wir
	tun m�ssen um das Byte in den Strom "zur�ckzugeben"
	ist den Wert von <varname role="register">ESI</varname> zu
	verringern und den von <varname role="register">EBX</varname>
	zu erh�hen:</para>

      <programlisting>ungetc:
	  dec	esi
	  inc	ebx
	  ret</programlisting>

      <para>Aber seien Sie vorsichtig! Wir sind auf der sicheren Seite,
	solange wir immer nur ein Zeichen im Voraus lesen. Wenn wir
	mehrere kommende Zeichen betrachten und
	<function>ungetc</function> mehrmals hintereinander aufrufen,
	wird es meistens funktionieren, aber nicht immer (und es wird
	ein schwieriger Debug). Warum?</para>

      <para>Solange <function>getchar</function>
	<function>read</function> nicht aufrufen muss, befinden sich
	alle im Voraus gelesenen Bytes noch im Puffer und
	<function>ungetc</function> arbeitet fehlerfrei. Aber sobald
	<function>getchar</function> <function>read</function> aufruft
	ver�ndert sich der Inhalt des Puffers.</para>

      <para>Wir k�nnen uns immer darauf verlassen, dass
	<function>ungetc</function> auf dem zuletzt mit
	<function>getchar</function> gelesenen Zeichen korrekt
	arbeitet, aber nicht auf irgendetwas, das davor gelesen
	wurde.</para>

      <para>Wenn Ihr Programm mehr als ein Byte im Voraus lesen soll,
	haben Sie mindestens zwei M�glichkeiten:</para>

      <para>Die einfachste L�sung ist, Ihr Programm so zu
	�ndern, dass es immer nur ein Byte im Voraus liest, wenn
	das m�glich ist.</para>

      <para>Wenn Sie diese M�glichkeit nicht haben, bestimmen Sie
	zuerst die maximale Anzahl an Zeichen, die Ihr Programm auf
	einmal an den Eingabestrom zur�ckgeben muss. Erh�hen
	Sie diesen Wert leicht, nur um sicherzugehen, vorzugsweise auf
	ein Vielfaches von 16&mdash;damit er sich sch�n
	ausrichtet. Dann passen Sie den <varname>.bss</varname>
	Abschnitt Ihres Codes an und erzeugen einen kleinen
	Reserver-Puffer, direkt vor ihrem Eingabepuffer, in etwa
	so:</para>

      <programlisting>section	.bss
	  resb	16	; or whatever the value you came up with
  ibuffer	resb	BUFSIZE
  obuffer	resb	BUFSIZE</programlisting>

      <para>Au�erdem m�ssen Sie <function>ungetc</function>
	anpassen, sodass es den Wert des Bytes, das zur�ckgegeben
	werden soll, in <varname role="register">AL</varname>
	�bergibt:</para>

      <programlisting>ungetc:
	  dec	esi
	  inc	ebx
	  mov	[esi], al
	  ret</programlisting>

      <para>Mit dieser �nderung k�nnen Sie sicher
	<function>ungetc</function> bis zu 17 Mal hintereinander
	gqapaufrufen (der erste Aufruf erfolgt noch im Puffer, die
	anderen 16 entweder im Puffer oder in der Reserve).</para>
    </sect2>
  </sect1>

  <sect1 id="x86-command-line">
    <sect1info>
      <authorgroup>
	<author>
	  <firstname>Fabian</firstname>
	  <surname>Ruch</surname>
	  <contrib>�bersetzt von </contrib>
	</author>
      </authorgroup>
    </sect1info>

    <title>Kommandozeilenparameter</title>

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

    <para>Bevor ein &unix;-System ein Programm ausf�hrt, legt es
      einige Daten auf dem Stack ab (<function
      role="opcode">push</function>) und springt dann an das
      <varname>_start</varname>-Label des Programms. Ja, ich sagte
      springen, nicht aufrufen. Das bedeutet, dass auf die Daten
      zugegriffen werden kann, indem <varname>[esp+offset]</varname>
      ausgelesen wird oder die Daten einfach vom Stack genommen werden
      (<function role="opcode">pop</function>).</para>

    <para>Der Wert ganz oben auf dem Stack enth�lt die Zahl der
      Kommandozeilenparameter. Er wird traditionell
      <varname>argc</varname> wie "argument count" genannt.</para>

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

    <para>Der <varname>argv</varname>-Liste folgt ein NULL-Zeiger, was
      einfach eine <constant>0</constant> ist. Es gibt noch mehr, aber
      dies ist erst einmal genug f�r unsere Zwecke.</para>

    <note>
      <para>Falls Sie von der
	<acronym>&ms-dos;</acronym>-Programmierumgebung kommen, ist
	der gr��te Unterschied die Tatsache, dass jeder
	Parameter eine separate Zeichenkette ist. Der zweite
	Unterschied ist, dass es praktisch keine Grenze gibt, wie
	viele Parameter vorhanden sein k�nnen.</para>
    </note>

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

    <para>Erstens ben�tigen wir zwei neue Eintr�ge in unserer
      Liste mit den Systemaufrufnummern:</para>

    <programlisting>%define	SYS_open	5
%define	SYS_close	6</programlisting>

    <para>Zweitens f�gen wir zwei neue Makros am Ende der Datei
      ein:</para>

    <programlisting>%macro	sys.open	0
	system	SYS_open
%endmacro

%macro	sys.close	0
	system	SYS_close
%endmacro</programlisting>

    <para>Und hier ist schlie�lich unser ver�nderter
      Quelltext:</para>

    <programlisting>%include	'system.inc'

%define	BUFSIZE	2048

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

section .bss
ibuffer	resb	BUFSIZE
obuffer	resb	BUFSIZE

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

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

	pop	ecx
	jecxz	.init		; no more arguments

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

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

	pop	ecx
	jecxz	.init		; no more arguments

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

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

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

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

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

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

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

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

align 4
getchar:
	or	ebx, ebx
	jne	.fetch

	call	read

.fetch:
	lodsb
	dec	ebx
	ret

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

align 4
.done:
	call	write		; flush output buffer

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

	push	dword [fd.out]
	sys.close

	; return success
	push	dword 0
	sys.exit

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

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

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

    <para>Im <varname>.text</varname>-Abschnitt haben wir die
      Verweise auf <varname>stdin</varname> und
      <varname>stdout</varname> durch <varname>[fd.in]</varname> und
      <varname>[fd.out]</varname> ersetzt.</para>

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

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

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

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

    <para>Der Rest des Codes ist derselbe wie vorher, au�er
      dem Schlie�en der Ein- und Ausgabedatei vor dem Verlassen
      des Programms und, wie bereits erw�hnt, die Benutzung von
      <varname>[fd.in]</varname> und
      <varname>[fd.out]</varname>.</para>

    <para>Unsere Bin�rdatei ist nun kolossale 768 Bytes
      gro�.</para>

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

    <itemizedlist>
      <listitem>
	<para>Die Fehlerbehandlung eine Warnung auf
	  <varname>stderr</varname> ausgeben lassen.</para>
      </listitem>

      <listitem>
	<para>Den <function>Lese</function>- und
	  <function>Schreib</function>funkionen eine Fehlerbehandlung
	  hinzuf�gen.</para>
      </listitem>

      <listitem>
	<para>Schlie�en von <varname>stdin</varname>, sobald wir
	  eine Eingabedatei �ffnen, von <varname>stdout</varname>,
	  sobald wir eine Ausgabedatei �ffnen.</para>
      </listitem>

      <listitem>
	<para>Hinzuf�gen von Kommandozeilenschaltern wie zum
	  Beispiel <parameter>-i</parameter> und
	  <parameter>-o</parameter>, sodass wir die Ein- und
	  Ausgabedatei in irgendeiner Reihenfolge angeben oder
	  vielleicht von <varname>stdin</varname> lesen und in eine
	  Datei schreiben k�nnen.</para>
      </listitem>

      <listitem>
	<para>Ausgeben einer Gebrauchsanweisung, falls die
	  Kommandozeilenparameter fehlerhaft sind.</para>
      </listitem>
    </itemizedlist>

    <para>Ich beabsichtige, diese Verbesserungen dem Leser als
      �bung zu hinterlassen: Sie wissen bereits alles, das Sie
      wissen m�ssen, um die Verbesserungen
      durchzuf�hren.</para>
  </sect1>

  <sect1 id="x86-environment">
    <sect1info>
      <authorgroup>
	<author>
	  <firstname>Fabian</firstname>
	  <surname>Ruch</surname>
	  <contrib>�bersetzt von </contrib>
	</author>
      </authorgroup>
    </sect1info>

    <title>Die &unix;-Umgebung</title>

    <para>Ein entscheidendes Konzept hinter &unix; ist die Umgebung,
      die durch <emphasis>Umgebungsvariablen</emphasis> festgelegt
      wird. Manche werden vom System gesetzt, andere von Ihnen und
      wieder andere von der <application>shell</application> oder
      irgendeinem Programm, das ein anderes l�dt.</para>

    <sect2 id="x86-find-environment">
      <title>Umgebungsvariablen herausfinden</title>

      <para>Ich sagte vorher, dass wenn ein Programm mit der
	Ausf�hrung beginnt, der Stack <varname>argc</varname>
	gefolgt vom durch NULL beendeten <varname>argv</varname>-Array
	und etwas Anderem enth�lt. Das "etwas Andere" ist die
	<emphasis>Umgebung</emphasis> oder, um genauer zu sein, ein
	durch NULL beendetes Array von Zeigern auf
	<emphasis>Umgebungsvariablen</emphasis>. Davon wird oft als
	<varname>env</varname> gesprochen.</para>

      <para>Der Aufbau von <varname>env</varname> entspricht dem von
	<varname>argv</varname>, eine Liste von Speicheradressen gefolgt
	von NULL (<constant>0</constant>). In diesem Fall gibt es kein
	<varname>"envc"</varname>&mdash;wir finden das Ende heraus,
	indem wir nach dem letzten NULL suchen.</para>

      <para>Die Variablen liegen normalerweise in der Form
	<varname>name=value</varname> vor, aber manchmal kann der
	<varname>=value</varname>-Teil fehlen. Wir m�ssen diese
	M�glichkeit in Betracht ziehen.</para>
    </sect2>

    <sect2 id="x86-webvar">
      <title>webvars</title>

      <para>Ich k�nnte Ihnen einfach etwas Code zeigen, der die
	Umgebung in der Art vom &unix;-Befehl
	<application>env</application> ausgibt. Aber ich dachte, dass es
	interessanter sei, ein einfaches CGI-Werkzeug in Assembler zu
	schreiben.</para>

      <sect3 id="x86-cgi">
	<title>CGI: Ein kurzer �berblick</title>

	<para>Ich habe eine <ulink
	  url="http://www.whizkidtech.redprince.net/cgi-bin/tutorial">detaillierte
	  <acronym>CGI</acronym>-Anleitung</ulink> auf meiner Webseite,
	  aber hier ist ein sehr kurzer �berblick �ber
	  <acronym>CGI</acronym>:</para>

	<itemizedlist>
	  <listitem>
	    <para>Der Webserver kommuniziert mit dem
	      <acronym>CGI</acronym>-Programm, indem er
	      <emphasis>Umgebungsvariablen</emphasis> setzt.</para>
	  </listitem>

	  <listitem>
	    <para>Das <acronym>CGI</acronym>-Programm schreibt seine
	      Ausgabe auf <filename>stdout</filename>. Der Webserver
	      liest von da.</para>
	  </listitem>

	  <listitem>
	    <para>Die Ausgabe muss mit einem
	      <acronym>HTTP</acronym>-Kopfteil gefolgt von zwei
	      Leerzeilen beginnen.</para>
	  </listitem>

	  <listitem>
	    <para>Das Programm gibt dann den
	      <acronym>HTML</acronym>-Code oder was f�r einen
	      Datentyp es auch immer verarbeitet
	      aus.</para>
	  </listitem>

	  <listitem>
	    <note>
	      <para>W�hrend bestimmte
		<emphasis>Umgebungsvariablen</emphasis> Standardnamen
		benutzen, unterscheiden sich andere, abh�ngig vom
		Webserver. Dies macht <application>webvars</application>
		zu einem recht n�tzlichen Werkzeug.</para>
	    </note>
	  </listitem>
	</itemizedlist>
      </sect3>

      <sect3 id="x86-webvars-the-code">
	<title>Der Code</title>

	<para>Unser <application>webvars</application>-Programm muss
	  also den <acronym>HTTP</acronym>-Kopfteil gefolgt von etwas
	  <acronym>HTML</acronym>-Auszeichnung versenden. Dann muss es
	  die <emphasis>Umgebungsvariablen</emphasis> eine nach der
	  anderen auslesen und sie als Teil der
	  <acronym>HTML</acronym>-Seite versenden.</para>

	<para>Nun der Code. Ich habe Kommentare und Erkl�rungen
	  direkt in den Code eingef�gt:</para>

	<programlisting>
;;;;;;; webvars.asm ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; Copyright (c) 2000 G. Adam Stanislav
; All rights reserved.
;
; Redistribution and use in source and binary forms, with or without
; modification, are permitted provided that the following conditions
; are met:
; 1. Redistributions of source code must retain the above copyright
;    notice, this list of conditions and the following disclaimer.
; 2. Redistributions in binary form must reproduce the above copyright
;    notice, this list of conditions and the following disclaimer in the
;    documentation and/or other materials provided with the distribution.
;
; THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
; ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
; IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
; ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
; FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
; OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
; HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
; LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
; OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
; SUCH DAMAGE.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; Version 1.0
;
; Started:	 8-Dec-2000
; Updated:	 8-Dec-2000
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
%include	'system.inc'

section	.data
http	db	'Content-type: text/html', 0Ah, 0Ah
	db	'&lt;?xml version="1.0" encoding="UTF-8"?&gt;', 0Ah
	db	'&lt;!DOCTYPE html PUBLIC "-//W3C/DTD XHTML Strict//EN" '
	db	'"DTD/xhtml1-strict.dtd"&gt;', 0Ah
	db	'&lt;html xmlns="http://www.w3.org/1999/xhtml" '
	db	'xml.lang="en" lang="en"&gt;', 0Ah
	db	'&lt;head&gt;', 0Ah
	db	'&lt;title&gt;Web Environment&lt;/title&gt;', 0Ah
	db	'&lt;meta name="author" content="G. Adam Stanislav" /&gt;', 0Ah
	db	'&lt;/head&gt;', 0Ah, 0Ah
	db	'&lt;body bgcolor="#ffffff" text="#000000" link="#0000ff" '
	db	'vlink="#840084" alink="#0000ff"&gt;', 0Ah
	db	'&lt;div class="webvars"&gt;', 0Ah
	db	'&lt;h1&gt;Web Environment&lt;/h1&gt;', 0Ah
	db	'&lt;p&gt;The following &lt;b&gt;environment variables&lt;/b&gt; are defined '
	db	'on this web server:&lt;/p&gt;', 0Ah, 0Ah
	db	'&lt;table align="center" width="80" border="0" cellpadding="10" '
	db	'cellspacing="0" class="webvars"&gt;', 0Ah
httplen	equ	$-http
left	db	'&lt;tr&gt;', 0Ah
	db	'&lt;td class="name"&gt;&lt;tt&gt;'
leftlen	equ	$-left
middle	db	'&lt;/tt&gt;&lt;/td&gt;', 0Ah
	db	'&lt;td class="value"&gt;&lt;tt&gt;&lt;b&gt;'
midlen	equ	$-middle
undef	db	'&lt;i&gt;(undefined)&lt;/i&gt;'
undeflen	equ	$-undef
right	db	'&lt;/b&gt;&lt;/tt&gt;&lt;/td&gt;', 0Ah
	db	'&lt;/tr&gt;', 0Ah
rightlen	equ	$-right
wrap	db	'&lt;/table&gt;', 0Ah
	db	'&lt;/div&gt;', 0Ah
	db	'&lt;/body&gt;', 0Ah
	db	'&lt;/html&gt;', 0Ah, 0Ah
wraplen	equ	$-wrap

section	.text
global	_start
_start:
	; First, send out all the http and xhtml stuff that is
	; needed before we start showing the environment
	push	dword httplen
	push	dword http
	push	dword stdout
	sys.write

	; Now find how far on the stack the environment pointers
	; are. We have 12 bytes we have pushed before "argc"
	mov	eax, [esp+12]

	; We need to remove the following from the stack:
	;
	;	The 12 bytes we pushed for sys.write
	;	The  4 bytes of argc
	;	The EAX*4 bytes of argv
	;	The  4 bytes of the NULL after argv
	;
	; Total:
	;	20 + eax * 4
	;
	; Because stack grows down, we need to ADD that many bytes
	; to ESP.
	lea	esp, [esp+20+eax*4]
	cld		; This should already be the case, but let's be sure.

	; Loop through the environment, printing it out
.loop:
	pop	edi
	or	edi, edi	; Done yet?
	je	near .wrap

	; Print the left part of HTML
	push	dword leftlen
	push	dword left
	push	dword stdout
	sys.write

	; It may be tempting to search for the '=' in the env string next.
	; But it is possible there is no '=', so we search for the
	; terminating NUL first.
	mov	esi, edi	; Save start of string
	sub	ecx, ecx
	not	ecx		; ECX = FFFFFFFF
	sub	eax, eax
repne	scasb
	not	ecx		; ECX = string length + 1
	mov	ebx, ecx	; Save it in EBX

	; Now is the time to find '='
	mov	edi, esi	; Start of string
	mov	al, '='
repne	scasb
	not	ecx
	add	ecx, ebx	; Length of name

	push	ecx
	push	esi
	push	dword stdout
	sys.write

	; Print the middle part of HTML table code
	push	dword midlen
	push	dword middle
	push	dword stdout
	sys.write

	; Find the length of the value
	not	ecx
	lea	ebx, [ebx+ecx-1]

	; Print "undefined" if 0
	or	ebx, ebx
	jne	.value

	mov	ebx, undeflen
	mov	edi, undef

.value:
	push	ebx
	push	edi
	push	dword stdout
	sys.write

	; Print the right part of the table row
	push	dword rightlen
	push	dword right
	push	dword stdout
	sys.write

	; Get rid of the 60 bytes we have pushed
	add	esp, byte 60

	; Get the next variable
	jmp	.loop

.wrap:
	; Print the rest of HTML
	push	dword wraplen
	push	dword wrap
	push	dword stdout
	sys.write

	; Return success
	push	dword 0
	sys.exit</programlisting>

	<para>Dieser Code erzeugt eine 1.396-Byte gro�e
	  Bin�rdatei. Das meiste davon sind Daten, d.h., die
	  <acronym>HTML</acronym>-Auszeichnung, die wir versenden
	  m�ssen.</para>

	<para>Assemblieren Sie es wie immer:</para>

	<screen>&prompt.user; <userinput>nasm -f elf webvars.asm</userinput>
&prompt.user; <userinput>ld -s -o webvars webvars.o</userinput></screen>

	<para>Um es zu benutzen, m�ssen Sie
	  <filename>webvars</filename> auf Ihren Webserver hochladen.
	  Abh�ngig von Ihrer Webserver-Konfiguration, m�ssen
	  Sie es vielleicht in einem speziellen
	  <filename>cgi-bin</filename>-Verzeichnis ablegen oder es mit
	  einer <filename>.cgi</filename>-Dateierweiterung
	  versehen.</para>

	<para>Schlie�lich ben�tigen Sie Ihren Webbrowser,
	  um sich die Ausgabe anzusehen. Um die Ausgabe auf meinem
	  Webserver zu sehen, gehen Sie bitte auf <ulink
	  url="http://www.int80h.org/webvars/"><filename>http://www.int80h.org/webvars/</filename></ulink>.
	  Falls Sie neugierig sind, welche zus�tzlichen Variablen
	  in einem passwortgesch�tzten Webverzeichnis vorhanden
	  sind, gehen Sie auf <ulink
	  url="http://www.int80h.org/private/"><filename>http://www.int80h.org/private/</filename></ulink>
	  unter Benutzung des Benutzernamens <userinput>asm</userinput>
	  und des Passworts <userinput>programmer</userinput>.</para>
      </sect3>
    </sect2>
  </sect1>

  <sect1 id="x86-files">
    <sect1info>
      <authorgroup>
	<author>
	  <firstname>Paul</firstname>
	  <surname>Keller</surname>
	  <contrib>�bersetzt von </contrib>
	</author>
	<author>
	  <firstname>Fabian</firstname>
	  <surname>Borschel</surname>
	</author>
      </authorgroup>
    </sect1info>

    <title>Arbeiten mit Dateien</title>

    <para>Wir haben bereits einfache Arbeiten mit Dateien gemacht:
      Wir wissen wie wir sie �ffnen und schliessen, oder wie
      man sie mit Hilfe von Buffern liest und schreibt. Aber &unix;
      bietet viel mehr Funktionalit�t wenn es um Dateien geht.
      Wir werden einige von ihnen in dieser Sektion untersuchen und
      dann mit einem netten Datei Konvertierungs Werkzeug
      abschliessen.</para>

    <para>In der Tat, Lasst uns am Ende beginnen, also mit dem Datei
      Konvertierungs Werkzeug. Es macht Programmieren immer einfacher,
      wenn wir bereits am Anfang wissen was das End Produkt bezwecken
      soll.</para>

    <para>Eines der ersten Programme die ich f�r &unix; schrieb
      war <ulink url="ftp://ftp.int80h.org/unix/tuc/"><application>
      tuc</application></ulink>, ein Text-Zu-&unix; Datei Konvertierer.
      Es konvertiert eine Text Datei von einem anderen Betriebssystem
      zu einer &unix; Text Datei. Mit anderen Worten, es �ndert
      die verschiedenen Arten von Zeilen Begrenzungen zu der Zeilen
      Begrenzungs Konvention von &unix;. Es speichert die Ausgabe in
      einer anderen Datei. Optional konvertiert es eine &unix; Text
      Datei zu einer <acronym>DOS</acronym> Text Datei.</para>

    <para>Ich habe <application>tuc</application> sehr oft benutzt,
      aber nur von irgendeinem anderen <acronym>OS</acronym> nach
      &unix; zu konvertieren, niemals anders herum. Ich habe mir immer
      gew�nscht das die Datei einfach �berschrieben wird
      anstatt das ich die Ausgabe in eine andere Datei senden muss.
      Meistens, habe ich diesen Befehl verwendet:</para>

    <screen>&prompt.user; <userinput>tuc <replaceable>myfile tempfile</replaceable></userinput>
&prompt.user; <userinput>mv <replaceable>tempfile myfile</replaceable></userinput></screen>

    <para>Es w�re sch� ein  <application>ftuc</application>
      zu haben, also, <emphasis>fast tuc</emphasis>, und es so zu
      benutzen:</para>

    <screen>&prompt.user; <userinput>ftuc <replaceable>myfile</replaceable></userinput></screen>

    <para>In diesem Kapitel werden wir dann, <application>ftuc
      </application> in Assembler schreiben (das Original
      <application>tuc</application> ist in C), und verschiedene
      Datei-Orientierte Kernel Dienste in dem Prozess studieren.</para>

    <para>Auf erste Sicht, ist so eine Datei Konvertierung sehr
      simpel: Alles was du zu tun hast, ist die Wagenr�ckl�ufe
      zu entfernen, richtig?</para>

    <para>Wenn du mit ja geantwortet hast, denk nochmal dar�ber
      nach: Dieses Vorgehen wird die meiste Zeit funktionieren
      (zumindest mit <acronym>MSDOS</acronym> Text Dateien), aber
      gelegentlich fehlschlagen.</para>

    <para>Das Problem ist das nicht alle &unix; Text Dateien ihre
      Zeilen mit einer Wagen R�cklauf / Zeilenvorschub Sequenz
      beenden. Manche benutzen Wagenr�cklauf ohne Zeilenvorschub.
      Andere kombinieren mehrere leere Zeilen in einen einzigen
      Wagenr�cklauf gefolgt von mehreren Zeilenvorsch�ben.
      Und so weiter.</para>

    <para>Ein Text Datei Konvertierer muss dann also in der Lage sein
      mit allen m�glichen Zeilenenden umzugehen:</para>

    <itemizedlist>
      <listitem>
	<para>Wagenr�cklauf / Zeilenvorschub</para>
      </listitem>

      <listitem>
	<para>Wagenr�cklauf</para>
      </listitem>

      <listitem>
	<para>Zeilenvorschub / Wagenr�cklauf</para>
      </listitem>

      <listitem>
	<para>Zeilenvorschub</para>
      </listitem>
    </itemizedlist>

    <para>Es sollte au�erdem in der Lage sein mit Dateien
      umzugehen die irgendeine Art von Kombination der oben stehenden
      M�glichkeiten verwendet. (z.B., Wagenr�cklauf gefolgt
      von mehreren Zeilenvorsch�ben).</para>

    <sect2 id="x86-finite-state-machine">
      <title>Endlicher Zustandsautomat</title>

      <para>Das Problem wird einfach gel�st in dem man eine
	Technik benutzt die sich <emphasis>Endlicher
	Zustandsautomat</emphasis> nennt, urspr�nglich wurde sie
	von den Designern digitaler elektronischer Schaltkreise
	entwickelt. Eine <emphasis>Endlicher Zustandsautomat</emphasis>
	ist ein digitaler Schaltkreis dessen Ausgabe nicht nur von der
	Eingabe abh�ngig ist sondern auch von der vorherigen
	Eingabe, d.h., von seinem Status. Der Mikroprozessor ist ein
	Beispiel f�r einen <emphasis>Endlichen Zustandsautomaten
	</emphasis>: Unser Assembler Sprach Code wird zu
	Maschinensprache �bersetzt in der manche Assembler Sprach
	Codes ein einzelnes Byte produzieren, w�hrend andere
	mehrere Bytes produzieren. Da der Microprozessor die Bytes
	einzeln aus dem Speicher liest, �ndern manche nur seinen
	Status anstatt eine Ausgabe zu produzieren. Wenn alle Bytes
	eines OP Codes gelesen wurden, produziert der Mikroprozessor
	eine Ausgabe, oder �ndert den Wert eines Registers,
	etc.</para>

      <para>Aus diesem Grund, ist jede Software eigentlich nur eine
	Sequenz von Status Anweisungen f�r den Mikroprozessor.
	Dennoch, ist das Konzept eines <emphasis>Endlichen
	Zustandsautomaten</emphasis> auch im Software Design sehr
	hilfreich.</para>

      <para>Unser Text Datei Konvertierer kann als
	<emphasis>Endlicher Zustandsautomat</emphasis> mit 3
	m�glichen Stati desgined werden. Wir k�nnten diese
	von 0-2 benennen, aber es wird uns das Leben leichter machen
	wenn wir ihnen symbolische Namen geben:</para>

      <itemizedlist>
	<listitem>
	  <para><symbol>ordinary</symbol></para>
	</listitem>

	<listitem>
	  <para><symbol>cr</symbol></para>
	</listitem>

	<listitem>
	  <para><symbol>lf</symbol></para>
	</listitem>
      </itemizedlist>

      <para>Unser Programm wird in dem <symbol>ordinary</symbol> Status
	starten. W�hrend dieses Status, h�ngt die Aktion des
	Programms von seiner Eingabe wie folgt ab:</para>

      <itemizedlist>
	<listitem>
	  <para>Wenn die Eingabe etwas anderes als ein
	    Wagenr�cklauf oder einem Zeilenvorschub ist, wird die
	    Eingabe einfach nur an die Ausgabe geschickt. Der Status
	    bleibt unver�ndert.</para>
	</listitem>

        <listitem>
	  <para>Wenn die Eingabe ein Wagenr�cklauf ist, wird der
	    Status auf <symbol>cr</symbol> gesetzt. Die Eingabe wird
	    dann verworfen, d.h., es entsteht keine Ausgabe.</para>
	</listitem>

        <listitem>
	  <para>Wenn die Eingabe ein Zeilenvorschub ist, wird der
	    Status auf <symbol>lf</symbol> gesetzt. Die Eingabe wird
	    dann verworfen.</para>
	</listitem>
      </itemizedlist>

      <para>Wann immer wir in dem <symbol>cr</symbol> Status sind,
	ist das weil die letzte Eingabe ein Wagenr�cklauf war,
	welcher nicht verarbeitet wurde. Was unsere Software in
	diesem Status macht h�ngt von der aktuellen Eingabe
	ab:</para>

      <itemizedlist>
	<listitem>
	  <para>Wenn die Eingabe irgendetwas anderes als ein
	    Wagenr�cklauf oder ein Zeilenvorschub ist, dann gib
	    einen Zeilenvorschub aus, dann gib die Eingabe aus und
	    dann �ndere den Status zu
	    <symbol>ordinary</symbol>.</para>
	</listitem>

	<listitem>
	  <para>Wenn die Eingabe ein Wagenr�cklauf ist, haben
	    wir zwei (oder mehr) Wagenr�ckl�ufe in einer
	    Reihe. Wir verwerfen die Eingabe, wir geben einen
	    Zeilenvorschub aus und lassen den Status
	    unver�ndert.</para>
	</listitem>

	<listitem>
	  <para>Wenn die Eingabe ein Zeilenvorschub ist, geben wir
	    den Zeilenvorschub aus und �ndern den Status zu
	    <symbol>ordinary</symbol>. Achte darauf, dass das nicht
	    das gleiche wie in dem Fall oben dr�ber ist &ndash;
	    w�rden wir versuchen beide zu kombinieren,
	    w�rden wir zwei Zeilenvorsch�be anstatt einen
	    ausgeben.</para>
	</listitem>
      </itemizedlist>

      <para>Letztendlich, sind wir in dem <symbol>lf</symbol> Status
	nachdem wir einen Zeilenvorschub empfangen haben der nicht
	nach einem Wagenr�cklauf kam. Das wird passieren wenn
	unsere Datei bereits im &unix; Format ist, oder jedesmal wenn
	mehrere Zeilen in einer Reihe durch einen einzigen
	Wagenr�cklauf gefolgt von mehreren Zeilenvorsch�ben
	ausgedr�ckt wird, oder wenn die Zeile mit einer
	Zeilenvorschub / Wagenr�cklauf Sequenz endet. Wir
	sollten mit unserer Eingabe in diesem Status folgenderma�en
	umgehen:</para>

      <itemizedlist>
	<listitem>
	  <para>Wenn die Eingabe irgendetwas anderes als ein
	    Wagenr�cklauf oder ein Zeilenvorschub ist, geben wir
	    einen Zeilenvorschub aus, geben dann  die Eingabe aus und
	    �ndern dann den Status zu <symbol>ordinary</symbol>.
	    Das ist exakt die gleiche Aktion wie in dem
	    <symbol>cr</symbol> Status nach dem Empfangen der selben
	    Eingabe.</para>
	</listitem>

	<listitem>
	  <para>Wenn die Eingabe ein Wagenr�cklauf ist, verwerfen
	    wir die Eingabe, geben einen Zeilenvorschub aus und
	    �ndern dann den Status zu <symbol>ordinary</symbol>.
	    </para>
	</listitem>

	<listitem>
	  <para>Wenn die Eingabe ein Zeilenvorschub ist, geben wir den
	    Zeilenvorschub aus und lassen den Status unver�ndert.
	    </para>
	</listitem>
      </itemizedlist>

      <sect3 id="x86-final-state">
	<title>Der Endg�ltige Status</title>

	<para>Der obige <emphasis>Endliche Zustandsautomat</emphasis>
	  funktioniert f�r die gesamte Datei, aber l�sst die
	  M�glichkeit das die letzte Zeile ignoriert wird. Das
	  wird jedesmal passieren wenn die Datei mit einem einzigen
	  Wagenr�cklauf oder einem einzigen Zeilenvorschub endet.
	  Daran habe ich nicht gedacht als ich
	  <application>tuc</application> schrieb, nur um festzustellen,
	  da� das letzte Zeilenende gelegentlich weggelassen
	  wird.</para>

	<para>Das Problem wird einfach dadurch gel�st, indem man
	  den Status �berpr�ft nachdem die gesamte Datei
	  verarbeitet wurde. Wenn der Status nicht
	  <symbol>ordinary</symbol> ist, m�ssen wir nur den
	  letzten Zeilenvorschub ausgeben.</para>

	<note>
	  <para>Nachdem wir unseren Algorithmus nun als einen
	    <emphasis>Endlichen Zustandsautomaten</emphasis> formuliert
	    haben, k�nnten wir einfach einen festgeschalteten
	    digitalen elektronischen Schaltkreis (einen "Chip")
	    designen, der die Umwandlung f�r uns �bernimmt.
	    Nat�rlich w�re das sehr viel teurer, als ein
	    Assembler Programm zu schreiben.</para>
	</note>
      </sect3>

      <sect3 id="x86-tuc-counter">
	<title>Der Ausgabe Z�hler</title>

	<para>Weil unser Datei Konvertierungs Programm
	  m�glicherweise zwei Zeichen zu einem kombiniert,
	  m�ssen wir einen Ausgabe Z�hler verwenden. Wir
	  initialisieren den Z�hler zu <constant>0</constant>
	  und erh�hen ihn jedes mal wenn wir ein Zeichen an die
	  Ausgabe schicken. Am Ende des Programms, wird der
	  Z�hler uns sagen auf welche Gr�sse wir die Datei
	  setzen m�ssen.</para>
      </sect3>
    </sect2>

    <sect2 id="x86-software-fsm">
      <title>Implementieren von EZ als Software</title>

      <para>Der schwerste Teil beim arbeiten mit einer
	<emphasis>Endlichen Zustandsmaschine</emphasis> ist das
	analysieren des Problems und dem ausdr�cken als eine
	<emphasis>Endliche Zustandsmaschine</emphasis>. That geschafft,
	schreibt sich die Software fast wie von selbst.</para>

      <para>In eine h�heren Sprache, wie etwa C, gibt es mehrere
	Hauptans�tze. Einer w�re ein <function
	role="statement">switch</function> Angabe zu verwenden die
	ausw�hlt welche Funktion genutzt werden soll. Zum
	Beispiel,</para>

      <programlisting>
	switch (state) {
	default:
	case REGULAR:
		regular(inputchar);
		break;
	case CR:
		cr(inputchar);
		break;
	case LF:
		lf(inputchar);
		break;
	}
      </programlisting>

      <para>Ein anderer Ansatz ist es ein Array von Funktions Zeigern
	zu benutzen, etwa wie folgt:</para>

      <programlisting>
	(output[state])(inputchar);
      </programlisting>

      <para>Noch ein anderer ist es aus <varname>state</varname> einen
	Funktions Zeiger zu machen und ihn zu der entsprechenden
	Funktion zeigen zu lassen:</para>

      <programlisting>
	(*state)(inputchar);
      </programlisting>

      <para>Das ist der Ansatz den wir in unserem Programm verwenden
	werden, weil es in Assembler sehr einfach und schnell geht.
	Wir werden einfach die Adresse der Prozedur in <varname
	role="register">EBX</varname> speichern und dann einfach das
	ausgeben:</para>

      <programlisting>
	call	ebx
      </programlisting>

      <para>Das ist wahrscheinlich schneller als die Adresse im Code
	zu hardcoden weil der Mikroprozessor die Adresse nicht aus dem
	Speicher lesen muss&mdash;es ist bereits in einer der Register
	gespeichert. Ich sagte <emphasis>wahrscheinlich</emphasis>
	weil durch das Cachen neuerer Mikroprozessoren beide Varianten
	in etwa gleich schnell sind.</para>
    </sect2>

    <sect2 id="memory-mapped-files">
      <title>Speicher abgebildete Dateien</title>

      <para>Weil unser Programm nur mit einzelnen Dateien
	funktioniert, k�nnen wir nicht den Ansatz verwedenden der
	zuvor funktioniert hat, d.h., von einer Eingabe Datei zu lesen
	und in eine Ausgabe Datei zu schreiben.</para>

      <para>&unix; erlaubt es uns eine Datei, oder einen Bereich einer
	Datei, in den Speicher abzubilden. Um das zu tun, m�ssen
	wir zuerst eine Datei mit den entsprechenden Lese/Schreib
	Flags �ffnen. Dann benutzen wir den <function
	role="syscall">mmap</function> system call um sie in den
	Speicher abzubilden. Ein Vorteil von <function
	role="syscall">mmap</function> ist, das es automatisch mit
	virtuellem Speicher arbeitet: Wir k�nnen mehr von der
	Datei im Speicher abbilden als wir �berhaupt
	physikalischen Speicher zur Verf�gung haben, noch immer
	haben wir aber durch normale OP Codes wie <function
	role="opcode">mov</function>, <function
	role="opcode">lods</function>, und <function
	role="opcode">stos</function> Zugriff darauf. Egal welche
	�nderungen wir an dem Speicherabbild der Datei vornehmen,
	sie werden vom System in die Datei geschrieben. Wir
	m�ssen die Datei nicht offen lassen: So lange sie
	abgebildet bleibt, k�nnen wir von ihr lesen und in sie
	schreiben.</para>

      <para>Ein 32-bit Intel Mikroprozessor kann auf bis zu vier
	Gigabyte Speicher zugreifen &ndash; physisch oder virtuell.
	Das FreeBSD System erlaubt es uns bis zu der H�lfte
	f�r die Datei Abbildung zu verwenden.</para>

      <para>Zur Vereinfachung, werden wir in diesem Tutorial nur
	Dateien konvertieren die in ihrere Gesamtheit im Speicher
	abgebildet werden k�nnen. Es gibt wahrscheinlich nicht
	all zu viele Text Dateien die eine Gr�sse von zwei
	Gigabyte �berschreiben. Falls unser Programm doch auf
	eine trifft, wird es einfach eine Meldung anzeigen mit dem
	Vorschlag das originale <application>tuc</application> statt
	dessen zu verwenden.</para>

      <para>Wenn du deine Kopie von
	<filename>syscalls.master</filename> �berpr�fst,
	wirst du zwei verschiedene Systemaufrufe
	finden die sich <function role="syscall">mmap</function>
	nennen. Das kommt von der Entwicklung von &unix;: Es gab das
	traditionelle <acronym>BSD</acronym> <function
	role="syscall">mmap</function>, Systemaufruf 71. Dieses wurde
	durch das <acronym>&posix;</acronym> <function
	role="syscall">mmap</function> ersetzt, Systemaufruf 197. Das
	FreeBSD System unterst�tzt beide, weil �ltere
	Programme mit der originalen <acronym>BSD</acronym> Version
	geschrieben wurden. Da neue Software die
	<acronym>&posix;</acronym> Version nutzt, werden wir diese
	auch verwenden.</para>

      <para>Die <filename>syscalls.master</filename> Datei zeigt die
	<acronym>&posix;</acronym> Version wie folgt:</para>

      <programlisting>
197	STD	BSD	{ caddr_t mmap(caddr_t addr, size_t len, int prot, \
			    int flags, int fd, long pad, off_t pos); }
      </programlisting>

      <para>Das weicht etwas von dem ab was
	<citerefentry><refentrytitle>mmap</refentrytitle>
	<manvolnum>2</manvolnum></citerefentry> sagt. Das ist weil
	<citerefentry><refentrytitle>mmap</refentrytitle>
	<manvolnum>2</manvolnum></citerefentry> die C Version
	beschreibt.</para>

      <para>Der Unterschiede liegt in dem <varname>long pad</varname>
	Argument, welches in der C Version nicht vorhanden ist. Wie
	auch immer, der FreeBSD Systemaufruf f�gt einen 32-bit
	Block ein nachdem es ein 64-Bit Argument auf den Stack
	ge<function role="opcode">push</function>t hat. In diesem
	Fall, ist <varname>off_t</varname> ein 64-Bit Wert.</para>

      <para>Wenn wir fertig sind mit dem Arbeiten einer im Speicher
	abgebildeten Datei, entfernen wir das Speicherabbild mit dem
	<function role="syscall">munmap</function> Systemaufruf:</para>

      <tip>
	<para>F�r eine detailliert Behandlung von <function
	  role="syscall">mmap</function>, sieh in W. Richard Stevens'
	  <ulink url="http://www.int80h.org/cgi-bin/isbn?isbn=0130810819">
	  Unix Network Programming, Volume 2, Chapter 12</ulink>
	  nach.</para>
      </tip>
    </sect2>

    <sect2 id="x86-file-size">
      <title>Feststellen der Datei Gr�sse</title>

      <para>Weil wir <function role="syscall">mmap</function> sagen
	m�ssen wie viele Bytes von Datei wir im Speicher abbilden
	wollen und wir au�erdem die gesamte Datei abbilden wollen,
	m�ssen wir die Gr�sse der Datei feststellen.</para>

      <para>Wir k�nnen den <function
	role="syscall">fstat</function>	Systemaufruf verwenden um alle
	Informationen �ber eine ge�ffnete Datei zu erhalten
	die uns das System geben kann. Das beinhaltet die Datei
	Gr�sse.</para>

      <para>Und wieder, zeigt uns <filename>syscalls.master</filename>
	zwei Versionen von <function role="syscall">fstat</function>,
	eine traditionelle (Systemaufruf 62), und eine
	<acronym>&posix;</acronym> (Systemaufruf 189) Variante.
	Nat�rlich, verwenden wir die <acronym>&posix;</acronym>
	Version:</para>

      <programlisting>
189	STD	POSIX	{ int fstat(int fd, struct stat *sb); }
      </programlisting>

      <para>Das ist ein sehr unkomplizierter Aufruf: Wir
	�bergeben ihm die Adresse einer
	<structname>stat</structname> Structure und den Deskriptor
	einer ge�ffneten Datei. Es wird den Inhalt der
	<structname>stat</structname> Struktur ausf�llen.</para>

      <para>Ich muss allerdings sagen, das ich versucht habe die
	<structname>stat</structname> Struktur in dem
	<varname>.bss</varname> Bereich zu deklarieren, und
	<function role="syscall">fstat</function> mochte es nicht:
	Es setzte das Carry Flag welches einen Fehler anzeigt.
	Nachdem ich den Code ver�nderte so dass er die Struktur
	auf dem Stack anlegt, hat alles gut funktioniert.</para>
    </sect2>

    <sect2 id="x86-ftruncate">
      <title>�ndern der Dateigr�sse</title>

      <para>Dadurch das unser Programm
      Wagenr�cklauf/Zeilenvorschub-Sequenzen in einfache
      Zeilenvorsch�be zusammenfassen k�nnte, k�nnte
      unsere Ausgabe kleiner sein als unsere Eingabe. Und da wir die
      Ausgabe in dieselbe Datei um, aus der wir unsere Eingabe
      erhalten, m�ssen wir eventuell die Dateigr�sse
      anpassen.</para>

      <para>Der Systemaufruf <function
      role="syscall">ftruncate</function> erlaubt uns, dies zu tun.
      Abgesehen von dem etwas ungl�cklich gew�hlten Namen
      <function role="syscall">ftruncate</function> k�nnen wir
      mit dieser Funktion eine Datei vergr�ssern, oder
      verkleinern.</para>

      <para>Und ja, wir werden zwei Versionen von <function
      role="syscall">ftruncate</function> in
      <filename>syscalls.master</filename> finden, eine �ltere
      (130) und eine neuere (201). Wir werden die neuere Version
      verwenden:</para>

      <programlisting>
201	STD	BSD	{ int ftruncate(int fd, int pad, off_t length); }
      </programlisting>

      <para>Beachten Sie bitte, dass hier wieder <varname>int
	pad</varname> verwendet wird.</para>
    </sect2>

    <sect2 id="x86-ftuc">
      <title>ftuc</title>

      <para>Wir wissen jetzt alles n�tige, um
      <application>ftuc</application> zu schreiben. Wir beginnen,
      indem wir ein paar neue Zeilen der Datei
      <filename>system.inc</filename> hinzuf�gen. Als erstes
      definieren wir irgendwo am Anfang der Datei einige Konstanten
      und Strukturen:</para>

      <programlisting>
;;;;;;; open flags
%define	O_RDONLY	0
%define	O_WRONLY	1
%define	O_RDWR	2

;;;;;;; mmap flags
%define	PROT_NONE	0
%define	PROT_READ	1
%define	PROT_WRITE	2
%define	PROT_EXEC	4
;;
%define	MAP_SHARED	0001h
%define	MAP_PRIVATE	0002h

;;;;;;; stat structure
struc	stat
st_dev		resd	1	; = 0
st_ino		resd	1	; = 4
st_mode		resw	1	; = 8, size is 16 bits
st_nlink	resw	1	; = 10, ditto
st_uid		resd	1	; = 12
st_gid		resd	1	; = 16
st_rdev		resd	1	; = 20
st_atime	resd	1	; = 24
st_atimensec	resd	1	; = 28
st_mtime	resd	1	; = 32
st_mtimensec	resd	1	; = 36
st_ctime	resd	1	; = 40
st_ctimensec	resd	1	; = 44
st_size		resd	2	; = 48, size is 64 bits
st_blocks	resd	2	; = 56, ditto
st_blksize	resd	1	; = 64
st_flags	resd	1	; = 68
st_gen		resd	1	; = 72
st_lspare	resd	1	; = 76
st_qspare	resd	4	; = 80
endstruc
      </programlisting>

      <para>Wir definieren die neuen Systemaufrufe:</para>

      <programlisting>
%define	SYS_mmap	197
%define	SYS_munmap	73
%define	SYS_fstat	189
%define	SYS_ftruncate	201
      </programlisting>

      <para>Wir f�gen die Makros hinzu:</para>

      <programlisting>
%macro	sys.mmap	0
	system	SYS_mmap
%endmacro

%macro	sys.munmap	0
	system	SYS_munmap
%endmacro

%macro	sys.ftruncate	0
	system	SYS_ftruncate
%endmacro

%macro	sys.fstat	0
	system	SYS_fstat
%endmacro
      </programlisting>

      <para>Und hier ist unser Code:</para>

      <programlisting>
;;;;;;; Fast Text-to-Unix Conversion (ftuc.asm) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Started:	21-Dec-2000
;; Updated:	22-Dec-2000
;;
;; Copyright 2000 G. Adam Stanislav.
;; All rights reserved.
;;
;;;;;;; v.1 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
%include	'system.inc'

section	.data
	db	'Copyright 2000 G. Adam Stanislav.', 0Ah
	db	'All rights reserved.', 0Ah
usg	db	'Usage: ftuc filename', 0Ah
usglen	equ	$-usg
co	db	"ftuc: Can't open file.", 0Ah
colen	equ	$-co
fae	db	'ftuc: File access error.', 0Ah
faelen	equ	$-fae
ftl	db	'ftuc: File too long, use regular tuc instead.', 0Ah
ftllen	equ	$-ftl
mae	db	'ftuc: Memory allocation error.', 0Ah
maelen	equ	$-mae

section	.text

align 4
memerr:
	push	dword maelen
	push	dword mae
	jmp	short error

align 4
toolong:
	push	dword ftllen
	push	dword ftl
	jmp	short error

align 4
facerr:
	push	dword faelen
	push	dword fae
	jmp	short error

align 4
cantopen:
	push	dword colen
	push	dword co
	jmp	short error

align 4
usage:
	push	dword usglen
	push	dword usg

error:
	push	dword stderr
	sys.write

	push	dword 1
	sys.exit

align 4
global	_start
_start:
	pop	eax		; argc
	pop	eax		; program name
	pop	ecx		; file to convert
	jecxz	usage

	pop	eax
	or	eax, eax	; Too many arguments?
	jne	usage

	; Open the file
	push	dword O_RDWR
	push	ecx
	sys.open
	jc	cantopen

	mov	ebp, eax	; Save fd

	sub	esp, byte stat_size
	mov	ebx, esp

	; Find file size
	push	ebx
	push	ebp		; fd
	sys.fstat
	jc	facerr

	mov	edx, [ebx + st_size + 4]

	; File is too long if EDX != 0 ...
	or	edx, edx
	jne	near toolong
	mov	ecx, [ebx + st_size]
	; ... or if it is above 2 GB
	or	ecx, ecx
	js	near toolong

	; Do nothing if the file is 0 bytes in size
	jecxz	.quit

	; Map the entire file in memory
	push	edx
	push	edx		; starting at offset 0
	push	edx		; pad
	push	ebp		; fd
	push	dword MAP_SHARED
	push	dword PROT_READ | PROT_WRITE
	push	ecx		; entire file size
	push	edx		; let system decide on the address
	sys.mmap
	jc	near memerr

	mov	edi, eax
	mov	esi, eax
	push	ecx		; for SYS_munmap
	push	edi

	; Use EBX for state machine
	mov	ebx, ordinary
	mov	ah, 0Ah
	cld

.loop:
	lodsb
	call	ebx
	loop	.loop

	cmp	ebx, ordinary
	je	.filesize

	; Output final lf
	mov	al, ah
	stosb
	inc	edx

.filesize:
	; truncate file to new size
	push	dword 0		; high dword
	push	edx		; low dword
	push	eax		; pad
	push	ebp
	sys.ftruncate

	; close it (ebp still pushed)
	sys.close

	add	esp, byte 16
	sys.munmap

.quit:
	push	dword 0
	sys.exit

align 4
ordinary:
	cmp	al, 0Dh
	je	.cr

	cmp	al, ah
	je	.lf

	stosb
	inc	edx
	ret

align 4
.cr:
	mov	ebx, cr
	ret

align 4
.lf:
	mov	ebx, lf
	ret

align 4
cr:
	cmp	al, 0Dh
	je	.cr

	cmp	al, ah
	je	.lf

	xchg	al, ah
	stosb
	inc	edx

	xchg	al, ah
	; fall through

.lf:
	stosb
	inc	edx
	mov	ebx, ordinary
	ret

align 4
.cr:
	mov	al, ah
	stosb
	inc	edx
	ret

align 4
lf:
	cmp	al, ah
	je	.lf

	cmp	al, 0Dh
	je	.cr

	xchg	al, ah
	stosb
	inc	edx

	xchg	al, ah
	stosb
	inc	edx
	mov	ebx, ordinary
	ret

align 4
.cr:
	mov	ebx, ordinary
	mov	al, ah
	; fall through

.lf:
	stosb
	inc	edx
	ret
      </programlisting>

      <warning>
	<para>Verwenden Sie dieses Programm nicht mit Dateien, die
	  sich auf Datentr�gern befinden, welche mit
	  <acronym>&ms-dos;</acronym> oder &windows; formatiert
	  wurden. Anscheinend gibt es im Code von FreeBSD einen
	  subtilen Bug, wenn <function role="syscall">mmap</function>
	  auf solchen Datentr�gern verwendet wird: Wenn die Datei
	  eine bestimmte Gr�sse �berschreitet, f�llt
	  <function role="syscall">mmap</function> den Speicher mit
	  lauter Nullen, und �berschreibt damit anschliessend den
	  Dateiinhalt.</para>
      </warning>
    </sect2>
  </sect1>

  <sect1 id="x86-one-pointed-mind">
    <sect1info>
      <authorgroup>
	<author>
	  <firstname>Daniel</firstname>
	  <surname>Seuffert</surname>
	  <contrib>�bersetzt von </contrib>
	</author>
      </authorgroup>
    </sect1info>

    <title>One-Pointed Mind</title>

    <para>Als ein Zen-Sch�ler liebe ich die Idee eines
      fokussierten Bewu�tseins: Tu nur ein Ding zur gleichen
      Zeit, aber mache es richtig.</para>

    <para>Das ist ziemlich genau die gleiche Idee, welche &unix;
      richtig funktionieren l�sst. W�hrend eine typische
      &windows;-Applikation versucht alles Vorstellbare zu tun (und
      daher mit Fehler durchsetzt ist), versucht eine
      &unix;-Applikation nur eine Funktion zu erf�llen und das
      gut.</para>

    <para>Der typische &unix;-Nutzer stellt sich sein eigenes System
      durch Shell-Skripte zusammen, die er selbst schreibt, und welche
      die Vorteile bestehender Applikationen dadurch kombinieren,
      indem sie die Ausgabe eines Programmes als Eingabe in ein
      anderes Programm durch eine Pipe �bergeben.</para>

    <para>Wenn Sie ihre eigene &unix;-Software schreiben, ist es
      generell eine gute Idee zu betrachten, welcher Teil der
      Probleml�sung durch bestehende Programme bewerkstelligt
      werden kann. Man schreibt nur die Programme selbst, f�r die
      keine vorhandene L�sung existiert.</para>

    <sect2 id="x86-csv">
      <title>CSV</title>

      <para>Ich will dieses Prinzip an einem besonderen Beispiel
	aus der realen Welt demonstrieren, mit dem ich k�rzlich
	konfrontiert wurde:</para>

      <para>Ich mu�te jeweils das elfte Feld von jedem
	Datensatz aus einer Datenbank extrahieren, die ich von einer
	Webseite heruntergeladen hatte. Die Datenbank war eine
	<acronym>CSV</acronym>-Datei, d.h. eine Liste von
	<emphasis>Komma-getrennten Werten</emphasis>. Dies ist ein
	ziemlich gew�hnliches Format f�r den Code-Austausch
	zwischen Menschen, die eine unterschiedliche
	Datenbank-Software nutzen.</para>

      <para>Die erste Zeile der Datei enth�lt eine Liste der
	Felder durch Kommata getrennt. Der Rest der Datei enth�lt
	die einzelnen Datens�tze mit durch Kommata getrennten
	Werten in jeder Zeile.</para>

      <para>Ich versuchte <application>awk</application> unter
	Nutzung des Kommas als Trenner. Da aber einige Zeilen durch in
	Bindestriche gesetzte Kommata getrennt waren, extrahierte
	<application>awk</application> das falsche Feld aus diesen
	Zeilen.</para>

      <para>Daher mu�te ich meine eigene Software schreiben,
	um das elfte Feld aus der <acronym>CSV</acronym>-Datei
	auszulesen. Aber durch Anwendung der &unix;-Philosophie
	mu�te ich nur einen einfachen Filter schreiben, das
	Folgende tat:</para>

      <itemizedlist>
	<listitem>
	  <para>Entferne die erste Zeile aus der Datei.</para>
	</listitem>

	<listitem>
	  <para>�ndere alle Kommata ohne Anf�hrungszeichen
	    in einen anderen Buchstaben.</para>
	</listitem>

	<listitem>
	  <para>Entferne alle Anf�hrungszeichen.</para>
	</listitem>
      </itemizedlist>

      <para>Streng genommen k�nnte ich
	<application>sed</application> benutzen, um die erste Zeile
	der Datei zu entfernen, aber das zu Bewerkstelligen war in
	meinem Programm sehr einfach, also entschloss ich mich dazu
	und reduzierte dadurch die Gr��e der
	Pipeline.</para>

      <para>Unter Ber�cksichtigung aller Faktoren kostete mich
	das Schreiben dieses Programmes ca. 20 Minuten. Das Schreiben
	eines Programmes, welches jeweils das elfte Feld aus einer
	<acronym>CSV</acronym>-Datei extrahiert h�tte wesentlich
	l�nger gedauert und ich h�tte es nicht
	wiederverwenden k�nnen, um ein anderes Feld aus irgendeiner
	anderen Datenbank zu extrahieren.</para>

      <para>Diesmal entschied ich mich dazu, etwas mehr Arbeit zu
	investieren, als man normalerweise f�r ein typisches
	Tutorial verwenden w�rde:</para>

      <itemizedlist>
	<listitem>
	  <para>Es parst die Kommandozeilen nach Optionen.</para>
	</listitem>

	<listitem>
	  <para>Es zeigt die richtige Nutzung an, falls es ein
	    falsches Argument findet.</para>
	</listitem>

	<listitem>
	  <para>Es gibt vern�nftige Fehlermeldungen aus.</para>
	</listitem>
      </itemizedlist>

      <para>Hier ist ein Beispiel f�r seine Nutzung:</para>

      <screen>Usage: csv [-t&lt;delim&gt;] [-c&lt;comma&gt;] [-p] [-o &lt;outfile&gt;] [-i &lt;infile&gt;]</screen>

      <para>Alle Parameter sind optional und k�nnen in beliebiger
	Reihenfolge auftauchen.</para>

      <para>Der <parameter>-t</parameter>-Parameter legt fest, was
	zu die Kommata zu ersetzen sind. Der <constant>tab</constant>
	ist die Vorgabe hierf�r. Zum Beispiel wird
	<parameter>-t;</parameter> alle unquotierten Kommata mit
	Semikolon ersetzen.</para>

      <para>Ich brauche die <parameter>-c</parameter>-Option nicht,
	aber sie k�nnte zuk�nftig n�tzlich sein. Sie
	erm�glicht mir festzulegen, da� ich einen anderen
	Buchstaben als das Kommata mit etwas anderem ersetzen
	m�chte. Zum Beispiel wird der Parameter
	<parameter>-c@</parameter> alle @-Zeichen ersetzen
	(n�tzlich, falls man eine Liste von Email-Adressen in
	Nutzername und Domain aufsplitten will).</para>

      <para>Die <parameter>-p</parameter>-Option erh�lt die
	erste Zeile, d.h. die erste Zeile der Datei wird nicht
	gel�scht. Als Vorgabe l�schen wir die erste Zeile,
	weil die <acronym>CSV</acronym>-Datei in der ersten Zeile
	keine Daten, sondern Feldbeschreibungen enth�lt.</para>

      <para>Die Parameter <parameter>-i</parameter>- und
	<parameter>-o</parameter>-Optionen erlauben es mir, die
	Ausgabe- und Eingabedateien festzulegen. Vorgabe sind
	<filename>stdin</filename> und <filename>stdout</filename>,
	also ist es ein regul�rer &unix;-Filter.</para>

      <para>Ich habe sichergestellt, da� sowohl <parameter>-i
	filename</parameter> und <parameter>-ifilename</parameter>
	akzeptiert werden. Genauso habe ich daf�r Sorge getragen,
	da� sowohl Eingabe- als auch Ausgabedateien festgelegt
	werden k�nnen.</para>

      <para>Um das elfte Feld jeden Datensatzes zu erhalten kann ich
	nun folgendes eingeben:</para>

      <screen>&prompt.user; <userinput>csv '-t;' <replaceable>data.csv</replaceable> | awk '-F;' '{print $11}'</userinput></screen>

      <para>Der Code speichert die Optionen (bis auf die
	Dateideskriptoren) in <varname role="register">EDX</varname>:
	Das Kommata in <varname role="register">DH</varname>, den
	neuen Feldtrenner in <varname role="register">DL</varname> und
	das Flag f�r die <parameter>-p</parameter>-Option in dem
	h�chsten Bit von <varname role="register">EDX</varname>.
	Ein kurzer Abgleich des Zeichens wird uns also eine schnelle
	Entscheidung dar�ber erlauben, was zu tun ist.</para>

      <para>Hier ist der Code:</para>

      <programlisting>
;;;;;;; csv.asm ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; Convert a comma-separated file to a something-else separated file.
;
; Started:	31-May-2001
; Updated:	 1-Jun-2001
;
; Copyright (c) 2001 G. Adam Stanislav
; All rights reserved.
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

%include	'system.inc'

%define	BUFSIZE	2048

section	.data
fd.in	dd	stdin
fd.out	dd	stdout
usg	db	'Usage: csv [-t&lt;delim&gt;] [-c&lt;comma&gt;] [-p] [-o &lt;outfile&gt;] [-i &lt;infile&gt;]', 0Ah
usglen	equ	$-usg
iemsg	db	"csv: Can't open input file", 0Ah
iemlen	equ	$-iemsg
oemsg	db	"csv: Can't create output file", 0Ah
oemlen	equ	$-oemsg

section .bss
ibuffer	resb	BUFSIZE
obuffer	resb	BUFSIZE

section	.text
align 4
ierr:
	push	dword iemlen
	push	dword iemsg
	push	dword stderr
	sys.write
	push	dword 1		; return failure
	sys.exit

align 4
oerr:
	push	dword oemlen
	push	dword oemsg
	push	dword stderr
	sys.write
	push	dword 2
	sys.exit

align 4
usage:
	push	dword usglen
	push	dword usg
	push	dword stderr
	sys.write
	push	dword 3
	sys.exit

align 4
global	_start
_start:
	add	esp, byte 8	; discard argc and argv[0]
	mov	edx, (',' &lt;&lt; 8) | 9

.arg:
	pop	ecx
	or	ecx, ecx
	je	near .init		; no more arguments

	; ECX contains the pointer to an argument
	cmp	byte [ecx], '-'
	jne	usage

	inc	ecx
	mov	ax, [ecx]

.o:
	cmp	al, 'o'
	jne	.i

	; Make sure we are not asked for the output file twice
	cmp	dword [fd.out], stdout
	jne	usage

	; Find the path to output file - it is either at [ECX+1],
	; i.e., -ofile --
	; or in the next argument,
	; i.e., -o file

	inc	ecx
	or	ah, ah
	jne	.openoutput
	pop	ecx
	jecxz	usage

.openoutput:
	push	dword 420	; file mode (644 octal)
	push	dword 0200h | 0400h | 01h
	; O_CREAT | O_TRUNC | O_WRONLY
	push	ecx
	sys.open
	jc	near oerr

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

.i:
	cmp	al, 'i'
	jne	.p

	; Make sure we are not asked twice
	cmp	dword [fd.in], stdin
	jne	near usage

	; Find the path to the input file
	inc	ecx
	or	ah, ah
	jne	.openinput
	pop	ecx
	or	ecx, ecx
	je near usage

.openinput:
	push	dword 0		; O_RDONLY
	push	ecx
	sys.open
	jc	near ierr		; open failed

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

.p:
	cmp	al, 'p'
	jne	.t
	or	ah, ah
	jne	near usage
	or	edx, 1 &lt;&lt; 31
	jmp	.arg

.t:
	cmp	al, 't'		; redefine output delimiter
	jne	.c
	or	ah, ah
	je	near usage
	mov	dl, ah
	jmp	.arg

.c:
	cmp	al, 'c'
	jne	near usage
	or	ah, ah
	je	near usage
	mov	dh, ah
	jmp	.arg

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

	; See if we are to preserve the first line
	or	edx, edx
	js	.loop

.firstline:
	; get rid of the first line
	call	getchar
	cmp	al, 0Ah
	jne	.firstline

.loop:
	; read a byte from stdin
	call	getchar

	; is it a comma (or whatever the user asked for)?
	cmp	al, dh
	jne	.quote

	; Replace the comma with a tab (or whatever the user wants)
	mov	al, dl

.put:
	call	putchar
	jmp	short .loop

.quote:
	cmp	al, '"'
	jne	.put

	; Print everything until you get another quote or EOL. If it
	; is a quote, skip it. If it is EOL, print it.
.qloop:
	call	getchar
	cmp	al, '"'
	je	.loop

	cmp	al, 0Ah
	je	.put

	call	putchar
	jmp	short .qloop

align 4
getchar:
	or	ebx, ebx
	jne	.fetch

	call	read

.fetch:
	lodsb
	dec	ebx
	ret

read:
	jecxz	.read
	call	write

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

align 4
.done:
	call	write		; flush output buffer

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

	push	dword [fd.out]
	sys.close

	; return success
	push	dword 0
	sys.exit

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

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

      <para>Vieles daraus ist aus <filename>hex.asm</filename>
	entnommen worden. Aber es gibt einen wichtigen Unterschied:
	Ich rufe nicht l�nger <function>write</function> auf,
	wann immer ich eine Zeilenvorschub ausgebe. Nun kann der Code
	sogar interaktiv genutzt werden.</para>

      <para>Ich habe eine bessere L�sung gefunden f�r das
	Interaktivit�tsproblem seit ich mit dem Schreiben dieses
	Kapitels begonnen habe. Ich wollte sichergehen, da� jede
	Zeile einzeln ausgegeben werden kann, falls erforderlich. Aber
	schlussendlich gibt es keinen Bedarf jede Zeile einzeln
	auszugeben, falls nicht-interaktiv genutzt.</para>

      <para>Die neue L�sung besteht darin, die Funktion
	<function>write</function> jedesmal aufzurufen, wenn ich den
	Eingabepuffer leer vorfinde. Auf diesem Wege liest das
	Programm im interaktiven Modus eine Zeile aus der Tastatur des
	Nutzers, verarbeitet sie und stellt fest, ob deren
	Eingabepuffer leer ist, dann leert es seine Ausgabe und liest
	die n�chste Zeile.</para>

      <sect3 id="x86-buffered-dark-side">
	<title>Die dunkle Seite des Buffering</title>

	<para>Diese �nderung verhindert einen mysteri�sen
	  Aufh�nger in einem speziellen Fall. Ich bezeichne dies
	  als die <emphasis>dunkle Seite des Buffering</emphasis>,
	  haupts�chlich, weil es eine nicht offensichtliche
	  Gefahr darstellt.</para>

	<para>Es ist unwahrscheinlich, da� dies mit dem
	  <application>csv</application>-Programm oben geschieht aber
	  lassen Sie uns einen weiteren Filter betrachten: Nehmen wir
	  an ihre Eingabe sind rohe Daten, die Farbwerte darstellen,
	  wie z.B. die Intensit�t eines Pixel mit den Farben
	  <emphasis>rot</emphasis>, <emphasis>gr�n</emphasis> und
	  <emphasis>blau</emphasis>. Unsere Ausgabe wird der negative
	  Wert unserer Eingabe sein.</para>

	<para>Solch ein Filter w�rde sehr einfach zu schreiben
	  sein. Der gr��te Teil davon w�rde so
	  aussehen wie all die anderen Filter, die wir bisher
	  geschrieben haben, daher beziehe ich mich nur auf den Kern
	  der Prozedur:</para>

	<programlisting>.loop:
	call	getchar
	not	al		; Create a negative
	call	putchar
	jmp	short .loop</programlisting>

	<para>Da dieser Filter mit rohen Daten arbeitet ist es
	  unwahrscheinlich, da� er interaktiv genutzt werden
	  wird.</para>

	<para>Aber das Programm k�nnte als
	  Bildbearbeitungssoftware tituliert werden. Wenn es nicht
	  <function>write</function> vor jedem Aufruf von
	  <function>read</function> durchf�hrt, ist die
	  M�glichkeit gegeben, das es sich aufh�ngt.</para>

	<para>Dies k�nnte passieren:</para>

	<procedure>
	  <step>
	    <para>Der Bildeditor wird unseren Filter laden mittels der
	    C-Funktion <function>popen()</function>.</para>
	  </step>

	  <step>
	    <para>Er wird die erste Zeile von Pixeln laden aus einer
	      Bitmap oder Pixmap.</para>
	  </step>

	  <step>
	    <para>Er wird die erste Zeile von Pixeln geschrieben in
	      die <emphasis>Pipe</emphasis>, welche zur Variable
	      <varname>fd.in</varname> unseres Filters
	      f�hrt.</para>
	  </step>

	  <step>
	    <para>Unser Filter wird jeden Pixel auslesen von der
	      Eingabe, in in seinen negativen Wert umkehren und ihn in
	      den Ausgabepuffer schreiben.</para>
	  </step>

	  <step>
	    <para>Unser Filter wird die Funktion
	      <function>getchar</function> aufrufen, um das
	      n�chste Pixel abzurufen.</para>
	  </step>

	  <step>
	    <para>Die Funktion <function>getchar</function> wird einen
	      leeren Eingabepuffer vorfinden und daher die Funktion
	      <function>read</function> aufrufen.</para>
	  </step>

	  <step>
	    <para><function>read</function> wird den Systemaufruf
	      <function role="syscall">SYS_read</function>
	      starten.</para>
	  </step>

	  <step>
	    <para>Der <emphasis>Kernel</emphasis> wird unseren Filter
	      unterbrechen, bis der Bildeditor mehr Daten zur Pipe
	      sendet.</para>
	  </step>

	  <step>
	    <para>Der Bildedior wird aus der anderen Pipe lesen,
	      welche verbunden ist mit <varname>fd.out</varname>
	      unseres Filters, damit er die erste Zeile des
	      auszugebenden Bildes setzen kann
	      <emphasis>bevor</emphasis> er uns die zweite Zeile der
	      Eingabe einliest.</para>
	  </step>

	  <step>
	    <para>Der <emphasis>Kernel</emphasis> unterbricht den
	      Bildeditor, bis er eine Ausgabe unseres Filters
	      erh�lt, um ihn an den Bildeditor
	      weiterzureichen.</para>
	  </step>
	</procedure>

	<para>An diesem Punkt wartet unser Filter auf den
	  Bildeditor, da� er ihm mehr Daten zur Verarbeitung
	  schicken m�ge. Gleichzeitig wartet der Bildeditor
	  darauf, da� unser Filter das Resultat der Berechnung
	  ersten Zeile sendet. Aber das Ergebnis sitzt in unserem
	  Ausgabepuffer.</para>

	<para>Der Filter und der Bildeditor werden fortfahren bis in
	  die Ewigkeit aufeinander zu warten (oder zumindest bis sie
	  per kill entsorgt werden). Unsere Software hat den eine
	  <link linkend="secure-race-conditions">Race Condition</link>
	  erreicht.</para>

	<para>Das Problem tritt nicht auf, wenn unser Filter seinen
	  Ausgabepuffer leert <emphasis>bevor</emphasis> er vom
	  <emphasis>Kernel</emphasis> mehr Eingabedaten
	  anfordert.</para>
      </sect3>
    </sect2>
  </sect1>

  <sect1 id="x86-fpu">
    <sect1info>
      <authorgroup>
	<author>
	  <firstname>Fabian</firstname>
	  <surname>Borschel</surname>
	  <contrib>�bersetzt von </contrib>
	</author>
      </authorgroup>
    </sect1info>

    <title>Die <acronym>FPU</acronym> verwenden</title>

    <para>Seltsamerweise erw�hnt die meiste Literatur zu
      Assemblersprachen nicht einmal die Existenz der
      <acronym>FPU</acronym>, oder
      <emphasis>floating point unit</emphasis>
      (Flie�komma-Recheneinheit), geschweige denn, da�
      auf die Programmierung mit dieser eingegangen wird.</para>

    <para>Dabei kann die Assemblerprogrammierung gerade bei
      hoch optimiertem <acronym>FPU</acronym>-Code, der
      <emphasis>nur</emphasis> mit einer Assemblersprache realisiert
      werden kann, ihre gro�e St�rke ausspielen.</para>

    <sect2 id="x86-fpu-organization">
      <title>Organisation der <acronym>FPU</acronym></title>

      <para>Die <acronym>FPU</acronym> besteht aus 8 80&ndash;bit
	Flie�komma-Registern. Diese sind in Form eines
	Stacks organisiert&mdash;Sie k�nnen einen Wert durch
	den Befehl <function>push</function> auf dem
	<acronym>TOS</acronym> (<emphasis>top of stack</emphasis>)
	ablegen, oder durch <function>pop</function> von diesem
	holen.</para>

      <para>Da also die Befehle <function role="opcode">push</function>
        und <function role="opcode">pop</function> schon verwendet
	werden, kann es	keine op-Codes in Assemblersprache mit diesen
	Namen geben.</para>

      <para>Sie k�nnen mit einen Wert auf dem
	<acronym>TOS</acronym> ablegen, indem Sie
	<function role="opcode">fld</function>, <function
	role="opcode">fild</function>, und <function
	role="opcode">fbld</function> verwenden. Mit weiteren op-Codes
	lassen sich <emphasis>Konstanten</emphasis>&mdash;wie z.B.
	<emphasis>Pi</emphasis>&mdash;auf dem <acronym>TOS</acronym>
	ablegen.</para>

      <para>Analog dazu k�nnen Sie einen Wert
	holen, indem Sie <function role="opcode">fst</function>,
	<function role="opcode">fstp</function>, <function
	role="opcode">fist</function>, <function
	role="opcode">fistp</function>, und <function
	role="opcode">fbstp</function> verwenden. Eigentlich
	holen (<function>pop</function>) nur die op-Codes, die auf
	<emphasis>p</emphasis> enden, einen Wert, w�hrend die
	anderen den Wert irgendwo speichern (<function>store</function>)
	ohne ihn vom <acronym>TOS</acronym> zu entfernen.</para>

      <para>Daten k�nnen zwischen dem <acronym>TOS</acronym>
	und dem Hauptspeicher als 32&ndash;bit, 64&ndash;bit oder
	80&ndash;bit <emphasis>real</emphasis>, oder als 16&ndash;bit,
	32&ndash;bit oder 64&ndash;bit <emphasis>Integer</emphasis>,
	oder als 80&ndash;bit <emphasis>packed decimal</emphasis>
	�bertragen werden.</para>

      <para>Das 80&ndash;bit <emphasis>packed decimal</emphasis>-Format
        ist ein Spezialfall des
	<emphasis>binary coded decimal</emphasis>-Formates,
	welches �blicherweise bei der Konvertierung zwischen der
	<acronym>ASCII</acronym>- und
	<acronym>FPU</acronym>-Darstellung von Daten verwendet wird.
	Dieses erlaubt die Verwendung von 18 signifikanten
	Stellen.</para>

      <para>Unabh�ngig davon, wie Daten im Speicher dargestellt
	werden, speichert die <acronym>FPU</acronym> ihre Daten immer
	im 80&ndash;bit <emphasis>real</emphasis>-Format in den
	Registern.</para>

      <para>Ihre interne Genauigkeit betr�gt mindestens 19
	Dezimalstellen. Selbst wenn wir also Ergebnisse im
	<acronym>ASCII</acronym>-Format mit voller 18&ndash;stelliger
	Genauigkeit darstellen lassen, werden immer noch korrekte
	Werte angezeigt.</para>

      <para>Des weiteren k�nnen mathematische Operationen auf
	dem <acronym>TOS</acronym> ausgef�hrt werden: Wir
	k�nnen dessen <emphasis>Sinus</emphasis> berechnen, wir
	k�nnen ihn <emphasis>skalieren</emphasis> (z.B.
	k�nnen wir ihn mit dem Faktor 2 Multiplizieren oder
	Dividieren), wir k�nnen dessen
	<emphasis>Logarithmus</emphasis> zur Basis 2 nehmen, und viele
	weitere Dinge.</para>

      <para>Wir k�nnen auch <acronym>FPU</acronym>-Register
	<emphasis>multiplizieren</emphasis>,
	<emphasis>dividieren</emphasis>, <emphasis>addieren</emphasis>
	und <emphasis>subtrahieren</emphasis>, sogar einzelne
	Register mit sich selbst.</para>

      <para>Der offizielle Intel op-Code f�r den
	<acronym>TOS</acronym> ist <varname
	role="register">st</varname> und f�r die
	<emphasis>Register</emphasis> <varname
	role="register">st(0)</varname>&ndash; <varname
	role="register">st(7)</varname>. <varname
	role="register">st</varname> und <varname
	role="register">st(0)</varname> beziehen sich dabei auf das
	gleiche Register.</para>

      <para>Aus welchen Gr�nden auch immer hat sich der
	Originalautor von <application>nasm</application> daf�r
	entschieden, andere op-Codes zu verwenden, n�mlich
	<varname role="register">st0</varname>&ndash; <varname
	role="register">st7</varname>. Mit anderen Worten, es gibt
	keine Klammern, und der <acronym>TOS</acronym> ist immer
	<varname role="register">st0</varname>, niemals einfach nur
	<function role="opcode">st</function>.</para>

      <sect3 id="x86-fpu-packed-decimal">
	<title>Das Packed Decimal-Format</title>

	<para>Das <emphasis>packed decimal</emphasis>-Format verwendet
	  10 Bytes (80 Bits) zur Darstellung von 18 Ziffern. Die so
	  dargestellte Zahl ist immer ein
	  <emphasis>Integer</emphasis>.</para>

        <tip>
          <para>Sie k�nnen durch Multiplikation des
	    <acronym>TOS</acronym> mit Potenzen von 10 die einzelnen
	    Dezimalstellen verschieben.</para>
	</tip>

        <para>Das h�chste Bit des h�chsten Bytes (Byte 9)
	  ist das <emphasis>Vorzeichenbit</emphasis>:
	  Wenn es gesetzt ist, ist die Zahl
	  <emphasis>negativ</emphasis>, ansonsten
	  <emphasis>positiv</emphasis>. Die restlichen Bits dieses
	  Bytes werden nicht verwendet bzw. ignoriert.</para>

        <para>Die restlichen 9 Bytes enthalten die 18 Ziffern der
	  gespeicherten Zahl: 2 Ziffern pro Byte.</para>

        <para>Die <emphasis>signifikantere Ziffer</emphasis> wird in
	  der <emphasis>oberen H�lfte</emphasis> (4 Bits) eines
	  Bytes gespeichert, die andere in der
	  <emphasis>unteren H�lfte</emphasis>.</para>

        <para>Vielleicht w�rden Sie jetzt annehmen, das
	  <constant>-1234567</constant> auf die folgende Art im
	  Speicher abgelegt wird (in hexadezimaler Notation):</para>

        <programlisting>80 00 00 00 00 00 01 23 45 67</programlisting>

	<para>Dem ist aber nicht so! Bei Intel werden alle Daten im
	  <emphasis>little&ndash;endian</emphasis>-Format gespeichert,
	  auch das <emphasis>packed decimal</emphasis>-Format.</para>

        <para>Dies bedeutet, da� <constant>-1234567</constant>
	  wie folgt gespeichert wird:</para>

        <programlisting>67 45 23 01 00 00 00 00 00 80</programlisting>

        <para>Erinnern Sie sich an diesen Umstand, bevor Sie sich
	  aus lauter Verzweiflung die Haare
	  ausrei�en.</para>

        <note>
	  <para>Das lesenswerte Buch&mdash;falls Sie es finden
	    k�nnen&mdash;ist Richard Startz' <ulink
	    url="http://www.int80h.org/cgi-bin/isbn?isbn=013246604X">
	    8087/80287/80387 for the IBM PC &amp; Compatibles</ulink>.
	    Obwohl es anscheinend die Speicherung der <emphasis>packed
	    decimal</emphasis> im little&ndash;endian-Format f�r
	    gegeben annimmt. Ich mache keine Witze �ber meine
	    Verzweiflung, als ich den Fehler im unten stehenden Filter
	    gesucht habe, <emphasis>bevor</emphasis> mir einfiel,
	    da� ich einfach mal versuchen sollte, das
	    little&ndash;endian-Format, selbst f�r diesen Typ von
	    Daten, anzuwenden.</para>
	</note>
      </sect3>
    </sect2>

    <sect2 id="x86-pinhole-photography">
      <title>Ausflug in die Lochblendenphotographie</title>

      <para>Um sinnvolle Programme zu schreiben, m�ssen wir nicht
	nur unsere Programmierwerkzeuge beherrschen, sondern auch das
	Umfeld, f�r das die Programme gedacht sind.</para>

      <para>Unser n�chster Filter wird uns dabei helfen, wann
	immer wir wollen, eine <emphasis>Lochkamera</emphasis> zu
	bauen. Wir brauchen also etwas Hintergrundwissen �ber
	die <emphasis>Lochblendenphotographie</emphasis>, bevor wir
	weiter machen k�nnen.</para>

      <sect3 id="x86-camera">
	<title>Die Kamera</title>

	<para>Die einfachste Form, eine Kamera zu beschreiben, ist
	  die eines abgeschlossenen, lichtundurchl�ssigen
	  Raumes, in dessen Abdeckung sich ein kleines Loch
	  befindet.</para>

	<para>Die Abdeckung ist normalerweise fest (z.B. eine
	  Schachtel), manchmal jedoch auch flexibel (z.B. ein Balgen).
	  Innerhalb der Kamera ist es sehr dunkel. Nur durch ein
	  kleines Loch kann Licht von einem einzigen Punkt aus in den
	  Raum eindringen (in manchen F�llen sind es mehrere
	  L�cher). Diese Lichtstrahlen kommen von einem Bild,
	  einer Darstellung von dem was sich au�erhalb der
	  Kamera, vor dem kleinen Loch, befindet.</para>

	<para>Wenn ein lichtempfindliches Material (wie z.B. ein Film)
	  in der Kamera angebracht wird, so kann dieses das Bild
	  einfangen.</para>

	<para>Das Loch enth�lt h�ufig eine
	  <emphasis>Linse</emphasis>, oder etwas linsenartiges,
	  h�ufig auch einfach <emphasis>Objektiv</emphasis>
	  genannt.</para>
      </sect3>

      <sect3 id="x86-the-pinhole">
	<title>Die Lochblende</title>

	<para>Streng genommen ist die Linse nicht notwendig: Die
	  urspr�nglichen Kameras verwendeten keine Linse, sondern
	  eine <emphasis>Lochblende</emphasis>. Selbst heutzutage
	  werden noch <emphasis>Lochblenden</emphasis> verwendet,
	  zum einen, um die Funktionsweise einer Kamera zu erlernen,
	  und zum anderen, um eine spezielle Art von Bildern zu
	  erzeugen.</para>

	<para>Das Bild, das von einer <emphasis>Lochblende</emphasis>
	  erzeugt wird, ist �berall scharf. Oder unscharf. Es
	  gibt eine ideale Gr��e f�r eine Lochblende:
	  Wenn sie gr��er oder kleiner ist, verliert das
	  Bild seine Sch�rfe.</para>
      </sect3>

      <sect3 id="x86-focal-length">
	<title>Brennweite</title>

	<para>Dieser ideale Lochblendendurchmesser ist eine Funktion
	  der Quadratwurzel der <emphasis>Brennweite</emphasis>,
	  welche dem Abstand der Lochblende von dem Film
	  entspricht.</para>

	<programlisting>     D = PC * sqrt(FL)</programlisting>

	<para>Hier ist <varname>D</varname> der ideale Durchmesser der
	  Lochblende, <varname>FL</varname> die Brennweite und
	  <constant>PC</constant> eine Konstante der Brennweite. Nach
	  Jay Bender hat die Konstante den Wert
	  <constant>0.04</constant>, nach Kenneth Connors
	  <constant>0.037</constant>. Andere Leute
	  haben andere Werte vorgeschlagen. Des weiteren gelten diese
	  Werte nur f�r Tageslicht: Andere Arten von
	  Licht ben�tigen andere konstante Werte, welche nur
	  durch Experimente bestimmt werden k�nnen.</para>
      </sect3>

      <sect3 id="x86-f-number">
	<title>Der f&ndash;Wert</title>

	<para>Der f&ndash;Wert ist eine sehr n�tzliche
	  Gr��e, die angibt, wieviel Licht den Film erreicht.
	  Ein Belichtungsmesser kann dies messen, um z.B. f�r
	  einen Film mit einer Empfindlichkeit von f5.6 eine
	  Belichtungsdauer von 1/1000 Sekunden auszurechnen.</para>

	<para>Es spielt keine Rolle, ob es eine 35&ndash;mm- oder
	  eine 6x9cm-Kamera ist, usw. Solange wir den f&ndash;Wert
	  kennen, k�nnen wir die ben�tigte Belichtungszeit
	  berechnen.</para>

	<para>Der f&ndash;Wert l��t sich einfach wie folgt
	  berechnen:</para>

	<programlisting>    F = FL / D</programlisting>

	<para>Mit anderen Worten, der f&ndash;Wert ergibt sich aus
	  der Brennweite (FL), dividiert durch den Durchmesser (D) der
	  Lochblende. Ein gro�er f&ndash;Wert impliziert also
	  entweder eine kleine Lochblende, oder eine gro�e
	  Brennweite, oder beides. Je gr��er also der
	  f&ndash;Wert ist, um so l�nger mu� die
	  Belichtungszeit sein.</para>

	<para>Des weiteren sind der Lochblendendurchmesser und die
	  Brennweite eindimensionale Me�gr��en,
	  w�hrend der Film und die Lochblende an sich
	  zweidimensionale Objekte darstellen. Das bedeutet, wenn
	  man f�r einen f&ndash;Wert <varname>A</varname>
	  eine Belichtungsdauer <varname>t</varname> bestimmt hat,
	  dann ergibt sich daraus f�r einen f&ndash;Wert
	  <varname>B</varname> eine Belichtungszeit von:</para>

	<programlisting>    t * (B / A)&#178;</programlisting>
      </sect3>

      <sect3 id="x86-normalized-f-number">
	<title>Normalisierte f&ndash;Werte</title>

	<para>W�hrend heutige moderne Kameras den Durchmesser
	  der Lochblende, und damit deren f&ndash;Wert, weich und
	  schrittweise ver�ndern k�nnen, war dies
	  fr�her nicht der Fall.</para>

	<para>Um unterschiedliche f&ndash;Werte einstellen zu
	  k�nnen, besa�en Kameras typischerweise eine
	  Metallplatte mit L�chern unterschiedlichen
	  Durchmessers als Lochblende.</para>

	<para>Die Durchmesser wurden entsprechend obiger Formel
	  gew�hlt, da� der resultierende f&ndash;Wert
	  ein fester Standardwert war, der f�r alle Kameras
	  verwendet wurde. Z.B. hat eine sehr alte Kodak Duaflex
	  IV Kamera in meinem Besitz drei solche L�cher
	  f�r die f&ndash;Werte 8, 11 und 16.</para>

	<para>Eine neuere Kamera k�nnte f&ndash;Werte wie 2.8,
	  4, 5.6, 8, 11, 16, 22, und 32 (und weitere) besitzen.
	  Diese Werte wurden nicht zuf�llig ausgew�hlt:
	  Sie sind alle vielfache der Quadratwurzel aus 2, wobei
	  manche Werte gerundet wurden.</para>
      </sect3>

      <sect3 id="x86-f-stop">
	<title>Der f&ndash;Stopp</title>

	<para>Eine typische Kamera ist so konzipiert, da� die
	  Nummernscheibe bei den normalisierten f&ndash;Werten
	  einrastet. Die Nummernscheibe <emphasis>stoppt</emphasis>
	  an diesen Positionen. Daher werden diese Positionen auch
	  f&ndash;Stopps genannt.</para>

	<para>Da die f&ndash;Werte bei jedem Stopp vielfache der
	  Quadratwurzel aus 2 sind, verdoppelt die Drehung der
	  Nummernscheibe um einen Stopp die f�r die gleiche
	  Belichtung ben�tigte Lichtmenge. Eine Drehung
	  um 2 Stopps vervierfacht die ben�tigte Belichtungszeit.
	  Eine Drehung um 3 Stopps verachtfacht sie, etc.</para>
      </sect3>
    </sect2>

    <sect2 id="x86-pinhole-software">
      <title>Entwurf der Lochblenden-Software</title>

      <para>Wir k�nnen jetzt festlegen, was genau unsere
	Lochblenden-Software tun soll.</para>

      <sect3 id="xpinhole-processing-input">
	<title>Verarbeitung der Programmeingaben</title>

	<para>Da der Hauptzweck des Programms darin besteht, uns
	  beim Entwurf einer funktionierenden Lochkamera zu
	  helfen, wird die <emphasis>Brennweite</emphasis>
	  die Programmeingabe sein. Dies ist etwas, das wir ohne
	  zus�tzliche Programme feststellen k�nnen:
	  Die geeignete Brennweite ergibt sich aus der
	  Gr��e des Films und der Art des Fotos, ob
	  dieses ein "normales" Bild, ein Weitwinkelbild oder
	  ein Telebild sein soll.</para>

	<para>Die meisten bisher geschriebenen Programme arbeiteten
	  mit einzelnen Zeichen, oder Bytes, als Eingabe: Das
	  <application>hex</application>-Programm konvertierte
	  einzelne Bytes in hexadezimale Werte, das
	  <application>csv</application>-Programm lie� entweder
	  einzelne Zeichen unver�ndert, l�schte oder
	  ver�nderte sie, etc.</para>

	<para>Das Programm <application>ftuc</application> verwendete
	  einen Zustandsautomaten, um h�chstens zwei gleichzeitig
	  eingegebene Bytes zu verarbeiten.</para>

	<para>Das <application>pinhole</application>-Programm dagegen
	  kann nicht nur mit einzelnen Zeichen arbeiten, sondern
	  mu� mit gr��eren syntaktischen Einheiten
	  zurrecht kommen.</para>

	<para>Wenn wir z.B. m�chten, da� unser Programm den
	  Lochblendendurchmesser (und weitere Werte, die wir
	  sp�ter noch diskutieren werden) f�r die Brennweiten
	  <constant>100 mm</constant>, <constant>150 mm</constant> und
	  <constant>210 mm</constant> berechnet, wollen wir etwa
	  folgendes eingeben:</para>

	<screen><userinput>100, 150, 210</userinput></screen>

	<para>Unser Programm mu� mit der gleichzeitigen Eingabe
	  von mehr als nur einem einzelnen Byte zurecht kommen. Wenn
	  es eine <constant>1</constant> erkennt, mu� es wissen,
	  da� dies die erste Stelle einer dezimalen Zahl ist.
	  Wenn es eine <constant>0</constant>, gefolgt von einer
	  weiteren <constant>0</constant> sieht, mu� es wissen,
	  da� dies zwei unterschiedliche Stellen mit der
	  gleichen Zahl sind.</para>

	<para>Wenn es auf das erste Komma trifft, mu� es wissen,
	  da� die folgenden Stellen nicht mehr zur ersten
	  Zahl geh�ren. Es mu� die Stellen der ersten
	  Zahl in den Wert <constant>100</constant> konvertieren
	  k�nnen. Und die Stellen der zweiten Zahl m�ssen
	  in den Wert <constant>150</constant> konvertiert werden.
	  Und die Stellen der dritten Zahl m�ssen in den Wert
	  <constant>210</constant> konvertiert werden.</para>

	<para>Wir m�ssen festlegen, welche Trennsymbole
	  zul�ssig sind: Sollen die Eingabewerte durch Kommas
	  voneinander getrennt werden? Wenn ja, wie sollen zwei
	  Zahlen behandelt werden, die durch ein anderes Zeichen
	  getrennt sind?</para>

	<para>Ich pers�nlich mag es einfach. Entweder etwas ist
	  eine Zahl, dann wird es verarbeitet, oder es ist keine
	  Zahl, dann wird es verworfen. Ich mag es nicht, wenn sich der
	  Computer bei der <emphasis>offensichtlichen</emphasis>
	  Eingabe eines zus�tzlichen Zeichens beschwert.
	  Duh!</para>

	<para>Zus�tzlich erlaubt es mir, die Monotonie des
	  Tippens zu durchbrechen, und eine Anfrage anstelle einer
	  simplen Zahl zu stellen:</para>

	<screen><userinput>Was ist der beste Lochblendendurchmesser
	  bei einer Brennweite von 150?</userinput></screen>

	<para>Es gibt keinen Grund daf�r, die Ausgabe mehrerer
	  Fehlermeldungen aufzuteilen:</para>

	<screen>Syntax error: Was
Syntax error: ist
Syntax error: der
Syntax error: beste</screen>

	<para>Et cetera, et cetera, et cetera.</para>

	<para>Zweitens mag ich das <constant>#</constant>-Zeichen, um
	  Kommentare zu markieren, die ab dem Zeichen bis zum Ende der
	  jeweiligen Zeile gehen. Dies verlangt nicht viel
	  Programmieraufwand, und erm�glicht es mir, Eingabedateien
	  f�r meine Programme als ausf�hrbare Skripte zu
	  handhaben.</para>

	<para>In unserem Fall m�ssen wir auch entscheiden, in
	  welchen Einheiten die Dateneingabe erfolgen soll: Wir
	  w�hlen <emphasis>Millimeter</emphasis>, da die meisten
	  Photographen die Brennweite in dieser Einheit messen.</para>

	<para>Letztendlich m�ssen wir noch entscheiden, ob wir die
	  Verwendung des dezimalen Punktes erlauben (in diesem Fall
	  m�ssen wir ber�cksichtigen, da� in vielen
	  L�ndern der Welt das dezimale <emphasis>Komma</emphasis>
	  verwendet wird).</para>

	<para>In unserem Fall w�rde das Zulassen eines dezimalen
	  Punktes/Kommas zu einer f�lschlicherweise angenommenen,
	  h�heren Genauigkeit f�hren: Der Unterschied
	  zwischen den Brennweiten <constant>50</constant> und
	  <constant>51</constant> ist fast nicht wahrnehmbar. Die
	  Zulassung von Eingaben wie <constant>50.5</constant> ist
	  also keine gute Idee. Beachten Sie bitte, das dies meine
	  Meinung ist. In diesem Fall bin ich der Autor des Programmes.
	  Bei Ihren eigenen Programmen m�ssen Sie selbst solche
	  Entscheidungen treffen.</para>
      </sect3>

      <sect3 id="x86-pinhole-options">
	<title>Optionen anbieten</title>

	<para>Das wichtigste, was wir zum Bau einer Lochkamera
	  wissen m�ssen, ist der Durchmesser der Lochblende. Da
	  wir scharfe Bilder schie�en wollen, werden wir obige
	  Formel f�r die Berechnung des korrekten Durchmessers zu
	  gegebener Brennweite verwenden. Da Experten mehrere
	  Werte f�r die <constant>PC</constant>-Konstante
	  anbieten, m�ssen wir uns hier f�r einen Wert
	  entscheiden.</para>

	<para>In der Programmierung unter &unix; ist es �blich,
	  zwei Hauptvarianten anzubieten, um Parameter an Programme zu
	  �bergeben, und des weiteren eine Standardeinstellung
	  f�r den Fall zu haben, das der Benutzer gar keine
	  Parameter angibt.</para>

	<para>Warum zwei Varianten, Parameter anzugeben?</para>

	<para>Ein Grund ist, eine (relativ) <emphasis>feste</emphasis>
	  Einstellung anzubieten, die automatisch bei jedem
	  Programmaufruf verwendet wird, ohne das wir diese
	  Einstellung immer und immer wieder mit angeben
	  m�ssen.</para>

	<para>Die feste Einstellung kann in einer Konfigurationsdatei
	  gespeichert sein, typischerweise im Heimatverzeichnis des
	  Benutzers. Die Datei hat �blicherweise denselben Namen
	  wie das zugeh�rige Programm, beginnt jedoch mit einem
	  Punkt. H�ufig wird <emphasis>"rc"</emphasis> dem
	  Dateinamen hinzugef�gt. Unsere Konfigurationsdatei
	  k�nnte also <filename>~/.pinhole</filename> oder
	  <filename>~/.pinholerc</filename> hei�en. (Die
	  Zeichenfolge <filename>~/</filename> steht f�r das
	  Heimatverzeichnis des aktuellen Benutzers.)</para>

	<para>Konfigurationsdateien werden h�ufig von Programmen
	  verwendet, die viele konfigurierbare Parameter besitzen.
	  Programme, die nur eine (oder wenige) Parameter anbieten,
	  verwenden h�ufig eine andere Methode: Sie erwarten die
	  Parameter in einer <emphasis>Umgebungsvariablen</emphasis>.
	  In unserem Fall k�nnten wir eine Umgebungsvariable mit
	  dem Namen <varname>PINHOLE</varname> benutzen.</para>

	<para>Normalerweise verwendet ein Programm entweder die eine,
	  oder die andere der beiden obigen Methoden. Ansonsten
	  k�nnte ein Programm verwirrt werden, wenn eine
	  Konfigurationsdatei das eine sagt, die Umgebungsvariable
	  jedoch etwas anderes.</para>

	<para>Da wir nur <emphasis>einen</emphasis> Parameter
	  unterst�tzen m�ssen, verwenden wir die zweite
	  Methode, und benutzen eine Umgebungsvariable mit dem
	  Namen <varname>PINHOLE</varname>.</para>

	<para>Der andere Weg erlaubt uns, <emphasis>ad hoc</emphasis>
	  Entscheidungen zu treffen: <emphasis>"Obwohl ich
	  normalerweise einen Wert von 0.039 verwende, will ich dieses
	  eine Mal einen Wert von 0.03872 anwenden."</emphasis>
	  Mit anderen Worten, dies erlaubt uns, die Standardeinstellung
	  au�er Kraft zu setzen.</para>

	<para>Diese Art der Auswahl wird h�ufig �ber
	  Kommandozeilenparameter gemacht.</para>

	<para>Schlie�lich braucht ein Programm
	  <emphasis>immer</emphasis> eine
	  <emphasis>Standardeinstellung</emphasis>. Der Benutzer
	  k�nnte keine Parameter angeben. Vielleicht wei�
	  er auch gar nicht, was er einstellen sollte. Vielleicht will
	  er es "einfach nur ausprobieren". Vorzugsweise wird die
	  Standardeinstellung eine sein, die die meisten Benutzer
	  sowieso w�hlen w�rden. Somit m�ssen diese
	  keine zus�tzlichen Parameter angeben, bzw. k�nnen
	  die Standardeinstellung ohne zus�tzlichen Aufwand
	  benutzen.</para>

	<para>Bei diesem System k�nnte das Programm
	  widerspr�chliche Optionen vorfinden, und auf die
	  folgende Weise reagieren:</para>

	<procedure>
	  <step>
	    <para>Wenn es eine <emphasis>ad
	      hoc</emphasis>-Einstellung vorfindet (z.B. ein
	      Kommandozeilenparameter), dann sollte es diese
	      Einstellung annehmen. Es mu� alle vorher
	      festgelegten sowie die standardm��ige
	      Einstellung ignorieren.</para>
	  </step>

	  <step>
	    <para><emphasis>Andererseits</emphasis>, wenn es eine
	      festgelegte Option (z.B. eine Umgebungsvariable)
	      vorfindet, dann sollte es diese akzeptieren und die
	      Standardeinstellung ignorieren.</para>
	  </step>

	  <step>
	    <para><emphasis>Ansonsten</emphasis> sollte es die
	      Standardeinstellung verwenden.</para>
	  </step>
	</procedure>

	<para>Wir m�ssen auch entscheiden, welches
	  <emphasis>Format</emphasis> unsere
	  <constant>PC</constant>-Option haben soll.</para>

	<para>Auf den ersten Blick scheint es einleuchtend, das
	  Format <varname>PINHOLE=0.04</varname> f�r die
	  Umgebungsvariable, und <parameter>-p0.04</parameter>
	  f�r die Kommandozeile zu verwenden.</para>

	<para>Dies zuzulassen w�re eigentlich eine
	  Sicherheitsl�cke. Die <constant>PC</constant>-Konstante
	  ist eine sehr kleine Zahl. Daher w�rden wir unsere
	  Anwendung mit verschiedenen, kleinen Werten f�r
	  <constant>PC</constant> testen. Aber was w�rde
	  passieren, wenn jemand das Programm mit einem sehr
	  gro�en Wert aufrufen w�rde?</para>

	<para>Es k�nnte abst�rzen, weil wir das Programm
	  nicht f�r den Umgang mit gro�en Werten
	  entworfen haben.</para>

	<para>Oder wir investieren noch weiter Zeit in das Programm,
	  so da� dieses dann auch mit gro�en Zahlen
	  umgehen kann. Wir k�nnten dies machen, wenn wir
	  kommerzielle Software f�r computertechnisch
	  unerfahrene Benutzer schreiben w�rden.</para>

	<para>Oder wir k�nnten auch sagen <emphasis>"Pech gehabt!
	  Der Benutzer sollte es besser wissen."</emphasis></para>

	<para>Oder wir k�nnten es f�r den Benutzer
	  unm�glich machen, gro�e Zahlen einzugeben. Dies
	  ist die Variante, die wir verwenden werden: Wir nehmen einen
	  <emphasis>impliziten 0.</emphasis>-Pr�fix an.</para>

	<para>Mit anderen Worten, wenn der Benutzer den Wert
	  <constant>0.04</constant> angeben will, so mu� er
	  entweder <parameter>-p04</parameter> als Parameter angeben,
	  oder <varname>PINHOLE=04</varname> als Variable in seiner
	  Umgebung definieren. Falls der Benutzer
	  <parameter>-p9999999</parameter> angibt, so wird dies als
	  <constant>0.9999999</constant> interpretiert&mdash;zwar
	  immer noch sinnlos, aber zumindest sicher.</para>

	<para>Zweitens werden viele Benutzer einfach die Konstanten
	  von Bender oder Connors benutzen wollen. Um es diesen
	  Benutzern einfacher zu machen, werden wir
	  <parameter>-b</parameter> als <parameter>-p04</parameter>,
	  und <parameter>-c</parameter> als
	  <parameter>-p037</parameter> interpretieren.</para>
      </sect3>

      <sect3 id="x86-pinhole-output">
	<title>Die Ausgabe</title>

	<para>Wir m�ssen festlegen, was und in welchem Format
	  unsere Anwendung Daten ausgeben soll.</para>

	<para>Da wir als Eingabe beliebig viele Brennweiten
	  erlauben, macht es Sinn, die Ergebnisse in Form
	  einer traditionellen Datenbank&ndash;Ausgabe darzustellen,
	  bei der zeilenweise zu jeder Brennweite der
	  zugeh�rige berechnete Wert, getrennt durch ein
	  <constant>tab</constant>-Zeichen, ausgegeben wird.</para>

	<para>Optional sollten wir dem Benutzer die M�glichkeit
	  geben, die Ausgabe in dem schon beschriebenen
	  <acronym>CSV</acronym>-Format festzulegen. In diesem Fall
	  werden wir zu Beginn der Ausgabe eine Zeile einf�gen,
	  in der die Beschreibungen der einzelnen Felder, durch
	  Kommas getrennt, aufgelistet werden, gefolgt von der Ausgabe
	  der Daten wie schon beschrieben, wobei das
	  <constant>tab</constant>-Zeichen durch ein
	  <constant>Komma</constant> ersetzt wird.</para>

	<para>Wir brauchen eine Kommandozeilenoption f�r das
	  <acronym>CSV</acronym>-Format. Wir k�nnen nicht
	  <parameter>-c</parameter> verwenden, da diese Option bereits
	  f�r <emphasis>verwende Connors Konstante</emphasis>
	  steht. Aus irgendeinem seltsamen Grund bezeichnen
	  viele Webseiten <acronym>CSV</acronym>-Dateien als
	  <emphasis>"Excel Kalkulationstabelle"</emphasis> (obwohl das
	  <acronym>CSV</acronym>-Format �lter ist als Excel). Wir
	  werden daher <parameter>-e</parameter> als Schalter f�r
	  die Ausgabe im <acronym>CSV</acronym>-Format
	  verwenden.</para>

	<para>Jede Zeile der Ausgabe wird mit einer Brennweite
	  beginnen. Dies mag auf den ersten Blick �berfl�ssig
	  erscheinen, besonders im interaktiven Modus: Der Benutzer
	  gibt einen Wert f�r die Brennweite ein, und das Programm
	  wiederholt diesen.</para>

	<para>Der Benutzer kann jedoch auch mehrere Brennweiten
	  in einer Zeile angeben. Die Eingabe kann auch aus einer Datei,
	  oder aus der Ausgabe eines anderen Programmes, kommen. In
	  diesen F�llen sieht der Benutzer die Eingabewerte
	  �berhaupt nicht.</para>

	<para>Ebenso kann die Ausgabe in eine Datei umgelenkt werden,
	  was wir sp�ter noch untersuchen werden, oder sie
	  k�nnte an einen Drucker geschickt werden, oder auch
	  als Eingabe f�r ein weiteres Programm dienen.</para>

	<para>Es macht also wohl Sinn, jede Zeile mit einer durch den
	  Benutzer eingegebenen Brennweite beginnen zu lassen.</para>

	<para>Halt! Nicht, wie der Benutzer die Daten eingegeben hat.
	  Was passiert, wenn der Benutzer etwas wie folgt
	  eingibt:</para>

	<screen><userinput>00000000150</userinput></screen>

	<para>Offensichtlich m�ssen wir die f�hrenden Nullen
	  vorher abschneiden.</para>

	<para>Wir m�ssen also die Eingabe des Benutzers
	  sorgf�ltig pr�fen, diese dann in der
	  <acronym>FPU</acronym> in die bin�re Form konvertieren,
	  und dann von dort aus ausgeben.</para>

	<para>Aber...</para>

	<para>Was ist, wenn der Benutzer etwas wie folgt eingibt:</para>

	<screen><userinput>17459765723452353453534535353530530534563507309676764423</userinput></screen>

	<para>Ha! Das packed decimal-Format der <acronym>FPU</acronym>
	  erlaubt uns die Eingabe einer 18&ndash;stelligen
	  Zahl. Aber der Benutzer hat mehr als 18 Stellen eingegeben.
	  Wie gehen wir damit um?</para>

	<para>Wir <emphasis>k�nnten</emphasis> unser Programm
	  so modifizieren, da� es die ersten 18 Stellen liest,
	  der <acronym>FPU</acronym> �bergibt, dann weitere
	  18 Stellen liest, den Inhalt des <acronym>TOS</acronym>
	  mit einem Vielfachen von 10, entsprechend der Anzahl der
	  zus�tzlichen Stellen multipliziert, und dann beide
	  Werte mittels <function>add</function> zusammen
	  addiert.</para>

	<para>Ja, wir k�nnten das machen. Aber in
	  <emphasis>diesem</emphasis> Programm w�re es
	  unn�tig (in einem anderen w�re es vielleicht
	  der richtige Weg): Selbst der Erdumfang in Millimetern ergibt
	  nur eine Zahl mit 11 Stellen. Offensichtlich k�nnen wir
	  keine Kamera dieser Gr��e bauen (jedenfalls jetzt
	  noch nicht).</para>

	<para>Wenn der Benutzer also eine so gro�e Zahl eingibt,
	  ist er entweder gelangweilt, oder er testet uns, oder er
	  versucht, in das System einzudringen, oder er spielt&mdash;
	  indem er irgendetwas anderes macht als eine Lochkamera
	  zu entwerfen.</para>

	<para>Was werden wir tun?</para>

	<para>Wir werden ihn ohrfeigen, gewisserma�en:</para>

	<screen>17459765723452353453534535353530530534563507309676764423	???	???	???	???	???</screen>

	<para>Um dies zu erreichen, werden wir einfach alle
	  f�hrenden Nullen ignorieren. Sobald wir eine Ziffer
	  gefunden haben, die nicht Null ist, initialisieren wir
	  einen Z�hler mit <constant>0</constant> und
	  beginnen mit drei Schritten:</para>

	<procedure>
	  <step>
	    <para>Sende die Ziffer an die Ausgabe.</para>
	  </step>

	  <step>
	    <para>F�ge die Ziffer einem Puffer hinzu, welchen wir
	      sp�ter benutzen werden, um den packed decimal-Wert
	      zu erzeugen, den wir an die
	      <acronym>FPU</acronym> schicken k�nnen.</para>
	  </step>

	  <step>
	    <para>Erh�he den Z�hler um eins.</para>
	  </step>
	</procedure>

	<para>W�hrend wir diese drei Schritte wiederholen,
	  m�ssen wir auf zwei Bedingungen achten:</para>

	<itemizedlist>
	  <listitem>
	    <para>Wenn der Z�hler den Wert 18 �bersteigt,
	      h�ren wir auf, Ziffern dem Puffer hinzuzuf�gen.
	      Wir lesen weiterhin Ziffern und senden sie an die
	      Ausgabe.</para>
	  </listitem>

	  <listitem>
	    <para>Wenn, bzw. <emphasis>falls</emphasis>, das
	      n�chste Eingabezeichen keine Zahl ist, sind wir mit
	      der Bearbeitung der Eingabe erst einmal fertig.</para>

	    <para>�brigends k�nnen wir einfach Zeichen, die
	      keine Ziffern sind, verwerfen, solange sie kein
	      <constant>#</constant>-Zeichen sind, welches wir an den
	      Eingabestrom zur�ckgeben m�ssen. Dieses Zeichen
	      markiert den Beginn eines Kommentars. An dieser Stelle
	      mu� die Erzeugung der Ausgabe fertig sein, und wir
	      m�ssen mit der Suche nach weiteren Eingabedaten
	      fortfahren.</para>
	  </listitem>
	</itemizedlist>

	<para>Es bleibt immer noch eine M�glichkeit
	  unber�cksichtigt: Wenn der Benutzer eine Null (oder
	  mehrere) eingibt, werden wir niemals eine von Null
	  verschiedene Zahl vorfinden.</para>

	<para>Wir k�nnen solch einen Fall immer anhand des
	  Z�hlerstandes feststellen, welcher dann
	  immer bei <constant>0</constant> bleibt. In diesem Fall
	  m�ssen wir einfach eine <constant>0</constant> an
	  die Ausgabe senden, und anschlie�end dem Benutzer
	  erneut eine "Ohrfeige" verpassen:</para>

	<screen>0	???	???	???	???	???</screen>

	<para>Sobald wir die Brennweite ausgegeben, und die
	  G�ltigkeit dieser Eingabe verifiziert haben,
	  (gr��er als <constant>0</constant> und kleiner
	  als 18 Zahlen) k�nnen wir den Durchmesser der
	  Lochblende berechnen.</para>

	<para>Es ist kein Zufall, da�
	  <emphasis>Lochblende</emphasis> das Wort
	  <emphasis>Loch</emphasis> enth�lt. In der Tat ist eine
	  Lochblende buchst�blich eine
	  <emphasis>Loch Blende</emphasis>, also eine Blende, in die
	  mit einer Nadel vorsichtig ein kleines Loch gestochen
	  wird.</para>

	<para>Daher ist eine typische Lochblende sehr klein. Unsere
	  Formel liefert uns das Ergebnis in Millimetern. Wir werden
	  dieses mit <constant>1000</constant> multiplizieren, so
	  da� die Ausgabe in <constant>Mikrometern</constant>
	  erfolgt.</para>

	<para>An dieser Stelle m�ssen wir auf eine weitere Falle
	  achten: <emphasis>Zu hohe Genauigkeit.</emphasis></para>

	<para>Ja, die <acronym>FPU</acronym> wurde f�r
	  mathematische Berechnungen mit hoher Genauigkeit entworfen.
	  Unsere Berechnungen hier erfordern jedoch keine solche
	  mathematische Genauigkeit. Wir haben es hier mit Physik zu
	  tun (Optik, um genau zu sein).</para>

	<para>Angenommen, wir wollten aus eine Lastkraftwagen eine
	  Lochkamera bauen (wir w�ren dabei nicht die
	  ersten, die das versuchen w�rden!). Angenommen, die
	  L�nge des Laderaumes betr�gt <constant>12</constant>
	  Meter lang, so da� wir eine Brennweite von
	  <constant>12000</constant> h�tten. Verwenden wir
	  Benders Konstante, so erhalten wir durch Multiplizieren
	  von <constant>0.04</constant> mit der Quadratwurzel aus
	  <constant>12000</constant> einen Wert von
	  <constant>4.381780460</constant> Millimetern, oder
	  <constant>4381.780460</constant> Micrometern.</para>

	<para>So oder so ist das Rechenergebnis absurd pr�zise.
	  Unser Lastkraftwagen ist nicht <emphasis>genau</emphasis>
	  <constant>12000</constant> Millimeter lang. Wir haben
	  diese L�nge nicht mit einer so hohen Genauigkeit
	  gemessen, weswegen es falsch w�re zu behaupten,
	  unser Lochblendendurchmesser m�sse exakt
	  <constant>4.381780460</constant> Millimeter sein. Es
	  reicht vollkommen aus, wenn der Durchmesser
	  <constant>4.4</constant> Millimeter betr�gt.</para>

	<note>
	  <para>Ich habe in obigem Beispiel das Rechenergebnis "nur"
	    auf 10 Stellen genau angegeben. Stellen Sie sich vor,
	    wie absurd es w�re, die vollen uns zur Verf�gung
	    stehenden, 18 Stellen anzugeben!</para>
	</note>

	<para>Wir m�ssen also die Anzahl der signifikanten Stellen
	  beschr�nken. Eine M�glichkeit w�re, die
	  Mikrometer durch eine ganze Zahl darzustellen. Unser
	  Lastkraftwaren w�rde dann eine Lochblende mit einem
	  Durchmesser von <constant>4382</constant> Mikrometern
	  ben�tigen. Betrachten wir diesen Wert, dann stellen wir
	  fest, das <constant>4400</constant> Mikrometer, oder
	  <constant>4.4</constant> Millimeter, immer noch genau
	  genug ist.</para>

	<para>Zus�tzlich k�nnen wir noch, unabh�ngig von
	  der Gr��e eines Rechenergebnisses, festlegen,
	  da� wir nur vier signifikante Stellen anzeigen wollen
	  (oder weniger). Leider bietet uns die <acronym>FPU</acronym>
	  nicht die M�glichkeit, das Ergebnis automatisch bis
	  auf eine bestimmte Stelle zu runden (sie sieht die Daten ja
	  nicht als Zahlen, sondern als bin�re Daten an).</para>

	<para>Wir m�ssen also selber einen Algorithmus entwerfen,
	  um die Anzahl der signifikanten Stellen zu reduzieren.</para>

	<para>Hier ist meiner (ich denke er ist peinlich&mdash;wenn
	  Ihnen ein besserer Algorithmus einf�llt, verraten sie
	  ihn mir <emphasis>bitte</emphasis>):</para>

	<procedure>
	  <step>
	    <para>Initialisiere einen Z�hler mit
	      <constant>0</constant>.</para>
	  </step>

	  <step>
	    <para>Solange die Zahl gr��er oder gleich
	      <constant>10000</constant> ist, dividiere die Zahl durch
	      <constant>10</constant>, und erh�he den Z�hler
	      um eins.</para>
	  </step>

	  <step>
	    <para>Gebe das Ergebnis aus.</para>
	  </step>

	  <step>
	    <para>Solange der Z�hler gr��er als
	      <constant>0</constant> ist, gebe eine
	      <constant>0</constant> aus, und reduziere den Z�hler
	      um eins.</para>
	  </step>
	</procedure>

	<note>
	  <para>Der Wert <constant>10000</constant> ist nur f�r
	    den Fall, da� Sie <emphasis>vier</emphasis>
	    signifikante Stellen haben wollen. F�r eine andere
	    Anzahl signifikanter Stellen m�ssen Sie den Wert
	    <constant>10000</constant> mit <constant>10</constant>,
	    hoch der Anzahl der gew�nschten signifikanten Stellen,
	    ersetzen.</para>
	</note>

	<para>Wir k�nnen so den Lochblendendurchmesser, auf vier
	  signifikante Stellen gerundet, ausgeben.</para>

	<para>An dieser Stellen kennen wir nun die <emphasis>Brennweite
	  </emphasis> und den
	  <emphasis>Lochblendendurchmesser</emphasis>. Wir haben also
	  jetzt genug Informationen, um den
	  <emphasis>f&ndash;Wert</emphasis> zu bestimmen.</para>

	<para>Wir werden den f&ndash;Wert, auf vier signifikante
	  Stellen gerundet, ausgeben. Es k�nnte passieren,
	  da� diese vier Stellen recht wenig aussagen. Um die
	  Aussagekraft des f&ndash;Wertes zu erh�hen, k�nnten
	  wir den n�chstliegenden, <emphasis>normalisierten
	  f&ndash;Wert</emphasis> bestimmen, also z.B. das
	  n�chstliegende Vielfache der Quadratwurzel aus 2.</para>

	<para>Wir erreichen dies, indem wir den aktuellen f&ndash;Wert
	  mit sich selbst multiplizieren, so da� wir dessen
	  Quadrat (<function>square</function>) erhalten.
	  Anschlie�end berechnen wir den Logarithmus zur Basis 2
	  von dieser Zahl. Dies ist sehr viel einfacher, als direkt den
	  Logarithmus zur Basis der Quadratwurzel aus 2 zu berechnen!
	  Wir runden dann das Ergebnis auf die n�chstliegende
	  ganze Zahl. Genau genommen k�nnen wir mit Hilfe der
	  <acronym>FPU</acronym> diese Berechnung beschleunigen: Wir
	  k�nnen den op-Code
	  <function role="opcode">fscale</function> verwenden, um eine
	  Zahl um 1 zu "skalieren", was dasselbe ist, wie eine Zahl
	  mittels <function role="opcode">shift</function> um eine
	  Stelle nach links zu verschieben. Am Ende berechnen wir noch
	  die Quadratwurzel aus allem, und erhalten dann den
	  n�chstliegenden, normalisierten f&ndash;Wert.</para>

	<para>Wenn das alles jetzt viel zu kompliziert wirkt&mdash;oder
	  viel zu aufwendig&mdash;wird es vielleicht klarer, wenn man
	  den Code selber betrachtet. Wir ben�tigen insgesamt
	  9 op-Codes:</para>

	<programlisting>fmul    st0, st0
    fld1
    fld     st1
    fyl2x
    frndint
    fld1
    fscale
    fsqrt
    fstp    st1</programlisting>

	<para>Die erste Zeile,
	  <function role="opcode">fmul st0, st0</function>, quadriert
	  den Inhalt des <acronym>TOS</acronym> (Top Of Stack, was
	  dasselbe ist wie <varname role="register">st</varname>, von
	  <application>nasm</application> auch
	  <varname role="register">st0</varname> genannt). Die Funktion
	  <function role="opcode">fld1</function> f�gt eine
	  <constant>1</constant> dem <acronym>TOS</acronym>
	  hinzu.</para>

	<para>Die n�chste Zeile, <function role="opcode">fld
	  st1</function>, legt das Quadrat auf dem
	  <acronym>TOS</acronym> ab. An diesem Punkt befindet sich das
	  Quadrat sowohl in <varname role="register">st</varname> als
	  auch in <varname role="register">st(2)</varname> (es wird
	  sich gleich zeigen, warum wir eine zweite Kopie auf dem
	  Stack lassen.) <varname role="register">st(1)</varname>
	  enth�lt die <constant>1</constant>.</para>

	<para>Im n�chsten Schritt, <function
	  role="opcode">fyl2x</function>, wird der Logarithmus von
	  <varname role="register">st</varname> zur Basis 2 berechnet,
	  und anschlie�end mit <varname
	  role="register">st(1)</varname> multipliziert. Deshalb haben
	  wir vorher die <constant>1</constant> in <varname
	  role="register">st(1)</varname> abgelegt.</para>

	<para>An dieser Stelle enth�lt
	  <varname role="register">st</varname> den gerade berechneten
	  Logarithmus, und <varname role="register">st(1)</varname>
	  das Quadrat des aktuellen f&ndash;Wertes, den wir f�r
	  sp�ter gespeichert haben.</para>

	<para><function role="opcode">frndint</function> rundet den
	  <acronym>TOS</acronym> zur n�chstliegenden ganzen Zahl.
	  <function role="opcode">fld1</function> legt eine
	  <constant>1</constant> auf dem Stack ab.
	  <function role="opcode">fscale</function> shiftet die
	  <constant>1</constant> auf dem <acronym>TOS</acronym> um
	  <varname role="register">st(1)</varname> Stellen, wodurch im
	  Endeffekt eine 2 in <varname role="register">st(1)</varname>
	  steht.</para>

	<para>Schlie�lich berechnet
	  <function role="opcode">fsqrt</function> die Quadratwurzel
	  des Rechenergebnisses, also des n�chstliegenden,
	  normalisierten f&ndash;Wertes.</para>

	<para>Wir haben nun den n�chstliegenden, normalisierten
	  f&ndash;Wert auf dem <acronym>TOS</acronym> liegen, den auf
	  den Logarithmus zur Basis 2 gerundeten, n�chstliegenden
	  ganzzahligen Wert in <varname
	  role="register">st(1)</varname>, und das Quadrat des
	  aktuellen f&ndash;Wertes in <varname
	  role="register">st(2)</varname>. Wir speichern den Wert
	  f�r eine sp�tere Verwendung in <varname
	  role="register">st(2)</varname>.</para>

	<para>Aber wir brauchen den Inhalt von
	  <varname role="register">st(1)</varname> gar nicht mehr. Die
	  letzte Zeile, <function role="opcode">fstp st1</function>,
	  platziert den Inhalt von <varname role="register">st</varname>
	  in <varname role="register">st(1)</varname>, und erniedrigt
	  den Stackpointer um eins. Dadurch ist der Inhalt von
	  <varname role="register">st(1)</varname> jetzt
	  <varname role="register">st</varname>, der Inhalt von
	  <varname role="register">st(2)</varname> jetzt
	  <varname role="register">st(1)</varname> usw. Der neue
	  <varname role="register">st</varname> speichert jetzt den
	  normalisierten f&ndash;Wert. Der neue
	  <varname role="register">st(1)</varname> speichert das
	  Quadrat des aktuellen f&ndash;Wertes f�r die
	  Nachwelt.</para>

	<para>Jetzt k�nnen wir den normalisierten f&ndash;Wert
	  ausgeben. Da er normalisiert ist, werden wir ihn nicht auf
	  vier signifikante Stellen runden, sondern stattdessen
	  mit voller Genauigkeit ausgeben.</para>

	<para>Der normalisierte f&ndash;Wert ist n�tzlich, solange
	  er so klein ist, da� wir ihn auf einem Photometer
	  wiederfinden k�nnen. Ansonsten brauchen wir eine andere
	  Methode, um die ben�tigten Belichtungsdaten zu
	  bestimmen.</para>

	<para>Wir haben weiter oben eine Formel aufgestellt, �ber
	  die wir einen f&ndash;Wert mit Hilfe eines anderen
	  f&ndash;Wertes und den zugeh�rigen Belichtungsdaten
	  bestimmen k�nnen.</para>

	<para>Jedes Photometer, das ich jemals gesehen habe, konnte
	  die ben�tigte Belichtungszeit f�r f5.6 berechnen.
	  Wir werden daher einen
	  <emphasis>"f5.6 Multiplizierer"</emphasis> berechnen, der
	  uns den Faktor angibt, mit dem wir die bei f5.6 gemessene
	  Belichtungszeit f�r unsere Lochkamera multiplizieren
	  m�ssen.</para>

	<para>Durch die Formel wissen wir, da� dieser Faktor
	  durch Dividieren unseres f&ndash;Wertes (der aktuelle Wert,
	  nicht der normalisierte) durch <constant>5.6</constant>
	  und anschlie�endes Quadrieren, berechnen
	  k�nnen.</para>

	<para>Mathematisch �quivalent dazu w�re, wenn wir
	  das Quadrat unseres f&ndash;Wertes durch das Quadrat von
	  <constant>5.6</constant> dividieren w�rden.</para>

	<para>Numerisch betrachtet wollen wir nicht zwei Zahlen
	  quadrieren, wenn es m�glich ist, nur
	  eine Zahl zu quadrieren. Daher wirkt die erste Variante
	  auf den ersten Blick besser.</para>

	<para>Aber...</para>

	<para><constant>5.6</constant> ist eine
	  <emphasis>Konstante</emphasis>. Wir m�ssen nicht
	  wertvolle Rechenzeit der <acronym>FPU</acronym> verschwenden.
	  Es reicht aus, da� wir die Quadrate der einzelnen
	  f&ndash;Werte durch den konstanten Wert
	  <constant>5.6&#178;</constant> dividieren. Oder wir
	  k�nnen den jeweiligen f&ndash;Wert durch
	  <constant>5.6</constant> dividieren, und dann das Ergebnis
	  quadrieren. Zwei M�glichkeiten, die gleich
	  erscheinen.</para>

	<para>Aber das sind sie nicht!</para>

	<para>Erinnern wir uns an die Grundlagen der Photographie
	  weiter oben, dann wissen wir, da� sich die
	  Konstante <constant>5.6</constant> aus dem 5-fachen der
	  Quadratwurzel aus 2 ergibt. Eine
	  <emphasis>irrationale</emphasis> Zahl. Das Quadrat dieser
	  Zahl ist <emphasis>exakt</emphasis>
	  <constant>32</constant>.</para>

	<para><constant>32</constant> ist nicht nur eine ganze Zahl,
	  sondern auch ein Vielfaches von 2. Wir brauchen also
	  gar nicht das Quadrat eines f&ndash;Wertes durch
	  <constant>32</constant> zu teilen. Wir m�ssen lediglich
	  mittels <function role="opcode">fscale</function> den
	  f&ndash;Wert um f�nf Stellen nach rechts shiften.
	  Aus Sicht der <acronym>FPU</acronym> m�ssen wir also
	  <function role="opcode">fscale</function> mit
	  <varname role="register">st(1)</varname>, welcher gleich
	  <constant>-5</constant> ist, auf den f&ndash;Wert anwenden.
	  Dies ist <emphasis>sehr viel schneller</emphasis> als die
	  Division.</para>

	<para>Jetzt wird es auch klar, warum wir das Quadrat des
	  f&ndash;Wertes ganz oben auf dem Stack der
	  <acronym>FPU</acronym> gespeichert haben. Die Berechnung
	  des f5.6 Multiplizierers ist die einfachste Berechnung
	  des gesamten Programmes! Wir werden das Ergebnis auf vier
	  signifikante Stellen gerundet ausgeben.</para>

	<para>Es gibt noch eine weitere n�tzliche Zahl, die wir
	  berechnen k�nnen: Die Anzahl der Stopps, die unser
	  f&ndash;Wert von f5.6 entfernt ist. Dies k�nnte
	  hilfreich sein, wenn unser f&ndash;Wert au�erhalb des
	  Me�bereiches unseres Photometers liegt, wir aber eine
	  Blende haben, bei der wir unterschiedliche Geschwindigkeiten
	  einstellen k�nnen, und diese Blende Stopps
	  benutzt.</para>

	<para>Angenommen, unser f&ndash;Wert ist 5 Stopps von f5.6
	  entfernt, und unser Photometer sagt uns, da� wir eine
	  Belichtungszeit von 1/1000 Sek. einstellen sollen. Dann
	  k�nnen wir unsere Blende auf die Geschwindigkeit 1/1000
	  einstellen, und unsere Skala um 5 Stopps verschieben.</para>

	<para>Diese Rechnung ist ebenfalls sehr einfach. Alles, was
	  wir tun m�ssen, ist, den Logarithmus des f5.6
	  Multiplizierers, den wir schon berechnet haben (wobei wir
	  dessen Wert vor der Rundung nehmen m�ssen) zur Basis 2
	  zu nehmen. Wir runden dann das Ergebnis zur n�chsten
	  ganzen Zahl hin, und geben dies aus. Wir m�ssen uns
	  nicht darum k�mmern, ob wir mehr als vier signifikante
	  Stellen haben: Das Ergebnis besteht
	  h�chstwahrscheinlich nur aus einer oder zwei
	  Stellen.</para>
      </sect3>
    </sect2>

    <sect2 id="x86-fpu-optimizations">
      <title>FPU Optimierungen</title>

      <para>In Assemblersprache k�nnen wir den Code f�r die
	<acronym>FPU</acronym> besser optimieren, als in einer der
	Hochsprachen, inklusive C.</para>

      <para>Sobald eine C-Funktion die Berechnung einer
	Flie�kommazahl durchf�hren will, l�dt sie erst
	einmal alle ben�tigten Variablen und Konstanten in die
	Register der <acronym>FPU</acronym>. Dann werden die
	Berechnungen durchgef�hrt, um das korrekte Ergebnis zu
	erhalten. Gute C-Compiler k�nnen diesen Teil des Codes
	sehr gut optimieren.</para>

      <para>Das Ergebnis wird "zur�ckgegeben", indem dieses auf
	dem <acronym>TOS</acronym> abgelegt wird. Vorher wird
	aufger�umt. S�mtliche Variablen und Konstanten, die
	w�hrend der Berechnung verwendet wurden, werden dabei
	aus der <acronym>FPU</acronym> entfernt.</para>

      <para>Was wir im vorherigen Abschnitt selber getan haben, kann so
	nicht durchgef�hrt werden: Wir haben das Quadrat des
	f&ndash;Wertes berechnet, und das Ergebnis f�r eine
	weitere Berechnung mit einer anderen Funktion auf dem Stack
	behalten.</para>

      <para>Wir <emphasis>wu�ten</emphasis>, da� wir
	diesen Wert sp�ter noch einmal brauchen w�rden. Wir
	wu�ten auch, da� auf dem Stack gen�gend Platz
	war (welcher nur Platz f�r 8 Zahlen bietet), um den Wert
	dort zu speichern.</para>

      <para>Ein C-Compiler kann nicht wissen, ob ein Wert auf dem
        Stack in naher Zukunft noch einmal gebraucht wird.</para>

      <para>Nat�rlich k�nnte der C-Programmierer dies wissen.
	Aber die einzige M�glichkeit, die er hat, ist, den Wert
	im verf�gbaren Speicher zu halten.</para>

      <para>Das bedeutet zum einen, da� der Wert mit der
	<acronym>FPU</acronym>-internen, 80-stelligen
	Genauigkeit in einer normalen C-Variable vom Typ
	<emphasis>double</emphasis> (64 Bit) oder vom Typ
	<emphasis>single</emphasis> (32 Bit) gespeichert wird.</para>

      <para>Dies bedeutet au�erdem, da� der Wert aus dem
	<acronym>TOS</acronym> in den Speicher verschoben werden
	mu�, und sp�ter wieder zur�ck. Von allen
	Operationen mit der <acronym>FPU</acronym> ist der Zugriff
	auf den Speicher die langsamste.</para>

      <para>Wann immer also mit der <acronym>FPU</acronym> in einer
	Assemblersprache programmiert wird, sollte nach
	M�glichkeiten gesucht werden, Zwischenergebnisse auf dem
	Stack der <acronym>FPU</acronym> zu lassen.</para>

      <para>Wir k�nnen mit dieser Idee noch einen Schritt weiter
	gehen! In unserem Programm verwenden wir eine
	<emphasis>Konstante</emphasis> (die wir <constant>PC</constant>
	genannt haben).</para>

      <para>Es ist unwichtig, wieviele Lochblendendurchmesser wir
	berechnen: 1, 10, 20, 1000, wir verwenden immer dieselbe
	Konstante. Daher k�nnen wir unser Programm so optimieren,
	da� diese Konstante immer auf dem Stack belassen
	wird.</para>

      <para>Am Anfang unseres Programmes berechnen wir die oben
	erw�hnte Konstante. Wir m�ssen die Eingabe f�r
	jede Dezimalstelle der Konstanten durch <constant>10</constant>
	dividieren.</para>

      <para>Multiplizieren geht sehr viel schneller als Dividieren.
	Wir teilen also zu Beginn unseres Programmes
	<constant>1</constant> durch <constant>10</constant>, um
	<constant>0.1</constant> zu erhalten, was wir auf dem Stack
	speichern: Anstatt da� wir nun f�r jede einzelne
	Dezimalstelle die Eingabe wieder durch <constant>10</constant>
	teilen,	multiplizieren wir sie stattdessen mit
	<constant>0.1</constant>.</para>

      <para>Auf diese Weise geben wir <constant>0.1</constant> nicht
	direkt ein, obwohl wir dies k�nnten. Dies hat einen Grund:
	W�hrend <constant>0.1</constant> durch nur eine einzige
	Dezimalstelle dargestellt werden kann, wissen wir nicht,
	wieviele <emphasis>bin�re</emphasis> Stellen ben�tigt
	werden. Wir �berlassen die Berechnung des bin�ren
	Wertes daher der <acronym>FPU</acronym>, mit dessen eigener,
	hoher Genauigkeit.</para>

      <para>Wir verwenden noch weitere Konstanten: Wir multiplizieren
	den Lochblendendurchmesser mit <constant>1000</constant>, um
	den Wert von Millimeter in Micrometer zu konvertieren. Wir
	vergleichen Werte mit <constant>10000</constant>, wenn wir
	diese auf vier signifikante Stellen runden wollen. Wir
	behalten also beide Konstanten, <constant>1000</constant>
	und <constant>10000</constant>, auf dem Stack. Und
	selbstverst�ndlich verwenden wir erneut die gespeicherte
	<constant>0.1</constant>, um Werte auf vier signifikante
	Stellen zu runden.</para>

      <para>Zu guter letzt behalten wir <constant>-5</constant> noch
	auf dem Stack. Wir brauchen diesen Wert, um das Quadrat des
	f&ndash;Wertes zu skalieren, anstatt diesen durch
	<constant>32</constant> zu teilen. Es ist kein Zufall, da�
	wir diese Konstante als letztes laden. Dadurch wird diese
	Zahl die oberste Konstante auf dem Stack. Wenn sp�ter
	das Quadrat des f&ndash;Wertes skaliert werden mu�,
	befindet sich die <constant>-5</constant> in
	<varname role="register">st(1)</varname>, also genau da, wo
	die Funktion <function role="opcode">fscale</function> diesen
	Wert erwartet.</para>

      <para>Es ist �blich, einige Konstanten per Hand zu erzeugen,
	anstatt sie aus dem Speicher zu laden. Genau das machen wir
	mit der <constant>-5</constant>:</para>

      <programlisting>
    	fld1			; TOS =  1
    	fadd	st0, st0	; TOS =  2
    	fadd	st0, st0	; TOS =  4
    	fld1			; TOS =  1
    	faddp	st1, st0	; TOS =  5
    	fchs			; TOS = -5</programlisting>

      <para>Wir k�nnen all diese Optimierungen in einer Regel
	zusammenfassen: <emphasis>Behalte wiederverwendbare Werte auf
	dem Stack!</emphasis></para>

      <tip>
	<para><emphasis>&postscript;</emphasis> ist eine
	  Stack-orientierte Programmiersprache. Es gibt weit mehr
	  B�cher �ber &postscript;, als �ber die
	  Assemblersprache der <acronym>FPU</acronym>: Werden Sie
	  in &postscript; besser, dann werden Sie auch automatisch
	  in der Programmierung der <acronym>FPU</acronym>
	  besser.</para>
      </tip>
    </sect2>

    <sect2 id="x86-pinhole-the-code">
      <title><application>pinhole</application>&mdash;Der Code</title>

      <programlisting>
;;;;;;; pinhole.asm ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; Find various parameters of a pinhole camera construction and use
;
; Started:	 9-Jun-2001
; Updated:	10-Jun-2001
;
; Copyright (c) 2001 G. Adam Stanislav
; All rights reserved.
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

%include	'system.inc'

%define	BUFSIZE	2048

section	.data
align 4
ten	dd	10
thousand	dd	1000
tthou	dd	10000
fd.in	dd	stdin
fd.out	dd	stdout
envar	db	'PINHOLE='	; Exactly 8 bytes, or 2 dwords long
pinhole	db	'04,', 		; Bender's constant (0.04)
connors	db	'037', 0Ah	; Connors' constant
usg	db	'Usage: pinhole [-b] [-c] [-e] [-p &lt;value&gt;] [-o &lt;outfile&gt;] [-i &lt;infile&gt;]', 0Ah
usglen	equ	$-usg
iemsg	db	"pinhole: Can't open input file", 0Ah
iemlen	equ	$-iemsg
oemsg	db	"pinhole: Can't create output file", 0Ah
oemlen	equ	$-oemsg
pinmsg	db	"pinhole: The PINHOLE constant must not be 0", 0Ah
pinlen	equ	$-pinmsg
toobig	db	"pinhole: The PINHOLE constant may not exceed 18 decimal places", 0Ah
biglen	equ	$-toobig
huhmsg	db	9, '???'
separ	db	9, '???'
sep2	db	9, '???'
sep3	db	9, '???'
sep4	db	9, '???', 0Ah
huhlen	equ	$-huhmsg
header	db	'focal length in millimeters,pinhole diameter in microns,'
	db	'F-number,normalized F-number,F-5.6 multiplier,stops '
	db	'from F-5.6', 0Ah
headlen	equ	$-header

section .bss
ibuffer	resb	BUFSIZE
obuffer	resb	BUFSIZE
dbuffer	resb	20		; decimal input buffer
bbuffer	resb	10		; BCD buffer

section	.text
align 4
huh:
	call	write
	push	dword huhlen
	push	dword huhmsg
	push	dword [fd.out]
	sys.write
	add	esp, byte 12
	ret

align 4
perr:
	push	dword pinlen
	push	dword pinmsg
	push	dword stderr
	sys.write
	push	dword 4		; return failure
	sys.exit

align 4
consttoobig:
	push	dword biglen
	push	dword toobig
	push	dword stderr
	sys.write
	push	dword 5		; return failure
	sys.exit

align 4
ierr:
	push	dword iemlen
	push	dword iemsg
	push	dword stderr
	sys.write
	push	dword 1		; return failure
	sys.exit

align 4
oerr:
	push	dword oemlen
	push	dword oemsg
	push	dword stderr
	sys.write
	push	dword 2
	sys.exit

align 4
usage:
	push	dword usglen
	push	dword usg
	push	dword stderr
	sys.write
	push	dword 3
	sys.exit

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

.arg:
	pop	ecx
	or	ecx, ecx
	je	near .getenv		; no more arguments

	; ECX contains the pointer to an argument
	cmp	byte [ecx], '-'
	jne	usage

	inc	ecx
	mov	ax, [ecx]
	inc	ecx

.o:
	cmp	al, 'o'
	jne	.i

	; Make sure we are not asked for the output file twice
	cmp	dword [fd.out], stdout
	jne	usage

	; Find the path to output file - it is either at [ECX+1],
	; i.e., -ofile --
	; or in the next argument,
	; i.e., -o file

	or	ah, ah
	jne	.openoutput
	pop	ecx
	jecxz	usage

.openoutput:
	push	dword 420	; file mode (644 octal)
	push	dword 0200h | 0400h | 01h
	; O_CREAT | O_TRUNC | O_WRONLY
	push	ecx
	sys.open
	jc	near oerr

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

.i:
	cmp	al, 'i'
	jne	.p

	; Make sure we are not asked twice
	cmp	dword [fd.in], stdin
	jne	near usage

	; Find the path to the input file
	or	ah, ah
	jne	.openinput
	pop	ecx
	or	ecx, ecx
	je near usage

.openinput:
	push	dword 0		; O_RDONLY
	push	ecx
	sys.open
	jc	near ierr		; open failed

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

.p:
	cmp	al, 'p'
	jne	.c
	or	ah, ah
	jne	.pcheck

	pop	ecx
	or	ecx, ecx
	je	near usage

	mov	ah, [ecx]

.pcheck:
	cmp	ah, '0'
	jl	near usage
	cmp	ah, '9'
	ja	near usage
	mov	esi, ecx
	jmp	.arg

.c:
	cmp	al, 'c'
	jne	.b
	or	ah, ah
	jne	near usage
	mov	esi, connors
	jmp	.arg

.b:
	cmp	al, 'b'
	jne	.e
	or	ah, ah
	jne	near usage
	mov	esi, pinhole
	jmp	.arg

.e:
	cmp	al, 'e'
	jne	near usage
	or	ah, ah
	jne	near usage
	mov	al, ','
	mov	[huhmsg], al
	mov	[separ], al
	mov	[sep2], al
	mov	[sep3], al
	mov	[sep4], al
	jmp	.arg

align 4
.getenv:
	; If ESI = 0, we did not have a -p argument,
	; and need to check the environment for "PINHOLE="
	or	esi, esi
	jne	.init

	sub	ecx, ecx

.nextenv:
	pop	esi
	or	esi, esi
	je	.default	; no PINHOLE envar found

	; check if this envar starts with 'PINHOLE='
	mov	edi, envar
	mov	cl, 2		; 'PINHOLE=' is 2 dwords long
rep	cmpsd
	jne	.nextenv

	; Check if it is followed by a digit
	mov	al, [esi]
	cmp	al, '0'
	jl	.default
	cmp	al, '9'
	jbe	.init
	; fall through

align 4
.default:
	; We got here because we had no -p argument,
	; and did not find the PINHOLE envar.
	mov	esi, pinhole
	; fall through

align 4
.init:
	sub	eax, eax
	sub	ebx, ebx
	sub	ecx, ecx
	sub	edx, edx
	mov	edi, dbuffer+1
	mov	byte [dbuffer], '0'

	; Convert the pinhole constant to real
.constloop:
	lodsb
	cmp	al, '9'
	ja	.setconst
	cmp	al, '0'
	je	.processconst
	jb	.setconst

	inc	dl

.processconst:
	inc	cl
	cmp	cl, 18
	ja	near consttoobig
	stosb
	jmp	short .constloop

align 4
.setconst:
	or	dl, dl
	je	near perr

	finit
	fild	dword [tthou]

	fld1
	fild	dword [ten]
	fdivp	st1, st0

	fild	dword [thousand]
	mov	edi, obuffer

	mov	ebp, ecx
	call	bcdload

.constdiv:
	fmul	st0, st2
	loop	.constdiv

	fld1
	fadd	st0, st0
	fadd	st0, st0
	fld1
	faddp	st1, st0
	fchs

	; If we are creating a CSV file,
	; print header
	cmp	byte [separ], ','
	jne	.bigloop

	push	dword headlen
	push	dword header
	push	dword [fd.out]
	sys.write

.bigloop:
	call	getchar
	jc	near done

	; Skip to the end of the line if you got '#'
	cmp	al, '#'
	jne	.num
	call	skiptoeol
	jmp	short .bigloop

.num:
	; See if you got a number
	cmp	al, '0'
	jl	.bigloop
	cmp	al, '9'
	ja	.bigloop

	; Yes, we have a number
	sub	ebp, ebp
	sub	edx, edx

.number:
	cmp	al, '0'
	je	.number0
	mov	dl, 1

.number0:
	or	dl, dl		; Skip leading 0's
	je	.nextnumber
	push	eax
	call	putchar
	pop	eax
	inc	ebp
	cmp	ebp, 19
	jae	.nextnumber
	mov	[dbuffer+ebp], al

.nextnumber:
	call	getchar
	jc	.work
	cmp	al, '#'
	je	.ungetc
	cmp	al, '0'
	jl	.work
	cmp	al, '9'
	ja	.work
	jmp	short .number

.ungetc:
	dec	esi
	inc	ebx

.work:
	; Now, do all the work
	or	dl, dl
	je	near .work0

	cmp	ebp, 19
	jae	near .toobig

	call	bcdload

	; Calculate pinhole diameter

	fld	st0	; save it
	fsqrt
	fmul	st0, st3
	fld	st0
	fmul	st5
	sub	ebp, ebp

	; Round off to 4 significant digits
.diameter:
	fcom	st0, st7
	fstsw	ax
	sahf
	jb	.printdiameter
	fmul	st0, st6
	inc	ebp
	jmp	short .diameter

.printdiameter:
	call	printnumber	; pinhole diameter

	; Calculate F-number

	fdivp	st1, st0
	fld	st0

	sub	ebp, ebp

.fnumber:
	fcom	st0, st6
	fstsw	ax
	sahf
	jb	.printfnumber
	fmul	st0, st5
	inc	ebp
	jmp	short .fnumber

.printfnumber:
	call	printnumber	; F number

	; Calculate normalized F-number
	fmul	st0, st0
	fld1
	fld	st1
	fyl2x
	frndint
	fld1
	fscale
	fsqrt
	fstp	st1

	sub	ebp, ebp
	call	printnumber

	; Calculate time multiplier from F-5.6

	fscale
	fld	st0

	; Round off to 4 significant digits
.fmul:
	fcom	st0, st6
	fstsw	ax
	sahf

	jb	.printfmul
	inc	ebp
	fmul	st0, st5
	jmp	short .fmul

.printfmul:
	call	printnumber	; F multiplier

	; Calculate F-stops from 5.6

	fld1
	fxch	st1
	fyl2x

	sub	ebp, ebp
	call	printnumber

	mov	al, 0Ah
	call	putchar
	jmp	.bigloop

.work0:
	mov	al, '0'
	call	putchar

align 4
.toobig:
	call	huh
	jmp	.bigloop

align 4
done:
	call	write		; flush output buffer

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

	push	dword [fd.out]
	sys.close

	finit

	; return success
	push	dword 0
	sys.exit

align 4
skiptoeol:
	; Keep reading until you come to cr, lf, or eof
	call	getchar
	jc	done
	cmp	al, 0Ah
	jne	.cr
	ret

.cr:
	cmp	al, 0Dh
	jne	skiptoeol
	ret

align 4
getchar:
	or	ebx, ebx
	jne	.fetch

	call	read

.fetch:
	lodsb
	dec	ebx
	clc
	ret

read:
	jecxz	.read
	call	write

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

align 4
.empty:
	add	esp, byte 4
	stc
	ret

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

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

align 4
bcdload:
	; EBP contains the number of chars in dbuffer
	push	ecx
	push	esi
	push	edi

	lea	ecx, [ebp+1]
	lea	esi, [dbuffer+ebp-1]
	shr	ecx, 1

	std

	mov	edi, bbuffer
	sub	eax, eax
	mov	[edi], eax
	mov	[edi+4], eax
	mov	[edi+2], ax

.loop:
	lodsw
	sub	ax, 3030h
	shl	al, 4
	or	al, ah
	mov	[edi], al
	inc	edi
	loop	.loop

	fbld	[bbuffer]

	cld
	pop	edi
	pop	esi
	pop	ecx
	sub	eax, eax
	ret

align 4
printnumber:
	push	ebp
	mov	al, [separ]
	call	putchar

	; Print the integer at the TOS
	mov	ebp, bbuffer+9
	fbstp	[bbuffer]

	; Check the sign
	mov	al, [ebp]
	dec	ebp
	or	al, al
	jns	.leading

	; We got a negative number (should never happen)
	mov	al, '-'
	call	putchar

.leading:
	; Skip leading zeros
	mov	al, [ebp]
	dec	ebp
	or	al, al
	jne	.first
	cmp	ebp, bbuffer
	jae	.leading

	; We are here because the result was 0.
	; Print '0' and return
	mov	al, '0'
	jmp	putchar

.first:
	; We have found the first non-zero.
	; But it is still packed
	test	al, 0F0h
	jz	.second
	push	eax
	shr	al, 4
	add	al, '0'
	call	putchar
	pop	eax
	and	al, 0Fh

.second:
	add	al, '0'
	call	putchar

.next:
	cmp	ebp, bbuffer
	jb	.done

	mov	al, [ebp]
	push	eax
	shr	al, 4
	add	al, '0'
	call	putchar
	pop	eax
	and	al, 0Fh
	add	al, '0'
	call	putchar

	dec	ebp
	jmp	short .next

.done:
	pop	ebp
	or	ebp, ebp
	je	.ret

.zeros:
	mov	al, '0'
	call	putchar
	dec	ebp
	jne	.zeros

.ret:
	ret</programlisting>

      <para>Der Code folgt demselben Aufbau wie alle anderen Filter,
	die wir bisher gesehen haben, bis auf eine Kleinigkeit:</para>

      <blockquote>
	<para>Wir nehmen nun nicht mehr an, da� das Ende
	  der Eingabe auch das Ende der n�tigen Arbeit bedeutet,
	  etwas, das wir f�r <emphasis>zeichenbasierte</emphasis>
	  Filter automatisch angenommen haben.</para>

	<para>Dieser Filter verarbeitet keine Zeichen. Er verarbeitet
	  eine <emphasis>Sprache</emphasis> (obgleich eine sehr
	  einfache, die nur aus Zahlen besteht).</para>

	<para>Wenn keine weiteren Eingaben vorliegen, kann das zwei
	  Ursachen haben:</para>

	<itemizedlist>
	  <listitem>
	    <para>Wir sind fertig und k�nnen aufh�ren. Dies
	      ist dasselbe wie vorher.</para>
	  </listitem>

	  <listitem>
	    <para>Das Zeichen, das wir eingelesen haben, war eine Zahl.
	      Wir haben diese am Ende unseres <acronym>ASCII</acronym>
	      &ndash;zu&ndash;float Kovertierungspuffers gespeichert.
	      Wir m�ssen nun den gesamten Pufferinhalt in eine
	      Zahl konvertieren, und die letzte Zeile unserer Ausgabe
	      ausgeben.</para>
	  </listitem>
	</itemizedlist>

	<para>Aus diesem Grund haben wir unsere <function>getchar
	  </function>- und <function>read</function>-Routinen
	  so angepa�t, da� sie das
	  <varname role="register">carry flag</varname>
	  <emphasis>clear</emphasis> immer dann zur�ckgeben, wenn
	  wir ein weiteres Zeichen aus der Eingabe lesen, und das
	  <varname role="register">carry flag</varname>
	  <emphasis>set</emphasis> immer dann zur�ckgeben, wenn
	  es keine weiteren Eingabedaten gibt.</para>

	<para>Selbstverst�ndlich verwenden wir auch hier die
	  Magie der Assemblersprache! Schauen Sie sich
	  <function>getchar</function> n�her an. Dieses gibt
	  <emphasis>immer</emphasis> das
	  <varname role="register">carry flag</varname>
	  <emphasis>clear</emphasis> zur�ck.</para>

	<para>Dennoch basiert der Hauptteil unseres Programmes auf dem
	  <varname role="register">carry flag</varname>, um diesem eine
	  Beendigung mitzuteilen&mdash;und es funktioniert.</para>

	<para>Die Magie passiert in <function>read</function>. Wann
	  immer weitere Eingaben durch das System zur Verf�gung
	  stehen, ruft diese Funktion <function>getchar</function>
	  auf, welche ein weiteres Zeichen aus dem Eingabepuffer
	  einliest, und anschlie�end das
	  <varname role="register">carry flag</varname>
	  <emphasis>clear</emphasis>t.</para>

	<para>Wenn aber <function>read</function> keine weiteren
	  Eingaben von dem System bekommt, ruft dieses
	  <emphasis>nicht</emphasis> <function>getchar</function>
	  auf. Stattdessen addiert der op-Code
	  <function role="opcode">add esp, byte 4</function>
	  <constant>4</constant> zu
	  <varname role="register">ESP</varname> hinzu,
	  <emphasis>setzt</emphasis> das
	  <varname role="register">carry flag</varname>, und
	  springt zur�ck.</para>

	<para>Wo springt diese Funktion hin? Wann immer ein Programm
	  den op-Code <function role="opcode">call</function>
	  verwendet, <function role="opcode">push</function>t der
	  Mikroprozessor die R�cksprungandresse, d.h. er
	  speichert diese ganz oben auf dem Stack (nicht auf dem
	  Stack der <acronym>FPU</acronym>, sondern auf dem Systemstack,
	  der sich im Hauptspeicher befindet). Wenn ein Programm den
	  op-Code <function role="opcode">ret</function> verwendet,
	  <function role="opcode">pop</function>t der
	  Mikroprozessor den R�ckgabewert von dem Stack, und
	  springt zu der Adresse, die dort gespeichert wurde.</para>

	<para>Da wir aber <constant>4</constant> zu
	  <varname role="register">ESP</varname> hinzuaddiert haben
	  (welches das Register der Stackzeiger ist), haben wir
	  effektiv dem Mikroprzessor eine kleine
	  <emphasis>Amnesie</emphasis> verpa�t: Dieser erinnert
	  sich nun nicht mehr daran, da�
	  <function>getchar</function> durch
	  <function>read</function> aufgerufen wurde.</para>

	<para>Und da <function>getchar</function> nichts vor dem
	  Aufruf von <function>read</function> auf dem Stack abgelegt
	  hat, enth�lt der Anfang des Stacks nun die
	  R�cksprungadresse von der Funktion, die
	  <function>getchar</function> aufgerufen hat. Soweit es den
	  Aufrufer betrifft, hat dieser <function>getchar</function>
	  ge<function role="opcode">call</function>t, welche mit
	  einem gesetzten
	  <varname role="register">carry flag</varname>
	  <function role="opcode">ret</function>urned.</para>
      </blockquote>

      <para>Des weiteren wird die Routine <function>bcdload</function>
	bei einem klitzekleinen Problem zwischen der Big&ndash;Endian-
	und Little&ndash;Endian-Codierung aufgerufen.</para>

      <para>Diese konvertiert die Textrepr�sentation einer
	Zahl in eine andere Textrepr�sentation: Der Text wird
	in der Big&ndash;Endian-Codierung gespeichert, die
	<emphasis>packed decimal</emphasis>-Darstellung jedoch in der
	Little&ndash;Endian-Codierung.</para>

      <para>Um dieses Problem zu l�sen haben wir vorher den
	op-Code <function>std</function> verwendet. Wir machen diesen
	Aufruf sp�ter mittels <function>cld</function> wieder
	r�ckg�ngig: Es ist sehr wichtig, da� wir keine
	Funktion mittels <function>call</function> aufrufen, die von
	einer Standardeinstellung des
	<emphasis>Richtungsflags</emphasis> abh�ngig ist,
	w�hrend <function>std</function> ausgef�hrt
	wird.</para>

      <para>Alles weitere in dem Programm sollte leicht zu verstehen
	sein, vorausgesetzt, da� Sie das gesamte vorherige
	Kapitel gelesen haben.</para>

      <para>Es ist ein klassisches Beispiel f�r das Sprichwort,
	da� das Programmieren eine Menge Denkarbeit, und nur
	ein wenig Programmcode ben�tigt. Sobald wir uns �ber
	jedes Detail im klaren sind, steht der Code fast schon
	da.</para>
    </sect2>

    <sect2 id="x86-pinhole-using">
      <title>Das Programm <application>pinhole</application>
	verwenden</title>

      <para>Da wir uns bei dem Programm daf�r entschieden haben,
	alle Eingaben, die keine Zahlen sind, zu ignorieren (selbst
	die in Kommentaren), k�nnen wir jegliche
	<emphasis>textbasierten Eingaben</emphasis> verarbeiten. Wir
	<emphasis>m�ssen</emphasis> dies nicht tun, wir
	<emphasis>k�nnten</emphasis> aber.</para>

      <para>Meiner bescheidenen Meinung nach wird ein Programm durch
	die M�glichkeit, anstatt einer strikten Eingabesyntax
	textbasierte Anfragen stellen zu k�nnen, sehr viel
	benutzerfreundlicher.</para>

      <para>Angenommen, wir wollten eine Lochkamera f�r einen
	4x5 Zoll Film bauen. Die standardm��ige Brennweite
	f�r diesen Film ist ungef�hr 150mm.
	Wir wollen diesen Wert <emphasis>optimieren</emphasis>, so
	da� der Lochblendendurchmesser eine m�glichst
	runde Zahl ergibt. Lassen Sie uns weiter annehmen, da�
	wir zwar sehr gut mit Kameras umgehen k�nnen, daf�r
	aber nicht so gut mit Computern. Anstatt das wir nun eine
	Reihe von Zahlen eingeben, wollen wir lieber ein paar
	<emphasis>Fragen</emphasis> stellen.</para>

      <para>Unsere Sitzung k�nnte wie folgt aussehen:</para>

      <screen>&prompt.user; <userinput>pinhole

Computer,

Wie gro� m��te meine Lochblende bei einer Brennweite
von 150 sein?</userinput>
150	490	306	362	2930	12
<userinput>Hmmm... Und bei 160?</userinput>
160	506	316	362	3125	12
<userinput>La� uns bitte 155 nehmen.</userinput>
155	498	311	362	3027	12
<userinput>Ah, la� uns 157 probieren...</userinput>
157	501	313	362	3066	12
<userinput>156?</userinput>
156	500	312	362	3047	12
<userinput>Das ist es! Perfekt! Vielen Dank!
^D</userinput></screen>

      <para>Wir haben herausgefunden, da� der
	Lochblendendurchmesser bei einer Brennweite von 150 mm
	490 Mikrometer, oder 0.49 mm ergeben w�rde. Bei einer fast
	identischen Brennweite von 156 mm w�rden wir
	einen Durchmesser von genau einem halben Millimeter
	bekommen.</para>
    </sect2>

    <sect2 id="x86-pinhole-scripting">
      <title>Skripte schreiben</title>

      <para>Da wir uns daf�r entschieden haben, das Zeichen
	<constant>#</constant> als den Anfang eines Kommentares zu
	interpretieren, k�nnen wir unser
	<application>pinhole</application>-Programm auch als
	<emphasis>Skriptsprache</emphasis> verwenden.</para>

      <para>Sie haben vielleicht schon einmal
	<application>shell</application><emphasis>-Skripte</emphasis>
	gesehen, die mit folgenden Zeichen begonnen haben:</para>

      <programlisting>#! /bin/sh</programlisting>

      <para>...oder...</para>

      <programlisting>#!/bin/sh</programlisting>

      <para>... da das Leerzeichen hinter dem <function>#!</function>
	optional ist.</para>

      <para>Wann immer &unix; eine Datei ausf�hren soll, die mit
	einem <function>#!</function> beginnt, wird angenommen, das
	die Datei ein Skript ist. Es f�gt den Befehl an das Ende
	der ersten Zeile an, und versucht dann, dieses
	auszuf�hren.</para>

      <para>Angenommen, wir haben unser Programm
	<application>pinhole</application> unter
	<application>/usr/local/bin/</application> installiert, dann
	k�nnen wir nun Skripte schreiben, um unterschiedliche
	Lochblendendurchmesser f�r mehrere Brennweiten
	zu berechnen, die normalerweise mit 120er Filmen verwendet
	werden.</para>

      <para>Das Skript k�nnte wie folgt aussehen:</para>

      <programlisting>#! /usr/local/bin/pinhole -b -i
# Find the best pinhole diameter
# for the 120 film

### Standard
80

### Wide angle
30, 40, 50, 60, 70

### Telephoto
100, 120, 140</programlisting>

      <para>Da ein 120er Film ein Film mittlerer Gr��e ist,
	k�nnten wir die Datei <application>medium</application>
	nennen.</para>

      <para>Wir k�nnen die Datei ausf�hrbar machen und dann
	aufrufen, als w�re es ein Programm:</para>

      <screen>&prompt.user; <userinput>chmod 755 medium</userinput>
&prompt.user; <userinput>./medium</userinput></screen>

      <para>&unix; wird den letzten Befehl wie folgt
	interpretieren:</para>

      <screen>&prompt.user; <userinput>/usr/local/bin/pinhole -b -i ./medium</userinput></screen>

      <para>Es wird den Befehl ausf�hren und folgendes
	ausgeben:</para>

      <screen>80	358	224	256	1562	11
30	219	137	128	586	9
40	253	158	181	781	10
50	283	177	181	977	10
60	310	194	181	1172	10
70	335	209	181	1367	10
100	400	250	256	1953	11
120	438	274	256	2344	11
140	473	296	256	2734	11</screen>

      <para>Lassen Sie uns nun das folgende eingeben:</para>

      <screen>&prompt.user; <userinput>./medium -c</userinput></screen>

      <para>&unix; wird dieses wie folgt behandeln:</para>

      <screen>&prompt.user; <userinput>/usr/local/bin/pinhole -b -i ./medium -c</userinput></screen>

      <para>Dadurch erh�lt das Programm zwei
	widerspr�chliche Optionen: <parameter>-b</parameter> und
	<parameter>-c</parameter> (Verwende Benders Konstante und
	verwende Connors Konstante). Wir haben unser Programm so
	geschrieben, da� sp�ter eingelesene Optionen die
	vorheringen �berschreiben&mdash;unser Programm wird also
	Connors Konstante f�r die Berechnungen verwenden:</para>

      <screen>80	331	242	256	1826	11
30	203	148	128	685	9
40	234	171	181	913	10
50	262	191	181	1141	10
60	287	209	181	1370	10
70	310	226	256	1598	11
100	370	270	256	2283	11
120	405	296	256	2739	11
140	438	320	362	3196	12</screen>

      <para>Wir entscheiden uns am Ende doch f�r Benders
	Konstante. Wir wollen die Ergebnisse im CSV-Format in
	einer Datei speichern:</para>

      <screen>&prompt.user; <userinput>./medium -b -e &gt; bender</userinput>
&prompt.user; <userinput>cat bender</userinput>
focal length in millimeters,pinhole diameter in microns,F-number,normalized F-number,F-5.6 multiplier,stops from F-5.6
80,358,224,256,1562,11
30,219,137,128,586,9
40,253,158,181,781,10
50,283,177,181,977,10
60,310,194,181,1172,10
70,335,209,181,1367,10
100,400,250,256,1953,11
120,438,274,256,2344,11
140,473,296,256,2734,11
&prompt.user;</screen>
    </sect2>
  </sect1>

  <sect1 id="x86-caveats">
    <sect1info>
      <authorgroup>
	<author>
	  <firstname>Daniel</firstname>
	  <surname>Seuffert</surname>
	  <contrib>�bersetzt von </contrib>
	</author>
      </authorgroup>
    </sect1info>

    <title>Vorsichtsmassnahmen</title>

    <para>Assembler-Programmierer, die aufwuchsen mit
      <acronym>&ms-dos;</acronym> und windows &windows; neigen oft
      dazu Shotcuts zu verwenden. Das Lesen der Tastatur-Scancodes und
      das direkte Schreiben in den Grafikspeicher sind zwei klassische
      Beispiele von Gewohnheiten, die unter
      <acronym>&ms-dos;</acronym> nicht verp�nt sind, aber nicht
      als richtig angesehen werden.</para>

    <para>Warum dies? Sowohl das <acronym>PC-BIOS</acronym> als auch
      <acronym>&ms-dos;</acronym> sind notorisch langsam bei der
      Ausf�hrung dieser Operationen.</para>

    <para>Sie m�gen versucht sein �hnliche Angewohnheiten
      in der &unix;-Umgebung fortzuf�hren. Zum Beispiel habe ich
      eine Webseite gesehen, welche erkl�rt, wie man auf einem
      beliebten &unix;-Ableger die Tastatur-Scancodes
      verwendet.</para>

    <para>Das ist generell eine <emphasis>sehr schlechte
      Idee</emphasis> in einer &unix;-Umgebung! Lassen Sie mich
      erkl�ren warum.</para>

    <sect2 id="x86-protected">
      <title>&unix; ist gesch�tzt</title>

      <para>Zum Einen mag es schlicht nicht m�glich sein.
	&unix; l�uft im Protected Mode. Nur der Kernel und
	Ger�tetreiber d�rfen direkt auf die Hardware
	zugreifen. Unter Umst�nden erlaubt es Ihnen ein
	bestimmter &unix;-Ableger Tastatur-Scancodes auszulesen, aber
	ein wirkliches &unix;-Betriebssystem wird dies zu verhindern
	wissen. Und falls eine Version es Ihnen erlaubt wird es eine
	andere nicht tun, daher kann eine sorgf�ltig erstellte
	Software �ber Nacht zu einem �berkommenen
	Dinosaurier werden.</para>
    </sect2>

    <sect2 id="x86-abstraction">
      <title>&unix; ist eine Abstraktion</title>

      <para>Aber es gibt einen viel wichtigeren Grund, weshalb Sie
	nicht versuchen sollten, die Hardware direkt anzusprechen
	(nat�rlich nicht, wenn Sie einen Ger�tetreiber
	schreiben), selbst auf den &unix;-�hnlichen Systemen, die
	es Ihnen erlauben:</para>

      <para><emphasis>&unix; ist eine Abstraktion!</emphasis></para>

      <para>Es gibt einen wichtigen Unterschied in der
	Design-Philosophie zwischen <acronym>&ms-dos;</acronym> und
	&unix;. <acronym>&ms-dos;</acronym> wurde entworfen als
	Einzelnutzer-System. Es l�uft auf einem Rechner mit einer
	direkt angeschlossenen Tastatur und einem direkt
	angeschlossenem Bildschirm. Die Eingaben des Nutzers kommen
	nahezu immer von dieser Tastatur. Die Ausgabe Ihres Programmes
	erscheint fast immer auf diesem Bildschirm.</para>

      <para>Dies ist NIEMALS garantiert unter &unix;. Es ist sehr
	verbreitet f�r ein &unix;, da� der Nutzer seine
	Aus- und Eingaben kanalisiert und umleitet:</para>

      <screen>&prompt.user; <userinput>program1 | program2 | program3 &gt; file1</userinput></screen>

      <para>Falls Sie eine Anwendung
	<application>program2</application> geschrieben haben, kommt
	ihre Eingabe nicht von der Tastatur, sondern von der Ausgabe
	von <application>program1</application>. Gleichermassen geht
	Ihre Ausgabe nicht auf den Bildschirm, sondern wird zur
	Eingabe f�r <application>program3</application>, dessen
	Ausgabe wiederum in <filename>file1</filename> endet.</para>

      <para>Aber es gibt noch mehr! Selbst wenn Sie sichergestellt
	haben, da� Ihre Eingabe und Ausgabe zum Terminal kommt
	bzw. gelangt, dann ist immer noch nicht garantiert, da�
	ihr Terminal ein PC ist: Es mag seinen Grafikspeicher nicht
	dort haben, wo Sie ihn erwarten, oder die Tastatur k�nnte
	keine <acronym>PC</acronym>-�hnlichen Scancodes erzeugen
	k�nnen. Es mag ein &macintosh; oder irgendein anderer
	Rechner sein.</para>

      <para>Sie m�gen nun den Kopf sch�tteln: Mein
	Programm ist in <acronym>PC</acronym>-Assembler geschrieben,
	wie kann es auf einem &macintosh; laufen? Aber ich habe nicht
	gesagt, da� Ihr Programm auf &macintosh; l�uft, nur
	sein Terminal mag ein &macintosh; sein.</para>

      <para>Unter &unix; mu� der Terminal nicht direkt am
	Rechner angeschlossen sein, auf dem die Software l�uft,
	er kann sogar auf einem anderen Kontinent sein oder sogar auf
	einem anderen Planeten. Es ist nicht ungew�hnlich,
	da� ein &macintosh;-Nutzer in Australien sich auf ein
	&unix;-System in Nordamerika (oder sonstwo) mittels
	<application>telnet</application> verbindet. Die Software
	l�uft auf einem Rechner w�hrend das Terminal sich
	auf einem anderen Rechner befindet: Falls Sie versuchen
	sollten die Scancodes auszulesen werden Sie die falschen
	Eingaben erhalten!</para>

      <para>Das Gleiche gilt f�r jede andere Hardware: Eine
	Datei, welche Sie einlesen, mag auf einem Laufwerk sein, auf
	das Sie keinen direkten Zugriff haben. Eine Kamera, deren
	Bilder Sie auslesen, befindet sich m�glicherweise in
	einem Space Shuttle, durch Satelliten mit Ihnen
	verbunden.</para>

      <para>Das sind die Gr�nde, weshalb Sie niemals unter
	&unix; Annahmen treffen d�rfen, woher Ihre Daten kommen
	oder gehen. Lassen Sie immer das System den physischen Zugriff
	auf die Hardware regeln.</para>

      <note>
	<para>Das sind Vorsichtsmassnahmen, keine absoluten Regeln.
	  Ausnahmen sind m�glich. Wenn zum Beispiel ein
	  Texteditor bestimmt hat, da� er auf einer lokalen
	  Maschine l�uft, dann mag er die Tastatur-Scancodes
	  direkt auslesen, um eine bessere Kontrolle zu
	  gew�hrleisten. Ich erw�hne diese
	  Vorsichtsmassnahmen nicht, um Ihnen zu sagen, was sie tun
	  oder lassen sollen, ich will Ihnen nur bewusst machen,
	  da� es bestimmte Fallstricke gibt, die Sie erwarten,
	  wenn Sie soeben ihn &unix; von <acronym>&ms-dos;</acronym>
	  angelangt sind. Kreative Menschen brechen oft Regeln und das
	  ist in Ordnung, solange sie wissen welche Regeln und
	  warum.</para>
      </note>
    </sect2>
  </sect1>

  <sect1 id="x86-acknowledgements">
    <sect1info>
      <authorgroup>
	<author>
	  <firstname>Daniel</firstname>
	  <surname>Seuffert</surname>
	  <contrib>�bersetzt von </contrib>
	</author>
      </authorgroup>
    </sect1info>

    <title>Danksagungen</title>

    <para>Dieses Handbuch w�re niemals m�glich gewesen
      ohne die Hilfe vieler erfahrener FreeBSD-Programmierer aus
      &a.hackers;. Viele dieser Personen haben geduldig meine Fragen
      beantwortet und mich in die richtige Richtung gewiesen bei
      meinem Versuch, die tieferen liegenden Mechanismen der
      &unix;-Systemprogrammierung zu erforschen im Allgemeinen und bei
      FreeBSD im Besonderen.</para>

    <para>Thomas M. Sommers �ffnete die T�ren f�r
      mich. Seine <ulink
      url="http://www.codebreakers-journal.com/content/view/262/27/">Wie
      schreibe ich "Hallo Welt" in FreeBSD-Assembler?</ulink> Webseite
      war mein erster Kontakt mit Assembler-Programmierung unter
      FreeBSD.</para>

    <para>Jake Burkholder hat die T�r offen gehalten durch das
      bereitwillige Beantworten all meiner Fragen und das
      Zurverf�gungstellen von Assembler-Codebeispielen.</para>

    <para>Copyright &copy; 2000-2001 G. Adam Stanislav. Alle Rechte
      vorbehalten.</para>
  </sect1>
</chapter>