Wie verwende ich Valgrind, um Speicherlecks zu finden?

180

Wie verwende ich valgrind, um die Speicherlecks in einem Programm zu finden?

Bitte hilft mir jemand und beschreibt die Schritte zur Durchführung des Verfahrens?

Ich benutze Ubuntu 10.04 und habe ein Programm a.c, bitte helfen Sie mir.

user484457
quelle
16
Sie verwenden valgrind, um Ihr kompiliertes Programm zu testen , nicht den Quellcode.
Tony
6
Die unten von @RageD gegebene Antwort ist richtig. Warum akzeptierst du sie nicht?
Pratik Singhal
1
Ein Leck wird durch etwas verursacht, das Sie nicht tun - dh. frei zugeteilter Speicher. Daher kann Valgrind Ihnen nicht zeigen, wo sich das Leck befindet - nur Sie wissen, wo der zugewiesene Speicher nicht mehr benötigt wird. Indem Sie jedoch mitteilen, welche Zuordnung nicht frei ist () d, indem Sie die Verwendung dieses Speichers durch Ihr Programm verfolgen, sollten Sie in der Lage sein, zu bestimmen, wo er frei werden soll () d. Ein häufiger Fehler ist das Beenden einer Funktion, ohne den zugewiesenen Speicher freizugeben.
MikeW
1
Verwandte: mit jedem Tool: stackoverflow.com/questions/6261201/…
Ciro Santilli 法轮功 冠状 病 六四 事件 法轮功

Antworten:

294

Wie man Valgrind ausführt

Nicht um das OP zu beleidigen, sondern für diejenigen, die auf diese Frage kommen und noch neu in Linux sind - möglicherweise müssen Sie Valgrind auf Ihrem System installieren .

sudo apt install valgrind  # Ubuntu, Debian, etc.
sudo yum install valgrind  # RHEL, CentOS, Fedora, etc.

Valgrind kann problemlos für C / C ++ - Code verwendet werden, kann aber bei ordnungsgemäßer Konfiguration auch für andere Sprachen verwendet werden (siehe dies für Python).

Übergeben Sie zum Ausführen von Valgrind die ausführbare Datei als Argument (zusammen mit allen Parametern an das Programm).

valgrind --leak-check=full \
         --show-leak-kinds=all \
         --track-origins=yes \
         --verbose \
         --log-file=valgrind-out.txt \
         ./executable exampleParam1

Die Flaggen sind kurz:

  • --leak-check=full: "Jedes einzelne Leck wird detailliert dargestellt"
  • --show-leak-kinds=all: Zeigen Sie im "vollständigen" Bericht alle "bestimmten, indirekten, möglichen, erreichbaren" Leckarten an.
  • --track-origins=yes: Bevorzugen Sie die nützliche Ausgabe gegenüber der Geschwindigkeit. Dies verfolgt die Ursprünge nicht initialisierter Werte, die bei Speicherfehlern sehr nützlich sein können. Schalten Sie das Gerät aus, wenn Valgrind nicht akzeptabel langsam ist.
  • --verbose: Kann Sie über ungewöhnliches Verhalten Ihres Programms informieren. Wiederholen Sie dies für mehr Ausführlichkeit.
  • --log-file: In eine Datei schreiben. Nützlich, wenn die Ausgabe den Terminalraum überschreitet.

Schließlich möchten Sie einen Valgrind-Bericht sehen, der folgendermaßen aussieht:

HEAP SUMMARY:
    in use at exit: 0 bytes in 0 blocks
  total heap usage: 636 allocs, 636 frees, 25,393 bytes allocated

All heap blocks were freed -- no leaks are possible

ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

Ich habe ein Leck, aber WO ?

Sie haben also einen Speicherverlust und Valgrind sagt nichts Sinnvolles. Vielleicht so etwas:

5 bytes in 1 blocks are definitely lost in loss record 1 of 1
   at 0x4C29BE3: malloc (vg_replace_malloc.c:299)
   by 0x40053E: main (in /home/Peri461/Documents/executable)

Werfen wir einen Blick auf den C-Code, den ich auch geschrieben habe:

#include <stdlib.h>

int main() {
    char* string = malloc(5 * sizeof(char)); //LEAK: not freed!
    return 0;
}

Nun, es gingen 5 Bytes verloren. Wie ist es passiert? Der Fehlerbericht sagt nur mainund malloc. In einem größeren Programm wäre es sehr mühsam, nach etwas zu suchen. Dies liegt daran, wie die ausführbare Datei kompiliert wurde . Wir können tatsächlich zeilenweise Details darüber erhalten, was schief gelaufen ist. Kompilieren Sie Ihr Programm mit einem Debug-Flag neu (ich verwende es gcchier):

