Rheinwerk Computing < openbook > Rheinwerk Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger

 << zurück
Linux-UNIX-Programmierung von Jürgen Wolf
Das umfassende Handbuch – 2., aktualisierte und erweiterte Auflage 2006
Buch: Linux-UNIX-Programmierung

Linux-UNIX-Programmierung
1216 S., mit CD, 49,90 Euro
Rheinwerk Computing
ISBN 3-89842-749-8
gp Kapitel 17 Werkzeuge für Programmierer
  gp 17.1 Make
    gp 17.1.1 Erzeugen eines Makefiles
    gp 17.1.2 Variablen, Makros und Abkürzungen
    gp 17.1.3 Implizite Regeln
    gp 17.1.4 Musterregeln
    gp 17.1.5 make zur Installation verwenden
    gp 17.1.6 make-Optionen
    gp 17.1.7 Ausblick
  gp 17.2 Bibliotheken erstellen
    gp 17.2.1 Statische Bibliotheken erstellen
    gp 17.2.2 Dynamische Bibliotheken (Shared Libraries) erstellen
    gp 17.2.3 Dynamisches Nachladen von Bibliotheken
  gp 17.3 RPM
    gp 17.3.1 Verzeichnisse, die RPM benötigt
    gp 17.3.2 Ein eigenes RPM-Paket erstellen
    gp 17.3.3 Sources
    gp 17.3.4 Die Spec-Datei
    gp 17.3.5 Paket erstellen
    gp 17.3.6 Das Paket installieren
  gp 17.4 RCS und CVS
    gp 17.4.1 Software-Configuration-Management-Systeme (SCM)
    gp 17.4.2 RCS
    gp 17.4.3 CVS
  gp 17.5 Zeitmessung von Programmen
    gp 17.5.1 Einfache Zeitmessung mit TIME – Laufzeit von Prozessen
    gp 17.5.2 Profiling mit GPROF – Laufzeit von Funktionen
    gp 17.5.3 Analyse mit GCOV
  gp 17.6 Debuggen mit gdb und ddd
  gp 17.7 STRACE – Systemaufrufe verfolgen
  gp 17.8 Memory Leaks und unerlaubte Speicherzugriffe
    gp 17.8.1 efence
    gp 17.8.2 valgrind
  gp 17.9 Ausblick


Rheinwerk Computing

17.8 Memory Leaks und unerlaubte Speicherzugriffdowntop

Mit den Problemen von Buffer Overflows und den Memory Leaks haben sehr viele Programmierer zu kämpfen, die ältere Software überholen und verbessern müssen. Hierbei finden sich recht häufig Funktionen oder Fehler, bei denen eine Speicherüberschreitung stattfindet. Ein Speicherleck (Memory Leak) tritt auf, wenn einmal in einer Funktion alloziierter Speicher angefordert und nie wieder freigegeben (free()) wird. Wenn diese Funktion dann immer wieder aufgerufen wird und erneut Speicher anfordert, sollte es logisch sein, dass dem Programm zwangsläufig immer weniger Speicher zur Verfügung steht. Irgendwann werden Sie sich dann wundern, warum das Programm so erheblich langsam wird, und müssen es zwangsweise neu starten.

Das Schlimme an diesen beiden Fehlern ist, dass häufig das Pogramm nach der Ausführung scheinbar fehlerfrei ausgeführt wird. Meistens treten diese Fehler irgendwann später auf, wenn das Programm freigegeben wurde. Eine falsche Ausgabe von Daten bis hin zum Absturz der Anwendung wären einige der Folgen des Fehlers.

Für beide Probleme gibt es mittlerweile eine Menge hervorragender Tools, vom einfachen (kostenlosen) Konsolen-Tool bis hin zum ultimativen Alleskönner-Tool, das allerdings auch (zu Recht) seinen Preis hat.


Rheinwerk Computing

17.8.1 efence  downtop

Als Beispiel möchte ich Ihnen das Tool efence etwas näher vorstellen. Die Bibliothek efence liegt den meisten Linux-Distributionen bei und lässt sich ggf. problemlos nachinstallieren. Das Prinzip von efence ist einfach, aber effektiv, um Speicherüberläufe zu verhindern. Dazu wird ein Speicherbereich so angelegt, dass sich vor und nach diesem Bereich ein nicht existierender Speicherbereich befindet; ein Loch, wenn Sie so wollen. Wenn hierbei ein Byte durch dieses Loch fällt, bricht efence das Programm ab. Damit dies auch funktioniert, werden die Standardfunktionen zum Reservieren von Speicher wie malloc(), realloc(), calloc() und free() durch eigene Routinen ersetzt, die von efence zur Verfügung gestellt werden. Es gibt außerdem noch weitere Funktionen, die Ihnen diese Bibliothek zur Verfügung stellt. Hierzu sei allerdings wieder die Manual Page von efence empfohlen.

