Dieses Tutorial ist lediglich ein kurzer Ueberblick in einige wichtige Entwicklungswerkzeuge die bei der Programmierung unter Linux/Unix hilfreich sein koennen (bzw. mir waren und noch immer sind). Sollten sich inhaltliche Fehler eingeschlichen haben, dann schickt mir einfach eine Mail strcat@gmx.net und ich werde sie beheben. Ich habe diese Uebersicht geschrieben, weil ich zur Zeit an meiner eigenen Distrubtion schreibe und mich deswegen sehr haeufig mit diesen Entwicklungsprogrammen auseinandersetzen muss. Dieses Tutorial richtet sich hauptsaechlich an User mit Programmierkenntnissen und dient mir als Nachschlagewerk.
Alle Beispiele und Kommandos beziehen sich auf Linux (Version 2.6.6); Bei Verwendung von {Free,Net,Open}BSD kann es zu Inkompatiblitaet kommen. So ist per Default strace auf OpenBSD nicht vorhanden (OpenBSD verwendet ktrace). Die eingesetzten Programmversionen sind folgende:

  • gcc version 3.3.4

  • GNU ld version 2.15.90.0.3 20040415

  • GNU gdb 6.1.1

  • strace — version 4.5.4

  • GNU ar 2.15.90.0.3 20040415

  • ldconfig (GNU libc) 2.3.2

  • GNU Make 3.80

gcc - Der GNU C-Compiler

Die meisten Kommandozeilenangaben des GNU-Compilers gcc entsprechen denen anderer C-Compilier unter Linux/Unix, jedoch gibt es auch einige gcc-spezifische Angaben und Eigenheiten. Hier werden die wichtigsten Kommandozeilenangaben und Eigenschaften des gcc-Compiliert kurz vorgestellt. Fuer weitergehende Informationen sollte man die Manpage von gcc aufrufen.

$ gcc [option(en)] datei(en)
$ cc [option(en)] datei(en)
$ g++ [option(en)] datei(en)
$ c++ [option(en)] datei(en)

Klassifikation der Dateitypen durch Suffixe

gcc ist das Kommando zum Aufruf des GNU-C-Compilers. Es erzeugt ausfuehrbare Programme, indem es die angegebenen Datei(en) kompiliert. bzw. assembliert, bevor es den Linker ld aufruft, um die entsprechenden Objectdateien zu einem ausfuehrbaren Programm zusammenbinden zu lasen. Die Voreinstellung ist, dass gcc das erzeugte Programm in einer Datei mit dem Namen a.out ablegt. Als Datei(en) akzeptiert gcc eine ganze Reihe von Dateitypen, die gcc dabei ueber das Suffix klassifiziert Die wichtigsten Suffixe sind folgende:

.c

Ein C-Quellprogramm wird zunaechst in eine Objectdatei uebersetzt, wobei fuer den Namen der Objectdatei das Suffix .c durch .o ersetzt wird. Falls nur ein C-Quellprogramm beim Aufruf des Compiliers angegeben ist, wir die .o-Datei sofort gelinkt und dann geloescht.'

.h

C-Headerdatei

.C

C-Quellprogramm

.cc

C-Quellprogramm

.cxx

C-Quellprogramm

.m

Objective C-Quellprogramm

.s

Ein Assembler-Quellprogramm wird zunaechst assembliert und daraus dann eine Objectdatei erstellt, wobei fuer den Namen der Objectdate das Suffix .s durch .o ersetzt wird. Falls nur ein Assembler-Quellprogramm beim Aufruf des Compiliers angegeben ist, wir die .o-Datei sofort gelinkt und dann geloescht.

.S

Assembler-Quellprogramm. Anders als bei der Endung .s wird ein solches Assemblerprogramm auf durch den Praeprozessort geschickt

.i

Vom Praeprozessor vorverarbeitetes C-Quellprogramm. Ein solches vorverarbeitetes C-Quellprogramm wird zunaechst in eine Objectdatei uebersetzt, wobei fuer den Namen der Objectdatei das Suffix .i durch .o ersetzt wird. Falls nur eine .i-Datei beim Aufruf des Compilers angegeben ist, wie die .o-Datei sofort gelinkt und dann geloescht.

.ii

Vom Praeprozessor vorverarbeitetes C-Quellprogramm.

Note

gcc legt normalerweise seine uebersetzten Dateien im Working-Directory ab; deshalb ist es wichtig, dass das Working-Directory nicht schreibgeschuetzt ist.

Wichtige Optionen

Jetzt folgt eine Auflistung wichtiger Optionen, die beim Arbeiten mit gcc haeufig benoetigt werden

-ansi

schaltet den ANSI-C-Standard ein, so dass nur ANSI-C-Konstrukte in den zu kompilierenden Quellprogrammen verwendet werden koennen.

-c

(compile only) die angegebenen Quellprogramme nur kompilieren und nicht linken. In diesem Fall werden die erzeugten Objectdateien (Suffix .o) nicht geloescht.

-C

(Comment) veranlasst den Praeprozessor, alle Kommentarzeilen an den Compiler weiterzuleiten. Ausnahme sind dabei Kommentare, die in Zeilen mit Praeprozessoranweisungen stehen; wird oft mit der Option -E benutzt

-Dname[=wert]

(Define) definiert den Namen name fuer den Praeprozessort, als ob dieser Name mit #define in jedem Quellprogramm definiert waere. Fall nur -Dname angegeben ist, entspricht die der Angabe -Dname=1. Wird fuer wert ein String angegeben, muss die Interpretation der Anfuehrungszeigen durch die Shell ausgeschlatet werden, wie z. B. -D"sprache=german" oder -D\"sprache=german\". Sollte der String Leerzeichen enthalten, empfiehtl sich die erste Angabeform.

-E

die angegebenen Quellprogamme werden nur durch den Praeprozessor geschickt und das Ergebnis wird auf der Standardausgabe ausgegeben.

-g, -ggdb

(debug) fuegt Debug-Informationen zum generierten Programm bzw. zu den Objectdateien hinzu. -g veranlasst gcc, nur Standard-Debug-Informationen hinzuzufuegen, waehrend -ggdb dagegen bewirkt, dass gcc spezielle Debug-Informationen hinzufuegt, die nur der Debugger gdb versteht. gcc kann im uebrigen - anders als andere Compiler - auch fuer optimierten Code Debug-Informationen generieren.

-ldirectory

(Include-directory) fuegt das angegebene directory zur Liste der Directories hinzu, in denen nach #include-Dateien zu suchen ist. Die voreingestellte Suche fuer in spitzen Klammern angegebene #include-Dateien ist das Directory /usr/include und fuer in Anfuehrungszeichen angegebene #include-Dateien das Working-Direztory.

-lname

(libary) verwendet zum linken die Bibliothek libname.so bzw. libname.a. Wenn nicht anders vorgesehen, verwendet gcc zum Linken dynamische Bibliotheken (libname.so) statt statischer Bibliotheken (libname.a). Der Linker sucht nach Funktionen (unresolved references) in allen angegebenen Bibliotheken in der Reihenfolge, in der diese angegeben sind, bis jeweils der erste passende Eintrag gefunden wurde.

-Ldirectory

(Libary) fuegt das angegebene Directory zur Liste der Directories hinzu, in denen nach Bibliotheksdateien zu suchen ist. Wenn nicht anders angegeben, zieht der gcc dynamische Bibliotheken (shared libaries) vor. Das voreingestellt Directory fuer die Suche nach Bibliotheken ist /usr/lib.

-o name

(output) Normalerweise erzeugt gcc eine Ausgabedatei mit dem Namen a.out. Wird ein anderer Name fuer die von gcc erzeugte Datei gewuenscht, ist die mit dieser Option moegilch. Diese Option ist auch sehr nuetzlich, wenn die Ausgabedatei(en) in ein anderes Directory anzulegen sind.

-O, -On

(Optimize) schaltet den Optimierer ein. Ueber die Angabe einer Ziffer kann man diese Optimierungsstufe festlegen. -O ohne Angabe einer Ziffer entspricht der niedrigsten Optimierungsstufe (-O1). -O0 schaltet die Optimierung aus. -O3 die die zur Zeit hoechte Optimierungsstufe.

-p

(Profiling) fuegt in den Objectdateien zusaetzlichen Profilingcode hinzu. Der Profilingcode zaehlt mit, wie oft die einzelnen Funktionen aufgerufen werden und schreibt diese Information in die Datei gmon.out. Mit Hilfe von gprof kann daraus dann nach dem Programmlauf eine lesbare Protokolldatei generiert werden.

-pendantic

weist gcc an, alle Warnungen und Fehlermeldungen auszugeben, die vom ANSI-C-Standard gefordert werden.

-static

zum Linken werden nur statische Bibliotheken verwendet.

-S

Die angegebenen C-Dateien werden uebersetzt, jedoch nicht assembliert oder gelinkt. Die dabei erzeugten Assemblerprogramme werden in Dateien mit dem Suffix .s abgelegt.

-Uname

(Undefine) Definition des Namens name fuer den Praeprozessor aufheben, so als ob die Definition fuer name mit #undef in jedem Quellprogramm aufgehoben worden waere. Falls derselbe Name sowohl in einer -D, als auch in einer -U Option erwaehnt ist, so hat -U die hoeherer Prioritaet.