gcc -o executable -std=c11 -Wall main.c         # suppose it was this at first
gcc -o executable -std=c11 -Wall -ggdb3 main.c  # add -ggdb3 to it

Mit diesem Debug-Build zeigt Valgrind auf die genaue Codezeile , die den durchgesickerten Speicher zuweist! (Der Wortlaut ist wichtig: Es ist möglicherweise nicht genau der Ort, an dem sich Ihr Leck befindet, sondern das , was durchgesickert ist. Die Spur hilft Ihnen dabei, herauszufinden, wo .)

5 bytes in 1 blocks are definitely lost in loss record 1 of 1
   at 0x4C29BE3: malloc (vg_replace_malloc.c:299)
   by 0x40053E: main (main.c:4)

Techniken zum Debuggen von Speicherlecks und -fehlern

  • Nutzen Sie www.cplusplus.com ! Es hat eine großartige Dokumentation zu C / C ++ - Funktionen.
  • Allgemeine Hinweise für Speicherlecks:
    • Stellen Sie sicher, dass Ihr dynamisch zugewiesener Speicher tatsächlich freigegeben wird.
    • Ordnen Sie keinen Speicher zu und vergessen Sie nicht, den Zeiger zuzuweisen.
    • Überschreiben Sie einen Zeiger nicht mit einem neuen, es sei denn, der alte Speicher wird freigegeben.
  • Allgemeine Hinweise für Speicherfehler:
    • Greifen Sie auf Adressen und Indizes zu und schreiben Sie auf diese, von denen Sie sicher sind, dass sie Ihnen gehören. Speicherfehler unterscheiden sich von Lecks. Sie sind oft nur IndexOutOfBoundsException Typprobleme.
    • Greifen Sie nach dem Freigeben nicht auf den Speicher zu oder schreiben Sie nicht in den Speicher.
  • Manchmal können Ihre Lecks / Fehler miteinander verknüpft sein, ähnlich wie bei einer IDE, die feststellt, dass Sie noch keine schließende Klammer eingegeben haben. Durch die Lösung eines Problems können andere Probleme behoben werden. Suchen Sie daher nach einem Problem, das als guter Schuldiger gilt, und wenden Sie einige der folgenden Ideen an:

    • Listen Sie die Funktionen in Ihrem Code auf, die von dem "fehlerhaften" Code abhängen, der den Speicherfehler aufweist. Folgen Sie der Programmausführung (vielleicht sogar in gdbvielleicht) und suchen Sie nach Vor- / Nachbedingungsfehlern. Die Idee ist, die Ausführung Ihres Programms zu verfolgen und sich dabei auf die Lebensdauer des zugewiesenen Speichers zu konzentrieren.
    • Versuchen Sie, den "beleidigenden" Codeblock auskommentieren (im Rahmen des Zumutbaren, damit Ihr Code weiterhin kompiliert wird). Wenn der Valgrind-Fehler behoben ist, haben Sie gefunden, wo er sich befindet.
  • Wenn alles andere fehlschlägt, versuchen Sie es nachzuschlagen. Valgrind hat auch Dokumentation !

Ein Blick auf häufige Lecks und Fehler

Achte auf deine Zeiger

60 bytes in 1 blocks are definitely lost in loss record 1 of 1
   at 0x4C2BB78: realloc (vg_replace_malloc.c:785)
   by 0x4005E4: resizeArray (main.c:12)
   by 0x40062E: main (main.c:19)

Und der Code:

#include <stdlib.h>
#include <stdint.h>

struct _List {
    int32_t* data;
    int32_t length;
};
typedef struct _List List;

List* resizeArray(List* array) {
    int32_t* dPtr = array->data;
    dPtr = realloc(dPtr, 15 * sizeof(int32_t)); //doesn't update array->data
    return array;
}

int main() {
    List* array = calloc(1, sizeof(List));
    array->data = calloc(10, sizeof(int32_t));
    array = resizeArray(array);

    free(array->data);
    free(array);
    return 0;
}

Als Lehrassistent habe ich diesen Fehler oft gesehen. Der Schüler verwendet eine lokale Variable und vergisst, den ursprünglichen Zeiger zu aktualisieren. Der Fehler besteht darin, dass reallocder zugewiesene Speicher tatsächlich an einen anderen Ort verschoben und die Position des Zeigers geändert werden kann. Wir gehen dann, resizeArrayohne zu sagen, array->datawohin das Array verschoben wurde.