Um eine Anwendung mit efence zu übersetzen, muss die Bibliothek libefence mit -lefence hinzugelinkt werden. Zur Demonstration soll folgendes fehlerhafte Listing verwendet werden:

/* memory.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BUF 10
static char *alloc_memory( unsigned int mem ) {
   return malloc( mem );
}
int main(void) {
        char* ptr = alloc_memory( BUF );
        char* buf;
        char array[BUF];
        unsigned int i;
        printf("Eingabe machen : ");
        fgets(ptr, BUF, stdin);
        printf("Die Eingabe lautet : %s", ptr );
        /* Speicherüberschreitung */
        strcat(ptr, " 123456789");
        printf("%s\n", ptr );
        /* falsches free() */
        for( i = 0; i<BUF; i++ ) {
                buf = alloc_memory( BUF * BUF );
                /* tu was mit dem Speicher … */
                /* Ein nicht reservierter Speicher */
                /* wird freigegeben */
                free(ptr);
        }
        return EXIT_SUCCESS;
}

Testen Sie dieses Beispiel erst einmal ohne jeden Hintergedanken an Fehler:

$~> gcc -o memory memory.c
$~> ./memory
Eingabe machen : Jürgen
Die Eingabe lautet : Jürgen
Jürgen
 123456789

Obwohl sich im Listing zwei gravierende Fehler befinden, lässt sich das Programm problemlos (!) ausführen. Jetzt soll dasselbe Beispiel nochmals mit der efence-Bibliothek und dem Compilerflag für den Debugger GDB übersetzt werden:

$~> gcc -o memory memory.c -ggdb -lefence
$~> ./memory
Electric Fence 2.2.0 Copyright (C) 1987–1999 Bruce Perens <bruce@perens.com>
Eingabe machen: Jürgen
Die Eingabe lautet: Jürgen
Speicherzugriffsfehler

Hier sieht es schon ein wenig anders aus. Ein Speicherzugriffsfehler wurde mithilfe von efence entdeckt. Um mehr hierüber zu erfahren, sollten Sie jetzt das Programm mit dem Debugger GDB starten:

$~> gdb memory
GNU gdb 5.3
...
(gdb) run
Starting program: /home/tot/memory
[New Thread 16384 (LWP 4341)]
Electric Fence 2.2.0 Copyright (C) 1987–1999 Bruce Perens 
Eingabe machen: Jürgen
Die Eingabe lautet: Jürgen
Program received signal SIGSEGV, Segmentation fault.
[Switching to Thread 16384 (LWP 4341)]
0x400a533d in strcat () from /lib/libc.so.6
(gdb) bt
#0  0x400a533d in strcat () from /lib/libc.so.6
#1  0xbffff444 in ?? ()
#2  0x400408ae in __libc_start_main () from /lib/libc.so.6

Nach einem Aufruf von bt (auch where) finden Sie von unten nach oben die Stelle, an der das Programm mit dem Signal SIGSEGV abgebrochen hat. strcat() in der main()-Funktion ist hier der Übeltäter. Nach der Beseitigung des Fehlers sollten Sie die Anwendung natürlich weiterhin testen. Also ein Neuübersetzen mithilfe von efence:

$~> gcc -o memory memory.c -ggdb -lefence
$~> gdb memory
GNU gdb 5.3
...
(gdb) run
Starting program: /home/tot/memory
[New Thread 16384 (LWP 4372)]
Electric Fence 2.2.0 Copyright (C) 1987–1999 Bruce Perens
Eingabe machen: Jürgen
Die Eingabe lautet: Jürgen
ElectricFence Aborting: free(401b3ff6): address not from malloc().
Program received signal SIGILL, Illegal instruction.
[Switching to Thread 16384 (LWP 4372)]
0x40054691 in kill () from /lib/libc.so.6
(gdb) bt
#0  0x40054691 in kill () from /lib/libc.so.6
#1  0x40029127 in do_abort () from /usr/lib/libefence.so.0
#2  0x400293e4 in EF_Abort () from /usr/lib/libefence.so.0
#3  0x40028c8e in free () from /usr/lib/libefence.so.0
#4  0x080485ed in main () at memory.c:33
#5  0x400408ae in __libc_start_main () from /lib/libc.so.6
(gdb) list 33
28              for( i = 0; i<BUF; i++ )
29              {
30                      buf = alloc_memory( BUF * BUF );
31                      /* tu was mit dem Speicher */
32          /* Ein nicht reservierter Speicher wird freigegeben */
33                      free(ptr);
34              }
35              return 0;
36      }