-Wall

Aktiviert alle im allgemeinen sinnvollen Warnungen, ueber die gcc verfuegt. Mit dieser Option erreicht man einen aehnlich sicheren Code, wie wenn man den Syntaxpruefer lint auf seine Quellprogramme anwenden wuerde. gcc erlaubt es jedoch, einzelne Warnungen an- oder auszuschalten. Um sich alle diese Warnungstypen auflisten zu lassen, sollte man sich die Manpage von gcc ansehen.

C-Erweiterungen im gcc

gcc bietet einige Konstrukte an, die nicht von ANSI C vorgeschrieben sind. Nachfolgend sind einige wichtige solcher Konstrukte beschrieben.

Der Datentyp long long

Der Datentyp long long steht fuer eine Speichereinheit, die mindestens so viele Bytes wie long umfasst. Auf 32-Bit-Plattformen ist long 32 Bit und long long 64 Bit gross. Auf 64-Bit-Plattformen sind sowohl long, als auch long long 64 Bit gross; dasselbe gilt auf diesen Plattformen fuer Zeiger.

inline-Funktionen

Von dieser Art von Funktionen wird insbesondere in den Linux-Kernprogrammen Gebrauch gemacht. Die Funktionen laufen so schnell wie Makros ab, da das Stackmanagement entfaellt, andererseits bieten die inline-Funktionen die Vorteile von Funktionen (Typueberpruefung der Argumente, Auswertung der Argumente vor dem Funktionsaufruf, …) an. Programme die inline-Funktionen verwenden, muessen wenigstens mit der minimalen Optimierung (-O bzw. -O0) kompiliert werden.

Zusaetzliche alternative Schluesselwoerter

gcc bietet eine Reihe von zusaetzlichen Schluesselwoertern an, die nicht von ANSI C vorgeschrieben sind. Solche zusaetzlichen Schluesselwoerter werden von gcc in zwei Varianten angeboten: einmal das Schluesselwort selbst (wie z. B. attribute) und zum anderen das Schluesselwort mit zwei vorangestellen und zwei angefuegten Unterstricken (wie z. B. __attribute__). Wird gcc mit der Option -ansi aufgerufen, kann er die zusaetzlichen normalen Schluesselwoerter nicht erkennen. Deshalb wurde zu jedem zusaetzlichen Schluesselwort alternativ in den Headerdateien ein entsprechender Datentyp mit zwei vorangestellen und zwei angefuegten Unterstrichen angeboten.
Das zusaetzliche Schluesselwort attribut ermoeglicht es, gcc mehr Informationen ueber eine Funktion, Variable oder einen Datentyp zu geben, als dies mit den Standardkonstrukten von ANSI C moeglich ist. Nachfolgend sind einige moegliche Attribute aufgefuehrt:

aligned

legt fest, wie eine Variable oder Datenstruktur im Speicher anzuordnen ist.

packed

legt fest, dass bei der Ausrichtung der Daten keine Luecken verwendet werden sollen.

noreturn

legt fest, dass eine Funktion nie zum Aufrufer zurueckkehrt, was es gcc ermoeglicht, besseren Code zu generieren.

Attribute fuer Funktionen muessen der Funktionsdeklaration hinzugefuegt werden, wie z. B.:

void function(int, flot) __attribute__((__noreturn__));

Das Schluesselwort \\Schluesselwort attribut ist nach den Funktionsparametern, gefolgt von dem zu setzendem Attribut, das sich in doppelten Klammernpaaren befindet, anzugeben. Sollen mehrere Attribute gesetzt werden, muessen diese mit Kommata getrennt werden, wie z. B.:

extern void ext3_panix (struct super_block *. const char *.
        const char *, ...)
__attribute__ ((noreturn, format(printf, 3, 4)));

Diese Deklaration legt fest, dass ext3_panic nicht zur aufrufenden Funktion zurueckkehrt und das die uebergebenen Argumente (ab dem dritten) wie bei der Funktion printf zu behandeln sind: Das dritte Argumente legt den Formatierungsstring fest und das vierte Argument ist der erste zu ersetzende Parameter im Formatierungsstring. Spaeter werden weitere Attribute (z. B. beim Erzeugen von dynamischen Bibliotheken) vorgestellt. Alle moeglichen Attribute stehen in der Manpage von gcc.

Der Linux/Unix-Linker

ld ist der Linux/Unix-Linker, der mehrere Objectdateien zu einem ausfuehrbaren Programm zusammenbindet. Objectdateien erkenn ld am Suffix .o. Archivbibliotheken, in denen der Linker nach unresolved references suchen soll, erkennt er am Suffix .a. Fal einer der angegebenen Dateien weder das .o-, noch das .a-Suffix hat, so nimmt ld an, dass es sich um eine Archivbibliothek oder um eine Textdatei, die Link-Editordirektiven enthaelt, handelt. Die Voreinstellung ist, dass ld das erzeugte Programm in einer Datei mit dem Namen a.out ablegt, wenn keine Fehler aufgetreten sind; ansonsten bricht ld mit einer Fehlermeldung ab. Explizit auf der Kommandozeile angegebene Bibliotheken werden nur nach unresolved references durchsucht, die aus zuvor angegebenen Objectdateien resultieren. Allgemein gilt, dass man alle Objectdateien vor den Bibliotheken auf der Kommandozeile angeben sollte.

Einige wichtige Optionen

Da ld auf den einzelnen Systemen auch die unterschiedlichsten Optionen anbietet, werde ich hier nur die wichtigsten Optionen vorstellen, die auch auf den meisten Systemen gueltig sind. Um spezielle fuer ein System angebotetnen Optionen zu erfahren, sollte man die Manpage von ld oder die mitgelieferten Dokumentationen durchlesen.

-e startsymbol (entry)

Die Adresse des Symbols startsymbol soll die Startaddresse fuer das erzeugte ausfuehrbare Programm sein.

-lname (libary)

verwendet zum Linken die Bibliothek libname.so bzw. libname.a. Wenn nicht anders vorgegeben, verwendet gcc zum Linken dynamische Bibliotheken (libname.so) statt statischer Bibliotheken (libname.a). Der Linker sucht nach Funktionen (unresolved references) in allen angegebenen Bibliotheken in der Reihenfolge, in der diese angegeben sind, bis jeweils der erste passende Eintrag gefunden wurde.

-Ldirectory (Libary)

fuegt das angegebene Verzeichnis zur Liste der Directories hinzu, in denen nach Bibliotheksdateien zu suchen ist. Wenn nicht anders angegeben, zieht gcc dynamische Bibliotheken (shared libaries) der Verwendung von statischen Bibliotheken (static libaries) vor. Das voreingestellte Directory fuer die Suche nach Bibliotheken ist /usr/lib.

-o name (output)

Normalerweise erzeugt ld eine Ausgabedatei mit dem Namen a.out. Wird ein anderer Name gewuenscht, so ist dies mit dieser Option moeglich.

-s (strip)

entfernt Zeilennimmerneintraege und Symboltabelleninformationen bei der Generierung des ausfuehrbaren Programms.

-u symbolname (undefine)

bewirkt, dass symbolname als undefiniertes Symbol in der Symboltabelle eingetragen wird. Das ist beim ausschliesslichen Laden einer Bibliothek nuetzlich, da die Symboltabelle anfaenglich leer ist und mindestens eine unresolved reference benoetigt wird, um ld zu zwingen, Funktionen aus einer Bibliothek in das Programm zu uebernehmen. Diese Option muss unbedingt vor dem entsprechenden Bibliotheksnamen auf der Kommandozeile angegeben werden.

Da ld automatisch von cc bzw. gcc aufgerufen wird, nachdem cc bzw. gcc alle C- und Assemblerprogramme assembliert bzw. kompiliert hast, wird meist cc bzw. gcc zur Erzeugung eines ausfuehrbaren Programms verwendet.

gdb - Der GNU-Debugger

db ist der uebliche Debugger unter Linux/Unix. Hier wird der GNU-gbd von der Free Software Foundation beschrieben, dessen Bedienung weitgehend der entspricht, wie sie auch fuer die unter anderen Unix-Systemen angebotenen Debuggern des gleichen Namens gilt. Der hier beschriebene gdb ist ein kommandozeilenorientierter Debugger, zu dem es inzwischen mehrere graphische Debugger angeboten werden, wie z. B.:

xxgdb

ist eine graphische Oberflaeche zum GNU-Debugger gdb und ermoeglicht ein leichtes Debuggen von C-Programmen, indem man im eingeblendeten Quellcode mit der Mouse Breakpoints setzen kann, sich den Inhalt von Variablen und des Stacks anzeigen lassen kann, ..

ddd

ist wie xxgdb eine graphische Oberflaeche zum GNU-Debugger gdb und ermoeglicht ein ebenso leichtes Debuggen von C-Programmen.

kdbg

ist eine beim Desktopenvironment KDE mitgelieferte graphische Oberflaeche zum GNU-Debugger gdb

Um sich einen ersten Ueberblick ueber die von gdb angebotenen Kommandos zu verschaffen, empfiehlt es sich, gdb zu starten und dann das gdb-Kommando help aufzurufen.

