#include <pronix.de>

Home

 Programmieren
C-Programmieren
+ OpenBook
+ Linuxprogrammierung
+ Gtk+
+ Win32-API
Perl
CGI

 Bücher
C von A bis Z
C M&T easy
Rezensionen

 Service
Links
Feedback
Mailingliste
Newsletter

 Sonstiges
FAQ
Impressum
 

Kapitel 26: Sicheres Programmieren

26.2. Memory Leaks (Speicherlecks)            zurück  Ein Kapitel tiefer  zum Inhaltsverzeichnis

Wie bei Buffer Overflows sind auch Memory Leaks in den meisten Fällen durch Programmierfehler zu erklären. Der erste Verdacht, es könnte sich bei Memory Leaks um Hardwareprobleme handeln, täuscht.

Ein Memory Leak entsteht, wenn ein Programm dynamisch Speicher allokiert (malloc(), realloc() ...) und diese Speicherressourcen nicht mehr an das System zurückgibt (mittels free()). Es steht nicht unendlich viel Speicher vom Heap dafür zur Verfügung.

Programme wie das jetzt folgende erzeugen keine Probleme, wenn der Speicher nicht mehr an den Heap zurückgegeben wird:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
   char *p;

   p = (char *)malloc(sizeof("Hallo Welt\n"));
   strcpy(p, "Hallo Welt\n");
   if(NULL == p)
      {
         fprintf(stderr, "Abbruch: Speichermangel !!\n");
         exit(0);
      }
   printf("%s",p);
   return 0;
}

Hier bekommt der Heap seinen Speicher bei Beendigung des Programms sofort wieder zurück.

Was ist aber mit Programmen, die dauerhaft im Einsatz sein müssen? Ein gutes Beispiel sind Telefongesellschaften, die jedes laufende, eingehende und ausgehende Gespräch nach dem FIFO-Prinzip auf dem Heap ablegen und ständig für diese Datensätze Speicher auf dem Heap allokieren bzw. für ältere Datensätze wieder freigeben müssen.

Ein (stupides) Beispiel:

#include <stdio.h>
#include <stdlib.h>

int main()
{
   char *p;

   while(p = (char *)malloc(64000))
      {
         if(NULL == p)
            {
               fprintf(stderr, "Speicherplatzmangel!!\n");
               exit(0);
            }
         /*Tu was mit dem reservierten Speicher*/
      }
   return 0;
}

Dieses Programm wird wohl eine Weile ohne Probleme laufen. Aber umso länger das Programm läuft, umso mehr Speicher benötigt es vom Heap. Dies wird sich auf Dauer schlecht auf die Performance des Systems auswirken. Denn der Heap ist ja nicht nur für ein Programm allein da. Die anderen Programme, die ebenfalls Ressourcen benötigen, werden immer langsamer. Am Ende bleibt einem nichts anderes mehr übrig, als das System neu zu starten (abhängig vom Betriebssystem).

Meistens ist es aber schon geschehen, und das Programm längst fertig gestellt, wenn ein Speicherleck gefunden wird. Dann kann guter Rat teuer werden, wenn Sie sich nicht auskennen.

Eine primitive Möglichkeit, sofern Sie im Besitz des Quellcodes sind, ist es, so genannte Wrapper-Makros für speicherallozierte und speicherfreigebende Funktionen zu schreiben. Beispielsweise für die malloc()-Funktion:

#define malloc(size) \
        malloc(size);\
        printf("malloc in Zeile %ld der Datei %s (%ld Bytes) \n"\
        ,__LINE__,__FILE__, size);\
        count_malloc++;

Bei Verwendung der malloc()-Funktion im Programm wird jetzt jeweils eine Ausgabe auf dem Bildschirm erzeugt, die anzeigt, in welcher Zeile, in welchem Programm und wie viel Speicher malloc() verwendet. Außerdem wird die Verwendung von malloc() mitgezählt.

Dasselbe wird anschließend auch mit der Funktion free() vorgenommen. Die Anzahl der gezählten malloc()- und free()-Aufrufe wird am Ende in eine Datei namens DEBUG_FILE geschrieben.

