                          FreeBSD Developers' Handbook

  The FreeBSD Documentation Project

   Version: 43126

   Copyright (c) 2000-2010 The FreeBSD Documentation Project

   Copyright (c) 2008-2010 The FreeBSD German Documentation Project

   Redistribution and use in source (SGML DocBook) and 'compiled' forms
   (SGML, HTML, PDF, PostScript, RTF and so forth) with or without
   modification, are permitted provided that the following conditions are
   met:

    1. Redistributions of source code (SGML DocBook) must retain the above
       copyright notice, this list of conditions and the following disclaimer
       as the first lines of this file unmodified.

    2. Redistributions in compiled form (transformed to other DTDs, converted
       to PDF, PostScript, RTF and other formats) 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.

  Wichtig:

   THIS DOCUMENTATION IS PROVIDED BY THE FREEBSD DOCUMENTATION PROJECT "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 FREEBSD DOCUMENTATION
   PROJECT 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
   DOCUMENTATION, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

   FreeBSD ist ein eingetragenes Warenzeichen der FreeBSD Foundation.

   Apple, FireWire, Mac, Macintosh, Mac OS, Quicktime und TrueType sind
   eingetragene Warenzeichen von Apple Computer, Inc., in den Vereinigten
   Staaten und anderen La:ndern.

   IBM, AIX, OS/2, PowerPC, PS/2, S/390 und ThinkPad sind Warenzeichen der
   International Business Machines Corporation in den Vereinigten Staaten,
   anderen La:ndern oder beiden.

   IEEE, POSIX und 802 sind eingetragene Warenzeichen vom Institute of
   Electrical and Electronics Engineers, Inc. in den Vereinigten Staaten.

   Intel, Celeron, EtherExpress, i386, i486, Itanium, Pentium und Xeon sind
   Warenzeichen oder eingetragene Warenzeichen der Intel Corporation oder
   ihrer Gesellschaften in den Vereinigten Staaten und in anderen La:ndern.

   Linux ist ein eingetragenes Warenzeichen von Linus Torvalds.

   Microsoft, MS-DOS, Outlook, Windows, Windows Media und Windows NT sind
   entweder eingetragene Warenzeichen oder Warenzeichen der Microsoft
   Corporation in den Vereinigten Staaten und/oder in anderen La:ndern.

   Motif, OSF/1 und UNIX sind eingetragene Warenzeichen und IT DialTone und
   The Open Group sind Warenzeichen der The Open Group in den Vereinigten
   Staaten und in anderen La:ndern.

   Sun, Sun Microsystems, Java, Java Virtual Machine, JDK, JSP, JVM, Netra,
   Solaris, StarOffice und SunOS sind Warenzeichen oder eingetragene
   Warenzeichen von Sun Microsystems, Inc. in den Vereinigten Staaten und in
   anderen La:ndern.

   Viele Produktbezeichnungen von Herstellern und Verka:ufern sind
   Warenzeichen. Soweit dem FreeBSD Project das Warenzeichen bekannt ist,
   werden die in diesem Dokument vorkommenden Bezeichnungen mit dem Symbol
   "(TM)" oder dem Symbol "(R)" gekennzeichnet.

   August 2000 von .
   Zusammenfassung

   Willkommen zum Entwickler-Handbuch. Dieses Handbuch ist jederzeit unter
   Bearbeitung und das Ergebnis der Arbeit vieler Einzelpersonen. Dies kann
   dazu fu:hren, dass bestimmte Bereiche nicht mehr aktuell sind und auf den
   neuesten Stand gebracht werden mu:ssen. Bei Unklarheiten empfiehlt es sich
   daher stets, auch die englische Originalversion des Handbuchs zu lesen.

   Wenn Sie bei der U:bersetzung dieses Handbuchs mithelfen mo:chten, senden
   Sie bitte eine E-Mail an die Mailingliste 'FreeBSD German Documentation
   Project' <de-bsd-translators@de.FreeBSD.org>.

   Die aktuelle Version dieses Handbuchs ist immer auf dem FreeBSD-Webserver
   verfu:gbar und kann in verschiedenen Formaten und in komprimierter Form
   vom FreeBSD-FTP-Server oder einem der zahlreichen Spiegel heruntergeladen
   werden (a:ltere Versionen finden Sie hingegen unter
   http://docs.FreeBSD.org/doc/).

   [ Split HTML / Single HTML ]

     ----------------------------------------------------------------------

   Inhaltsverzeichnis

   I. Grundlagen

                1. Einfu:hrung

                             1.1. Unter FreeBSD entwickeln

                             1.2. Die Vision von BSD

                             1.3. Grundlegende Richtlinien

                             1.4. Der Aufbau von /usr/src

                2. Werkzeuge zur Programmierung

                             2.1. U:berblick

                             2.2. Zusammenfassung

                             2.3. Einfu:hrung in die Programmierung

                             2.4. Kompilieren mit dem cc

                             2.5. Make

                             2.6. Debuggen

                             2.7. Emacs als Entwicklungsumgebung verwenden

                             2.8. Weiterfu:hrende Literatur

                3. Sicheres Programmieren

                             3.1. Zusammenfassung

                             3.2. Methoden des sicheren Entwurfs

                             3.3. Puffer-U:berla:ufe

                             3.4. SetUID-Themen

                             3.5. Die Umgebung ihrer Programme einschra:nken

                             3.6. Vertrauen

                             3.7. Race-Conditions

                4. Lokalisierung und Internationalisierung - L10N und I18N

                             4.1. I18N-konforme Anwendungen programmieren

                             4.2. Lokalisierte Nachrichten mit POSIX.1 Native
                             Language Support (NLS)

                5. Vorgaben und Richtlinien fu:r das Quelltextverzeichnis

                             5.1. Stil-Richtlinien

                             5.2. MAINTAINER eines Makefiles

                             5.3. Beigesteuerte Software

                             5.4. Belastende Dateien

                             5.5. Shared-Libraries

                6. Regressions- und Performance-Tests

                             6.1. Mikro-Benchmark-Checkliste

   II. Interprozess-Kommunikation

                7. Sockets

                8. IPv6 Internals

                             8.1. IPv6/IPsec-Implementierung

   III. Kernel

                9. Einen FreeBSD-Kernel bauen und installieren

                             9.1. Einen Kernel auf die "traditionelle" Art
                             und Weise bauen

                             9.2. Einen Kernel auf die "neue" Art und Weise
                             bauen

                10. Kernel-Fehlersuche

                             10.1. Besorgen eines Speicherauszugs nach einem
                             Kernel-Absturz (Kernel-Crash-Dump)

                             10.2. Fehlersuche in einem Speicherauszug nach
                             einem Kernel-Absturz mit kgdb

                             10.3. Fehlersuche in einem Speicherauszug nach
                             einem Absturz mit DDD

                             10.4. Online-Kernel-Fehlersuche mit DDB

                             10.5. Online-Kernel-Fehlersuche mit GDB auf
                             einem entfernten System

                             10.6. Fehlersuche bei einem Konsolen-Treiber

                             10.7. Fehlersuche bei Deadlocks

                             10.8. Glossar der Kernel-Optionen zur
                             Fehlersuche

   IV. Architekturen

                11. x86-Assembler-Programmierung

                             11.1. Synopsis

                             11.2. Die Werkzeuge

                             11.3. Systemaufrufe

                             11.4. Ru:ckgabewerte

                             11.5. Portablen Code erzeugen

                             11.6. Unser erstes Programm

                             11.7. UNIX(R)-Filter schreiben

                             11.8. Gepufferte Eingabe und Ausgabe

                             11.9. Kommandozeilenparameter

                             11.10. Die UNIX(R)-Umgebung

                             11.11. Arbeiten mit Dateien

                             11.12. One-Pointed Mind

                             11.13. Die FPU verwenden

                             11.14. Vorsichtsmassnahmen

                             11.15. Danksagungen

   V. Anhang

                Literaturverzeichnis

   Stichwortverzeichnis

   Liste der Beispiele

   2.1. Eine einfache .emacs-Datei

                               Teil I. Grundlagen

   Inhaltsverzeichnis

   1. Einfu:hrung

                1.1. Unter FreeBSD entwickeln

                1.2. Die Vision von BSD

                1.3. Grundlegende Richtlinien

                1.4. Der Aufbau von /usr/src

   2. Werkzeuge zur Programmierung

                2.1. U:berblick

                2.2. Zusammenfassung

                2.3. Einfu:hrung in die Programmierung

                2.4. Kompilieren mit dem cc

                2.5. Make

                2.6. Debuggen

                2.7. Emacs als Entwicklungsumgebung verwenden

                2.8. Weiterfu:hrende Literatur

   3. Sicheres Programmieren

                3.1. Zusammenfassung

                3.2. Methoden des sicheren Entwurfs

                3.3. Puffer-U:berla:ufe

                3.4. SetUID-Themen

                3.5. Die Umgebung ihrer Programme einschra:nken

                3.6. Vertrauen

                3.7. Race-Conditions

   4. Lokalisierung und Internationalisierung - L10N und I18N

                4.1. I18N-konforme Anwendungen programmieren

                4.2. Lokalisierte Nachrichten mit POSIX.1 Native Language
                Support (NLS)

   5. Vorgaben und Richtlinien fu:r das Quelltextverzeichnis

                5.1. Stil-Richtlinien

                5.2. MAINTAINER eines Makefiles

                5.3. Beigesteuerte Software

                5.4. Belastende Dateien

                5.5. Shared-Libraries

   6. Regressions- und Performance-Tests

                6.1. Mikro-Benchmark-Checkliste

Kapitel 1. Einfu:hrung

   Beigetragen von Murray Stokely und Jeroen Ruigrok van der Werven.
   U:bersetzt von Fabian Borschel.
   Inhaltsverzeichnis

   1.1. Unter FreeBSD entwickeln

   1.2. Die Vision von BSD

   1.3. Grundlegende Richtlinien

   1.4. Der Aufbau von /usr/src

1.1. Unter FreeBSD entwickeln

   Hier sind wir also. Ihr System ist vollsta:ndig installiert und Sie wollen
   mit dem Programmieren beginnen. Aber womit sollen Sie anfangen? Was bietet
   Ihnen FreeBSD? Was kann es fu:r einen Programmierer tun?

   Dies sind einige der Fragen, welche dieses Handbuch zu beantworten
   versucht. Natu:rlich gibt es, analog zu anderen Berufen, auch bei
   Programmierern unterschiedliche Leistungsniveaus. Fu:r die einen ist es
   ein Hobby, fu:r die anderen ist es der Beruf. Die Informationen in diesem
   Kapitel du:rften eher fu:r den Programmieranfa:nger geeignet sein;
   allerdings ko:nnte es auch fu:r Programmierer, die bisher nichts mit der
   FreeBSD-Plattform zu tun hatten, interessante Informationen enthalten.

1.2. Die Vision von BSD

   Ziel ist es, das bestmo:gliche UNIX(R)-artige Betriebssystempaket zu
   erstellen, mit dem gebu:hrenden Respekt gegenu:ber der Ideologie der
   urspru:nglichen Software, sowie der Bedienbarkeit, Leistungsfa:higkeit und
   Stabilita:t.

1.3. Grundlegende Richtlinien

   Unsere Ideologie kann durch die folgenden Leitfa:den beschrieben werden.

     * Fu:ge keine neue Funktionalita:t hinzu, solange ein Programmierer
       diese nicht zur Fertigstellung einer realen Anwendung beno:tigt.

     * Zu entscheiden, was ein System ist, ist genauso wichtig wie zu
       entscheiden, was ein System nicht ist. Versuchen Sie nicht, alle
       mo:glichen Wu:nsche zu erfu:llen; machen Sie lieber das System
       erweiterbar, so dass zusa:tzliche Bedu:rfnisse in einer
       aufwa:rtskompatiblen Weise bedient werden ko:nnen.

     * Das Einzige, das schlimmer ist, als von einem Beispiel auf die
       Allgemeinheit zu schliessen, ist, von u:berhaupt keinem Beispiel auf
       die Allgemeinheit zu schliessen.

     * Solange ein Problem nicht vollsta:ndig verstanden wurde, ist es
       besser, keine Lo:sung bereitzustellen.

     * Wenn Sie 90% des gewu:nschten Effektes bei nur 10% des Aufwands
       erreichen ko:nnen, sollten Sie besser die einfachere Lo:sung
       verwenden.

     * Grenzen Sie Komplexita:t so gut wie mo:glich ein.

     * Stellen Sie Mechanismen anstelle von Strategien bereit. U:berlassen
       Sie insbesondere Strategien fu:r die Benutzerschnittstelle dem
       Benutzerprogramm.

   Aus Scheifler & Gettys: "X Window System"

1.4. Der Aufbau von /usr/src

   Der vollsta:ndige Quelltext von FreeBSD ist u:ber unser o:ffentliches
   Repository verfu:gbar. Der Quelltext wird normalerweise in /usr/src
   abgelegt und entha:lt die folgenden Unterverzeichnisse:

   Verzeichnis                          Beschreibung                          
   bin/        Quelldateien fu:r Dateien in /bin                              
   cddl/       Quelldateien fu:r Programme, die unter der Common Development  
               and Distribution License stehen                                
   contrib/    Quelldateien fu:r Dateien von beigesteuerter Software          
   crypto/     Quelldateien fu:r die Kryptographie                            
   etc/        Quelldateien fu:r Dateien in /etc                              
   games/      Quelldateien fu:r Dateien in /usr/games                        
   gnu/        Programme, die unter der GNU Public License stehen             
   include/    Quelldateien fu:r Dateien in /usr/include                      
   kerberos5/  Quelldateien fu:r Kerberos Version 5                           
   lib/        Quelldateien fu:r Dateien in /usr/lib                          
   libexec/    Quelldateien fu:r Dateien in /usr/libexec                      
   release/    Dateien, die fu:r die Erstellung eines FreeBSD-Releases no:tig 
               sind                                                           
   rescue/     Bausystem fu:r die /rescue-Programme                           
   sbin/       Quelldateien fu:r Dateien in /sbin                             
   secure/     Quelldateien fu:r FreeSec                                      
   share/      Quelldateien fu:r Dateien in /usr/share                        
   sys/        Kernel-Quelldateien                                            
   tools/      Programme zum Verwalten und Testen von FreeBSD                 
   usr.bin/    Quelldateien fu:r Dateien in /usr/bin                          
   usr.sbin/   Quelldateien fu:r Dateien in /usr/sbin                         

Kapitel 2. Werkzeuge zur Programmierung

   Contributed by James Raynard und Murray Stokely.
   U:bersetzt von Dirk Arlt und Fabian Borschel.
   Inhaltsverzeichnis

   2.1. U:berblick

   2.2. Zusammenfassung

   2.3. Einfu:hrung in die Programmierung

   2.4. Kompilieren mit dem cc

   2.5. Make

   2.6. Debuggen

   2.7. Emacs als Entwicklungsumgebung verwenden

   2.8. Weiterfu:hrende Literatur

2.1. U:berblick

   Dieses Kapitel ist eine Einfu:hrung in die Benutzung einiger der Werkzeuge
   zur Programmierung die mit FreeBSD ausgeliefert werden. Trotzdem ist
   vieles auch auf verschiedene andere Versionen von UNIX(R) u:bertragbar.
   Dieses Kapitel soll kein Versuch sein Programmierung detailliert zu
   beschreiben. Der gro:sste Teil dieses Kapitels setzt wenige oder gar keine
   Programmierkenntnisse voraus, dennoch sollten die meisten Programmierer
   etwas Sinnvolles darin finden.

2.2. Zusammenfassung

   FreeBSD bietet eine exzellente Entwicklungsumgebung. Compiler fu:r C und
   C++, sowie ein Assembler sind im Basissystem enthalten. Natu:rlich finden
   sich auch klassische UNIX(R)-Werkzeuge wie sed und awk. Sollte das nicht
   genug sein, finden sich zahlreiche weitere Compiler und Interpreter in der
   Ports-Sammlung. Der folgende Abschnitt, Einfu:hrung in die Programmierung,
   za:hlt ein paar der verfu:gbaren Optionen auf. FreeBSD ist kompatibel zu
   vielen Standards wie POSIX(R) und ANSI C, sowie zu seinem eigenen BSD
   Erbe. So ist es mo:glich Anwendungen zu schreiben, welche ohne oder
   zumindest ohne wesentliche A:nderungen auf einer grossen Zahl an
   Plattformen kompilieren und laufen werden.

   Allerdings ko:nnen all diese Mo:glichkeiten anfangs etwas u:berwa:ltigend
   sein, wenn Sie vorher nie Programme auf einem UNIX(R)-System geschrieben
   haben. Dieses Dokument hat die Zielsetzung ihnen beim Einstieg zu helfen
   ohne allzu weit in fortgeschrittene Themen vorzudringen. Die Intention
   ist, dass dieses Dokument ihnen ausreichend Basiswissen vermittelt und die
   weitergehende Dokumentation sinnvoll nutzen zu ko:nnen.

   Der gro:sste Teil dieses Dokuments erfordert wenige oder gar keine
   Kenntnisse in der Programmierung, es werden trotzdem Basiswissen im Umgang
   mit UNIX(R) und die Bereitschaft zu lernen vorausgesetzt!

2.3. Einfu:hrung in die Programmierung

   Ein Programm ist eine Zusammenstellung von Anweisungen, die den Computer
   auffordern verschiedenste Dinge zu tun. Dieser Abschnitt gibt ihnen einen
   U:berblick u:ber die beiden wesentlichen Methoden diese Anweisungen oder
   "Befehle", wie man diese Anweisungen u:blicherweise nennt, zu geben. Die
   eine Methode nutzt einen Interpreter, die andere einen Compiler. Da
   menschliche Sprachen fu:r einen Computer nicht unmissversta:ndlich sind,
   werden diese Befehle in einer Sprache geschrieben die speziell fu:r diesen
   Zweck gedacht ist.

  2.3.1. Interpreter

   Mit einem Interpreter ist die Sprache vielmehr eine Umgebung, in der Sie
   ein Kommando an der Kommandozeile eingeben welches dann von der Umgebung
   ausgefu:hrt wird. Fu:r kompliziertere Programme ko:nnen Sie die Befehle in
   eine Datei schreiben und den Interpreter dazu bringen diese Datei zu laden
   und die enthaltenen Befehle auszufu:hren. Falls etwas schief geht werden
   viele Interpreter Sie an einen Debugger weiterleiten.

   Der Vorteil hierbei ist, das Sie das Ergebnis ihres Befehls direkt sehen
   und Fehler sofort korrigiert werden ko:nnen. Der gro:sste Nachteil bei
   dieser Methode entsteht, wenn Sie ihr Programm mit jemandem teilen wollen.
   Diese Person muss den selben Interpreter nutzen wie Sie es tun und Sie
   muss wissen wie dieser zu bedienen ist. Zudem werden Benutzer es nicht
   begru:ssen sich in einem Debugger wiederzufinden, wenn Sie einmal die
   falsche Taste dru:cken! Bei einem Blick auf die Leistungsfa:higkeit
   brauchen Interpreter oftmals viel Speicher und erzeugen den Code nicht so
   effizient wie Compiler.

   Meiner Meinung nach sind interpretierte Sprachen der beste Anfang, wenn
   Sie bisher noch nicht programmiert haben. Diese Art von Umgebung findet
   man typischerweise bei Sprachen wie Lisp, Smalltalk, Perl und Basic. Man
   ko:nnte auch sagen, dass die UNIX(R) Shell (sh, csh) fu:r sich bereits
   einen Interpreter darstellt und viele Leute schreiben tatsa:chlich Shell
   "Scripten" um sich bei einigen "Haushaltsaufgaben" auf ihren Maschinen
   helfen zu lassen. Tatsa:chlich war es ein wesentlicher Teil der originalen
   UNIX(R) Philosophie eine grosse Zahl an kleinen Hilfsprogrammen zur
   Verfu:gung zu stellen, welche mittels eines Shellskripts miteinander
   kombiniert werden um bestimmte Aufgaben zu u:bernehmen.

  2.3.2. Fu:r FreeBSD verfu:gbare Interpreter

   Im folgenden eine Liste der u:ber die FreeBSD Ports-Sammlung verfu:gbaren
   Interpreter einschliesslich einer kurzen Ero:rterung der popula:ren
   interpretierten Sprachen.

   Anleitungen wie man Anwendungen aus der Ports-Sammlung erha:lt und
   installiert ko:nnen Sie dem Kapitel Benutzen der Ports-Sammlung aus dem
   FreeBSD Handbuch entnehmen.

   BASIC

           Kurz fu:r Beginner's All-purpose Symbolic Instruction Code.
           Entwickelt in den 50er Jahren um Studenten in Programmierung zu
           unterrichten, wurde BASIC in den 80er Jahren mit jedem
           ansta:ndigen Personal Computer ausgeliefert und war fu:r viele
           Programmierer die erste Programmiersprache. BASIC ist auch die
           Grundlage fu:r Visual Basic.

           Der Bywater Basic Interpreter findet sich in der Ports-Sammlung
           unter lang/bwbasic und Phil Cockroft's Basic Interpreter (auch
           bekannt als Rabbit Basic) findet sich unter lang/pbasic.

   Lisp

           Diese Sprache wurde in den spa:ten 50er Jahren als Alternative zu
           den, zu dieser Zeit popula:ren, "zahlenverarbeitenden" Sprachen
           entwickelt. Anstelle auf Zahlen basiert Lisp auf Listen;
           tatsa:chlich ist der Name Lisp eine Kurzform fu:r "List
           Processing" (Listen abarbeiten). Sehr popula:r fu: AI (Artificial
           Intelligence/ ku:nstliche Intelligez) (Fach-) Kreisen.

           Lisp ist eine extrem kraftvolle und durchdachte Sprache, kann aber
           auch recht gross und unhandlich sein.

           Zahlreiche Ausformungen von Lisp, die auf UNIX(R) Systemen laufen
           sind u:ber die Ports-Sammlung verfu:gbar. GNU Common Lisp befindet
           sich in lang/gcl. CLISP von Bruno Haible und Michael Stoll ist in
           lang/clisp zu finden. Fu:r CMUCL, welches auch einen
           hoch-optimierten Compiler entha:lt, oder einfachere Ausformungen
           wie SLisp, das die meisten ga:ngigen Lisp Konstrukte in wenigen
           hundert Zeilen C Code entha:lt sind in lang/cmucl und lang/slisp
           ebenfalls enthalten.

   Perl

           Unter Systemadministratoren zum Schreiben von Skripten sehr
           beliebt; wird ha:ufig auch auf World Wide Web Servern verwendet,
           um CGI-Skripte zu schreiben.

           Perl ist in der Ports-Sammlung unter lang/perl5.8 fu:r alle
           FreeBSD-Versionen verfu:gbar, und wird im Basissystem von 4.x als
           /usr/bin/perl installiert.

   Scheme

           Ein Dialekt von Lisp, der kompakter und sauberer als Common Lisp
           ist. Dieser Dialekt ist an Universita:ten sehr beliebt, da er zum
           einen fu:r den Unterricht im Grundstudium einfach genug ist, und
           zum anderen ein ausreichend hohes Abstraktionsniveau fu:r den
           Einsatz in der Forschung bietet.

           Scheme ist in der Ports-Sammlung in Form des Elk Scheme
           Interpreters als lang/elk verfu:gbar. Den MIT Scheme Interpreter
           findet man unter lang/mit-scheme, und den SCM Scheme Interpreter
           unter lang/scm.

   Icon

           Icon ist eine Hochsprache mit ausgereiften Mo:glichkeiten zur
           Verarbeitung von Zeichenketten und Strukturen. Die unter FreeBSD
           verfu:gbare Version von Icon steht in der Ports-Sammlung unter
           lang/icon zur Verfu:gung.

   Logo

           Logo ist eine leicht zu erlernende Programmiersprache, welche in
           vielen Kursen als einfu:hrende Programmiersprache gewa:hlt wird.
           Sie ist ein ideales Arbeitswerkzeug beim Unterricht mit jungen
           Menschen, da mit ihr die Erstellung komplizierter geometrischer
           Oberfla:chen selbst fu:r kleine Kinder einfach ist.

           Die fu:r FreeBSD aktuellste, verfu:gbare Version findet man in der
           Ports-Sammlung unter lang/logo.

   Python

           Python ist eine objektorientierte, interpretierte
           Programmiersprache. Die Verfechter von Python argumentieren, dass
           sie eine der besten Programmiersprachen fu:r Programmieranfa:nger
           sei, da sie einfach zu erlernen ist, und anderen popula:ren
           interpretierten Programmiersprachen, welche zur Entwicklung
           grosser und komplexer Anwendungen verwendet werden, in nichts
           nachsteht (Perl und Tcl sind zwei solcher bekannten
           Programmiersprachen).

           Die aktuellste Version von Python ist in der Ports-Sammlung unter
           lang/python verfu:gbar.

   Ruby

           Ruby ist eine interpretierte und rein objektorientierte
           Programmiersprache. Sie wurde wegen ihrer leicht versta:ndlichen
           Syntax, ihrer Flexibilita:t und der Mo:glichkeit, grosse und
           komplexe Programme einfach zu entwickeln und zu pflegen, popula:r.

           Ruby ist in der Ports-Sammlung unter lang/ruby18 verfu:gbar.

   Tcl und Tk

           Tcl ist eine einbettbare, interpretierte Programmiersprache,
           welche aufgrund ihrer Portierbarkeit auf viele unterschiedliche
           Plattformen eine weite Verbreitung erfahren hat. Sie kann sowohl
           fu:r die schnelle Entwicklung kleinerer Prototypen, als auch (in
           Verbindung mit Tk, einem GUI Toolkit) vollwertiger, ausgereifter
           Programme verwendet werden.

           Es sind mehrere Versionen von Tcl als Ports fu:r FreeBSD
           verfu:gbar. Die aktuellste Version, Tcl 8.5, ist unter lang/tcl85
           verfu:gbar.

  2.3.3. Compiler

   Compiler sind eher anders. Zuerst schreibt man seinen Code unter
   Verwendung eines Editors in eine Datei (oder mehrere Dateien).
   Anschliessend ruft man den Compiler auf um zu sehen, ob dieser das
   Programm annimmt. Wenn das Programm nicht kompiliert werden konnte, muss
   man die Za:hne zusammenbeissen und wieder zum Editor zuru:ckkehren; falls
   das Programm kompiliert und eine ausfu:hrbare Anwendung erzeugt wurde,
   kann man diese u:ber eine Eingabeaufforderung oder u:ber einen Debugger
   aufrufen um zu sehen, ob sie auch funktioniert. [1]

   Offensichtlich ist diese Art der Programmierung nicht so direkt wie die
   Verwendung eines Interpreters. Jedoch sind auf diese Weise viele Dinge
   mo:glich, die mit einem Interpreter nur sehr schwer oder u:berhaupt nicht
   realisierbar wa:ren, wie z.B. das Schreiben von Code, der sehr eng mit dem
   Betriebsystem zusammen arbeitet-oder das Schreiben eines eigenen
   Betriebsystems selbst! Des weiteren ist so das Erzeugen von sehr
   effizientem Code mo:glich, da sich der Compiler fu:r die Optimierung Zeit
   nehmen kann, was bei einem Interpreter inakzeptabel wa:re. Ferner ist das
   Verbreiten von Programmen, welche fu:r einen Compiler geschrieben wurden,
   einfacher als welche, die fu:r einen Interpreter geschrieben wurden-man
   muss in ersterem Fall nur die ausfu:hrbare Datei verbreiten,
   vorausgesetzt, dass das gleiche Betriebssystem verwendet wird.

   Programmiersprachen, die kompiliert werden, sind unter anderem Pascal, C
   und C++. C und C++ sind eher unbarmherzige Programmiersprachen und daher
   eher fu:r erfahrene Programmierer gedacht; Pascal auf der anderen Seite
   wurde zu Ausbildungszwecken entworfen, und stellt daher eine
   einsteigerfreundliche Programmiersprache dar. FreeBSD beinhaltet im
   Basissystem keine Unterstu:tzung fu:r Pascal, stellt jedoch u:ber die
   Ports-Sammlung den Free Pascal Compiler unter lang/fpc zur Verfu:gung.

   Da der editier-kompilier-ausfu:hr-debug-Kreislauf unter Verwendung
   mehrerer Programme eher mu:hsam ist haben viele Hersteller von Compilern
   integrierte Entwicklungsumgebungen (Integrated Development Environment;
   auch kurz IDE) entwickelt. FreeBSD bietet zwar im Basissystem keine IDE
   an, stellt jedoch u:ber die Ports-Sammlung IDEs wie devel/kdevelop oder
   Emacs zur Verfu:gung, wobei letztere weit verbreitet ist. Die Verwendung
   von Emacs als IDE wird unter Abschnitt 2.7, "Emacs als
   Entwicklungsumgebung verwenden" diskutiert.

2.4. Kompilieren mit dem cc

   Dieser Abschnitt behandelt ausschliesslich den GNU Compiler fu:r C und
   C++, da dieser bereits im Basissystem von FreeBSD enthalten ist. Er kann
   mittels cc oder gcc aufgerufen werden. Die Details zur Erstellung einer
   Anwendung mit einem Interpreter variieren zwischen verschiedenen
   Interpretern mehr oder weniger stark, und werden meist ausfu:hrlich in der
   zugeho:rigen Dokumentation oder Online-Hilfe beschrieben.

   Sobald Sie Ihr Meisterwerk fertig geschrieben haben besteht der na:chste
   Schritt darin, dieses (hoffentlich!) unter FreeBSD zum Laufen zu bekommen.
   Dies beinhaltet u:blicherweise mehrere Schritte, wobei jeder einzelne
   Schritt von einem separaten Programm durchgefu:hrt wird.

    1. Aufbereiten Ihres Quelltextes durch Entfernen von Kommentaren, sowie
       weiteren Tricks wie das Ersetzen von Macros in C.

    2. U:berpru:fen der Syntax Ihres Quelltextes, um die Einhaltung der
       Sprachregeln sicherzustellen. Wenn Sie diese verletzt haben werden
       entsprechende Fehlermeldungen Ihnen dies mitteilen!

    3. U:bersetzen des Quelltextes in Assemblersprache -diese ist dem
       eigentlichen Maschinencode schon sehr nahe, jedoch immer noch fu:r
       Menschen lesbar. Angeblich. [2]

    4. U:bersetzen der Assemblersprache in Maschinencode-genau, wir sprechen
       hier von Bits und Bytes, Einsen und Nullen.

    5. U:berpru:fen, ob Sie Dinge wie Funktionen und globale Variablen in
       einheitlicher Weise verwendet haben. Wenn Sie z.B. eine nicht
       existierende Funktion aufgerufen haben, wird eine entsprechende
       Fehlermeldung Ihnen dies mitteilen.

    6. Wenn aus mehreren Quelltextdateien eine ausfu:hrbare Datei erstellt
       werden soll wird herausgefunden, wie die einzelnen Codeteile
       zusammengefu:gt werden mu:ssen.

    7. Ausarbeiten, wie das Programm aussehen muss, damit der Lader zur
       Laufzeit des Systems dieses in den Speicher laden und ausfu:hren kann.

    8. Endgu:ltiges Schreiben der ausfu:hrbaren Datei in das Dateisystem.

   Das Wort kompilieren wird ha:ufig fu:r die Schritte 1 bis 4 verwendet-die
   anderen werden mit dem Wort verlinken zusammengefasst. Manchmal wird
   Schritt 1 auch als Pre-Processing und die Schritte 3-4 als assemblieren
   bezeichnet.

   Glu:cklicherweise werden alle diese Details vor Ihnen verborgen, da cc ein
   Frontend ist, welches sich um die Ausfu:hrung all dieser Programme mit den
   richtigen Argumenten fu:r Sie ku:mmert; einfaches eingeben von

 % cc foobar.c

   fu:hrt zur U:bersetzung von foobar.c durch alle bereits erwa:hnten
   Schritte. Wenn Sie mehr als eine Datei u:bersetzen wollen mu:ssen Sie
   etwas wie folgt eingeben

 % cc foo.c bar.c

   Beachten Sie, dass die U:berpru:fung der Syntax genau dies tut-das reine
   U:berpru:fen der Syntax. Es findet keine U:berpru:fung bzgl. logischer
   Fehler statt, die Sie vielleicht gemacht haben, wie z.B. das Programm in
   eine Endlosschleife zu versetzen, oder Bubble Sort zu verwenden, wenn Sie
   eigentlich Binary Sort benutzen wollten. [3]

   Es gibt haufenweise Optionen fu:r cc, die alle in der zugeho:rigen
   Manualpage beschrieben werden. Im Folgenden werden ein paar der
   wichtigsten Optionen mit Beispielen ihrer Anwendung gezeigt.

   -o filename

           Die Name der Ausgabedatei. Wenn Sie diese Option nicht verwenden
           erstellt cc eine Datei mit dem Namen a.out. [4]

 % cc foobar.c               executable is a.out
 % cc -o foobar foobar.c     executable is foobar
            

   -c

           Dies kompiliert die Datei nur, verlinkt sie jedoch nicht.
           Nu:tzlich fu:r Spielereien, um die Syntax auf Korrektheit zu
           u:berpru:fen, oder falls Sie ein Makefile verwenden.

 % cc -c foobar.c
            

           Dieser Befehl erzeugt eine Objektdatei (nicht ausfu:hrbar) mit den
           Namen foobar.o. Diese kann mit anderen Objektdateien zusammen zu
           einer ausfu:hrbaren Datei verlinkt werden.

   -g

           Diese Option erzeugt die Debug-Version einer ausfu:hrbaren Datei.
           Dabei fu:gt der Compiler zusa:tzliche Informationen daru:ber,
           welcher Funktionsaufruf zu welcher Zeile im Quelltext geho:rt, der
           ausfu:hrbaren Datei hinzu. Ein Debugger kann Ihnen mit Hilfe
           dieser Information den zugeho:rigen Quelltext anzeigen, wa:hrend
           Sie den Programmverlauf schrittweise verfolgen, was sehr hilfreich
           sein kann; der Nachteil dabei ist, dass durch die zusa:tzlichen
           Informationen das Programm viel gro:sser wird. Normalerweise
           verwendet man die Option -g wa:hrend der Entwicklung eines
           Programms, und fu:r die "Release-Version", wenn man von der
           Korrektheit des Programms u:berzeugt ist, kompiliert man das
           Programm dann ohne diese Option.

 % cc -g foobar.c
            

           Mit diesem Befehl wird eine Debug-Version des Programms erzeugt.
           [5]

   -O

           Diese Option erzeugt eine optimierte Version der ausfu:hrbaren
           Datei. Der Compiler verwendet einige clevere Tricks, um das
           erzeugte Programm schneller zu machen. Sie ko:nnen hinter der
           Option -O eine Zahl angeben, um eine ho:heres Level der
           Optimierung festzulegen. Dadurch wird jedoch ha:ufig eine
           fehlerhafte Optimierung seitens des Compilers aufgedeckt. Zum
           Beispiel erzeugte die Version des cc, welche mit dem FreeBSD
           Release 2.1.0 mitgeliefert wurde, bei Verwendung der Option -O2
           unter bestimmten Umsta:nden falschen Code.

           Optimierungen werden normalerweise nur beim Kompilieren von
           Release-Versionen aktiviert.

 % cc -O -o foobar foobar.c
            

           Durch diesen Befehl wird eine optimierte Version von foobar
           erzeugt.

   Die folgenden drei Flags zwingen den cc dazu, Ihren Code auf die
   Einhaltung der internationalen Standards hin zu u:berpru:fen, welche
   ha:ufig als ANSI Standards bezeichnet werden, obwohl sie streng genommen
   zum ISO Standard geho:ren.

   -Wall

           Aktivieren aller Warnmeldungen, die die Autoren des cc fu:r
           wichtig halten. Trotz des Namens dieser Option werden dadurch
           nicht sa:mtliche Warnungen ausgegeben, die der cc ausgeben
           ko:nnte.

   -ansi

           Deaktivieren der meisten, jedoch nicht aller, nicht-ANSI C
           Eigenschaften, die der cc bietet. Trotz des Namens ist durch diese
           Option nicht sichergestellt, dass Ihr Code diese Standards auch
           vollsta:ndig einha:lt.

   -pedantic

           Deaktivieren aller Eigenschaften des cc, welche nicht konform zu
           ANSI C sind.

   Ohne diese Flags wird Ihnen der cc die Verwendung eigener Erweiterungen
   des Standards erlauben. Einige dieser Erweiterungen sind zwar sehr
   nu:tzlich, werden jedoch nicht von anderen Compilern
   unterstu:tzt-eigentlich ist eines der Hauptziele des Standards, das Leute
   Code so schreiben ko:nnen, dass dieser mit jedem Compiler auf beliebigen
   Systemen funktioniert. Dies wird ha:ufig als portabler Code bezeichnet.

   Im Allgemeinen sollten Sie versuchen, Ihren Code so portabel wie mo:glich
   zu schreiben, da Sie ansonsten eventuell das gesamte Programm noch einmal
   neu schreiben mu:ssen, falls dieser in einer anderen Umgebung laufen
   soll-und wer weiss schon was er in ein paar Jahren verwenden wird?

 % cc -Wall -ansi -pedantic -o foobar foobar.c

   Durch diesen Befehl wird eine ausfu:hrbare Datei namens foobar erzeugt,
   nachdem foobar.c auf die Einhaltung der Standards u:berpru:ft wurde.

   -llibrary

           Mit dieser Option kann eine Bibliothek mit Funktionen angegeben
           werden, die wa:hrend des Verlinkens verwendet wird.

           Das am ha:ufigsten auftretende Beispiel dieser Option ist die
           U:bersetzung eines Programmes, welches einige der mathematischen
           Funktionen in C verwendet. Im Gegensatz zu den meisten anderen
           Plattformen befinden sich diese Funktionen in einer separaten
           Bibliothek, deren Verwendung Sie dem Compiler explizit mitteilen
           mu:ssen.

           Angenommen eine Bibliothek heisst libirgendwas.a, dann mu:ssen Sie
           dem cc als Argument -lirgendwas u:bergeben. Zum Beispiel heisst
           die Mathematik-Bibliothek libm.a, und daher mu:ssen Sie dem cc als
           Argument -lm u:bergeben. Ein typisches "Manko" der
           Mathematik-Bibliothek ist, dass diese immer die letzte Bibliothek
           auf der Kommandozeile sein muss.

 % cc -o foobar foobar.c -lm
            

           Durch diesen Befehl werden die Funktionen aus der
           Mathematik-Bibliothek in foobar gelinkt.

           Wenn Sie C++-Code kompilieren wollen, mu:ssen Sie -lg++, bzw.
           -lstdc++ falls Sie FreeBSD 2.2 oder neuer verwenden, zu Ihrer
           Kommandozeile hinzufu:gen, um Ihr Programm gegen die Funktionen
           der C++ Bibliothek zu linken. Alternativ ko:nnen Sie anstatt cc
           auch c++ aufrufen, welcher dies fu:r Sie erledigt. c++ kann unter
           FreeBSD auch als g++ aufgerufen werden.

 % cc -o foobar foobar.cc -lg++     Bei FreeBSD 2.1.6 oder a:lter
 % cc -o foobar foobar.cc -lstdc++  Bei FreeBSD 2.2 und neuer
 % c++ -o foobar foobar.cc
            

           Beide Varianten erzeugen eine ausfu:hrbare foobar aus der C++
           Quelltextdatei foobar.cc. Beachten Sie bitte, dass auf UNIX(R)
           Systemen C++ Quelltextdateien u:blicherweise auf .C, .cxx oder .cc
           enden, und nicht wie bei MS-DOS(R) auf .cpp (welche schon
           anderweitig benutzt wurde). Der gcc hat normalerweise anhand
           dieser Information entschieden, welcher Compiler fu:r die
           Quelltextdatei zum Einsatz kommen soll; allerdings gilt diese
           Einschra:nkung jetzt nicht mehr, und Sie ko:nnen Ihre C++-Dateien
           ungestraft auf .cpp enden lassen!

  2.4.1. Ha:ufig auftretende cc-Fragen und -Probleme

   2.4.1.1. Ich versuche ein Programm zu schreiben, welches die Funktion
   sin() verwendet, erhalte jedoch eine Fehlermeldung. Was bedeutet diese?

   2.4.1.2. So, ich habe jetzt dieses einfache Programm als U:bung fu:r -lm
   geschrieben. Alles was es macht ist, 2.1 hoch 6 zu berechnen.

   2.4.1.3. Wie kann ich das korrigieren?

   2.4.1.4. Ich habe eine Datei mit dem Namen foobar.c kompiliert, kann
   jedoch nirgends eine ausfu:hrbare Datei namens foobar finden. Wo befindet
   sich diese?

   2.4.1.5. OK, ich habe eine ausfu:hrbare Datei namens foobar, ich kann sie
   sehen, wenn ich ls aufrufe. Gebe ich jedoch foobar in die Kommandozeile
   ein wird mir gesagt, dass eine Datei mit diesem Namen nicht existiert.
   Warum kann die Datei nicht gefunden werden?

   2.4.1.6. Ich habe meine ausfu:hrbare Datei test genannt, allerdings
   passiert nichts wenn ich diese aufrufe. Was ist hier los?

   2.4.1.7. Ich habe mein Programm kompiliert und bei dessen Aufruf sah
   zuerst alles gut aus. Jedoch gab es dann eine Fehlermeldung, welche
   irgendetwas mit core dumped lautete. Was bedeutet das?

   2.4.1.8. Faszinierendes Zeugs, aber was soll ich jetzt machen?

   2.4.1.9. Als mein Programm den core dump erzeugt hat, sagte es etwas von
   einem segmentation fault. Was ist das?

   2.4.1.10. Wenn ich einen core dump erhalte erscheint manchmal die Meldung
   bus error. In meinem UNIX(R)-Buch steht, dass die Ursache ein
   Hardwareproblem sei. Der Computer scheint aber weiterhin zu funktionieren.
   Ist dies wahr?

   2.4.1.11. Diese Sache mit den core dumps ho:rt sich sehr nu:tzlich an,
   wenn ich so etwas selber an beliebiger Stelle bewirken ko:nnte. Kann ich
   das tun, oder muss ich warten bis ein Fehler auftritt?

2.4.1.1.  Ich versuche ein Programm zu schreiben, welches die Funktion sin()         
          verwendet, erhalte jedoch eine Fehlermeldung. Was bedeutet diese?          
                                                                                     
          /var/tmp/cc0143941.o: Undefined symbol `_sin' referenced from text segment 
                                                                                     
          Wenn Sie mathematische Funktionen wie sin() verwenden wollen, mu:ssen Sie  
          den cc anweisen, die Mathematik-Bibliothek wie folgt zu verlinken:         
                                                                                     
          % cc -o foobar foobar.c -lm                                                
                                                                                     
2.4.1.2.  So, ich habe jetzt dieses einfache Programm als U:bung fu:r -lm            
          geschrieben. Alles was es macht ist, 2.1 hoch 6 zu berechnen.              
                                                                                     
          #include <stdio.h>                                                         
                                                                                     
          int main() {                                                               
                  float f;                                                           
                                                                                     
                  f = pow(2.1, 6);                                                   
                  printf("2.1 ^ 6 = %f\n", f);                                       
                  return 0;                                                          
          }                                                                          
                                                                                     
                                                                                     
          und ich habe es wie folgt kompiliert:                                      
                                                                                     
          % cc temp.c -lm                                                            
                                                                                     
                                                                                     
          wie mir gesagt wurde. Allerdings bekomme ich jetzt bei der Ausfu:hrung die 
          folgende Ausgabe:                                                          
                                                                                     
          % ./a.out                                                                  
          2.1 ^ 6 = 1023.000000                                                      
                                                                                     
                                                                                     
          Das ist nicht die richtige Antwort! Was ist hier los?                      
          Wenn der Compiler Ihren Funktionsaufruf sieht, u:berpru:ft er, ob er schon 
          einmal einen Prototypen fu:r diese gesehen hat. Wenn nicht nimmt er als    
          Ru:ckgabewert den Typ int an, was sicherlich nicht das ist, was Sie an     
          dieser Stelle wollen.                                                      
2.4.1.3.  Wie kann ich das korrigieren?                                              
          Die Prototypen der mathematischen Funktionen befinden sich in der Datei    
          math.h. Wenn Sie diese Datei in Ihrem Quelltext includen ist der Compiler  
          in der Lage, den Prototypen zu finden, und wird aufho:ren, seltsame Dinge  
          mit Ihrer Berechnung zu machen!                                            
                                                                                     
          #include <math.h>                                                          
          #include <stdio.h>                                                         
                                                                                     
          int main() {                                                               
          ...                                                                        
                                                                                     
                                                                                     
          Nach erneutem Compilieren sollte das Folgende bei der Ausfu:hrung          
          ausgegeben werden:                                                         
                                                                                     
          % ./a.out                                                                  
          2.1 ^ 6 = 85.766121                                                        
                                                                                     
                                                                                     
          Wenn Sie irgendwelche mathematischen Funktionen verwenden sollten Sie      
          immer die Datei math.h includen und nicht vergessen, Ihr Programm gegen    
          die Mathematik-Bibliothek zu verlinken.                                    
2.4.1.4.  Ich habe eine Datei mit dem Namen foobar.c kompiliert, kann jedoch         
          nirgends eine ausfu:hrbare Datei namens foobar finden. Wo befindet sich    
          diese?                                                                     
          Denken Sie daran, dass der cc die ausfu:hrbare Datei a.out nennt, wenn Sie 
          nicht explizit einen Namen angeben. Verwenden Sie in solch einem Fall die  
          Option -o filename:                                                        
                                                                                     
          % cc -o foobar foobar.c                                                    
                                                                                     
2.4.1.5.  OK, ich habe eine ausfu:hrbare Datei namens foobar, ich kann sie sehen,    
          wenn ich ls aufrufe. Gebe ich jedoch foobar in die Kommandozeile ein wird  
          mir gesagt, dass eine Datei mit diesem Namen nicht existiert. Warum kann   
          die Datei nicht gefunden werden?                                           
          Im Gegensatz zu MS-DOS(R) sucht UNIX(R) nicht im aktuellen Verzeichnis     
          nach einem ausfu:hrbaren Programm, das Sie versuchen auszufu:hren, solange 
          Sie dies nicht explizit mit angeben. Sie ko:nnen entweder ./foobar         
          eingeben, was soviel bedeutet wie "fu:hre eine Datei namens foobar im      
          aktuellen Verzeichnis aus", oder Sie ko:nnen Ihre Umgebungsvariable PATH   
          so erweitern, dass sie a:hnlich wie folgt aussieht                         
                                                                                     
          bin:/usr/bin:/usr/local/bin:.                                              
                                                                                     
                                                                                     
          Der Punkt am Ende bedeutet "siehe im aktuellen Verzeichnis nach, wenn es   
          in keinem der anderen zu finden war".                                      
2.4.1.6.  Ich habe meine ausfu:hrbare Datei test genannt, allerdings passiert nichts 
          wenn ich diese aufrufe. Was ist hier los?                                  
          Bei den meisten UNIX(R)-Systeme existiert bereits ein Programm mit dem     
          Namen test im Verzeichnis /usr/bin, und die Shell nimmt dieses, bevor sie  
          im aktuellen Verzeichnis nachsieht. Sie ko:nnen entweder den folgenden     
          Befehl eingeben:                                                           
                                                                                     
          % ./test                                                                   
                                                                                     
                                                                                     
          oder Sie ko:nnen einen geeigneteren Namen fu:r Ihr Programm wa:hlen!       
2.4.1.7.  Ich habe mein Programm kompiliert und bei dessen Aufruf sah zuerst alles   
          gut aus. Jedoch gab es dann eine Fehlermeldung, welche irgendetwas mit     
          core dumped lautete. Was bedeutet das?                                     
          Der Name core dump stammt noch aus sehr fru:hen Zeiten von UNIX(R), als    
          die Maschinen noch Kernspeicher zum Speichern von Daten verwendeten.       
          Einfach ausgedru:ckt, wenn bei einem Programm unter bestimmen Bedingungen  
          ein Fehler auftrat, hat das System den Inhalt des Kernspeichers auf der    
          Festplatte in eine Datei namens core geschrieben, welche der Programmierer 
          dann na:her untersuchen konnte, um die Ursache des Fehlers herauszufinden. 
2.4.1.8.  Faszinierendes Zeugs, aber was soll ich jetzt machen?                      
          Verwenden Sie den gdb, um das Speicherabbild zu untersuchen (siehe         
          Abschnitt 2.6, "Debuggen").                                                
2.4.1.9.  Als mein Programm den core dump erzeugt hat, sagte es etwas von einem      
          segmentation fault. Was ist das?                                           
          Diese Meldung heisst im Prinzip, dass Ihr Programm eine illegale Operation 
          mit dem Speicher durchfu:hren wollte; UNIX(R) wurde so entworfen, dass es  
          das andere Programme und das Betriebssystem selbst vor wildgewordenen      
          Programmen schu:tzt.                                                       
                                                                                     
          Ha:ufige Ursachen hierfu:r sind:                                           
                                                                                     
            * Der Versuch, einen NULL-Zeiger zu beschreiben, z.B.                    
                                                                                     
           char *foo = NULL;                                                         
           strcpy(foo, "bang!");                                                     
                                                                                     
                                                                                     
            * Einen Zeiger zu verwenden, welcher noch nicht initialisiert wurde,     
              z.B.                                                                   
                                                                                     
           char *foo;                                                                
           strcpy(foo, "bang!");                                                     
                                                                                     
                                                                                     
              Der Zeiger hat einen zufa:lligen Wert, welcher mit etwas Glu:ck in     
              einen Bereich des Speichers zeigt, der fu:r Ihr Programm nicht         
              verfu:gbar ist, und der Kernel bricht Ihr Programm ab, bevor es        
              irgendwelchen Schaden anrichten kann. Wenn Sie Pech haben zeigt der    
              Zeiger irgendwo mitten in Ihr eigenes Programm, und vera:ndert dort    
              ihre eigenen Datenstrukturen, was zu sehr seltsamen Fehlern Ihres      
              Programmes fu:hrt.                                                     
                                                                                     
            * Der Versuch, auf Daten ausserhalb eines Arrays zuzugreifen, z.B.       
                                                                                     
           int bar[20];                                                              
           bar[27] = 6;                                                              
                                                                                     
                                                                                     
            * Der Versuch, Daten in eine Speicherbereich zu schreiben, der nur       
              lesbar ist, z.B.                                                       
                                                                                     
           char *foo = "My string";                                                  
           strcpy(foo, "bang!");                                                     
                                                                                     
                                                                                     
              UNIX(R)-Compiler speichern ha:ufig feste Zeichenketten wie "My string" 
              in nur lesbaren Speicherbereichen ab.                                  
                                                                                     
            * Wenn man unerlaubte Operationen mit malloc() und free() ausfu:hrt,     
              z.B.                                                                   
                                                                                     
           char bar[80];                                                             
           free(bar);                                                                
                                                                                     
                                                                                     
              oder                                                                   
                                                                                     
           char *foo = malloc(27);                                                   
           free(foo);                                                                
           free(foo);                                                                
                                                                                     
                                                                                     
          Einzelne solcher Fehler fu:hren zwar nicht immer zu einem Fehlverhalten    
          des Programms, stellen jedoch immer eine falsche Verwendung dar. Manche    
          Systeme und Compiler sind toleranter als andere, weshalb Programme auf dem 
          einen System einwandfrei laufen, auf dem anderen System jedoch abstu:rzen. 
2.4.1.10. Wenn ich einen core dump erhalte erscheint manchmal die Meldung bus error. 
          In meinem UNIX(R)-Buch steht, dass die Ursache ein Hardwareproblem sei.    
          Der Computer scheint aber weiterhin zu funktionieren. Ist dies wahr?       
          Nein, glu:cklicherweise nicht (es sei denn Sie haben wirklich ein          
          Hardwareproblem...). U:blicherweise ist dies ein Weg Ihnen mitzuteilen,    
          dass Sie auf Speicher in einer Weise zugegriffen haben, in der Sie dies    
          nicht tun sollten.                                                         
2.4.1.11. Diese Sache mit den core dumps ho:rt sich sehr nu:tzlich an, wenn ich so   
          etwas selber an beliebiger Stelle bewirken ko:nnte. Kann ich das tun, oder 
          muss ich warten bis ein Fehler auftritt?                                   
          Ja, nehmen sie einfach eine andere Konsole oder XTerm und fu:hren Sie      
                                                                                     
          % ps                                                                       
                                                                                     
                                                                                     
          aus, um die Prozess-ID Ihres Programms herauszufinden. Fu:hren Sie         
          anschliessend                                                              
                                                                                     
          % kill -ABRT pid                                                           
                                                                                     
                                                                                     
          aus, wobei pid die Prozess-ID ist, die Sie vorher ermittelt haben.         
                                                                                     
          Dies ist nu:tzlich, wenn sich Ihr Programm z.B. in einer Endlosschleife    
          verfangen hat. Sollte Ihr Programm das Signal SIGABRT abfangen, gibt es    
          noch andere Mo:glichkeiten, die denselben Effekt haben.                    
                                                                                     
          Alternativ ko:nnen Sie einen core dump aus Ihrem Programm heraus           
          erstellen, indem Sie die Funktion abort() aufrufen. Weitere Informationen  
          daru:ber ko:nnen Sie in der Manualpage abort(3) nachlesen.                 
                                                                                     
          Wenn Sie einen core dump von ausserhalb Ihres Programms erzeugen wollen,   
          ohne dabei den Prozess abzubrechen, ko:nnen Sie das Programm gcore         
          verwenden. Weitere Informationen dazu finden Sie in der zugeho:rigen       
          Manualpage gcore(1).                                                       

2.5. Make

  2.5.1. Was ist make?

   Wenn Sie an einem einfachen Programm mit nur einer oder zwei
   Quelltextdateien arbeiten, ist die Eingabe von

 % cc file1.c file2.c

   zwar nicht aufwendig, wird aber mit zunehmender Anzahl der
   Quelltextdateien sehr la:stig-und auch das Kompilieren kann eine Weile
   dauern.

   Eine Mo:glichkeit dies zu umgehen besteht in der Verwendung von
   Objektdateien, wobei man nur die Quelltextdateien neu kompiliert, die
   vera:ndert wurden. So ko:nnten wir etwa folgendes erhalten:

 % cc file1.o file2.o ... file37.c ...

   falls wir seit dem letzten Kompiliervorgang nur die Datei file37.c
   vera:ndert haben. Dadurch ko:nnte der Kompiliervorgang um einiges
   beschleunigt werden, es muss jedoch immer noch alles von Hand eingegeben
   werden.

   Oder wir ko:nnten uns ein Shell Skript schreiben. Dieses wu:rde jedoch
   alles immer wieder neu kompilieren, was bei einem grossen Projekt sehr
   ineffizient wa:re.

   Was ist, wenn wir hunderte von Quelltextdateien ha:tten? Was ist, wenn wir
   in einem Team mit anderen Leuten arbeiten wu:rden, die vergessen uns
   Bescheid zu sagen, falls sie eine der Quelltextdateien vera:ndert haben,
   die wir ebenfalls benutzen?

   Vielleicht ko:nnten wir beide Lo:sungen kombinieren und etwas wie ein
   Shell Skript schreiben, welches eine Art magische Regel enthalten wu:rde,
   die feststellt, welche Quelltextdateien neu kompiliert werden mu:ssten.
   Alles was wir bra:uchten wa:re ein Programm, das diese Regeln verstehen
   ko:nnte, da diese Aufgabe etwas zu kompliziert fu:r eine Shell ist.

   Dieses Programm heisst make. Es liest eine Datei namens makefile, welche
   ihm sagt, wie unterschiedliche Dateien voneinander abha:ngen, und
   berechnet, welche Dateien neu kompiliert werden mu:ssen und welche nicht.
   Zum Beispiel ko:nnte eine Regel etwas sagen wie "wenn fromboz.o a:lter als
   fromboz.c ist, bedeutet dies, dass jemand die Datei fromboz.c vera:ndert
   haben muss, und diese daher neu kompiliert werden muss." Das makefile
   entha:lt ausserdem Regeln die make sagen, wie die Quelltextdatei neu
   kompiliert werden muss, was dieses Tool noch sehr viel ma:chtiger macht.

   Makefiles werden normalerweise im selben Verzeichnis wie die
   Quelltextdateien abgelegt, zu denen sie geho:ren, und kann makefile,
   Makefile oder MAKEFILE heissen. Die meisten Programmierer verwenden den
   Namen Makefile, da diese Schreibweise dafu:r sorgt, dass die Datei gut
   lesbar ganz oben in der Verzeichnisliste aufgefu:hrt wird. [6]

  2.5.2. Beispielhafte Verwendung von make

   Hier ist eine sehr einfache make Datei:

 foo: foo.c
         cc -o foo foo.c

   Sie besteht aus zwei Zeilen, einer Abha:ngigkeitszeile und einer
   Erzeugungszeile.

   Die Abha:ngigkeitszeile hier besteht aus dem Namen des Programms (auch
   Ziel genannt), gefolgt von einem Doppelpunkt und einem Leerzeichen, und
   anschliessend dem Namen der Quelltextdatei. Wenn make diese Zeile liest
   u:berpru:ft es die Existenz von foo; falls diese Datei existiert
   vergleicht es das Datum der letzten A:nderung von foo mit der von foo.c.
   Falls foo nicht existiert, oder a:lter als foo.c ist, liest es die
   Erzeugungszeile um herauszufinden, was zu tun ist. Mit anderen Worten,
   dies ist die Regel die festlegt, wann foo.c neu kompiliert werden muss.

   Die Erzeugungszeile beginnt mit einem tab (dru:cken Sie dazu die
   tab-Taste) gefolgt von dem Befehl, mit dem Sie foo manuell erzeugen
   wu:rden. Wenn foo veraltet ist, oder nicht existiert, fu:hrt make diesen
   Befehl aus, um die Datei zu erzeugen. Mit anderen Worten, dies ist die
   Regel die make sagt, wie foo.c kompiliert werden muss.

   Wenn Sie also make eingeben wird dieses sicherstellen, dass foo bzgl.
   Ihrer letzten A:nderungen an foo.c auf dem neuesten Stand ist. Dieses
   Prinzip kann auf Makefiles mit hunderten von Zielen-es ist bei FreeBSD
   praktisch mo:glich, das gesamte Betriebssystem zu kompilieren, indem man
   nur make world im richtigen Verzeichnis eingibt!

   Eine weitere nu:tzliche Eigenschaft der makefiles ist, dass die Ziele
   keine Programme sein mu:ssen. Wir ko:nnten zum Beispiel eine make Datei
   haben, die wie folgt aussieht:

 foo: foo.c
         cc -o foo foo.c

 install:
         cp foo /home/me

   Wir ko:nnen make sagen welches Ziel wir erzeugt haben wollen, indem wir
   etwas wie folgt eingeben:

 % make target

   make wird dann nur dieses Ziel beachten und alle anderen ignorieren. Wenn
   wir zum Beispiel make foo mit dem obigen makefile eingeben, dann wird make
   das Ziel install ignorieren.

   Wenn wir nur make eingeben wird make immer nur nach dem ersten Ziel suchen
   und danach mit dem Suchen aufho:ren. Wenn wir hier also nur make
   eingegeben ha:tten, wu:rde es nur zu dem Ziel foo gehen, gegebenenfalls
   foo neu kompilieren, und danach einfach aufho:ren, ohne das Ziel install
   zu beachten.

   Beachten Sie, dass das install-Ziel von nichts anderem abha:ngt! Dies
   bedeutet, dass der Befehl in der nachfolgenden Zeile immer ausgefu:hrt
   wird, wenn wir dieses Ziel mittels make install aufrufen. In diesem Fall
   wird die Datei foo in das Heimatverzeichnis des Benutzers kopiert. Diese
   Vorgehensweise wird ha:ufig bei makefiles von Anwendungen benutzt, damit
   die Anwendung nach erfolgreicher Kompilierung in das richtige Verzeichnis
   installiert werden kann.

   Dieser Teil ist etwas schwierig zu erkla:ren. Wenn Sie immer noch nicht so
   richtig verstanden haben, wie make funktioniert, wa:re es das Beste, sie
   erstellen sich selber ein einfaches Programm wie "hello world" und eine
   make Datei wie die weiter oben angegebene, und experimentieren damit
   selber ein bisschen herum. Als na:chstes ko:nnten Sie mehrere
   Quelltextdateien verwenden, oder in Ihrer Quelltextdatei eine Header-Datei
   includen. Der Befehl touch ist an dieser Stelle ganz hilfreich-er
   vera:ndert das Datum einer Datei, ohne das Sie diese extra editieren
   mu:ssen.

  2.5.3. Make und include-Dateien

   C-Code beginnt ha:ufig mit einer Liste von Dateien, die included werden
   sollen, zum Beispiel stdio.h. Manche dieser Dateien sind include-Dateien
   des Systems, andere geho:ren zum aktuellen Projekt, an dem Sie gerade
   arbeiten:

 #include <stdio.h>
 #include "foo.h"

 int main(....

   Um sicherzustellen, dass diese Datei neu kompiliert wird, wenn foo.h
   vera:ndert wurde, mu:ssen Sie diese Datei Ihrem Makefile hinzufu:gen:

 foo: foo.c foo.h

   Sobald Ihr Projekt gro:sser wird und Sie mehr und mehr eigene
   include-Dateien verwalten mu:ssen wird es nur noch sehr schwer mo:glich
   sein, die U:bersicht u:ber alle include-Dateien und Dateien, die von
   diesen abha:ngen, beizubehalten. Falls Sie eine include-Datei vera:ndern,
   jedoch das erneute Kompilieren aller Dateien, die von dieser Datei
   abha:ngen, vergessen, werden die Folgen verheerend sein. Der gcc besitzt
   eine Option, bei der er Ihre Dateien analysiert und eine Liste aller
   include-Dateien und deren Abha:ngigkeiten erstellt: -MM.

   Wenn Sie das Folgende zu Ihrem Makefile hinzufu:gen:

 depend:
         gcc -E -MM *.c > .depend

   und make depend ausfu:hren, wird die Datei .depend mit einer Liste von
   Objekt-Dateien, C-Dateien und den include-Dateien auftauchen:

 foo.o: foo.c foo.h

   Falls Sie foo.h vera:ndern werden beim na:chsten Aufruf von make alle
   Dateien, die von foo.h abha:ngen, neu kompiliert.

   Vergessen Sie nicht jedes mal make depend aufzurufen, wenn Sie eine
   include-Datei zu einer Ihrer Dateien hinzugefu:gt haben.

  2.5.4. FreeBSD Makefiles

   Makefiles ko:nnen eher schwierig zu schreiben sein. Glu:cklicherweise
   kommen BSD-basierende Systeme wie FreeBSD mit einigen sehr ma:chtigen
   solcher Dateien als Teil des Systems daher. Ein sehr gutes Beispiel dafu:r
   ist das FreeBSD Portssystem. Hier ist der grundlegende Teil eines
   typischen Makefiles des Portssystems:

 MASTER_SITES=   ftp://freefall.cdrom.com/pub/FreeBSD/LOCAL_PORTS/
 DISTFILES=      scheme-microcode+dist-7.3-freebsd.tgz

 .include <bsd.port.mk>

   Wenn wir jetzt in das Verzeichnis dieses Ports wechseln und make aufrufen,
   passiert das Folgende:

    1. Es wird u:berpru:ft, ob sich der Quelltext fu:r diesen Port bereits
       auf Ihrem System befindet.

    2. Falls dies nicht der Fall ist wird eine FTP-Verbindung zu der URL in
       MASTER_SITES aufgebaut und der Quelltext heruntergeladen.

    3. Die Checksumme fu:r den Quelltext wird berechnet und mit der schon
       bekannten und fu:r sicher und gut empfundenen verglichen. Damit wird
       sichergestellt, dass der Quelltext bei der U:bertragung nicht
       bescha:digt wurde.

    4. Sa:mtliche Anpassungen, die no:tig sind, damit der Quelltext unter
       FreeBSD funktioniert, werden vorgenommen-dieser Vorgang wird auch
       patchen genannt.

    5. Alle speziellen Konfigurationen, die am Quelltext no:tig sind, werden
       vorgenommen. (Viele UNIX(R) Programmdistributionen versuchen
       herauszufinden, auf welcher UNIX(R)-Version sie kompiliert werden
       sollen und welche optionalen UNIX(R)-Features vorhanden sind-an dieser
       Stelle erhalten sie die Informationen im FreeBSD Ports Szenario).

    6. Der Quelltext fu:r das Programm wird kompiliert. Im Endeffekt wechseln
       wir in das Verzeichnis, in das der Quelltext entpackt wurde, und rufen
       make auf-die eigene make-Datei des Programms besitzt die no:tigen
       Informationen um dieses zu bauen.

    7. Wir haben jetzt eine kompilierte Version des Programmes. Wenn wir
       wollen ko:nnen wir dieses jetzt testen; wenn wir u:berzeugt vom
       Programm sind, ko:nnen wir make install eingeben. Dadurch werden das
       Programm sowie alle zugeho:rigen Dateien an die richtige Stelle
       kopiert; es wird auch ein Eintrag in der Paketdatenbank erzeugt,
       sodass der Port sehr einfach wieder deinstalliert werden kann, falls
       wir unsere Meinung u:ber dieses gea:ndert haben.

   Ich glaube jetzt werden Sie mit mir u:bereinstimmen, dass dies ziemlich
   eindrucksvoll fu:r ein Skript mit vier Zeilen ist!

   Das Geheimnis liegt in der letzten Zeile, die make anweist, in das
   makefile des Systems mit dem Namen bsd.port.mk zu sehen. Man kann diese
   Zeile zwar leicht u:bersehen, aber hierher kommt all das klevere
   Zeugs-jemand hat ein makefile geschrieben, welches make anweist, alle
   weiter oben beschriebenen Schritte durchzufu:hren (neben vielen weiteren
   Dingen, die ich nicht angesprochen habe, einschliesslich der Behandlung
   sa:mtlicher Fehler, die auftreten ko:nnten) und jeder kann darauf
   zuru:ckgreifen, indem er eine einzige Zeile in seine eigene make-Datei
   einfu:gt!

   Falls Sie einen Blick in die makefiles des Systems werfen mo:chten, finden
   Sie diese in /usr/share/mk. Es ist aber wahrscheinlich besser, wenn Sie
   damit noch warten, bis Sie ein bisschen mehr Praxiserfahrung mit makefiles
   gesammelt haben, da die dortigen makefiles sehr kompliziert sind (und wenn
   Sie sich diese ansehen sollten Sie besser eine Kanne starken Kaffee
   griffbereit haben!)

  2.5.5. Fortgeschrittene Verwendung von make

   Make ist ein sehr ma:chtiges Werkzeug und kann noch sehr viel mehr als die
   gezeigten einfachen Beispiele weiter oben. Bedauerlicherweise gibt es
   mehrere verschiedene Versionen von make, und sie alle unterscheiden sich
   betra:chtlich voneinander. Der beste Weg herauszufinden was sie ko:nnen
   ist wahrscheinlich deren Dokumentation zu lesen-hoffentlich hat diese
   Einfu:hrung Ihnen genu:gend Grundkenntnisse vermitteln ko:nnen, damit Sie
   dies tun ko:nnen.

   Die Version von make, die in FreeBSD enthalten ist, ist Berkeley make; es
   gibt eine Anleitung dazu in /usr/share/doc/psd/12.make. Um sich diese
   anzusehen, mu:ssen Sie

 % zmore paper.ascii.gz

   in diesem Verzeichnis ausfu:hren.

   Viele Anwendungen in den Ports verwenden GNU make, welches einen sehr
   guten Satz an "info"-Seiten mitbringt. Falls Sie irgendeinen dieser Ports
   installiert haben wurde GNU make automatisch als gmake mit installiert. Es
   ist auch als eigensta:ndiger Port und Paket verfu:gbar.

   Um sich die Info-Seiten fu:r GNU make anzusehen mu:ssen Sie die Datei dir
   in /usr/local/info um einen entsprechenden Eintrag erweitern. Dies
   beinhaltet das Einfu:gen einer Zeile wie

  * Make: (make).                 The GNU Make utility.

   in die Datei. Nachdem Sie dies getan haben ko:nnen Sie info eingeben und
   dann den Menu:eintrag make auswa:hlen (oder Sie ko:nnen in Emacs die
   Tastenkombination C-h i verwenden).

2.6. Debuggen

  2.6.1. Der Debugger

   Der Debugger bei FreeBSD heisst gdb (GNU debugger). Sie ko:nnen Ihn durch
   die Eingabe von

 % gdb progname

   starten, wobei viele Leute ihn vorzugsweise innerhalb von Emacs aufrufen.
   Sie erreichen dies durch die Eingabe von:

 M-x gdb RET progname RET

   Die Verwendung eines Debuggers erlaubt Ihnen Ihr Programm unter
   kontrollierteren Bedingungen ausfu:hren zu ko:nnen. Typischerweise ko:nnen
   Sie so Zeile fu:r Zeile durch Ihr Programm gehen, die Werte von Variablen
   untersuchen, diese vera:ndern, dem Debugger sagen er soll das Programm bis
   zu einem bestimmten Punkt ausfu:hren und dann anhalten, und so weiter und
   so fort. Sie ko:nnen damit sogar ein schon laufendes Programm untersuchen,
   oder eine Datei mit einem Kernspeicherabbild laden um herauszufinden,
   warum das Programm abgestu:rzt ist. Es ist sogar mo:glich damit den Kernel
   zu debuggen, wobei dies etwas trickreicher als bei den Benutzeranwendungen
   ist, welche wir in diesem Abschnitt behandeln werden.

   Der gdb besitzt eine recht gute Online-Hilfe, sowie einen Satz von
   Info-Seiten, weshalb sich dieser Abschnitt auf ein paar grundlegende
   Befehle beschra:nken wird.

   Falls Sie den textbasierten Kommandozeilen-Stil abstossend finden gibt es
   ein graphisches Front-End dafu:r (devel/xxgdb) in der Ports-Sammlung.

   Dieser Abschnitt ist als Einfu:hrung in die Verwendung des gdb gedacht und
   beinhaltet nicht spezielle Themen wie das Debuggen des Kernels.

  2.6.2. Ein Programm im Debugger ausfu:hren

   Sie mu:ssen das Programm mit der Option -g kompiliert haben um den gdb
   effektiv einsetzen zu ko:nnen. Es geht auch ohne diese Option, allerdings
   werden Sie dann nur den Namen der Funktion sehen, in der Sie sich gerade
   befinden, anstatt direkt den zugeho:rigen Quelltext. Falls Sie eine
   Meldung wie die folgende sehen:

 ... (no debugging symbols found) ...

   wenn der gdb gestartet wird, dann wissen Sie, dass das Programm nicht mit
   der Option -g kompiliert wurde.

   Geben Sie in der Eingabeaufforderung des gdb break main ein. Dies weist
   den Debugger an, dass Sie nicht daran interessiert sind, den einleitenden
   Schritten beim Programmstart zuzusehen und dass am Anfang Ihres Codes die
   Ausfu:hrung beginnen soll. Geben Sie nun run ein, um das Programm zu
   starten - es wird starten und beim Aufruf von main() vom Debugger
   angehalten werden. (Falls Sie sich jemals gewundert haben von welcher
   Stelle main() aufgerufen wird, dann wissen Sie es jetzt!).

   Sie ko:nnen nun Schritt fu:r Schritt durch Ihr Programm gehen, indem Sie n
   dru:cken. Wenn Sie zu einem Funktionsaufruf kommen ko:nnen Sie diese
   Funktion durch dru:cken von s betreten. Sobald Sie sich in einem
   Funktionsaufruf befinden ko:nnen Sie diesen durch dru:cken von f wieder
   verlassen. Sie ko:nnen auch up und down verwenden, um sich schnell den
   Aufrufer einer Funktion anzusehen.

   Hier ist ein einfaches Beispiel, wie man mit Hilfe des gdb einen Fehler in
   einem Programm findet. Dies ist unser eigenes Programm (mit einem
   absichtlich eingebauten Fehler):

 #include <stdio.h>

 int bazz(int anint);

 main() {
         int i;

         printf("This is my program\n");
         bazz(i);
         return 0;
 }

 int bazz(int anint) {
         printf("You gave me %d\n", anint);
         return anint;
 }

   Dieses Programm setzt i auf den Wert 5 und u:bergibt dies einer Funktion
   bazz(), welche den Wert ausgibt, den Sie von uns erhalten hat.

   Wenn wir das Programm kompilieren und ausfu:hren erhalten wir

 % cc -g -o temp temp.c
 % ./temp
 This is my program
 anint = 4231

   Das ist nicht was wir erwartet hatten! Es ist Zeit, dass wir sehen was
   hier passiert!

 % gdb temp
 GDB is free software and you are welcome to distribute copies of it
  under certain conditions; type "show copying" to see the conditions.
 There is absolutely no warranty for GDB; type "show warranty" for details.
 GDB 4.13 (i386-unknown-freebsd), Copyright 1994 Free Software Foundation, Inc.
 (gdb) break main                                Skip the set-up code
 Breakpoint 1 at 0x160f: file temp.c, line 9.    gdb puts breakpoint at main()
 (gdb) run                                       Run as far as main()
 Starting program: /home/james/tmp/temp          Program starts running

 Breakpoint 1, main () at temp.c:9               gdb stops at main()
 (gdb) n                                         Go to next line
 This is my program                              Program prints out
 (gdb) s                                         step into bazz()
 bazz (anint=4231) at temp.c:17                  gdb displays stack frame
 (gdb)

   Halt mal! Wieso hat denn anint den Wert 4231? Haben wir dieser Variablen
   nicht in main() den Wert 5 zugewiesen? Gehen wir mal zuru:ck zu main() und
   schauen dort nach.

 (gdb) up                                        Move up call stack
 #1  0x1625 in main () at temp.c:11              gdb displays stack frame
 (gdb) p i                                       Show us the value of i
 $1 = 4231                                       gdb displays 4231

   Oh! Anscheinend haben wir vergessen i zu initialisieren. Wir wollten
   eigentlich

 ...
 main() {
         int i;

         i = 5;
         printf("This is my program\n");
 ...

   schreiben, haben aber die Zeile mit i=5; vergessen. Da wir i nicht
   initialisiert haben hatte diese Variable gerade den Wert, der in dem ihr
   zugewiesenen Speicherbereich stand als wir das Programm gestartet haben,
   welcher in diesem Fall 4231 war.

  Anmerkung:

   Der gdb zeigt jedes mal, wenn wir eine Funktion betreten oder verlassen,
   den Inhalt des Stack-Rahmens an, selbst wenn wir uns mit up und down im
   Aufruf-Stack umher bewegen. Dabei wird der Name der Funktion sowie der
   u:bergebenen Argumente angezeigt, was uns dabei hilft, die U:bersicht zu
   behalten. (Der Stack ist ein Speicherbereich, in dem ein Programm
   Informationen u:ber die an eine Funktion u:bergebenen Argumente ablegt,
   sowie die Ru:cksprungadresse eines Funktionsaufrufes).

  2.6.3. Eine Kernspeicherdatei untersuchen

   Eine Kernspeicherdatei ist im Prinzip eine Datei, die den vollsta:ndigen
   Zustand eines Prozesses entha:lt, als dieses abgestu:rzt ist. In "den
   guten alten Zeiten" mussten Programmierer hexadezimale Listen der
   Kernspeicherdatei ausdrucken und u:ber Maschinencodehandbu:chern
   schwitzen, aber heutzutage ist das Leben etwas einfacher geworden.
   Zufa:lligerweise wird die Kernspeicherdatei unter FreeBSD und anderen
   4.4BSD-Systemen progname.core anstatt einfach nur core genannt, um
   deutlich zu machen, zu welchem Programm eine Kernspeicherdatei geho:rt.

   Um eine Kernspeicherdatei zu untersuchen mu:ssen Sie den gdb wie gewohnt
   starten. An Stelle von break oder run mu:ssen Sie das Folgende eingeben

 (gdb) core progname.core

   Wenn Sie sich nicht in demselben Verzeichnis befinden wie die
   Kernspeicherdatei mu:ssen Sie zuerst dir /path/to/core/file eingeben.

   Sie sollten dann etwas wie folgt sehen:

 % gdb a.out
 GDB is free software and you are welcome to distribute copies of it
  under certain conditions; type "show copying" to see the conditions.
 There is absolutely no warranty for GDB; type "show warranty" for details.
 GDB 4.13 (i386-unknown-freebsd), Copyright 1994 Free Software Foundation, Inc.
 (gdb) core a.out.core
 Core was generated by `a.out'.
 Program terminated with signal 11, Segmentation fault.
 Cannot access memory at address 0x7020796d.
 #0  0x164a in bazz (anint=0x5) at temp.c:17
 (gdb)

   In diesem Fall hiess das Programm a.out, weshalb die Kernspeicherdatei den
   Namen a.out.core tra:gt. Wie wir sehen ko:nnen stu:rzte das Programm in
   einer Funktion namens bazz ab, als es versuchte auf einen Speicherbereich
   zuzugreifen, der dem Programm nicht zur Verfu:gung stand.

   Manchmal ist es ganz nu:tzlich zu sehen, wie eine Funktion aufgerufen
   wurde, da bei komplexen Programmen das eigentliche Problem schon sehr viel
   weiter oben auf dem Aufruf-Stack aufgetreten sein ko:nnte. Der Befehl bt
   veranlasst den gdb dazu, einen Backtrace des Aufruf-Stacks auszugeben:

 (gdb) bt
 #0  0x164a in bazz (anint=0x5) at temp.c:17
 #1  0xefbfd888 in end ()
 #2  0x162c in main () at temp.c:11
 (gdb)

   Die Funktion end() wird aufgerufen, wenn ein Programm abstu:rzt; in diesem
   Fall wurde die Funktion bazz() aus der main()-Funktion heraus aufgerufen.

  2.6.4. Ein bereits laufendes Programm untersuchen

   Eine der tollsten Features des gdb ist die Mo:glichkeit, damit bereits
   laufende Programme zu untersuchen. Dies bedeutet natu:rlich, dass Sie die
   erforderlichen Rechte dafu:r besitzen. Ein ha:ufig auftretendes Problem
   ist das Untersuchen eines Programmes, welches sich selber forkt.
   Vielleicht will man den Kindprozess untersuchen, aber der Debugger erlaubt
   einem nur den Zugriff auf den Elternprozess.

   Was Sie an solch einer Stelle machen ist, Sie starten einen weiteren gdb,
   ermitteln mit Hilfe von ps die Prozess-ID des Kindprozesses, und geben

 (gdb) attach pid

   im gdb ein, und ko:nnen dann wie u:blich mit der Fehlersuche fortfahren.

   "Das ist zwar alles sehr scho:n," werden Sie jetzt vielleicht denken,
   "aber in der Zeit, in der ich diese Schritte durchfu:hre, ist der
   Kindprozess schon la:ngst u:ber alle Berge". Fu:rchtet euch nicht, edler
   Leser, denn Ihr mu:sst wie folgt vorgehen (freundlicherweise zur
   Verfu:gung gestellt von den Info-Seite des gdb):

 ...
 if ((pid = fork()) < 0)         /* _Always_ check this */
         error();
 else if (pid == 0) {            /* child */
         int PauseMode = 1;

         while (PauseMode)
                 sleep(10);      /* Wait until someone attaches to us */
         ...
 } else {                        /* parent */
         ...

   Alles was Sie jetzt noch tun mu:ssen ist, sich an den Kindprozess
   ranzuha:ngen, PauseMode auf 0 zu setzen und auf den sleep()
   Funktionsaufruf zu warten, um zuru:ckzukehren!

2.7. Emacs als Entwicklungsumgebung verwenden

  2.7.1. Emacs

   Leider werden UNIX(R)-Systeme nicht mit einem
   alles-was-du-jemals-brauchst-und-vieles-mehr-megapaket an integrierten
   Entwicklungsumgebungen ausgestattet, die bei anderen Systemen dabei sind.
   [7] Trotzdem ist es mo:glich, seine eigene Entwicklungsumgebung
   aufzusetzen. Diese wird vielleicht nicht so hu:bsch und integriert sein,
   aber dafu:r ko:nnen Sie sie Ihren eigenen Wu:nschen anpassen. Und sie ist
   frei. Und Sie haben die Quelltexte davon.

   Der Schlu:ssel zu all dem ist Emacs. Es gibt zwar ein paar Leute die ihn
   hassen, es gibt jedoch auch viele die ihn lieben. Falls Sie zu ersteren
   geho:ren befu:rchte ich, dass dieser Abschnitt Ihnen wenig interessantes
   zu bieten hat. Des weiteren beno:tigen Sie eine angemessene Menge an
   freiem Speicher, um ihn zu benutzen-ich wu:rde 8MB fu:r den Textmodus und
   16MB unter X als absolutes Minimum empfehlen, um eine halbwegs brauchbare
   Performance zu erhalten.

   Emacs ist im Prinzip ein extrem anpassbarer Editor- in der Tat ist er so
   stark vera:nderbar, dass er eher einem Betriebssystem als einem Editor
   gleicht! Viele Entwickler und Systemadministratoren erledigen praktisch
   ihre gesamte Arbeit aus Emacs heraus und beenden ihn nur, um sich komplett
   auszuloggen.

   Es ist nicht einmal mo:glich alles hier zusammenzufassen, was man mit dem
   Emacs machen kann. Im Folgenden werden einige Features aufgelistet, die
   fu:r einen Entwickler interessant sein ko:nnten:

     * Sehr ma:chtiger Editor, der suchen-und-ersetzen mit Zeichenfolgen und
       regula:ren Ausdru:cken (Pattern) sowie das direkte Anspringen von
       Anfang/Ende von Blockausdru:cken erlaubt, etc, etc.

     * Pull-Down Menu:s und eine Online-Hilfe.

     * Sprachunabha:ngige Syntaxhervorhebung und automatische Einru:ckung.

     * Vollsta:ndig konfigurierbar.

     * Sie ko:nnen Programme im Emacs kompilieren und debuggen.

     * Bei Kompilationsfehlern ko:nnen Sie direkt zu der entsprechenden Zeile
       im Quelltext springen.

     * Benutzerfreundliches Front-End fu:r das info-Programm, um die GNU
       Hypertext Dokumentation inklusive der Dokumentation des Emacs selber.

     * Benutzerfreundliches Front-End fu:r den gdb um sich beim Verfolgen der
       Programmanweisungen den zugeho:rigen Quelltext anzeigen zu lassen.

     * Sie ko:nnen E-Mails und News im Usenet lesen, wa:hrend ihr Programm
       kompiliert wird.

   Und zweifelsfrei viele weitere Punkte, die ich u:bersehen habe.

   Emacs kann unter FreeBSD u:ber den editors/emacs Port installiert werden.

   Sobald er installiert ist starten Sie ihn, und geben dann C-h t ein, um
   die Einfu:hrung in Emacs zu lesen-d.h. Sie sollen bei gedru:ckter
   Strg-Taste die h-Taste dru:cken, beide wieder loslassen und anschliessend
   t dru:cken. (Alternativ ko:nnen Sie mit der Maus den Eintrag Emacs
   Tutorial aus dem Hilfe-Menu: auswa:hlen).

   Obwohl der Emacs Menu:s besitzt ist das Erlernen der Tastaturkombinationen
   lohnenswert, da man beim Editieren sehr viel schneller Tastenkombinationen
   eingeben kann, als die Maus zu finden und mit dieser dann an der richtigen
   Stelle zu klicken. Und wenn Sie sich mit erfahrenen Emacs-Benutzern
   unterhalten werden Sie feststellen, dass diese ha:ufig nebenbei Ausdru:cke
   wie "M-x replace-s RET foo RET bar RET" verwenden, weshalb das Erlernen
   dieser sehr nu:tzlich ist. Und Emacs hat auf jeden Fall weit mehr
   nu:tzliche Funktionen als das diese in der Menu:leiste unterzubringen
   wa:ren.

   Zum Glu:ck ist es sehr einfach die jeweiligen Tastaturkombinationen
   herauszubekommen, da diese direkt neben den Menu:eintra:gen stehen. Meine
   Empfehlung wa:re, den Menu:eintrag fu:r, sagen wir, das O:ffnen einer
   Datei zu verwenden, bis Sie die Funktionsweise verstanden haben und sie
   mit dieser vertraut sind, und es dann mit C-x C-f versuchen. Wenn Sie
   damit zufrieden sind, gehen Sie zum na:chsten Menu:eintrag.

   Falls Sie sich nicht daran erinnern ko:nnen, was eine bestimmte
   Tastenkombination macht, wa:hlen Sie Describe Key aus dem Hilfe-Menu: aus
   und geben Sie die Tastenkombination ein-Emacs sagt Ihnen dann was diese
   macht. Sie ko:nnen ebenfalls den Menu:eintrag Command Apropos verwenden,
   um alle Befehle, die ein bestimmtes Wort enthalten, mit den zugeho:rigen
   Tastenkombinationen aufgelistet zu bekommen.

   U:brigends bedeutet der Ausdruck weiter oben, bei gedru:ckter Meta-Taste x
   zu dru:cken, beide wieder loszulassen, replace-s einzugeben (Kurzversion
   fu:r replace-string-ein weiteres Feature des Emacs ist, dass Sie Befehle
   abku:rzen ko:nnen), anschliessend die return-Taste zu dru:cken, dann foo
   einzugeben (die Zeichenkette, die Sie ersetzen mo:chten), dann wieder
   return, dann die Leertaste zu dru:cken (die Zeichenkette, mit der Sie foo
   ersetzen mo:chten) und anschliessend erneut return zu dru:cken. Emacs wird
   dann die gewu:nschte suchen-und-ersetzen-Operation ausfu:hren.

   Wenn Sie sich fragen was in aller Welt die Meta-Taste ist, das ist eine
   spezielle Taste die viele UNIX(R)-Workstations besitzen.
   Bedauerlicherweise haben PCs keine solche Taste, und daher ist es
   u:blicherweise die alt-Taste (oder falls Sie Pech haben die Esc-Taste).

   Oh, und um den Emacs zu verlassen mu:ssen sie C-x C-c (das bedeutet, Sie
   mu:ssen bei gedru:ckter Strg-Taste zuerst x und dann c dru:cken) eingeben.
   Falls Sie noch irgendwelche ungespeicherten Dateien offen haben wird Emacs
   Sie fragen ob Sie diese speichern wollen. (Ignorieren Sie bitte die Stelle
   der Dokumentation, an der gesagt wird, dass C-z der u:bliche Weg ist,
   Emacs zu verlassen-dadurch wird der Emacs in den Hintergrund geschaltet,
   was nur nu:tzlich ist, wenn Sie an einem System ohne virtuelle Terminals
   arbeiten).

  2.7.2. Emacs konfigurieren

   Emacs kann viele wundervolle Dinge; manche dieser Dinge sind schon
   eingebaut, andere mu:ssen erst konfiguriert werden.

   Anstelle einer proprieta:ren Macrosprache verwendet der Emacs fu:r die
   Konfiguration eine speziell fu:r Editoren angepasste Version von Lisp,
   auch bekannt als Emacs Lisp. Das Arbeiten mit Emacs Lisp kann sehr
   hilfreich sein, wenn Sie darauf aufbauend etwas wie Common Lisp lernen
   mo:chten. Emacs Lisp hat viele Features von Common Lisp obwohl es
   betra:chtlich kleiner ist (und daher auch einfacher zu beherrschen).

   Der beste Weg um Emacs Lisp zu erlernen besteht darin, sich das Emacs
   Tutorial herunterzuladen.

   Es ist jedoch keine Kenntnis von Lisp erforderlich, um mit der
   Konfiguration von Emacs zu beginnen, da ich eine beispielhafte
   .emacs-Datei hier eingefu:gt habe, die fu:r den Anfang ausreichen sollte.
   Kopieren Sie diese einfach in Ihr Heimverzeichnis und starten Sie den
   Emacs neu, falls dieser bereits la:uft; er wird die Befehle aus der Datei
   lesen und Ihnen (hoffentlich) eine brauchbare Grundeinstellung bieten.

  2.7.3. Eine beispielhafte .emacs-Datei

   Bedauerlicherweise gibt es hier viel zu viel, um es im Detail zu
   erkla:ren; es gibt jedoch ein oder zwei Punkte, die besonders
   erwa:hnenswert sind.

     * Alles was mit einem ; anfa:ngt ist ein Kommentar und wird von Emacs
       ignoriert.

     * In der ersten Zeile mit -*- Emacs-Lisp -*- sorgt dafu:r, dass wir die
       Datei .emacs in Emacs selber editieren ko:nnen und uns damit alle
       tollen Features zum Editieren von Emacs Lisp zur Verfu:gung stehen.
       Emacs versucht dies normalerweise anhand des Dateinamens auszumachen,
       was vielleicht bei .emacs nicht funktionieren ko:nnte.

     * Die Tab-Taste ist in manchen Modi an die Einru:ckungsfunktion
       gebunden, so dass beim dru:cken dieser Taste die aktuelle Zeile
       eingeru:ckt wird. Wenn Sie ein tab-Zeichen in einen Text, welchen auch
       immer Sie dabei schreiben, einfu:gen wollen, mu:ssen Sie bei
       gedru:ckter Strg-Taste die Tab-Taste dru:cken.

     * Diese Datei unterstu:tzt Syntax Highlighting fu:r C, C++, Perl, Lisp
       und Scheme, indem die Sprache anhand des Dateinamens erraten wird.

     * Emacs hat bereits eine vordefinierte Funktion mit dem Namen
       next-error. Diese erlaubt es einem, in einem Fenster mit der
       Kompilierungsausgabe mittels M-n von einem zum na:chsten
       Kompilierungsfehler zu springen; wir definieren eine komplementa:re
       Funktion previous-error, die es uns erlaubt, mittels M-p von einem zum
       vorherigen Kompilierungsfehler zu springen. Das scho:nste Feature von
       allen ist, dass mittels C-c C-c die Quelltextdatei, in der der Fehler
       aufgetreten ist, geo:ffnet und die betreffende Zeile direkt
       angesprungen wird.

     * Wir aktivieren die Mo:glichkeit von Emacs als Server zu agieren, so
       dass wenn Sie etwas ausserhalb von Emacs machen und eine Datei
       editieren mo:chten, Sie einfach das folgende eingeben ko:nnen

 % emacsclient filename
          

       und dann die Datei in Ihrem Emacs editieren ko:nnen! [8]

   Beispiel 2.1. Eine einfache .emacs-Datei

 ;; -*-Emacs-Lisp-*-

 ;; This file is designed to be re-evaled; use the variable first-time
 ;; to avoid any problems with this.
 (defvar first-time t
   "Flag signifying this is the first time that .emacs has been evaled")

 ;; Meta
 (global-set-key "\M- " 'set-mark-command)
 (global-set-key "\M-\C-h" 'backward-kill-word)
 (global-set-key "\M-\C-r" 'query-replace)
 (global-set-key "\M-r" 'replace-string)
 (global-set-key "\M-g" 'goto-line)
 (global-set-key "\M-h" 'help-command)

 ;; Function keys
 (global-set-key [f1] 'manual-entry)
 (global-set-key [f2] 'info)
 (global-set-key [f3] 'repeat-complex-command)
 (global-set-key [f4] 'advertised-undo)
 (global-set-key [f5] 'eval-current-buffer)
 (global-set-key [f6] 'buffer-menu)
 (global-set-key [f7] 'other-window)
 (global-set-key [f8] 'find-file)
 (global-set-key [f9] 'save-buffer)
 (global-set-key [f10] 'next-error)
 (global-set-key [f11] 'compile)
 (global-set-key [f12] 'grep)
 (global-set-key [C-f1] 'compile)
 (global-set-key [C-f2] 'grep)
 (global-set-key [C-f3] 'next-error)
 (global-set-key [C-f4] 'previous-error)
 (global-set-key [C-f5] 'display-faces)
 (global-set-key [C-f8] 'dired)
 (global-set-key [C-f10] 'kill-compilation)

 ;; Keypad bindings
 (global-set-key [up] "\C-p")
 (global-set-key [down] "\C-n")
 (global-set-key [left] "\C-b")
 (global-set-key [right] "\C-f")
 (global-set-key [home] "\C-a")
 (global-set-key [end] "\C-e")
 (global-set-key [prior] "\M-v")
 (global-set-key [next] "\C-v")
 (global-set-key [C-up] "\M-\C-b")
 (global-set-key [C-down] "\M-\C-f")
 (global-set-key [C-left] "\M-b")
 (global-set-key [C-right] "\M-f")
 (global-set-key [C-home] "\M-<")
 (global-set-key [C-end] "\M->")
 (global-set-key [C-prior] "\M-<")
 (global-set-key [C-next] "\M->")

 ;; Mouse
 (global-set-key [mouse-3] 'imenu)

 ;; Misc
 (global-set-key [C-tab] "\C-q\t")       ; Control tab quotes a tab.
 (setq backup-by-copying-when-mismatch t)

 ;; Treat 'y' or <CR> as yes, 'n' as no.
 (fset 'yes-or-no-p 'y-or-n-p)
 (define-key query-replace-map [return] 'act)
 (define-key query-replace-map [?\C-m] 'act)

 ;; Load packages
 (require 'desktop)
 (require 'tar-mode)

 ;; Pretty diff mode
 (autoload 'ediff-buffers "ediff" "Intelligent Emacs interface to diff" t)
 (autoload 'ediff-files "ediff" "Intelligent Emacs interface to diff" t)
 (autoload 'ediff-files-remote "ediff"
   "Intelligent Emacs interface to diff")

 (if first-time
     (setq auto-mode-alist
           (append '(("\\.cpp$" . c++-mode)
                     ("\\.hpp$" . c++-mode)
                     ("\\.lsp$" . lisp-mode)
                     ("\\.scm$" . scheme-mode)
                     ("\\.pl$" . perl-mode)
                     ) auto-mode-alist)))

 ;; Auto font lock mode
 (defvar font-lock-auto-mode-list
   (list 'c-mode 'c++-mode 'c++-c-mode 'emacs-lisp-mode 'lisp-mode 'perl-mode 'scheme-mode)
   "List of modes to always start in font-lock-mode")

 (defvar font-lock-mode-keyword-alist
   '((c++-c-mode . c-font-lock-keywords)
     (perl-mode . perl-font-lock-keywords))
   "Associations between modes and keywords")

 (defun font-lock-auto-mode-select ()
   "Automatically select font-lock-mode if the current major mode is in font-lock-auto-mode-list"
   (if (memq major-mode font-lock-auto-mode-list)
       (progn
         (font-lock-mode t))
     )
   )

 (global-set-key [M-f1] 'font-lock-fontify-buffer)

 ;; New dabbrev stuff
 ;(require 'new-dabbrev)
 (setq dabbrev-always-check-other-buffers t)
 (setq dabbrev-abbrev-char-regexp "\\sw\\|\\s_")
 (add-hook 'emacs-lisp-mode-hook
           '(lambda ()
              (set (make-local-variable 'dabbrev-case-fold-search) nil)
              (set (make-local-variable 'dabbrev-case-replace) nil)))
 (add-hook 'c-mode-hook
           '(lambda ()
              (set (make-local-variable 'dabbrev-case-fold-search) nil)
              (set (make-local-variable 'dabbrev-case-replace) nil)))
 (add-hook 'text-mode-hook
           '(lambda ()
              (set (make-local-variable 'dabbrev-case-fold-search) t)
              (set (make-local-variable 'dabbrev-case-replace) t)))

 ;; C++ and C mode...
 (defun my-c++-mode-hook ()
   (setq tab-width 4)
   (define-key c++-mode-map "\C-m" 'reindent-then-newline-and-indent)
   (define-key c++-mode-map "\C-ce" 'c-comment-edit)
   (setq c++-auto-hungry-initial-state 'none)
   (setq c++-delete-function 'backward-delete-char)
   (setq c++-tab-always-indent t)
   (setq c-indent-level 4)
   (setq c-continued-statement-offset 4)
   (setq c++-empty-arglist-indent 4))

 (defun my-c-mode-hook ()
   (setq tab-width 4)
   (define-key c-mode-map "\C-m" 'reindent-then-newline-and-indent)
   (define-key c-mode-map "\C-ce" 'c-comment-edit)
   (setq c-auto-hungry-initial-state 'none)
   (setq c-delete-function 'backward-delete-char)
   (setq c-tab-always-indent t)
 ;; BSD-ish indentation style
   (setq c-indent-level 4)
   (setq c-continued-statement-offset 4)
   (setq c-brace-offset -4)
   (setq c-argdecl-indent 0)
   (setq c-label-offset -4))

 ;; Perl mode
 (defun my-perl-mode-hook ()
   (setq tab-width 4)
   (define-key c++-mode-map "\C-m" 'reindent-then-newline-and-indent)
   (setq perl-indent-level 4)
   (setq perl-continued-statement-offset 4))

 ;; Scheme mode...
 (defun my-scheme-mode-hook ()
   (define-key scheme-mode-map "\C-m" 'reindent-then-newline-and-indent))

 ;; Emacs-Lisp mode...
 (defun my-lisp-mode-hook ()
   (define-key lisp-mode-map "\C-m" 'reindent-then-newline-and-indent)
   (define-key lisp-mode-map "\C-i" 'lisp-indent-line)
   (define-key lisp-mode-map "\C-j" 'eval-print-last-sexp))

 ;; Add all of the hooks...
 (add-hook 'c++-mode-hook 'my-c++-mode-hook)
 (add-hook 'c-mode-hook 'my-c-mode-hook)
 (add-hook 'scheme-mode-hook 'my-scheme-mode-hook)
 (add-hook 'emacs-lisp-mode-hook 'my-lisp-mode-hook)
 (add-hook 'lisp-mode-hook 'my-lisp-mode-hook)
 (add-hook 'perl-mode-hook 'my-perl-mode-hook)

 ;; Complement to next-error
 (defun previous-error (n)
   "Visit previous compilation error message and corresponding source code."
   (interactive "p")
   (next-error (- n)))

 ;; Misc...
 (transient-mark-mode 1)
 (setq mark-even-if-inactive t)
 (setq visible-bell nil)
 (setq next-line-add-newlines nil)
 (setq compile-command "make")
 (setq suggest-key-bindings nil)
 (put 'eval-expression 'disabled nil)
 (put 'narrow-to-region 'disabled nil)
 (put 'set-goal-column 'disabled nil)
 (if (>= emacs-major-version 21)
         (setq show-trailing-whitespace t))

 ;; Elisp archive searching
 (autoload 'format-lisp-code-directory "lispdir" nil t)
 (autoload 'lisp-dir-apropos "lispdir" nil t)
 (autoload 'lisp-dir-retrieve "lispdir" nil t)
 (autoload 'lisp-dir-verify "lispdir" nil t)

 ;; Font lock mode
 (defun my-make-face (face color &optional bold)
   "Create a face from a color and optionally make it bold"
   (make-face face)
   (copy-face 'default face)
   (set-face-foreground face color)
   (if bold (make-face-bold face))
   )

 (if (eq window-system 'x)
     (progn
       (my-make-face 'blue "blue")
       (my-make-face 'red "red")
       (my-make-face 'green "dark green")
       (setq font-lock-comment-face 'blue)
       (setq font-lock-string-face 'bold)
       (setq font-lock-type-face 'bold)
       (setq font-lock-keyword-face 'bold)
       (setq font-lock-function-name-face 'red)
       (setq font-lock-doc-string-face 'green)
       (add-hook 'find-file-hooks 'font-lock-auto-mode-select)

       (setq baud-rate 1000000)
       (global-set-key "\C-cmm" 'menu-bar-mode)
       (global-set-key "\C-cms" 'scroll-bar-mode)
       (global-set-key [backspace] 'backward-delete-char)
                                         ;      (global-set-key [delete] 'delete-char)
       (standard-display-european t)
       (load-library "iso-transl")))

 ;; X11 or PC using direct screen writes
 (if window-system
     (progn
       ;;      (global-set-key [M-f1] 'hilit-repaint-command)
       ;;      (global-set-key [M-f2] [?\C-u M-f1])
       (setq hilit-mode-enable-list
             '(not text-mode c-mode c++-mode emacs-lisp-mode lisp-mode
                   scheme-mode)
             hilit-auto-highlight nil
             hilit-auto-rehighlight 'visible
             hilit-inhibit-hooks nil
             hilit-inhibit-rebinding t)
       (require 'hilit19)
       (require 'paren))
   (setq baud-rate 2400)                 ; For slow serial connections
   )

 ;; TTY type terminal
 (if (and (not window-system)
          (not (equal system-type 'ms-dos)))
     (progn
       (if first-time
           (progn
             (keyboard-translate ?\C-h ?\C-?)
             (keyboard-translate ?\C-? ?\C-h)))))

 ;; Under UNIX
 (if (not (equal system-type 'ms-dos))
     (progn
       (if first-time
           (server-start))))

 ;; Add any face changes here
 (add-hook 'term-setup-hook 'my-term-setup-hook)
 (defun my-term-setup-hook ()
   (if (eq window-system 'pc)
       (progn
 ;;      (set-face-background 'default "red")
         )))

 ;; Restore the "desktop" - do this as late as possible
 (if first-time
     (progn
       (desktop-load-default)
       (desktop-read)))

 ;; Indicate that this file has been read at least once
 (setq first-time nil)

 ;; No need to debug anything now

 (setq debug-on-error nil)

 ;; All done
 (message "All done, %s%s" (user-login-name) ".")
        

  2.7.4. Erweitern des von Emacs unterstu:tzten Sprachbereichs

   Das ist jetzt alles sehr scho:n wenn Sie ausschliesslich in einer der
   Sprachen programmieren wollen, um die wir uns bereits in der .emacs-Datei
   geku:mmert haben (C, C++, Perl, Lisp und Scheme), aber was passiert wenn
   eine neue Sprache namens "whizbang" herauskommt, mit jeder Menge neuen
   tollen Features?

   Als erstes muss festgestellt werden, ob whizbang mit irgendwelchen Dateien
   daherkommt, die Emacs etwas u:ber die Sprache sagen. Diese enden
   u:blicherweise auf .el, der Kurzform fu:r "Emacs Lisp". Falls whizbang zum
   Beispiel ein FreeBSD Port ist, ko:nnten wir diese Dateien mittels

 % find /usr/ports/lang/whizbang -name "*.el" -print

   finden und durch Kopieren in das Emacs-seitige Lisp-Verzeichnis
   installieren. Unter FreeBSD ist dies /usr/local/share/emacs/site-lisp.

   Wenn zum Beispiel die Ausgabe des find-Befehls wie folgt war

 /usr/ports/lang/whizbang/work/misc/whizbang.el

   ko:nnten wir das folgende tun

 # cp /usr/ports/lang/whizbang/work/misc/whizbang.el /usr/local/share/emacs/site-lisp

   Als na:chstes mu:ssen wir festlegen, welche Dateiendung Quelltextdateien
   fu:r whizbang haben. Lassen Sie uns um der Argumente Willen annehmen, die
   Dateiendung sei .wiz. Wir mu:ssen dann einen Eintrag unserer .emacs-Datei
   hinzufu:gen um sicherzustellen, dass Emacs die Informationen in
   whizbang.el auch verwenden kann.

   Suchen Sie den auto-mode-alist Eintrag in der .emacs-Datei und fu:gen Sie
   an dieser Stelle eine Zeile wie folgt fu:r whizbang hinzu:

 ...
 ("\\.lsp$" . lisp-mode)
 ("\\.wiz$" . whizbang-mode)
 ("\\.scm$" . scheme-mode)
 ...

   Dies bedeutet das Emacs automatisch in den whizbang-mode wechseln wird,
   wenn Sie eine Datei mit der Dateiendung .wiz editieren.

   Direkt darunter werden Sie den Eintrag font-lock-auto-mode-list finden.
   Erweitern Sie den whizbang-mode um diesen wie folgt:

 ;; Auto font lock mode
 (defvar font-lock-auto-mode-list
   (list 'c-mode 'c++-mode 'c++-c-mode 'emacs-lisp-mode 'whizbang-mode 'lisp-mode 'perl-mode 'scheme-mode)
   "List of modes to always start in font-lock-mode")

   Dies bedeutet das Emacs immer font-lock-mode (z.B. Syntax Highlighting)
   aktiviert, wenn Sie eine .wiz-Datei editieren.

   Und das ist alles was beno:tigt wird. Falls es weitere Dinge gibt, die
   automatisch beim O:ffnen einer .wiz-Datei ausgefu:hrt werden sollen,
   ko:nnen Sie einen whizbang-mode hook-Eintrag hinzufu:gen (fu:r ein
   einfaches Beispiel, welches auto-indent hinzufu:gt, sehen Sie sich bitte
   my-scheme-mode-hook an).

2.8. Weiterfu:hrende Literatur

   Fu:r Informationen zum Aufsetzen einer Entwicklungsumgebung, um
   Fehlerbehebungen an FreeBSD selber beizusteuern sehen Sie sich bitte
   development(7) an.

     * Brian Harvey and Matthew Wright Simply Scheme MIT 1994. ISBN
       0-262-08226-8

     * Randall Schwartz Learning Perl O'Reilly 1993 ISBN 1-56592-042-2

     * Patrick Henry Winston and Berthold Klaus Paul Horn Lisp (3rd Edition)
       Addison-Wesley 1989 ISBN 0-201-08319-1

     * Brian W. Kernighan and Rob Pike The Unix Programming Environment
       Prentice-Hall 1984 ISBN 0-13-937681-X

     * Brian W. Kernighan and Dennis M. Ritchie The C Programming Language
       (2nd Edition) Prentice-Hall 1988 ISBN 0-13-110362-8

     * Bjarne Stroustrup The C++ Programming Language Addison-Wesley 1991
       ISBN 0-201-53992-6

     * W. Richard Stevens Advanced Programming in the Unix Environment
       Addison-Wesley 1992 ISBN 0-201-56317-7

     * W. Richard Stevens Unix Network Programming Prentice-Hall 1990 ISBN
       0-13-949876-1

     ----------------------------------------------------------------------

   [1] Wenn die Anwendung u:ber eine Eingabeaufforderung gestartet wird
   ko:nnte bei Auftreten eines Programmfehlers dieses abgebrochen und ein
   Speicherabbild erzeugt werden.

   [2] Um genau zu sein u:bersetzt der cc den Quelltext an dieser Stelle
   nicht in Assemblersprache, sondern in seine eigene, maschinenunabha:ngige
   Sprache namens p-code.

   [3] Falls Sie es nicht wussten, Binary Sort ist, im Gegensatz zu Bubble
   Sort, eine effektive Mo:glichkeit, Dinge zu sortieren.

   [4] Der Grund dafu:r ist im Haufen der Geschichte begraben.

   [5] Beachten Sie, dass an dieser Stelle die Option -o zum Festlegen des
   Namens der ausfu:hrbaren Datei nicht verwendet wurde, weswegen an dieser
   Stelle die erzeugte Datei a.out heisst. Die Erzeugung einer Debug-Version
   namens foobar ist als U:bung dem Leser u:berlassen!

   [6] Verwenden Sie nicht MAKEFILE mit lauter Grossbuchstaben, da diese
   Schreibweise ha:ufig fu:r Dokumentationsdateien wie README benutzt wird.

   [7] Es gibt jetzt einige ma:chtige und freie IDEs in der Ports-Sammlung
   wie etwa KDevelop.

   [8] Viele Emacs-Benutzer setzen Ihre EDITOR-Umgebungsvariable auf
   emacsclient, so dass dies immer passiert, wenn sie eine Datei editieren
   mu:ssen.

Kapitel 3. Sicheres Programmieren

   Contributed by Murray Stokely.
   U:bersetzt von Hagen Ku:hl.
   Inhaltsverzeichnis

   3.1. Zusammenfassung

   3.2. Methoden des sicheren Entwurfs

   3.3. Puffer-U:berla:ufe

   3.4. SetUID-Themen

   3.5. Die Umgebung ihrer Programme einschra:nken

   3.6. Vertrauen

   3.7. Race-Conditions

3.1. Zusammenfassung

   Dieses Kapitel beschreibt einige Sicherheitsprobleme, die
   UNIX(R)-Programmierer seit Jahrzehnten qua:len, und inzwischen verfu:gbare
   Werkzeuge, die Programmierern helfen, Sicherheitslu:cken in ihrem
   Quelltext zu vermeiden.

3.2. Methoden des sicheren Entwurfs

   Sichere Anwendungen zu schreiben erfordert eine sehr skeptische und
   pessimistische Lebenseinstellung. Anwendungen sollten nach dem Prinzip der
   "geringsten Privilegien" ausgefu:hrt werden, sodass kein Prozess mit mehr
   als dem absoluten Minimum an Zugriffsrechten arbeitet, die er zum
   Erfu:llen seiner Aufgabe beno:tigt. Wo es mo:glich ist, sollte Quelltext,
   der bereits u:berpru:ft wurde, wiederverwendet werden, um ha:ufige Fehler,
   die andere schon korrigiert haben, zu vermeiden.

   Eine der Stolperfallen der UNIX(R)-Umgebung ist, dass es sehr einfach ist
   Annahmen u:ber die Konsistenz der Umgebung zu machen. Anwendungen sollten
   Nutzereingaben (in allen Formen) niemals trauen, genauso wenig wie den
   System-Ressourcen, der Inter-Prozess-Kommunikation oder dem zeitlichen
   Ablauf von Ereignissen. UNIX(R)-Prozesse arbeiten nicht synchron. Daher
   sind logische Operationen selten atomar.

3.3. Puffer-U:berla:ufe

   Puffer-U:berla:ufe gibt es schon seit den Anfa:ngen der
   Von-Neuman-Architektur 1. Sie erlangten zum ersten Mal durch den
   Internetwurm Morris im Jahre 1988 o:ffentliche Bekanntheit.
   Unglu:cklicherweise funktioniert der gleiche grundlegende Angriff noch
   heute. Die bei weitem ha:ufigste Form eines Puffer-U:berlauf-Angriffs
   basiert darauf, den Stack zu korrumpieren.

   Die meisten modernen Computer-Systeme verwenden einen Stack, um Argumente
   an Prozeduren zu u:bergeben und lokale Variablen zu speichern. Ein Stack
   ist ein last-in-first-out-Puffer (LIFO) im hohen Speicherbereich eines
   Prozesses. Wenn ein Programm eine Funktion aufruft wird ein neuer
   "Stackframe" erzeugt. Dieser besteht aus den Argumenten, die der Funktion
   u:bergeben wurden und einem variabel grossem Bereich fu:r lokale
   Variablen. Der "Stack-Pointer" ist ein Register, dass die aktuelle Adresse
   der Stack-Spitze entha:lt. Da sich dieser Wert oft a:ndert, wenn neue
   Werte auf dem Stack abgelegt werden, bieten viele Implementierungen einen
   "Frame-Pointer", der nahe am Anfang des Stack-Frames liegt und es so
   leichter macht lokale Variablen relativ zum aktuellen Stackframe zu
   adressieren. 1 Die Ru:cksprungadresse der Funktionen werden ebenfalls auf
   dem Stack gespeichert und das ist der Grund fu:r Stack-U:berlauf-Exploits.
   Denn ein bo:swilliger Nutzer kann die Ru:cksprungadresse der Funktion
   u:berschreiben indem er eine lokale Variable in der Funktion u:berlaufen
   la:sst, wodurch es ihm mo:glich ist beliebigen Code auszufu:hren.

   Obwohl Stack-basierte Angriffe bei weitem die Ha:ufigsten sind, ist es
   auch mo:glich den Stack mit einem Heap-basierten (malloc/free) Angriff zu
   u:berschreiben.

   Die C-Programmiersprache fu:hrt keine automatischen
   Bereichsu:berpru:fungen bei Feldern oder Zeigern durch, wie viele andere
   Sprachen das tun. Ausserdem entha:lt die C-Standardbibliothek eine
   Handvoll sehr gefa:hrlicher Funktionen.

   strcpy(char *dest, const char *src)       Kann den Puffer dest u:berlaufen 
                                             lassen                           
   strcat(char *dest, const char *src)       Kann den Puffer dest u:berlaufen 
                                             lassen                           
   getwd(char *buf)                          Kann den Puffer buf u:berlaufen  
                                             lassen                           
   gets(char *s)                             Kann den Puffer s u:berlaufen    
                                             lassen                           
   [vf]scanf(const char *format, ...)        Kann sein Argument u:berlaufen   
                                             lassen                           
   realpath(char *path, char                 Kann den Puffer path u:berlaufen 
   resolved_path[])                          lassen                           
   [v]sprintf(char *str, const char *format, Kann den Puffer str u:berlaufen  
   ...)                                      lassen                           

  3.3.1. Puffer-U:berlauf Beispiel

   Das folgende Quellcode-Beispiel entha:lt einen Puffer-U:berlauf, der
   darauf ausgelegt ist die Ru:cksprungadresse zu u:berschreiben und die
   Anweisung direkt nach dem Funktionsaufruf zu u:berspringen. (Inspiriert
   durch 4)

 #include <stdio.h>

 void manipulate(char *buffer) {
 char newbuffer[80];
 strcpy(newbuffer,buffer);
 }

 int main() {
 char ch,buffer[4096];
 int i=0;

 while ((buffer[i++] = getchar()) != '\n') {};

 i=1;
 manipulate(buffer);
 i=2;
 printf("The value of i is : %d\n",i);
 return 0;
 }

   Betrachten wir nun, wie das Speicherabbild dieses Prozesses aussehen
   wu:rde, wenn wir 160 Leerzeichen in unser kleines Programm eingeben, bevor
   wir Enter dru:cken.

   [XXX figure here!]

   Offensichtlich kann man durch bo:swilligere Eingaben bereits kompilierten
   Programmtext ausfu:hren (wie z.B. exec(/bin/sh)).

  3.3.2. Puffer-U:berla:ufe vermeiden

   Die direkteste Lo:sung, um Stack-U:berla:ufe zu vermeiden, ist immer
   gro:ssenbegrenzten Speicher und String-Copy-Funktionen zu verwenden.
   strncpy und strncat sind Teil der C-Standardbibliothek. Diese Funktionen
   akzeptieren einen La:ngen-Parameter. Dieser Wert sollte nicht gro:sser
   sein als die La:nge des Zielpuffers. Die Funktionen kopieren dann bis zu
   `length' Bytes von der Quelle zum Ziel. Allerdings gibt es einige
   Probleme. Keine der Funktionen garantiert, dass die Zeichenkette
   NUL-terminiert ist, wenn die Gro:sse des Eingabepuffers so gross ist wie
   das Ziel. Ausserdem wird der Parameter length zwischen strncpy und strncat
   inkonsistent definiert, weshalb Programmierer leicht bezu:glich der
   korrekten Verwendung durcheinander kommen ko:nnen. Weiterhin gibt es einen
   spu:rbaren Leistungsverlust im Vergleich zu strcpy, wenn eine kurze
   Zeichenkette in einen grossen Puffer kopiert wird. Denn strncpy fu:lt den
   Puffer bis zur angegebenen La:nge mit NUL auf.

   In OpenBSD wurde eine weitere Mo:glichkeit zum kopieren von
   Speicherbereichen implementiert, die dieses Problem umgeht. Die Funktionen
   strlcpy und strlcat garantieren, dass das Ziel immer NUL-terminiert wird,
   wenn das Argument length ungleich null ist. Fu:r weitere Informationen
   u:ber diese Funktionen lesen Sie bitte 6. Die OpenBSD-Funktionen strlcpy
   und strlcat sind seit Version 3.3 auch in FreeBSD verfu:gbar.

    3.3.2.1. Compiler-basierte Laufzeitu:berpru:fung von Grenzen

   Unglu:cklicherweise gibt es immer noch sehr viel Quelltext, der allgemein
   verwendet wird und blind Speicher umherkopiert, ohne eine der gerade
   besprochenen Funktionen, die Begrenzungen unterstu:tzen, zu verwenden.
   Glu:cklicherweise gibt es einen Weg, um solche Angriffe zu verhindern -
   U:berpru:fung der Grenzen zur Laufzeit, die in verschiedenen C/C++
   Compilern eingebaut ist.

   ProPolice ist eine solche Compiler-Eigenschaft und ist in den gcc(1)
   Versionen 4.1 und ho:her integriert. Es ersetzt und erweitert die gcc(1)
   StackGuard-Erweiterung von fru:her.

   ProPolice schu:tzt gegen stackbasierte Pufferu:berla:ufe und andere
   Angriffe durch das Ablegen von Pseudo-Zufallszahlen in Schlu:sselbereichen
   des Stacks bevor es irgendwelche Funktionen aufruft. Wenn eine Funktion
   beendet wird, werden diese "Kanarienvo:gel" u:berpru:ft und wenn
   festgestellt wird, dass diese vera:ndert wurden wird das Programm sofort
   abgebrochen. Dadurch wird jeglicher Versuch, die Ru:cksprungadresse oder
   andere Variablen, die auf dem Stack gespeichert werden, durch die
   Ausfu:hrung von Schadcode zu manipulieren, nicht funktionieren, da der
   Angreifer auch die Pseudo-Zufallszahlen unberu:hrt lassen mu:sste.

   Ihre Anwendungen mit ProPolice neu zu kompilieren ist eine effektive
   Massnahme, um sie vor den meisten Puffer-U:berlauf-Angriffen zu schu:tzen,
   aber die Programme ko:nnen noch immer kompromittiert werden.

    3.3.2.2. Bibliotheks-basierte Laufzeitu:berpru:fung von Grenzen

   Compiler-basierte Mechanismen sind bei Software, die nur im Bina:rformat
   vertrieben wird, und die somit nicht neu kompiliert werden kann vo:llig
   nutzlos. Fu:r diesen Fall gibt es einige Bibliotheken, welche die
   unsicheren Funktionen der C-Bibliothek (strcpy, fscanf, getwd, etc..) neu
   implementieren und sicherstellen, dass nicht hinter den Stack-Pointer
   geschrieben werden kann.

     * libsafe
     * libverify
     * libparanoia

   Leider haben diese Bibliotheks-basierten Verteidigungen mehrere
   Schwa:chen. Diese Bibliotheken schu:tzen nur vor einer kleinen Gruppe von
   Sicherheitslu:cken und sie ko:nnen das eigentliche Problem nicht lo:sen.
   Diese Massnahmen ko:nnen versagen, wenn die Anwendung mit
   -fomit-frame-pointer kompiliert wurde. Ausserdem kann der Nutzer die
   Umgebungsvariablen LD_PRELOAD und LD_LIBRARY_PATH u:berschreiben oder
   lo:schen.

3.4. SetUID-Themen

   Es gibt zu jedem Prozess mindestens sechs verschiedene IDs, die diesem
   zugeordnet sind. Deshalb mu:ssen Sie sehr vorsichtig mit den
   Zugriffsrechten sein, die Ihr Prozess zu jedem Zeitpunkt besitzt. Konkret
   bedeutet dass, das alle seteuid-Anwendungen ihre Privilegien abgeben
   sollten, sobald sie diese nicht mehr beno:tigen.

   Die reale Benutzer-ID kann nur von einem Superuser-Prozess gea:ndert
   werden. Das Programm login setzt sie, wenn sich ein Benutzer am System
   anmeldet, und sie wird nur selten gea:ndert.

   Die effektive Benutzer-ID wird von der Funktion exec() gesetzt, wenn ein
   Programm das seteuid-Bit gesetzt hat. Eine Anwendung kann seteuid()
   jederzeit aufrufen, um die effektive Benutzer-ID entweder auf die reale
   Benutzer-ID oder die gespeicherte set-user-ID zu setzen. Wenn eine der
   exec()-Funktionen die effektive Benutzer-ID setzt, wird der vorherige Wert
   als gespeicherte set-user-ID abgelegt.

3.5. Die Umgebung ihrer Programme einschra:nken

   Die herko:mmliche Methode, um einen Prozess einzuschra:nken, besteht in
   dem Systemaufruf chroot(). Dieser Aufruf a:ndert das Wurzelverzeichnis,
   auf das sich alle Pfadangaben des Prozesses und jegliche Kind-Prozesse
   beziehen. Damit dieser Systemaufruf gelingt, muss der Prozess
   Ausfu:hrungsrechte (Durchsuchungsrechte) fu:r das Verzeichnis haben, auf
   das er sich bezieht. Die neue Umgebung wird erst wirksam, wenn Sie mittels
   chdir() in Ihre neue Umgebung wechseln. Es sollte erwa:hnt werden, dass
   ein Prozess recht einfach aus der chroot-Umgebung ausbrechen kann, wenn er
   root-Rechte besitzt. Das kann man erreichen, indem man Gera:tedateien
   anlegt, um Kernel-Speicher zu lesen, oder indem man einen Debugger mit
   einem Prozess ausserhalb seiner chroot(8)-Umgebung verbindet, oder auf
   viele andere kreative Wege.

   Das Verhalten des Systemaufrufs chroot() kann durch die
   kern.chroot.allow_open_directories sysctl-Variable beeinflusst werden.
   Wenn diese auf 0 gesetzt ist, wird chroot() mit EPERM fehlschlagen, wenn
   irgendwelche Verzeichnisse geo:ffnet sind. Wenn die Variable auf den
   Standardwert 1 gesetzt ist, wird chroot() mit EPERM fehlschlagen, wenn
   irgendwelche Verzeichnisse geo:ffnet sind und sich der Prozess bereits in
   einer chroot()-Umgebung befindet. Bei jedem anderen Wert wird die
   U:berpru:fung auf geo:ffnete Verzeichnisse komplett umgangen.

  3.5.1. Die Jail-Funktionalita:t in FreeBSD

   Das Konzept einer Jail (Gefa:ngnis) erweitert chroot(), indem es die Macht
   des Superusers einschra:nkt, um einen echten 'virtuellen Server' zu
   erzeugen. Wenn ein solches Gefa:ngnis einmal eingerichtet ist, muss die
   gesamte Netzwerkkommunikation u:ber eine bestimmte IP-Adresse erfolgen und
   die "root-Privilegien" innerhalb der Jail sind sehr stark eingeschra:nkt.

   Solange Sie sich in einer Jail befinden, werden alle Tests auf
   Superuser-Rechte durch den Aufruf von suser() fehlschlagen. Allerdings
   wurden einige Aufrufe von suser() abgea:ndert, um die neue
   suser_xxx()-Schnittstelle zu implementieren. Diese Funktion ist dafu:r
   verantwortlich, festzustellen, ob bestimmte Superuser-Rechte einem
   eingesperrten Prozess zur Verfu:gung stehen.

   Ein Superuser-Prozess innerhalb einer Jail darf folgendes:

     * Berechtigungen vera:ndern mittels: setuid, seteuid, setgid, setegid,
       setgroups, setreuid, setregid, setlogin
     * Ressourcenbegrenzungen setzen mittels setrlimit
     * Einige sysctl-Variablen (kern.hostname) vera:ndern
     * chroot()
     * Ein Flag einer vnode setzen: chflags, fchflags
     * Attribute einer vnode setzen wie Dateiberechtigungen, Eigentu:mer,
       Gruppe, Gro:sse, Zugriffszeit und Modifikationszeit
     * Binden eines Prozesses an einen o:ffentlichen privilegierten Port
       (ports < 1024)

   Jails sind ein ma:chtiges Werkzeug, um Anwendungen in einer sicheren
   Umgebung auszufu:hren, aber sie haben auch ihre Nachteile. Derzeit wurden
   die IPC-Mechanismen noch nicht an suser_xxx angepasst, so dass Anwendungen
   wie MySQL nicht innerhalb einer Jail ausgefu:hrt werden ko:nnen. Der
   Superuser-Zugriff hat in einer Jail nur eine sehr eingeschra:nkte
   Bedeutung, aber es gibt keine Mo:glichkeit zu definieren was "sehr
   eingeschra:nkt" heisst.

  3.5.2. POSIX(R).1e Prozess Capabilities

   POSIX(R) hat einen funktionalen Entwurf (Working Draft) herausgegeben, der
   Ereignisu:berpru:fung, Zugriffskontrolllisten, feiner einstellbare
   Privilegien, Informationsmarkierung und verbindliche Zugriffskontrolle
   entha:lt.

   Dies ist im Moment in Arbeit und das Hauptziel des TrustedBSD-Projekts.
   Ein Teil der bisherigen Arbeit wurde in FreeBSD-CURRENT u:bernommen
   (cap_set_proc(3)).

3.6. Vertrauen

   Eine Anwendung sollte niemals davon ausgehen, dass irgendetwas in der
   Nutzerumgebung vernu:nftig ist. Das beinhaltet (ist aber sicher nicht
   darauf beschra:nkt): Nutzereingaben, Signale, Umgebungsvariablen,
   Ressourcen, IPC, mmaps, das Arbeitsverzeichnis im Dateisystem,
   Dateideskriptoren, die Anzahl geo:ffneter Dateien, etc..

   Sie sollten niemals annehmen, dass Sie jede Art von inkorrekten Eingaben
   abfangen ko:nnen, die ein Nutzer machen kann. Stattdessen sollte Ihre
   Anwendung positive Filterung verwenden, um nur eine bestimmte Teilmenge an
   Eingaben zuzulassen, die Sie fu:r sicher halten. Ungeeignete
   Datenu:berpru:fung ist die Ursache vieler Exploits, besonders fu:r
   CGI-Skripte im Internet. Bei Dateinamen mu:ssen Sie besonders vorsichtig
   sein, wenn es sich um Pfade ("../", "/"), symbolische Verknu:pfungen und
   Shell-Escape-Sequenzen handelt.

   Perl bietet eine wirklich coole Funktion, den sogenannten "Taint"-Modus,
   der verwendet werden kann, um zu verhindern, dass Skripte Daten, die von
   ausserhalb des Programmes stammen, auf unsichere Art und Weise verwenden.
   Dieser Modus u:berpru:ft Kommandozeilenargumente, Umgebungsvariablen,
   Lokalisierungsinformationen, die Ergebnisse von Systemaufrufen (readdir(),
   readlink(), getpwxxx()) und alle Dateieingaben.

3.7. Race-Conditions

   Eine Race-Condition ist ein unnormales Verhalten, das von einer
   unerwarteten Abha:ngigkeit beim Timing von Ereignissen verursacht wird.
   Mit anderen Worten heisst das, ein Programmierer nimmt irrtu:mlicher Weise
   an, dass ein bestimmtes Ereignis immer vor einem anderen stattfindet.

   Einige der ha:ufigsten Ursachen fu:r Race-Conditions sind Signale,
   Zugriffspru:fungen und das O:ffnen von Dateien. Signale sind von Natur aus
   asynchrone Ereignisse, deshalb ist besondere Vorsicht im Umgang damit
   geboten. Das Pru:fen des Zugriffs mittels der Aufrufe access(2) gefolgt
   von open(2) ist offensichtlich nicht atomar. Benutzer ko:nnen zwischen den
   beiden Aufrufen Dateien verschieben. Stattdessen sollten privilegierte
   Anwendungen seteuid() direkt gefolgt von open() aufrufen. Auf die gleiche
   Art sollte eine Anwendung immer eine korrekte Umask vor dem Aufruf von
   open() setzen, um sto:rende Aufrufe von chmod() zu umgehen.

Kapitel 4. Lokalisierung und Internationalisierung - L10N und I18N

   U:bersetzt von Jochen Neumeister.
   Inhaltsverzeichnis

   4.1. I18N-konforme Anwendungen programmieren

   4.2. Lokalisierte Nachrichten mit POSIX.1 Native Language Support (NLS)

4.1. I18N-konforme Anwendungen programmieren

   Um Ihre Anwendung verwendbarer fu:r andere Sprachen zu machen, hoffen wir,
   dass Sie I18N-konform programmieren. Der GNU gcc-Compiler und Bibliotheken
   fu:r grafische Benutzeroberfla:chen wie QT und GTK unterstu:tzen I18N
   durch eine spezielle Verarbeitung von Zeichenketten. Das Erstellen eines
   I18N-konformen Programms ist sehr einfach und erlaubt anderen
   Mitwirkenden, Ihre Programme leichter in andere Sprachen zu u:bersetzen.
   Lesen Sie die Bibliothek-spezifischen I18N-Dokumentationen fu:r weitere
   Details.

   Im Gegensatz zur allgemeinen Meinung ist I18N-konformer Code einfach zu
   programmieren. U:blicherweise umfasst dies nur das Einbetten Ihrer
   Zeichenketten in Bibliothek-spezifische Funktionen. Stellen Sie ausserdem
   bitte sicher, dass Sie Unterstu:tzung fu:r Unicode- und Multibyte-Zeichen
   vorsehen.

  4.1.1. Ein Aufruf, die I18N-Bemu:hungen zu vereinheitlichen

   Wir sind darauf aufmerksam geworden, dass die einzelnen
   I18N-/L10N-Bemu:hungen fu:r jedes Land wiederholt wurden. Viele von uns
   haben somit unproduktiverweise das Rad immer wieder neu erfunden. Wir
   hoffen, dass die verschiedenen grossen Gruppen fu:r I18N Ihre Bemu:hungen
   in einer Gruppe vereinen ko:nnen, a:hnlich der Zusta:ndigkeit des
   Core-Teams.

   Derzeit hoffen wir, dass wenn Sie I18N-konforme Programme schreiben oder
   portieren, diese an die betreffenden FreeBSD-Mailinglisten jedes Landes
   schicken, um sie testen zu lassen. Wir hoffen in Zukunft, Anwendungen zu
   entwickeln, die in allen Sprachen direkt und ohne unsaubere A:nderungen
   funktionieren.

   Die FreeBSD internationalization-Mailingliste ist eingerichtet worden.
   Wenn Sie I18N-/L10N-Entwickler sind, schicken Sie bitte Ihre Kommentare,
   Ideen, Fragen und alles, das Sie mit dem Thema in Verbindung bringen,
   dorthin.

  4.1.2. Perl und Python

   Perl und Python bieten Bibliotheken fu:r I18N und zur Behandlung von
   Unicode-Zeichen. Bitte nutzen Sie diese fu:r I18N-Konformita:t.

4.2. Lokalisierte Nachrichten mit POSIX.1 Native Language Support (NLS)

   Beigetragen von Gabor Ko:vesdan.

   U:ber die Basisfunktionen von I18N hinaus, wie das Bereitstellen von
   verschiedenen Eingabecodierungen oder die diversen nationalen
   Konventionen, zum Beispiel die verschiedenen Dezimalpunkte, ist es auf
   einem ho:heren Level von I18N mo:glich, die Ausgabe von Programmen zu
   lokalisieren. Ein Weg dies zu tun besteht in der Nutzung der POSIX.1
   NLS-Funktionen von FreeBSD.

  4.2.1. Organisation von lokalisierten Mitteilungen in Katalog Dateien

   POSIX.1 NLS basiert auf Katalogdateien, welche die lokalisierten
   Mitteilungen in der entsprechenden Codierung enthalten. Die Mitteilungen
   sind in Sets organisiert und jede Mitteilung ist durch eine eindeutige
   Zahl in dem jeweiligen Set identifiziert. Die Katalogdateien werden nach
   der Lokale, von den jeweiligen lokalisierten Mitteilungen, die sie
   enthalten, gefolgt von der .msg Endung benannt. Zum Beispiel werden die
   ungarischen Mitteilungen fu:r das ISO8859-2 Encoding in einer Datei mit
   dem Dateinamen hu_HU.ISO8859-2 gespeichert.

   Diese Katalogdateien sind normale Textdateien, welche die nummerierten
   Mitteilungen enthalten. Es ist mo:glich Kommentare in die Dateien zu
   schreiben, indem Sie ein $-Zeichen an den Anfang der Zeile setzen. Das
   Setzen von Grenzen wird ebenfalls durch spezielle Kommentare mo:glich
   wobei das Schlu:sselwort set direkt nach dem $-Zeichen folgen muss. Dem
   Schlu:sselwort set folgt dann die Set-Nummer. Ein Beispiel:

 $set 1

   Der aktuelle Mitteilungseintrag startet mit der Mitteilungsnummer gefolgt
   von der lokalisierten Nachricht. Die bekannten Modifikatoren von printf(3)
   werden akzeptiert:

 15 "File not found: %s\n"

   Die Katalogdateien mu:ssen in bina:rer Form vorliegen, bevor sie von einem
   Programm benutzt werden ko:nnen. Dies wird mit dem gencat(1) Tool
   durchgefu:hrt. Das erste Argument ist der Dateiname des kompilierten
   Katalogs und die weiteren Argumente sind die Eingabekataloge. Die
   lokalisierten Mitteilungen ko:nnen auf mehrere Katalogdateien aufgeteilt
   sein. Danach werden dann alle auf einmal mit dem gencat(1) Tool
   kompiliert.

  4.2.2. Nutzung der Katalogdateien im Quellcode

   Das Benutzen der Katalogdateien ist einfach. Um die relevante Funktion zu
   nutzen, muss nl_types.h in die Quelldatei eingefu:gt werden. Bevor ein
   Katalog benutzt werden kann, muss er mit catopen(3) geo:ffnet werden. Die
   Funktion hat 2 Argumente. Der erste Parameter ist der Name des
   installierten und kompilierten Katalogs. Normalerweise wird der Name des
   Programmes, zum Beispiel grep, genutzt. Dieser Name wird zum Suchen der
   kompilierten Katalogdatei benutzt. Der Aufruf von catopen(3) sucht nach
   dieser Datei in /usr/share/nls/locale/catname und in
   /usr/local/share/nls/locale/catname, wobei locale die gesetzte Lokale und
   catname der Katalogname ist. Der zweite Parameter ist eine Konstante, die
   zwei Werte haben kann:

     * NL_CAT_LOCALE, hat die Bedeutung, dass die benutzte Katalogdatei auf
       LC_MESSAGES basiert.

     * 0, hat die Bedeutung, dass LANG benutzt wird, um die Katalogdatei zu
       o:ffnen.

   Der catopen(3) Aufruf gibt einen Katalogidentifizierer vom Type nl_catd
   zuru:ck. Sehen Sie in der Manualpage nach, um eine Liste mit mo:glichen
   Fehlercodes zu erhalten.

   Nach dem O:ffnen eines Katalogs, kann catgets(3) benutzt werden, um
   Mitteilungen zu erhalten. Der erste Parameter ist der
   Katalogidentifizierer, der von catopen(3) zuru:ck gegeben wurde, das
   zweite ist die Nummer des Sets, das dritte die Nummer der Mitteilung und
   das vierte ist eine Fallbackmitteilung, die angezeigt wird, falls die
   gewu:nschte Mitteilung in der Katalogdatei nicht verfu:gbar ist.

   Nach der Nutzung der Katalogdatei, muss sie mit dem Kommando catclose(3),
   geschlossen werden. Es besitzt ein Argument, die Katalog ID.

  4.2.3. Ein Beispiel aus der Praxis

   Das folgende Beispiel zeigt einen einfachen Weg wie man NLS-Kataloge
   flexibel nutzen kann.

   Die nachfolgenden Zeilen mu:ssen in eine allgemeine Headerdatei, die in
   allen Quelldateien vorhanden ist, die lokalisierte Mitteilungen benutzen,
   eingefu:gt werden:

 #ifdef WITHOUT_NLS
 #define getstr(n)         nlsstr[n]
 #else
 #include <nl_types.h>

 extern nl_catd            catalog;
 #define getstr(n)         catgets(catalog, 1, n, nlsstr[n])
 #endif

 extern char              *nlsstr[];
        

   Als na:chstes fu:gen Sie die folgenden Zeilen in den globalen
   Deklarationsteil der Hauptquelldatei ein:

 #ifndef WITHOUT_NLS
 #include <nl_types.h>
 nl_catd   catalog;
 #endif

 /*
 * Default messages to use when NLS is disabled or no catalog
 * is found.
 */
 char    *nlsstr[] = {
         "",
 /* 1*/  "some random message",
 /* 2*/  "some other message"
 };
        

   Als na:chstes kommt der Code der den Katalog o:ffnet, liest und schliesst:

 #ifndef WITHOUT_NLS
  catalog = catopen("myapp", NL_CAT_LOCALE);
 #endif

 ...

 printf(getstr(1));

 ...

 #ifndef WITHOUT_NLS
  catclose(catalog);
 #endif
        

    4.2.3.1. Reduzierung von zu lokalisierenden Zeichenketten

   Es gibt einen guten Weg, Zeichenketten die lokaliesert werden mu:ssen,
   durch den Einsatz von libc-Fehlermeldungen zu reduzieren. Dadurch
   vermeidet man Duplikate und erstellt gleiche Meldungen fu:r ha:ufige
   Fehlermeldungen, die bei vielen Programmen auftreten ko:nnen.

   Als erstes ist hier ein Beispiel, dass keine libc-Fehlermeldungen benutzt:

 #include <err.h>
 ...
 if (!S_ISDIR(st.st_mode))
  err(1, "argument is not a directory");
          

   Dies kann so abgea:ndert werden, dass eine Fehlermeldung durch Auslesen
   der Variabel errno ausgegeben wird. Die Fehlermeldung wird entsprechend
   dem Beispiel ausgegeben:

 #include <err.h>
 #include <errno.h>
 ...
 if (!S_ISDIR(st.st_mode)) {
  errno = ENOTDIR;
  err(1, NULL);
 }
          

   In diesem Beispiel wurde die benutzerdefinierte Zeichenkette entfernt.
   U:bersetzer haben weniger Arbeit, wenn sie ein Programm lokalisieren und
   die Benutzer sehen die u:bliche ""Not a directory"" Fehlermeldung, wenn
   dieser Fehler auftritt. Diese Meldung wird ihnen wahrscheinlich vertraut
   erscheinen. Bitte beachten Sie, dass es notwendig ist, errno.h
   hinzuzufu:gen um einen direkten Zugriff auf errno zu haben.

   Es lohnt sich darauf hinzuweisen, dass es Fa:lle gibt, in denen errno
   automatisch aufgerufen wird, so dass es nicht notwendig ist, es explizit
   zu tun:

 #include <err.h>
 ...
 if ((p = malloc(size)) == NULL)
  err(1, NULL);
          

  4.2.4. Benutzung von bsd.nls.mk

   Das Benutzen von Katalogdateien setzt einige sich wiederholende Schritte,
   wie das kompilieren und installieren der Kataloge, voraus. Um diese
   Schritte zu vereinfachen, stellt bsd.nls.mk einige Makros zur Verfu:gung.
   Es ist nicht notwendig bsd.nls.mk explizit hinein zu kopieren, es wird
   automatisch aus den allgemeinen Makefiles wie bsd.prog.mk oder bsd.lib.mk
   gezogen.

   Normalerweise reicht es, NLSNAME zu definieren, die den Namen des
   Kataloges als erstes Argument von catopen(3) enthalten sollte und die
   Katalogdateien in NLS ohne ihre Endung .msg auflistet. Hier ist ein
   Beispiel, das es ermo:glicht, NLS mit dem obigen Code zu deaktivieren. Die
   WITHOUT_NLS Variable von make(1) muss so definiert werden, dass das
   Programm ohne NLS-Unterstu:tzung gebaut wird.

 .if !defined(WITHOUT_NLS)
 NLS=     es_ES.ISO8859-1
 NLS+=    hu_HU.ISO8859-2
 NLS+=    pt_BR.ISO8859-1
 .else
 CFLAGS+= -DWITHOUT_NLS
 .endif
        

   Normalerweise werden die Katalogdateien in dem nls-Unterverzeichnis
   abgelegt. Dies ist der Standard von bsd.nls.mk. Es ist mo:glich, mit der
   NLSSRCDIR-Variablen von make(1) diese zu u:berschreiben. Der Standardname
   der vorkompilierten Katalogdateien folgt den Namenskonventionen, wie oben
   beschrieben. Er kann durch die NLSNAME-Variablen u:berschrieben werden. Es
   gibt noch weitere Optionen, um eine Feinabstimmung zur Verarbeitung der
   Katalogdateien zu erreichen. Da sie nicht notwendig sind, werden sie hier
   nicht weiter beschrieben. Weitere Informationen u:ber bsd.nls.mk finden
   Sie in der Datei selbst. Der Text ist kurz und leicht zu verstehen.

Kapitel 5. Vorgaben und Richtlinien fu:r das Quelltextverzeichnis

   Beigesteuert von Poul-Henning Kamp und Giorgos Keramidas.
   U:bersetzt von Axel Gruner.
   Inhaltsverzeichnis

   5.1. Stil-Richtlinien

   5.2. MAINTAINER eines Makefiles

   5.3. Beigesteuerte Software

   5.4. Belastende Dateien

   5.5. Shared-Libraries

   Dieses Kapitel dokumentiert verschiedene Vorgaben und Richtlinien fu:r das
   FreeBSD-Quelltextverzeichnis.

5.1. Stil-Richtlinien

   Ein konsistenter Code-Stil ist extrem wichtig, besonders in einem so
   grossen Projekt wie FreeBSD. Der Code sollte dem FreeBSD Code-Stil
   entsprechen, welcher in style(9) und style.Makefile(5) genauer beschrieben
   ist.

5.2. MAINTAINER eines Makefiles

   Wenn ein bestimmter Bereich der FreeBSD src/-Distribution von einer Person
   oder Gruppe gepflegt wird, kann dies durch einen Eintrag in die Datei
   src/MAINTAINERS der O:ffentlichkeit mitgeteilt werden. Maintainer eines
   Ports in der Ports-Sammlung ko:nnen ihre Verantwortung u:ber den Port
   durch einen Eintrag in die MAINTAINER-Zeile im Makefile des Ports der Welt
   mitteilen.

 MAINTAINER= email-addresses

  Tipp:

   Fu:r andere Teile des Repositories oder andere Abschnitte, die noch keinen
   Maintainer aufweisen, oder falls Sie sich nicht sicher sind, wer der
   Maintainer ist, sehen Sie sich die Commit-Historie des betreffenden Ports
   an. Es ist recht ha:ufig der Fall, dass ein Maintainer nicht explizit
   aufgefu:hrt ist, aber trotzdem diejenigen Personen, die den Port seit den
   letzten paar Jahren aktiv betreuen, daran interessiert sind, A:nderungen
   zu begutachten. Selbst wenn dies nicht explizit in der Dokumentation oder
   im Quellcode erwa:hnt ist, wird es trotzdem als ho:fliche Geste angesehen,
   wenn man nach einer U:berpru:fung der eigenen A:nderungen fragt.

   Die Rolle eines Maintainers ist die folgende:

     * Der Maintainer ist verantwortlich fu:r diesen Code. Er oder sie muss
       einerseits fu:r die Behebung von Fehlern und das Beantworten von
       Problemberichten fu:r diesen Code die Verantwortung tragen und
       andererseits, falls es sich um beigesteuerte Software handelt, neue
       Versionen verfolgen und bereitstellen.

     * A:nderungen an Verzeichnissen, die ein Maintainer definiert hat,
       sollten an den Maintainer fu:r eine U:berpru:fung gesendet werden,
       bevor diese committet werden. Nur wenn der Maintainer in einer
       inakzeptablen Zeitspanne auf mehrere E-Mails nicht antwortet, ko:nnen
       die A:nderungen, die mit dem Commit in Kraft treten, auch ohne
       U:berpru:fung durch den Maintainer vollzogen werden. Dennoch wird
       empfohlen, dass die A:nderungen, falls mo:glich, von jemand anderem
       u:berpru:ft werden.

     * Es ist natu:rlich nicht akzeptabel, einer Person oder Gruppe den
       Status eines Maintainers zu geben, so lange sie nicht zustimmt, diese
       Pflicht auf sich zu nehmen. Andererseits muss es kein einzelner Mensch
       sein. Eine Gruppe von Menschen ist genauso in Ordnung.

5.3. Beigesteuerte Software

   Beigesteuert von Poul-Henning Kamp, David O'Brien und Gavin Atkinson.

   Einige Teile der FreeBSD-Distribution enthalten Software, die aktiv
   ausserhalb des FreeBSD-Projektes gepflegt wird. Aus historischen Gru:nden
   nennen wir dies contributed Software. Beispiele dafu:r sind sendmail, gcc
   und patch.

   U:ber die Jahre wurden verschiedene Methoden genutzt, um solche Software
   zu verwalten, und jede hat Vor- wie auch Nachteile. So hat sich kein
   eindeutiger Gewinner herauskristallisiert.

   Es wurde viel u:ber diesen Umstand diskutiert und eine Methode als die
   "offizielle" vorgestellt, um in Zukunft diese Art der Software zu
   importieren. Ferner wird dringend geraten, dass sich existierende,
   beigesteuerte Software diesem Modell anna:hert, da es signifikante
   Vorteile gegenu:ber der alten Methode gibt. Dazu geho:rt auch, dass jeder
   einfach Diffs bezu:glich der "offiziellen" Quelltext-Versionen erzeugen
   kann (auch ohne direkten Repository-Zugang). Dies wird es deutlich
   vereinfachen, A:nderungen an die Hauptentwickler zuru:ckfliessen zu
   lassen.

   Letztendlich kommt es jedoch auf die Menschen an, welche die Arbeit
   leisten. Wenn die Durchfu:hrung dieses Modells bei einem Paket mal nicht
   mo:glich ist, ko:nnen Ausnahmen dieser Regeln nur mit Genehmigung des
   Core-Teams und der U:bereinstimmung der anderen Entwickler gewa:hrt
   werden. Die Fa:higkeit, dieses Paket auch in Zukunft pflegen zu ko:nnen,
   ist eine der Schlu:sselfragen bei dieser Entscheidung.

  Anmerkung:

   Durch einige bedauernswerte Einschra:nkungen des RCS-Dateiformats und die
   Handhabung von Herstellerzweigen ist von unwesentlichen, trivialen
   und/oder kosmetischen A:nderungen an Dateien dringend abzuraten, die dem
   Herstellerzweig folgen. "Grammatikalische oder sprachliche
   Fehlerbehebungen" sind explizit unter der "Kosmetik"-Kategorie einzuordnen
   und sollten vermieden werden. Das Repository kann sich durch A:nderungen
   einzelner Zeichen dramatisch aufbla:hen.

  5.3.1. Herstellerimports mit CVS

   Das file-Werkzeug soll als Beispiel dienen, wie dieses Modell
   funktioniert:

   src/contrib/file entha:lt den Quelltext so, wie vom Maintainer dieses
   Pakets bereitgestellt. Teile, die unter FreeBSD ga:nzlich unnutzbar sind,
   ko:nnen entfernt werden. Im Fall von file(1) wurde u.a. das
   Unterverzeichnis python und Dateien mit dem Pra:fix lt vor dem Import
   entfernt.

   src/lib/libmagic entha:lt ein Makefile im bmake-Stil, das die Regeln des
   Standard-Makefiles bsd.lib.mk nutzt, um die Bibliothek zu erstellen und
   die Dokumentation zu installieren.

   src/usr.bin/file entha:lt ein Makefile im bmake-Stil, welches das
   file-Programm erstellt und installiert, ebenso die dazugeho:rigen
   Manualpages, welche die Regeln von bsd.prog.mk nutzen.

   Das Entscheidende ist hier das src/contrib/file-Verzeichnis, welches nach
   den folgenden Regeln erstellt wird: Es muss den Quelltext aus dem Original
   enthalten (ohne RCS-Schlu:sselworte und im korrekten Herstellerzweig) mit
   so wenig FreeBSD-spezifischen A:nderungen wie mo:glich. Sollte es Zweifel
   geben, wie hier zu verfahren ist, unbedingt zuerst nachfragen und nicht
   auf gut Glu:ck etwas probieren in der vagen Hoffnung, dass es "irgendwie
   funktioniert".

   Aufgrund der eingangs schon erwa:hnten Einschra:nkungen bei
   Herstellerzweigen ist es erforderlich, dass "offizielle" Fehlerbehebungen
   vom Hersteller in die Originalquellen der Distribution einfliessen und als
   Resultat wieder in den Herstellerzweig importiert werden. Offizielle
   Fehlerbehebungen sollten nie direkt in FreeBSD eingepflegt und "committet"
   werden, da dies den Herstellerzweig zersto:ren wu:rde und der Import von
   zuku:nftigen Versionen wa:re um ein Vielfaches schwerer, da es zu
   Konflikten kommen wu:rde.

   Da einige Pakete Dateien enthalten, die zur Kompatibilita:t mit anderen
   Architekturen und Umgebungen als FreeBSD gedacht sind, ist es zula:ssig,
   diese Teile zu lo:schen, wenn sie fu:r FreeBSD nicht von Interesse sind,
   und so Speicherplatz zu sparen. Dateien, die ein Copyright und
   Release-artige Informationen zu den vorhandenen Dateien enthalten, sollten
   nicht gelo:scht werden.

   Falls es einfacher erscheint, ko:nnen die bmake-Makefiles vom
   Verzeichnisbaum durch einige Dienstprogramme automatisch erstellt werden,
   was es hoffentlich sogar noch einfacher macht, eine Version zu
   aktualisieren. Ist dies geschehen, so stellen Sie bitte sicher, diese
   Werkzeuge in das Verzeichnis src/tools gleich mit dem Port an sich
   einzuchecken, sodass es fu:r zuku:nftige Maintainer verfu:gbar ist.

   Im Verzeichnis src/contrib/file sollte eine Datei mit dem Namen
   FREEBSD-upgrade hinzugefu:gt werden und sie sollte den Stand wie folgt
   anzeigen:

     * Welche Dateien ausgelassen wurden.

     * Von wo die Original-Distribution stammt und/oder wo die offizielle
       Hauptseite zu finden ist.

     * Wohin Fehlerbehebungen an den Originalautor gesendet werden ko:nnen.

     * Mo:glicherweise eine U:bersicht, welche FreeBSD-spezifischen
       A:nderungen vorgenommen wurden.

   Ein Beispielinhalt von src/contrib/groff/FREEBSD-upgrade ist hier
   aufgelistet:

 $FreeBSD: src/contrib/groff/FREEBSD-upgrade,v 1.5.12.1 2005/11/15 22:06:18 ru Exp $

 This directory contains virgin sources of the original distribution files
 on a "vendor" branch.  Do not, under any circumstances, attempt to upgrade
 the files in this directory via patches and a cvs commit.

 To upgrade to a newer version of groff, when it is available:
         1. Unpack the new version into an empty directory.
            [Do not make ANY changes to the files.]

         2. Use the command:
                 cvs import -m 'Virgin import of FSF groff v<version>' \
                         src/contrib/groff FSF v<version>

            For example, to do the import of version 1.19.2, I typed:
                 cvs import -m 'Virgin import of FSF groff v1.19.2' \
                         src/contrib/groff FSF v1_19_2

         3. Follow the instructions printed out in step 2 to resolve any
            conflicts between local FreeBSD changes and the newer version.

 Do not, under any circumstances, deviate from this procedure.

 To make local changes to groff, simply patch and commit to the main
 branch (aka HEAD).  Never make local changes on the FSF branch.

 All local changes should be submitted to Werner Lemberg <wl@gnu.org> or
 Ted Harding <ted.harding@nessie.mcc.ac.uk> for inclusion in the next
 vendor release.

 ru@FreeBSD.org - 20 October 2005

   Eine weitere Mo:glichkeit ist es, eine Liste von Dateien, die nicht
   enthalten sein sollen zu pflegen, was besonders dann sehr hilfreich sein
   kann, wenn die Liste ziemlich gross oder kompliziert ist bzw. Imports sehr
   ha:ufig stattfinden. Durch erstellen einer Datei namens FREEBSD-Xlist im
   gleichen Verzeichnis, in welches das Herstellerverzeichnis importiert
   werden soll, die eine Liste von auszuschliessenden Dateinamen-Mustern pro
   Zeile entha:lt, ko:nnen zuku:nftige Imports folgendermassen durchgefu:hrt
   werden:

 % tar -X FREEBSD-Xlist -xzf vendor-source.tgz

   Als Beispiel einer FREEBSD-Xlist-Datei wird hier diejenige von
   src/contrib/tcsh gezeigt:

 */BUGS
 */config/a*
 */config/bs2000
 */config/bsd
 */config/bsdreno
 */config/[c-z]*
 */tests
 */win32

  Anmerkung:

   Bitte importieren Sie weder FREEBSD-upgrade noch FREEBSD-Xlist mit den
   beigesteuerten Quellen. Stattdessen sollten Sie diese Dateien nach dem
   initialen Import hinzufu:gen.

  5.3.2. Herstellerimports mit SVN

   Beigetragen von Dag-Erling Smo/rgrav.

   Dieser Abschnitt beschreibt die Prozedur fu:r Herstellerimports mit
   Subversion im Detail.

    1. Vorbereiten des Quellbaums

       Wenn dies Ihr erster Import nach dem Wechsel zu SVN ist, sollen Sie
       den Herstellerbaum aufra:umen, verflachen und die Merge-Historie in
       den Hauptzweig vorbereiten. Falls das nicht Ihr erster Import ist,
       ko:nnen Sie diesen Schritt ohne Probleme u:berspringen.

       Wa:hrend der Konvertierung von CVS zu SVN wurden Herstellerzweige mit
       der gleichen Struktur wie der Hauptzweig importiert. Beispielsweise
       wurden die foo Herstellerquellen in vendor/foo/dist/contrib/foo
       abgelegt, jedoch ist dies unpraktisch und zwecklos. Was wir wirklich
       wollen, ist dass die Herstellerquellen direkt in vendor/foo/dist
       liegen, beispielsweise so:

 % cd vendor/foo/dist/contrib/foo
 % svn move $(svn list) ../..
 % cd ../..
 % svn remove contrib
 % svn propdel -R svn:mergeinfo
 % svn commit

       Beachten Sie, dass das propdel-Bit notwendig ist, da mit Subversion
       1.5 automatisch svn:mergeinfo zu jedem Verzeichnis hinzugefu:gt wird,
       das Sie kopieren oder verschieben. In diesem Fall brauchen Sie diese
       Informationen nicht, da Sie nichts in den Zweig mergen werden, den Sie
       gelo:scht haben.

  Anmerkung:

       Sie werden wahrscheinlich die Tags genauso verflachen wollen. Die
       Prozedur dafu:r ist die selbe. Wenn Sie dies tun, sollten Sie den
       Commit bis zum Schluss aufschieben.

       Pru:fen Sie den dist-Baum und fu:hren Sie alle no:tigen
       Aufra:umarbeiten durch, die Sie fu:r sinnvoll erachten. Sie werden
       mo:glicherweise die Erweiterung von Schlu:sselwo:rtern deaktivieren
       wollen, da dies auf unmodifizierten Quellen keinen Sinn ergibt. In
       machen Fa:llen kann dies sogar scha:dlich sein.

 % svn propdel svn:keywords -R .
 % svn commit

       Bootstrappen der svn:mergeinfo auf dem Zielverzeichnis (des
       Hauptzweiges) auf die Revision die mit der letzten A:nderung, die im
       Herstellerzweig vor dem Import der neuen Quellen durchgefu:hrt wurde,
       korrespondiert, wird ebenso beno:tigt:

 % cd head/contrib/foo
 % svn merge --record-only svn_base/vendor/foo/dist@12345678 .
 % svn commit

       Dabei entspricht svn_base dem Basisverzeichnis Ihres SVN-Repositories,
       z.B. svn+ssh://svn.FreeBSD.org/base.

    2. Neue Quellen importieren

       Bereiten Sie einen kompletten, sauberen Baum mit Herstellerquellen
       vor. Mit SVN ko:nnen wir eine komplette Distribution in dem
       Herstellerzweig aufbewahren, ohne den Hauptzweig aufzubla:hen.
       Importieren Sie alles, aber mergen Sie nur das, was wirklich beno:tigt
       wird.

       Beachten Sie, dass Sie alle Dateien, die seit dem letzten
       Herstellerimport hinzugefu:gt wurden, auch einbeziehen und diejenigen,
       welche entfernt wurden, auch lo:schen mu:ssen. Um dies zu
       bewerkstelligen, sollten Sie sortierte Listen der Bestandteile des
       Herstellerbaums und von den Quellen, Sie die vorhaben zu importieren,
       vorbereiten:

 % cd vendor/foo/dist
 % svn list -R | grep -v '/$' | sort > ../old
 % cd ../foo-9.9
 % find . -type f | cut -c 3- | sort > ../new

       Mit diesen beiden Dateien, wird Ihnen das folgende Kommando alle
       Dateien auflisten, die entfernt wurden (nur die Dateien in old):

 % comm -23 ../old ../new

       Der folgende Befehl wird die hinzugefu:gten Dateien auflisten (nur
       diejenigen Dateien in new):

 % comm -13 ../old ../new

       Wir fu:hren dies nun zusammen:

 % cd vendor/foo/foo-9.9
 % tar cf - . | tar xf - -C ../dist
 % cd ../dist
 % comm -23 ../old ../new | xargs svn remove
 % comm -13 ../old ../new | xargs svn add

  Warnung:

       Wenn in der neuen Version neue Verzeichnisse hinzugekommen sind, wird
       dieser letzte Befehl fehlschlagen. Sie mu:ssen diese Verzeichnisse
       hinzufu:gen und anschliessend den Befehl erneut ausfu:hren. Genauso
       mu:ssen Sie Verzeichnisse, die entfernt wurden, ha:ndisch lo:schen.

       Pru:fen Sie die Eigenschaften jeder neuen Datei:

          * Alle Textdateien sollten svn:eol-style auf den Wert native
            gesetzt haben.

          * Alle Bina:rdateien sollten svn:mime-type auf
            application/octet-stream gesetzt haben, ausser es existiert ein
            passenderer Medientyp.

          * Ausfu:hrbare Dateien sollten svn:executable auf * gesetzt haben.

          * Es sollten keine anderen Eigenschaften auf den Dateien im Baum
            gesetzt sein.

  Anmerkung:

       Sie sind bereit, zu committen, jedoch sollten Sie zuerst die Ausgabe
       von svn stat und svn diff u:berpru:fen, um sicher zu gehen, dass alles
       in Ordnung ist.

       Sobald Sie den die neue Release-Version des Herstellers committed
       haben, sollten Sie Ihn fu:r zuku:nftige Referenzen taggen. Die beste
       und schnellste Methode ist, dies direkt im Repository zu tun:

 % svn copy svn_base/vendor/foo/dist svn_base/vendor/foo/9.9

       Um den neuen Tag zu bekommen, brauchen Sie nur ihre Arbeitskopie von
       vendor/foo zu aktualisieren.

  Anmerkung:

       Wenn Sie lieber die Kopie in der ausgecheckten Kopie durchfu:hren
       wollen, vergessen Sie nicht, die generierte svn:mergeinfo wie oben
       beschrieben zu entfernen.

    3. Mit -HEAD mergen

       Nachdem Sie Ihren Import vorbereitet haben, wird es Zeit zu mergen.
       Die Option --accept=postpone weist SVN an, noch keine merge-Konflikte
       aufzulo:sen, weil wir uns um diese manuell ku:mmern werden:

 % cd head/contrib/foo
 % svn update
 % svn merge --accept=postpone svn_base/vendor/foo/dist

       Lo:sen Sie die Konflikte und stellen Sie sicher, dass alle Dateien,
       die im Herstellerzweig hinzugefu:gt oder entfernt wurden, auch sauber
       im Hauptzweig hinzugefu:gt bzw. gelo:scht wurden. Es ist immer ratsam,
       diese Unterschiede gegen den Herstellerbaum zu pru:fen:

 % svn diff --no-diff-deleted --old=svn_base/vendor/foo/dist --new=.

       Die Option --no-diff-deleted weist SVN an, keine Dateien zu pru:fen,
       die sich zwar im Herstellerbaum, aber nicht im Hauptzweig befinden.

  Anmerkung:

       Bei SVN gibt es das Konzept von innerhalb und ausserhalb des
       Herstellerbaums nicht. Wenn eine Datei, die zuvor eine lokale
       A:nderung hatte, aber nun keine mehr besitzt, entfernen Sie einfach
       das was u:brig ist, wie FreeBSD Versionstags, damit diese nicht
       la:nger in den diffs gegen den Herstellerbaum erscheinen.

       Wenn irgendwelche A:nderungen notwendig sind, um die Welt mit den
       neuen Quellen zu bauen, machen Sie diese jetzt und testen Sie diese
       bis Sie sicher sind, dass alles korrekt gebaut wird und richtig
       funktionert.

    4. Commit

       Nun sind Sie bereit fu:r den Commit. Stellen Sie sicher, dass Sie
       alles in einem einzigen Schritt durchfu:hren. Idealerweise sollten Sie
       alle diese Schritte in einem sauberen Baum durchgefu:hrt haben. Falls
       dies der Fall ist, ko:nnen Sie einfach aus dem obersten Verzeichnis
       dieses Baums committen. Dies ist der beste Weg, um U:berraschungen zu
       vermeiden. Wenn Sie dies korrekt durchfu:hren, wird der Baum atomar
       von einem konsistenten Zustand mit dem alten Code in einen neuen
       konsistenten Zustand mit dem neuen Code u:berfu:hrt.

5.4. Belastende Dateien

   Es kann gelegentlich notwendig sein, belastende Dateien in den
   FreeBSD-Quelltextbaum zu integrieren. Braucht ein Gera:t zum Beispiel ein
   Stu:ck bina:ren Code, der zuerst geladen werden muss, bevor das Gera:t
   funktioniert, und wir haben keine Quellen zu diesem Code, dann wird die
   bina:re Datei als belastend bezeichnet. Die folgenden Richtlinien sind
   beim Aufnehmen von belastenden Dateien in den FreeBSD-Quelltextbaum zu
   beachten.

    1. Jede Datei, die durch die System-CPU(s) ausgefu:hrt wird und nicht als
       Quelltext vorliegt, ist belastend.

    2. Jede Datei, deren Lizenz restriktiver ist als die BSD- oder
       GNU-Lizenz, ist belastend.

    3. Eine Datei, die herunterladbare Bina:r-Daten entha:lt, ist nur
       belastend, wenn (1) oder (2) zutreffen. Sie muss in einem ASCII-Format
       gespeichert werden, das Architektur-neutral ist (file2c oder
       uuencoding wird empfohlen).

    4. Jede belastende Datei braucht eine spezielle Genehmigung vom
       Core-Team, bevor diese in das Repository aufgenommen werden darf.

    5. Belastende Dateien liegen unter src/contrib oder src/sys/contrib.

    6. Das komplette Modul sollte auch am Stu:ck aufbewahrt werden. Es gibt
       keinen Grund, dieses zu teilen, ausser es gibt einen Code-Austausch
       mit Quelltext, der nicht belastend ist.

    7. Objekt-Dateien werden wie folgt benannt: arch/filename.o.uu>.

    8. Kernel-Dateien:

         a. Sollten immer nach conf/files.* verweisen (um den Bau einfach zu
            halten).

         b. Sollten sich immer in LINT befinden, jedoch entscheidet das
            Core-Team je nach Fall, ob es auskommentiert wird oder nicht. Das
            Core-Team kann sich zu einem spa:teren Zeitpunkt immer noch
            anders entscheiden.

         c. Der Release-Engineer entscheidet, ob es in ein Release
            aufgenommen wird oder nicht.

    9. Userland-Dateien:

         a. Das Core-Team entscheidet, ob der Code von make world gebaut wird
            oder nicht.

         b. Der Release-Engineer entscheidet, ob es in das Release
            aufgenommen wird oder nicht.

5.5. Shared-Libraries

   Beigesteuert von Satoshi Asami, Peter Wemm und David O'Brien.

   Sollten Sie die Unterstu:tzung fu:r Shared-Libraries bei einem Port oder
   einem Stu:ck Software, das dies nicht hat, hinzufu:gen, sollten die
   Versionsnummern dessen Regeln folgen. Im Allgemeinen hat die sich daraus
   resultierende Nummer nichts mit der Release-Version der Software zu tun.

   Die drei Grundsa:tze zum Erstellen von Shared-Libraries sind:

     * Sie beginnen mit 1.0.

     * Gibt es eine A:nderung, die abwa:rtskompatibel ist, so springen Sie
       zur na:chsten Minor-Version (beachten Sie, dass ELF-Systeme die
       Minor-Version ignorieren).

     * Gibt es eine inkompatible A:nderung, so springen Sie bitte zur
       na:chsten Major-Version.

   Zum Beispiel wird beim Hinzufu:gen von Funktionen und oder
   Fehlerbehebungen zur na:chsten Minor-Version gesprungen, beim Lo:schen
   einer Funktion, A:ndern von Funktionsaufrufen usw. a:ndert sich die
   Major-Version.

   Bleiben Sie bei Versionsnummern in der Form major.minor (x.y). Unser
   dynamischer Linker a.out kann mit Versionsnummern in der Form x.y.z nicht
   gut umgehen. Jede Versionsnummer nach dem y (die dritte Zahl) wird vo:llig
   ignoriert, wenn Versionsnummern der Shared-Libraries verglichen werden, um
   zu bestimmen, mit welcher Bibliothek eine Anwendung verlinkt wird. Sind
   zwei Shared-Libraries vorhanden, die sich nur in der "micro"-Revision
   unterscheiden, so wird ld.so zu der ho:heren verlinken. Dies bedeutet,
   dass wenn Sie mit libfoo.so.3.3.3 verlinken, der Linker nur 3.3 in den
   Header aufnimmt und alles linkt, was mit libfoo.so.3 .(irgendetwas >=
   3).(ho:chste verfu:gbare Nummer) beginnt.

  Anmerkung:

   ld.so wird immer die ho:chste "Minor"-Revision benutzen. Beispielsweise
   wird es die libc.so.2.2 bevorzugen gegenu:ber der libc.so.2.0, auch dann,
   wenn das Programm urspru:nglich mit libc.so.2.0 verlinkt war.

   Unser dynamischer ELF-Linker kann keine Minor-Versionen handhaben. Dennoch
   sollten die Major- und Minor-Versionen genutzt werden, da unsere Makefiles
   "das Richtige machen" bezogen auf den Systemtyp.

   Fu:r nicht-Port-Bibliotheken lautet die Richtlinie, die
   Shared-Library-Versionsnummer nur einmal zwischen den Releases zu a:ndern.
   Weiterhin ist es vorgeschrieben, die Major-Version der Shared-Libraries
   nur bei Major-OS-Releases zu a:ndern (beispielsweise von 6.0 auf 7.0).
   Wenn Sie also eine A:nderung an einer Systembibliothek vornehmen, die eine
   neue Versionsnummer beno:tigt, u:berpru:fen Sie die Commit-Logs des
   Makefiles. Es liegt in der Verantwortung des Committers, dass sich eine
   erste solche A:nderung seit dem letzten Release in der aktualisierten
   Versionsnummer der Shared-Library im Makefile a:ussert, folgende
   A:nderungen werden nicht beru:cksichtigt.

Kapitel 6. Regressions- und Performance-Tests

   U:bersetzt von Ju:rgen Lock.
   Inhaltsverzeichnis

   6.1. Mikro-Benchmark-Checkliste

   Regressions-Tests werden durchgefu:hrt, um zu u:berpru:fen, ob ein
   bestimmter Teil des Systems wie erwartet funktioniert, und um
   sicherzustellen, dass bereits beseitigte Fehler nicht wieder eingebaut
   werden.

   Die FreeBSD-Regressions-Testwerkzeuge finden Sie im FreeBSD-Quelltextbaum
   unter src/tools/regression.

6.1. Mikro-Benchmark-Checkliste

   Dieser Abschnitt entha:lt Tipps, wie ordnungsgema:sse Mikro-Benchmarks
   unter FreeBSD oder fu:r FreeBSD selbst erstellt werden.

   Es ist nicht mo:glich, immer alle der folgenden Vorschla:ge zu
   beru:cksichtigen, aber je mehr davon, desto besser wird der Benchmark
   kleine Unterschiede nachweisen ko:nnen.

     * Schalten Sie APM und alles andere, das den Systemtakt beeinflusst, ab
       (ACPI?).

     * Starten Sie in den Single-User-Modus. cron(8) und andere Systemdienste
       verursachen nur Sto:rungen. Genauso der sshd(8)-Systemdienst. Falls
       wa:hrend des Tests SSH-Zugriff beno:tigt wird, schalten Sie entweder
       die Neuerstellung des SSHv1-Schlu:ssels ab oder beenden Sie den
       sshd-Elternprozess wa:hrend der Tests.

     * Beenden Sie ntpd(8).

     * Falls syslog(3)-Ereignisse erzeugt werden, starten Sie syslogd(8) mit
       leerer /etc/syslogd.conf oder beenden Sie es.

     * Sorgen Sie fu:r mo:glichst wenig Disk-I/O; vermeiden Sie es ganz wenn
       mo:glich.

     * Ha:ngen Sie keine Dateisysteme ein, die Sie nicht beno:tigen.

     * Ha:ngen Sie /, /usr und die anderen Dateisysteme nur lesbar ein wenn
       mo:glich. Dies verhindert, dass atime-Aktualisierungen auf der
       Festplatte (usw.) das Ergebnis verfa:lschen.

     * Initialisieren Sie das beschreibbare Test-Dateisystem mit newfs(8) neu
       und fu:llen Sie es aus einer tar(1)- oder dump(8)-Datei vor jedem
       Lauf. Ha:ngen Sie es aus und wieder ein, bevor Sie den Test starten.
       Dies sorgt fu:r einen konsistenten Dateisystemaufbau. Bei einem
       "worldstone"-Test bezieht sich dies auf /usr/obj (Initialisieren Sie
       es einfach mit newfs neu und ha:ngen Sie es ein). Um absolut
       reproduzierbare Ergebnisse zu bekommen, fu:llen Sie das Dateisystem
       aus einer dd(1)-Datei (d.h. dd if=myimage of=/dev/ad0s1h bs=1m).

     * Benutzen Sie malloc-gestu:tzte oder vorbelastete md(4)-Partitionen.

     * Starten Sie zwischen den einzelnen Durchla:ufen neu, dies sichert
       einen konsistenteren Zustand.

     * Entfernen Sie alle nicht unbedingt beno:tigten Gera:tetreiber aus dem
       Kernel. Wenn z.B. USB fu:r den Test nicht beno:tigt wird, entfernen
       Sie es aus dem Kernel. Gera:tetreiber, die sich Hardware zuteilen,
       haben oft "tickende" Timeouts.

     * Konfigurieren Sie nicht Hardware, die nicht benutzt wird. Entfernen
       Sie Festplatten mit atacontrol(8) und camcontrol(8), wenn diese fu:r
       den Test nicht gebraucht werden.

     * Konfigurieren Sie nicht das Netzwerk, es sei denn es wird getestet,
       oder warten Sie, bis der Test fertig ist, wenn Sie das Ergebnis auf
       einen anderen Rechner u:bertragen wollen.

       Falls das System an ein o:ffentliches Netzwerk angeschlossen sein
       muss, achten Sie auf Spitzen im Broadcast-Verkehr. Obwohl dieser kaum
       auffa:llt, wird er CPU-Zyklen brauchen. A:hnliches gilt fu:r
       Multicast.

     * Legen Sie jedes Dateisystem auf eine eigene Festplatte. Dies minimiert
       Jitter durch Optimierungen von Lesekopfbewegungen.

     * Minimieren Sie Ausgaben auf serielle oder VGA-Konsolen.
       Ausgabenumleitung in Dateien ergibt weniger Jitter (serielle Konsolen
       werden leicht zum Flaschenhals). Benutzen Sie die Tastatur nicht,
       wa:hrend der Test la:uft, sogar space oder back-space wirken sich auf
       die Ergebnisse aus.

     * Stellen Sie sicher, dass der Test lang genug la:uft, aber nicht zu
       lange. Wenn er zu kurz ist, sind Zeitstempel ein Problem. Wenn er zu
       lang ist, werden Temperatura:nderungen und Drift die Frequenz von
       Quarzkristallen im Rechner beeinflussen. Daumenregel: mehr als eine
       Minute, weniger als eine Stunde.

     * Versuchen Sie, die Temperatur in der Umgebung des Rechners so stabil
       wie mo:glich zu halten. Diese beeinflusst sowohl Quarzkristalle als
       auch Festplatten-Algorithmen. Um einen wirklich stabilen Takt zu
       erhalten, wa:re es auch mo:glich, einen stabilisierten Takt
       anzuschliessen. D.h. besorgen Sie sich einen OCXO + PLL und koppeln
       Sie das Ausgangssignal mit den Taktgeberschaltkreisen anstelle des
       Quarzkristalls der Hauptplatine. Wenden Sie sich an Poul-Henning Kamp,
       wenn Sie mehr Informationen hieru:ber beno:tigen.

     * Lassen Sie den Test mindestens drei Mal laufen, besser mehr als 20
       Mal, sowohl fu:r "vor" als auch fu:r "nach" dem Code. Versuchen Sie
       abzuwechseln (d.h. nicht erst 20 Mal "vorher" und dann 20 Mal
       "nachher"), dies ermo:glicht, umgebungsbedingte Effekte zu erkennen.
       Wechseln Sie nicht 1:1 ab, sondern 3:3; dies erlaubt,
       Wechselwirkungseffekte zu erkennen.

       Ein gutes Muster ist: bababa{bbbaaa}*. Dies gibt Hinweise nach den
       ersten 1+1-La:ufen (sodass Sie den Test stoppen ko:nnen, falls er
       vo:llig daneben geht), Sie ko:nnen die Standardabweichung nach den
       ersten 3+3-La:ufen u:berpru:fen (zeigt an, ob sich ein la:ngerer Lauf
       lohnt), spa:ter Trends und Wechselwirkungen.

     * Benutzen Sie ministat(1), um festzustellen, ob Ihre Ergebnisse
       signifikant sind. U:berlegen Sie sich, das Buch "Cartoon guide to
       statistics" ISBN: 0062731025 zu kaufen. Es ist sehr empfehlenswert,
       falls Sie Dinge wie Standardabweichung und Studentsche t-Verteilung
       vergessen oder nie gelernt haben.

     * Benutzen Sie keinen Hintergrund-fsck(8), wenn Sie ihn nicht selbst
       testen wollen. Schalten Sie auch background_fsck in /etc/rc.conf aus,
       es sei denn der Benchmark wird nicht mindestens 60+"Laufzeit von fsck"
       Sekunden nach Systemstart gestartet, da rc(8) startet und pru:ft, ob
       fsck auf irgendeinem der Dateisysteme laufen muss, wenn
       Hintergrund-fsck eingeschaltet ist. Stellen Sie ebenfalls sicher, dass
       keine Snapshots herumliegen, falls der Benchmark nicht ein Test mit
       Snapshots ist.

     * Falls der Benchmark unerwartet schlechte Performance zeigt,
       u:berpru:fen Sie Dinge wie grosse Mengen Interrupts von unerwarteten
       Quellen. Es gibt Berichte, dass einige ACPI-Versionen sich "daneben
       benehmen" und ein U:bermass an Interrupts erzeugen. Um zu helfen,
       ungewo:hnliche Testergebnisse zu diagnostizieren, machen Sie ein paar
       Momentaufnahmen von vmstat -i und suchen Sie nach Ungewo:hnlichem.

     * Gehen Sie mit Parametern zur Optimierung von Kernel, Userland und
       Fehlersuche vorsichtig um. Es passiert schnell, irgendetwas
       durchrutschen zu lassen und dann spa:ter festzustellen, dass der Test
       nicht das gleiche verglichen hat.

     * Erstellen Sie nie Benchmarks unter Verwendung der Kernel-Optionen
       WITNESS und INVARIANTS, wenn der Test nicht diese Merkmale selbst
       untersuchen soll. WITNESS kann zu 400% und mehr Performance-Abnahme
       fu:hren. A:hnliches gilt fu:r Userland-malloc(3)-Parameter,
       Voreinstellungen hierbei unterscheiden sich bei -CURRENT von denen bei
       Production-Releases.

                      Teil II. Interprozess-Kommunikation

   Inhaltsverzeichnis

   7. Sockets

   8. IPv6 Internals

                8.1. IPv6/IPsec-Implementierung

Kapitel 7. Sockets

   Dieses Kapitel ist noch nicht u:bersetzt. Lesen Sie bitte das Original in
   englischer Sprache. Wenn Sie helfen wollen, dieses Kapitel zu u:bersetzen,
   senden Sie bitte eine E-Mail an die Mailingliste 'FreeBSD German
   Documentation Project' <de-bsd-translators@de.FreeBSD.org>.

Kapitel 8. IPv6 Internals

   Inhaltsverzeichnis

   8.1. IPv6/IPsec-Implementierung

8.1. IPv6/IPsec-Implementierung

   Contributed by Yoshinobu Inoue.
   U:bersetzt von Michelle Wechter und Ju:rgen Dankoweit.

   Dieser Abschnitt erkla:rt die von der IPv6- und IPsec-Implementierung
   abha:ngigen Internas. Die Funktionalita:ten wurden vom KAME-Projekt
   abgeleitet

  8.1.1. IPv6

    8.1.1.1. Konformita:t

   Die IPv6 abha:ngigen Funktionen richten sich nach, oder versuchen sich
   nach den neuesten IPv6-Spezifikationen zu richten. (Achtung: Dies ist
   keine vollsta:ndige Liste - es wa:re zu aufwa:ndig, diese zu pflegen...).

   Fu:r weitere Details beachten sie bitte die entsprechenden Kapitel, RFCs,
   manual pages, oder Kommentare in den Quelltexten.

   Konformita:tspru:fungen wurden basierend auf KAME-STABLE-Kit des
   TAHI-Projekts durchgefu:hrt. Die Ergebnisse ko:nnen unter
   http://www.tahi.org/report/KAME/ eingesehen werden. In der Vergangenheit
   begleiteten wir auch Tests mit unseren a:lteren "Snapshots" an der Univ.
   of New Hampshire IOL (http://www.iol.unh.edu/).

     * RFC1639: FTP Operation Over Big Address Records (FOOBAR)

          * RFC2428 wird gegenu:ber RFC1639 bevorzugt. FTP-Clients versuchen
            zuerst RFC2428, dann im Fehlerfall RFC1639.

     * RFC1886: DNS Extensions to support IPv6

     * RFC1933: Transition Mechanisms for IPv6 Hosts and Routers

          * IPv4 kompatible Adressen werden nicht unterstu:tzt.

          * Automatisches Tunneln (beschrieben in 4.3 dieses RFC) wird nicht
            unterstu:tzt.

          * Die gif(4)-Schnittstelle implementiert einen IPv[46]-over-IPv[46]
            Tunnel in einer allgemeinen Art und Weise und es umfasst
            "configured tunnel" wie in der Spezifikation beschrieben. Siehe
            auch 23.5.1.5 in diese Dokument fu:r weitere Details.

     * RFC1981: Path MTU Discovery for IPv6

     * RFC2080: RIPng for IPv6

          * usr.sbin/route6d unterstu:tzt dies.

     * RFC2292: Advanced Sockets API for IPv6

          * Unterstu:tzte Bibliotheksfunktionen bzw. Kernel-APIs, siehe auch
            sys/netinet6/ADVAPI.

     * RFC2362: Protocol Independent Multicast-Sparse Mode (PIM-SM)

          * RFC2362 definiert Paketformate fu:r PIM-SM.
            draft-ietf-pim-ipv6-01.txt wurde basierend auf diesem RFC
            verfasst.

     * RFC2373: IPv6 Addressing Architecture

          * Unterstu:tzt vom Knoten erforderliche Adressen und richtet sich
            nach den Erfordernissen des Bereichs.

     * RFC2374: An IPv6 Aggregatable Global Unicast Address Format

          * Unterstu:tzt die 64-Bit-Breite einer Interface ID.

     * RFC2375: IPv6 Multicast Address Assignments

          * Userland-Applikationen nutzen die bekannten Adressen, die in den
            RFC festgelegt sind.

     * RFC2428: FTP Extensions for IPv6 and NATs

          * RFC2428 wird gegenu:ber RFC1639 bevorzugt. FTP-Clients versuchen
            zuerst RFC2428, dann im Fehlerfall RFC1639.

     * RFC2460: IPv6 specification

     * RFC2461: Neighbor discovery for IPv6

          * Siehe auch 23.5.1.2 in diesem Dokument fu:r weitere Details.

     * RFC2462: IPv6 Stateless Address Autoconfiguration

          * Siehe auch 23.5.1.4 in diesem Dokument fu:r weitere Details.

     * RFC2463: ICMPv6 for IPv6 specification

          * Siehe auch 23.5.1.9 in diesem Dokument fu:r weitere Details.

     * RFC2464: Transmission of IPv6 Packets over Ethernet Networks

     * RFC2465: MIB for IPv6: Textual Conventions and General Group

          * Notwendige Statistiken werden vom Kernel gesammelt. Die aktuelle
            IPv6-MIB-Unterstu:tzung wird als Patch-Sammlung fu:r ucd-snmp
            bereitgestellt.

     * RFC2466: MIB for IPv6: ICMPv6 group

          * Notwendige Statistiken werden vom Kernel gesammelt. Die aktuelle
            IPv6-MIB-Unterstu:tzung wird als Patch-Sammlung fu:r ucd-snmp
            bereitgestellt.

     * RFC2467: Transmission of IPv6 Packets over FDDI Networks

     * RFC2497: Transmission of IPv6 packet over ARCnet Networks

     * RFC2553: Basic Socket Interface Extensions for IPv6

          * IPv4 mapped address (3.7) and special behavior of IPv6 wildcard
            bind socket (3.8) are supported. See 23.5.1.12 in this document
            for details.

     * RFC2675: IPv6 Jumbogramms

          * Siehe auch 23.5.1.7 in diesem Dokument fu:r weitere Details.

     * RFC2710: Multicast Listener Discovery for IPv6

     * RFC2711: IPv6 router alert option

     * draft-ietf-ipngwg-router-renum-08: Router renumbering for IPv6

     * draft-ietf-ipngwg-icmp-namelookups-02: IPv6 Name Lookups Through ICMP

     * draft-ietf-ipngwg-icmp-name-lookups-03: IPv6 Name Lookups Through ICMP

     * draft-ietf-pim-ipv6-01.txt: PIM for IPv6

          * pim6dd(8) implementiert dense mode. pim6sd(8) implementiert
            sparse mode.

     * draft-itojun-ipv6-tcp-to-anycast-00: Unterbrechen einer TCP-Verbindung
       toward IPv6 anycast address

     * draft-yamamoto-wideipv6-comm-model-00

          * Beachte 23.5.1.6 in deisem Dokument fu:r weitere Deatils.

     * draft-ietf-ipngwg-scopedaddr-format-00.txt : Eine Erweiterung des
       Format for IPv6 Scoped Addresses

    8.1.1.2. Neighbor Discovery

   Neighbor Discovery ist weitestgehend stabil. Zur Zeit werden
   Addressauflo:sung, Duplicated Address Detection (DAD), und Neighbor
   Unreachability Detection (NUD) unterstu:tzt. In der na:heren Zukunft
   werden wir Proxy Neighbor Advertisement Unterstu:tzung in den Kernel
   einbauen und Unsolicited Neighbor Advertisement U:bertragungskommandos als
   Verwaltungsprogramm zur Verfu:gung stellen.

   Falls DAD versagt, wird die Adresse als "duplicated" markiert und eine
   Nachricht wird erzeugt, die an Syslog gesandt wird (und fu:r gewo:hnlich
   an die Konsole). Die "duplicated"-Markierung kann mit ifconfig(8)
   u:berpru:ft werden. Es liegt in der Verantwortung des Administrators, auf
   DAD-Fehler zu achten und diese zu beheben. Dieses Verhalten sollte in der
   na:heren Zukunft verbessert werden.

   Manche Netzwerktreiber verbinden Multicast-Pakete mit sich selbst, sogar,
   wenn es vorgeschrieben ist, es nicht zu tun (vor allem im
   Promiscuous-Modus). In solchen Fa:llen ko:nnte DAD versagen, weil die
   DAD-Steuerung ein inbound NS packet sieht (eigentlich vom Knoten selber)
   und betrachtet es als ein Duplikat. Sie ko:nnten sich die #if-Bedingung
   ansehen, die in sys/netinet6/nd6_nbr.c:nd6_dad_timer() als "Workaround"
   mit "heuristics" markiert ist (Beachte, dass das Kodefragment im Abschnitt
   "heuristics" nicht der Spezifikation entspricht).

   Neighbor Discovery specification (RFC2461) kommuniziert in den folgenden
   Fa:llen nicht u:ber neighbor cache handling:

    1. Der Knoten empfing ein unverlangtes RS/NS/NA/redirect-Paket ohne
       Link-Layer-Adresse, wenn kein neighbor cache-Eintrag vorhanden ist.

    2. neighbor cache handling bei Gera:ten ohne Link-Layer-Adresse (wir
       beno:tigen einen neighbor cache Eintrag fu:r das IsRouter-Bit)

   Im ersten Fall implemenierten wir einen Workaround basierend auf
   Diskussionen in der IETF-Ipngwg-Mailing-Liste. Fu:r weitere Details
   beachten Sie die Kommentare im Quelltext und im Email-Thread, der bei
   (IPng 7155) mit dem Datum vom 6. Feb 1999 gestartet wurde.

   IPv6 on-link Erkennungsregel (RFC2461) ist recht unterschiedlich zu
   U:bernahmen im BSD-Netzwerkkode. Zur Zeit wird keine on-link
   Erkennungsregel unterstu:tzt, bei der die Defaultrouter-Liste leer ist
   (RFC2461, Abschnitt 5.2, letzter Satz im zweiten Absatz - beachte, dass
   die Spezifikation das Wort "host" und "Knoten" an mehreren Stellen im
   Abschnitt missbraucht).

   Um mo:gliche DoS-Attacken und unendliche Schleifen zu verhindern, werden
   bis jetzt nur 10 Optionen bei ND-Paketen akzeptiert. Deshalb werden nur
   die ersten 10 Pra:fixe beru:cksichtigt, wenn man 20-Pra:fixoptionen zu RA
   hinzugefu:gt hat. Falls das zu Schwierigkeiten fu:hren sollte, dann sollte
   in der FREEBSD-CURRENT-Mailing-Liste gefragt werden und/oder die Variable
   nd6_maxndopt in sys/netinet6/nd6.c modifizieren. Falls die Nachfrage gross
   genug ist, ko:nnte man einen sysctl-Knopf fu:r die Variable vorsehen.

    8.1.1.3. Bereichsindex

   IPv6 benutzt Adressbereiche (Scoped Addresses). Deshalb ist es sehr
   wichtig, mit einer IPv6-Adresse einen Bereichsindex anzugeben
   (Schnittstellenindex fu:r link-local-Adresse, oder einen Lageindex fu:r
   site-local-Adressen). Ohne einen Bereichsindex ist ein IPv6-Adressbereich
   fu:r den Kernel zweideutig und dem Kernel ist es nicht mo:glich, die
   Ausgabeschnittstelle fu:r ein Paket festzustellen.

   Gewo:hnliche Userland-Anwendungen sollten die erweiterte
   Programmierschnittstelle (RFC2292) benutzen, um den Bereichsindex oder
   Schnittstellenindex festzulegen. Fu:r a:hnliche Zwecke wurde in RFC2553
   sin6_scope_id member in der sockaddr_in6-Struktur definiert. Wie auch
   immer, die Semantik fu:r sin6_scope_id ist ziemlich wage. Wenn man auf
   Portierbarkeit der Anwendung achten muss, dann schlagen wir vor, die
   erweiterte Programmierschnittstelle anstelle von sin6_scope_id zu
   benutzen.

   Im Kernel ist ein Schnittstellenindex fu:r link-local scoped-Adressen in
   das zweite 16bit-Wort (drittes und viertes Byte) der IPv6-Adresse
   eingebettet. Zum Beispiel sieht man folgendes

         fe80:1::200:f8ff:fe01:6317

   in der Routing-Tabelle und in der Schnittstellenadress-Struktur
   (structin6_ifaddr). Oben genannte Adresse ist eine "link-local unicast
   address" die zu einer Netzwerkschnittstelle geho:rt, deren
   Schnittstellenbezeichner 1 (eins) ist. Der eingebettete Index ermo:glicht
   es, IPv6 link local-Adressen u:ber mehrere Schnittstellen hinweg effektiv
   und mit wenig A:nderungen am Kode zu identifizieren.

   Routing-Da:monen und Konfigurationsprogramme wie route6d(8) und
   ifconfig(8) werden den "eingebetteten" Bereichsindex vera:ndern mu:ssen.
   Diese Programme benutzen routing sockets und ioctls (wie SIOCGIFADDR_IN6)
   und die Kernel-Programmierschnittstelle wird IPv6-Adressen, dessen zweites
   16-Bit-Word gesetzt ist, zuru:ckgeben. Diese Programmierschnittstellen
   dienen zur A:nderung der Kernel-internen Struktur. Programme, die diese
   Programmierschnittstellen benutzen, mu:ssen ohnehin auf Unterschiede in
   den Kerneln vorbereitet sein.

   Wenn man einen Adressbereich in der Kommandozeile angibt, schreibt man
   niemals die eingebettete Form (so etwas wie ff02:1::1 or fe80:2::fedc).
   Man erwartet nicht, dass es funktioniert. Man benutzt immer die
   Standardform wie ff02::1 oder fe80::fedc, zusammen mit der
   Kommandozeilenoption, die die Schnittstelle festlegt (wie ping6 -I ne0
   ff02::1). Allgemein gilt, wenn ein Kommando keine Kommandozeilenoption
   hat, um die Ausgabeschnittstelle zu definieren, ist dieses Kommando noch
   nicht fu:r Adressbereiche bereit. Dies scheint der Pra:misse von IPv6
   entgegenzustehen. Wir glauben, dass die Spezifikationen einige
   Verbesserungen beno:tigen.

   Einige der Userland-Werkzeuge unterstu:tzen die erweiterte numerische
   IPv6-Syntax wie sie in draft-ietf-ipngwg-scopedaddr-format-00.txt
   beschrieben ist. Man kann die ausgehende Verbindung angeben, indem man den
   Namen der ausgehenden Schnittstelle wie folgt benutzt: "fe80::1%ne0". Auf
   diese Art und Weise ist man in der Lage, eine link-local scoped Adresse
   ohne viele Schwierigkeiten anzugeben.

   Um die Erweiterungen im eigenen Programm zu nutzen, muss man
   getaddrinfo(3) und getnameinfo(3) mit NI_WITHSCOPEID verwenden. Die
   Implementierung setzt im Moment eine 1-zu-1 Beziehung zwischen einer
   Verbindung und einer Schnittstelle voraus, die sta:rker ist, als es die
   Spezifikationen beschreiben.

    8.1.1.4. Plug and Play

   Der gro:sste Teil der statuslosen IPv6-Adress-Autokonfiguration ist im
   Kernel implementiert. Neighbor-Discovery-Funktionen sind als ganzes im
   Kernel implementiert. Router-Advertisement (RA) Eingabe fu:r Hosts ist im
   Kernel implementiert. Router-Solicitation (RS) Ausgabe fu:r Hosts,
   RS-Eingabe fu:r Router und RA-Ausgabe fu:r Router ist im Userland
   implementiert.

      8.1.1.4.1. Zuweisung von link-local und speziellen Adressen

   Die IPv6 link-local-Adresse wird aus einer IEEE802-Adresse (Ethernet MAC
   address) erzeugt. Jeder Schnittstelle wird automatisch eine IPv6
   link-local-Adresse zugewiesen, sobald die Schnittstelle aktiv ist
   (IFF_UP). Ebenso wird eine direkte Route fu:r die link-local-Adresse zur
   Routing-Tabelle hinzugefu:gt.

   Hier ist eine Ausgabe des netstat-Kommandos:

 Internet6:
 Destination                   Gateway                   Flags      Netif Expire
 fe80:1::%ed0/64               link#1                    UC          ed0
 fe80:2::%ep0/64               link#2                    UC          ep0

   Schnittstellen, die keine IEEE802-Adresse haben (Pseudo-Schnittstellen wie
   Tunnel-Schnittstellen oder ppp-Schnittstellen), borgen sich eine
   IEEE802-Adresse von anderen Schnittstellen wie Ethernet-Schnittstellen
   aus, wann immer das mo:glich ist. Wenn keine IEEE802-Gera:te eingebaut
   sind, wird als letzte Mo:glichkeit eine Pseudo-Zufallszahl - MD5(hostname)
   - als Quelle fu:r eine link-local-Adresse benutzt. Falls diese fu:r den
   Einsatz nicht geeignet sein sollte, dann muss man eine link-local-Adresse
   manuell konfigurieren.

   Falls eine Schnittstelle nicht imstande ist, IPv6-Adressen zu handhaben
   (wie fehlende Unterstu:tzung des multicast), wird keine link-local-Adresse
   der Schnittstelle zugewiesen. Siehe Abschnitt 2 fu:r weitere Details.

   Jede Schnittstelle verbindet die solicited multicast Adresse und
   link-local all-nodes multicast-Adressen (z.B. fe80::1:ff01:6317 und
   ff02::1, jeweils zu der Verbindung, an die die Schnittstelle verbunden
   ist). zusa:tzlich zu einer link-local-Adresse wird eine loopback-Adresse
   (::1) einer loopback-Schnittstelle zugewiesen. Ausserdem werden ::1/128
   und ff01::/32 automatisch zur Routing-Tabelle hinzugefu:gt und die
   loopback-Schnittstelle verbindet sich mit der node-local multicast Gruppe
   ff01::1.

      8.1.1.4.2. Stateless address autoconfiguration beim Host

   In der IPv6-Spezifikation werden Knoten in zwei Kategorien unterteilt:
   Router und Hosts. Router leiten Pakete, die an andere adressiert sind,
   weiter, Hosts leiten Pakete nicht weiter. net.inet6.ip6.forwarding
   definiert, ob dieser Knoten ein Router oder ein Host ist (Router falls es
   1 ist, Host, falls es 0 ist).

   Sobald ein Host ein Router-Advertisement vom Router ho:rt, kann er sich
   selbst mit statusloser automatischer Adressen konfigurieren. Dieses
   Verhalten kann mit net.inet6.ip6.accept_rtadv (der Host konfiguriert sich
   selber, wenn es auf 1 gesetzt ist) beeinflusst werden. Bei einer
   automatischen Konfiguration wird das Netzwerkadresspra:fix fu:r die
   empfangende Schnittstelle (fu:r gewo:hnlich das globale Adresspra:fix)
   hinzugefu:gt. Die Standard-Route wird ebenso konfiguriert. Router erzeugen
   periodisch Router-Advertisement-Pakete. Um einen benachbarten Router
   aufzufordern, ein RA-Paket zu erzeugen, kann eine Host-Router-Solicitation
   u:bertragen werden. Um jederzeit ein RS-Paket zu erzeugen, benutzt man das
   rtsol-Kommando. Ein rtsold(8)-Da:mon ist ebenso verfu:gbar. rtsold(8)
   erzeugt Router-Solicitation, wann immer es notwendig ist und es
   funktioniert grossartig "bei normadischem Einsatz" (Notebooks/Laptops).
   Falls jemand Router-Advertisements zu ignorieren wu:nscht, setzt man mit
   sysctl et.inet6.ip6.accept_rtadv auf 0.

   Um Router-Advertisement von einem Router aus zu erzeugen, benutzt man den
   rtadvd(8)-Da:mon.

   Beachte, dass die IPv6-Spezifikation von folgenden Punkte ausgeht und
   nicht konforme Fa:lle werden als nicht spezifiziert ausgelassen:

     * Nur Hosts ho:ren auf Router-Angebote

     * Hosts haben eine einzige Netzwerk-Schnittstelle (ausser loopback)

   Deshalb ist es unklug, net.inet6.ip6.accept_rtadv bei Routern oder bei
   Hosts mit mehreren Schnittstellen einzuschalten. Ein falsch konfigurierter
   Knoten kann sich seltsam verhalten (nicht konforme Konfiguration ist fu:r
   diejenigen erlaubt, die Experimente durchfu:hren mo:chten).

   Eine Zusammenfassung des sysctl-Angaben:

         accept_rtadv forwarding Rolle des Knotens
    ---     ---    ---
     0       0    Host (wird manuell konfiguriert)
     0       1    Router
     1       0    automatisch konfigurierter Host
                      (Die Spezifikation setzt voraus, dass der Host nur eine einzelne Schnittstelle hat, ein automatisch konfigurierter Host mit mehreren Schnittstellen ist ausserhalb der Betrachtung)
     1       1    ungu:ltig, oder fu:r Experimentierzwecke (ausserhalb der Spezifikation)

   RFC2462 hat eine U:berpru:fungsregel gegen eingehende
   RA-prefix-information-option, in 5.5.3 (e). Dies dient zum Schutz des
   Hosts vor schlecht oder falsch konfigurierten Routern, die eine sehr kurze
   Pra:fixlebenszeit anku:ndigen. Es gab Aktualisierungen von Jim Bound in
   der ipngwg-Mailing-Liste (suche nach "(ipng 6712)" im Archive) und es
   wurde Jims Aktualisierung implementiert.

   Siehe auch 23.5.1.2 im Dokument fu:r das Verha:ltnis zwischen DAD und
   autoconfiguration.

    8.1.1.5. Generische Tunnel-Schnittstelle

   GIF (Generische Schnittstelle) ist eine Pseudoschnittstelle fu:r
   konfigurierte Tunnel. Details sind in gif(4) beschrieben. Im Moment sind

     * v6 in v6

     * v6 in v4

     * v4 in v6

     * v4 in v4

   verfu:gbar. Benutze gifconfig(8), um die physikalische (ausserhalb
   liegende) Quelle und die Zieladresse den gif-Schnittstellen zuzuweisen.
   Eine Konfiguration, die die selbe Adressfamilie fu:r innere und a:ussere
   IP-Header (v4 in v4, oder v6 in v6) benutzt, ist gefa:hrlich. Es ist sehr
   leicht, Schnittstellen und Routing-Tabellen so zu konfigurieren, dass eine
   unendliche Ebene von Tunneln ausgefu:hrt wird. Seien Sie also gewarnt.

   gif kann ECN-freundlich konfiguriert werden. Beachte 23.5.4.5 fu:r eine
   ECN-Freundlichkeit von Tunneln und gif(4) wie man sie konfiguriert.

   Falls man einen IPv4-in-IPv6-Tunnel mit einer gif-Schnittstelle
   konfigurieren mo:chte, sollte man gif(4) sorgfa:ltig lesen. Man muss die
   IPv6 link-local Adresse, die automatisch der gif-Schnittstelle zugewiesen
   wird, entfernen.

    8.1.1.6. Source Address Selection

   Im Moment ist die Regel zur Auswahl der Quelle bereichsorientiert (es gibt
   einige Ausnahmen - siehe unten). Fu:r ein gegebenes Ziel wird eine
   Quell-IPv6-Adresse durch folgende Regel ausgewa:hlt:

    1. Falls die Quelladresse explizit durch den Benutzer angegeben ist (z.B.
       u:ber das erweiterte API), dann wird die angegebene Adresse benutzt.

    2. Falls eine Adresse der ausgehenden Schnittstelle zugewiesen wird, die
       den selben Bereich wie die Zieladresse hat (was normalerweise durch
       einen Blick in die Routing-Tabelle festgestellt werden kann), dann
       wird diese Adresse benutzt.

       Dies ist ein typischer Fall.

    3. Falls keine Adresse der obigen Bedingung genu:gt, dann wa:hlt man eine
       globale Adresse, die einer der Schnittstellen des sendenden Knotens
       zugewiesen ist.

    4. Falls keine Adresse der obigen Bedingung genu:gt und die Zieladresse
       ist im site local-Bereich, dann wa:hlt man eine eine site
       local-Adresse, die einer der Schnittstellen des sendenden Knotens
       zugewiesen ist.

    5. Falls keine Adresse der obigen Bedingung genu:gt, dann wa:hlt man eine
       Adresse, die mit einem Eintrag in der Routing-Tabelle fu:r das Ziel
       verbunden ist. Dies ist die letzte Mo:glichkeit, die eine
       Bereichsverletzung verursachen ko:nnte.

   Zum Beispiel, ::1 ist ausgewa:hlt fu:r ff01::1, fe80:1::200:f8ff:fe01:6317
   fu:r fe80:1::2a0:24ff:feab:839b (beachte den eingebetteten
   Schnittstelleindex - beschrieben in 23.5.1.3 - er hilft uns, die richtige
   Quelladresse auszuwa:hlen. Diese eingebetteten Indexe werden nicht
   u:bertragen). Falls die ausgehende Schnittstelle mehrere Adressen fu:r
   einen Bereich hat, wird die Quelle gewa:hlt, die die breiteste passende
   Basis hat (Regel 3). Angenommen 2001:0DB8:808:1:200:f8ff:fe01:6317 und
   2001:0DB8:9:124:200:f8ff:fe01:6317 sind einer ausgehenden Schnittstelle
   zugewiesen. 2001:0DB8:808:1:200:f8ff:fe01:6317 wird als Quelle fu:r das
   Ziel 2001:0DB8:800::1 ausgewa:hlt.

   Beachte, dass obige Regel nicht in der IPv6-Spezifikation dokumentiert
   ist. Es wird als "up to implementation"-Punkt betrachtet. Es gibt einige
   Fa:lle, bei denen die obige Regel nicht benutzt werden soll. Ein Beispiel
   ist die verbundene TCP-Sitzung und man benutzt die Adresse, die in tcb als
   Quelle gehalten wird. Ein anderes Beispiel ist die Quelladresse fu:r
   Neighbor Advertisement. Laut Spezifikation (RFC2461 7.2.2) sollte die
   Quelle des NA die Zieladresse des korrespondierenden Ziel des NS sein. In
   diesem Fall folgen wir eher der Spezifikation, als der obigen
   longest-match-Regel.

   Fu:r neue Verbindungen werden (wenn Regel eins nicht zutrifft) abgelehnte
   Adressen (Adressen mit bevorzugter Lebenszeit = 0) nicht ausgewa:hlt, wenn
   andere Auswahlmo:glichkeiten bestehen. Wenn keine anderen
   Auswahlmo:glichkeiten bestehen, werden abgelehnte Adressen als letzte
   Mo:glichkeit benutzt. Falls mehrere Auswahlmo:glichkeiten fu:r abgelehnte
   Adressen bestehen, dann wird ogige Regel verwendet, um aus diesen
   abgelehnten Adressen auszuwa:hlen. Falls man aus bestimmten Gru:nden die
   Benutzung abgelehnter Adressen unterbinden mo:chte, dann setzt man
   net.inet6.ip6.use_deprecated auf 0. Der Punkt bezu:glich der abgelehnten
   Adressen ist in RFC2462 5.5.4 beschrieben (Beachte: Im Moment wird in der
   IETF ipngwg daru:ber debatiert, wie angelehnte Adressen benutzt werden
   sollen).

    8.1.1.7. Jumbo Payload

   Die Jumbo-Payload hop-by-hop-Option ist implementiert und kann benutzt
   werden, um IPv6-Pakete mit Datenpaketen gro:sser als 65.535 Oktette. Aber
   im Moment wird keine physikalische Schnittstelle unterstu:tzt, deren MTU
   gro:sser ist als 65.536, so dass diese Datenpakete nur bei den
   loopback-Schnittstellen zu finden sind (z.B. lo0).

   Falls man die Jumbo Payloads testen mo:chte, muss man zuna:chst den Kernel
   rekonfigurieren, so dass die MTU der loopback-Schnittstelle gro:sser
   65.535 Bytes sein kann. Fu:ge folgende Zeile zur Kernel-Konfiguration
   hinzu:

   options "LARGE_LOMTU" #Um Jumbo Payload zu testen

   und dann kompiliere den Kernel neu.

   Dann kann man die Jumbo-Payloads mittels ping6(8)-Kommando mit den
   Optionen -b und -s testen. Die Option -b muss angegeben werden, um die
   Gro:sse des Socket-Puffers zu erho:n, und die Option -s gibt die Gro:sse
   des Pakets an, die gro:sser als 65.535 sein sollte. Beispielsweise gibt
   man folgendes ein:

 % ping6 -b 70000 -s 68000 ::1

   Die IPv6-Spezifikation verlangt, dass die Jumbo-Payload-Option nicht in
   einem Paket verwendet werden darf, das einen fragmentierten Header hat.
   Falls diese Bedingung nicht zutrifft, dann muss eine
   ICMPv6-Parameter-Problem-Nachricht an den Absender geschickt werden. Die
   Spezifikation ist befolgt, aber man kann normalerweise nicht einen
   ICMPv6-Fehler sehen, der durch diese Forderung hervorgerufen wird.

   Wenn ein IPv6-Paket empfangen wird, dann wird die Rahmenla:nge gepru:ft
   und sie wird mit der Gro:sse verglichen, die im Datenfeld fu:r die
   Paketgro:sse des IPv6-Headers oder im Wert fu:r die Jumbo-Payload-Option
   angegeben ist, sofern vorhanden. Falls ersterer kleiner als letzterer ist,
   dann wird das Paket abgelehnt und die Statistiken werden erho:ht. Man kann
   die Statistik als Ausgabe des netstat(8)-Kommandos mit der `-s -p
   ip6'-Option sehen:

 % netstat -s -p ip6
           ip6:
                 (snip)
                 1 with data size < data length

   So, der Kernel sendet keinen ICMPv6-Fehler, ausser das fehlerhafte Paket
   ist ein aktuelles Jumbo-Payload, dessen Paketgro:sse gro:sser als 65,535
   Bytes ist. Wie oben beschrieben, gibt es momentan keine physikalische
   Schnittstelle, die eine so riesige MTU unterstu:tzt, daher gibt es so
   selten einen ICMPv6-Fehler.

   TCP/UDP over Jumbogramm wird im Moment nicht unterstu:tzt. Dies kommt
   daher, weil wir kein Medium (ausser loopback) haben, dies zu testen.
   Melden Sie sich, falls Sie es beno:tigen.

   IPsec funktioniert nicht mit Jumbogramm. Dies ist bedingt durch einige
   A:nderungen an der Spezifikation, welche die Unterstu:tzung von AH mit
   Jumbogramm betrifft (AH-Header-Gro:sse beeinflusst die La:nge des
   Datenpakets und das macht es richtig schwierig, ein eingehendes Paket mit
   Jumbo-Payload-Option so gut zu authentifizieren wie ein AH).

   Es gibt grundlegende Punkte in der *BSD-Unterstu:tzung fu:r Jumbogramms.
   Wir wu:rden jene gerne ansprechen, aber wir beno:tigen mehr Zeit diese
   fertig zu stellen. Um ein paar zu benennen:

     * mbuf pkthdr.len-Feld ist in 4.4BSD typisiert als "int", so dass es
       kein Jumbogramm mit len > 2G bei 32Bit-Architekturen aufnehmen kann.
       Wenn wir Jumbogramme geeignet unterstu:tzen wollten, dann muss das
       Feld erweitert werden, damit es 4G + IPv6-Header + link-layer-Header
       aufnehmen kann. Deshalb muss es schliesslich auf int64_t (u_int32_t
       ist NICHT genug) erweitert werden.

     * Irrigerweise benutzen wir "int" an vielen Stellen, um die Paketla:nge
       aufzunehmen. Wir mu:ssen sie in einen gro:sseren ganzzahligen Typ
       konvertieren. Es braucht grosse Vorsicht, weil wir sonst einen
       U:berlauf wa:hrend der Berechnung der Paketla:nge erleben ko:nnen.

     * Irrigerweise pru:fen wir das ip6_plen-Feld des IPv6-Header fu:r packet
       payload length an verschiedenen Stellen. Wir sollten mbuf pkthdr.len
       stattdessen pru:fen. ip6_input() wird bei der Eingabe eine Pru:fung
       der Jumbo -Payload-Option durchfu:hren und wir ko:nnen danach mbuf
       pkthdr.len sicher benutzen.

     * Natu:rlich braucht der TCP-Kode an einigen Stellen eine sorgfa:ltige
       Aktualisierung.

    8.1.1.8. Verhindern von Schleifen beim Verarbeiten von Headern

   Die IPv6-Spezifikation erlaubt eine willku:rliche Zahl von
   Erweiterungs-Headern, die in einem Paket platziert werden ko:nnen. Wenn
   wir IPv6-Kode fu:r die Paketverarbeitung auf die Art und Weise
   implementieren wie wir es beim BSD-IPv4-Kode geschehen ist, dann wu:rde
   wegen einer lange Kette von Funktionsaufrufen der Kernel-Stack
   u:berlaufen. sys/netinet6-Kode ist behutsam entwickelt wurden, um einen
   U:berlauf des Kernel-Stacks zu verhindern. Deswegen definiert der
   sys/netinet6-Kode seine eigene Protocol-Switch-Struktur "struct
   ip6protosw" (siehe auch netinet6/ip6protosw.h). Aus Gru:nden der
   Kompatibilita:t gibt es keine solche Aktualisierung im IPv4-Teil
   (sys/netinet), aber eine kleine A:nderung ist zum pr_input()-Prototyp
   hinzugefu:gt worden. So ist "struct ipprotosw" ebenso definiert. Deswegen
   kann der Kernel-Stack sich aufbla:hen, wenn man ein IPsec-over-IPv4-Paket
   mit einer massiven Zahl von IPSec-Header empfa:ngt. IPsec-over-IPv6 ist in
   Ordnung. (Natu:rlich muss fu:r all diese zu verarbeitenden IPSec-Header
   jeder einzelne IPSec-Header jede IPSec-Pru:fung durchlaufen. So wird es
   einem anonymen Angreifer unmo:glich gemacht eine Attacke durchzufu:hren.)

    8.1.1.9. ICMPv6

   Nachdem RFC2463 vero:ffentlicht worden war, hat die IETF-ipngwg
   beschlossen ICMPv6-Fehler-Pakete gegen ICMPv6 umzuleiten, um einen
   ICMPv6-Sturm auf einem Netzwerkmedium zu unterbinden. Dies ist bereits im
   Kernel implementiert.

    8.1.1.10. Anwendungen

   Fu:r Programmierung des Userland unterstu:tzen wir das IPv6-Socket-API wie
   es in RFC2553, RFC2292 und in aufkommenden Internet-Konzepten beschrieben
   ist.

   TCP/UDP u:ber IPv6 ist verfu:gbar und ziemlich stabil. Man kann sich an
   telnet(1), ftp(1), rlogin(1), rsh(1), ssh(1), usw. erfreuen. Diese
   Anwendungen sind unabha:ngig vom Protokoll. Das liegt daran, weil diese
   Programme automatisch IPv4 oder IPv6 entsprechend des DNS auswa:hlen.

    8.1.1.11. Kernel Interna

   Wa:hrend ip_forward() ip_output() aufruft, ruft ip6_forward() direkt
   if_output() auf, da Router IPv6-Pakete nicht in Fragmente teilen du:rfen.

   ICMPv6 sollte das original Paket so lang wie mo:glich bis maximal 1280
   halten. UDP6/IP6 port unreach, zum Beispiel, sollte alle
   Erweiterungs-Header und die unvera:nderten UDP6- und IP6-Header enthalten.
   Um das originale Paket zu erhalten, konvertieren alle IP6-Funktionen
   ausser TCP niemals Network-Byte-Order in Host-Byte-Order.

   tcp_input(), udp6_input() und icmp6_input() ko:nnen nicht voraussetzen,
   dass der IP6-Header vor dem Transport-Header, der zum Extension-Header
   geho:rt, kommt. Deshalb wurde in6_cksum() implementiert, um Pakete, deren
   IP6-Header und Transport-Header nicht fortlaufend ist, zu behandeln. Weder
   TCP/IP6- noch UDP6/IP6-Header-Strukturen existieren, um eine Pru:summe zu
   bilden.

   Um IP6-Header, Extension-Header und Transport-Headers leichter verarbeiten
   zu ko:nnen, werden nun Netzwerktreiber beno:tigt, die Pakete in einem
   internen mbuf oder in einem oder mehreren externen mbuf speichern ko:nnen.
   Ein typischer alter Treiber legt zwei interne mbuf fu:r 96 - 204 Bytes an
   Daten an, wie auch immer wird ein solches Paket jetzt in einem externen
   mbuf gespeichert.

   netstat -s -p ip6 ermittelt, ob der Treiber sich nach solchen
   Erfordernissen richtet, oder nicht. Im folgenden Beispiel verletzt "cce0"
   dies Erfordernisse (Fu:r weitere Informationen, siehe Abschnitt 2.).

 Mbuf statistics:
                 317 one mbuf
                 two or more mbuf::
                         lo0 = 8
                         cce0 = 10
                 3282 one ext mbuf
                 0 two or more ext mbuf

   Jede Eingabefunktion ruft IP6_EXTHDR_CHECK am Anfang auf, um zu pru:fen,
   ob der Bereich zwischen IP6 und seinen Header durchgehend ist.
   IP6_EXTHDR_CHECK ruft m_pullup() nur dann auf, wenn mbuf das M_LOOP-Flag
   gestzt hat, weil das Paket von der Loopback-Schnittstelle kommt.
   m_pullup() wird niemals aufgerufen, wenn Pakete von physikalischen
   Netzwerkschnittstellen kommen.

   IP- und IP6-Reassemble-Funktionen rufen niemals m_pullup() auf.

    8.1.1.12. IPv4-Mapped-Address und IPv6-Wildcard-Socket

   RFC2553 beschreibt IPv4-Mapped-Address (3.7) und die spezielle
   Verhaltensweise des IPv6-Wildcard-Bind-Socket (3.8). Die Spezifikation
   gestattet es:

     * IPv4-Verbindungen von AF_INET6-Wildcard-Bind-Socket zu erlauben.

     * IPv4-Pakete u:ber AF_INET6-Socket zu transportieren, indem eine
       spezielle Form der Adresse wie ::ffff:10.1.1.1 benutzt wird.

   Aber die Spezifikation ist sehr kompliziert und spezifiziert nicht, wie
   der Socket-Layer sich verhalten soll. Darauf Bezug nehmend nennen wir hier
   ersteren "ho:rende Seite" und letzteren "beginnende Seite".

   Man kann einen Wildcard-Bind auf demselben Port bei beiden Adressfamilien
   durchfu:hren.

   Die folgende Tabelle zeigt das Verhalten von FreeBSD 4.x.

 Ho:rende Seite          Beginnende Seite
                 (AF_INET6-Wildcard-      (Verbindung zu ::ffff:10.1.1.1)
                 Socket erreicht IPv4 Verb.)
                 ---                     ---
 FreeBSD 4.x     Konfigurierbar            unterstu:tzt
                 Standard: erlaubt

   Die folgende Abschnitte zeigen mehr Details und wie man das Verhalten
   konfigurieren kann.

   Kommentare auf der ho:renden Seite:

   Es sieht so aus, dass RFC2553 zu wenig zu den Punkten u:ber Wildcard-Bind
   erla:utert, speziell zum Punkt u:ber Port-Space, Fehler-Modus und
   Beziehung zwischen AF_INET/INET6 wildcard bind. Es kann mehrere
   unterschiedliche Interpretationen zu diesem RFC geben, die sich nach
   diesen richten, aber sich unterschiedlich verhalten. Um eine portable
   Anwendung zu implementieren, sollte man deshalb nicht ein bestimmtes
   Verhalten des Kernels voraussetzen. Der Einsatz von getaddrinfo(3) ist der
   sicherste Weg. Port number space und wildcard bind issues wurden Mitte Mai
   1999 detailliert in der Ipv6imp-Mailing-Liste diskutiert und es sieht so
   aus, als ob es keinen konkreten Konsens gab (means, up to implementers).
   Vielleicht sollte man die Archive der Mailing-Liste pru:fen.

   Wenn eine Server-Anwendung IPv4- und IPv6-Verbindungen annehmen mo:chte,
   dann gibt es zwei Alternativen.

   Eine benutzt AF_INET- und AF_INET6-Socket (man beno:tigt zwei Sockets).
   Benutze getaddrinfo(3) mit gesetztem AI_PASSIVE-Bit in ai_flags, socket(2)
   und bind(2) fu:r alle zuru:ckgegebenen Adressen. Mit dem o:ffnen mehrerer
   Sockets kann man Verbindungen an dem Socket mit der richtigen
   Adressfamilie annehmen. IPv4-Verbindungen werden vom AF_INET-Socket und
   IPv6-Verbindungen vom AF_INET6-Socket angenommen.

   Ein anderer Weg ist einen AF_INET6 wildcard bind-Socket zu verwenden. Man
   benutzt getaddrinfo(3) mit AI_PASSIVE in ai_flags, mit AF_INET6 in
   ai_family, man setzt das erste Argument hostname auf NULL, socket(2) und
   bind(2) auf die zuru:ckgegebene Adresse (es sollte eine unspezifizierte
   IPv6-Adresse sein). Man kann IPv4- und IPv6-Paket u:ber diesen Socket
   annehmen.

   Um nur IPv6-Datenverkehr portabel an AF_INET6 wildcard gebundenen Socket
   zu unterstu:tzen, pru:ft man, sobald die Verbindung Zustande gekommen ist,
   immer die Peer-Adresse gegen den ho:renden AF_INET6-Socket. Wenn die
   Adresse eine IPv4-Mapped-Adresse ist, dann sollte man die Verbindung
   zuru:ckweisen. Man kann die Bedingung mit dem IN6_IS_ADDR_V4MAPPED()-Makro
   pru:fen.

   Um diesen Punkt leichter lo:sen zu ko:nnen, gibt es fu:r setsockopt(2) die
   System abha:ngige Option IPV6_BINDV6ONLY, die wie folgt benutzt wird.

         int on;

         setsockopt(s, IPPROTO_IPV6, IPV6_BINDV6ONLY,
                    (char *)&on, sizeof (on)) < 0));

   Wenn der Aufruf erfolgreich ist, dann empfa:ngt dieser Socket nur
   IPv6-Pakete.

   Kommentare zur sendenden Seite:

   Ratschlag an Anwendungsentwickler: um eine portable IPv6-Anwendung zu
   implementieren (die mit verschiedenen IPv6-Kerneln funktioniert), ist das
   Folgende der Schlu:ssel zum Erfolg wie wir glauben:

     * NIEMALS AF_INET oder AF_INET6 hart kodieren.

     * Benutze getaddrinfo(3) und getnameinfo(3) u:berall im System. Benutze
       niemals gethostby*(), getaddrby*(), inet_*() oder getipnodeby*() (Um
       bestehende Applikationen leicht IPv6 fa:hig zu machen, wird
       getipnodeby*() manchmal nu:tzlich sein. Falss es aber mo:glich sein
       sollte, versuche den Kode neu zu schreiben und getaddrinfo(3) und
       getnameinfo(3) zu benutzen)

     * Wenn man sich an ein Ziel verbinden mo:chte, benutze getaddrinfo(3)
       und versuche alle zuru:ckgegebenen Ziele, wie telnet(1) es macht.

     * Einige IPv6-Stacks sind mit fehlerhafter getaddrinfo(3) verschickt
       worden. Man verschickt als letzte Mo:glichkeit eine minimal arbeitende
       Version der Anwendung.

   Wenn man einen AF_INET6-Socket fu:r jeweils eine ausgehende IPv4- und
   IPv6-Verbingung benutzen mo:chte, dann muss man getipnodebyname(3)
   benutzen. Wenn man seine existierende Anwendung mit wenig Aufwand
   IPv6-fa:hig machen mo:chte, dann sollte dieser Versuch gewa:hlt werden.
   Aber beachte bitte, dass dies eine tempora:re Lo:sung ist, weil
   getipnodebyname(3) selber noch zu empfehlen ist, da es noch keine
   Adressbereiche verarbeitet. Fu:r eine IPv6-NAmensauflo:sung ist
   getaddrinfo(3) das bevorzugte API. Deshalb sollte man seine Anwendung so
   umschreiben, dass getaddrinfo(3) benutzt wird, wann man Zeit dazu hat.

   Wenn man Anwendungen schreibt, die ausgehende Verbindungen herstellen,
   wird die Geschichte viel einfacher, wenn man AF_INET und AF_INET6 als
   total getrennte Adressfamilien behandelt. {set,get}sockopt funktioniert
   viel einfacher, DNS-Angelegenheiten werden einfacher gemacht. Wir
   empfehlen sich nicht auf IPv4-Mapped-Adressen zu verlassen.

      8.1.1.12.1. Einheitlicher TCP-und INPCB-Kode

   FreeBSD 4.x benutzt shared TCP-Kode zwischen IPv4 und IPv6 (von
   sys/netinet/tcp*) und separaten udp4/6-Kode. Es benutzt eine
   vereinheitlichte inpcb-Struktur.

   Die Plattform kann fu:r eine Unterstu:tzung von IPv4-mapped-Adressen
   konfiguriert werden. Die Kernel-Konfiguration la:sst sich wie folgt
   zusammenfassen:

     * By default, AF_INET6 socket will grab IPv4 connections in certain
       condition, and can initiate connection to IPv4 destination embedded in
       IPv4 mapped IPv6 address.

     * Man kann es wie unten beschrieben abschalten.

       sysctl net.inet6.ip6.mapped_addr=0

        8.1.1.12.1.1. Ho:rende Seite

   Jeder Socket kann fu:r eine Unterstu:tzung eines speziellen AF_INET6
   wildcard bind (Standardma:ssig eingeschaltet) konfiguriert werden. Man
   kann es auf Socket-Basis mit setsockopt(2) wie unten beschrieben
   abschalten.

         int on;

         setsockopt(s, IPPROTO_IPV6, IPV6_BINDV6ONLY,
                    (char *)&on, sizeof (on)) < 0));

   Wildcard-AF_INET6-Socket schnappt sich die IPv4-Verbindung, wenn, und nur
   wenn folgende Bedingungen erfu:llt sind::

     * Es gibt keinen AF_INET-Socket, der zu einer IPv4-Verbindung passt

     * Der AF_INET6-Socket ist so konfiguriert, dass er IPv4-Datenverkehr
       akzeptiert, z.B. gibt getsockopt(IPV6_BINDV6ONLY) 0 zuru:ck.

   Es gibt kein Problem mit der O:ffnen/Schliessen-Reihenfolge.

        8.1.1.12.1.2. initiating side

   FreeBSD 4.x unterstu:tzt ausgehende Verbindungen zu IPv4 mapped Adressen
   (::ffff:10.1.1.1), falls der Knoten so konfiguriert ist, dass er IPv4
   mapped Adressen unterstu:tzt.

    8.1.1.13. sockaddr_storage

   Als RFC2553 kurz vor der Vollendung stand, gab es eine Diskussion, wie
   struct sockaddr_storage Mitglieder benannt werden sollten. Ein Vorschlag
   war "__" den Mitgliedern (wie "__ss_len") voranzustellen und es sollten
   sie nicht vera:ndert werden. Der andere Vorschlag war, nichts
   voranzustellen (wie "ss_len") also mussten wir solche Mitglieder direkt
   vera:ndern. Es gab keinen klaren Konsens.

   Als Ergebnis definiert RFC2553 die Struktur sockaddr_storage wie folgt:

         struct sockaddr_storage {
                 u_char  __ss_len;       /* address length */
                 u_char  __ss_family;    /* address family */
                 /* and bunch of padding */
         };

   Im Gegensatz dazu definiert der XNET-Entwurf die Struktur wie folgt:

         struct sockaddr_storage {
                 u_char  ss_len;         /* address length */
                 u_char  ss_family;      /* address family */
                 /* and bunch of padding */
         };

   Im Dezember 1999 kam man u:berein, dass RFC2553bis letztere Definition
   (XNET) aufnehmen sollte.

   Die aktuelle Implementierung ist konform zur XNET-Definition basierend auf
   der RFC2553bis Diskussion.

   Wenn man mehrere IPv6-Implementierungen betrachtet, wird man beide
   Definitionen sehen. Fu:r Userland-Programmierer ist der folgende Weg der
   meist portable um damit umzugehen:

    1. Man versichert sich, dass ss_family und/oder ss_len fu:r die Plattform
       verfu:gbar sind, indem man GNU autoconf verwendet,

    2. Man benutzet -Dss_family=__ss_family um alle Vorkommen
       (einschliesslich der Header-Files) zu __ss_family zu vereinheitlichen,
       oder

    3. Man benutzt niemals __ss_family. Man fu:hre einen Typecast nach
       sockaddr * durch und verwendet sa_family wie folgt:

         struct sockaddr_storage ss;
         family = ((struct sockaddr *)&ss)->sa_family

  8.1.2. Netzwerktreiber

   Die beiden folgenden Dinge mu:ssen zwingend von Standardtreibern
   unterstu:tzt werden:

    1. Mbuf-Clustering-Erfordernis. In diesem stabilen Release haben wir fu:r
       alle Betriebssystem MINCLSIZE in MHLEN+1 gea:ndert, damit sich alle
       Treiber wie erwartet verhalten.

    2. Multicast. Falls ifmcstat(8) keine Multicast-Gruppe fu:r die
       Schnittstelle liefert, dann muss diese Schnittstelle u:berarbeitet
       werden.

   Falls keiner der Treiber die Erfordernisse erfu:llt, dann ko:nnen die
   Treiber nicht fu:r IPv6/IPSec-Kommunikation verwendet werden. Falls man
   ein Problem beim Einsatz von IPv6/IPSec mit seiner Karte hat, dann melde
   es bitte bei FreeBSD problem reports.

   (Beachte: In der Vergangenheit haben wir gefordert, dass alle
   PCMCIA-Treiber einen Aufruf nach in6_ifattach() haben. Inzwischen haben
   wir keine solche Forderung mehr)

  8.1.3. Translator

   Wir kategorisieren einen IPv4/IPv6-Translator in 4 Typen:

     * Translator A --- Er wird im fru:hen Stadium des U:bergangs benutzt um
       es zu ermo:glichen, dass eine Verbindung von einem IPv6-Host auf einer
       IPv6-Insel zu einem IPv4-Host im IPv4-Ozean hergestellt wird.

     * Translator B --- Er wird im fru:hen Stadium des U:bergangs benutzt um
       es zu ermo:glichen, dass eine Verbindung von einem IPv4-Host im
       IPv4-Ozean zu einem IPv6-Host auf einer IPv6-Insel hergestellt wird.

     * Translator C --- Er wird im fru:hen Stadium des U:bergangs benutzt um
       es zu ermo:glichen, dass eine Verbindung von einem IPv4-Host auf einer
       IPv4-Insel zu einem IPv6-Host im IPv6-Ozean hergestellt wird.

     * Translator D --- Er wird im fru:hen Stadium des U:bergangs benutzt um
       es zu ermo:glichen, dass eine Verbindung von einem IPv6-Host im
       IPv6-Ozean zu einem IPv4-Host auf einer IPv4-Insel hergestellt wird.

   Ein TCP-Relay-Translator der Kategorie A wird unterstu:tzt. Er wird
   "FAITH" genannt. Wir stellen ebenso einen IP-Header-Translator der
   Kataegorie A zur Verfu:gung (Letzterer ist noch nicht in FreeBSD 4.x
   u:bernommen).

    8.1.3.1. FAITH TCP-Relay-Translator

   Das FAITH-System benutzt mit Hilfe des Kernels den faithd(8) genannten
   TCP-Relay-Daemon. FAITH wird einen IPv6-Adress-Pra:fix reservieren und
   eine TCP-Verbindungen an diesen Pra:fix zum IPv4-Ziel weiterleiten.

   Wenn beispielsweise der IPv6-Pra:fix 2001:0DB8:0200:ffff:: ist und das
   IPv6-Ziel fu:r TCP-Verbindungen 2001:0DB8:0200:ffff::163.221.202.12 ist,
   dann wird die Verbindung an das IPv4-Ziel 163.221.202.12 weitergeleitet.

         IPv4-Ziel-Knoten (163.221.202.12)
           ^
           | IPv4 tcp toward 163.221.202.12
         FAITH-relay dual stack node
           ^
           | IPv6 TCP toward 2001:0DB8:0200:ffff::163.221.202.12
         source IPv6 node

   faithd(8) muss auf FAITH-relay dual stack node aufgerufen werden.

   Fu:r weitere Details siehe src/usr.sbin/faithd/README

  8.1.4. IPsec

   IPsec besteht hauptsa:chlich aus drei Komponenten.

    1. Policy Management

    2. Key Management

    3. AH und ESP Behandlung

    8.1.4.1. Regel Management

   Im Kernel ist experimenteller Kode fu:r Regel-Management implementiert. Es
   gibt zwei Wege eine Sicherheitsregel zu handhaben. Einer ist eine Regel
   fu:r jeden Socket mithilfe von setsockopt(2) zu konfigurieren. Fu:r diesen
   Fall ist die Konfiguration der Regel in ipsec_set_policy(3) beschrieben.
   Der andere Weg ist eine auf einem Kernel-Packet-Filter basierende Regel
   mithilfe der PF_KEY-Schnittstelle mittels setkey(8) zu konfigurieren.

   Der Regeleintrag mit seinen Indices wird nicht sortiert, so dass es sehr
   wichtig ist, wann ein Eintrag hinzugefu:gt wird.

    8.1.4.2. Key Management

   Der in dieser Bibliothek (sys/netkey) implementierte Kode fu:r das key
   management ist eine Eigenentwicklung der PFKEYv2-Implementierung. Er ist
   konform zu RFC2367.

   Die Eigenentwicklung des IKE-Daemons "racoon" ist in der Bibliothek
   (kame/kame/racoon) implementiert. Grundsa:tzlich muss man racoon als
   Da:monprozess laufen lassen, dann setzt man eine Regel auf, die Schlu:ssel
   erwartet (a:hnlich wie ping -P 'out ipsec esp/transport//use'). Der Kernel
   wird den racoon-Da:mon wegen des notwendigen Austauschs der Schlu:ssel
   kontaktieren.

    8.1.4.3. AH- und ESP-Handhabung

   Das IPsec-Modul ist als "hook" in die Standard-IPv4/IPv6-Verarbeitung
   implementiert. Sobald ein Paket gesendet wird, pru:ft ip{,6_output(), ob
   eine ESP/AH-Verarbeitung notwendig ist. Es findet eine U:berpru:fung
   statt, ob eine passende SPD (Security Policy Database) gefunden wurde.
   Wenn ESP/AH beno:tigt wird, dann wird {esp,ah}{4,6}_output() aufgerufen
   und mbuf wird folglich aktualisiert. Wenn ein Paket empfangen wird, dann
   wird {esp,ah}4_input() basierend auf der Protokollnummer aufgerufen, z.B.
   (*inetsw[proto])(). {esp,ah}4_input() entschlu:sselt/pru:ft die
   Authentizita:t des Pakets und entfernt den daisy-chained-Header und das
   Padding des ESP/AH. Es ist sicherer den ESP/AH-Header beim Empfang zu
   entfernen, weil man das empfangene Paket niemals so wie es ist benutzt.

   Mit der Verwendung von ESP/AH wird die effektive
   TCP4/6-Datensegmentgro:sse durch weitere von ESP/AH eingefu:gte
   Daisy-chained-Headers beeinflusst. Unser Kode beru:cksichtigt dies.

   Grundlegende Crypto-Funktionen sind im Verzeichnis "sys/crypto" zu finden.
   ESP/AH-Umformungen sind zusammen mit den Wrapper-Funktionen in
   {esp,ah}_core.c gelistet. Wenn man einige Algorithmen hinzufu:gen mo:chte,
   dann fu:gt man in {esp,ah}_core.c eine Wrapper-Funktion hinzu und tra:gt
   seinen Crypto-Algorithmus in sys/crypto ein.

   Der Tunnel-Modus wird in diesem Release teilweise mit den folgenden
   Restriktionen unterstu:tzt:

     * Der IPsec-Tunnel ist nicht mit der generischen Tunnelschnittstelle
       kombiniert. Man muss sehr vorsichtig sein, weil man sonst eine
       Endlosschleife zwischen ip_output() und tunnelifp->if_output()
       aufbaut. Die Meinungen gehen auseinander, ob es besser ist dies zu
       vereinheitlichen, oder nicht.

     * Die Betrachtung von MTU und des "Don't Fragment"-Bits (IPv4) mu:ssen
       mehr gepru:ft werden, aber grundsa:tzlichen arbeiten sie gut.

     * Das Authentifizierungsmodel fu:r einen AH-Tunnel muss u:berarbeitet
       werden. Man muss eventuell die "policy management engine"
       u:berarbeiten.

    8.1.4.4. Konformita:t zu RFCs und IDs

   Der IPsec-Kode im Kernel ist konform (oder versucht konform zu sein) zu
   den folgenden Standards:

   Die "alte IPsec"-Spezifikation, die in rfc182[5-9].txt dokumentiert ist

   Die "neue IPsec"-Spezifikation, die rfc240[1-6].txt, rfc241[01].txt,
   rfc2451.txt und draft-mcdonald-simple-ipsec-api-01.txt (Der Entwurf ist
   erloschen, aber man kann ihn sich von ftp://ftp.kame.net/pub/internet
   -drafts/ holen) dokumentiert ist (Beachte: Die IKE-Spezifikationen
   rfc241[7-9].txt sind im Userland als "racoon"-IKE-Daemon implementiert).

   Aktuell werden folgende Algorithmen unterstu:tzt:

     * altes IPsec-AH

          * null crypto Pru:fsumme (Kein Dokument, nur fu:r Debug-Zwecke)

          * keyed MD5 mit 128bit crypto Pru:fsumme (rfc1828.txt)

          * keyed SHA1 mit 128bit crypto Pru:fsumme (kein Document)

          * HMAC MD5 mit 128bit crypto Pru:fsumme (rfc2085.txt)

          * HMAC SHA1 mit 128bit crypto Pru:fsumme (kein Dokument)

     * altes IPsec-ESP

          * null encryption (kein Dokument, a:hnlich zu rfc2410.txt)

          * DES-CBC-Modus (rfc1829.txt)

     * neues IPsec-AH

          * null crypto Pru:fsumme (kein Dokument, nur fu:r Debug-Zwecke)

          * keyed MD5 mit 96bit crypto Pru:fsumme (kein Dokument)

          * keyed SHA1 mit 96bit crypto Pru:fsumme (kein Dokument)

          * HMAC MD5 mit 96bit crypto Pru:fsumme (rfc2403.txt)

          * HMAC SHA1 mit 96bit crypto Pru:fsumme (rfc2404.txt)

     * neues IPsec-ESP

          * null encryption (rfc2410.txt)

          * DES-CBC mit abgeleiteter IV
            (draft-ietf-ipsec-ciph-des-derived-01.txt, Entwurf abgelaufen)

          * DES-CBC mit expliziter IV (rfc2405.txt)

          * 3DES-CBC mit expliziter IV (rfc2451.txt)

          * BLOWFISH CBC (rfc2451.txt)

          * CAST128 CBC (rfc2451.txt)

          * RC5 CBC (rfc2451.txt)

          * Jeder Algorithmus kann kombiniert werden mit:

               * ESP-Beglaubigung mit HMAC-MD5(96bit)

               * ESP-Beglaubigung mit HMAC-SHA1(96bit)

   Die folgenden Algorithmen werden NICHT unterstu:tzt:

     * altes IPsec-AH

          * HMAC MD5 mit 128bit crypto Pru:fsumme + 64bit replay prevention
            (rfc2085.txt)

          * keyed SHA1 mit 160bit crypto Pru:fsumme + 32bit padding
            (rfc1852.txt)

   IPsec (im Kernel) und IKE (im Userland als "racoon") wurden bei
   unterschiedlichen Interoperabilita:tstests gepru:ft und es ist bekannt,
   dass es mit vielen anderen Implementierungen gut zusammenarbeitet.
   Ausserdem wurde die IPsec-Implementierung sowie die breite Abdeckung mit
   IPsec-Crypto-Algorithmen, die in den RFCs dokumentiert sind, gepru:ft (es
   werden nur Algorithmen ohne intellektuelle Besitzanspru:che behandelt).

    8.1.4.5. ECN-Betrachtung von IPsec-Tunneln

   ECN-freundliche IPsec-Tunnel werden unterstu:tzt wie es in
   draft-ipsec-ecn-00.txt beschrieben ist.

   Normale IPsec-Tunnel sind in RFC2401 beschrieben. Fu:r eine Kapselung wird
   das IPv4-TOS-Feld (oder das IPv6-Traffic-Class-Feld) vom inneren in den
   a:usseren IP-Header kopiert. Fu:r eine Entkapselung wird der a:ssere
   IP-Header einfach verworfen. Die Entkapselungsregel ist nicht mit ECN
   kompatibel, sobald das ECN-Bit im a:usseren IP-TOS/Traffic-Class-Feld
   verloren geht.

   Um einen IPsec-Tunnel ECN-freundlich zu machen, sollte man die Kapselungs-
   und Entkapselungsprozeduren modifizieren. Dies ist in
   http://www.aciri.org/floyd/papers/draft-ipsec-ecn-00.txt, Kapitel 3,
   beschrieben.

   Die IPsec-Tunnel-Implementierung kann drei Zusta:nde annehmen, indem man
   net.inet.ipsec.ecn (oder net.inet6.ipsec6.ecn) auf diese Werte setzt:

     * RFC2401: Keine Betrachtung von ECN (Sysctl-Wert -1)

     * ECN verboten (Sysctl-Wert 0)

     * ECN erlaubt (Sysctl-Wert 1)

   Beachte, dass dieses Verhalten per-node konfigurierbar ist und nicht
   per-SA (draft-ipsec-ecn-00 mo:chte per-SA Konfiguration).

   Das Verhalten ist wie folgt zusammengefasst (man beachte auch den
   Quelltext fu:r weitere Details):

                 encapsulate                     decapsulate
                 ---                             ---
 RFC2401         kopiere alle TOS-Bits               lo:sche TOS-Bits im a:usseren
                 von innen nach aussen.            (benutze innere TOS-Bits so wie sie sind)

 ECN verboten   kopiere TOS-Bits ausser fu:r ECN    lo:sche TOS-Bits im a:usseren
                 (maskiert mit 0xfc) von innen   (benutze innere TOS-Bits so wie sie sind)
                 nach aussen.  Setze ECN-Bits auf 0.

 ECN erlaubt     kopiere TOS-Bits ausser fu:r ECN    benutze innere TOS-Bits mit einigen A:nderungen.
                 CE (maskiert mit 0xfe) von      Wenn das a:ussere ECN-CE-Bit 1 ist,
                 innen nach aussen.                 setze das ECN-CE-Bit im
                 Setze ECN-CE-Bit auf 0.            Inneren.

   Allgemeine Strategie zur Konfiguration:

     * Wenn beide IPsec-Tunnel-Endpunkte ein ECN-freundliches Verhalten
       beherrschen, dann sollte man besser beide Endpunkte auf "ECN allowed"
       (Sysctl-Wert 1) setzen.

     * Wenn das andere Ende das TOS-Bit sehr strikt handhabt, dann benutzt
       man "RFC2401" (Sysctl-Wert -1).

     * in den anderen Fa:llen benutzt man "ECN verboten" (Sysctl-Wert 0).

   Der Standard ist "ECN verboten" (Sysctl-Wert 0).

   Fu:r weitere Informationen siehe auch:

   http://www.aciri.org/floyd/papers/draft-ipsec-ecn-00.txt, RFC2481
   (Explicit Congestion Notification), src/sys/netinet6/{ah,esp}_input.c

   (Dank gebu:hrt Kenjiro Cho <kjc@csl.sony.co.jp> fu:r seine detailliert
   Analyse)

    8.1.4.6. Interoperabilita:t

   Hier sind einige Plattformen angegeben, die in der Vergangenheit die
   IPsec/IKE-Interoperabilita:t mit dem KAME-Kode getestet haben. Beachte,
   dass beide Enden vielleicht ihre Implementierung vera:ndert haben, deshalb
   sollte man folgende Liste nur fu:r Referenzzwecke benutzen.

   Altiga, Ashley-laurent (vpcom.com), Data Fellows (F-Secure), Ericsson ACC,
   FreeS/WAN, HITACHI, IBM AIX(R), IIJ, Intel, Microsoft(R) Windows NT(R),
   NIST (linux IPsec + plutoplus), Netscreen, OpenBSD, RedCreek, Routerware,
   SSH, Secure Computing, Soliton, Toshiba, VPNet, Yamaha RT100i

                                Teil III. Kernel

   Inhaltsverzeichnis

   9. Einen FreeBSD-Kernel bauen und installieren

                9.1. Einen Kernel auf die "traditionelle" Art und Weise bauen

                9.2. Einen Kernel auf die "neue" Art und Weise bauen

   10. Kernel-Fehlersuche

                10.1. Besorgen eines Speicherauszugs nach einem
                Kernel-Absturz (Kernel-Crash-Dump)

                10.2. Fehlersuche in einem Speicherauszug nach einem
                Kernel-Absturz mit kgdb

                10.3. Fehlersuche in einem Speicherauszug nach einem Absturz
                mit DDD

                10.4. Online-Kernel-Fehlersuche mit DDB

                10.5. Online-Kernel-Fehlersuche mit GDB auf einem entfernten
                System

                10.6. Fehlersuche bei einem Konsolen-Treiber

                10.7. Fehlersuche bei Deadlocks

                10.8. Glossar der Kernel-Optionen zur Fehlersuche

Kapitel 9. Einen FreeBSD-Kernel bauen und installieren

   U:bersetzt von Johann Kois.
   Inhaltsverzeichnis

   9.1. Einen Kernel auf die "traditionelle" Art und Weise bauen

   9.2. Einen Kernel auf die "neue" Art und Weise bauen

   Ein Kernelentwickler muss wissen, wie der Bau eines angepassten Kernels
   funktioniert, da das Debuggen des FreeBSD-Kernels nur durch den Bau eines
   neuen Kernels mo:glich ist. Es gibt zwei Wege, einen angepassten Kernel zu
   bauen:

     * Den "traditionellen" Weg

     * Den "neuen" Weg

  Anmerkung:

   Die folgenden Ausfu:hrungen setzen voraus, dass Sie den Abschnitt
   Erstellen und Installation eines angepassten Kernels des FreeBSD-Handbuchs
   gelesen haben und daher wissen, wie man einen FreeBSD-Kernel baut.

9.1. Einen Kernel auf die "traditionelle" Art und Weise bauen

   Bis FreeBSD 4.X wurde dieser Weg zum Bau eines angepassten Kernels
   empfohlen. Sie ko:nnen Ihren Kernel nach wie vor auf diese Art und Weise
   bauen (anstatt das Target "buildkernel" der Makefiles im Verzeichnis
   /usr/src/ zu verwenden). Dies kann beispielsweise sinnvoll sein, wenn Sie
   am Kernel-Quellcode arbeiten. Haben Sie nur ein oder zwei Optionen der
   Kernelkonfigurationsdatei gea:ndert, ist dieser Weg in der Regel schneller
   als der "neue" Weg. Andererseits kann es aber auch zu unerwarteten Fehlern
   beim Bau des Kernels kommen, wenn Sie Ihren Kernel unter aktuellen
   FreeBSD-Versionen auf diese Art und Weise bauen.

    1. Erzeugen Sie den Kernel-Quellcode mit config(8):

 # /usr/sbin/config MYKERNEL

    2. Wechseln Sie in das Build-Verzeichnis. config(8) gibt den Namen dieses
       Verzeichnisses aus, wenn die Erzeugung des Kernel-Quellcodes im
       vorherigen Schritt erfolgreich abgeschlossen wurde.

 # cd ../compile/MYKERNEL

    3. Kompilieren Sie den neuen Kernel:

 # make depend
 # make

    4. Installieren Sie den neuen Kernel:

 # make install

9.2. Einen Kernel auf die "neue" Art und Weise bauen

   Dieser Weg wird fu:r alle aktuellen FreeBSD-Versionen empfohlen. Lesen Sie
   bitte den Abschnitt Erstellen und Installation eines angepassten Kernels
   des FreeBSD-Handbuchs, wenn Sie Ihren Kernel auf diese Art und Weise bauen
   wollen.

Kapitel 10. Kernel-Fehlersuche

   Contributed by Paul Richards, Jo:rg Wunsch und Robert Watson.
   U:bersetzt von Fabian Ruch.
   Inhaltsverzeichnis

   10.1. Besorgen eines Speicherauszugs nach einem Kernel-Absturz
   (Kernel-Crash-Dump)

   10.2. Fehlersuche in einem Speicherauszug nach einem Kernel-Absturz mit
   kgdb

   10.3. Fehlersuche in einem Speicherauszug nach einem Absturz mit DDD

   10.4. Online-Kernel-Fehlersuche mit DDB

   10.5. Online-Kernel-Fehlersuche mit GDB auf einem entfernten System

   10.6. Fehlersuche bei einem Konsolen-Treiber

   10.7. Fehlersuche bei Deadlocks

   10.8. Glossar der Kernel-Optionen zur Fehlersuche

10.1. Besorgen eines Speicherauszugs nach einem Kernel-Absturz
(Kernel-Crash-Dump)

   Wenn ein Entwicklungs-Kernel (z.B. FreeBSD-CURRENT) wie zum Beispiel ein
   Kernel unter Extrembedingungen (z.B. sehr hohe Belastungsraten (Load),
   eine a:usserst hohe Anzahl an gleichzeitigen Benutzern, Hunderte jail(8)s
   usw.) eingesetzt oder eine neue Funktion oder ein neuer Gera:tetreiber in
   FreeBSD-STABLE verwendet wird (z.B. PAE), tritt manchmal eine Kernel-Panic
   ein. In einem solchen Fall zeigt dieses Kapitel, wie dem Absturz
   nu:tzliche Informationen entnommen werden ko:nnen.

   Bei Kernel-Panics ist ein Neustart unvermeidlich. Nachdem ein System neu
   gestartet wurde, ist der Inhalt des physikalischen Speichers (RAM),
   genauso wie jedes Bit, das sich vor der Panic auf dem Swap-Gera:t befand,
   verloren. Um die Bits im physikalischen Speicher zu erhalten, zieht der
   Kernel Nutzen aus dem Swap-Gera:t als voru:bergehenden Ablageort, wo die
   Bits, welche sich im RAM befinden, auch nach einem Neustart nach einem
   Absturz verfu:gbar sind. Durch diese Vorgehensweise kann ein
   Kernel-Abbild, wenn FreeBSD nach einem Absturz startet, abgezogen und mit
   der Fehlersuche begonnen werden.

  Anmerkung:

   Ein Swap-Gera:t, das als Ausgabegera:t (Dump-Device) konfiguriert wurde,
   verha:lt sich immer noch wie ein Swap-Gera:t. Die Ausgabe auf
   Nicht-Swap-Gera:te (wie zum Beispiel Ba:nder oder CDRWs) wird zur Zeit
   nicht unterstu:tzt. Ein "Swap-Gera:t" ist gleichbedeutend mit einer
   "Swap-Partition".

   Es stehen verschiedene Arten von Speicherabzu:gen zur Verfu:gung:
   komplette Speicherabzu:ge (full memory dumps), welche den gesamten Inhalt
   des physischen Speichers beinhalten, Miniauszu:ge (minidumps), die nur die
   gerade verwendeten Speicherseiten des Kernels enthalten (FreeBSD 6.2 und
   ho:here Versionen) und Textauszu:ge (textdumps), welche geskriptete oder
   Debugger-Ausgaben enthalten (FreeBSD 7.1 und ho:her). Miniauszu:ge sind
   der Standardtyp der Abzu:ge seit FreeBSD 7.0 und fangen in den meisten
   Fa:llen alle no:tigen Informationen ein, die in einem kompletten
   Kernel-Speicherabzug enthalten sind, da die meisten Probleme nur durch den
   Zustand des Kernels isoliert werden ko:nnen.

  10.1.1. Konfigurieren des Ausgabegera:ts

   Bevor der Kernel den Inhalt seines physikalischen Speichers auf einem
   Ausgabegera:t ablegt, muss ein solches konfiguriert werden. Ein
   Ausgabegera:t wird durch Benutzen des dumpon(8)-Befehls festgelegt, um dem
   Kernel mitzuteilen, wohin die Speicherauszu:ge bei einem Kernel-Absturz
   gesichert werden sollen. Das dumpon(8)-Programm muss aufgerufen werden,
   nachdem die Swap-Partition mit swapon(8) konfiguriert wurde. Dies wird
   normalerweise durch Setzen der dumpdev-Variable in rc.conf(5) auf den Pfad
   des Swap-Gera:ts (der empfohlene Weg, um einen Kernel-Speicherauszug zu
   entnehmen) bewerkstelligt, oder u:ber AUTO, um die erste konfigurierte
   Swap-Partition zu verwenden. In HEAD ist die Standardeinstellung fu:r
   dumpdev AUTO und a:nderte sich in den RELENG_*-Zweigen (mit Ausnahme von
   RELENG_7, bei dem AUTO beibehalten wurde) auf NO. In FreeBSD 9.0-RELEASE
   und spa:teren Versionen fragt bsdinstall, ob Speicherauszu:ge fu:r das
   Zielsystem wa:hrend des Installationsvorgangs aktiviert werden sollen.

  Tipp:

   Vergleichen Sie /etc/fstab oder swapinfo(8) fu:r eine Liste der
   Swap-Gera:te.

  Wichtig:

   Stellen Sie sicher, dass das in rc.conf(5) festgelegte dumpdir vor einem
   Kernel-Absturz vorhanden ist.

 # mkdir /var/crash
 # chmod 700 /var/crash

   Denken Sie auch daran, dass der Inhalt von /var/crash heikel ist und sehr
   wahrscheinlich vertrauliche Informationen wie Passwo:rter entha:lt.

  10.1.2. Entnehmen eines Kernel-Speicherauszugs (Kernel-Dump)

   Sobald ein Speicherauszug auf ein Ausgabegera:t geschrieben wurde, muss er
   entnommen werden, bevor das Swap-Gera:t eingeha:ngt wird. Um einen
   Speicherauszug aus einem Ausgabegera:t zu entnehmen, benutzen Sie das
   savecore(8)-Programm. Falls dumpdev in rc.conf(5) gesetzt wurde, wird
   savecore(8) automatisch beim ersten Start in den Multiuser-Modus nach dem
   Absturz und vor dem Einha:ngen des Swap-Gera:ts aufgerufen. Der
   Speicherort des entnommenen Kernels ist im rc.conf(5)-Wert dumpdir,
   standardma:ssig /var/crash, festgelegt und der Dateiname wird vmcore.0
   sein.

   In dem Fall, dass bereits eine Datei mit dem Namen vmcore.0 in /var/crash
   (oder auf was auch immer dumpdir gesetzt ist) vorhanden ist, erho:ht der
   Kernel die angeha:ngte Zahl bei jedem Absturz um eins und verhindert
   damit, dass ein vorhandener vmcore (z.B. vmcore.1) u:berschrieben wird.
   Wa:hrend der Fehlersuche, mo:chten Sie ho:chst wahrscheinlich den vmcore
   mit der ho:chsten Version in /var/crash benutzen, wenn Sie den passenden
   vmcore suchen.

  Tipp:

   Falls Sie einen neuen Kernel testen, aber einen anderen starten mu:ssen,
   um Ihr System wieder in Gang zu bringen, starten Sie es nur in den
   Singleuser-Modus, indem Sie das -s-Flag an der Boot-Eingabeaufforderung
   benutzen, und nehmen dann folgende Schritte vor:

 # fsck -p
 # mount -a -t ufs       # make sure /var/crash is writable
 # savecore /var/crash /dev/ad0s1b
 # exit                  # exit to multi-user

   Dies weist savecore(8) an, einen Kernel-Speicherauszug aus /dev/ad0s1b zu
   entnehmen und den Inhalt in /var/crash abzulegen. Vergessen Sie nicht
   sicherzustellen, dass das Zielverzeichnis /var/crash genug freien
   Speicherplatz fu:r den Speicherauszug zur Verfu:gung hat. Vergessen Sie
   auch nicht, den korrekten Pfad des Swap-Gera:ts anzugeben, da es sehr
   wahrscheinlich anders als /dev/ad0s1b lautet!

10.2. Fehlersuche in einem Speicherauszug nach einem Kernel-Absturz mit kgdb

  Anmerkung:

   Dieser Abschnitt deckt kgdb(1) ab, wie es in FreeBSD 5.3 und spa:ter zu
   finden ist. In fru:heren Versionen muss gdb -k benutzt werden, um einen
   Kernspeicherauszug auszulesen.

   Sobald ein Speicherauszug zur Verfu:gung steht, ist es recht einfach
   nu:tzliche Informationen fu:r einfache Probleme daraus zu bekommen. Bevor
   Sie sich auf die Interna von kgdb(1) stu:rzen, um die Fehler im
   Kernspeicherauszug zu suchen und zu beheben, machen Sie die Debug-Version
   Ihres Kernels (normalerweise kernel.debug genannt) und den Pfad der
   Quelldateien, die zum Bau Ihres Kernels verwendet wurden (normalerweise
   /usr/obj/usr/src/sys/KERNCONF, wobei KERNCONF das in einer
   Kernel-config(5) festgelegte ident ist), ausfindig. Mit diesen beiden
   Informationen kann die Fehlersuche beginnen.

   Um in den Debugger zu gelangen und mit dem Informationserhalt aus dem
   Speicherauszug zu beginnen, sind zumindest folgende Schritte no:tig:

 # cd /usr/obj/usr/src/sys/KERNCONF
 # kgdb kernel.debug /var/crash/vmcore.0

   Sie ko:nnen Fehler im Speicherauszug nach dem Absturz suchen, indem Sie
   die Kernel-Quellen benutzen, genauso wie Sie es bei jedem anderen Programm
   ko:nnen.

   Dieser erste Speicherauszug ist aus einem 5.2-BETA-Kernel und der Absturz
   ist tief im Kernel begru:ndet. Die Ausgabe unten wurde dahingehend
   bearbeitet, dass sie nun Zeilennummern auf der linken Seite einschliesst.
   Diese erste Ablaufverfolgung (Trace) untersucht den Befehlszeiger
   (Instruction-Pointer) und beschafft eine Zuru:ckverfolgung (Back-Trace).
   Die Adresse, die in Zeile 41 fu:r den list-Befehl benutzt wird, ist der
   Befehlszeiger und kann in Zeile 17 gefunden werden. Die meisten Entwickler
   wollen zumindest dies zugesendet bekommen, falls Sie das Problem nicht
   selber untersuchen und beheben ko:nnen. Falls Sie jedoch das Problem
   lo:sen, stellen Sie sicher, dass Ihr Patch seinen Weg in den Quellbaum
   mittels eines Fehlerberichts, den Mailinglisten oder ihres Privilegs, zu
   committen, findet!

  1:# cd /usr/obj/usr/src/sys/KERNCONF
  2:# kgdb kernel.debug /var/crash/vmcore.0
  3:GNU gdb 5.2.1 (FreeBSD)
  4:Copyright 2002 Free Software Foundation, Inc.
  5:GDB is free software, covered by the GNU General Public License, and you are
  6:welcome to change it and/or distribute copies of it under certain conditions.
  7:Type "show copying" to see the conditions.
  8:There is absolutely no warranty for GDB.  Type "show warranty" for details.
  9:This GDB was configured as "i386-undermydesk-freebsd"...
 10:panic: page fault
 11:panic messages:
 12:---
 13:Fatal trap 12: page fault while in kernel mode
 14:cpuid = 0; apic id = 00
 15:fault virtual address   = 0x300
 16:fault code:             = supervisor read, page not present
 17:instruction pointer     = 0x8:0xc0713860
 18:stack pointer           = 0x10:0xdc1d0b70
 19:frame pointer           = 0x10:0xdc1d0b7c
 20:code segment            = base 0x0, limit 0xfffff, type 0x1b
 21:                        = DPL 0, pres 1, def32 1, gran 1
 22:processor eflags        = resume, IOPL = 0
 23:current process         = 14394 (uname)
 24:trap number             = 12
 25:panic: page fault
 26      cpuid = 0;
 27:Stack backtrace:
 28
 29:syncing disks, buffers remaining... 2199 2199 panic: mi_switch: switch in a critical section
 30:cpuid = 0;
 31:Uptime: 2h43m19s
 32:Dumping 255 MB
 33: 16 32 48 64 80 96 112 128 144 160 176 192 208 224 240
 34:---
 35:Reading symbols from /boot/kernel/snd_maestro3.ko...done.
 36:Loaded symbols for /boot/kernel/snd_maestro3.ko
 37:Reading symbols from /boot/kernel/snd_pcm.ko...done.
 38:Loaded symbols for /boot/kernel/snd_pcm.ko
 39:#0  doadump () at /usr/src/sys/kern/kern_shutdown.c:240
 40:240             dumping++;
 41:(kgdb) list *0xc0713860
 42:0xc0713860 is in lapic_ipi_wait (/usr/src/sys/i386/i386/local_apic.c:663).
 43:658                     incr = 0;
 44:659                     delay = 1;
 45:660             } else
 46:661                     incr = 1;
 47:662             for (x = 0; x < delay; x += incr) {
 48:663                     if ((lapic->icr_lo & APIC_DELSTAT_MASK) == APIC_DELSTAT_IDLE)
 49:664                             return (1);
 50:665                     ia32_pause();
 51:666             }
 52:667             return (0);
 53:(kgdb) backtrace
 54:#0  doadump () at /usr/src/sys/kern/kern_shutdown.c:240
 55:#1  0xc055fd9b in boot (howto=260) at /usr/src/sys/kern/kern_shutdown.c:372
 56:#2  0xc056019d in panic () at /usr/src/sys/kern/kern_shutdown.c:550
 57:#3  0xc0567ef5 in mi_switch () at /usr/src/sys/kern/kern_synch.c:470
 58:#4  0xc055fa87 in boot (howto=256) at /usr/src/sys/kern/kern_shutdown.c:312
 59:#5  0xc056019d in panic () at /usr/src/sys/kern/kern_shutdown.c:550
 60:#6  0xc0720c66 in trap_fatal (frame=0xdc1d0b30, eva=0)
 61:    at /usr/src/sys/i386/i386/trap.c:821
 62:#7  0xc07202b3 in trap (frame=
 63:      {tf_fs = -1065484264, tf_es = -1065484272, tf_ds = -1065484272, tf_edi = 1, tf_esi = 0, tf_ebp = -602076292, tf_isp = -602076324, tf_ebx = 0, tf_edx = 0, tf_ecx = 1000000, tf_eax = 243, tf_trapno = 12, tf_err = 0, tf_eip = -1066321824, tf_cs = 8, tf_eflags = 65671, tf_esp = 243, tf_ss = 0})
 64:    at /usr/src/sys/i386/i386/trap.c:250
 65:#8  0xc070c9f8 in calltrap () at {standard input}:94
 66:#9  0xc07139f3 in lapic_ipi_vectored (vector=0, dest=0)
 67:    at /usr/src/sys/i386/i386/local_apic.c:733
 68:#10 0xc0718b23 in ipi_selected (cpus=1, ipi=1)
 69:    at /usr/src/sys/i386/i386/mp_machdep.c:1115
 70:#11 0xc057473e in kseq_notify (ke=0xcc05e360, cpu=0)
 71:    at /usr/src/sys/kern/sched_ule.c:520
 72:#12 0xc0575cad in sched_add (td=0xcbcf5c80)
 73:    at /usr/src/sys/kern/sched_ule.c:1366
 74:#13 0xc05666c6 in setrunqueue (td=0xcc05e360)
 75:    at /usr/src/sys/kern/kern_switch.c:422
 76:#14 0xc05752f4 in sched_wakeup (td=0xcbcf5c80)
 77:    at /usr/src/sys/kern/sched_ule.c:999
 78:#15 0xc056816c in setrunnable (td=0xcbcf5c80)
 79:    at /usr/src/sys/kern/kern_synch.c:570
 80:#16 0xc0567d53 in wakeup (ident=0xcbcf5c80)
 81:    at /usr/src/sys/kern/kern_synch.c:411
 82:#17 0xc05490a8 in exit1 (td=0xcbcf5b40, rv=0)
 83:    at /usr/src/sys/kern/kern_exit.c:509
 84:#18 0xc0548011 in sys_exit () at /usr/src/sys/kern/kern_exit.c:102
 85:#19 0xc0720fd0 in syscall (frame=
 86:      {tf_fs = 47, tf_es = 47, tf_ds = 47, tf_edi = 0, tf_esi = -1, tf_ebp = -1077940712, tf_isp = -602075788, tf_ebx = 672411944, tf_edx = 10, tf_ecx = 672411600, tf_eax = 1, tf_trapno = 12, tf_err = 2, tf_eip = 671899563, tf_cs = 31, tf_eflags = 642, tf_esp = -1077940740, tf_ss = 47})
 87:    at /usr/src/sys/i386/i386/trap.c:1010
 88:#20 0xc070ca4d in Xint0x80_syscall () at {standard input}:136
 89:---Can't read userspace from dump, or kernel process---
 90:(kgdb) quit

   Diese na:chste Ablaufverfolgung ist ein a:lterer Speicherauszug aus
   FreeBSD 2-Zeiten, aber ist komplizierter und zeigt mehr der
   gdb-Funktionen. Lange Zeilen wurden gefaltet, um die Lesbarkeit zu
   verbessern, und die Zeilen wurden zur Verweisung nummeriert. Trotzdem ist
   es eine reale Fehlerverfolgung (Error-Trace), die wa:hrend der Entwicklung
   des pcvt-Konsolentreibers entstanden ist.

  1:Script started on Fri Dec 30 23:15:22 1994
  2:# cd /sys/compile/URIAH
  3:# gdb -k kernel /var/crash/vmcore.1
  4:Reading symbol data from /usr/src/sys/compile/URIAH/kernel
 ...done.
  5:IdlePTD 1f3000
  6:panic: because you said to!
  7:current pcb at 1e3f70
  8:Reading in symbols for ../../i386/i386/machdep.c...done.
  9:(kgdb) backtrace
 10:#0  boot (arghowto=256) (../../i386/i386/machdep.c line 767)
 11:#1  0xf0115159 in panic ()
 12:#2  0xf01955bd in diediedie () (../../i386/i386/machdep.c line 698)
 13:#3  0xf010185e in db_fncall ()
 14:#4  0xf0101586 in db_command (-266509132, -266509516, -267381073)
 15:#5  0xf0101711 in db_command_loop ()
 16:#6  0xf01040a0 in db_trap ()
 17:#7  0xf0192976 in kdb_trap (12, 0, -272630436, -266743723)
 18:#8  0xf019d2eb in trap_fatal (...)
 19:#9  0xf019ce60 in trap_pfault (...)
 20:#10 0xf019cb2f in trap (...)
 21:#11 0xf01932a1 in exception:calltrap ()
 22:#12 0xf0191503 in cnopen (...)
 23:#13 0xf0132c34 in spec_open ()
 24:#14 0xf012d014 in vn_open ()
 25:#15 0xf012a183 in open ()
 26:#16 0xf019d4eb in syscall (...)
 27:(kgdb) up 10
 28:Reading in symbols for ../../i386/i386/trap.c...done.
 29:#10 0xf019cb2f in trap (frame={tf_es = -260440048, tf_ds = 16, tf_\
 30:edi = 3072, tf_esi = -266445372, tf_ebp = -272630356, tf_isp = -27\
 31:2630396, tf_ebx = -266427884, tf_edx = 12, tf_ecx = -266427884, tf\
 32:_eax = 64772224, tf_trapno = 12, tf_err = -272695296, tf_eip = -26\
 33:6672343, tf_cs = -266469368, tf_eflags = 66066, tf_esp = 3072, tf_\
 34:ss = -266427884}) (../../i386/i386/trap.c line 283)
 35:283                             (void) trap_pfault(&frame, FALSE);
 36:(kgdb) frame frame->tf_ebp frame->tf_eip
 37:Reading in symbols for ../../i386/isa/pcvt/pcvt_drv.c...done.
 38:#0  0xf01ae729 in pcopen (dev=3072, flag=3, mode=8192, p=(struct p\
 39:roc *) 0xf07c0c00) (../../i386/isa/pcvt/pcvt_drv.c line 403)
 40:403             return ((*linesw[tp->t_line].l_open)(dev, tp));
 41:(kgdb) list
 42:398
 43:399             tp->t_state |= TS_CARR_ON;
 44:400             tp->t_cflag |= CLOCAL;  /* cannot be a modem (:-) */
 45:401
 46:402     #if PCVT_NETBSD || (PCVT_FREEBSD >= 200)
 47:403             return ((*linesw[tp->t_line].l_open)(dev, tp));
 48:404     #else
 49:405             return ((*linesw[tp->t_line].l_open)(dev, tp, flag));
 50:406     #endif /* PCVT_NETBSD || (PCVT_FREEBSD >= 200) */
 51:407     }
 52:(kgdb) print tp
 53:Reading in symbols for ../../i386/i386/cons.c...done.
 54:$1 = (struct tty *) 0x1bae
 55:(kgdb) print tp->t_line
 56:$2 = 1767990816
 57:(kgdb) up
 58:#1  0xf0191503 in cnopen (dev=0x00000000, flag=3, mode=8192, p=(st\
 59:ruct proc *) 0xf07c0c00) (../../i386/i386/cons.c line 126)
 60:       return ((*cdevsw[major(dev)].d_open)(dev, flag, mode, p));
 61:(kgdb) up
 62:#2  0xf0132c34 in spec_open ()
 63:(kgdb) up
 64:#3  0xf012d014 in vn_open ()
 65:(kgdb) up
 66:#4  0xf012a183 in open ()
 67:(kgdb) up
 68:#5  0xf019d4eb in syscall (frame={tf_es = 39, tf_ds = 39, tf_edi =\
 69: 2158592, tf_esi = 0, tf_ebp = -272638436, tf_isp = -272629788, tf\
 70:_ebx = 7086, tf_edx = 1, tf_ecx = 0, tf_eax = 5, tf_trapno = 582, \
 71:tf_err = 582, tf_eip = 75749, tf_cs = 31, tf_eflags = 582, tf_esp \
 72:= -272638456, tf_ss = 39}) (../../i386/i386/trap.c line 673)
 73:673             error = (*callp->sy_call)(p, args, rval);
 74:(kgdb) up
 75:Initial frame selected; you cannot go up.
 76:(kgdb) quit

   Kommentare zum Skript oben:

   Zeile 6:

           Dies ist ein Speicherauszug, der innerhalb von DDB genommen wurde
           (siehe unten), deswegen der Kommentar zur Panic "because you said
           to!" und die eher lange Stack-Ablaufverfolgung (Stack-Trace); der
           anfa:ngliche Grund fu:r das Starten von DDB war jedoch ein
           Seitenfehler-Trap (Page-Fault-Trap).

   Zeile 20:

           Dies ist die Position der Funktion trap() in der
           Stack-Ablaufverfolgung.

   Zeile 36:

           Erzwingt die Benutzung eines neuen Stack-Frames; dies ist nicht
           mehr notwendig. Die Stack-Frames sollen jetzt an die richtige
           Stelle im Speicher zeigen, selbst im Falle eines Traps. Nach einem
           Blick auf den Code in Zeile 403 ergibt sich mit hoher
           Wahrscheinlichkeit, dass entweder der Zeigerzugriff auf "tp"
           fehlerbehaftet oder der Array-Zugriff unerlaubt war.

   Zeile 52:

           Der Zeiger scheint verda:chtig, aber besitzt zufa:llig eine
           gu:ltige Adresse.

   Zeile 56:

           Jedoch zeigt er offensichtlich auf nichts und so haben wir unseren
           Fehler gefunden! (Fu:r diejenigen, die nichts mit diesem
           speziellen Stu:ck Code anfangen ko:nnen: tp->t_line verweist hier
           auf das Zeilenverhalten (Line-Discipline) des Konsolen-Gera:ts,
           was eine ziemlich kleine Ganzzahl (Integer) sein muss.)

  Tipp:

   Falls Ihr System regelma:ssig abstu:rzt und und Sie bald keinen freien
   Speicherplatz mehr zur Verfu:gung haben, ko:nnte das Lo:schen alter
   vmcore-Dateien in /var/core einen betra:chtlichen Betrag an Speicherplatz
   einsparen.

10.3. Fehlersuche in einem Speicherauszug nach einem Absturz mit DDD

   Die Untersuchung eines Speicherauszugs nach einem Kernel-Absturz mit einem
   grafischen Debugger wie ddd ist auch mo:glich (Sie mu:ssen den
   devel/ddd-Port installieren, um den ddd-Debugger benutzen zu ko:nnen).
   Nehmen Sie die -k mit in die ddd-Kommandozeile auf, die Sie normalerweise
   benutzen wu:rden. Zum Beispiel:

 # ddd --debugger kgdb kernel.debug /var/crash/vmcore.0

   Sie sollten nun in der Lage sein, die Untersuchung des Speicherauszugs
   nach dem Absturz unter Benutzung der grafischen Schnittstelle von ddd
   anzugehen.

10.4. Online-Kernel-Fehlersuche mit DDB

   Wa:hrend kgdb als Offline-Debugger eine Benutzerschnittstelle auf
   ho:chster Ebene bietet, gibt es einige Dinge, die es nicht kann. Die
   wichtigsten sind das Setzen von Breakpoints und das Abarbeiten des
   Kernel-Codes in Einzelschritten (Single-Stepping).

   Falls Sie eine systemnahe Fehlersuche an Ihrem Kernel vorhaben, steht
   Ihnen ein Online-Debugger mit dem Namen DDB zur Verfu:gung. Er erlaubt
   Ihnen das Setzen von Breakpoints, die Abarbeitung von Kernel-Funktionen in
   Einzelschritten, das Untersuchen und Vera:ndern von Kernel-Variablen usw.
   Jedoch hat er keinen Zugriff auf Kernel-Quelldateien, sondern kann nur, im
   Gegensatz zu gdb, welches auf die ganzen Informationen zur Fehlersuche
   zuru:ckgreifen kann, auf globale und statische Symbole zugreifen.

   Um DDB in Ihren Kernel einzubinden, fu:gen Sie die Optionen

 options KDB

 options DDB

   Ihrer Konfigurationsdatei hinzu und bauen Sie den Kernel neu. (Details zur
   Konfiguration des FreeBSD-Kernels finden Sie im FreeBSD-Handbuch).

  Anmerkung:

   Falls Sie eine a:ltere Version des Boot-Blocks haben, ko:nnte es sein,
   dass Ihre Symbole zur Fehlersuche noch nicht einmal geladen werden.
   Aktualisieren Sie den Boot-Block; aktuelle Versionen laden die DDB-Symbole
   automatisch.

   Sobald Ihr Kernel mit DDB startet, gibt es mehrere Wege, um in DDB zu
   gelangen. Der erste und fru:heste Weg ist, das Boot-Flag -d gleich an der
   Boot-Eingabeaufforderung einzugeben. Der Kernel startet dann in den
   Debug-Modus und betritt DDB noch vor jedweder Gera:tesuche. Somit ko:nnen
   Sie Funktionen zur Gera:tesuche/-bereitstellung auf Fehler untersuchen.
   FreeBSD-CURRENT-Benutzer mu:ssen die sechste Option im Boot-Menu:
   auswa:hlen, um an eine Eingabeaufforderung zu gelangen.

   Das zweite Szenario ist der Gang in den Debugger, sobald das System schon
   gestartet ist. Es gibt zwei einfache Wege dies zu erreichen. Falls Sie von
   der Eingabeaufforderung aus in den Debugger gelangen mo:chten, geben Sie
   einfach folgenden Befehl ab:

 # sysctl debug.kdb.enter=1

  Anmerkung:

   Um eine schnelle Panic zu erzwingen, geben Sie das folgende Kommando ein:

 # sysctl debug.kdb.panic=1

   Anderenfalls ko:nnen Sie ein Tastenku:rzel auf der Tastatur benutzen, wenn
   Sie an der Systemkonsole sind. Die Voreinstellung fu:r die
   break-to-debugger-Sequenz ist Ctrl+Alt+ESC. In syscons kann diese Sequenz
   auf eine andere Tastenkombination gelegt werden (remap) und manche der
   verfu:gbaren Tastaturlayouts tun dies, stellen Sie also sicher, dass Sie
   die richtige Sequenz kennen, die benutzt werden soll. Fu:r serielle
   Konsolen ist eine Option vorhanden, die die Benutzung einer Unterbrechung
   der seriellen Verbindung (BREAK) auf der Kommandozeile erlaubt, um in DDB
   zu gelangen (options BREAK_TO_DEBUGGER in der Kernel-Konfigurationsdatei).
   Dies ist jedoch nicht der Standard, da viele serielle Adapter in
   Verwendung sind, die grundlos eine BREAK-Bedingung erzeugen, zum Beispiel
   bei Ziehen des Kabels.

   Die dritte Mo:glichkeit ist, dass jede Panic-Bedingung in DDB springt,
   falls der Kernel hierfu:r konfiguriert ist. Aus diesem Grund ist es nicht
   sinnvoll einen Kernel mit DDB fu:r ein unbeaufsichtigtes System zu
   konfigurieren.

   Um die unbeaufsichtigte Funktionsweise zu erreichen fu:gen Sie:

 options KDB_UNATTENDED

   der Kernel-Konfigurationsdatei hinzu und bauen/installieren Sie den Kernel
   neu.

   Die DDB-Befehle a:hneln grob einigen gdb-Befehlen. Das Erste, das Sie
   vermutlich tun mu:ssen, ist einen Breakpoint zu setzen:

 break function-name address

   Zahlen werden standardma:ssig hexadezimal angegeben, aber um sie von
   Symbolnamen zu unterscheiden, muss Zahlen, die mit den Buchstaben a-f
   beginnen, 0x vorangehen (dies ist fu:r andere Zahlen beliebig). Einfache
   Ausdru:cke sind erlaubt, zum Beispiel: function-name + 0x103.

   Um den Debugger zu verlassen und mit der Abarbeitung fortzufahren, geben
   Sie ein:

 continue

   Um eine Stack-Ablaufverfolgung zu erhalten, benutzen Sie:

 trace

  Anmerkung:

   Beachten Sie, dass wenn Sie DDB mittels einer Schnelltaste betreten, der
   Kernel zurzeit einen Interrupt bereitstellt, sodass die
   Stack-Ablaufverfolgung Ihnen nicht viel nu:tzen ko:nnte.

   Falls Sie einen Breakpoint entfernen mo:chten, benutzen Sie

 del
 del address-expression

   Die erste Form wird direkt, nachdem ein Breakpoint anschlug, angenommen
   und entfernt den aktuellen Breakpoint. Die zweite kann jeden Breakpoint
   lo:schen, aber Sie mu:ssen die genaue Adresse angeben; diese kann bezogen
   werden durch:

 show b

   oder:

 show break

   Um den Kernel in Einzelschritten auszufu:hren, probieren Sie:

 s

   Dies springt in Funktionen, aber Sie ko:nnen DDB veranlassen, diese
   schrittweise zu verfolgen, bis die passende Ru:ckkehranweisung
   (Return-Statement) erreicht ist. Nutzen Sie hierzu:

 n

  Anmerkung:

   Dies ist nicht das gleiche wie die next-Anweisung von gdb; es ist wie gdbs
   finish. Mehrmaliges Dru:cken von n fu:hrt zu einer Fortsetzung.

   Um Daten aus dem Speicher zu untersuchen, benutzen Sie (zum Beispiel):

 x/wx 0xf0133fe0,40
 x/hd db_symtab_space
 x/bc termbuf,10
 x/s stringbuf

   fu:r Word/Halfword/Byte-Zugriff und
   Hexadezimal/Dezimal/Character/String-Ausgabe. Die Zahl nach dem Komma ist
   der Objektza:hler. Um die na:chsten 0x10 Objekte anzuzeigen benutzen Sie
   einfach:

 x ,10

   Gleichermassen benutzen Sie

 x/ia foofunc,10

   um die ersten 0x10 Anweisungen aus foofunc zu zerlegen (disassemble) und
   Sie zusammen mit ihrem Adressabstand (Offset) vom Anfang von foofunc
   auszugeben.

   Um Speicher zu vera:ndern benutzen Sie den Schreibbefehl:

 w/b termbuf 0xa 0xb 0
 w/w 0xf0010030 0 0

   Die Befehlsoption (b/h/w) legt die Gro:sse der Daten fest, die geschrieben
   werden sollen, der erste Ausdruck danach ist die Adresse, wohin
   geschrieben werden soll, und der Rest wird als Daten verarbeitet, die in
   aufeinander folgende Speicherstellen geschrieben werden.

   Falls Sie die aktuellen Register wissen mo:chten, benutzen Sie:

 show reg

   Alternativ ko:nnen Sie den Inhalt eines einzelnen Registers ausgeben mit
   z.B.

 p $eax

   und ihn bearbeiten mit:

 set $eax new-value

   Sollten Sie irgendeine Kernel-Funktion aus DDB heraus aufrufen wollen,
   geben Sie einfach ein:

 call func(arg1, arg2, ...)

   Der Ru:ckgabewert wird ausgegeben.

   Fu:r eine Zusammenfassung aller laufenden Prozesse im Stil von ps(1)
   benutzen Sie:

 ps

   Nun haben Sie herausgefunden, warum Ihr Kernel fehlschla:gt, und mo:chten
   neu starten. Denken Sie daran, dass, abha:ngig von der Schwere
   vorhergehender Sto:rungen, nicht alle Teile des Kernels wie gewohnt
   funktionieren ko:nnten. Fu:hren Sie eine der folgenden Aktionen durch, um
   Ihr System herunterzufahren und neu zu starten:

 panic

   Dies wird Ihren Kernel dazu veranlassen abzustu:rzen, einen Speicherauszug
   abzulegen und neu zu starten, sodass Sie den Kernspeicherauszug spa:ter
   auf ho:herer Ebene mit gdb auswerten ko:nnen. Diesem Befehl muss
   normalerweise eine weitere continue-Anweisung folgen.

 call boot(0)

   Du:rfte ein guter Weg sein, um das laufende System sauber
   herunterzufahren, alle Festplatten mittels sync() zu schreiben und
   schliesslich, in manchen Fa:llen, neu zu starten. Solange die Festplatten-
   und Dateisystemschnittstellen des Kernels nicht bescha:digt sind, ko:nnte
   dies ein guter Weg fu:r ein beinahe sauberes Abschalten sein.

 call cpu_reset()

   Dies ist der letzte Ausweg aus der Katastrophe und kommt beinahe dem
   Dru:cken des Ausschaltknopfes gleich.

   Falls Sie eine kurze Zusammenfassung aller Befehle beno:tigen, geben Sie
   einfach ein:

 help

   Es ist strengstens empfohlen, eine ausgedruckte Version der
   ddb(4)-Manualpage wa:hrend der Fehlersuche neben sich liegen zu haben.
   Denken Sie daran, dass es schwer ist, die Online-Hilfe zu lesen, wa:hrend
   der Ausfu:hrung des Kernels in Einzelschritten.

10.5. Online-Kernel-Fehlersuche mit GDB auf einem entfernten System

   Diese Funktion wird seit FreeBSD 2.2 unterstu:tzt und ist wirklich sehr
   geschickt.

   GDB unterstu:tzt die Fehlersuche von einem entfernten System aus bereits
   einige Zeit. Dies geschieht unter Benutzung eines sehr einfachen
   Protokolls u:ber eine serielle Verbindung. Anders als bei den anderen,
   oben beschriebenen, Vorgehensweisen werden hier zwei Systeme beno:tigt.
   Das eine ist das Hostsystem, welches die Umgebung zur Fehlersuche,
   einschliesslich aller Quellen und einer Kopie der Kernel-Bina:rdatei mit
   allen Symbolen bereitstellt, und das andere das Zielsystem, welches
   einfach nur eine Kopie desselben Kernels ausfu:hrt (ohne die Informationen
   zur Fehlersuche).

   Sie sollten den Kernel im Zweifelsfall mit config -g konfigurieren, DDB in
   die Konfiguration aufnehmen und den Kernel, wie sonst auch, kompilieren.
   Dies ergibt, aufgrund der zusa:tzlichen Informationen zur Fehlersuche,
   eine umfangreiche Bina:rdatei. Kopieren Sie diesen Kernel auf das
   Zielsystem, entfernen Sie die Symbole zur Fehlersuche mit strip -x und
   starten Sie ihn mit der -d-Boot-Option. Stellen Sie die serielle
   Verbindung zwischen dem Zielsystem, welches "flags 80" fu:r dessen
   sio-Gera:t gesetzt hat, und dem Hostsystem, welches die Fehlersuche
   u:bernimmt, her. Nun wechseln Sie auf dem Hostsystem in das Bauverzeichnis
   des Ziel-Kernels und starten gdb:

 % kgdb kernel
 GDB is free software and you are welcome to distribute copies of it
  under certain conditions; type "show copying" to see the conditions.
 There is absolutely no warranty for GDB; type "show warranty" for details.
 GDB 4.16 (i386-unknown-freebsd),
 Copyright 1996 Free Software Foundation, Inc...
 (kgdb)

   Stellen Sie die entfernte Sitzung zur Fehlersuche ein mit (angenommen, der
   erste serielle Port ist in Verwendung):

 (kgdb) target remote /dev/cuaa0

   Jetzt geben Sie auf dem Zielsystem, welches noch vor Beginn der
   Gera:tesuche in DDB gelangt ist, ein:

 Debugger("Boot flags requested debugger")
 Stopped at Debugger+0x35: movb  $0, edata+0x51bc
 db> gdb

   DDB antwortet dann mit:

 Next trap will enter GDB remote protocol mode

   Jedesmal wenn Sie gdb eingeben, wird zwischen dem lokalen DDB und
   entfernten GDB umgeschaltet. Um einen na:chsten Trap sofort zu erzwingen,
   geben Sie einfach s (step) ein. Ihr GDB auf dem Hostsystem erha:lt nun die
   Kontrolle u:ber den Ziel-Kernel:

 Remote debugging using /dev/cuaa0
 Debugger (msg=0xf01b0383 "Boot flags requested debugger")
     at ../../i386/i386/db_interface.c:257
 (kgdb)

   Sie ko:nnen mit dieser Sitzung wie mit jeder anderen GDB-Sitzung umgehen,
   einschliesslich vollem Zugriff auf die Quellen, Starten im gud-Modus
   innerhalb eines Emacs-Fensters (was Ihnen automatische Quelltext-Ausgabe
   in einem weiteren Emacs-Fenster bietet), usw.

10.6. Fehlersuche bei einem Konsolen-Treiber

   Da Sie nunmal einen Konsolen-Treiber beno:tigen, um DDB zu starten, ist
   alles ein wenig komplizierter, sobald der Konsolen-Treiber selbst versagt.
   Sie erinnern sich vielleicht an die Benutzung einer seriellen Konsole
   (entweder durch Vera:ndern des Boot-Blocks oder Eingabe von -h an der
   Boot:-Eingabeaufforderung) und das Anschliessen eines Standard-Terminals
   an Ihren ersten seriellen Port. DDB funktioniert auf jedem konfigurierten
   Konsolen-Treiber, auch auf einer seriellen Konsole.

10.7. Fehlersuche bei Deadlocks

   Sie erleben vielleicht mal sogenannte Deadlocks, wobei ein System
   aufho:rt, nu:tzliche Arbeit zu machen. Um in einer solchen Situation einen
   hilfreichen Fehlerbericht zu liefern, benutzen Sie ddb(4), wie oben
   beschrieben. Ha:ngen Sie die Ausgabe von ps und trace fu:r verda:chtige
   Prozesse an den Bericht an.

   Falls mo:glich, versuchen Sie, weitere Untersuchungen anzustellen. Der
   Empfang der Ausgaben unten ist besonders dann nu:tzlich, wenn Sie den
   Auslo:ser fu:r die Blockade des Systems auf VFS-Ebene vermuten. Fu:gen Sie
   die folgenden Optionen

 makeoptions             DEBUG=-g
         options         INVARIANTS
         options         INVARIANT_SUPPORT
         options         WITNESS
         options         DEBUG_LOCKS
         options         DEBUG_VFS_LOCKS
         options         DIAGNOSTIC

   der Kernel-Konfigurationsdatei hinzu. Wenn die Blockade ausgelo:st wird,
   stellen Sie, zusa:tzlich der Ausgabe vom ps-Befehl, die Informationen aus
   show pcpu, show allpcpu, show locks, show alllocks, show lockedvnods und
   alltrace bereit.

   Um aussagekra:ftige Zuru:ckverfolgungen von in Threads aufgeteilten
   Prozesse zu erhalten, benutzen Sie thread thread-id, um zum Thread-Stack
   zu wechseln und eine Zuru:ckverfolgung mit where anzustellen.

10.8. Glossar der Kernel-Optionen zur Fehlersuche

   Dieser Abschnitt bietet ein kurzes Glossar der zur Kompilierzeit
   verfu:gbaren Kernel-Optionen, die die Fehlersuche unterstu:tzen:

     * options KDB: Kompiliert das Kernel-Debugger-Framework ein. Wird von
       options DDB und options GDB beno:tigt. Kein oder nur geringer
       Leistungs-Overhead. Standardma:ssig wird bei einer Panic der Debugger
       gestartet, anstatt automatisch neu zu starten.

     * options KDB_UNATTENDED: Setzt den Standard des
       debug.debugger_on_panic-sysctl-Werts auf 0, welcher regelt, ob der
       Debugger bei einer Panic gestartet wird. Solange options KDB nicht in
       den Kernel einkompiliert ist, wird bei einer Panic automatisch neu
       gestartet; sobald es in den Kernel einkompiliert ist, wird
       standardma:ssig der Debugger gestartet, solange options KDB_UNATTENDED
       nicht einkompiliert ist. Falls Sie den Kernel-Debugger in den Kernel
       einkompiliert lassen wollen, aber mo:chten, dass das System neu
       startet, wenn Sie nicht zur Stelle sind, um den Debugger zur Diagnose
       zu benutzen, wa:hlen Sie diese Option.

     * options KDB_TRACE: Setzt den Standard des
       debug.trace_on_panic-sysctl-Werts auf 1, welcher regelt, ob der
       Debugger bei einer Panic automatisch eine Stack-Ablaufverfolgung
       ausgibt. Besonders wenn der Kernel mit KDB_UNATTENDED la:uft, kann
       dies hilfreich sein, um grundlegende Informationen zur Fehlersuche auf
       der seriellen oder Firewire-Konsole zu erhalten, wa:hrend immer noch
       zur Wiederherstellung neu gestartet wird.

     * options DDB: Kompiliert die Unterstu:tzung fu:r den Konsolen-Debugger
       DDB ein. Dieser interaktive Debugger la:uft auf was auch immer die
       aktive Konsole des Systems auf niedrigster Ebene ist, dazu geho:ren
       die Video-, serielle und Firewire-Konsole. Er bietet grundlegende,
       eingebaute Mo:glichkeiten zur Fehlersuche wie zum Beispiel das
       Erstellen von Stack-Ablaufverfolgungen, das Auflisten von Prozessen
       und Threads, das Ablegen des Lock-, VM- und Dateisystemstatus und die
       Verwaltung des Kernel-Speichers. DDB beno:tigt keine Software, die auf
       einem zweiten System la:uft, oder die Fa:higkeit, einen
       Kernspeicherauszug oder Kernel-Symbole zur vollen Fehlersuche zu
       erzeugen und bietet detaillierte Fehlerdiagnose des Kernels zur
       Laufzeit. Viele Fehler ko:nnen allein unter Benutzung der DDB-Ausgabe
       untersucht werden. Diese Option ha:ngt von options KDB ab.

     * options GDB: Kompiliert die Unterstu:tzung fu:r den Debugger GDB ein,
       welcher von einem entfernten System aus u:ber ein serielles Kabel oder
       Firewire agieren kann. Wenn der Debugger gestartet ist, kann GDB dazu
       verwendet werden, um Struktur-Inhalte einzusehen,
       Stack-Ablaufverfolgungen zu erzeugen, usw. Bei manchem Kernel-Status
       ist der Zugriff ungeschickter als mit DDB, welcher dazu in der Lage
       ist, nu:tzliche Zusammenfassungen des Kernel-Status automatisch zu
       erstellen wie zum Beispiel das automatische Abgehen der
       Lock-Fehlersuche oder der Strukturen zur Kernel-Speicher-Verwaltung,
       und es wird ein zweites System beno:tigt. Auf der anderen Seite
       verbindet GDB Informationen aus den Kernel-Quellen mit vollsta:ndigen
       Symbolen zur Fehlersuche, erkennt komplette Datenstrukturdefinitionen,
       lokale Variablen und ist in Skripten einsetzbar. Diese Option ha:ngt
       von options KDB ab, ist aber nicht zur Benutzung von GDB auf einem
       Kernel-Kernspeicherauszug no:tig.

     * options BREAK_TO_DEBUGGER, options ALT_BREAK_TO_DEBUGGER: Erlaubt ein
       Abbruch- oder Alternativ-Signal auf der Konsole, um in den Debugger zu
       gelangen. Falls sich das System ohne eine Panic aufha:ngt, ist dies
       ein nu:tzlicher Weg, um den Debugger zu erreichen. Aufgrund der
       aktuellen Verriegelung durch den Kernel ist ein Abbruch-Signal, das
       auf einer seriellen Konsole erzeugt wurde, deutlich
       vertrauenswu:rdiger beim Gelangen in den Debugger und wird allgemein
       empfohlen. Diese Option hat kaum oder keine Auswirkung auf den
       Durchsatz.

     * options INVARIANTS: Kompiliert eine grosse Anzahl an Aussagepru:fungen
       und -tests (Assertion-Checks und -Tests) ein, welche sta:ndig die
       Intaktheit der Kernel-Datenstrukturen und die Invarianten der
       Kernel-Algorithmen pru:fen. Diese Tests ko:nnen aufwendig sein und
       sind deswegen nicht von Anfang an einkompiliert, aber helfen
       nu:tzliches "fail stop"-Verhalten, wobei bestimmte Gruppen nicht
       erwu:nschten Verhaltens den Debugger o:ffnen, bevor Bescha:digungen an
       Kernel-Daten auftreten, bereitzustellen, welches es einfacher macht,
       diese auf Fehler hin zu untersuchen. Die Tests beinhalten Sa:ubern von
       Speicher und use-after-free-Pru:fungen, was eine der bedeutenderen
       Quellen von Overhead ist. Diese Option ha:ngt von options
       INVARIANT_SUPPORT ab.

     * options INVARIANT_SUPPORT: Viele der in options INVARIANTS vorhandenen
       Tests beno:tigen vera:nderte Datenstrukturen und zusa:tzliche
       Kernel-Symbole, die festgelegt werden mu:ssen.

     * options WITNESS: Diese Option aktiviert Verfolgung und Pru:fung von
       Lock-Anforderungen zur Laufzeit und ist als Werkzeug fu:r die
       Deadlock-Diagnose von unscha:tzbarem Wert. WITNESS pflegt ein Diagramm
       mit erworbenen Lock-Antra:gen nach Typ geordnet und pru:ft bei jedem
       Erwerb nach Zyklen (implizit oder explizit). Falls ein Zyklus entdeckt
       wird, werden eine Warnung und eine Stack-Ablaufverfolgung erzeugt und
       als Hinweis, dass ein mo:glicher Deadlock gefunden wurde, auf der
       Konsole ausgegeben. WITNESS wird beno:tigt, um die DDB-Befehle show
       locks, show witness und show alllocks benutzen zu ko:nnen. Diese
       Debug-Option hat einen bedeutenden Leistung-Overhead, welcher ein ein
       wenig durch Benutzung von options WITNESS_SKIPSPIN gemildert werden
       kann. Detaillierte Dokumentation kann in witness(4) gefunden werden.

     * options WITNESS_SKIPSPIN: Deaktiviert die Pru:fung von
       Spinlock-Lock-Anforderungen mit WITNESS zur Laufzeit. Da Spinlocks am
       ha:ufigsten im Scheduler angefordert werden und Scheduler-Ereignisse
       oft auftreten, kann diese Option Systeme, die mit WITNESS laufen,
       merklich beschleunigen. Diese Option ha:ngt von options WITNESS ab.

     * options WITNESS_KDB: Setzt den Standard des
       debug.witness.kdb-sysctl-Werts auf 1, was bewirkt, dass WITNESS den
       Debugger aufruft, sobald eine Lock-Anforderungsverletzung vorliegt,
       anstatt einfach nur eine Warnung auszugeben. Diese Option ha:ngt von
       options WITNESS ab.

     * options SOCKBUF_DEBUG: Fu:hrt umfassende Beschaffenheitspru:fungen in
       Socket-Puffern durch, was nu:tzlich zur Fehlersuche bei Socket-Fehlern
       und Anzeichen fu:r Ressourceblockaden (Race) in Protokollen und
       Gera:tetreibern, die mit Sockets arbeiten, sein kann. Diese Option hat
       bedeutende Auswirkung auf die Netzwerkleistung und kann die
       Zeitverha:ltnisse bei gegenseitiger Ressourceblockade in
       Gera:tetreibern a:ndern.

     * options DEBUG_VFS_LOCKS: Verfolgt Lock-Anforderungs-Einzelheiten bei
       lockmgr/vnode-Locks, was die Menge der Informationen, die von show
       lockdevnods in DDB angezeigt werden, vergro:ssert. Diese Option hat
       messbare Auswirkung auf die Leistung.

     * options DEBUG_MEMGUARD: Ein Ersatz fu:r die Kernel-Speicher-Zuweisung
       durch malloc(9), die das VM-System benutzt, um Lese- und
       Schreibzugriffe auf zugewiesenen Speicher nach der Freigabe zu
       entdecken. Details ko:nnen in memguard(9) gefunden werden. Diese
       Option hat bedeutende Auswirkung auf die Leistung, aber kann sehr
       nu:tzlich bei der Fehlersuche sein, wenn
       Kernel-Speicher-Bescha:digungen durch Fehler verursacht werden.

     * options DIAGNOSTIC: Aktiviert zusa:tzliche, aufwendigere Diagnosetests
       analog zu options INVARIANTS.

                             Teil IV. Architekturen

   Inhaltsverzeichnis

   11. x86-Assembler-Programmierung

                11.1. Synopsis

                11.2. Die Werkzeuge

                11.3. Systemaufrufe

                11.4. Ru:ckgabewerte

                11.5. Portablen Code erzeugen

                11.6. Unser erstes Programm

                11.7. UNIX(R)-Filter schreiben

                11.8. Gepufferte Eingabe und Ausgabe

                11.9. Kommandozeilenparameter

                11.10. Die UNIX(R)-Umgebung

                11.11. Arbeiten mit Dateien

                11.12. One-Pointed Mind

                11.13. Die FPU verwenden

                11.14. Vorsichtsmassnahmen

                11.15. Danksagungen

Kapitel 11. x86-Assembler-Programmierung

   Inhaltsverzeichnis

   11.1. Synopsis

   11.2. Die Werkzeuge

   11.3. Systemaufrufe

   11.4. Ru:ckgabewerte

   11.5. Portablen Code erzeugen

   11.6. Unser erstes Programm

   11.7. UNIX(R)-Filter schreiben

   11.8. Gepufferte Eingabe und Ausgabe

   11.9. Kommandozeilenparameter

   11.10. Die UNIX(R)-Umgebung

   11.11. Arbeiten mit Dateien

   11.12. One-Pointed Mind

   11.13. Die FPU verwenden

   11.14. Vorsichtsmassnahmen

   11.15. Danksagungen

   Dieses Kapitel wurde geschrieben von G. Adam Stanislav.

11.1. Synopsis

   U:bersetzt von Hagen Ku:hl.

   Assembler-Programmierung unter UNIX(R) ist ho:chst undokumentiert. Es wird
   allgemein angenommen, dass niemand sie jemals benutzen will, da
   UNIX(R)-Systeme auf verschiedenen Mikroprozessoren laufen, und man deshalb
   aus Gru:nden der Portabilita:t alles in C schreiben sollte.

   In Wirklichkeit ist die Portabilita:t von C gro:sstenteils ein Mythos.
   Auch C-Programme mu:ssen angepasst werden, wenn man sie von einem UNIX(R)
   auf ein anderes portiert, egal auf welchem Prozessor jedes davon la:uft.
   Typischerweise ist ein solches Programm voller Bedingungen, die
   unterscheiden fu:r welches System es kompiliert wird.

   Sogar wenn wir glauben, dass jede UNIX(R)-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?

   In diesem Kapitel mo:chte ich versuchen zu zeigen, wie man
   Assembler-Sprache verwenden kann, um UNIX(R)-Programme, besonders unter
   FreeBSD, zu schreiben.

   Dieses Kapitel erkla:rt nicht die Grundlagen der Assembler-Sprache. Zu
   diesem Thema gibt es bereits genug Quellen (einen vollsta:ndigen
   Online-Kurs finden Sie in Randall Hydes Art of Assembly Language; oder
   falls Sie ein gedrucktes Buch bevorzugen, ko:nnen Sie einen Blick auf Jeff
   Duntemanns Assembly Language Step-by-Step werfen). Jedenfalls sollte jeder
   Assembler-Programmierer nach diesem Kapitel schnell und effizient
   Programme fu:r FreeBSD schreiben ko:nnen.

   Copyright (c) 2000-2001 G. Adam Stanislav. All rights reserved.

11.2. Die Werkzeuge

   U:bersetzt von Hagen Ku:hl.

  11.2.1. Der Assembler

   Das wichtigste Werkzeug der Assembler-Programmierung ist der Assembler,
   diese Software u:bersetzt Assembler-Sprache in Maschinencode.

   Fu:r FreeBSD stehen zwei verschiedene Assembler zur Verfu:gung. Der erste
   ist as(1), der die traditionelle UNIX(R)-Assembler-Sprache verwendet.
   Dieser ist Teil des Systems.

   Der andere ist /usr/ports/devel/nasm. Dieser benutzt die Intel-Syntax und
   sein Vorteil ist, dass es Code fu: viele Vetriebssysteme u:bersetzen kann.
   Er muss gesondert installiert werden, aber ist vo:llig frei.

   In diesem Kapitel wird die nasm-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.

  11.2.2. Der Linker

   Die Ausgabe des Assemblers muss, genau wie der Code jedes Compilers,
   gebunden werden, um eine ausfu:hrbare Datei zu bilden.

   Der Linker ld(1) ist der Standard und Teil von FreeBSD. Er funktioniert
   mit dem Code beider Assembler.

11.3. Systemaufrufe

   U:bersetzt von Hagen Ku:hl.

  11.3.1. Standard-Aufrufkonvention

   Standardma:ssig benutzt der FreeBSD-Kernel die C-Aufrufkonvention.
   Weiterhin wird, obwohl auf den Kernel durch int 80h zugegriffen wird,
   angenommen, dass das Programm eine Funktion aufruft, die int 80h
   verwendet, anstatt int 80h direkt aufzurufen.

   Diese Konvention ist sehr praktisch und der Microsoft(R)-Konvention von
   MS-DOS(R) u:berlegen. Warum? Weil es die UNIX(R)-Konvention jedem
   Programm, egal in welcher Sprache es geschrieben ist, erlaubt auf den
   Kernel zuzugreifen.

   Ein Assembler-Programm kann das ebenfalls. Beispielsweise ko:nnten wir
   eine Datei o:ffnen:

 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

   Das ist ein sehr sauberer und portabler Programmierstil. Wenn Sie das
   Programm auf ein anderes UNIX(R) portieren, das einen anderen Interrupt
   oder eie andere Art der Parameteru:bergabe verwendet, mu:ssen sie nur die
   Prozedur kernel a:ndern.

   Aber Assembler-Programmierer lieben es Taktzyklen zu schinden. Das obige
   Beispiel beno:tigt eine call/ret-Kombination. Das ko:nnen wir entfernen,
   indem wir einen weiteren Parameter mit push u:bergeben:

 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

   Die Konstante 5, die wir in EAX ablegen, identifiziert die
   Kernel-Funktion, die wir aufrufen. In diesem Fall ist das open.

  11.3.2. Alternative Aufruf-Konvention

   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.

   Linux ist ein UNIX(R)-artiges System. Allerdings verwendet dessen Kernel
   die gleiche Systemaufruf-Konvention, bei der Parameter in Registern
   abgelegt werden, wie MS-DOS(R). Genau wie bei der UNIX(R)-Konvention wird
   die Nummer der Funktion in EAX abgelegt. Allerdings werden die Parameter
   nicht auf den Stack gelegt, sondern in die Register EBX, ECX, EDX, ESI,
   EDI, EBP:

 open:
         mov     eax, 5
         mov     ebx, path
         mov     ecx, flags
         mov     edx, mode
         int     80h

   Diese Konvention hat einen grossen Nachteil gegenu:ber der von UNIX(R),
   was die Assembler-Programmierung angeht: Jedesmal, wenn Sie einen
   Kernel-Aufruf machen, mu:ssen Sie die Register pushen und sie spa:ter
   popen. Das macht Ihren Code unfo:rmiger und langsamer. Dennoch la:sst
   FreeBSD ihnen die Wahl.

   Wenn Sie sich fu:r die Linux-Konvention entscheiden, mu:ssen Sie es das
   System wissen lassen. Nachdem ihr Programm u:bersetzt und gebunden wurde,
   mu:ssen Sie die ausfu:hrbare Datei kennzeichnen:

 %
         brandelf -t Linux
         filename

  11.3.3. Welche Konvention Sie verwenden sollten

   Wenn Sie speziell fu:r FreeBSD programmieren, sollten Sie die
   UNIX(R)-Konvention verwenden: Diese ist schneller, Sie ko:nnen globale
   Variablen in Registern ablegen, Sie mu:ssen die ausfu:hrbare Datei nicht
   kennzeichnen und Sie erzwingen nicht die Installation der Linux-Emulation
   auf dem Zielsystem.

   Wenn Sie portablen Programmcode erzeugen wollen, der auch unter Linux
   funktioniert, wollen Sie den FreeBSD-Nutzern vielleicht dennoch den
   effizientesten Programmcode bieten, der mo:glich ist. Ich werde Ihnen
   zeigen, wie Sie das erreichen ko:nnen, nachdem ich die Grundlagen erkla:rt
   habe.

  11.3.4. Aufruf-Nummern

   Um dem Kernel mitzuteilen welchen Dienst Sie aufrufen, legen Sie dessen
   Nummer in EAX ab. Natu:rlich mu:ssen Sie dazu wissen welche Nummer die
   Richtige ist.

    11.3.4.1. Die Datei syscalls

   Die Nummer der Funktionen sind in der Datei syscalls aufgefu:hrt. Mittels
   locate syscalls finden Sie diese in verschiedenen Formaten, die alle auf
   die gleiche Weise aus syscalls.master erzeugt werden.

   Die Master-Datei fu:r die UNIX(R)-Standard-Aufrufkonvention finden sie
   unter /usr/src/sys/kern/syscalls.master. Falls Sie die andere Konvention,
   die im Linux-Emulations-Modus implementiert ist, verwenden mo:chten, lesen
   Sie bitte /usr/src/sys/i386/linux/syscalls.master.

  Anmerkung:

   FreeBSD und Linux unterscheiden sich nicht nur in den Aufrufkonventionen,
   sie haben teilweise auch verschiedene Nummern fu:r die gleiche Funktion.

   syscalls.master beschreibt, wie der Aufruf gemacht werden muss:

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

   In der ersten Spalte steht die Nummer, die in EAX abgelegt werden muss.

   Die Spalte ganz rechts sagt uns welche Parameter wir pushen mu:ssen. Die
   Reihenfolge ist dabei von rechts nach links.

   Um beispielsweise eine Datei mittels open zu o:ffnen, mu:ssen wir zuerst
   den mode auf den Stack pushen, danach die flags, dann die Adresse an der
   der path gespeichert ist.

11.4. Ru:ckgabewerte

   U:bersetzt von Hagen Ku:hl.

   Ein Systemaufruf wa:re meistens nicht sehr nu:tzlich, wenn er nicht
   irgendeinen Wert zuru:ckgibt: Beispielsweise den Dateideskriptor einer
   geo:ffneten Datei, die Anzahl an Bytes die in einen Puffer gelesen wurde,
   die Systemzeit, etc.

   Ausserdem muss Sie das System informieren, falls ein Fehler auftritt: Wenn
   eine Datei nicht existiert, die Systemressourcen erscho:pft sind, wir ein
   ungu:ltiges Argument u:bergeben haben, etc.

  11.4.1. Manualpages

   Der herko:mmliche Ort, um nach Informationen u:ber verschiedene
   Systemaufrufe unter UNIX(R)-Systemen zu suchen, sind die Manualpages.
   FreeBSD beschreibt seine Systemaufrufe in Abschnitt 2, manchmal auch
   Abschnitt 3.

   In open(2) steht beispielsweise:

     Falls erfolgreich, gibt open() einen nicht negativen Integerwert, als
     Dateideskriptor bezeichnet, zuru:ck. Es gibt -1 im Fehlerfall zuru:ck
     und setzt errno um den Fehler anzuzeigen.

   Ein Assembler-Programmierer, der neu bei UNIX(R) und FreeBSD ist, wird
   sich sofort fragen: Wo finde ich errno und wie erreiche ich es?

  Anmerkung:

   Die Information der Manualpage bezieht sich auf C-Programme. Der
   Assembler-Programmierer beno:tigt zusa:tzliche Informationen.

  11.4.2. Wo sind die Ru:ckgabewerde?

   Leider gilt: Es kommt darauf an... Fu:r die meisten Systemaufrufe liegt er
   in EAX, aber nicht fu:r alle. Eine gute Daumenregel, wenn man zum ersten
   Mal mit einem Systemaufruf arbeitet, ist in EAX nach dem Ru:ckgabewert zu
   suchen. Wenn er nicht dort ist, sind weitere Untersuchungen no:tig.

  Anmerkung:

   Mir ist ein Systemaufruf bekannt, der den Ru:ckgabewert in EDX ablegt:
   SYS_fork Alle anderen mit denen ich bisher gearbeitet habe verwenden EAX.
   Allerdings habe ich noch nicht mit allen gearbeitet.

  Tipp:

   Wenn Sie die Antwort weder hier, noch irgendwo anders finden, studieren
   Sie den Quelltext von libc und sehen sich an, wie es mit dem Kernel
   zusammenarbeitet.

  11.4.3. Wo ist errno?

   Tatsa:chlich, nirgendwo...

   errno ist ein Teil der Sprache C, nicht des UNIX(R)-Kernels. Wenn man
   direkt auf Kernel-Dienste zugreift, wird der Fehlercode in EAX
   zuru:ckgegeben, das selbe Register in dem der Ru:ckgabewert, bei einem
   erfolgreichen Aufruf landet.

   Das macht auch Sinn. Wenn kein Fehler auftritt, gibt es keinen Fehlercode.
   Wenn ein Fehler auftritt, gibt es keinen Ru:ckgabewert. Ein einziges
   Register kann also beides enthalten.

  11.4.4. Feststellen, dass ein Fehler aufgetreten ist

   Wenn Sie die Standard FreeBSD-Aufrufkonvention verwenden wird das carry
   flag gelo:scht wenn der Aufruf erfolgreich ist und gesetzt wenn ein Fehler
   auftritt.

   Wenn Sie den Linux-Emulationsmodus verwenden ist der vorzeichenbehaftete
   Wert in EAX nicht negativ, bei einem erfolgreichen Aufruf. Wenn ein Fehler
   auftritt ist der Wert negativ, also -errno.

11.5. Portablen Code erzeugen

   U:bersetzt von Hagen Ku:hl.

   Portabilita:t ist im Allgemeinen keine Sta:rke der
   Assembler-Programmierung. Dennoch ist es, besonders mit nasm, mo:glich
   Assembler-Programme fu:r verschiedene Plattformen zu schreiben. Ich selbst
   habe bereits Assembler-Bibliotheken geschrieben die auf so
   unterschiedlichen Systemen wie Windows(R) und FreeBSD u:bersetzt werden
   ko:nnen.

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

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

  11.5.1. Mit Funktionsnummern umgehen

   In vielen Fa: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
   abha:ngig von der Zielarchitektur unterschiedlich definieren:

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

  11.5.2. Umgang mit Konventionen

   Sowohl die Aufrufkonvention, als auch die Ru:ckgabewerte (das errno
   Problem) kann man mit Hilfe von Makros lo:sen:

 %ifdef  LINUX

 %macro  system  0
         call    kernel
 %endmacro

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

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

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

         or      eax, eax
         js      .errno
         clc
         ret

 .errno:
         neg     eax
         stc
         ret

 %else

 %macro  system  0
         int     80h
 %endmacro

 %endif

  11.5.3. Umgang mit anderen Portabilita:tsangelegenheiten

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

   In diesem Fa:llen mu:ssen Sie zwei verschiedene Handler fu:r diese
   Systemaufrufe schreiben und bedingte Assemblierung benutzen, um diese zu
   u:bersetzen. Glu:cklicherweise wird der gro:sste Teil Ihres Codes nicht
   den Kernel aufrufen und Sie werden deshalb nur wenige solcher bedingten
   Abschnitte beno:tigen.

  11.5.4. Eine Bibliothek benutzen

   Sie ko:nnen Portabilita:tsprobleme im Hauptteil ihres Codes komplett
   vermeiden, indem Sie eine Bibliothek fu:r Systemaufrufe schreiben.
   Erstellen Sie eine Bibliothek fu:r FreeBSD, eine fu:r Linux und weitere
   fu:r andere Betriebssysteme.

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

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

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

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

 ...

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

 sys.err:
         neg     eax
         stc
         ret

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

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

  11.5.5. Eine Include-Datei verwenden

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

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

  Anmerkung:

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

   Wir ko:nnen unsere system.inc beginnen indem wir die
   Standard-Dateideskriptoren deklarieren:

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

   Als Na:chstes erzeugen wir einen symbolischen Namen fu:r jeden
   Systemaufruf:

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

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

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

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

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

   Letztlich erzeugen wir Makros fu:r jeden Systemaufruf. Diese Argumente
   erwarten keine Argumente.

 %macro  sys.exit        0
         system  SYS_exit
 %endmacro

 %macro  sys.fork        0
         system  SYS_fork
 %endmacro

 %macro  sys.read        0
         system  SYS_read
 %endmacro

 %macro  sys.write       0
         system  SYS_write
 %endmacro

 ; [etc...]

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

11.6. Unser erstes Programm

   U:bersetzt von Hagen Ku:hl.

   Jetzt sind wir bereit fu:r unser erstes Programm, das u:bliche Hello,
   World!

  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

   Hier folgt die Erkla:rung des Programms: Zeile 1 fu:gt die Definitionen
   ein, die Makros und den Code aus system.inc.

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

   Die Zeilen 7 bis 16 enthalten den Code. Beachten Sie bitte, dass FreeBSD
   das Dateiformat elf fu:r diese ausfu:hrbare Datei verwendet, bei dem jedes
   Programm mit dem Label _start beginnt (oder, um genau zu sein, wird dies
   vom Linker erwartet). Diese Label muss global sein.

   Die Zeilen 10 bis 13 weisen das System an hbytes Bytes der Zeichenkette
   hello nach stdout zu schreiben.

   Die Zeilen 15 und 16 weisen das System an das Programm mit dem
   Ru:ckgabewert 0 zu beenden. Der Systemaufruf SYS_exit kehrt niemals
   zuru:ck, somit endet das Programm hier.

  Anmerkung:

   Wenn Sie von MS-DOS(R)-Assembler zu UNIX(R) gekommen sind, sind Sie es
   vielleicht gewohnt direktauf die Video-Hardware zu schreiben. Unter
   FreeBSD mu:ssen Sie sich darum keine Gedanken machen, ebenso bei jeder
   anderen Art von UNIX(R). Soweit es Sie betrifft schreiben Sie in eine
   Datei namens stdout. Das kann der Bildschirm, oder ein telnet-Terminal,
   eine wirkliche Datei, oder die Eingabe eines anderen Programms sein. Es
   liegt beim System herauszufinden, welches davon es tatsa:chlich ist.

  11.6.1. Den Code assemblieren

   Geben Sie den Code (ausser den Zeilennummern) in einen Editor ein und
   speichern Sie ihn in einer Datei namens hello.asm. Um es zu assemblieren
   beno:tigen Sie nasm.

    11.6.1.1. nasm installieren

   Wenn Sie nasm noch nicht installiert haben geben Sie folgendes ein:

 % su
 Password:your root password
 # cd /usr/ports/devel/nasm
 # make install
 # exit
 %

   Sie ko:nnen auch make install clean anstatt make install eingeben, wenn
   Sie den Quelltext von nasm nicht behalten mo:chten.

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

  Anmerkung:

   Wenn es sich bei Ihrem System nicht um FreeBSD handelt, mu:ssen Sie nasm
   von dessen Homepage herunterladen. Sie ko:nnen es aber dennoch verwenden
   um FreeBSD code zu assemblieren.

   Nun ko:nnen Sie den Code assemblieren, binden und ausfu:hren:

 % nasm -f elf hello.asm
 % ld -s -o hello hello.o
 % ./hello
 Hello, World!
 %

11.7. UNIX(R)-Filter schreiben

   U:bersetzt von Hagen Ku:hl.

   Ein ha:ufiger Typ von UNIX(R)-Anwendungen ist ein Filter - ein Programm,
   das Eingaben von stdin liest, sie verarbeitet und das Ergebnis nach stdout
   schreibt.

   In diesem Kapitel mo:chten wir einen einfachen Filter entwickeln und
   lernen, wie wir von stdin lesen und nach stdout schreiben. Dieser Filter
   soll jedes Byte seiner Eingabe in eine hexadezimale Zahl gefolgt von einem
   Leerzeichen umwandeln.

 %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

   Im Datenabschnitt erzeugen wir ein Array mit Namen hex. Es entha:lt die 16
   hexadezimalen Ziffern in aufsteigender Reihenfolge. Diesem Array folgt ein
   Puffer, den wir sowohl fu:r die Ein- als auch fu:r die Ausgabe verwenden.
   Die ersten beiden Bytes dieses Puffers werden am Anfang auf 0 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.

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

   Um das Byte zu lesen, bitten wir das System ein Byte von stdin zu lesen
   und speichern es im ersten Byte von buffer. Das System gibt die Anzahl an
   Bytes, die gelesen wurden, in EAX zuru:ck. Diese wird 1 sein, wenn eine
   Eingabe empfangen wird und 0, wenn keine Eingabedaten mehr verfu:gbar
   sind. Deshalb u:berpru:fen wir den Wert von EAX. Wenn dieser 0 ist,
   springen wir zu .done, ansonsten fahren wir fort.

  Anmerkung:

   Zu Gunsten der Einfachheit ignorieren wir hier die Mo:glichkeit eines
   Fehlers.

   Die Umwandlungsroutine in eine Hexadezimalzahl liest das Byte aus buffer
   in EAX, oder genaugenommen nur in AL, wobei die u:brigen Bits von EAX auf
   null gesetzt werden. Ausserdem kopieren wir das Byte nach EDX, da wir die
   oberen vier Bits (Nibble) getrennt von den unteren vier Bits umwandeln
   mu:ssen. Das Ergebnis speichern wir in den ersten beiden Bytes des
   Puffers.

   Als Na:chstes bitten wir das System die drei Bytes in den Puffer zu
   schreiben, also die zwei hexadezimalen Ziffern und das Leerzeichen nach
   stdout. Danach springen wir wieder an den Anfang des Programms und
   verarbeiten das na:chste Byte.

   Wenn die gesamte Eingabe verarbeitet ist, bitten wie das System unser
   Programm zu beenden und null zuru:ckzuliefern, welches traditionell die
   Bedeutung hat, dass unser Programm erfolgreich war.

   Fahren Sie fort und speichern Sie den Code in eine Datei namens hex.asm.
   Geben Sie danach folgendes ein (^D bedeutet, dass Sie die Steuerungstaste
   dru:cken und dann D eingeben, wa:hrend Sie Steuerung gedru:ckt halten):

 % nasm -f elf hex.asm
 % ld -s -o hex hex.o
 % ./hex
 Hello, World!
 48 65 6C 6C 6F 2C 20 57 6F 72 6C 64 21 0A Here I come!
 48 65 72 65 20 49 20 63 6F 6D 65 21 0A ^D %

  Anmerkung:

   Wenn Sie von MS-DOS(R) zu UNIX(R) wechseln, wundern Sie sich vielleicht,
   warum jede Zeile mit 0A an Stelle von 0D 0A endet. Das liegt daran, dass
   UNIX(R) nicht die CR/LF-Konvention, sondern die "new line"-Konvention
   verwendet, welches hexadezimal als 0A dargestellt wird.

   Ko: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 ko:nnen wir unser
   Programm anpassen um einen Zeilenumbruch an Stelle eines Leerzeichens nach
   jedem 0A auszugeben:

 %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

   Wir haben das Leerzeichen im Register CL abgelegt. Das ko:nnen wir
   bedenkenlos tun, da UNIX(R)-Systemaufrufe im Gegensatz zu denen von
   Microsoft(R) Windows(R) keine Werte von Registern a:ndern in denen sie
   keine Werte zuru:ckliefern.

   Das bedeutet, dass wir CL nur einmal setzen mu:ssen. Dafu:r haben wir ein
   neues Label .loop eingefu:gt, zu dem wir an Stelle von _start springen, um
   das na:chste Byte einzulesen. Ausserdem haben wir das Label .hex
   eingefu:gt, somit ko:nnen wir wahlweise ein Leerzeichen oder einen
   Zeilenumbruch im dritten Byte von buffer ablegen.

   Nachdem Sie hex.asm entsprechend der Neuerungen gea:ndert haben, geben Sie
   Folgendes ein:

 % nasm -f elf hex.asm
 % ld -s -o hex hex.o
 % ./hex
 Hello, World!
 48 65 6C 6C 6F 2C 20 57 6F 72 6C 64 21 0A
 Here I come!
 48 65 72 65 20 49 20 63 6F 6D 65 21 0A
 ^D %

   Das sieht doch schon besser aus. Aber der Code ist ziemlich ineffizient!
   Wir fu:hren fu:r jeden einzelne Byte zweimal einen Systemaufruf aus (einen
   zum Lesen und einen um es in die Ausgabe zu schreiben).

11.8. Gepufferte Eingabe und Ausgabe

   U:bersetzt von Hagen Ku:hl.

   Wir ko:nnen die Effizienz unseres Codes erho: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 fu:r Byte aus dem
   Puffer.

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

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

 %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

   Als dritten Abschnitt im Quelltext haben wir .bss. Dieser Abschnitt wird
   nicht in unsere ausfu:hrbare Datei eingebunden und kann daher nicht
   initialisiert werden. Wir verwenden resb anstelle von db. Dieses
   reserviert einfach die angeforderte Menge an uninitialisiertem Speicher zu
   unserer Verwendung.

   Wir nutzen, die Tatsache, dass das System die Register nicht vera:ndert:
   Wir benutzen Register, wo wir anderenfalls globale Variablen im Abschnitt
   .data verwenden mu:ssten. Das ist auch der Grund, warum die
   UNIX(R)-Konvention, Parameter auf dem Stack zu u:bergeben, der von
   Microsoft, hierfu:r Register zu verwenden, u:berlegen ist: Wir ko:nnen
   Register fu:r unsere eigenen Zwecke verwenden.

   Wir verwenden EDI und ESI als Zeiger auf das na:chste zu lesende oder
   schreibende Byte. Wir verwenden EBX und ECX, um die Anzahl der Bytes in
   den beiden Puffern zu za:hlen, damit wir wissen, wann wir die Ausgabe an
   das System u:bergeben, oder neue Eingabe vom System entgegen nehmen
   mu:ssen.

   Lassen Sie uns sehen, wie es funktioniert:

 % nasm -f elf hex.asm
 % ld -s -o hex hex.o
 % ./hex
 Hello, World!
 Here I come!
 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
 ^D %

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

 %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

   Lassen Sie uns jetzt einen Blick darauf werfen, wie es funktioniert.

 % nasm -f elf hex.asm
 % ld -s -o hex hex.o
 % ./hex
 Hello, World!
 48 65 6C 6C 6F 2C 20 57 6F 72 6C 64 21 0A
 Here I come!
 48 65 72 65 20 49 20 63 6F 6D 65 21 0A
 ^D %

   Nicht schlecht fu:r eine 644 Byte grosse Bina:rdatei, oder?

  Anmerkung:

   Dieser Ansatz fu:r gepufferte Ein- und Ausgabe entha:lt eine Gefahr, auf
   die ich im Abschnitt Die dunkle Seite des Buffering eingehen werde.

  11.8.1. Ein Zeichen ungelesen machen

  Warnung:

   Das ist vielleicht ein etwas fortgeschrittenes Thema, das vor allem fu:r
   Programmierer interessant ist, die mit der Theorie von Compilern vertraut
   sind. Wenn Sie wollen, ko:nnen Sie zum na:chsten Abschnitt springen und
   das hier vielleicht spa:ter lesen.

   Unser Beispielprogramm beno:tigt es zwar nicht, aber etwas
   anspruchsvollere Filter mu:ssen ha:ufig vorausschauen. Mit anderen Worten,
   sie mu:ssen wissen was das na:chste Zeichen ist (oder sogar mehrere
   Zeichen). Wenn das na:chste Zeichen einen bestimmten Wert hat, ist es Teil
   des aktuellen Tokens, ansonsten nicht.

   Zum Beispiel ko:nnten Sie den Eingabestrom fu: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.

   Das fu:hrt uns zu einem interessanten Problem: Wie kann man ein Zeichen
   zuru:ck in den Eingabestrom geben, damit es spa:ter noch einmal gelesen
   werden kann?

   Eine mo:gliche Lo:sung ist, das Zeichen in einer Variable zu speichern und
   ein Flag zu setzen. Wir ko:nnen getchar so anpassen, dass es das Flag
   u:berpru:ft und, wenn es gesetzt ist, das Byte aus der Variable anstatt
   dem Eingabepuffer liest und das Flag zuru:ck setzt. Aber natu:rlich macht
   uns das langsamer.

   Die Sprache C hat eine Funktion ungetc() fu:r genau diesen Zweck. Gibt es
   einen schnellen Weg, diese in unserem Code zu implementieren? Ich mo:chte
   Sie bitten nach oben zu scrollen und sich die Prozedur getchar anzusehen
   und zu versuchen eine scho:ne und schnelle Lo:sung zu finden, bevor Sie
   den na:chsten Absatz lesen. Kommen Sie danach hierher zuru:ck und schauen
   sich meine Lo:sung an.

   Der Schlu:ssel dazu ein Zeichen an den Eingabestrom zuru:ckzugeben, liegt
   darin, wie wir das Zeichen bekommen:

   Als erstes u:berpru:fen wir, ob der Puffer leer ist, indem wir den Wert
   von EBX testen. Wenn er null ist, rufen wir die Prozedur read auf.

   Wenn ein Zeichen bereit ist verwenden wir lodsb, dann verringern wir den
   Wert von EBX. Die Anweisung lodsb ist letztendlich identisch mit:

         mov     al, [esi]
           inc   esi

   Das Byte, welches wir abgerufen haben, verbleibt im Puffer bis read zum
   na:chsten Mal aufgerufen wird. Wir wissen nicht wann das passiert, aber
   wir wissen, dass es nicht vor dem na:chsten Aufruf von getchar passiert.
   Daher ist alles was wir tun mu:ssen um das Byte in den Strom
   "zuru:ckzugeben" ist den Wert von ESI zu verringern und den von EBX zu
   erho:hen:

 ungetc:
           dec   esi
           inc   ebx
           ret

   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 ungetc mehrmals hintereinander aufrufen, wird es meistens
   funktionieren, aber nicht immer (und es wird ein schwieriger Debug).
   Warum?

   Solange getchar read nicht aufrufen muss, befinden sich alle im Voraus
   gelesenen Bytes noch im Puffer und ungetc arbeitet fehlerfrei. Aber sobald
   getchar read aufruft vera:ndert sich der Inhalt des Puffers.

   Wir ko:nnen uns immer darauf verlassen, dass ungetc auf dem zuletzt mit
   getchar gelesenen Zeichen korrekt arbeitet, aber nicht auf irgendetwas,
   das davor gelesen wurde.

   Wenn Ihr Programm mehr als ein Byte im Voraus lesen soll, haben Sie
   mindestens zwei Mo:glichkeiten:

   Die einfachste Lo:sung ist, Ihr Programm so zu a:ndern, dass es immer nur
   ein Byte im Voraus liest, wenn das mo:glich ist.

   Wenn Sie diese Mo:glichkeit nicht haben, bestimmen Sie zuerst die maximale
   Anzahl an Zeichen, die Ihr Programm auf einmal an den Eingabestrom
   zuru:ckgeben muss. Erho:hen Sie diesen Wert leicht, nur um sicherzugehen,
   vorzugsweise auf ein Vielfaches von 16-damit er sich scho:n ausrichtet.
   Dann passen Sie den .bss Abschnitt Ihres Codes an und erzeugen einen
   kleinen Reserver-Puffer, direkt vor ihrem Eingabepuffer, in etwa so:

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

   Ausserdem mu:ssen Sie ungetc anpassen, sodass es den Wert des Bytes, das
   zuru:ckgegeben werden soll, in AL u:bergibt:

 ungetc:
           dec   esi
           inc   ebx
           mov   [esi], al
           ret

   Mit dieser A:nderung ko:nnen Sie sicher ungetc bis zu 17 Mal
   hintereinander gqapaufrufen (der erste Aufruf erfolgt noch im Puffer, die
   anderen 16 entweder im Puffer oder in der Reserve).

11.9. Kommandozeilenparameter

   U:bersetzt von Fabian Ruch.

   Unser hex-Programm wird nu:tzlicher, wenn es die Dateinamen der Ein- und
   Ausgabedatei u:ber die Kommandozeile einlesen kann, d.h., wenn es
   Kommandozeilenparameter verarbeiten kann. Aber... Wo sind die?

   Bevor ein UNIX(R)-System ein Programm ausfu:hrt, legt es einige Daten auf
   dem Stack ab (push) und springt dann an das _start-Label des Programms.
   Ja, ich sagte springen, nicht aufrufen. Das bedeutet, dass auf die Daten
   zugegriffen werden kann, indem [esp+offset] ausgelesen wird oder die Daten
   einfach vom Stack genommen werden (pop).

   Der Wert ganz oben auf dem Stack entha:lt die Zahl der
   Kommandozeilenparameter. Er wird traditionell argc wie "argument count"
   genannt.

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

   Der argv-Liste folgt ein NULL-Zeiger, was einfach eine 0 ist. Es gibt noch
   mehr, aber dies ist erst einmal genug fu:r unsere Zwecke.

  Anmerkung:

   Falls Sie von der MS-DOS(R)-Programmierumgebung kommen, ist der gro:sste
   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 ko:nnen.

   Ausgeru:stet mit diesen Kenntnissen, sind wir beinahe bereit fu:r eine
   weitere Version von hex.asm. Zuerst mu:ssen wir jedoch noch ein paar
   Zeilen zu system.inc hinzufu:gen:

   Erstens beno:tigen wir zwei neue Eintra:ge in unserer Liste mit den
   Systemaufrufnummern:

 %define SYS_open        5
 %define SYS_close       6

   Zweitens fu:gen wir zwei neue Makros am Ende der Datei ein:

 %macro  sys.open        0
         system  SYS_open
 %endmacro

 %macro  sys.close       0
         system  SYS_close
 %endmacro

   Und hier ist schliesslich unser vera:nderter Quelltext:

 %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

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

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

   Der .text-Abschnitt beginnt nun mit einer einfachen Fehlerbehandlung,
   welche nur das Programm mit einem Ru:ckgabewert von 1 beendet. Die
   Fehlerbehandlung befindet sich vor _start, sodass wir in geringer
   Entfernung von der Stelle sind, an der der Fehler auftritt.

   Selbstversta:ndlich beginnt die Programmausfu:hrung immer noch bei _start.
   Zuerst entfernen wir argc und argv[0] vom Stack: Sie sind fu:r uns nicht
   von Interesse (sprich, in diesem Programm).

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

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

   Der Rest des Codes ist derselbe wie vorher, ausser dem Schliessen der Ein-
   und Ausgabedatei vor dem Verlassen des Programms und, wie bereits
   erwa:hnt, die Benutzung von [fd.in] und [fd.out].

   Unsere Bina:rdatei ist nun kolossale 768 Bytes gross.

   Ko:nnen wir das Programm immer noch verbessern? Natu:rlich! Jedes Programm
   kann verbessert werden. Hier finden sich einige Ideen, was wir tun
   ko:nnten:

     * Die Fehlerbehandlung eine Warnung auf stderr ausgeben lassen.

     * Den Lese- und Schreibfunkionen eine Fehlerbehandlung hinzufu:gen.

     * Schliessen von stdin, sobald wir eine Eingabedatei o:ffnen, von
       stdout, sobald wir eine Ausgabedatei o:ffnen.

     * Hinzufu:gen von Kommandozeilenschaltern wie zum Beispiel -i und -o,
       sodass wir die Ein- und Ausgabedatei in irgendeiner Reihenfolge
       angeben oder vielleicht von stdin lesen und in eine Datei schreiben
       ko:nnen.

     * Ausgeben einer Gebrauchsanweisung, falls die Kommandozeilenparameter
       fehlerhaft sind.

   Ich beabsichtige, diese Verbesserungen dem Leser als U:bung zu
   hinterlassen: Sie wissen bereits alles, das Sie wissen mu:ssen, um die
   Verbesserungen durchzufu:hren.

11.10. Die UNIX(R)-Umgebung

   U:bersetzt von Fabian Ruch.

   Ein entscheidendes Konzept hinter UNIX(R) ist die Umgebung, die durch
   Umgebungsvariablen festgelegt wird. Manche werden vom System gesetzt,
   andere von Ihnen und wieder andere von der shell oder irgendeinem
   Programm, das ein anderes la:dt.

  11.10.1. Umgebungsvariablen herausfinden

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

   Der Aufbau von env entspricht dem von argv, eine Liste von
   Speicheradressen gefolgt von NULL (0). In diesem Fall gibt es kein
   "envc"-wir finden das Ende heraus, indem wir nach dem letzten NULL suchen.

   Die Variablen liegen normalerweise in der Form name=value vor, aber
   manchmal kann der =value-Teil fehlen. Wir mu:ssen diese Mo:glichkeit in
   Betracht ziehen.

  11.10.2. webvars

   Ich ko:nnte Ihnen einfach etwas Code zeigen, der die Umgebung in der Art
   vom UNIX(R)-Befehl env ausgibt. Aber ich dachte, dass es interessanter
   sei, ein einfaches CGI-Werkzeug in Assembler zu schreiben.

    11.10.2.1. CGI: Ein kurzer U:berblick

   Ich habe eine detaillierte CGI-Anleitung auf meiner Webseite, aber hier
   ist ein sehr kurzer U:berblick u:ber CGI:

     * Der Webserver kommuniziert mit dem CGI-Programm, indem er
       Umgebungsvariablen setzt.

     * Das CGI-Programm schreibt seine Ausgabe auf stdout. Der Webserver
       liest von da.

     * Die Ausgabe muss mit einem HTTP-Kopfteil gefolgt von zwei Leerzeilen
       beginnen.

     * Das Programm gibt dann den HTML-Code oder was fu:r einen Datentyp es
       auch immer verarbeitet aus.

     * Anmerkung:

       Wa:hrend bestimmte Umgebungsvariablen Standardnamen benutzen,
       unterscheiden sich andere, abha:ngig vom Webserver. Dies macht webvars
       zu einem recht nu:tzlichen Werkzeug.

    11.10.2.2. Der Code

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

   Nun der Code. Ich habe Kommentare und Erkla:rungen direkt in den Code
   eingefu:gt:

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

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

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

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

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

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

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

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

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

         push    ecx
         push    esi
         push    dword stdout
         sys.write

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

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

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

         mov     ebx, undeflen
         mov     edi, undef

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

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

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

         ; Get the next variable
         jmp     .loop

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

         ; Return success
         push    dword 0
         sys.exit

   Dieser Code erzeugt eine 1.396-Byte grosse Bina:rdatei. Das meiste davon
   sind Daten, d.h., die HTML-Auszeichnung, die wir versenden mu:ssen.

   Assemblieren Sie es wie immer:

 % nasm -f elf webvars.asm
 % ld -s -o webvars webvars.o

   Um es zu benutzen, mu:ssen Sie webvars auf Ihren Webserver hochladen.
   Abha:ngig von Ihrer Webserver-Konfiguration, mu:ssen Sie es vielleicht in
   einem speziellen cgi-bin-Verzeichnis ablegen oder es mit einer
   .cgi-Dateierweiterung versehen.

   Schliesslich beno:tigen Sie Ihren Webbrowser, um sich die Ausgabe
   anzusehen. Um die Ausgabe auf meinem Webserver zu sehen, gehen Sie bitte
   auf http://www.int80h.org/webvars/. Falls Sie neugierig sind, welche
   zusa:tzlichen Variablen in einem passwortgeschu:tzten Webverzeichnis
   vorhanden sind, gehen Sie auf http://www.int80h.org/private/ unter
   Benutzung des Benutzernamens asm und des Passworts programmer.

11.11. Arbeiten mit Dateien

   U:bersetzt von Paul Keller und Fabian Borschel.

   Wir haben bereits einfache Arbeiten mit Dateien gemacht: Wir wissen wie
   wir sie o:ffnen und schliessen, oder wie man sie mit Hilfe von Buffern
   liest und schreibt. Aber UNIX(R) bietet viel mehr Funktionalita:t wenn es
   um Dateien geht. Wir werden einige von ihnen in dieser Sektion untersuchen
   und dann mit einem netten Datei Konvertierungs Werkzeug abschliessen.

   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.

   Eines der ersten Programme die ich fu:r UNIX(R) schrieb war tuc, ein
   Text-Zu-UNIX(R) Datei Konvertierer. Es konvertiert eine Text Datei von
   einem anderen Betriebssystem zu einer UNIX(R) Text Datei. Mit anderen
   Worten, es a:ndert die verschiedenen Arten von Zeilen Begrenzungen zu der
   Zeilen Begrenzungs Konvention von UNIX(R). Es speichert die Ausgabe in
   einer anderen Datei. Optional konvertiert es eine UNIX(R) Text Datei zu
   einer DOS Text Datei.

   Ich habe tuc sehr oft benutzt, aber nur von irgendeinem anderen OS nach
   UNIX(R) zu konvertieren, niemals anders herum. Ich habe mir immer
   gewu:nscht das die Datei einfach u:berschrieben wird anstatt das ich die
   Ausgabe in eine andere Datei senden muss. Meistens, habe ich diesen Befehl
   verwendet:

 % tuc myfile tempfile
 % mv tempfile myfile

   Es wa:re scho: ein ftuc zu haben, also, fast tuc, und es so zu benutzen:

 % ftuc myfile

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

   Auf erste Sicht, ist so eine Datei Konvertierung sehr simpel: Alles was du
   zu tun hast, ist die Wagenru:ckla:ufe zu entfernen, richtig?

   Wenn du mit ja geantwortet hast, denk nochmal daru:ber nach: Dieses
   Vorgehen wird die meiste Zeit funktionieren (zumindest mit MSDOS Text
   Dateien), aber gelegentlich fehlschlagen.

   Das Problem ist das nicht alle UNIX(R) Text Dateien ihre Zeilen mit einer
   Wagen Ru:cklauf / Zeilenvorschub Sequenz beenden. Manche benutzen
   Wagenru:cklauf ohne Zeilenvorschub. Andere kombinieren mehrere leere
   Zeilen in einen einzigen Wagenru:cklauf gefolgt von mehreren
   Zeilenvorschu:ben. Und so weiter.

   Ein Text Datei Konvertierer muss dann also in der Lage sein mit allen
   mo:glichen Zeilenenden umzugehen:

     * Wagenru:cklauf / Zeilenvorschub

     * Wagenru:cklauf

     * Zeilenvorschub / Wagenru:cklauf

     * Zeilenvorschub

   Es sollte ausserdem in der Lage sein mit Dateien umzugehen die irgendeine
   Art von Kombination der oben stehenden Mo:glichkeiten verwendet. (z.B.,
   Wagenru:cklauf gefolgt von mehreren Zeilenvorschu:ben).

  11.11.1. Endlicher Zustandsautomat

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

   Aus diesem Grund, ist jede Software eigentlich nur eine Sequenz von Status
   Anweisungen fu:r den Mikroprozessor. Dennoch, ist das Konzept eines
   Endlichen Zustandsautomaten auch im Software Design sehr hilfreich.

   Unser Text Datei Konvertierer kann als Endlicher Zustandsautomat mit 3
   mo:glichen Stati desgined werden. Wir ko:nnten diese von 0-2 benennen,
   aber es wird uns das Leben leichter machen wenn wir ihnen symbolische
   Namen geben:

     * ordinary

     * cr

     * lf

   Unser Programm wird in dem ordinary Status starten. Wa:hrend dieses
   Status, ha:ngt die Aktion des Programms von seiner Eingabe wie folgt ab:

     * Wenn die Eingabe etwas anderes als ein Wagenru:cklauf oder einem
       Zeilenvorschub ist, wird die Eingabe einfach nur an die Ausgabe
       geschickt. Der Status bleibt unvera:ndert.

     * Wenn die Eingabe ein Wagenru:cklauf ist, wird der Status auf cr
       gesetzt. Die Eingabe wird dann verworfen, d.h., es entsteht keine
       Ausgabe.

     * Wenn die Eingabe ein Zeilenvorschub ist, wird der Status auf lf
       gesetzt. Die Eingabe wird dann verworfen.

   Wann immer wir in dem cr Status sind, ist das weil die letzte Eingabe ein
   Wagenru:cklauf war, welcher nicht verarbeitet wurde. Was unsere Software
   in diesem Status macht ha:ngt von der aktuellen Eingabe ab:

     * Wenn die Eingabe irgendetwas anderes als ein Wagenru:cklauf oder ein
       Zeilenvorschub ist, dann gib einen Zeilenvorschub aus, dann gib die
       Eingabe aus und dann a:ndere den Status zu ordinary.

     * Wenn die Eingabe ein Wagenru:cklauf ist, haben wir zwei (oder mehr)
       Wagenru:ckla:ufe in einer Reihe. Wir verwerfen die Eingabe, wir geben
       einen Zeilenvorschub aus und lassen den Status unvera:ndert.

     * Wenn die Eingabe ein Zeilenvorschub ist, geben wir den Zeilenvorschub
       aus und a:ndern den Status zu ordinary. Achte darauf, dass das nicht
       das gleiche wie in dem Fall oben dru:ber ist - wu:rden wir versuchen
       beide zu kombinieren, wu:rden wir zwei Zeilenvorschu:be anstatt einen
       ausgeben.

   Letztendlich, sind wir in dem lf Status nachdem wir einen Zeilenvorschub
   empfangen haben der nicht nach einem Wagenru:cklauf kam. Das wird
   passieren wenn unsere Datei bereits im UNIX(R) Format ist, oder jedesmal
   wenn mehrere Zeilen in einer Reihe durch einen einzigen Wagenru:cklauf
   gefolgt von mehreren Zeilenvorschu:ben ausgedru:ckt wird, oder wenn die
   Zeile mit einer Zeilenvorschub / Wagenru:cklauf Sequenz endet. Wir sollten
   mit unserer Eingabe in diesem Status folgendermassen umgehen:

     * Wenn die Eingabe irgendetwas anderes als ein Wagenru:cklauf oder ein
       Zeilenvorschub ist, geben wir einen Zeilenvorschub aus, geben dann die
       Eingabe aus und a:ndern dann den Status zu ordinary. Das ist exakt die
       gleiche Aktion wie in dem cr Status nach dem Empfangen der selben
       Eingabe.

     * Wenn die Eingabe ein Wagenru:cklauf ist, verwerfen wir die Eingabe,
       geben einen Zeilenvorschub aus und a:ndern dann den Status zu
       ordinary.

     * Wenn die Eingabe ein Zeilenvorschub ist, geben wir den Zeilenvorschub
       aus und lassen den Status unvera:ndert.

    11.11.1.1. Der Endgu:ltige Status

   Der obige Endliche Zustandsautomat funktioniert fu:r die gesamte Datei,
   aber la:sst die Mo:glichkeit das die letzte Zeile ignoriert wird. Das wird
   jedesmal passieren wenn die Datei mit einem einzigen Wagenru:cklauf oder
   einem einzigen Zeilenvorschub endet. Daran habe ich nicht gedacht als ich
   tuc schrieb, nur um festzustellen, dass das letzte Zeilenende gelegentlich
   weggelassen wird.

   Das Problem wird einfach dadurch gelo:st, indem man den Status u:berpru:ft
   nachdem die gesamte Datei verarbeitet wurde. Wenn der Status nicht
   ordinary ist, mu:ssen wir nur den letzten Zeilenvorschub ausgeben.

  Anmerkung:

   Nachdem wir unseren Algorithmus nun als einen Endlichen Zustandsautomaten
   formuliert haben, ko:nnten wir einfach einen festgeschalteten digitalen
   elektronischen Schaltkreis (einen "Chip") designen, der die Umwandlung
   fu:r uns u:bernimmt. Natu:rlich wa:re das sehr viel teurer, als ein
   Assembler Programm zu schreiben.

    11.11.1.2. Der Ausgabe Za:hler

   Weil unser Datei Konvertierungs Programm mo:glicherweise zwei Zeichen zu
   einem kombiniert, mu:ssen wir einen Ausgabe Za:hler verwenden. Wir
   initialisieren den Za:hler zu 0 und erho:hen ihn jedes mal wenn wir ein
   Zeichen an die Ausgabe schicken. Am Ende des Programms, wird der Za:hler
   uns sagen auf welche Gro:sse wir die Datei setzen mu:ssen.

  11.11.2. Implementieren von EZ als Software

   Der schwerste Teil beim arbeiten mit einer Endlichen Zustandsmaschine ist
   das analysieren des Problems und dem ausdru:cken als eine Endliche
   Zustandsmaschine. That geschafft, schreibt sich die Software fast wie von
   selbst.

   In eine ho:heren Sprache, wie etwa C, gibt es mehrere Hauptansa:tze. Einer
   wa:re ein switch Angabe zu verwenden die auswa:hlt welche Funktion genutzt
   werden soll. Zum Beispiel,

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

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

         (output[state])(inputchar);
      

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

         (*state)(inputchar);
      

   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 EBX speichern und dann einfach das ausgeben:

         call    ebx
      

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

  11.11.3. Speicher abgebildete Dateien

   Weil unser Programm nur mit einzelnen Dateien funktioniert, ko: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.

   UNIX(R) erlaubt es uns eine Datei, oder einen Bereich einer Datei, in den
   Speicher abzubilden. Um das zu tun, mu:ssen wir zuerst eine Datei mit den
   entsprechenden Lese/Schreib Flags o:ffnen. Dann benutzen wir den mmap
   system call um sie in den Speicher abzubilden. Ein Vorteil von mmap ist,
   das es automatisch mit virtuellem Speicher arbeitet: Wir ko:nnen mehr von
   der Datei im Speicher abbilden als wir u:berhaupt physikalischen Speicher
   zur Verfu:gung haben, noch immer haben wir aber durch normale OP Codes wie
   mov, lods, und stos Zugriff darauf. Egal welche A:nderungen wir an dem
   Speicherabbild der Datei vornehmen, sie werden vom System in die Datei
   geschrieben. Wir mu:ssen die Datei nicht offen lassen: So lange sie
   abgebildet bleibt, ko:nnen wir von ihr lesen und in sie schreiben.

   Ein 32-bit Intel Mikroprozessor kann auf bis zu vier Gigabyte Speicher
   zugreifen - physisch oder virtuell. Das FreeBSD System erlaubt es uns bis
   zu der Ha:lfte fu:r die Datei Abbildung zu verwenden.

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

   Wenn du deine Kopie von syscalls.master u:berpru:fst, wirst du zwei
   verschiedene Systemaufrufe finden die sich mmap nennen. Das kommt von der
   Entwicklung von UNIX(R): Es gab das traditionelle BSD mmap, Systemaufruf
   71. Dieses wurde durch das POSIX(R) mmap ersetzt, Systemaufruf 197. Das
   FreeBSD System unterstu:tzt beide, weil a:ltere Programme mit der
   originalen BSD Version geschrieben wurden. Da neue Software die POSIX(R)
   Version nutzt, werden wir diese auch verwenden.

   Die syscalls.master Datei zeigt die POSIX(R) Version wie folgt:

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

   Das weicht etwas von dem ab was mmap(2) sagt. Das ist weil mmap(2) die C
   Version beschreibt.

   Der Unterschiede liegt in dem long pad Argument, welches in der C Version
   nicht vorhanden ist. Wie auch immer, der FreeBSD Systemaufruf fu:gt einen
   32-bit Block ein nachdem es ein 64-Bit Argument auf den Stack gepusht hat.
   In diesem Fall, ist off_t ein 64-Bit Wert.

   Wenn wir fertig sind mit dem Arbeiten einer im Speicher abgebildeten
   Datei, entfernen wir das Speicherabbild mit dem munmap Systemaufruf:

  Tipp:

   Fu:r eine detailliert Behandlung von mmap, sieh in W. Richard Stevens'
   Unix Network Programming, Volume 2, Chapter 12 nach.

  11.11.4. Feststellen der Datei Gro:sse

   Weil wir mmap sagen mu:ssen wie viele Bytes von Datei wir im Speicher
   abbilden wollen und wir ausserdem die gesamte Datei abbilden wollen,
   mu:ssen wir die Gro:sse der Datei feststellen.

   Wir ko:nnen den fstat Systemaufruf verwenden um alle Informationen u:ber
   eine geo:ffnete Datei zu erhalten die uns das System geben kann. Das
   beinhaltet die Datei Gro:sse.

   Und wieder, zeigt uns syscalls.master zwei Versionen von fstat, eine
   traditionelle (Systemaufruf 62), und eine POSIX(R) (Systemaufruf 189)
   Variante. Natu:rlich, verwenden wir die POSIX(R) Version:

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

   Das ist ein sehr unkomplizierter Aufruf: Wir u:bergeben ihm die Adresse
   einer stat Structure und den Deskriptor einer geo:ffneten Datei. Es wird
   den Inhalt der stat Struktur ausfu:llen.

   Ich muss allerdings sagen, das ich versucht habe die stat Struktur in dem
   .bss Bereich zu deklarieren, und fstat mochte es nicht: Es setzte das
   Carry Flag welches einen Fehler anzeigt. Nachdem ich den Code vera:nderte
   so dass er die Struktur auf dem Stack anlegt, hat alles gut funktioniert.

  11.11.5. A:ndern der Dateigro:sse

   Dadurch das unser Programm Wagenru:cklauf/Zeilenvorschub-Sequenzen in
   einfache Zeilenvorschu:be zusammenfassen ko:nnte, ko:nnte unsere Ausgabe
   kleiner sein als unsere Eingabe. Und da wir die Ausgabe in dieselbe Datei
   um, aus der wir unsere Eingabe erhalten, mu:ssen wir eventuell die
   Dateigro:sse anpassen.

   Der Systemaufruf ftruncate erlaubt uns, dies zu tun. Abgesehen von dem
   etwas unglu:cklich gewa:hlten Namen ftruncate ko:nnen wir mit dieser
   Funktion eine Datei vergro:ssern, oder verkleinern.

   Und ja, wir werden zwei Versionen von ftruncate in syscalls.master finden,
   eine a:ltere (130) und eine neuere (201). Wir werden die neuere Version
   verwenden:

 201     STD     BSD     { int ftruncate(int fd, int pad, off_t length); }
      

   Beachten Sie bitte, dass hier wieder int pad verwendet wird.

  11.11.6. ftuc

   Wir wissen jetzt alles no:tige, um ftuc zu schreiben. Wir beginnen, indem
   wir ein paar neue Zeilen der Datei system.inc hinzufu:gen. Als erstes
   definieren wir irgendwo am Anfang der Datei einige Konstanten und
   Strukturen:

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

   Wir definieren die neuen Systemaufrufe:

 %define SYS_mmap        197
 %define SYS_munmap      73
 %define SYS_fstat       189
 %define SYS_ftruncate   201
      

   Wir fu:gen die Makros hinzu:

 %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
      

   Und hier ist unser Code:

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

  Warnung:

   Verwenden Sie dieses Programm nicht mit Dateien, die sich auf
   Datentra:gern befinden, welche mit MS-DOS(R) oder Windows(R) formatiert
   wurden. Anscheinend gibt es im Code von FreeBSD einen subtilen Bug, wenn
   mmap auf solchen Datentra:gern verwendet wird: Wenn die Datei eine
   bestimmte Gro:sse u:berschreitet, fu:llt mmap den Speicher mit lauter
   Nullen, und u:berschreibt damit anschliessend den Dateiinhalt.

11.12. One-Pointed Mind

   U:bersetzt von Daniel Seuffert.

   Als ein Zen-Schu:ler liebe ich die Idee eines fokussierten Bewusstseins:
   Tu nur ein Ding zur gleichen Zeit, aber mache es richtig.

   Das ist ziemlich genau die gleiche Idee, welche UNIX(R) richtig
   funktionieren la:sst. Wa:hrend eine typische Windows(R)-Applikation
   versucht alles Vorstellbare zu tun (und daher mit Fehler durchsetzt ist),
   versucht eine UNIX(R)-Applikation nur eine Funktion zu erfu:llen und das
   gut.

   Der typische UNIX(R)-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 u:bergeben.

   Wenn Sie ihre eigene UNIX(R)-Software schreiben, ist es generell eine gute
   Idee zu betrachten, welcher Teil der Problemlo:sung durch bestehende
   Programme bewerkstelligt werden kann. Man schreibt nur die Programme
   selbst, fu:r die keine vorhandene Lo:sung existiert.

  11.12.1. CSV

   Ich will dieses Prinzip an einem besonderen Beispiel aus der realen Welt
   demonstrieren, mit dem ich ku:rzlich konfrontiert wurde:

   Ich musste jeweils das elfte Feld von jedem Datensatz aus einer Datenbank
   extrahieren, die ich von einer Webseite heruntergeladen hatte. Die
   Datenbank war eine CSV-Datei, d.h. eine Liste von Komma-getrennten Werten.
   Dies ist ein ziemlich gewo:hnliches Format fu:r den Code-Austausch
   zwischen Menschen, die eine unterschiedliche Datenbank-Software nutzen.

   Die erste Zeile der Datei entha:lt eine Liste der Felder durch Kommata
   getrennt. Der Rest der Datei entha:lt die einzelnen Datensa:tze mit durch
   Kommata getrennten Werten in jeder Zeile.

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

   Daher musste ich meine eigene Software schreiben, um das elfte Feld aus
   der CSV-Datei auszulesen. Aber durch Anwendung der UNIX(R)-Philosophie
   musste ich nur einen einfachen Filter schreiben, das Folgende tat:

     * Entferne die erste Zeile aus der Datei.

     * A:ndere alle Kommata ohne Anfu:hrungszeichen in einen anderen
       Buchstaben.

     * Entferne alle Anfu:hrungszeichen.

   Streng genommen ko:nnte ich sed 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 Gro:sse
   der Pipeline.

   Unter Beru:cksichtigung aller Faktoren kostete mich das Schreiben dieses
   Programmes ca. 20 Minuten. Das Schreiben eines Programmes, welches jeweils
   das elfte Feld aus einer CSV-Datei extrahiert ha:tte wesentlich la:nger
   gedauert und ich ha:tte es nicht wiederverwenden ko:nnen, um ein anderes
   Feld aus irgendeiner anderen Datenbank zu extrahieren.

   Diesmal entschied ich mich dazu, etwas mehr Arbeit zu investieren, als man
   normalerweise fu:r ein typisches Tutorial verwenden wu:rde:

     * Es parst die Kommandozeilen nach Optionen.

     * Es zeigt die richtige Nutzung an, falls es ein falsches Argument
       findet.

     * Es gibt vernu:nftige Fehlermeldungen aus.

   Hier ist ein Beispiel fu:r seine Nutzung:

 Usage: csv [-t<delim>] [-c<comma>] [-p] [-o <outfile>] [-i <infile>]

   Alle Parameter sind optional und ko:nnen in beliebiger Reihenfolge
   auftauchen.

   Der -t-Parameter legt fest, was zu die Kommata zu ersetzen sind. Der tab
   ist die Vorgabe hierfu:r. Zum Beispiel wird -t; alle unquotierten Kommata
   mit Semikolon ersetzen.

   Ich brauche die -c-Option nicht, aber sie ko:nnte zuku:nftig nu:tzlich
   sein. Sie ermo:glicht mir festzulegen, dass ich einen anderen Buchstaben
   als das Kommata mit etwas anderem ersetzen mo:chte. Zum Beispiel wird der
   Parameter -c@ alle @-Zeichen ersetzen (nu:tzlich, falls man eine Liste von
   Email-Adressen in Nutzername und Domain aufsplitten will).

   Die -p-Option erha:lt die erste Zeile, d.h. die erste Zeile der Datei wird
   nicht gelo:scht. Als Vorgabe lo:schen wir die erste Zeile, weil die
   CSV-Datei in der ersten Zeile keine Daten, sondern Feldbeschreibungen
   entha:lt.

   Die Parameter -i- und -o-Optionen erlauben es mir, die Ausgabe- und
   Eingabedateien festzulegen. Vorgabe sind stdin und stdout, also ist es ein
   regula:rer UNIX(R)-Filter.

   Ich habe sichergestellt, dass sowohl -i filename und -ifilename akzeptiert
   werden. Genauso habe ich dafu:r Sorge getragen, dass sowohl Eingabe- als
   auch Ausgabedateien festgelegt werden ko:nnen.

   Um das elfte Feld jeden Datensatzes zu erhalten kann ich nun folgendes
   eingeben:

 % csv '-t;' data.csv | awk '-F;' '{print $11}'

   Der Code speichert die Optionen (bis auf die Dateideskriptoren) in EDX:
   Das Kommata in DH, den neuen Feldtrenner in DL und das Flag fu:r die
   -p-Option in dem ho:chsten Bit von EDX. Ein kurzer Abgleich des Zeichens
   wird uns also eine schnelle Entscheidung daru:ber erlauben, was zu tun
   ist.

   Hier ist der Code:

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

 %include        'system.inc'

 %define BUFSIZE 2048

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

 section .bss
 ibuffer resb    BUFSIZE
 obuffer resb    BUFSIZE

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

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

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

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

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

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

         inc     ecx
         mov     ax, [ecx]

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

 .loop:
         ; read a byte from stdin
         call    getchar

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

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

 .put:
         call    putchar
         jmp     short .loop

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

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

         cmp     al, 0Ah
         je      .put

         call    putchar
         jmp     short .qloop

 align 4
 getchar:
         or      ebx, ebx
         jne     .fetch

         call    read

 .fetch:
         lodsb
         dec     ebx
         ret

 read:
         jecxz   .read
         call    write

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

 align 4
 .done:
         call    write           ; flush output buffer

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

         push    dword [fd.out]
         sys.close

         ; return success
         push    dword 0
         sys.exit

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

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

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

   Ich habe eine bessere Lo:sung gefunden fu:r das Interaktivita:tsproblem
   seit ich mit dem Schreiben dieses Kapitels begonnen habe. Ich wollte
   sichergehen, dass jede Zeile einzeln ausgegeben werden kann, falls
   erforderlich. Aber schlussendlich gibt es keinen Bedarf jede Zeile einzeln
   auszugeben, falls nicht-interaktiv genutzt.

   Die neue Lo:sung besteht darin, die Funktion write 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 na:chste Zeile.

    11.12.1.1. Die dunkle Seite des Buffering

   Diese A:nderung verhindert einen mysterio:sen Aufha:nger in einem
   speziellen Fall. Ich bezeichne dies als die dunkle Seite des Buffering,
   hauptsa:chlich, weil es eine nicht offensichtliche Gefahr darstellt.

   Es ist unwahrscheinlich, dass dies mit dem csv-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
   Intensita:t eines Pixel mit den Farben rot, gru:n und blau. Unsere Ausgabe
   wird der negative Wert unserer Eingabe sein.

   Solch ein Filter wu:rde sehr einfach zu schreiben sein. Der gro:sste Teil
   davon wu:rde so aussehen wie all die anderen Filter, die wir bisher
   geschrieben haben, daher beziehe ich mich nur auf den Kern der Prozedur:

 .loop:
         call    getchar
         not     al              ; Create a negative
         call    putchar
         jmp     short .loop

   Da dieser Filter mit rohen Daten arbeitet ist es unwahrscheinlich, dass er
   interaktiv genutzt werden wird.

   Aber das Programm ko:nnte als Bildbearbeitungssoftware tituliert werden.
   Wenn es nicht write vor jedem Aufruf von read durchfu:hrt, ist die
   Mo:glichkeit gegeben, das es sich aufha:ngt.

   Dies ko:nnte passieren:

    1. Der Bildeditor wird unseren Filter laden mittels der C-Funktion
       popen().

    2. Er wird die erste Zeile von Pixeln laden aus einer Bitmap oder Pixmap.

    3. Er wird die erste Zeile von Pixeln geschrieben in die Pipe, welche zur
       Variable fd.in unseres Filters fu:hrt.

    4. Unser Filter wird jeden Pixel auslesen von der Eingabe, in in seinen
       negativen Wert umkehren und ihn in den Ausgabepuffer schreiben.

    5. Unser Filter wird die Funktion getchar aufrufen, um das na:chste Pixel
       abzurufen.

    6. Die Funktion getchar wird einen leeren Eingabepuffer vorfinden und
       daher die Funktion read aufrufen.

    7. read wird den Systemaufruf SYS_read starten.

    8. Der Kernel wird unseren Filter unterbrechen, bis der Bildeditor mehr
       Daten zur Pipe sendet.

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

   10. Der Kernel unterbricht den Bildeditor, bis er eine Ausgabe unseres
       Filters erha:lt, um ihn an den Bildeditor weiterzureichen.

   An diesem Punkt wartet unser Filter auf den Bildeditor, dass er ihm mehr
   Daten zur Verarbeitung schicken mo:ge. Gleichzeitig wartet der Bildeditor
   darauf, dass unser Filter das Resultat der Berechnung ersten Zeile sendet.
   Aber das Ergebnis sitzt in unserem Ausgabepuffer.

   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 Race Condition erreicht.

   Das Problem tritt nicht auf, wenn unser Filter seinen Ausgabepuffer leert
   bevor er vom Kernel mehr Eingabedaten anfordert.

11.13. Die FPU verwenden

   U:bersetzt von Fabian Borschel.

   Seltsamerweise erwa:hnt die meiste Literatur zu Assemblersprachen nicht
   einmal die Existenz der FPU, oder floating point unit
   (Fliesskomma-Recheneinheit), geschweige denn, dass auf die Programmierung
   mit dieser eingegangen wird.

   Dabei kann die Assemblerprogrammierung gerade bei hoch optimiertem
   FPU-Code, der nur mit einer Assemblersprache realisiert werden kann, ihre
   grosse Sta:rke ausspielen.

  11.13.1. Organisation der FPU

   Die FPU besteht aus 8 80-bit Fliesskomma-Registern. Diese sind in Form
   eines Stacks organisiert-Sie ko:nnen einen Wert durch den Befehl push auf
   dem TOS (top of stack) ablegen, oder durch pop von diesem holen.

   Da also die Befehle push und pop schon verwendet werden, kann es keine
   op-Codes in Assemblersprache mit diesen Namen geben.

   Sie ko:nnen mit einen Wert auf dem TOS ablegen, indem Sie fld, fild, und
   fbld verwenden. Mit weiteren op-Codes lassen sich Konstanten-wie z.B.
   Pi-auf dem TOS ablegen.

   Analog dazu ko:nnen Sie einen Wert holen, indem Sie fst, fstp, fist,
   fistp, und fbstp verwenden. Eigentlich holen (pop) nur die op-Codes, die
   auf p enden, einen Wert, wa:hrend die anderen den Wert irgendwo speichern
   (store) ohne ihn vom TOS zu entfernen.

   Daten ko:nnen zwischen dem TOS und dem Hauptspeicher als 32-bit, 64-bit
   oder 80-bit real, oder als 16-bit, 32-bit oder 64-bit Integer, oder als
   80-bit packed decimal u:bertragen werden.

   Das 80-bit packed decimal-Format ist ein Spezialfall des binary coded
   decimal-Formates, welches u:blicherweise bei der Konvertierung zwischen
   der ASCII- und FPU-Darstellung von Daten verwendet wird. Dieses erlaubt
   die Verwendung von 18 signifikanten Stellen.

   Unabha:ngig davon, wie Daten im Speicher dargestellt werden, speichert die
   FPU ihre Daten immer im 80-bit real-Format in den Registern.

   Ihre interne Genauigkeit betra:gt mindestens 19 Dezimalstellen. Selbst
   wenn wir also Ergebnisse im ASCII-Format mit voller 18-stelliger
   Genauigkeit darstellen lassen, werden immer noch korrekte Werte angezeigt.

   Des weiteren ko:nnen mathematische Operationen auf dem TOS ausgefu:hrt
   werden: Wir ko:nnen dessen Sinus berechnen, wir ko:nnen ihn skalieren
   (z.B. ko:nnen wir ihn mit dem Faktor 2 Multiplizieren oder Dividieren),
   wir ko:nnen dessen Logarithmus zur Basis 2 nehmen, und viele weitere
   Dinge.

   Wir ko:nnen auch FPU-Register multiplizieren, dividieren, addieren und
   subtrahieren, sogar einzelne Register mit sich selbst.

   Der offizielle Intel op-Code fu:r den TOS ist st und fu:r die Register
   st(0)- st(7). st und st(0) beziehen sich dabei auf das gleiche Register.

   Aus welchen Gru:nden auch immer hat sich der Originalautor von nasm dafu:r
   entschieden, andere op-Codes zu verwenden, na:mlich st0- st7. Mit anderen
   Worten, es gibt keine Klammern, und der TOS ist immer st0, niemals einfach
   nur st.

    11.13.1.1. Das Packed Decimal-Format

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

  Tipp:

   Sie ko:nnen durch Multiplikation des TOS mit Potenzen von 10 die einzelnen
   Dezimalstellen verschieben.

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

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

   Die signifikantere Ziffer wird in der oberen Ha:lfte (4 Bits) eines Bytes
   gespeichert, die andere in der unteren Ha:lfte.

   Vielleicht wu:rden Sie jetzt annehmen, das -1234567 auf die folgende Art
   im Speicher abgelegt wird (in hexadezimaler Notation):

 80 00 00 00 00 00 01 23 45 67

   Dem ist aber nicht so! Bei Intel werden alle Daten im little-endian-Format
   gespeichert, auch das packed decimal-Format.

   Dies bedeutet, dass -1234567 wie folgt gespeichert wird:

 67 45 23 01 00 00 00 00 00 80

   Erinnern Sie sich an diesen Umstand, bevor Sie sich aus lauter
   Verzweiflung die Haare ausreissen.

  Anmerkung:

   Das lesenswerte Buch-falls Sie es finden ko:nnen-ist Richard Startz'
   8087/80287/80387 for the IBM PC & Compatibles. Obwohl es anscheinend die
   Speicherung der packed decimal im little-endian-Format fu:r gegeben
   annimmt. Ich mache keine Witze u:ber meine Verzweiflung, als ich den
   Fehler im unten stehenden Filter gesucht habe, bevor mir einfiel, dass ich
   einfach mal versuchen sollte, das little-endian-Format, selbst fu:r diesen
   Typ von Daten, anzuwenden.

  11.13.2. Ausflug in die Lochblendenphotographie

   Um sinnvolle Programme zu schreiben, mu:ssen wir nicht nur unsere
   Programmierwerkzeuge beherrschen, sondern auch das Umfeld, fu:r das die
   Programme gedacht sind.

   Unser na:chster Filter wird uns dabei helfen, wann immer wir wollen, eine
   Lochkamera zu bauen. Wir brauchen also etwas Hintergrundwissen u:ber die
   Lochblendenphotographie, bevor wir weiter machen ko:nnen.

    11.13.2.1. Die Kamera

   Die einfachste Form, eine Kamera zu beschreiben, ist die eines
   abgeschlossenen, lichtundurchla:ssigen Raumes, in dessen Abdeckung sich
   ein kleines Loch befindet.

   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 Fa:llen sind es mehrere Lo:cher). Diese
   Lichtstrahlen kommen von einem Bild, einer Darstellung von dem was sich
   ausserhalb der Kamera, vor dem kleinen Loch, befindet.

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

   Das Loch entha:lt ha:ufig eine Linse, oder etwas linsenartiges, ha:ufig
   auch einfach Objektiv genannt.

    11.13.2.2. Die Lochblende

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

   Das Bild, das von einer Lochblende erzeugt wird, ist u:berall scharf. Oder
   unscharf. Es gibt eine ideale Gro:sse fu:r eine Lochblende: Wenn sie
   gro:sser oder kleiner ist, verliert das Bild seine Scha:rfe.

    11.13.2.3. Brennweite

   Dieser ideale Lochblendendurchmesser ist eine Funktion der Quadratwurzel
   der Brennweite, welche dem Abstand der Lochblende von dem Film entspricht.

      D = PC * sqrt(FL)

   Hier ist D der ideale Durchmesser der Lochblende, FL die Brennweite und PC
   eine Konstante der Brennweite. Nach Jay Bender hat die Konstante den Wert
   0.04, nach Kenneth Connors 0.037. Andere Leute haben andere Werte
   vorgeschlagen. Des weiteren gelten diese Werte nur fu:r Tageslicht: Andere
   Arten von Licht beno:tigen andere konstante Werte, welche nur durch
   Experimente bestimmt werden ko:nnen.

    11.13.2.4. Der f-Wert

   Der f-Wert ist eine sehr nu:tzliche Gro:sse, die angibt, wieviel Licht den
   Film erreicht. Ein Belichtungsmesser kann dies messen, um z.B. fu:r einen
   Film mit einer Empfindlichkeit von f5.6 eine Belichtungsdauer von 1/1000
   Sekunden auszurechnen.

   Es spielt keine Rolle, ob es eine 35-mm- oder eine 6x9cm-Kamera ist, usw.
   Solange wir den f-Wert kennen, ko:nnen wir die beno:tigte Belichtungszeit
   berechnen.

   Der f-Wert la:sst sich einfach wie folgt berechnen:

     F = FL / D

   Mit anderen Worten, der f-Wert ergibt sich aus der Brennweite (FL),
   dividiert durch den Durchmesser (D) der Lochblende. Ein grosser f-Wert
   impliziert also entweder eine kleine Lochblende, oder eine grosse
   Brennweite, oder beides. Je gro:sser also der f-Wert ist, um so la:nger
   muss die Belichtungszeit sein.

   Des weiteren sind der Lochblendendurchmesser und die Brennweite
   eindimensionale Messgro:ssen, wa:hrend der Film und die Lochblende an sich
   zweidimensionale Objekte darstellen. Das bedeutet, wenn man fu:r einen
   f-Wert A eine Belichtungsdauer t bestimmt hat, dann ergibt sich daraus
   fu:r einen f-Wert B eine Belichtungszeit von:

     t * (B / A)^2

    11.13.2.5. Normalisierte f-Werte

   Wa:hrend heutige moderne Kameras den Durchmesser der Lochblende, und damit
   deren f-Wert, weich und schrittweise vera:ndern ko:nnen, war dies fru:her
   nicht der Fall.

   Um unterschiedliche f-Werte einstellen zu ko:nnen, besassen Kameras
   typischerweise eine Metallplatte mit Lo:chern unterschiedlichen
   Durchmessers als Lochblende.

   Die Durchmesser wurden entsprechend obiger Formel gewa:hlt, dass der
   resultierende f-Wert ein fester Standardwert war, der fu:r alle Kameras
   verwendet wurde. Z.B. hat eine sehr alte Kodak Duaflex IV Kamera in meinem
   Besitz drei solche Lo:cher fu:r die f-Werte 8, 11 und 16.

   Eine neuere Kamera ko:nnte f-Werte wie 2.8, 4, 5.6, 8, 11, 16, 22, und 32
   (und weitere) besitzen. Diese Werte wurden nicht zufa:llig ausgewa:hlt:
   Sie sind alle vielfache der Quadratwurzel aus 2, wobei manche Werte
   gerundet wurden.

    11.13.2.6. Der f-Stopp

   Eine typische Kamera ist so konzipiert, dass die Nummernscheibe bei den
   normalisierten f-Werten einrastet. Die Nummernscheibe stoppt an diesen
   Positionen. Daher werden diese Positionen auch f-Stopps genannt.

   Da die f-Werte bei jedem Stopp vielfache der Quadratwurzel aus 2 sind,
   verdoppelt die Drehung der Nummernscheibe um einen Stopp die fu:r die
   gleiche Belichtung beno:tigte Lichtmenge. Eine Drehung um 2 Stopps
   vervierfacht die beno:tigte Belichtungszeit. Eine Drehung um 3 Stopps
   verachtfacht sie, etc.

  11.13.3. Entwurf der Lochblenden-Software

   Wir ko:nnen jetzt festlegen, was genau unsere Lochblenden-Software tun
   soll.

    11.13.3.1. Verarbeitung der Programmeingaben

   Da der Hauptzweck des Programms darin besteht, uns beim Entwurf einer
   funktionierenden Lochkamera zu helfen, wird die Brennweite die
   Programmeingabe sein. Dies ist etwas, das wir ohne zusa:tzliche Programme
   feststellen ko:nnen: Die geeignete Brennweite ergibt sich aus der Gro:sse
   des Films und der Art des Fotos, ob dieses ein "normales" Bild, ein
   Weitwinkelbild oder ein Telebild sein soll.

   Die meisten bisher geschriebenen Programme arbeiteten mit einzelnen
   Zeichen, oder Bytes, als Eingabe: Das hex-Programm konvertierte einzelne
   Bytes in hexadezimale Werte, das csv-Programm liess entweder einzelne
   Zeichen unvera:ndert, lo:schte oder vera:nderte sie, etc.

   Das Programm ftuc verwendete einen Zustandsautomaten, um ho:chstens zwei
   gleichzeitig eingegebene Bytes zu verarbeiten.

   Das pinhole-Programm dagegen kann nicht nur mit einzelnen Zeichen
   arbeiten, sondern muss mit gro:sseren syntaktischen Einheiten zurrecht
   kommen.

   Wenn wir z.B. mo:chten, dass unser Programm den Lochblendendurchmesser
   (und weitere Werte, die wir spa:ter noch diskutieren werden) fu:r die
   Brennweiten 100 mm, 150 mm und 210 mm berechnet, wollen wir etwa folgendes
   eingeben:

 100, 150, 210

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

   Wenn es auf das erste Komma trifft, muss es wissen, dass die folgenden
   Stellen nicht mehr zur ersten Zahl geho:ren. Es muss die Stellen der
   ersten Zahl in den Wert 100 konvertieren ko:nnen. Und die Stellen der
   zweiten Zahl mu:ssen in den Wert 150 konvertiert werden. Und die Stellen
   der dritten Zahl mu:ssen in den Wert 210 konvertiert werden.

   Wir mu:ssen festlegen, welche Trennsymbole zula: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?

   Ich perso: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 offensichtlichen Eingabe eines
   zusa:tzlichen Zeichens beschwert. Duh!

   Zusa:tzlich erlaubt es mir, die Monotonie des Tippens zu durchbrechen, und
   eine Anfrage anstelle einer simplen Zahl zu stellen:

 Was ist der beste Lochblendendurchmesser
           bei einer Brennweite von 150?

   Es gibt keinen Grund dafu:r, die Ausgabe mehrerer Fehlermeldungen
   aufzuteilen:

 Syntax error: Was
 Syntax error: ist
 Syntax error: der
 Syntax error: beste

   Et cetera, et cetera, et cetera.

   Zweitens mag ich das #-Zeichen, um Kommentare zu markieren, die ab dem
   Zeichen bis zum Ende der jeweiligen Zeile gehen. Dies verlangt nicht viel
   Programmieraufwand, und ermo:glicht es mir, Eingabedateien fu:r meine
   Programme als ausfu:hrbare Skripte zu handhaben.

   In unserem Fall mu:ssen wir auch entscheiden, in welchen Einheiten die
   Dateneingabe erfolgen soll: Wir wa:hlen Millimeter, da die meisten
   Photographen die Brennweite in dieser Einheit messen.

   Letztendlich mu:ssen wir noch entscheiden, ob wir die Verwendung des
   dezimalen Punktes erlauben (in diesem Fall mu:ssen wir beru:cksichtigen,
   dass in vielen La:ndern der Welt das dezimale Komma verwendet wird).

   In unserem Fall wu:rde das Zulassen eines dezimalen Punktes/Kommas zu
   einer fa:lschlicherweise angenommenen, ho:heren Genauigkeit fu:hren: Der
   Unterschied zwischen den Brennweiten 50 und 51 ist fast nicht wahrnehmbar.
   Die Zulassung von Eingaben wie 50.5 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 mu:ssen Sie selbst solche
   Entscheidungen treffen.

    11.13.3.2. Optionen anbieten

   Das wichtigste, was wir zum Bau einer Lochkamera wissen mu:ssen, ist der
   Durchmesser der Lochblende. Da wir scharfe Bilder schiessen wollen, werden
   wir obige Formel fu:r die Berechnung des korrekten Durchmessers zu
   gegebener Brennweite verwenden. Da Experten mehrere Werte fu:r die
   PC-Konstante anbieten, mu:ssen wir uns hier fu:r einen Wert entscheiden.

   In der Programmierung unter UNIX(R) ist es u:blich, zwei Hauptvarianten
   anzubieten, um Parameter an Programme zu u:bergeben, und des weiteren eine
   Standardeinstellung fu:r den Fall zu haben, das der Benutzer gar keine
   Parameter angibt.

   Warum zwei Varianten, Parameter anzugeben?

   Ein Grund ist, eine (relativ) feste Einstellung anzubieten, die
   automatisch bei jedem Programmaufruf verwendet wird, ohne das wir diese
   Einstellung immer und immer wieder mit angeben mu:ssen.

   Die feste Einstellung kann in einer Konfigurationsdatei gespeichert sein,
   typischerweise im Heimatverzeichnis des Benutzers. Die Datei hat
   u:blicherweise denselben Namen wie das zugeho:rige Programm, beginnt
   jedoch mit einem Punkt. Ha:ufig wird "rc" dem Dateinamen hinzugefu:gt.
   Unsere Konfigurationsdatei ko:nnte also ~/.pinhole oder ~/.pinholerc
   heissen. (Die Zeichenfolge ~/ steht fu:r das Heimatverzeichnis des
   aktuellen Benutzers.)

   Konfigurationsdateien werden ha:ufig von Programmen verwendet, die viele
   konfigurierbare Parameter besitzen. Programme, die nur eine (oder wenige)
   Parameter anbieten, verwenden ha:ufig eine andere Methode: Sie erwarten
   die Parameter in einer Umgebungsvariablen. In unserem Fall ko:nnten wir
   eine Umgebungsvariable mit dem Namen PINHOLE benutzen.

   Normalerweise verwendet ein Programm entweder die eine, oder die andere
   der beiden obigen Methoden. Ansonsten ko:nnte ein Programm verwirrt
   werden, wenn eine Konfigurationsdatei das eine sagt, die Umgebungsvariable
   jedoch etwas anderes.

   Da wir nur einen Parameter unterstu:tzen mu:ssen, verwenden wir die zweite
   Methode, und benutzen eine Umgebungsvariable mit dem Namen PINHOLE.

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

   Diese Art der Auswahl wird ha:ufig u:ber Kommandozeilenparameter gemacht.

   Schliesslich braucht ein Programm immer eine Standardeinstellung. Der
   Benutzer ko:nnte keine Parameter angeben. Vielleicht weiss 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 wa:hlen wu:rden. Somit mu:ssen diese keine
   zusa:tzlichen Parameter angeben, bzw. ko:nnen die Standardeinstellung ohne
   zusa:tzlichen Aufwand benutzen.

   Bei diesem System ko:nnte das Programm widerspru:chliche Optionen
   vorfinden, und auf die folgende Weise reagieren:

    1. Wenn es eine ad hoc-Einstellung vorfindet (z.B. ein
       Kommandozeilenparameter), dann sollte es diese Einstellung annehmen.
       Es muss alle vorher festgelegten sowie die standardma:ssige
       Einstellung ignorieren.

    2. Andererseits, wenn es eine festgelegte Option (z.B. eine
       Umgebungsvariable) vorfindet, dann sollte es diese akzeptieren und die
       Standardeinstellung ignorieren.

    3. Ansonsten sollte es die Standardeinstellung verwenden.

   Wir mu:ssen auch entscheiden, welches Format unsere PC-Option haben soll.

   Auf den ersten Blick scheint es einleuchtend, das Format PINHOLE=0.04 fu:r
   die Umgebungsvariable, und -p0.04 fu:r die Kommandozeile zu verwenden.

   Dies zuzulassen wa:re eigentlich eine Sicherheitslu:cke. Die PC-Konstante
   ist eine sehr kleine Zahl. Daher wu:rden wir unsere Anwendung mit
   verschiedenen, kleinen Werten fu:r PC testen. Aber was wu:rde passieren,
   wenn jemand das Programm mit einem sehr grossen Wert aufrufen wu:rde?

   Es ko:nnte abstu:rzen, weil wir das Programm nicht fu:r den Umgang mit
   grossen Werten entworfen haben.

   Oder wir investieren noch weiter Zeit in das Programm, so dass dieses dann
   auch mit grossen Zahlen umgehen kann. Wir ko:nnten dies machen, wenn wir
   kommerzielle Software fu:r computertechnisch unerfahrene Benutzer
   schreiben wu:rden.

   Oder wir ko:nnten auch sagen "Pech gehabt! Der Benutzer sollte es besser
   wissen."

   Oder wir ko:nnten es fu:r den Benutzer unmo:glich machen, grosse Zahlen
   einzugeben. Dies ist die Variante, die wir verwenden werden: Wir nehmen
   einen impliziten 0.-Pra:fix an.

   Mit anderen Worten, wenn der Benutzer den Wert 0.04 angeben will, so muss
   er entweder -p04 als Parameter angeben, oder PINHOLE=04 als Variable in
   seiner Umgebung definieren. Falls der Benutzer -p9999999 angibt, so wird
   dies als 0.9999999 interpretiert-zwar immer noch sinnlos, aber zumindest
   sicher.

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

    11.13.3.3. Die Ausgabe

   Wir mu:ssen festlegen, was und in welchem Format unsere Anwendung Daten
   ausgeben soll.

   Da wir als Eingabe beliebig viele Brennweiten erlauben, macht es Sinn, die
   Ergebnisse in Form einer traditionellen Datenbank-Ausgabe darzustellen,
   bei der zeilenweise zu jeder Brennweite der zugeho:rige berechnete Wert,
   getrennt durch ein tab-Zeichen, ausgegeben wird.

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

   Wir brauchen eine Kommandozeilenoption fu:r das CSV-Format. Wir ko:nnen
   nicht -c verwenden, da diese Option bereits fu:r verwende Connors
   Konstante steht. Aus irgendeinem seltsamen Grund bezeichnen viele
   Webseiten CSV-Dateien als "Excel Kalkulationstabelle" (obwohl das
   CSV-Format a:lter ist als Excel). Wir werden daher -e als Schalter fu:r
   die Ausgabe im CSV-Format verwenden.

   Jede Zeile der Ausgabe wird mit einer Brennweite beginnen. Dies mag auf
   den ersten Blick u:berflu:ssig erscheinen, besonders im interaktiven
   Modus: Der Benutzer gibt einen Wert fu:r die Brennweite ein, und das
   Programm wiederholt diesen.

   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 Fa:llen sieht der Benutzer die Eingabewerte
   u:berhaupt nicht.

   Ebenso kann die Ausgabe in eine Datei umgelenkt werden, was wir spa:ter
   noch untersuchen werden, oder sie ko:nnte an einen Drucker geschickt
   werden, oder auch als Eingabe fu:r ein weiteres Programm dienen.

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

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

 00000000150

   Offensichtlich mu:ssen wir die fu:hrenden Nullen vorher abschneiden.

   Wir mu:ssen also die Eingabe des Benutzers sorgfa:ltig pru:fen, diese dann
   in der FPU in die bina:re Form konvertieren, und dann von dort aus
   ausgeben.

   Aber...

   Was ist, wenn der Benutzer etwas wie folgt eingibt:

 17459765723452353453534535353530530534563507309676764423

   Ha! Das packed decimal-Format der FPU erlaubt uns die Eingabe einer
   18-stelligen Zahl. Aber der Benutzer hat mehr als 18 Stellen eingegeben.
   Wie gehen wir damit um?

   Wir ko:nnten unser Programm so modifizieren, dass es die ersten 18 Stellen
   liest, der FPU u:bergibt, dann weitere 18 Stellen liest, den Inhalt des
   TOS mit einem Vielfachen von 10, entsprechend der Anzahl der zusa:tzlichen
   Stellen multipliziert, und dann beide Werte mittels add zusammen addiert.

   Ja, wir ko:nnten das machen. Aber in diesem Programm wa:re es unno:tig (in
   einem anderen wa:re es vielleicht der richtige Weg): Selbst der Erdumfang
   in Millimetern ergibt nur eine Zahl mit 11 Stellen. Offensichtlich ko:nnen
   wir keine Kamera dieser Gro:sse bauen (jedenfalls jetzt noch nicht).

   Wenn der Benutzer also eine so grosse Zahl eingibt, ist er entweder
   gelangweilt, oder er testet uns, oder er versucht, in das System
   einzudringen, oder er spielt- indem er irgendetwas anderes macht als eine
   Lochkamera zu entwerfen.

   Was werden wir tun?

   Wir werden ihn ohrfeigen, gewissermassen:

 17459765723452353453534535353530530534563507309676764423        ???     ???     ???     ???     ???

   Um dies zu erreichen, werden wir einfach alle fu:hrenden Nullen
   ignorieren. Sobald wir eine Ziffer gefunden haben, die nicht Null ist,
   initialisieren wir einen Za:hler mit 0 und beginnen mit drei Schritten:

    1. Sende die Ziffer an die Ausgabe.

    2. Fu:ge die Ziffer einem Puffer hinzu, welchen wir spa:ter benutzen
       werden, um den packed decimal-Wert zu erzeugen, den wir an die FPU
       schicken ko:nnen.

    3. Erho:he den Za:hler um eins.

   Wa:hrend wir diese drei Schritte wiederholen, mu:ssen wir auf zwei
   Bedingungen achten:

     * Wenn der Za:hler den Wert 18 u:bersteigt, ho:ren wir auf, Ziffern dem
       Puffer hinzuzufu:gen. Wir lesen weiterhin Ziffern und senden sie an
       die Ausgabe.

     * Wenn, bzw. falls, das na:chste Eingabezeichen keine Zahl ist, sind wir
       mit der Bearbeitung der Eingabe erst einmal fertig.

       U:brigends ko:nnen wir einfach Zeichen, die keine Ziffern sind,
       verwerfen, solange sie kein #-Zeichen sind, welches wir an den
       Eingabestrom zuru:ckgeben mu:ssen. Dieses Zeichen markiert den Beginn
       eines Kommentars. An dieser Stelle muss die Erzeugung der Ausgabe
       fertig sein, und wir mu:ssen mit der Suche nach weiteren Eingabedaten
       fortfahren.

   Es bleibt immer noch eine Mo:glichkeit unberu:cksichtigt: Wenn der
   Benutzer eine Null (oder mehrere) eingibt, werden wir niemals eine von
   Null verschiedene Zahl vorfinden.

   Wir ko:nnen solch einen Fall immer anhand des Za:hlerstandes feststellen,
   welcher dann immer bei 0 bleibt. In diesem Fall mu:ssen wir einfach eine 0
   an die Ausgabe senden, und anschliessend dem Benutzer erneut eine
   "Ohrfeige" verpassen:

 0       ???     ???     ???     ???     ???

   Sobald wir die Brennweite ausgegeben, und die Gu:ltigkeit dieser Eingabe
   verifiziert haben, (gro:sser als 0 und kleiner als 18 Zahlen) ko:nnen wir
   den Durchmesser der Lochblende berechnen.

   Es ist kein Zufall, dass Lochblende das Wort Loch entha:lt. In der Tat ist
   eine Lochblende buchsta:blich eine Loch Blende, also eine Blende, in die
   mit einer Nadel vorsichtig ein kleines Loch gestochen wird.

   Daher ist eine typische Lochblende sehr klein. Unsere Formel liefert uns
   das Ergebnis in Millimetern. Wir werden dieses mit 1000 multiplizieren, so
   dass die Ausgabe in Mikrometern erfolgt.

   An dieser Stelle mu:ssen wir auf eine weitere Falle achten: Zu hohe
   Genauigkeit.

   Ja, die FPU wurde fu: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).

   Angenommen, wir wollten aus eine Lastkraftwagen eine Lochkamera bauen (wir
   wa:ren dabei nicht die ersten, die das versuchen wu:rden!). Angenommen,
   die La:nge des Laderaumes betra:gt 12 Meter lang, so dass wir eine
   Brennweite von 12000 ha:tten. Verwenden wir Benders Konstante, so erhalten
   wir durch Multiplizieren von 0.04 mit der Quadratwurzel aus 12000 einen
   Wert von 4.381780460 Millimetern, oder 4381.780460 Micrometern.

   So oder so ist das Rechenergebnis absurd pra:zise. Unser Lastkraftwagen
   ist nicht genau 12000 Millimeter lang. Wir haben diese La:nge nicht mit
   einer so hohen Genauigkeit gemessen, weswegen es falsch wa:re zu
   behaupten, unser Lochblendendurchmesser mu:sse exakt 4.381780460
   Millimeter sein. Es reicht vollkommen aus, wenn der Durchmesser 4.4
   Millimeter betra:gt.

  Anmerkung:

   Ich habe in obigem Beispiel das Rechenergebnis "nur" auf 10 Stellen genau
   angegeben. Stellen Sie sich vor, wie absurd es wa:re, die vollen uns zur
   Verfu:gung stehenden, 18 Stellen anzugeben!

   Wir mu:ssen also die Anzahl der signifikanten Stellen beschra:nken. Eine
   Mo:glichkeit wa:re, die Mikrometer durch eine ganze Zahl darzustellen.
   Unser Lastkraftwaren wu:rde dann eine Lochblende mit einem Durchmesser von
   4382 Mikrometern beno:tigen. Betrachten wir diesen Wert, dann stellen wir
   fest, das 4400 Mikrometer, oder 4.4 Millimeter, immer noch genau genug
   ist.

   Zusa:tzlich ko:nnen wir noch, unabha:ngig von der Gro:sse eines
   Rechenergebnisses, festlegen, dass wir nur vier signifikante Stellen
   anzeigen wollen (oder weniger). Leider bietet uns die FPU nicht die
   Mo:glichkeit, das Ergebnis automatisch bis auf eine bestimmte Stelle zu
   runden (sie sieht die Daten ja nicht als Zahlen, sondern als bina:re Daten
   an).

   Wir mu:ssen also selber einen Algorithmus entwerfen, um die Anzahl der
   signifikanten Stellen zu reduzieren.

   Hier ist meiner (ich denke er ist peinlich-wenn Ihnen ein besserer
   Algorithmus einfa:llt, verraten sie ihn mir bitte):

    1. Initialisiere einen Za:hler mit 0.

    2. Solange die Zahl gro:sser oder gleich 10000 ist, dividiere die Zahl
       durch 10, und erho:he den Za:hler um eins.

    3. Gebe das Ergebnis aus.

    4. Solange der Za:hler gro:sser als 0 ist, gebe eine 0 aus, und reduziere
       den Za:hler um eins.

  Anmerkung:

   Der Wert 10000 ist nur fu:r den Fall, dass Sie vier signifikante Stellen
   haben wollen. Fu:r eine andere Anzahl signifikanter Stellen mu:ssen Sie
   den Wert 10000 mit 10, hoch der Anzahl der gewu:nschten signifikanten
   Stellen, ersetzen.

   Wir ko:nnen so den Lochblendendurchmesser, auf vier signifikante Stellen
   gerundet, ausgeben.

   An dieser Stellen kennen wir nun die Brennweite und den
   Lochblendendurchmesser. Wir haben also jetzt genug Informationen, um den
   f-Wert zu bestimmen.

   Wir werden den f-Wert, auf vier signifikante Stellen gerundet, ausgeben.
   Es ko:nnte passieren, dass diese vier Stellen recht wenig aussagen. Um die
   Aussagekraft des f-Wertes zu erho:hen, ko:nnten wir den na:chstliegenden,
   normalisierten f-Wert bestimmen, also z.B. das na:chstliegende Vielfache
   der Quadratwurzel aus 2.

   Wir erreichen dies, indem wir den aktuellen f-Wert mit sich selbst
   multiplizieren, so dass wir dessen Quadrat (square) erhalten.
   Anschliessend 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
   na:chstliegende ganze Zahl. Genau genommen ko:nnen wir mit Hilfe der FPU
   diese Berechnung beschleunigen: Wir ko:nnen den op-Code fscale verwenden,
   um eine Zahl um 1 zu "skalieren", was dasselbe ist, wie eine Zahl mittels
   shift um eine Stelle nach links zu verschieben. Am Ende berechnen wir noch
   die Quadratwurzel aus allem, und erhalten dann den na:chstliegenden,
   normalisierten f-Wert.

   Wenn das alles jetzt viel zu kompliziert wirkt-oder viel zu aufwendig-wird
   es vielleicht klarer, wenn man den Code selber betrachtet. Wir beno:tigen
   insgesamt 9 op-Codes:

 fmul    st0, st0
     fld1
     fld     st1
     fyl2x
     frndint
     fld1
     fscale
     fsqrt
     fstp    st1

   Die erste Zeile, fmul st0, st0, quadriert den Inhalt des TOS (Top Of
   Stack, was dasselbe ist wie st, von nasm auch st0 genannt). Die Funktion
   fld1 fu:gt eine 1 dem TOS hinzu.

   Die na:chste Zeile, fld st1, legt das Quadrat auf dem TOS ab. An diesem
   Punkt befindet sich das Quadrat sowohl in st als auch in st(2) (es wird
   sich gleich zeigen, warum wir eine zweite Kopie auf dem Stack lassen.)
   st(1) entha:lt die 1.

   Im na:chsten Schritt, fyl2x, wird der Logarithmus von st zur Basis 2
   berechnet, und anschliessend mit st(1) multipliziert. Deshalb haben wir
   vorher die 1 in st(1) abgelegt.

   An dieser Stelle entha:lt st den gerade berechneten Logarithmus, und st(1)
   das Quadrat des aktuellen f-Wertes, den wir fu:r spa:ter gespeichert
   haben.

   frndint rundet den TOS zur na:chstliegenden ganzen Zahl. fld1 legt eine 1
   auf dem Stack ab. fscale shiftet die 1 auf dem TOS um st(1) Stellen,
   wodurch im Endeffekt eine 2 in st(1) steht.

   Schliesslich berechnet fsqrt die Quadratwurzel des Rechenergebnisses, also
   des na:chstliegenden, normalisierten f-Wertes.

   Wir haben nun den na:chstliegenden, normalisierten f-Wert auf dem TOS
   liegen, den auf den Logarithmus zur Basis 2 gerundeten, na:chstliegenden
   ganzzahligen Wert in st(1), und das Quadrat des aktuellen f-Wertes in
   st(2). Wir speichern den Wert fu:r eine spa:tere Verwendung in st(2).

   Aber wir brauchen den Inhalt von st(1) gar nicht mehr. Die letzte Zeile,
   fstp st1, platziert den Inhalt von st in st(1), und erniedrigt den
   Stackpointer um eins. Dadurch ist der Inhalt von st(1) jetzt st, der
   Inhalt von st(2) jetzt st(1) usw. Der neue st speichert jetzt den
   normalisierten f-Wert. Der neue st(1) speichert das Quadrat des aktuellen
   f-Wertes fu:r die Nachwelt.

   Jetzt ko:nnen wir den normalisierten f-Wert ausgeben. Da er normalisiert
   ist, werden wir ihn nicht auf vier signifikante Stellen runden, sondern
   stattdessen mit voller Genauigkeit ausgeben.

   Der normalisierte f-Wert ist nu:tzlich, solange er so klein ist, dass wir
   ihn auf einem Photometer wiederfinden ko:nnen. Ansonsten brauchen wir eine
   andere Methode, um die beno:tigten Belichtungsdaten zu bestimmen.

   Wir haben weiter oben eine Formel aufgestellt, u:ber die wir einen f-Wert
   mit Hilfe eines anderen f-Wertes und den zugeho:rigen Belichtungsdaten
   bestimmen ko:nnen.

   Jedes Photometer, das ich jemals gesehen habe, konnte die beno:tigte
   Belichtungszeit fu:r f5.6 berechnen. Wir werden daher einen "f5.6
   Multiplizierer" berechnen, der uns den Faktor angibt, mit dem wir die bei
   f5.6 gemessene Belichtungszeit fu:r unsere Lochkamera multiplizieren
   mu:ssen.

   Durch die Formel wissen wir, dass dieser Faktor durch Dividieren unseres
   f-Wertes (der aktuelle Wert, nicht der normalisierte) durch 5.6 und
   anschliessendes Quadrieren, berechnen ko:nnen.

   Mathematisch a:quivalent dazu wa:re, wenn wir das Quadrat unseres f-Wertes
   durch das Quadrat von 5.6 dividieren wu:rden.

   Numerisch betrachtet wollen wir nicht zwei Zahlen quadrieren, wenn es
   mo:glich ist, nur eine Zahl zu quadrieren. Daher wirkt die erste Variante
   auf den ersten Blick besser.

   Aber...

   5.6 ist eine Konstante. Wir mu:ssen nicht wertvolle Rechenzeit der FPU
   verschwenden. Es reicht aus, dass wir die Quadrate der einzelnen f-Werte
   durch den konstanten Wert 5.6^2 dividieren. Oder wir ko:nnen den
   jeweiligen f-Wert durch 5.6 dividieren, und dann das Ergebnis quadrieren.
   Zwei Mo:glichkeiten, die gleich erscheinen.

   Aber das sind sie nicht!

   Erinnern wir uns an die Grundlagen der Photographie weiter oben, dann
   wissen wir, dass sich die Konstante 5.6 aus dem 5-fachen der Quadratwurzel
   aus 2 ergibt. Eine irrationale Zahl. Das Quadrat dieser Zahl ist exakt 32.

   32 ist nicht nur eine ganze Zahl, sondern auch ein Vielfaches von 2. Wir
   brauchen also gar nicht das Quadrat eines f-Wertes durch 32 zu teilen. Wir
   mu:ssen lediglich mittels fscale den f-Wert um fu:nf Stellen nach rechts
   shiften. Aus Sicht der FPU mu:ssen wir also fscale mit st(1), welcher
   gleich -5 ist, auf den f-Wert anwenden. Dies ist sehr viel schneller als
   die Division.

   Jetzt wird es auch klar, warum wir das Quadrat des f-Wertes ganz oben auf
   dem Stack der FPU 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.

   Es gibt noch eine weitere nu:tzliche Zahl, die wir berechnen ko:nnen: Die
   Anzahl der Stopps, die unser f-Wert von f5.6 entfernt ist. Dies ko:nnte
   hilfreich sein, wenn unser f-Wert ausserhalb des Messbereiches unseres
   Photometers liegt, wir aber eine Blende haben, bei der wir
   unterschiedliche Geschwindigkeiten einstellen ko:nnen, und diese Blende
   Stopps benutzt.

   Angenommen, unser f-Wert ist 5 Stopps von f5.6 entfernt, und unser
   Photometer sagt uns, dass wir eine Belichtungszeit von 1/1000 Sek.
   einstellen sollen. Dann ko:nnen wir unsere Blende auf die Geschwindigkeit
   1/1000 einstellen, und unsere Skala um 5 Stopps verschieben.

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

  11.13.4. FPU Optimierungen

   In Assemblersprache ko:nnen wir den Code fu:r die FPU besser optimieren,
   als in einer der Hochsprachen, inklusive C.

   Sobald eine C-Funktion die Berechnung einer Fliesskommazahl durchfu:hren
   will, la:dt sie erst einmal alle beno:tigten Variablen und Konstanten in
   die Register der FPU. Dann werden die Berechnungen durchgefu:hrt, um das
   korrekte Ergebnis zu erhalten. Gute C-Compiler ko:nnen diesen Teil des
   Codes sehr gut optimieren.

   Das Ergebnis wird "zuru:ckgegeben", indem dieses auf dem TOS abgelegt
   wird. Vorher wird aufgera:umt. Sa:mtliche Variablen und Konstanten, die
   wa:hrend der Berechnung verwendet wurden, werden dabei aus der FPU
   entfernt.

   Was wir im vorherigen Abschnitt selber getan haben, kann so nicht
   durchgefu:hrt werden: Wir haben das Quadrat des f-Wertes berechnet, und
   das Ergebnis fu:r eine weitere Berechnung mit einer anderen Funktion auf
   dem Stack behalten.

   Wir wussten, dass wir diesen Wert spa:ter noch einmal brauchen wu:rden.
   Wir wussten auch, dass auf dem Stack genu:gend Platz war (welcher nur
   Platz fu:r 8 Zahlen bietet), um den Wert dort zu speichern.

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

   Natu:rlich ko:nnte der C-Programmierer dies wissen. Aber die einzige
   Mo:glichkeit, die er hat, ist, den Wert im verfu:gbaren Speicher zu
   halten.

   Das bedeutet zum einen, dass der Wert mit der FPU-internen, 80-stelligen
   Genauigkeit in einer normalen C-Variable vom Typ double (64 Bit) oder vom
   Typ single (32 Bit) gespeichert wird.

   Dies bedeutet ausserdem, dass der Wert aus dem TOS in den Speicher
   verschoben werden muss, und spa:ter wieder zuru:ck. Von allen Operationen
   mit der FPU ist der Zugriff auf den Speicher die langsamste.

   Wann immer also mit der FPU in einer Assemblersprache programmiert wird,
   sollte nach Mo:glichkeiten gesucht werden, Zwischenergebnisse auf dem
   Stack der FPU zu lassen.

   Wir ko:nnen mit dieser Idee noch einen Schritt weiter gehen! In unserem
   Programm verwenden wir eine Konstante (die wir PC genannt haben).

   Es ist unwichtig, wieviele Lochblendendurchmesser wir berechnen: 1, 10,
   20, 1000, wir verwenden immer dieselbe Konstante. Daher ko:nnen wir unser
   Programm so optimieren, dass diese Konstante immer auf dem Stack belassen
   wird.

   Am Anfang unseres Programmes berechnen wir die oben erwa:hnte Konstante.
   Wir mu:ssen die Eingabe fu:r jede Dezimalstelle der Konstanten durch 10
   dividieren.

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

   Auf diese Weise geben wir 0.1 nicht direkt ein, obwohl wir dies ko:nnten.
   Dies hat einen Grund: Wa:hrend 0.1 durch nur eine einzige Dezimalstelle
   dargestellt werden kann, wissen wir nicht, wieviele bina:re Stellen
   beno:tigt werden. Wir u:berlassen die Berechnung des bina:ren Wertes daher
   der FPU, mit dessen eigener, hoher Genauigkeit.

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

   Zu guter letzt behalten wir -5 noch auf dem Stack. Wir brauchen diesen
   Wert, um das Quadrat des f-Wertes zu skalieren, anstatt diesen durch 32 zu
   teilen. Es ist kein Zufall, dass wir diese Konstante als letztes laden.
   Dadurch wird diese Zahl die oberste Konstante auf dem Stack. Wenn spa:ter
   das Quadrat des f-Wertes skaliert werden muss, befindet sich die -5 in
   st(1), also genau da, wo die Funktion fscale diesen Wert erwartet.

   Es ist u:blich, einige Konstanten per Hand zu erzeugen, anstatt sie aus
   dem Speicher zu laden. Genau das machen wir mit der -5:

         fld1                    ; TOS =  1
         fadd    st0, st0        ; TOS =  2
         fadd    st0, st0        ; TOS =  4
         fld1                    ; TOS =  1
         faddp   st1, st0        ; TOS =  5
         fchs                    ; TOS = -5

   Wir ko:nnen all diese Optimierungen in einer Regel zusammenfassen: Behalte
   wiederverwendbare Werte auf dem Stack!

  Tipp:

   PostScript(R) ist eine Stack-orientierte Programmiersprache. Es gibt weit
   mehr Bu:cher u:ber PostScript(R), als u:ber die Assemblersprache der FPU:
   Werden Sie in PostScript(R) besser, dann werden Sie auch automatisch in
   der Programmierung der FPU besser.

  11.13.5. pinhole-Der Code

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

 %include        'system.inc'

 %define BUFSIZE 2048

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

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

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

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

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

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

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

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

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

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

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

         inc     ecx
         mov     ax, [ecx]
         inc     ecx

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

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

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

         or      ah, ah
         jne     .openoutput
         pop     ecx
         jecxz   usage

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

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

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

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

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

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

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

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

         pop     ecx
         or      ecx, ecx
         je      near usage

         mov     ah, [ecx]

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

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

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

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

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

         sub     ecx, ecx

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

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

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

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

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

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

         inc     dl

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

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

         finit
         fild    dword [tthou]

         fld1
         fild    dword [ten]
         fdivp   st1, st0

         fild    dword [thousand]
         mov     edi, obuffer

         mov     ebp, ecx
         call    bcdload

 .constdiv:
         fmul    st0, st2
         loop    .constdiv

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

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

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

 .bigloop:
         call    getchar
         jc      near done

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

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

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

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

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

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

 .ungetc:
         dec     esi
         inc     ebx

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

         cmp     ebp, 19
         jae     near .toobig

         call    bcdload

         ; Calculate pinhole diameter

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

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

 .printdiameter:
         call    printnumber     ; pinhole diameter

         ; Calculate F-number

         fdivp   st1, st0
         fld     st0

         sub     ebp, ebp

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

 .printfnumber:
         call    printnumber     ; F number

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

         sub     ebp, ebp
         call    printnumber

         ; Calculate time multiplier from F-5.6

         fscale
         fld     st0

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

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

 .printfmul:
         call    printnumber     ; F multiplier

         ; Calculate F-stops from 5.6

         fld1
         fxch    st1
         fyl2x

         sub     ebp, ebp
         call    printnumber

         mov     al, 0Ah
         call    putchar
         jmp     .bigloop

 .work0:
         mov     al, '0'
         call    putchar

 align 4
 .toobig:
         call    huh
         jmp     .bigloop

 align 4
 done:
         call    write           ; flush output buffer

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

         push    dword [fd.out]
         sys.close

         finit

         ; return success
         push    dword 0
         sys.exit

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

 .cr:
         cmp     al, 0Dh
         jne     skiptoeol
         ret

 align 4
 getchar:
         or      ebx, ebx
         jne     .fetch

         call    read

 .fetch:
         lodsb
         dec     ebx
         clc
         ret

 read:
         jecxz   .read
         call    write

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

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

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

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

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

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

         std

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

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

         fbld    [bbuffer]

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

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

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

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

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

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

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

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

 .second:
         add     al, '0'
         call    putchar

 .next:
         cmp     ebp, bbuffer
         jb      .done

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

         dec     ebp
         jmp     short .next

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

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

 .ret:
         ret

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

     Wir nehmen nun nicht mehr an, dass das Ende der Eingabe auch das Ende
     der no:tigen Arbeit bedeutet, etwas, das wir fu:r zeichenbasierte Filter
     automatisch angenommen haben.

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

     Wenn keine weiteren Eingaben vorliegen, kann das zwei Ursachen haben:

       * Wir sind fertig und ko:nnen aufho:ren. Dies ist dasselbe wie vorher.

       * Das Zeichen, das wir eingelesen haben, war eine Zahl. Wir haben
         diese am Ende unseres ASCII -zu-float Kovertierungspuffers
         gespeichert. Wir mu:ssen nun den gesamten Pufferinhalt in eine Zahl
         konvertieren, und die letzte Zeile unserer Ausgabe ausgeben.

     Aus diesem Grund haben wir unsere getchar - und read-Routinen so
     angepasst, dass sie das carry flag clear immer dann zuru:ckgeben, wenn
     wir ein weiteres Zeichen aus der Eingabe lesen, und das carry flag set
     immer dann zuru:ckgeben, wenn es keine weiteren Eingabedaten gibt.

     Selbstversta:ndlich verwenden wir auch hier die Magie der
     Assemblersprache! Schauen Sie sich getchar na:her an. Dieses gibt immer
     das carry flag clear zuru:ck.

     Dennoch basiert der Hauptteil unseres Programmes auf dem carry flag, um
     diesem eine Beendigung mitzuteilen-und es funktioniert.

     Die Magie passiert in read. Wann immer weitere Eingaben durch das System
     zur Verfu:gung stehen, ruft diese Funktion getchar auf, welche ein
     weiteres Zeichen aus dem Eingabepuffer einliest, und anschliessend das
     carry flag cleart.

     Wenn aber read keine weiteren Eingaben von dem System bekommt, ruft
     dieses nicht getchar auf. Stattdessen addiert der op-Code add esp, byte
     4 4 zu ESP hinzu, setzt das carry flag, und springt zuru:ck.

     Wo springt diese Funktion hin? Wann immer ein Programm den op-Code call
     verwendet, pusht der Mikroprozessor die Ru:cksprungandresse, d.h. er
     speichert diese ganz oben auf dem Stack (nicht auf dem Stack der FPU,
     sondern auf dem Systemstack, der sich im Hauptspeicher befindet). Wenn
     ein Programm den op-Code ret verwendet, popt der Mikroprozessor den
     Ru:ckgabewert von dem Stack, und springt zu der Adresse, die dort
     gespeichert wurde.

     Da wir aber 4 zu ESP hinzuaddiert haben (welches das Register der
     Stackzeiger ist), haben wir effektiv dem Mikroprzessor eine kleine
     Amnesie verpasst: Dieser erinnert sich nun nicht mehr daran, dass
     getchar durch read aufgerufen wurde.

     Und da getchar nichts vor dem Aufruf von read auf dem Stack abgelegt
     hat, entha:lt der Anfang des Stacks nun die Ru:cksprungadresse von der
     Funktion, die getchar aufgerufen hat. Soweit es den Aufrufer betrifft,
     hat dieser getchar gecallt, welche mit einem gesetzten carry flag
     returned.

   Des weiteren wird die Routine bcdload bei einem klitzekleinen Problem
   zwischen der Big-Endian- und Little-Endian-Codierung aufgerufen.

   Diese konvertiert die Textrepra:sentation einer Zahl in eine andere
   Textrepra:sentation: Der Text wird in der Big-Endian-Codierung
   gespeichert, die packed decimal-Darstellung jedoch in der
   Little-Endian-Codierung.

   Um dieses Problem zu lo:sen haben wir vorher den op-Code std verwendet.
   Wir machen diesen Aufruf spa:ter mittels cld wieder ru:ckga:ngig: Es ist
   sehr wichtig, dass wir keine Funktion mittels call aufrufen, die von einer
   Standardeinstellung des Richtungsflags abha:ngig ist, wa:hrend std
   ausgefu:hrt wird.

   Alles weitere in dem Programm sollte leicht zu verstehen sein,
   vorausgesetzt, dass Sie das gesamte vorherige Kapitel gelesen haben.

   Es ist ein klassisches Beispiel fu:r das Sprichwort, dass das
   Programmieren eine Menge Denkarbeit, und nur ein wenig Programmcode
   beno:tigt. Sobald wir uns u:ber jedes Detail im klaren sind, steht der
   Code fast schon da.

  11.13.6. Das Programm pinhole verwenden

   Da wir uns bei dem Programm dafu:r entschieden haben, alle Eingaben, die
   keine Zahlen sind, zu ignorieren (selbst die in Kommentaren), ko:nnen wir
   jegliche textbasierten Eingaben verarbeiten. Wir mu:ssen dies nicht tun,
   wir ko:nnten aber.

   Meiner bescheidenen Meinung nach wird ein Programm durch die Mo:glichkeit,
   anstatt einer strikten Eingabesyntax textbasierte Anfragen stellen zu
   ko:nnen, sehr viel benutzerfreundlicher.

   Angenommen, wir wollten eine Lochkamera fu:r einen 4x5 Zoll Film bauen.
   Die standardma:ssige Brennweite fu:r diesen Film ist ungefa:hr 150mm. Wir
   wollen diesen Wert optimieren, so dass der Lochblendendurchmesser eine
   mo:glichst runde Zahl ergibt. Lassen Sie uns weiter annehmen, dass wir
   zwar sehr gut mit Kameras umgehen ko:nnen, dafu:r aber nicht so gut mit
   Computern. Anstatt das wir nun eine Reihe von Zahlen eingeben, wollen wir
   lieber ein paar Fragen stellen.

   Unsere Sitzung ko:nnte wie folgt aussehen:

 % pinhole

 Computer,

 Wie gross mu:sste meine Lochblende bei einer Brennweite
 von 150 sein?
 150     490     306     362     2930    12
 Hmmm... Und bei 160?
 160     506     316     362     3125    12
 Lass uns bitte 155 nehmen.
 155     498     311     362     3027    12
 Ah, lass uns 157 probieren...
 157     501     313     362     3066    12
 156?
 156     500     312     362     3047    12
 Das ist es! Perfekt! Vielen Dank!
 ^D

   Wir haben herausgefunden, dass der Lochblendendurchmesser bei einer
   Brennweite von 150 mm 490 Mikrometer, oder 0.49 mm ergeben wu:rde. Bei
   einer fast identischen Brennweite von 156 mm wu:rden wir einen Durchmesser
   von genau einem halben Millimeter bekommen.

  11.13.7. Skripte schreiben

   Da wir uns dafu:r entschieden haben, das Zeichen # als den Anfang eines
   Kommentares zu interpretieren, ko:nnen wir unser pinhole-Programm auch als
   Skriptsprache verwenden.

   Sie haben vielleicht schon einmal shell-Skripte gesehen, die mit folgenden
   Zeichen begonnen haben:

 #! /bin/sh

   ...oder...

 #!/bin/sh

   ... da das Leerzeichen hinter dem #! optional ist.

   Wann immer UNIX(R) eine Datei ausfu:hren soll, die mit einem #! beginnt,
   wird angenommen, das die Datei ein Skript ist. Es fu:gt den Befehl an das
   Ende der ersten Zeile an, und versucht dann, dieses auszufu:hren.

   Angenommen, wir haben unser Programm pinhole unter /usr/local/bin/
   installiert, dann ko:nnen wir nun Skripte schreiben, um unterschiedliche
   Lochblendendurchmesser fu:r mehrere Brennweiten zu berechnen, die
   normalerweise mit 120er Filmen verwendet werden.

   Das Skript ko:nnte wie folgt aussehen:

 #! /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

   Da ein 120er Film ein Film mittlerer Gro:sse ist, ko:nnten wir die Datei
   medium nennen.

   Wir ko:nnen die Datei ausfu:hrbar machen und dann aufrufen, als wa:re es
   ein Programm:

 % chmod 755 medium
 % ./medium

   UNIX(R) wird den letzten Befehl wie folgt interpretieren:

 % /usr/local/bin/pinhole -b -i ./medium

   Es wird den Befehl ausfu:hren und folgendes ausgeben:

 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

   Lassen Sie uns nun das folgende eingeben:

 % ./medium -c

   UNIX(R) wird dieses wie folgt behandeln:

 % /usr/local/bin/pinhole -b -i ./medium -c

   Dadurch erha:lt das Programm zwei widerspru:chliche Optionen: -b und -c
   (Verwende Benders Konstante und verwende Connors Konstante). Wir haben
   unser Programm so geschrieben, dass spa:ter eingelesene Optionen die
   vorheringen u:berschreiben-unser Programm wird also Connors Konstante fu:r
   die Berechnungen verwenden:

 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

   Wir entscheiden uns am Ende doch fu:r Benders Konstante. Wir wollen die
   Ergebnisse im CSV-Format in einer Datei speichern:

 % ./medium -b -e > bender
 % cat bender
 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
 %

11.14. Vorsichtsmassnahmen

   U:bersetzt von Daniel Seuffert.

   Assembler-Programmierer, die aufwuchsen mit MS-DOS(R) und windows
   Windows(R) 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 MS-DOS(R) nicht
   verpo:nt sind, aber nicht als richtig angesehen werden.

   Warum dies? Sowohl das PC-BIOS als auch MS-DOS(R) sind notorisch langsam
   bei der Ausfu:hrung dieser Operationen.

   Sie mo:gen versucht sein a:hnliche Angewohnheiten in der UNIX(R)-Umgebung
   fortzufu:hren. Zum Beispiel habe ich eine Webseite gesehen, welche
   erkla:rt, wie man auf einem beliebten UNIX(R)-Ableger die
   Tastatur-Scancodes verwendet.

   Das ist generell eine sehr schlechte Idee in einer UNIX(R)-Umgebung!
   Lassen Sie mich erkla:ren warum.

  11.14.1. UNIX(R) ist geschu:tzt

   Zum Einen mag es schlicht nicht mo:glich sein. UNIX(R) la:uft im Protected
   Mode. Nur der Kernel und Gera:tetreiber du:rfen direkt auf die Hardware
   zugreifen. Unter Umsta:nden erlaubt es Ihnen ein bestimmter
   UNIX(R)-Ableger Tastatur-Scancodes auszulesen, aber ein wirkliches
   UNIX(R)-Betriebssystem wird dies zu verhindern wissen. Und falls eine
   Version es Ihnen erlaubt wird es eine andere nicht tun, daher kann eine
   sorgfa:ltig erstellte Software u:ber Nacht zu einem u:berkommenen
   Dinosaurier werden.

  11.14.2. UNIX(R) ist eine Abstraktion

   Aber es gibt einen viel wichtigeren Grund, weshalb Sie nicht versuchen
   sollten, die Hardware direkt anzusprechen (natu:rlich nicht, wenn Sie
   einen Gera:tetreiber schreiben), selbst auf den UNIX(R)-a:hnlichen
   Systemen, die es Ihnen erlauben:

   UNIX(R) ist eine Abstraktion!

   Es gibt einen wichtigen Unterschied in der Design-Philosophie zwischen
   MS-DOS(R) und UNIX(R). MS-DOS(R) wurde entworfen als Einzelnutzer-System.
   Es la: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.

   Dies ist NIEMALS garantiert unter UNIX(R). Es ist sehr verbreitet fu:r ein
   UNIX(R), dass der Nutzer seine Aus- und Eingaben kanalisiert und umleitet:

 % program1 | program2 | program3 > file1

   Falls Sie eine Anwendung program2 geschrieben haben, kommt ihre Eingabe
   nicht von der Tastatur, sondern von der Ausgabe von program1.
   Gleichermassen geht Ihre Ausgabe nicht auf den Bildschirm, sondern wird
   zur Eingabe fu:r program3, dessen Ausgabe wiederum in file1 endet.

   Aber es gibt noch mehr! Selbst wenn Sie sichergestellt haben, dass Ihre
   Eingabe und Ausgabe zum Terminal kommt bzw. gelangt, dann ist immer noch
   nicht garantiert, dass ihr Terminal ein PC ist: Es mag seinen
   Grafikspeicher nicht dort haben, wo Sie ihn erwarten, oder die Tastatur
   ko:nnte keine PC-a:hnlichen Scancodes erzeugen ko:nnen. Es mag ein
   Macintosh(R) oder irgendein anderer Rechner sein.

   Sie mo:gen nun den Kopf schu:tteln: Mein Programm ist in PC-Assembler
   geschrieben, wie kann es auf einem Macintosh(R) laufen? Aber ich habe
   nicht gesagt, dass Ihr Programm auf Macintosh(R) la:uft, nur sein Terminal
   mag ein Macintosh(R) sein.

   Unter UNIX(R) muss der Terminal nicht direkt am Rechner angeschlossen
   sein, auf dem die Software la:uft, er kann sogar auf einem anderen
   Kontinent sein oder sogar auf einem anderen Planeten. Es ist nicht
   ungewo:hnlich, dass ein Macintosh(R)-Nutzer in Australien sich auf ein
   UNIX(R)-System in Nordamerika (oder sonstwo) mittels telnet verbindet. Die
   Software la:uft auf einem Rechner wa:hrend das Terminal sich auf einem
   anderen Rechner befindet: Falls Sie versuchen sollten die Scancodes
   auszulesen werden Sie die falschen Eingaben erhalten!

   Das Gleiche gilt fu: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
   mo:glicherweise in einem Space Shuttle, durch Satelliten mit Ihnen
   verbunden.

   Das sind die Gru:nde, weshalb Sie niemals unter UNIX(R) Annahmen treffen
   du:rfen, woher Ihre Daten kommen oder gehen. Lassen Sie immer das System
   den physischen Zugriff auf die Hardware regeln.

  Anmerkung:

   Das sind Vorsichtsmassnahmen, keine absoluten Regeln. Ausnahmen sind
   mo:glich. Wenn zum Beispiel ein Texteditor bestimmt hat, dass er auf einer
   lokalen Maschine la:uft, dann mag er die Tastatur-Scancodes direkt
   auslesen, um eine bessere Kontrolle zu gewa:hrleisten. Ich erwa:hne diese
   Vorsichtsmassnahmen nicht, um Ihnen zu sagen, was sie tun oder lassen
   sollen, ich will Ihnen nur bewusst machen, dass es bestimmte Fallstricke
   gibt, die Sie erwarten, wenn Sie soeben ihn UNIX(R) von MS-DOS(R)
   angelangt sind. Kreative Menschen brechen oft Regeln und das ist in
   Ordnung, solange sie wissen welche Regeln und warum.

11.15. Danksagungen

   U:bersetzt von Daniel Seuffert.

   Dieses Handbuch wa:re niemals mo:glich gewesen ohne die Hilfe vieler
   erfahrener FreeBSD-Programmierer aus FreeBSD technical discussions. Viele
   dieser Personen haben geduldig meine Fragen beantwortet und mich in die
   richtige Richtung gewiesen bei meinem Versuch, die tieferen liegenden
   Mechanismen der UNIX(R)-Systemprogrammierung zu erforschen im Allgemeinen
   und bei FreeBSD im Besonderen.

   Thomas M. Sommers o:ffnete die Tu:ren fu:r mich. Seine Wie schreibe ich
   "Hallo Welt" in FreeBSD-Assembler? Webseite war mein erster Kontakt mit
   Assembler-Programmierung unter FreeBSD.

   Jake Burkholder hat die Tu:r offen gehalten durch das bereitwillige
   Beantworten all meiner Fragen und das Zurverfu:gungstellen von
   Assembler-Codebeispielen.

   Copyright (c) 2000-2001 G. Adam Stanislav. Alle Rechte vorbehalten.

                                 Teil V. Anhang

   Inhaltsverzeichnis

   Literaturverzeichnis

Literaturverzeichnis

   [1] Dave A Patterson und John L Hennessy. Copyright (c) 1998 Morgan
   Kaufmann Publishers, Inc.. 1-55860-428-6. Morgan Kaufmann Publishers,
   Inc.. Computer Organization and Design. The Hardware / Software Interface.
   1-2.

   [2] W. Richard Stevens. Copyright (c) 1993 Addison Wesley Longman, Inc..
   0-201-56317-7. Addison Wesley Longman, Inc.. Advanced Programming in the
   Unix Environment. 1-2.

   [3] Marshall Kirk McKusick und George Neville-Neil. Copyright (c) 2004
   Addison-Wesley Publishing Company, Inc.. 0-201-70245-2. Addison-Wesley.
   The Design and Implementation of the FreeBSD Operating System. 1-2.

   [4] Aleph One. Phrack 49; "Smashing the Stack for Fun and Profit".

   [5] Chrispin Cowan, Calton Pu und Dave Maier. StackGuard; Automatic
   Adaptive Detection and Prevention of Buffer-Overflow Attacks.

   [6] Todd Miller und Theo de Raadt. strlcpy and strlcat -- consistent, safe
   string copy and concatenation..

                              Stichwortverzeichnis

  A

   Arguments, Puffer-U:berla:ufe

  B

   Beigesteuerte Software, Beigesteuerte Software

   Benutzer-IDs

                effective Benutzer-ID, SetUID-Themen

                reale Benutzer-ID, SetUID-Themen

  C

   chroot(), Die Umgebung ihrer Programme einschra:nken

   Core-Team, Belastende Dateien

  D

   Datenvalidierung, Vertrauen

  F

   Frame-Pointer, Puffer-U:berla:ufe

  G

   GCC, Compiler-basierte Laufzeitu:berpru:fung von Grenzen

   GTK, I18N-konforme Anwendungen programmieren

  J

   Jail, Die Jail-Funktionalita:t in FreeBSD

  L

   LIFO, Puffer-U:berla:ufe

  M

   Morris Internetwurm, Puffer-U:berla:ufe

  N

   NUL-Terminierung, Puffer-U:berla:ufe vermeiden

  O

   OpenBSD, Puffer-U:berla:ufe vermeiden

  P

   Perl, Perl und Python

   Perl Taint-Modus, Vertrauen

   Ports-Maintainer, MAINTAINER eines Makefiles

   positive Filterung, Vertrauen

   POSIX.1e Process Capabilities, POSIX(R).1e Prozess Capabilities

   ProPolice, Compiler-basierte Laufzeitu:berpru:fung von Grenzen

   Prozessabbild

                Frame-Pointer, Puffer-U:berla:ufe

                Stack-Pointer, Puffer-U:berla:ufe

   Pru:fung von Grenzen

                Bibliotheks-basiert, Bibliotheks-basierte
                Laufzeitu:berpru:fung von Grenzen

                Compiler-basiert, Compiler-basierte Laufzeitu:berpru:fung von
                Grenzen

   Puffer-U:berlauf, Puffer-U:berla:ufe, Compiler-basierte
   Laufzeitu:berpru:fung von Grenzen

   Python, Perl und Python

  Q

   Qt, I18N-konforme Anwendungen programmieren

  R

   Race-Conditions

                O:ffnen von Dateien, Race-Conditions

                Signale, Race-Conditions

                Zugriffspru:fungen, Race-Conditions

   Release-Engineer, Belastende Dateien

   Ru:cksprungadresse, Puffer-U:berla:ufe

  S

   seteuid, SetUID-Themen

   Stack, Puffer-U:berla:ufe

   Stack-Frame, Puffer-U:berla:ufe

   Stack-Pointer, Puffer-U:berla:ufe

   Stack-U:berlauf, Puffer-U:berla:ufe

   StackGuard, Compiler-basierte Laufzeitu:berpru:fung von Grenzen

   style, Stil-Richtlinien

  T

   TrustedBSD, POSIX(R).1e Prozess Capabilities

  V

   Von-Neuman, Puffer-U:berla:ufe

  Z

   Zeichenketten-Kopierfunktioen

                strncpy, Puffer-U:berla:ufe vermeiden

   Zeichenketten-Kopierfunktionen

                strlcat, Puffer-U:berla:ufe vermeiden

                strlcpy, Puffer-U:berla:ufe vermeiden

                strncat, Puffer-U:berla:ufe vermeiden