$ gdb
(gdb) help
List of classes of commands:

aliases -- Aliases of other commands
breakpoints -- Making program stop at certain points
data -- Examining data
files -- Specifying and examining files
internals -- Maintenance commands
obscure -- Obscure features
running -- Running the program
stack -- Examining the stack
status -- Status inquiries
support -- Support facilities
tracepoints -- Tracing of program execution without stopping the program
user-defined -- User-defined commands

Type "help" followed by a class name for a list of commands in that class.
Type "help all" for the list of all commands.
Type "help" followed by command name for full documentation.
Type "apropos word" to search for commands related to "word".
Command name abbreviations are allowed if unambiguous.
(gdb)

In diesem Beispiel kann man unter anderem sehen, dass man sich detailliertere Informationen zu den einzelnen gdb-Kommandos anzeigen lassen kann, indem man help commando eingibt. Wie oben gezeigt ist es auch moeglich, gdb ohne Angabe von Argumenten zu starten, aber ueblicherweise ruft man den gdb jedoch mit einem Argument, dem Namen des zu debuggenden Programms, auf. Dazu uebergibt man gdb den Namen einfach als Argument: gdb programm. Zusaetzlich zum Namen des zu debuggenden Programms kann auch eine core-Datei angegeben werden, die bei einem vorherigen Start des Programms vom System generiert wurde. Der Programmaufruf waere hier gdb programm core-file. Der gdb bietet auch die Moeglichkeit, ein gerade laufendes Programm zu debuggen. Dazu muss der gdb sich mit diesem laufenden Prozess verbinden koennen, was man durch gdb programm PID erreicht.
Im gdb ist es nicht notwendig, die entsprechenden gdb-Kommandos vollstaendig auszuschreiben, sondern diese koennen auch abgekuerzt werden. So kann z. B. statt dem Kommando running nur run oder r verwendet werden. Um das letzte Kommando zu wiederhole, muss man lediglich die Return-Taste druecken, was das schrittweise Debuggen eines Programms erheblich erleichtert. Einige gdb-Kommandos koennen auch mit Formatangaben aufgerufen werden, um das Ausgabeformat von Werten festzulegen. Solche Formatangaben muessen mit / beginnend unmittelbar nach dem entsprechenden gdb-Kommando angegeben werden. Formatangaben bestehen aus vier Komponenten: /zfg, die im einzelnen folgende Bedeutung haben:

z

Fuer das optionale z ist ein Wiederholungszaehler (Voreinstellung 1) anzugeben.

f

Fuer f ist ein Formatbuchstabe anzugeben: o (oktal), x (hexadezimal), d (dezimal), u (unsigned), t (binaer), f (float), i (instruction), c (char) oder s (string).

g

Fuer das optionale g ist eine Groesse anzugeben: b (byte), h (halfword, 2 bytes), w (word, 4 bytes) oder g (giant, 8 bytes). Die Voreinstellung fuer die Groesse ist ein zur Formatangabe passender Wert.

Hat man einmal fuer ein gdb-Kommando eine Formatangabe festgelegt, muss man diese bei einem erneuten Aufruf des Kommandos nicht wieder eingeben, da gdb dann immer die zuletzt definierte Formatangabe wiederverwendet. Nachfolgend sind die am haeufigsten benutzten gdb-Kommandos kurz aufgelistet:

attach, at

gdb soll sich mit einem gerade ablaufenden Prozess verbinden. Dazu ist beim Aufruf von attach die PID des entsprechenden Prozesses anzugeben. Dieses Kommando haelt den Prozess an, an dem sich gdb anhaengen soll. Ein Losloesen von einem solchen Prozess ist mit dem gdb-Kommando detach moeglich.

backtrace, bt

zeigt den aktuellen Stack-Inhalt an.

break, b

setzt einen breakpoint. Als Argument kann dabei ein Funktionsname, eine Zeilennummer der gerade aktiven Datei (Datei, deren Code gerade ausgefuehrt wird), ein Dateiname gefolgt von einer Zeilennummer (dateiname:zeilennummer) oder sogar eine beliebige Adresse (\*Addresse) angegeben werden. gdb vergibt an jeden Breakpoint eine Nummer, welche er dem Benutzer auch mitteilt.

clear, cl

loescht einen Breakpoint. Als Argumente sind die gleichen Argumente wie bei break erlaubt.

condition, cond

legt fuer einen Breakpoint, dessen Nummer hier als erstes Argument anzugeben ist, eine Bedingung fest, die mit den weiteren Argumenten festgelegt wird, wie z. B. condition 2 zrg == NULL Die Ausfuehrung des Programms wird dann an diesem Breakpoint nur noch angehalten, wenn die angegebene Bedingung zu diesem Zeitpunkt erfuellt ist.

continue, c

setzt die Ausfuehrung eines angehaltenen Programms fort.

delete, d

loescht einen Breakpoint. Die Nummer des zu loeschenden Breakpoints muss als Argument uebergeben werden. Ist keine Nummer angegeben, werden alle Breakpoints geloescht.

display, disp

zeigt den Wer eines Ausdruckts, der durch die angegebenen Argumente festgelegt wird, jedesmal an, wenn die Ausfuehrung des Programms angehalten wird. Ueber eine zusaetzliche Formatangabe kann man dabei noch festlegen, wie dieser Wert auszugeben ist. Jedem mit display anzuzeigenden Ausdruckt wird von gdb eine Nummer zugeteilt, die er dem Benutzer mitteilt. Um das automatische anzeigen eines Werts fuer einen Ausdruck wieder auszuschalten, muss undisplay mit dieser Nummer aufgerufen werden. Wird undisplay ohne jegliche Argumente aufgerufen, werden alle automatischen Anzeigen, die mit display eingerichtet wurde, ausgeschaltet.

help, h

gibt Hilfsinformationen aus. Ohne Argumente wird eine kurze Zusammenfassung der verfuegbaren Hilfe angezeigt.

list, l

zeigt die ersten 10 Zeilen um die aktuelle Zeile der Datei, deren Code gerade ausgefuehrt wird, an. Aufeinanderfolgende Aufrufe von list zeigen immer die naechsten 10 folgenden Zeilen an. Wird eine Zahl als Argument angegeben, dann werden 10 Zeilen einer negativen Zahl als Argument moeglich. Mit der Angabe eines Dateinamens gefolgt von einer Zeilennummer (dateiname:zeilennummer) werden nur 10 Zeilen aus der Datei dateiname und diese Zeilennummer angezeigt. Wird als Argument ein Funktionsname angegeben, werden die ersten 10 Zeilen dieser Funktion aufgelistet. Bei einem Argument in der Form \*Addresse werden die Zeilen angezeigt, die den Code zu dieser Adresse umgeben.

next, n

setzt ein angehaltenes Programm fort, indem es den Code bis zur naechsten Zeile des Quellprogramms ausfuehrt. Handelt es sich bei der aktuellen Zeile des Quellprogramms um eine Funktion, so wird diese vollstaendig ausgefuehrt. Soll diese Funktion schrittweise durchlaufen werden, muss das gdb-Kommando step verwendet werden.

nexti

setzt ein angehaltenes Programm fort, indem es den Code bis zum naechsten Assemblerbefehl ausfuehrt. Funktionsaufrufe werden dabei vollstaendig ausgefuehrt. Soll eine Funktion schrittweise durchlaufen werden, muss man hier ebenfalls das gdb-Kommando step anwende.

print, p

gibt den Wer des Ausdrucks, der ueber die Argumente festgelegt wird, in der entsprechend festgelegten Form aus. Moechte man sich z. B. die Adresse in einem int-Zeiger (int *zgr) ausgeben lassen, muss man print *zgr eingeben. Bei der Ausgabe von Strukturvariablen mit print werden die einzelnen Komponenten dieser Struktur angezeigt. Ueber eine zusaetzliche Formatangabe kann man bei print noch festlegen, wie die entsprechenden Werte auszugeben sind.

run, r

startet das aktuelle Programm von Begin an. Die Argumente fuer run sind die Argumente, die man beim Aufruf des Programms auf der Kommandozeile angeben wuerde. Dabei koennen die Shell-Metazeichen fuer Dateinamensexpandierung (*, [] usw.) genauso angegeben werden wie Zeichen zur Ein-/Ausgabeumlenkung (>, >> usw.). Pipes dagegen sind hier nicht erlaubt. Wird run ohne Argumente aufgerufen, benutzt es die Argumente des letzten run-Aufrufs oder die Argumente, die mit dem letzten set args - Kommando festgelegt wurden.

set

weist Variablen Werte zu. Um die Kommandozeilenargumente fuer das Programm, das man gerade mit gdb analysiert, nachtraeglich festzulegen oder neu zu setzen, steht das Kommando set args .. zur Verfuegung. Das set-Kommando verfuegt ueber eine Vielzahl von weiteren Subkommandos, die man sich mit help set anzeigen lassen kann.

step, s

setzt ein angehaltenes Programm fort, indem es den Code bis zur naechsten Zeile des Quellprogramms ausfuehrt. Handelt es sich bei der aktuellen Zeile des Quellprogramms um eine Funktion, wird nur die erste Zeile dieser Funktion ausgefuehrt. Soll diese Funktion vollstaendig durchlaeufen werden, muss das gdb-Kommando next verwendet werden.