#ifndef MEM_CHECK
#define MEM_CHECK
#define DEBUG_FILE "Debug"

static int count_malloc=0;
static int count_free  =0;
FILE *f;

#define malloc(size) \
        malloc(size);\
        printf("malloc in Zeile %ld der Datei %s (%ld Bytes) \n"\
        ,__LINE__,__FILE__, size);\
        count_malloc++;

#define free(x)\
        free(x); \
        x=NULL;\
        printf("free in Zeile %ld der Datei %s\n",__LINE__,__FILE__);\
        count_free++;

#define return 0; \
        f=fopen(DEBUG_FILE, "w");\
        fprintf(f, "Anzahl malloc : %ld\n",count_malloc);\
        fprintf(f, "Anzahl free   : %ld\n",count_free);\
        fclose(f);\
        printf("Datei : %s erstellt\n", DEBUG_FILE);\
        return 0;

#endif

Hier wurde eine Headerdatei namens mem_check.h erstellt, womit alle Aufrufe von malloc() und free() auf dem Bildschirm ausgeben werden. Und zwar darüber, in welcher Datei und welcher Zeile sich ein Aufruf dieser Funktion befindet. Außerdem wird auch die Anzahl der malloc()- und free()-Aufrufe mitgezählt. Sind mehr malloc()-Aufrufe als free()-Aufrufe vorhanden, wurde auf jeden Fall ein Speicherleck im Programm gefunden. Hier ein Listing zum Testen:

#include <stdio.h>
#include <stdlib.h>
#include "mem_check.h"

int main()
{
   char *p;

   p = (char *)malloc(sizeof("Hallo Welt\n"));
   if(NULL == p)
      {
         fprintf(stderr, "Speichermangel!!!\n");
         exit(0);
      }
   strcpy(p, "Hallo Welt\n");
   printf("%s",p);
   malloc(1024);
   free(p);
   return 0;
}

In der Praxis und bei größeren Projekten ist diese Version, Memory Leaks aufzuspüren, nur bedingt geeignet. Mit dem Makro return 0 habe ich es mir allzu leicht gemacht. Dies setzt nämlich voraus, dass ein Programm auch damit beendet wird. Oft haben Sie es aber mit dauerhaft laufenden Programmen zu tun.

Genauso sieht es mit der Zuordnung des allokierten und freigegebenen Speichers aus. Welches malloc() gehört zu welchem free()? Aber das Prinzip dürfte verstanden sein. Wenn Sie Fehler wie Memory Leaks finden wollen, haben Sie notfalls mit Wrapper-Makros eine gute Möglichkeit.

Meistens werden Sie schon eher auf eines der mittlerweile vielen Tools oder auf eine der Bibliotheken, die dafür programmiert wurden, zurückgreifen.

26.2.1 Bibliotheken und Tools zu Memory Leaks
Es gibt mittlerweile eine unüberschaubare Menge von solchen Debugging Tools. Daher erfolgt hier ein kleiner Überblick mit Angabe der Bezugsquellen. Meistens finden Sie dabei auf diesen Webseiten gleich die Dokumentation für die Anwendung.

ccmalloc
Bezugsquelle: http://www.inf.ethz.ch/personal/biere/projects/ccmalloc/
ccmalloc wird mit dem C/C++-Programm verlinkt und gibt nach Beendigung des Programms einen Bericht über Memory Leaks aus. ccmalloc ist nicht geeignet, um festzustellen, ob versucht wurde, aus illegalen Speicherbereichen zu lesen.

dbmalloc
Bezugsquelle: ftp://ftp.digital.com/pub/usenet/comp.sources.misc/volume32/dbmalloc/
dbmalloc ist in einer kommerziellen und einer kostenlosen Version erhältlich. Besondere Merkmale von dbmalloc sind:

  • Funktionsfluss, Datei und Zeileninformationen werden mit angegeben
  • Gibt Adressen zurück (hilfreich zusammen mit Debuggern)
  • Grenzberreichsüberprüfung
  • Ausgabe auf die Standard-Fehlerausgabe
  • Findet Memory Leaks

