Dynamische Analysemethoden
Hier beschreibe ich einige dynamische Analysemethoden.
Bei dynamischen Methoden wird das Programm tatsächlich ausgeführt, um den Aufrufgraph zu ermitteln.
Das Gegenteil von dynamischen Methoden sind statische Methoden, die versuchen, sie allein aus dem Quelltext zu ermitteln, ohne das Programm auszuführen.
Vorteile der dynamischen Methoden:
- fängt Funktionszeiger und virtuelle C++-Aufrufe ab. Diese sind in großer Zahl in jeder nicht-trivialen Software vorhanden.
Nachteile der dynamischen Methoden:
- Sie müssen das Programm ausführen, was langsam sein kann oder ein Setup erfordert, das Sie nicht haben, z. B. Cross-Compilation
- werden nur Funktionen angezeigt, die tatsächlich aufgerufen wurden. Einige Funktionen könnten z.B. abhängig von den Befehlszeilenargumenten aufgerufen werden oder nicht.
KcacheGrind
https://kcachegrind.github.io/html/Home.html
Testprogramm:
int f2(int i) { return i + 2; }
int f1(int i) { return f2(2) + i + 1; }
int f0(int i) { return f1(1) + f2(2); }
int pointed(int i) { return i; }
int not_called(int i) { return 0; }
int main(int argc, char **argv) {
int (*f)(int);
f0(1);
f1(1);
f = pointed;
if (argc == 1)
f(1);
if (argc == 2)
not_called(1);
return 0;
}
Verwendung:
sudo apt-get install -y kcachegrind valgrind
# Compile the program as usual, no special flags.
gcc -ggdb3 -O0 -o main -std=c99 main.c
# Generate a callgrind.out.<PID> file.
valgrind --tool=callgrind ./main
# Open a GUI tool to visualize callgrind data.
kcachegrind callgrind.out.1234
Sie befinden sich nun in einem fantastischen GUI-Programm, das eine Menge interessanter Leistungsdaten enthält.
Wählen Sie unten rechts die Registerkarte "Anrufdiagramm". Hier wird ein interaktives Anrufdiagramm angezeigt, das beim Anklicken der Funktionen mit Leistungskennzahlen in anderen Fenstern korreliert.
Um das Diagramm zu exportieren, klicken Sie es mit der rechten Maustaste an und wählen Sie "Grafik exportieren". Das exportierte PNG sieht wie folgt aus:
Daran können wir erkennen, dass:
- der Wurzelknoten ist
_start
der den eigentlichen ELF-Einstiegspunkt darstellt und Glibc-Initialisierungs-Boilerplate enthält
f0
, f1
y f2
werden wie erwartet voneinander abgerufen
pointed
wird ebenfalls angezeigt, obwohl wir sie mit einem Funktionszeiger aufgerufen haben. Sie wäre möglicherweise nicht aufgerufen worden, wenn wir ein Kommandozeilenargument übergeben hätten.
not_called
wird nicht angezeigt, weil es im Lauf nicht aufgerufen wurde, weil wir kein zusätzliches Kommandozeilenargument übergeben haben.
Das Tolle an valgrind
ist, dass es keine besonderen Kompilierungsoptionen erfordert.
Daher können Sie es auch dann verwenden, wenn Sie nicht über den Quellcode, sondern nur über die ausführbare Datei verfügen.
valgrind
schafft das, indem es Ihren Code durch eine leichtgewichtige "virtuelle Maschine" laufen lässt. Dies macht die Ausführung im Vergleich zur nativen Ausführung auch extrem langsam.
Wie in der Grafik zu sehen ist, werden auch Zeitinformationen über jeden Funktionsaufruf ermittelt, die zur Erstellung eines Programmprofils verwendet werden können, was wahrscheinlich der ursprüngliche Verwendungszweck dieser Einrichtung ist, und nicht nur zur Anzeige von Aufrufgraphen: Wie kann ich ein Profil für C++-Code erstellen, der unter Linux läuft?
Getestet auf Ubuntu 18.04.
gcc -finstrument-functions
+ etrace
https://github.com/elcritch/etrace
-finstrument-functions
fügt Rückrufe hinzu parst etrace die ELF-Datei und implementiert alle Rückrufe.
Ich konnte es aber leider nicht zum Laufen bringen: Warum funktioniert `-finstrument-functions` bei mir nicht?
Die angegebene Ausgabe hat das Format:
\-- main
| \-- Crumble_make_apple_crumble
| | \-- Crumble_buy_stuff
| | | \-- Crumble_buy
| | | \-- Crumble_buy
| | | \-- Crumble_buy
| | | \-- Crumble_buy
| | | \-- Crumble_buy
| | \-- Crumble_prepare_apples
| | | \-- Crumble_skin_and_dice
| | \-- Crumble_mix
| | \-- Crumble_finalize
| | | \-- Crumble_put
| | | \-- Crumble_put
| | \-- Crumble_cook
| | | \-- Crumble_put
| | | \-- Crumble_bake
Dies ist wahrscheinlich die effizienteste Methode, abgesehen von spezieller Hardware-Tracing-Unterstützung, hat aber den Nachteil, dass Sie den Code neu kompilieren müssen.