Ungültiges Schreiben

1 errors in context 1 of 1:
Invalid write of size 1
   at 0x4005CA: main (main.c:10)
 Address 0x51f905a is 0 bytes after a block of size 26 alloc'd
   at 0x4C2B975: calloc (vg_replace_malloc.c:711)
   by 0x400593: main (main.c:5)

Und der Code:

#include <stdlib.h>
#include <stdint.h>

int main() {
    char* alphabet = calloc(26, sizeof(char));

    for(uint8_t i = 0; i < 26; i++) {
        *(alphabet + i) = 'A' + i;
    }
    *(alphabet + 26) = '\0'; //null-terminate the string?

    free(alphabet);
    return 0;
}

Beachten Sie, dass Valgrind uns auf die kommentierte Codezeile oben verweist. Das Array der Größe 26 ist indiziert [0,25], weshalb *(alphabet + 26)es sich um ein ungültiges Schreiben handelt - es ist außerhalb der Grenzen. Ein ungültiger Schreibvorgang ist eine häufige Folge von Fehlern nacheinander. Schauen Sie sich die linke Seite Ihrer Zuweisungsoperation an.

Ungültiges Lesen

1 errors in context 1 of 1:
Invalid read of size 1
   at 0x400602: main (main.c:9)
 Address 0x51f90ba is 0 bytes after a block of size 26 alloc'd
   at 0x4C29BE3: malloc (vg_replace_malloc.c:299)
   by 0x4005E1: main (main.c:6)

Und der Code:

#include <stdlib.h>
#include <stdint.h>

int main() {
    char* destination = calloc(27, sizeof(char));
    char* source = malloc(26 * sizeof(char));

    for(uint8_t i = 0; i < 27; i++) {
        *(destination + i) = *(source + i); //Look at the last iteration.
    }

    free(destination);
    free(source);
    return 0;
}

Valgrind zeigt uns auf die kommentierte Zeile oben. Schauen Sie sich hier die letzte Iteration an
*(destination + 26) = *(source + 26);. Jedoch *(source + 26)ist wieder die Grenzen aus, ähnlich wie bei der ungültigen Schreib. Ungültige Lesevorgänge sind auch eine häufige Folge von Fehlern nacheinander. Schauen Sie sich die rechte Seite Ihrer Zuweisungsoperation an.


Die Open Source (U / Dys) Topia

Woher weiß ich, wann das Leck mir gehört? Wie finde ich mein Leck, wenn ich den Code eines anderen verwende? Ich habe ein Leck gefunden, das nicht meins ist. soll ich etwas tun Alle sind berechtigte Fragen. Zunächst zwei Beispiele aus der Praxis, die zwei Klassen gemeinsamer Begegnungen zeigen.

Jansson : eine JSON-Bibliothek

#include <jansson.h>
#include <stdio.h>

int main() {
    char* string = "{ \"key\": \"value\" }";

    json_error_t error;
    json_t* root = json_loads(string, 0, &error); //obtaining a pointer
    json_t* value = json_object_get(root, "key"); //obtaining a pointer
    printf("\"%s\" is the value field.\n", json_string_value(value)); //use value

    json_decref(value); //Do I free this pointer?
    json_decref(root);  //What about this one? Does the order matter?
    return 0;
}

Dies ist ein einfaches Programm: Es liest eine JSON-Zeichenfolge und analysiert sie. Bei der Erstellung verwenden wir Bibliotheksaufrufe, um das Parsen für uns durchzuführen. Jansson nimmt die erforderlichen Zuordnungen dynamisch vor, da JSON verschachtelte Strukturen von sich selbst enthalten kann. Dies bedeutet jedoch nicht, dass wir decrefden uns gegebenen Speicher von jeder Funktion "befreien" oder "befreien". Tatsächlich löst dieser Code, den ich oben geschrieben habe, sowohl ein "ungültiges Lesen" als auch ein "ungültiges Schreiben" aus. Diese Fehler verschwinden, wenn Sie die decrefLeitung für herausnehmen value.

Warum? Die Variable valuewird in der Jansson-API als "geliehene Referenz" betrachtet. Jansson verfolgt den Speicher für Sie und Sie müssen einfach decref JSON-Strukturen unabhängig voneinander erstellen. Die Lektion hier: Lesen Sie die Dokumentation . Ja wirklich. Es ist manchmal schwer zu verstehen, aber sie sagen dir, warum diese Dinge passieren. Stattdessen haben wir bereits Fragen zu diesem Speicherfehler.

SDL : eine Grafik- und Spielebibliothek

