Effizientes Memory Debugging in C/C++ Adam Szalkowski Embedded Computing Conference 2014
Ursachen/ Symptome Debugging Tools
Ursachen / Symptome Was habe ich falsch gemacht? Was kann denn passieren im schlimmsten Fall? Aber mein Code funktioniert doch
1. Memory Leaks Allozierter Speicher, der nicht wieder freigegeben wird Nicht-virtueller Destruktor Freigabe durch Exception Handling übersprungen Symptome: Systemspeicher läuft voll
1. Memory Leaks Allozierter Speicher, der nicht wieder freigegeben wird Nicht-virtueller Destruktor Freigabe durch Exception Handling übersprungen Symptome: Systemspeicher läuft voll void MemoryLeak() { int* array = new int[4]; if (condition) { throw Exception(); } delete[] array; }
2. Falsche Speicheroperationen Unpassendes free/delete/delete[] Unpassendes free/delete/delete[] nach malloc/new/new[] delete von lokalen/globalen Objekten Mehrfaches delete Gefahren: Programmabbruch Destruktor wird nicht ausgeführt Je nach Implementierung können die Heap Datenstrukturen inkonsistent werden
2. Falsche Speicheroperationen Unpassendes free/delete/delete[] Unpassendes free/delete/delete[] nach malloc/new/new[] delete von lokalen/globalen Objekten Mehrfaches delete Gefahren: Programmabbruch void WrongDelete() { int* array = new int[4]; delete array; delete array; } Destruktor wird nicht ausgeführt Je nach Implementierung können die Heap Datenstrukturen inkonsistent werden
3. Nicht initialisierte Variable Variable, die vor der Verwendung nicht initialisiert wurden Allokation auf Heap oder Stack Ignorieren von Compilerwarnung Symptome: Verhalten abhängig von Compileroptimierungen Zufällige Initialisierung Kann bei jedem Funktionsaufruf verschieden sein
3. Nicht initialisierte Variable Variable, die vor der Verwendung nicht initialisiert wurden Allokation auf Heap oder Stack Ignorieren von Compilerwarnung Symptome: Verhalten abhängig von Compileroptimierungen Zufällige Initialisierung void UninitializedLocal() { int array[4]; if (array[2] > 0) { } else { } } Kann bei jedem Funktionsaufruf verschieden sein
4. Ungültige Referenzen Zeiger auf Speicherorte, die eigentlich nicht mehr gültig sind Freigegebener Speicher Lokale Variable ausserhalb des Scope (z.b. nach return) Gefahren: Andere Werte können überschrieben werden Wert kann sich zu jedem Zeitpunkt ändern Abhängig von Compileroptimierungen
4. Ungültige Referenzen Zeiger auf Speicherorte, die eigentlich nicht mehr gültig sind Freigegebener Speicher Lokale Variable ausserhalb des Scope (z.b. nach return) Gefahren: void OutsideOfScope() { int* array; if (array[2] > 0) { int scoped_array[4]; array = &scoped_array; } int x,y; Andere Werte können überschrieben werden array[1] = 1; Wert kann sich } zu jedem Zeitpunkt ändern Abhängig von Compileroptimierungen
5. Index-Überlauf (OOB) Zugriff auf Elemente ausserhalb eines allozierten Speicherbereichs Off-by-one Fehler Fehlende Terminierung von Strings (z.b. nach strncpy) Gefahren: Lokale oder globale Variable können überschrieben werden Heap Datenstrukturen können überschrieben werden Potentielle Sicherheitslücken!
5. Index-Überlauf (OOB) Zugriff auf Elemente ausserhalb eines allozierten Speicherbereichs Off-by-one Fehler Fehlende Terminierung von Strings (z.b. nach strncpy) Gefahren: void ArrayLocalOOB() { int array[4]; int scalar = 3; Lokale oder globale Variable können überschrieben werden } Heap Datenstrukturen können überschrieben werden Potentielle Sicherheitslücken! for(int i=1; i <= 4; ++i) { array[i] = 0; }
6. Strict Aliasing Zwei Pointer verschiedenen Typs dürfen nicht auf dasselbe Objekt zeigen Seit C89/90 bzw C++98 Teil des Standards Erlaubt viele Compileroptimierungen Compiler darf Operationen umsortieren Compiler darf annehmen, dass sich Werte von Variablen nicht ändern Gefahren: Unerwartete Ergebnisse
6. Strict Aliasing Zwei Pointer verschiedenen Typs dürfen nicht auf dasselbe Objekt zeigen Seit C89/90 bzw C++98 Teil des Standards Erlaubt viele Compileroptimierungen Compiler darf Operationen umsortieren Compiler value darf annehmen, = 1; dass sich Werte von Variablen nicht ändern Gefahren: void StrictAliasing() { int value; set_values(value, reinterpret_cast<short*>(&value)); } static void set_values( int& value, short* value2) { } *value2 = 2; value = value - *value2; Unerwartete Ergebnisse
7. Data Race Unsynchronisierte Speicherzugriffe zwischen mehreren Threads Fehlende Synchronisierung durch Mutexe, Semaphoren & co Nicht-atomare Operationen Gefahren: Unerwartete Ergebnisse Fehlende Synchronisierung
7. Data Race Unsynchronisierte Speicherzugriffe zwischen mehreren Threads Fehlende Synchronisierung durch Mutexe, Semaphoren & co Nicht-atomare Operationen Gefahren: static volatile int value; void* thread_proc(void* param) { for(int i=0; i<1000000; ++i) { value += 1; } return 0; } Unerwartete Ergebnisse Fehlende Synchronisierung
Debugging Tools Welche Fehler kann/muss man statisch detektieren? Wie kann man fehlerhafte Speicherzugriffe automatisch detektieren? Was kann ich tun um Fehler zu vermeiden?
With a little help from my compiler Memory Leaks Nicht initialisierte Variable Statische Analyse und Laufzeittests Voraussetzungen: -Wall -O1 (besser -O2) -Wstrict-aliasing=2 -fstack-protector-all Ungültige Referenzen Falsche Speicheroperationen Index-Überlauf Strict Aliasing Data Race
Statische Analysen Clang Static Analyzer CppCheck Oink Cqual++ Splint cccc Klocwork Insight Gimpel Coverity PC-Lint PVS-Studio... Memory Leaks Nicht initialisierte Variable Ungültige Referenzen Falsche Speicheroperationen Index-Überlauf Strict Aliasing Data Race
GDB Memory Leaks Watchpoints (gdb) print &(x->a) (gdb) watch *$1 (gdb) continue Conditional Breakpoints (gdb) break main.cpp:20 (gdb) cond 1 (i == 3) Nicht initialisierte Variable Ungültige Referenzen Falsche Speicheroperationen Index-Überlauf Strict Aliasing Data Race
valgrind Memory Leaks Framework zur automatischen dynamischen Analyse von Programmen Unterstützt viele Architekturen Nur Linux (und Mac OSX) Nicht initialisierte Variable Ungültige Referenzen Memcheck SGCheck Helgrind Falsche Speicheroperationen Index-Überlauf Strict Aliasing Data Race
Dr. Memory Memory Leaks Nicht initialisierte Variable Ungültige Referenzen Windows und Linux Nur Intel x86 (32bit) Features und Performance ähnlich zu memcheck Falsche Speicheroperationen Index-Überlauf Strict Aliasing Data Race
Address/Thread Sanitizer GCC >= 3.8, clang >= 3.2 Memory Leaks Nicht initialisierte Variable Address Sanitizer: -fsanitize=address Thread Sanitizer: -fsanitize=thread Findet Data races und new/delete races Ungültige Referenzen Falsche Speicheroperationen Index-Überlauf Strict Aliasing Data Race
Vermeiden ist billiger als Fehlersuche Vermeiden von Pointern/new/delete std::shared_ptr, std::unique_ptr std::vector, std::string, std::array in-class initialization of non-static members class C { int a = 1; }; Synchronisation und atomare Operationen std::atomic std::lock_guard, std::call_once, std::future Memory Leaks Nicht initialisierte Variable Ungültige Referenzen Falsche Speicheroperationen Index-Überlauf Strict Aliasing Data Race
Ursachen/ Symptome Debugging Tools