Nun haben Sie einen weiteren Übeltäter ausgemacht. Hier bekommen Sie beim Aufruf von bt gleich die Zeilennummer und die Funktion mit ausgegeben. Im Beispiel wurde das Programm mit dem Signal für einen illegalen Maschinenbefehl in der main-Funktion in Zeile 33 abgebrochen. Bei einem genauen Blick auf free() fällt auf, dass hier ein unerlaubter Zeiger freigegeben wurde. Anstatt buf wird hier immer wieder ptr freigegeben. Nach einem erneuten Korrigieren des Fehlers läuft die Anwendung erstmals ohne einen Abbruch von efence.

Sie konnten zwar jetzt mithilfe von efence kleinere bis größere Übel beseitigen. Dennoch sollte nicht unerwähnt bleiben, dass efence nur Speicherüberschreitungen auf dem Heap erkennt. Speicherüberschreitungen auf statischen Puffern hingegen, wie z. B. bei einem Array, kann efence ebenso wenig erkennen wie Memory Leaks. Ein weiterer Nachteil von efence ist der sehr hohe Speicherverbrauch von Anwendungen, womit die Performance erheblich ausgebremst wird. Somit sollten Sie nach dem ausgiebigen Testen mit efence die finale Version Ihrer Anwendung möglichst ohne efence übersetzen und anbieten.


Rheinwerk Computing

17.8.2 valgrind  toptop

Einen Schritt weiter wie efence geht das Tool valgrind. Hiermit lassen sich auch Memory Leaks und noch einiges mehr ermitteln. Z. B. ist es mit valgrind auch möglich, den Schreib- oder Lesezugriff auf nicht alloziierten Speicher, über Array-Grenzen und gar über einige Bereiche des Stacks hinaus, zu finden. valgrind benötigt im Gegensatz zu efence auch keine extra Bibliothek und belastet somit die Programmgröße und die Performance überhaupt nicht. Zur Demonstration soll valgrind wieder auf das fehlerhafte Listing angewandt werden, das schon im Beispiel von efence verwendet wurde (Ausgabe verkürzt).