#include "SDL2/SDL.h"

int main(int argc, char* argv[]) {
    if (SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO) != 0) {
        SDL_Log("Unable to initialize SDL: %s", SDL_GetError());
        return 1;
    }

    SDL_Quit();
    return 0;
}

Was ist los mit diesem Code ? Es verliert durchweg ~ 212 KiB Speicher für mich. Nehmen Sie sich einen Moment Zeit, um darüber nachzudenken. Wir schalten SDL ein und dann aus. Antworten? Da ist nichts falsch.

Das mag zunächst bizarr klingen . Um ehrlich zu sein, Grafiken sind chaotisch und manchmal muss man einige Lecks als Teil der Standardbibliothek akzeptieren. Die Lehre hier: Sie müssen nicht jedes Speicherleck unterdrücken . Manchmal müssen Sie nur die Lecks unterdrücken, da es sich um bekannte Probleme handelt, gegen die Sie nichts tun können . (Dies ist nicht meine Erlaubnis, Ihre eigenen Lecks zu ignorieren!)

Antworten auf die Leere

Woher weiß ich, wann das Leck mir gehört?
Es ist. (99% sicher jedenfalls)

Wie finde ich mein Leck, wenn ich den Code eines anderen verwende?
Wahrscheinlich hat es jemand anderes bereits gefunden. Probieren Sie Google aus! Wenn dies fehlschlägt, verwenden Sie die Fähigkeiten, die ich Ihnen oben gegeben habe. Wenn dies fehlschlägt und Sie meistens API-Aufrufe und wenig von Ihrem eigenen Stack-Trace sehen, lesen Sie die nächste Frage.

Ich habe ein Leck gefunden, das nicht meins ist. soll ich etwas tun
Ja! Die meisten APIs bieten Möglichkeiten, Fehler und Probleme zu melden. Benutze sie! Helfen Sie dabei, die Tools zurückzugeben, die Sie in Ihrem Projekt verwenden!


Weiterführende Literatur

Danke, dass du so lange bei mir bist. Ich hoffe, Sie haben etwas gelernt, als ich versuchte, mich um das breite Spektrum der Menschen zu kümmern, die zu dieser Antwort kamen. Einige Dinge, die Sie hoffentlich unterwegs gefragt haben: Wie funktioniert der Speicherzuweiser von C? Was ist eigentlich ein Speicherverlust und ein Speicherfehler? Wie unterscheiden sie sich von Segfaults? Wie funktioniert Valgrind? Wenn Sie eines davon hatten, füttern Sie bitte Ihre Neugier:

Joshua Detwiler
quelle
4
Weitaus bessere Antwort, schade, dass dies nicht die akzeptierte Antwort ist.
A. Smoliak
Ich glaube, es ist eine gute Praxis, so etwas zu tun, ich habe einige selbst gemacht
A. Smoliak
1
Kann ich diese Antwort markieren und als zukünftige Referenz für mich selbst verwenden? Gute Arbeit!
Zap
Ist das memcheckTool standardmäßig aktiviert?
Abhiarora
@abhiarora Ja. Die Manpage sagt uns, dass dies memcheckdas Standardwerkzeug ist:--tool=<toolname> [default: memcheck]
Joshua Detwiler
146

Versuche dies:

valgrind --leak-check=full -v ./your_program

Solange valgrind installiert ist, wird es Ihr Programm durchlaufen und Ihnen mitteilen, was falsch ist. Es kann Ihnen Hinweise und ungefähre Stellen geben, an denen Ihre Lecks gefunden werden können. Wenn Sie einen Segfault haben, versuchen Sie es durchzulaufen gdb.

RageD
quelle
Was bedeutet "your_program"? Ist dieser Quellcode-Speicherort oder Anwendungsname wie eine APK-Datei?
HoangVu
7
your_program== der Name der ausführbaren Datei oder ein beliebiger Befehl, mit dem Sie Ihre Anwendung ausführen.
RageD
27

Du kannst rennen:

valgrind --leak-check=full --log-file="logfile.out" -v [your_program(and its arguments)]
Rajat Paliwal
quelle
1

Sie können einen Alias ​​in der .bashrc-Datei wie folgt erstellen

alias vg='valgrind --leak-check=full -v --track-origins=yes --log-file=vg_logfile.out'

Wenn Sie also Speicherlecks überprüfen möchten, tun Sie dies einfach

vg ./<name of your executable> <command line parameters to your executable>

Dadurch wird eine Valgrind-Protokolldatei im aktuellen Verzeichnis generiert.

Sachin Rastogi
quelle