stepi

setzt ein angehaltenes Programm fort, indem es den Code bis zum naechsten Assemblerbefehl ausfuehrt. Handelt es sich bei der aktuellen Zeile des Quellprogramms um eine Funktion, wird nur die erste Assembleranweisung dieser Funktion ausgefuehrt. Soll diese Funktion vollstaendig durchlaeufen werden, muss das gdb-Kommando next verwendet werden.

quit, q

beendet den gdb.

whatis, wha

zeigt den Datentyp des als Argument uebergebenen Ausdrucks an.

where, whe

zeigt den aktuellen Stack-Inhalt an.

x

zeigt den Inhalt von Speicher an. x verhaelt sich weitgehend wie print, kann jedoch nur den Inhalt von Addressen, die als Argumente anzugeben sind, in einer beliebigen Form anzeigen. Die Form, in der ein Speicherbereich anzuzeigen ist, kann ueber eine zusaetzliche Formatangabe festgelegt werden.

strace - Mitprotokollieren aller Systemaufrufe

Bei der Fehlersuche in Programmen kann das Kommando strace , das jeden Aufruf einer Systemfunktion mitprotokolliert, wertvolle Dienste leisten. Moechte man sich z. B. alle Aufrufe von Systemfunktionen beim Ablauf des Kommandos date anzeigen lassen, muss man nur strace date aufrufen.

strace verfolgt den Ablauf des Kommandos command mit und gibt alle Systemaufrufe und Signale auf die Standardfehlerausgabe oder - wenn die Option -o file angegeben ist - in die Datei file Jede Zeile der Ausgabe enthaelt einen Systemaufruf, seine Argumente in Klammern und den Rueckgabewert, wie z. B.:

open("file.txt", O_RDONLY) = 3

Bei einem Fehler (meist der Rueckgabewert -1) wird die Fehlernummer (als symbolischer Name) und die zugehoerige Fehlermeldung mitausgegeben, wie z. B.:

open("filenotexist.txt", O_RDONLY) = 1 ENOENT (No such file or directory)

Neuere Versionen von strace rufen stat64 bzw. lstat64, anstelle von open auf:

stat64("filenotexist.txt", 0x805a0d4)       = -1 ENOENT (No such file or directory)
lstat64("filenotexist.txt", 0x805a0d4)      = -1 ENOENT (No such file or directory)

bzw.

stat64("brief.txt", {st_mode=S_IFREG|0600, st_size=4, ...}) = 0
lstat64("brief.txt", {st_mode=S_IFREG|0600, st_size=4, ...}) = 0
Note
Weiterhin gilt folgendes:
  • Signale werden mit ihren Signalnummern ausgegeben.

  • Argumente werden (wenn moeglich) in lesbarer Form ausgegeben.

  • Bei Zeigern auf Strukturen werden nich die in den Zeigern enthaltenen Addressen, sondern die einzelnen Komponenten der Strukturen (in geschweiften Klammern) ausgegeben, auf die diese Zeiger zeigen.

  • Bei Zeigern auf Zeichenketten werden nicht die in den Zeigern enthaltenen Addressen, sondern die Zeichenketten (in Anfuehrungszeichen) ausgegeben, auf die diese Zeiger zeigen.

  • Nicht druckbare Zeigen werden, wie in C ueblich, als Escape-Sequenzen ausgegeben.

  • Waehrend fuer Strukturen geschweifte Klammern verwendet werden, zeigen eckige Klammern Arrays an.

Optionen von strace

Nachfolgend eine kurze Beschreibung zu einigen Optionen von strace

-c

erstellt eine Zeitstatistik fuer jeden Systemaufruf und gibt diese am Ende aus.

-d

gibt eigene Debug-Informationen aus

-f

kreiert ein mit strace ueberwachter Prozess mit fork Kindprozesse, werden die Systemaufrufe dieser Kindprozesse ebenfalls protokolliert.

-ff

Wenn die Option -o file angegeben ist, werden die Systemaufrufe jedes Kindprozesses in der Datei file.pid protokolliert, wobei pid die PID des jeweiligen Kindprozesses ist.

-h

gibt Help-Informationen aus.

-i

Am Anfang jeder Zeile wird der Befehlszaehler (Instructions-Pointer) zum Zeitpunkt des Systemaufrufs ausgegeben.

-q

unterdrueckt die Meldungen ueber das Anhalten und Freigeben von Prozessen. Dies geschieht automatisch, wenn die Ausgabe in eine Datei umgelenkt wird. Diese Option ist nur sinnvoll in Verbindung mit den Optionen -f oder -p pid.

-r

Zu jedem Systemaufruf wird der Zeitabstand zum vorherigen Systemaufruf in Sekunden und Mikrosekunden ausgegeben.

-t

Am Anfang jeder Zeile wied die aktuelle Uhrzeit im Format hh:mm:ss ausgegeben.

-tt

wie -t, nur das noch die Mikrosekunden mitausgegeben wird.

-ttt

Am Anfang jeder Zeile wird die aktuelle Uhrzeit in Sekunden und Mikrosekunden (seit Beginn der Epoche) ausgegeben.

-T

Am Ende jeder Zeile wird die von diesem Systemaufruf benoetigte Zeit ausgegeben.

-v

Alle komplexen Daten werden vollstaendig ausgegeben. Hierzu gehoeren z. B. Strukturen und Stringarrays. Normalerweise werden hierzu nur die ersten Komponenten oder Zeichen ausgegeben.

-V

gibt die Versionsnummer aus.

-x

Nichtdruckbare Zeichen in Strings werden als hexadezimale Zahlen ausgegeben.

-xx

Alle Zeichen in Strings werden als hexadezimale Zahlen ausgegeben.

-a column

Die Rueckgabewerte der Systemaufrufe werden in die Spalte column geschrieben. Voreinstellung ist -a 40.

-e expr