Generell wird dbmalloc, wie die meisten anderen Memory-Leaks-Tools zu einem Programm hinzugelinkt. Sie müssen also im Besitz des Quellcodes sein, um diesen neu zu übersetzen. Eine gute Anleitung in deutscher Sprache zu dieser Bibliothek finden Sie unter der URL: http://www.c-handbuch.org/

mpatrol
Bezugsquelle: http://www.cbmamiga.demon.co.uk/mpatrol/
mpatrol ist ein leistungsfähiges Tool zum Auffinden von Memory Leaks, was sich leider auf die Performance des Programms negativ auswirkt. Folgende Funktionsmerkmale stehen Ihnen dabei zur Verfügung:

  • Ein Abbild des Stacks wird bei einem Fehler angezeigt.
  • Datei- und Zeilen-Informationen werden mit ausgegeben.
  • Es ist kompatibel zu dmalloc, dbmalloc, insure und purify.
  • Es ist nicht unbedingt erforderlich, neu zu übersetzen, um seine Programme mit mpatrol zu testen.
  • Findet alle denkbaren Fehler auf dem Heap. Fehler auf dem Stack werden nicht gefunden.

Um ein Programm mit mpatrol zu testen, ist genau wie bei den meisten anderen Tools ein Überschreiben der speicheranfordernden und freigebenden Funktionsaufrufe notwendig. Bei mpatrol können Sie dies auf zwei Arten machen: Entweder Sie linken das Programm zu der statischen oder dynamischen Bibliothek oder Sie binden diese später durch einen Aufruf von

mpatrol --dynamc ./testprog -i file 

dynamisch mit in das Programm ein. Die letzte Möglichkeit funktioniert allerdings nur, wenn das Programm schon dynamisch zur Standard-C-Bibliothek übersetzt wurde und selbst dann nur auf einigen wenigen Systemen, die diesen Befehl unterstützen. Für eine deutsche Dokumentation sei auch hier wieder auf die Webseite http://www.c-handbuch.org/ verwiesen.

Es gibt außer diesen hier genannten Tools noch eine Reihe weiterer sehr guter Tools zum Auffinden von Memory Leaks. Einen guten Überblick können Sie sich auf der Seite http://www.cs.colorado.edu/~zorn/MallocDebug.html verschaffen.

26.3. Ausblick Sicherheitsprobleme            zurück  Ein Kapitel tiefer  zum Inhaltsverzeichnis

Es gibt noch eine Menge weiterer Sicherheitsprobleme, welche Sie auf den ersten Blick gar nicht als solche erkennen können. Schließlich haben Sie keine Garantie, dass Ihr Programm vor allen Problemen geschützt ist, wenn Sie auf sichere Funktionen zurückgreifen.

Zum Abschluss des Kapitels folgt hier noch ein kleiner Leitfaden zum Thema Sicherheit. Wenn Sie diese Punkte beherzigen, sollten sich Sicherheitsprobleme auf ein Minimum reduzieren lassen.

  • Vermeiden Sie Funktionen, die keine Längenprüfung der Ein- bzw. Ausgabe vornehmen (strcpy(), sprintf(), vsprintf(), scanf(), gets() …).
  • Verwenden Sie bei Funktionen, die eine formatierte Eingabe erwarten (etwa scanf()) eine Größenangabe (etwa %10s, %4d).
  • Ersetzen Sie gegebenenfalls unsichere Funktionen durch selbst geschriebene (z.B. gets() durch mygets() …).
  • Überprüfen Sie die Eingabe von der Tastatur auf zulässige Größe. Verlassen Sie sich nicht darauf, dass der Anwender schon das Richtige eingeben wird.
  • Verwenden Sie die exec-Funktionen und system() nur mit konstanten Strings. Lassen Sie niemals den Anwender selbst einen String zusammenbasteln.
  • Vergessen Sie bei der Funktion strlen() nicht, dass diese Funktion alle Zeichen ohne das Terminierungszeichen zählt. Beim Reservieren von Speicher müssen Sie dies berücksichtigen.
  • Und der Klassiker: Die Anzahl der Elemente in einem Array und die Adressierung über den Index beginnt bei 0.

Weiter mit Kapitel 27: CGI mit C            zum Inhaltsverzeichnis







 

 

© 2000 - 2003 Jürgen Wolf