$~> valgrind ./memory
==5767== Memcheck, a.k.a. Valgrind, a memory error detector for x86-linux.
...
==5767==
Eingabe machen: Jürgen
Die Eingabe lautet: Jürgen
==5767== Invalid write of size 1
==5767== at 0x401636FF: strcat (in /usr/lib/valgrind/valgrind.so)
==5767==    by 0x80484B2: main (in /home/tot/memory)
==5767==    by 0x4022E8AD: __libc_start_main (in /lib/libc.so.6)
==5767==    by 0x8048390: (within /home/tot/memory)
==5767==    Address 0x40F4902E is 0 bytes after a block of size 10 alloc'd
==5767== at 0x40162F43: malloc (in /usr/lib/valgrind/valgrind.so)
==5767==    by 0x8048444: alloc_memory (in /home/tot/memory)
==5767==    by 0x8048463: main (in /home/tot/memory)
==5767==    by 0x4022E8AD: __libc_start_main (in /lib/libc.so.6)
==5767==
==5767== Invalid write of size 1
==5767== at 0x40163708: strcat (in /usr/lib/valgrind/valgrind.so)
==5767==    by 0x80484B2: main (in /home/tot/memory)
==5767==    by 0x4022E8AD: __libc_start_main (in /lib/libc.so.6)
==5767==    by 0x8048390: (within /home/tot/memory)
==5767==    Address 0x40F49035 is 7 bytes after a block of size 10 alloc'd
==5767== at 0x40162F43: malloc (in /usr/lib/valgrind/valgrind.so)
==5767==    by 0x8048444: alloc_memory (in /home/tot/memory)
==5767==    by 0x8048463: main (in /home/tot/memory)
==5767==    by 0x4022E8AD: __libc_start_main (in /lib/libc.so.6)
==5767==
==5767== Invalid read of size 1
==5767==    at 0x40163721: strlen (in /usr/lib/valgrind/valgrind.so)
==5767==    by 0x402658BD: _IO_vfprintf_internal (in 
...
...
==5767== Invalid read of size 1
==5767== at 0x4028ACCA:_IO_default_xsputn_internal(in /lib/libc.so.6)
==5767== by 0x40289D24: _IO_file_xsputn@@GLIBC_2.1 (in /lib/libc.so.6)
==5767==    by 0x40265A7A: _IO_vfprintf_internal (in /lib/libc.so.6)
==5767==    by 0x4026CBBF: _IO_printf (in /lib/libc.so.6)
==5767==    Address 0x40F4902E is 0 bytes after a block of size 10 alloc'd
==5767== at 0x40162F43: malloc (in /usr/lib/valgrind/valgrind.so)
==5767==    by 0x8048444: alloc_memory (in /home/tot/memory)
==5767==    by 0x8048463: main (in /home/tot/memory)
==5767==    by 0x4022E8AD: __libc_start_main (in /lib/libc.so.6)
 123456789
==5767==
==5767== Invalid free() / delete / delete[]
==5767==    at 0x401631DC: free (in /usr/lib/valgrind/valgrind.so)
==5767==    by 0x80484F2: main (in /home/tot/memory)
==5767==    by 0x4022E8AD: __libc_start_main (in /lib/libc.so.6)
==5767==    by 0x8048390: (within /home/tot/memory)
==5767==    Address 0x40F49024 is 0 bytes inside a block of size 10 free'd
==5767==    at 0x401631DC: free (in /usr/lib/valgrind/valgrind.so)
==5767==    by 0x80484F2: main (in /home/tot/memory)
==5767==    by 0x4022E8AD: __libc_start_main (in /lib/libc.so.6)
==5767==    by 0x8048390: (within /home/tot/memory)
==5767==
==5767== ERROR SUMMARY: 39 errors from 6 contexts (suppressed: 0 from 0)
==5767== malloc/free: in use at exit: 1000 bytes in 10 blocks.
==5767== malloc/free: 11 allocs, 10 frees, 1010 bytes allocated.
==5767== For a detailed leak analysis,  rerun with: --leak-check=yes
==5767== For counts of detected errors, rerun with: -v

Es fällt gleich auf, dass valgrind um einiges geschwätziger ist als efence. Ich möchte Ihnen die Ausgabe ein bisschen genauer erläutern, ohne mich in Details zu verstricken. Z. B. teilt Ihnen valgrind mit den folgenden Zeilen mit

==5767== Invalid write of size 1
==5767== at 0x40163708: strcat (in /usr/lib/valgrind/valgrind.so)
==5767==    by 0x80484B2: main (in /home/tot/memory)
==5767==    by 0x4022E8AD: __libc_start_main (in /lib/libc.so.6)
==5767==    by 0x8048390: (within /home/tot/memory)
==5767==    Address 0x40F49035 is 7 bytes after a block of size 10 alloc'd
==5767== at 0x40162F43: malloc (in /usr/lib/valgrind/valgrind.so)
==5767==    by 0x8048444: alloc_memory (in /home/tot/memory)
==5767==    by 0x8048463: main (in /home/tot/memory)
==5767==    by 0x4022E8AD: __libc_start_main (in /lib/libc.so.6)

dass hier ein ungültiger Schreibvorgang mit strcat() in der main()-Funktion durchgeführt wurde. Und zwar wurden hier 7 Bytes hinter einem reservierten Speicherblock geschrieben, der eigentlich nur für eine Größe von 10 Bytes vorgesehen war. Also wurden insgesamt 17 Bytes in den Speicher geschrieben. Des Weiteren wird aufgelistet, wo der Speicher reserviert wurde.

Dasselbe Problem wird dann ebenfalls in dreifacher Ausführung mit der anschließenden Ausgabe von printf() beschrieben (siehe Invalid read of size 1). Hier wird schließlich auch ein Lesezugriff auf einen nicht alloziierten Bereich ausgeführt.

Ebenso wird der Fehler einer nicht erlaubten Freigabe (falscher Zeiger) des Speichers bemängelt. Vorbildlich hat hier valgrind also alle Fehler auf einmal aufgedeckt. Verbessern Sie auch hier wieder die Fehler, und führen Sie valgrind erneut aus:

$~> gcc -o memory memory.c
$~> valgrind ./memory
==5798== Memcheck, a.k.a. Valgrind, a memory error detector for x86-linux.
...
==5798==
Eingabe machen: Jürgen
Die Eingabe lautet: Jürgen
==5798==
==5798== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
==5798== malloc/free: in use at exit: 10 bytes in 1 blocks.
==5798== malloc/free: 11 allocs, 10 frees, 1010 bytes allocated.
==5798== For a detailed leak analysis,  rerun with: --leak-check=yes
==5798== For counts of detected errors, rerun with: -v

So, jetzt scheint alles in Ordnung zu sein; keine Fehler werden mehr angezeigt. Aber dennoch stimmt ja noch etwas nicht. Hier haben Sie noch ein Memory Leak entdeckt:

==5798== malloc/free: in use at exit: 10 bytes in 1 blocks.
==5798== malloc/free: 11 allocs, 10 frees, 1010 bytes allocated.

Nach dem Ende der Anwendung sind immer noch 10 Bytes (1 Block) Speicher übrig, der nicht freigegeben wurde. Eine Zeile tiefer wird Ihnen das nochmals bestätigt – 11 allocs und 10 frees. Sie haben ein Memory Leak gefunden. Hier gehen wir der Anweisung der Ausgabe nach und verwenden das Flag --leak-check=yes, um Genaueres zu erfahren:

$~> valgrind --leak-check=yes ./memory
...
==5801==
Eingabe machen: Jürgen
Die Eingabe lautet: Jürgen
==5801==
==5801== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
==5801== malloc/free: in use at exit: 10 bytes in 1 blocks.
==5801== malloc/free: 11 allocs, 10 frees, 1010 bytes allocated.
==5801== For counts of detected errors, rerun with: -v
==5801== searching for pointers to 1 not-freed blocks.
==5801== checked 3413996 bytes.
==5801==
==5801== 10 bytes in 1 blocks are definitely lost in loss record 1 of 1
==5801== at 0x40162F43: malloc (in /usr/lib/valgrind/valgrind.so)
==5801==    by 0x8048414: alloc_memory (in /home/tot/memory)
==5801==    by 0x8048433: main (in /home/tot/memory)
==5801==    by 0x4022E8AD: __libc_start_main (in /lib/libc.so.6)
==5801==
==5801== LEAK SUMMARY:
==5801==    definitely lost: 10 bytes in 1 blocks.
==5801==    possibly lost:   0 bytes in 0 blocks.
==5801==    still reachable: 0 bytes in 0 blocks.
==5801==         suppressed: 0 bytes in 0 blocks.
==5801== Reachable blocks (those to which a pointer was found) are not shown.
==5801== To see them, rerun with: --show-reachable=yes
==5801==

Auf jeden Fall haben Sie 10 Bytes in der main()-Funktion stehen lassen. Den Fehler können Sie jetzt auch noch ausbügeln, und dann dürfte valgrind nichts mehr zu bemängeln haben:

$~> gcc -o memory memory.c
$~> valgrind --leak-check=yes ./memory
...
==5815==
Eingabe machen: Jürgen
Die Eingabe lautet: Jürgen
==5815==
==5815== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
==5815== malloc/free: in use at exit: 0 bytes in 0 blocks.
==5815== malloc/free: 11 allocs, 11 frees, 1010 bytes allocated.
==5815== For counts of detected errors, rerun with: -v
==5815== No malloc'd blocks -- no leaks are possible.

Keine Beanstandungen mehr von valgrind. Jetzt sollte das Programm fehlerfrei sein.

 << zurück
  
  Zum Rheinwerk-Shop
Neuauflage: Linux-UNIX-Programmierung
Neuauflage:
Linux-UNIX-
Programmierung

bestellen
 Ihre Meinung?
Wie hat Ihnen das Openbook gefallen?
Ihre Meinung

 Buchtipps
Zum Rheinwerk-Shop: Linux-Server






 Linux-Server


Zum Rheinwerk-Shop: Das Komplettpaket LPIC-1 & LPIC-2






 Das Komplettpaket
 LPIC-1 & LPIC-2


Zum Rheinwerk-Shop: Linux-Hochverfügbarkeit






 Linux-
 Hochverfügbarkeit


Zum Rheinwerk-Shop: Shell-Programmierung






 Shell-
 Programmierung


Zum Rheinwerk-Shop: Linux Handbuch






 Linux Handbuch


 Lieferung
Versandkostenfrei bestellen in Deutschland, Österreich und der Schweiz
Info





Copyright © Rheinwerk Verlag GmbH 2006
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das Openbook denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt.
Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


Nutzungsbestimmungen | Datenschutz | Impressum

Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de

Cookie-Einstellungen ändern