Hier kann ein Ausdruck expr angegeben weden, der die Protokollierung der Systemaufrufe genauer festlegt. Der Ausdruck hat folgendes Format [typ=][!][wert1[,wert2].. Fuer typ kann trace, abbrev, verbose, raw, signal, faults, read oder write angegeben werden. Der Wer ist entweder ein Name oder eine Zahl; der voreingestellte Typ ist trace. Wird das Ausrufezeichen angegeben, so negiert es den Wert. Die Angabe von -e open, welche identisch zur Angabe -e trace=open ist, bewirkt, dass nur die open-Aufrufe von strace protokolliert werden. Bei der Angabe von -e trace=!open dagegen werden alle Systemaufrufe ausser open protokolliert. Als Spezialfaelle kann fuer die Werte auch all oder none angegeben werden.

-e abbrev=wert

beeinflusst die Ausgabe der einzelnen Komponententen von grossen Strukturen. Voreinstellung ist abbrev=all.

-e faults

Falsche Speicherzugriffe werden mitausgegeben. Diese Option wird aber nur von System V angeboten.

-e raw=liste

gibt die Argumente der in liste angegebenen Systemaufurfe nicht symbolisch, sondern als hexadezimale Zahlen aus.

-e read=liste

gibt bei allen Leseoperationen, die auf die in liste angegebenen Filedeskriptoren stattfinden, die gelesenen Daten als Hexa- und ASCII-Dump aus. Um sich z. B. alle von den Filedeskriptoren 3 und 5 gelesenen Daten anzeigen zulassen, muss strace -e read=3,5 angegeben werden.

-e signal=list

Es werden nur die in liste angegebenen Signale ausgegeben, wenn sie auftreten. Die Voreinstellung ist -e signal=all. Moechte man sich z. B. alle auftretenden Signale ausser SIGUSR1 anzeigen lassen, muss strace -e signal=!SIGUSR1 verwendet werden.

-e trace=liste

Es werden nur die Systemaufrufe protokolliert, die in liste angegeben sind.

-e trace=file

Es werden alle Systemaufrufe protokolliert, die zum Filesystem gehoeren.

-e trace=ipc

Es werden alls Systemaufrufe protokolliert, die zur Interprozesskommunukation (IPC von System V) gehoeren.

-e trace=network

Es werden alls Systemaufrufe protokolliert, die zur Netzwerkkommunikation gehoeren.

-e trace=process

Es werden alls Systemaufrufe protokolliert, die zur Prozesssteuerung gehoeren.

-e trace=signal

Es werden alls Systemaufrufe protokolliert, die zur Signalbehandlung gehoeren.

-e verbose=liste

Fuer alle Systemfunktionen, die in liste angegeben sind, wird bei Argumenten, die Zeiger auf Strukturen sind, der Inhalt der Strukturen und nicht nur der Zeigerwert (als hexadezimale Zahl) ausgegeben.

-e write=liste

gibt bei allen Schreiboperationen, die auf die in liste angegebenen Filedesktiptoren stattfinden, die geschriebenen Datein als Hexa- und ASCII-Dump aus. Um sich z. B. alle auf die Filedesktiptoren 3 und 5 geschriebenen Daten anzeigen zu lassen, muss strace -e write=3,5 angegeben werden.

-o file

schreibt seine Ausgabe nicht auf die Standardfehlerausgabe, sondern in die Datei file. Ist die Option -ff angegeben, wird die Ausgabe in die Datei file.pid geschrieben, wobei die PID die des Prozesses eingesetzt wird.

-O overhead

Durch das Protokollieren der Systemaufrufe entsteht ein Overhead, der eine mit -c erstellte Statistik verfaelscht. So kann der heuristisch vom Programm selbst ermittelte Wert korrigiert werden. Die Genauigkeit kann ein Aufrufer selbst ueberpruefen. Dazu muss er nur die Systemzeit, die das zu ueberwachende Programm verbraucht, mit dem Kommando time und der Option -c ermitteln und beide Werte vergleichen. Fuer overhead sind Mikrosekunden anzugeben.

-p pid

schaltet eine Ueberwachung fuer den gerade ablaufenden Prozess mit der angegebenen PID ein.

-s strsize

legt fest, dass fuer Strings strsize Zeichen auszugeben sind. Dateinamen zaehlen nicht zu solchen Strings, da diese immer vollstaendig auszugeben werden.

-S sortby

sortiert die Statistik, die bei der Angabe der Option -c erstellt wird, nach der Spalte sortby. Fuer sortby kann time, calls, name oder nothing (fuer unsortiert) angegeben werden. Die Voreinstellung ist -S time

ar - Erstellen und Verwalten von statischen Bibliotheken

Das Kommando ar ermoeglicht es, mehrere Dateien in einer sogenannten statischen Archivbibliothek unterzubringen. Ebenso koennen mit ar neue Dateien in einer bereits erstellen Archivbibliothek aufgenommen bzw. aus ihr extrahiert oder entfernt werden. Die Aufrufesyntax ist ar [-V] [-]schluessel [posname] archiv_datei [datei(en)]. Eine statische Archivbibliothek enthaelt am Anfang eine sogenannte Symboltabelle, die Informationen ueber die in der Bibliothek enthaltenen Dateien bereitstellt, um einen moeglichst effizienten Zugriff auf die jeweiligen Dateien durch die entsprechenden Tools zu ermoeglichen, wie z. B. dem Linker ld, dem man wohl eine Bibliothek von Objectdateien auf der Kommandozeile uebergibt. Eine Symboltabelle wird nur dann von ar erstellt, wenn sich wenigstens eine Datei in der Bibliothek befindet. Die Angaben auf der Kommandozeile bedeuten im einzelnen:

-V

gibt die Versionsnimmer von ar auf die Standardfehlerausgabe aus.

schluessel

legt die in einem Archiv durchzufuehrende Operation fest.

posname

muss der Name einer Datei aus dem Archiv sein. Hiermit kann eine Position innerhalb eines Archivs festgelegt werden.

archiv_datei

ist der Name des entsprechenden Archivs.

datei(en)

legt die zu bearbeitende Datei fest.

Schluesselvergabe

-d

Die angegebenen Dateinamen sollen aus dem Archiv geloescht werden. Hier koennen auch die Optionen -v und -l verwendet werden.

-r

Die angegebenen Dateinamen sollen die existierenden Dateien in der Archivdatei ersetzen. Es koennen die Optionen -v, -c oder -l angegeben werden. Ausserdem duerfen die Positionsparameter a, b und i spezifiziert werden. Bei Angabe eines Positionsparameters muss zusaetzlich ein Dateiname als Positionsname auf der Eingabezeile erscheinen.

-t

Es soll eine Liste der Dateien in der Archivdatei gezeigt werden.

-q

Die Dateien sollen ans Ende der Archivdatei angehaengt werden.

-p

Die angegebenen Dateien sollen mit ihrem Inhalt gezeigt werden.

-m

Die Dateien sollen ans Ende der Archivdatei verlagert werden.

-x

Die angegebenen Dateien sollen aus der Archivdatei extrahiert werden.

-v

Bei Angabe dieser Option zeigt das Kommando ar zusaetzliche Meldungen auf dem Bildschirm. Dies macht sich z. B. bei der Erstellung eines Archivs bemerkbar, da fuer jede archivierte Datei eine separate Meldung produziert wird.

-c

Die Meldung des Kommandos ar, wie normalerweise produziert wird, erscheint nach Angabe dieser Option nicht mehr.

-l

Diese Option steuert die Kreation temporaerer Dateien.

-s

bewirkt, dass die Symboltabelle fuer ein Archiv neu erstellt wird, selbst wenn ar nicht mit einem Kommando aufgerufen wird, das den Inhalt des Archivs aendert. Diese Option ist zur Wiederherstellung der Symboltabelle nuetzlich, wenn diese zuvor mit strip entfernt worden ist. Der Aufruf ar r archiv_datei ist aequivalent zu ranlib archiv_datei.

-u

Wenn u mit r verwendet wird, so werden nur die Dateien ersetzt, die seit ihrer letzten Archivierung modifiziert wurden.

a

Wenn a zusammen mit r oder m angegeben wird, so werden die Datei(en) nach der mit posname spezifierten Datei im Archiv eingefuegt.

-b

Wenn diese Option zusammen mit r oder m angegeben wird, so werden die Datei(en) vor der mit posname spezifierten Datei im Archiv eingefuegt.

-i

Wenn i zusammen mit r oder m angegeben wird, so werden die �Datei(en) vor der mit posname spezifierten Datei im Archiv eingefuegt.

-o

Beim Extrahieren von Dateien werden deren urspruenglichen Zeitmarken uebernommen. Normalerweise erhalten extrahierte Dateien als Zeitmarken den Zeitpunkt des Extrahierens.

Wenn bei datei(en) dieselbe Datei zweimal angegeben ist kann sich auch zweimal im Archiv aufgenommen werden. Archivdateien sollten immer das Suffix .a haben. Das Kommando ar bewirkt keine nennenswerte Speicherplatzeinsparungen, da die angegebenen Dateien nicht komprimiert werden. Manche Unix-Systeme fordern, dass die Symboltabelle einer Archivdatei eventuell zuerst mit ranlib archiv bzw. ar s archiv ktualisiert werden muss, bevor sie von ld bearbeitet werden kann. Zur Erstellung und Pflege von Archiven koennen auch die beiden Kommandos tar und cpio verwendet werden. Es ist aber wichtig zu wissen, dass alle drei Kommandos verschiedene Archivformate benutzen und somit ein einmal erstelltes Archiv auch nur wieder mit dem gleichen Kommando bearbeitet werden kann.

Typische Anwendungen

Das Kommando ar wird verwendet, um eine Archivbibliothek von kompilierten C-Funktionen anzulegen, die dem Linker ld zum Einbinden der benoetigten Funktionen vorgelegt wird. ld wird zwar automatisch von cc bzw. gcc aufgerufen, kann jedoch auch direkt aufgerufen werden. ar kann auch verwendet werden, um miteinander verwandte Textdateien (wie z. B. C-Quellprogramme oder Briefe) in einem Archiv unterzubringen. Dies fuehrt zu einer erheblichen Reduzierung der Dateien in einem Directory und dient somit auch der Uebersichtlichkeit. ar wird haeufig auch verwendet, wenn eine grosse Zahl von Dateien kopiert werden muss. In diesem Fall werden alle zu kopierenden Dateien zunaechst in einem Archiv abgelegt, bevor das gesamte Archiv kopiert wird.

1 $ ls
2  five.o  four.o  libOne.a  one.o  three.o  two.o
3  $ ar -t libOne.a
4  four.o
5  $ ar -q libOne.a two.o four.o
6  $ ar -t libOne.a
7  four.o
8  two.o
9  four.o
10 $ ar d libOne.a four.o
11 $ ar -t libOne.a
12 $ ls
13 two.o
14 four.o

In Zeile 1 ist zu sehen, dass im $PWD die Dateien five.o, four.o, libOne.a, one.o, three.o, two.o vorhanden sind und in Zeile 2 der Inhalt der Archivbibliothek libOne.a angezeigt (das Ergebnis steht in Zeile 4). In Zeile 5 werden die Objectdateien two.o und four.o in die Archivbibliothek libOne.a eingefuegt. Die Option -q sorgt dafuer, dass nicht ueberprueft wird, ob eine dieser Objectdateien bereits in der Archivbibliothek enthalten ist (wie man am Ergebnis in Zeile 7, 8 und 9 sehen kann). ar d libOne.a four.o entfernt die Objectdatei four.o wieder aus der Archivbibliothek.
Mit ar -t /usr/lib/libc.a | sort | less kann man sich alle Objectdateien aus der C-Standardbibliothek auflisten lassen. Um sich alle Funktionen aus einer Bibliothek (wie z. B. der C-Standardbibliothek) auflisten zu lassen, muss auf das Kommando nm (name mapper) zurueckgegriffen werden:

1 nm /usr/lib/libc.a | less
2 [...]
3 assert.o:
4          U _IO_fflush
5          U _IO_fputs
6          U __asprintf
7 00000000 T __assert_fail
8 [...]

Das Kuerzel T in Zeile 7 bedeutet dabei, dass die Funktion hier definiert ist, waehrend U (Zeile(n) 4, 5 und 6) anzeigt, dass diese Funktion lediglich aufgerufen wird.

Dynamische Bibliotheken

Dynamische Bibliotheken weisen einige Vorteile gegenueber statischen Bibliotheken auf:

  • Der ausfuehrbare Code einer dynamischen Bibliothek wird von System nur einmal in den Speicher geladen, so dass alle Prozesse, die diese dynamische Bibliothek benutzen, den gleichen Code benutzen. Deswegen sollte man Code, der von mehreren Programmen benutzt werden kann, in eine dynamische Bibliothek packe. Das spart viel Speicher beim gleichzeitigen Ablauf dieser Programme.

  • Diese Einsparung am Speicher bringt natuerlich auch Geschwindigkeitsvorteile mit sich, da dadurch weniger Paging (Ein- und Auslagern von Speicherseiten) stattfinden.

  • Da der Code einer dynamischen Bibliothek beim Linken nicht in das entsprechende Programm eingefuegt wird, sind die aus dem Linken resultierenden Programme kleiner als solche, die mit statischen Bibliotheken gelinkt werden. Das spart zum einen Speicherplatz auf der Festplatte, zum anderen fuerht es auch zu schnelleren Programmen, da das Laden von kleineren Programmen in den Arbeitsspeicher natuerlich auch weniger Zeit beansprucht.

  • Werden Fehler in einer dynamischen Bibliothek behoben oder eben nur erforderliche Aenderungen (wie z. B. Code-Optimierungen) an ihr vorgenommen, so erfordert dies keine neue Generierung der Programme, die diese Bibliothek benutzen. Bei statischen Bibliotheken dagegen muesste man alle Programme, die diese Bibliothek nutzen, neu kompilieren und linken.

Wie ueberall gibt es auch die Kehrseite der Medaille; die Nachteile der dynamischen Bibliotheken gegenueber statischen Bibliotheken sind folgende:

  • Ein mit dynamischen Bibliotheken gelinktes Programm ist fuer sich allein nicht ablauffaehig, da es immer die zugehoerigen dynamischen Bibliotheken benoetigt. Das bedeutet, dass bei der Auslieferung dieses Programms niemals vergessen werden darf, die zugehoerigen dynamischen Bibliotheken mitzuliefern, da das Programm sonst nicht laeuft. Es sei denn, es handelt sich um dynamische Bibliotheken, die allgemein vom jeweiligen System angeboten werden.

  • Da die von einem Programm benutzten dynamischen Bibliotheken beim Programmstart erst gesucht und geladen werden muessen, kann dies beim ersten Laden einer solchen Bibliothek zu einem zusaetzlichen Zeitaufwand fuehren, den statische Bibliotheken nicht benoetigen. Dieser Nachteil gilt jedoch nur fuer das erstmalige Laden! Da jedoch meist die entsprechenden dynamischen Bibliotheken schon fuer einen anderen Prozess in den Hauptspeicher geladen wurden, trifft diese auf den Grossteil von Programmen nicht mehr zu.

Entwerfen von dynamischen Bibliotheken

Ein wichtiger Grundsatz beim Entwurf von dynamischen Bibliotheken ist, dass diese immer abwaertskompatibel sein sollten. Das bedeutet, dass ein Programm, das mit einer aelteren Version dieser dynamischen Bibliothek gelinkt wurde, auch mit der neuen Version weiterhin lauffaehig sein sollte. Dieser Grundsatz muss nur dann nicht eingehalten werden, wenn eine voellig neue Version (major version) zu einer dynamischen Bibliotheken entwickelt wird. Jede dynamische Bibliothek hat einen speziellen Namen, den sogenannten soname, der dyn eigentlichen Namen der Bibliothek, sowie die Versionsnummer beinhaltet. Solange dynamische Bibliotheken abwaertskompatibel sind, also ihre Schnittstellen sich nicht aendern, sollte sich nur eine der Nummern (minor number) hinter der Hauptversionsnummer aendern.
Als Beispiel kann man die C-Bibliothek von Linux nehmen, die Abwaertskompatiblitaet fuer alle Unterversionen mit der gleichen Hauptversionsnummer garantiert. Da z. B. alle C-Bibliotheken mit der Hauptnummer 6 abwaerskompatibel sind, benutzen sie alle den gleichen soname (libc.so.6), der lediglich ein symbolischer Link auf die eigentliche, aktuelle dynamische Bibliothek ist:

1 $ ls -l /lib/libc.so.6
2 $ lrwxrwxrwx 1 root root 12 Sep 14 14:01 /lib/libc.so.6 -> libc-2.24.so*

Das Programm ldconfig sucht alle dynamischen Bibliotheken, die in bestimmten Directories liegen und erzeugt dann bei Bedarf einen symbolischen Link auf die jeweilige Version. ldconfigu untersucht standardmaessig immer die beiden Directories /lib und /usr/lib. Daneben untersucht es noch die Dateien der Directories, die auf der Kommandozeile beim Aufruf des Kommandos angegeben werden und die Directories, deren Namen sich in der Datei /etc/ld.so.conf befinden. Linkt man nun ein C-Programm, so wird der Linker immer nach einer Datei mit dem Namen /usr/lib/libc.so suchen, die ein symbolischer Link auf die gerade aktuelle dynamische C-Bibliothek ist. Um sich alle durch ldconfig eingerichteten symbolischen links anzeigen zu lassen, muss man nur ldconfig -p aufrufen. Benutzer, die eigene dynamische Bibliothekeb entwerfen wollen, sollten wissen, was zu beachten ist, damit eine neue dynamische Bibliothek abwaertskompatibel bleibt. Es gibt drei Arten von Aenderungen an einer dynamischen Bibliothek, die diese inkompatibel zu vorherigen Versionen werden laesst:

  • Das Aendern oder Entfernen von Funktionsschnittstellen, was ueblicherweise die von aussen aufrufbaren Funktionen sind.

  • Das Aendern eines Funktionscodes in der Form, dass diese Funktion sich nicht mehr so verhaelt, wie es in der urspruenglichen Spezifikation festgelegt ist.

  • Das Aendern von Datenstrukturen, die nach aussen sichtbar sind. Hierzu zaehlt jedoch nicht das Anfuegen zusaetzlicher Komponenten am Ende von Strukturen, die innerhalb der Bibliothek allokiert werden.

Dagegen ziehen die folgenden Modifikationen an einer dynamischen Bibliothek keine Inkompatiblitaet nach sich:

  • Hinzufuegen neuer Funktionen mit anderen Namen, um die Funtionalitaet einer existierenden dynamischen Bibliothek zu erweitern.

  • Hinzufuegen weiterer Komponenten am Ende von Strukturen, die innerhalb der Bibliothek allokiert werden. Die gilt jedoch nicht fuer Datenstrukturen, die nicht innerhalb der Bibliothek allokiert werden, da dann Programme, die mit frueheren Versionen gelinkt wurden, nicht genuegend Speicherplatz allokiert haben. Ebenso sollten keine Datenstrukturen erweitert werden, die in Arrays verwendet werden.

Generieren von dynamischen Bibliotheken

Beim Erzeugen von dynamischen Bibliotheken muss man sich an folgende Regeln halten:

  • Beim Kompilieren des Quellcodes mit gcc muss die Option 0fPIC (Position-Independent-Code) angegeben werden, um positionsunabhaengigen Code zu erzeugen, der an jede beliebige Adresse gelinkt und geladen werden kann

  • Zum Linken sollte cc bzw. gcc verwendet werden. Ein direktes Linke mit dem Linker ld ist nicht empfehlenswert, da der jeweilige C-Compiler automatisch den Linker ld mit den erforderlichen Optionen aufruft. Ein typische Aufruf zum linken einer dynamischen Bibliothek mit gcc waere z. B.: gcc -shared -Wl,soname,soname -o bibname object bibliothek. -Wl leitet dabei die Optionen an ld weiter, wobei die Kommata durch Leerzeichen ersetzt werden. Fuer soname ist der Bibliotheksname (mit Hauptversionsnummer) und fuer bibname der vollstaendige Bibliotheksname mit allen zugehoerigen Versionsnummern anzugeben. Fuer object ist eine Liste der Objectdateien anzugeben, die in diese dynamische Bibliothek aufzunehmen sind und fuer bibliothek ist evtl. eine Liste der Bibliotheken anzugeben, aus denen Funktionen in den Objectdateien aufgerufen werden. So empfiehlt es sich fast immer, die C-Bibliothek hier anzugeben (-lc). Um z. B. die dynamische Bibliothek libfnord.so.1.2.3 mit dem soname libfnord.so.1 aus den Objectdateien fnord.o und foobar.o zu erzeugen, koennte man folgenden Aufruf verwenden: gcc -shared -Wl,-soname,libfnord.so.1 -o libfnord.so.1.2.3 fnord.o foobar.o -lc

  • Beim gcc sollte niemals die Option -fomit-frame-pointer angegeben werden.

Installieren von dynamischen Bibliotheken

Die Installation von dynamischen Bibliotheken erfolgt ueblicherweise mit dem Programm ldconfig. Um eine dynamische Bibliothek korrekt zu installieren, empfiehlt sich folgende Vorgehensweise:

  • Kopieren der dynamischen Bibliothek in das Directory, indem sie aufbewahlrt werden soll.

  • Erzeugen eines symbolischen Links in /usr/lib mit dem Namen bibname’der auf die dynamische Bibliothek verweist. Das ist nur erforderlich, wenn man moechte, dass der Linker diese Bibliothek automatisch findet, so dass man nicht immer beim Linke die Option '-Lpfadname angeben muss.

  • Eventuelles Eintragen des Directorys, in dem sich der symbolische Link bzw. die Bibliothek befindet, in die Datei /etc/ld.so.conf. Dieser Eintrag ist jedoch nicht notwendig, wenn die dynamische Bibliothek bzw. der symbolische Link sich in einem der Directories /lib oder /usr/lib befindet oder der entsprechende Directoryname in der /etc/ld.so.conf eingetragen ist.

  • Aufrufen des Programms ldconfig, das einen weiteren symbolischen Link mit dem soname in dem Directory erzeugt, in dem die dynamische Bibliothek installiert wurde. ldconfig traegt die Bibliothek danach in den dynamischen Lade-Cache (die Datei /etc/ld.so.conf) ein, so dass der dynamishce Lader die Bibliothek finde, wenn Programme gestartet werden, die mit ihr gelinkt wurden, ohne das ein zeitaufwendiges Durchsuchen von vielen Directories erforderlich ist. Loescht man z. B. die Datei /etc/ld.so.conf, wird das fast immer dazu fuehren, dass das System merklich langsamer wird. In diesem Fall sollte man mit einem Aufruf von ldconfig eine neue Datei /etc/ld.so.conf erzeugen.

Beispiel fuer das Erzeugen, Installieren und Benutzen einer dynamischen Bibliothek

In diesem Abschnitt wird das Programm fehler.c (die Datei eighdr.h ist hier zu finden) in eine dynamische Bibliothek umgewandelt, so dass es alle Programme die es ab jetzt benutzen moechten, nicht mehr statisch dazubinden musst. Hierzu sind folgende Schritte notwendig:

  • Kompilieren des C-Programms fehler.c, um daraus eine Objectdatei zu erzeugen.

    • gcc -fPIC -g -c fehler.c

  • Generieren der dynamischen Bibliothek, indem man die Objectdatei fehler.o mit den entsprechenden Optionen linkt.

    • gcc -g -shared -WL,-soname,libfehler.so.1 -o libfehler.so.1.0 fehler.o -lc

  • Kopieren der Datei libfehler.so.1.0 nach /usr/local/lib, was normalerweise nur root erlaubt ist.

    • cp libfehler.so.1.0 /usr/local/lib

  • Erzeugen eines symbolischen Links in /usr/lib (ebenfalls nur root erlaubt).

    • cd /usr/lib && ln -s ../local/lib/libfehler.so.1.0 libfehler.so.1

  • Erzeugen eines symbolischen Links fuer den Linker, der benutzt werden soll, wenn die dynamische Bibliothek beim Linken mit -l angegeben werde. Hier soll der Name fehler angegeben werden koennen

    • cd /usr/lib && ln -s libfehler.so.1 libfehler.so

  • Aufrufen von ldconfig.

    • ldconfig

Jetzt wird fehlinfo.c kompiliert, das die eben erzeuge und installierte dynamische Bibliothek ausnutzt. Um die dynamische Bibliothek libfehler.so zum Programm fehlinfo dazu zu linken, empfiehlt sich die nachfolgend gezeigte Vorgehensweise:

  • Kompilieren: fehlinfo.c -→ fehlinfo.o

    • gcc -Wall -g -c fehlinfo.c

  • Linken mit der dynamischen Bibliothek:

    • gcc -g -o fehlinfo fehlinfo.o -lfehler

Wenn man das erzeugte Programm fehlinfo startet, bekommt man folgende Ausgabe zu sehen:

1 $ ./fehlinfo
2 Warnung (Kennung 'WARNUNG')
3 Warnung mit Systemmeldung Kennung 'WARNUNG_SYS'): Bad file descriptor
4 Fataler Fehler (Kennung 'FATAL')
5 Fataler Fehler mit Systemmeldung (Kennung 'FATAL_SYS'): Identifier removed

Der Eintrag 'rand()%50+1;'in den Zeilen 13 und 22 von fehlinfo.c sorgt dafuer, dass die "Warnungen" abwechseln; die Ausgabe kann und wird also unterschiedlich ausfallen. Um die von einem Programm benoetigten dynamischen Bibliotheken zu erfahren, muss man lediglich das Kommando ldd mit dem entsprechenden Programmnamen aufrufen, wie z. B.:

1 $ ldd fehlinfo
22libfehler.so => /usr/lib/libfehler.so (0x40021000)
32libc.so.6 => /lib/libc.so.6 (0x40023000)
40./lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)

Moeglichkeiten zur Benutzung von dynamischen Bibliotheken

Existiert sowohl eine dynamische wie auch eine statische Bibliothek zu einem Namen, so bindet der Linker automatisch die dynamische Bibliothek dazu, wenn er keine anderen Anweisungen enthaelt. Neben diesem einfachen Dazubinden von dynamischen Bibliotheken beim Linken, gibt es noch drei weitere Moeglichkeiten, dynamische Bibliotheken zu benutzen.

Benutzen von nicht installieren Bibliotheken

Startet man ein Programm, das dynamische Bibliotheken benutzt, versucht der dynamische Lader in dem Cache fuer Bibliotheken (/etc/ld.so.conf), der durch den Aufruf von ldconfig unter Zuhilfename der Datei /etc/ld.so.conf (enthaelt die Directories fuer dynamische Bibliotheken) erzeugt wurde, die vom Programm benutzten Bibliotheken zu finden. Ist jedoch die Environment-Variable LD_LIBARY_PATH gesetzt, werden zuerste die darin enthaltenen Directories, die wie bei PATH mit Doppelpunkt voneinander zu trennen sind, durchsucht, bevor der Cache herangezogen wird. So ist es moeglich, das man mit anderen Versionen von dynamischen Bibliotheken arbeitet, als die, welche instaliert sind. Dies mag z. B. notwendig sein, wenn man aeltere Programmversionen hat, die nicht mit einer neu installierten dynamischen Bibliothek ablauffaehig sind, dafuer aber mit einer aelteren Version dieser dynamischen Bibliothek. In diesem Fall kopiert man die aeltere Version in ein bestimmtes Directorie und setzt vor dem Programmstart die Environment-Variable LD_LIBARY_PATH entsprechend. Man kann auch ein Shellscript erstellen, dass wie folgt aussehen kann:

1 #!/bin/sh
2 export LD_LIBARY_PATH=alt_bibl_dir:$LD_LIBARY_PATH
3 exec alt_programm $*

Fuer alt_bibl_dir ist das Directory anzugeben, in dem sich die aeltere Version der dynamischen Bibliothek befindet und fuer alt_programm ist der Name des zu startenden Programms anzugeben.

Vorladen von dynamischen Bibliotheken

Manchmal moechte man nicht eine ganze dynamische Bibliothek, sondern nur einige Funktionen ersetzen. Da der dynamische Lader nach Funktionen sucht, indem er bei der ersten geladenen Bibliothek beginnt und dann in den anderen Bibliotheken in der Reihenfolge fortfaehrt, in der diese geladen wurden, reicht es zunaechst aus, nur eine neue Bibliothek zu laden, die nur die neuen Funktionen enthaelt, die zu ersetzen sind. Ein Beispiel hierzu ist die Bibliothek zlibc, die Funktionen, welche von der C-Bibliothek zur Dateibearbeitung angebogen werden, durch eigene Funktionen ersetzt, die mit komprimierten Dateien arbeiten koennen, Wird eine Datei geoeffnet, sucht zlibc sowohl nach der angegebenen Datei als auch nach einer mit gzip gepackten Version dieser Datei. Findet es die angegebenen ungepackte Datei, verhaelt sich die entsprechende Funktion genauso wie die Version dieser Funktion in der C-Bibliothek. Existiert die angegebene Datei aber nicht, dafuer aber eine gepackte Version dieser Datei, entpackt sie diese, ohne dass sich das aufrufende Programm darum muss. Um eine Bibliothek vorzuladen, gibt es zwei Moeglichkeiten.

Zum ersten das Setzen der Environment-Variable LD_PRELOAD LD_PRELOAD=/lib/vorlad.o exec /bin/programm $* und zum zweiten mit dem Eintragen der vorzuladenden Objectdateien in die Datei /etc/ld.so.preload. Fuer die Bibliothek zlibc koennte die folgende Zeile in die Datei /etc/ld.so.preload eingetragen werden: /lib/uncompress.o

Dynamisches Laden zur Laufzeit (shared objects)

Groessere Softwarepakete werden unter Linux/Unix ueblicherweise in Module zerlegt, die getrennt voneinander entwickelt werden. Manchmal sind diese Module eigenstaendige Programme, die mit anderen Modulen des Softwarepakets ueber Pipes oder andere Formen der Interprozesskommunikation kommunizieren. Eine andere Moeglichkeit der Kommunikation ist die Implementierung von sogenannten shared objects (geteilte Objecte). Solche shared objects koenen entweder Objectdateien oder dynamische Bibliotheken sein. da der Linker nicht von den shared objects wissen muss, ist es noch nicht einmal erforderlich, dass diese zum Zeitpunkt des Linkens existieren muessen. Ein weiterer Unterschied von shared objects zu dynamischen Bibliotheken ist, dass sie anders installiert werden wie die meisten dynamischen Bibliotheken.

Daneben muessen die von shared objects verwendeten Symbolnamen nicht eindeutig und einmals sein, was sie meist auch nicht sind, da verschiedene shared objects, die fuer die gleiche Schnittstelle entwickelt wurden, normalerweise auch Eintrittspunkte mit den gleichen Namen verwenden, was bei dynamischen Bibliotheken absolut unmoeglich ist.

Die haeufigste Anwendung von shared objects sind sogenannte generische Schnittstellen. Diese snid im Prinzip nicht anderes als Funktionszeiger, denen erst zur Laufzeit die Adresse der entsprechenden Funktion zugewiesen wird. So ist es moeglich, dass Programme beliebig erweiterbar sind, ohne das sie erneut kompiliert oder gelinkt werden muessen. Ein Beispiel fuer die Verwendung von generischen Schnittstellen koennte ein Programm sein, dass Simulationen fuer Industrieprozesse nach verschiedenen Verfahren durchfuehren kann. Diese Programm verwendet intern ein eigenes Format, um die berechneten Werte grafisch am Bildschrim darzustellen. Wird nur eine generische Schnittstelle geschafft, die die Durchfuehrung der Simulation in zur Laufzeit geladene shared objects verlagert, kann jederzeit ein neues Simulationsverfahren hinzugefuegt werden, ohne das diese Programm neu kompilier/gelinkt werden muss. Generische Schnittstellen setzen allerdings immer eine gute Dokumentation ihrer Funktionsweise voraus, damit auch andere Programmierer, die die Interna des jeweiligen aufrufenden Hauptprogramms nicht kennen, sie benutzen und so den Funktionsumfang des Hauptprogramms erweitern koennen. Dynamisches Laden erfordert folgende Aktivitaeten:

  • Oeffnen einer Bibliothek,

  • Suchen einer beliebigen Anzahl von Symbolen in dieser Bibliothek,

  • Auftretende Fehler behandeln und

  • Schliessen der Bibilothek.

Die hierzu notwendigen Funktionen dlopen, dlsym, dlerror und dlclose sind in der Headerdatei dlfcn.h deklariert.

1 #include <dlfcn.h>
2 void *dlopen(const char *filename, int flag);
3 void *dlsym(void *handle, char *symbol);
4 const char *dlerror(void);
5 int dlclose(void *handle);
dlopen

dlopen laedt die dynamische Bibliothek, deren Name ueber den Parameter filename angegeben ist und gibt einen Zeiger zurueck, mit dem nun Zugriffe (mit den Funktionen dlsym und dlclose) auf diese Bibliothek moeglich sind. Wird fuer filename ein absoluter Pfad (beginnen mit /) angegeben, muss dlopen die Bibliothek nicht suchen. Die ist der uebliche Weg um dlopen aufzurufen. Ist der fuer filename angegebener Pfad kein absoluter Pfad, sucht dlopen die entsprechende Bibliothek an den folgenden Stellen in der angegebenen Reihenfolge:

  • in den Directories, die in der Environment-Variable LD_ELF_LIBARY_PATH (durch Semikolons getrenne) angegeben sind. Wenn diese Variable nicht existiert, wird LD_LIBARY_PATH durchsucht,

  • die Bibliotheken, die in der Datei /etc/ld.so.conf aufgefuehrt sind,

  • im Directory /usr/lib und

  • im Directory /lib

Gibt man fuer filename den NULL-Zeiger an, oeffnet dlopen die Datei des aktuell ausgefuehrten Programms (was aber nur in sehr wenig Faellen sinnvoll ist). Undefinierte externe Referenzen in der dynamischen Bibliothek werden aufgeloest, indem andere zuvor mit RTLD_GLOBAL geoeffnete Bibliotheken und die Bibliotheken durchsucht werden, die in der Abhaengigkeitsliste dieser Bibliothek enthalten sind. Fuer flag kann eine der folgenden Konstanten angegeben werden:

RTLD_LAZY

Undefinierte Symbole in der dynamischen Bibliothek werden erst dann aufgeloest, wenn der Code dieser dynamischen Bibliothek ausgefuehrt wird.

RTLD_NOW

Alle undefinierten Symbole in der dynamischen Bibliothek werden aufgeloest, bevor die Funktion dlopen zurueckkehrt. Wenn das nicht moeglich ist, liefert dlopen den Rueckgabewert NULL. Dieses Flag wird meist waehrend der Entwickliung und Fehlersuche gesetzt, denn so wird man sofort ueber unaufgeloeste Referenzen in shared objects informiert.

RTLD_GLOBAL

In diesem Fall werden die hier definierten externen Symbole den Bibliotheken, die nachfolgend geladen werden, zur Verfuegung gestellt.

Enthaelt eine dynamische Bibliothek eine Funktion namens _init, wird diese ausgefuehrt, bevo dlopen zurueckkehrt. Wird die gleiche Bibliothek mehrmals geoeffnet, dann wird immer der gleiche Zeiger (handle) zurueckgegeben. Mit bitweisem OR (|) kann RTLD_GLOBAL mit RTLD_NOW und RTLD_LAZY verknuepft werden.

dlsym

dlsym sucht in der Bibliothek handle, was der Rueckgabewert der zuvor mit dlopen erfolgreich geoeffneten Bibliothek sein muss, nach dem Symbol mit dem Namen symbol. dlsym liefert die Adresse, an die dieses Symbol geladen wurde, oder, falls dieses nicht gefunden werden koennte, den NULL-Zeiger. Da es aber Symbole geben kann, die die Adresse NULL haben, laesst dieser Rueckgabewert nicht unbedingt auf einen Fehler schliessen. Deswegen ist es in diesem Fall empfehlenswert, die nachfolgende Funktion dlerror heranzuziehen, um eine Fehlerueberpruefung durchzufuehren.

dlerror

gibt NULL zurueck, wenn kein Fehler seit dem Oeffnen der dynamischen Bibliothek oder seit dem letzten Aufruf von dlerror aufgetreten ist, oder aber die Adresse der entsprechenden Fehlermeldung. Da jeder Aufruf von dlerror dazur fuehrt, dass eine eventuell vorhandene Fehlermeldung nach diesem Aufruf nicht mehr zur Verfuegung steht, sollte man diese Fehlermeldung in einer eigenen Variable speichern, wenn man sie fuer spaetere Zwecke wieder benoetigt.

dlclose

Jedesmal wenn dlopen eine Bibliothek oeffnet, wird ein internet Referenzzaehler erhoeh. Dieser Referenzzaehler wird bei jedem Aufruf von dlclose um 1 erniedrigt. Erst wenn dieser Referenzzaehler bedingt durch einen dlclose-Aufruf 0 wird, wird auch die Bibliothek geschlossen und der fuer sie allokierte Speicherplatz freigegeben. Enthaelt die dynamische Bibliothek eine Funktion namens _fini, wird diese ausgefuehrt, bevor dlclose zurueckkehrt. Durch den Referenzzaehler ist es moeglich, beliebig oft die entsprechende Bibliothek zu oeffnen und zu schliessen, ohne sich darum kuemmern zu muessen, ob die zugehoerigen shared objects bereits vom aufrufenden Code geladen wurden. Wenn diese Funktionen in einem Programm verwendet werden, muss man beim linken dieses Programms die Bibliothek libdl.so mit der Option -ldl dazulinken.

Das Programm sinus.c demonstriert das dynamische Laden eines shared object, indem es die Funktion sin aus der mathematischen Bibliothek laedt, um sie im Programm verwenden zu koennen. Nachdem man das Programm mit cc -o sinus sinus.c fehler.c -ldl kompiliert und gelinkt hat, kann man es starten, indem man ihm den Pfadnamen der mathematischen Bibliothek als Argument auf der Kommandozeile uebergibt:

1 $ ./sinus /usr/lib/libm.so
2 0.000000
3 0.099833
4 0.198669
5 0.295520
6 0.389418
7 0.479426

Nun soll aber eine eigene Funktion sin erstellt werden, die nicht den Sinus berechnet, sondern nur das Doppelte des uebergebenen Werts zurueckliefert, wie dies in folgendem Programm umgesetzt wird:

#include <stdio.h>
double sin(double wert)
{
  return(2*wert);
}

Aus diesem Programm (sin.c) wird mit den folgenden Aufrufen eine dynamische Bibliothek erstellt:

1 $ gcc -fPIC -Wall -g -c sin.c
2 $ gcc -g -shared -WL,-soname,sin.so.1 -o sin.so.1.0 sin.o -lc
3 $ ln -s sin.so.1.0 sin.so.1

Startet man das Programm sinus erneut und uebergibt ihm aber diesmal die eigene dynamische Bibliothek sin.so.1 im Working-Directory, verwendet es nur das shared object sin aus der eigenen Bibliothek:

1 $ export LD_LIBRARY_PATH=.
2 $ ./sinus sin.so.1.0
3 0.000000
4 0.200000
5 0.400000
6 0.600000
7 0.800000
8 1.000000

Hier sieht man also, dass beim Arbeiten mit shared objects also ein einfaches Austauschen von Funktionen moeglich ist, ohne dass die Originalprogramme erneut kompiliert und/oder gelinkt werden muessen.