Grundprinzipien der Linux-Speicherleckerkennung

1. mtrace analysiert Speicherlecks

mtrace (Memory Trace) ist ein Tool zur Erkennung von Speicherproblemen, das mit GNU Glibc geliefert wird. Es kann verwendet werden, um Speicherleckprobleme zu lokalisieren.

Sein Implementierungsquellcode befindet sich im Malloc-Verzeichnis des Glibc-Quellcodes. Sein grundlegendes Entwurfsprinzip besteht darin, eine Funktion void mtrace () zu entwerfen. Die Funktion verfolgt die Aufrufe von malloc/free und anderen Funktionen in der libc-Bibliothek und erkennt so, ob diese vorhanden sind ist ein Speicherleck. Bedingung. mtrace ist eine C-Funktion, deklariert und definiert in <mcheck.h>. Der Funktionsprototyp ist:

void mtrace(void);

mtrace-Prinzip

mtrace() Die Funktion installiert „Hook“-Funktionen für die Funktionen, die sich auf die dynamische Speicherzuweisung beziehen (z. B. malloc(), realloc(), memalign() und free()). Diese Hook-Funktionen zeichnen alle relevanten Speicherzuweisungen und die freigegebenen Tracking-Informationen auf und muntrace() entlädt die entsprechende Hook-Funktion.

Anhand der von diesen Hook-Funktionen generierten Debugging-Trace-Informationen können wir analysieren, ob Probleme wie „Speicherlecks“ vorliegen.

Legen Sie den Protokollgenerierungspfad fest

Der mtrace-Mechanismus erfordert, dass wir das Programm tatsächlich ausführen, bevor es Trace-Protokolle generieren kann. Bevor wir das Programm jedoch tatsächlich ausführen, müssen wir mtrace (die oben erwähnte Hook-Funktion) den Pfad zum Generieren der Protokolldatei mitteilen.

Es gibt zwei Möglichkeiten, den Protokollgenerierungspfad festzulegen: Eine besteht darin, die Umgebungsvariable festzulegen, export MALLOC_TRACE=./test.log // 当前目录下 die andere darin, sie auf Codeebene festzulegen: setenv("MALLOC_TRACE", "output_file_name", 1);``output_file_nameDies ist der Name der Datei, in der die Erkennungsergebnisse gespeichert werden.

Testbeispiel

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

int main(int argc, char **argv)
{
    mtrace();  // 开始跟踪

    char *p = (char *)malloc(100);
    free(p);
    p = NULL;
    p = (char *)malloc(100);

    muntrace();   // 结束跟踪,并生成日志信息
    return 0;
}

Wir hoffen, anhand des obigen Codes überprüfen zu können, ob vom Anfang bis zum Ende des Programms ein Speicherverlust vorliegt. Das Beispiel ist einfach. Sie können auf einen Blick erkennen, dass ein Speicherverlust vorliegt, also müssen wir dies tun Überprüfen Sie, ob mtrace nach Speicherlecks suchen kann, und prüfen Sie, wie die Ergebnisse analysiert und positioniert werden. gcc -g test.c -o testAusführbare Datei generieren.

Protokoll

Nachdem das Programm ausgeführt wurde, wird die Datei test.log im aktuellen Verzeichnis generiert. Wenn Sie sie öffnen, können Sie den folgenden Inhalt sehen:

= Start
@ ./test:[0x400624] + 0x21ed450 0x64
@ ./test:[0x400634] - 0x21ed450
@ ./test:[0x400646] + 0x21ed450 0x64
= End

Aus dieser Datei können wir ersehen, dass die drei Zeilen in der Mitte den Operationen malloc -> free -> malloc im Quellcode entsprechen. Interpretation : ./test bezieht sich auf den Namen des Programms, das wir ausführen. [0x400624] ist die Adressinformation im Maschinencode des ersten Aufrufs der Malloc-Funktion. + bedeutet, Speicher anzufordern (- bedeutet Freigabe), 0x21ed450 ist die Adresse Informationen, die von der Malloc-Funktion angefordert werden. 0x64 stellt die angeforderte Speichergröße dar.

Aus dieser Analyse geht hervor, dass die erste Anwendung veröffentlicht wurde, die zweite Anwendung jedoch nicht, und es liegt ein Speicherverlustproblem vor.

Leckanalyse

Verwenden Sie das Tool addr2line, um den Speicherort des Quellcodes zu ermitteln

Mit dem Befehlstool „addr2line“ können Sie die Zeilennummer der Quelldatei abrufen (Sie können damit den spezifischen Quellcodespeicherort anhand der Maschinencodeadresse lokalisieren).

# addr2line -e test 0x400624
/home/test.c:9

Verwenden Sie das Tool mtrace, um Protokollinformationen zu analysieren

mtrace + Pfad der ausführbaren Datei + Pfad der Protokolldatei  mtrace test ./test.logwerden ausgeführt und die folgenden Informationen werden ausgegeben:

Memory not freed:
-----------------
           Address     Size     Caller
0x00000000021ed450     0x64  at /home/test.c:14

2. Valgrind analysiert Speicherlecks

Einführung in das Valgrind-Tool

Valgrind ist eine Sammlung von Open-Source-Simulations- und Debugging-Tools (GPL V2) unter Linux. Valgrind besteht aus einem Kern und weiteren Debugging-Tools, die auf dem Kern basieren.

Der Kernel ähnelt einem Framework, das eine CPU-Umgebung simuliert und Dienste für andere Tools bereitstellt; andere Tools ähneln Plug-Ins und nutzen die vom Kernel bereitgestellten Dienste, um verschiedene spezifische Speicher-Debugging-Aufgaben auszuführen. Die Architektur von Valgrind ist in der folgenden Abbildung dargestellt

Bild

1、Memcheck

Das am häufigsten verwendete Tool dient zur Erkennung von Speicherproblemen in Programmen. Alle Lese- und Schreibvorgänge im Speicher werden erkannt und alle Aufrufe von malloc() / free() / new / delete werden erfasst.

Daher kann es die folgenden Probleme erkennen: Verwendung von nicht initialisiertem Speicher; Lesen/Schreiben freigegebener Speicherblöcke; Lesen/Schreiben von Speicherblöcken jenseits der Malloc-Zuweisung; Lesen/Schreiben ungeeigneter Speicherblöcke im Stapel; Speicherlecks, Verweisen auf einen Block. Speicherzeiger sind Für immer verloren; falsche malloc/free- oder new/delete-Übereinstimmung; dst- und src-Zeiger in memcpy()-bezogenen Funktionen überschneiden sich.

2、Callgrind

Ein Analysetool ähnlich wie gprof, das jedoch den Programmablauf detaillierter beobachtet und uns mehr Informationen liefern kann. Im Gegensatz zu gprof sind beim Kompilieren des Quellcodes keine besonderen Optionen erforderlich, es wird jedoch empfohlen, Debugging-Optionen hinzuzufügen.

Callgrind sammelt einige Daten, wenn das Programm ausgeführt wird, erstellt ein Funktionsaufrufdiagramm und kann optional eine Cache-Simulation durchführen. Am Ende des Laufs werden die Analysedaten in eine Datei geschrieben. callgrind_annotate kann den Inhalt dieser Datei in eine lesbare Form umwandeln.

3、Cachegrind

Der Cache-Analysator, der den First-Level-Cache I1, Dl und den Second-Level-Cache in der CPU simuliert, kann Cache-Fehler und -Treffer im Programm genau aufzeigen. Bei Bedarf kann es uns auch die Anzahl der Cache-Fehler, die Anzahl der Speicherreferenzen und die Anzahl der von jeder Codezeile, jeder Funktion, jedem Modul und dem gesamten Programm generierten Anweisungen liefern. Dies ist eine große Hilfe bei der Optimierung von Programmen.

4、Helgrind

Es wird hauptsächlich zur Überprüfung von Wettbewerbsproblemen verwendet, die in Multithread-Programmen auftreten. Helgrind sucht nach Speicherbereichen, auf die mehrere Threads zugreifen und die nicht konsistent gesperrt sind. In diesen Bereichen geht häufig die Synchronisierung zwischen Threads verloren und kann zu schwer zu findenden Fehlern führen.

Helgrind hat einen Rassenerkennungsalgorithmus namens „Eraser“ implementiert und weitere Verbesserungen vorgenommen, um die Anzahl der gemeldeten Fehler zu reduzieren. Allerdings befindet sich Helgrind noch im experimentellen Stadium.

5、Massiv

Der Stapelanalysator misst, wie viel Speicher ein Programm im Stapel verwendet, und gibt Auskunft über die Größe der Heap-Blöcke, der Heap-Verwaltungsblöcke und des Stapels.

Massif kann uns helfen, die Speichernutzung zu reduzieren. In modernen Systemen mit virtuellem Speicher kann es auch die Ausführung unserer Programme beschleunigen und die Wahrscheinlichkeit verringern, dass das Programm im Auslagerungsbereich verbleibt.

Zusätzlich werden Lakai und Nulgrind gestellt. Lackey ist ein kleines Tool, das selten verwendet wird; Nulgrind zeigt Entwicklern lediglich, wie man ein Tool erstellt.

Memcheck-Prinzip

Der Schwerpunkt dieses Artikels liegt auf der Erkennung von Speicherlecks, daher werde ich nicht zu viel über andere Tools von Valgrind erklären. Ich erkläre hauptsächlich die Arbeit von Memcheck. Das Prinzip der Memcheck-Erkennung von Speicherproblemen ist in der folgenden Abbildung dargestellt:

Bild

Der Schlüssel zur Fähigkeit von Memcheck, Speicherprobleme zu erkennen, liegt darin, dass zwei globale Tabellen erstellt werden.

  • Die Valid-Value-Tabelle verfügt über 8 Bits, die jedem Byte im gesamten Adressraum des Prozesses entsprechen. Außerdem gibt es für jedes Register der CPU einen entsprechenden Bitvektor. Diese Bits sind dafür verantwortlich, aufzuzeichnen, ob der Byte- oder Registerwert einen gültigen, initialisierten Wert hat.

  • Die Valid-Address-Tabelle verfügt über ein entsprechendes Bit für jedes Byte im gesamten Adressraum des Prozesses, das für die Aufzeichnung verantwortlich ist, ob die Adresse gelesen oder geschrieben werden kann.

  • Erkennungsprinzip: Wenn Sie ein Byte im Speicher lesen oder schreiben möchten, überprüfen Sie zunächst das diesem Byte entsprechende A-Bit in der Valid-Address-Tabelle. Wenn das A-Bit anzeigt, dass es sich bei dem Standort um einen ungültigen Standort handelt, meldet Memcheck einen Lese- und Schreibfehler. Der Kern ähnelt einer virtuellen CPU-Umgebung. Wenn also ein bestimmtes Byte im Speicher in die reale CPU geladen wird, wird das diesem Byte entsprechende V-Bit in der Valid-Value-Tabelle auch in die virtuelle CPU-Umgebung geladen. Sobald der Wert im Register zum Generieren einer Speicheradresse verwendet wird oder der Wert die Programmausgabe beeinflussen kann, überprüft Memcheck die entsprechenden V-Bits. Wenn der Wert nicht initialisiert wurde, wird ein nicht initialisierter Speicherfehler gemeldet.

Speicherlecktyp

Valgrind unterteilt Speicherlecks in 4 Kategorien:

  • Definitiv verloren: Der Speicher wurde nicht freigegeben, aber es gibt keinen Zeiger, der auf den Speicher zeigt, und auf den Speicher kann nicht zugegriffen werden. Es wurde festgestellt, dass ein Leck im laufenden Speicher unbedingt gepatcht werden muss.

  • Indirekt durchgesickert (indirekt verloren): Der durchgesickerte Speicherzeiger wird in dem durchgesickerten Speicher gespeichert. Da auf den durchgesickerten Speicher nicht zugegriffen werden kann, kann nicht auf den Speicher zugegriffen werden, der das indirekte Leck verursacht hat. Zum Beispiel:

struct list {
 struct list *next;
};

int main(int argc, char **argv)
{
 struct list *root;
 root = (struct list *)malloc(sizeof(struct list));
 root->next = (struct list *)malloc(sizeof(struct list));
 printf("root %p roop->next %p\n", root, root->next);
 root = NULL;
 return 0;
}

Was hier fehlt, ist der Root-Zeiger (der etablierte Leak-Typ), der dazu führt, dass der nächste im Root gespeicherte Zeiger zu einem indirekten Leak wird. Indirekt verloren gegangener Speicher muss auf jeden Fall gepatcht werden, aber normalerweise wird dieser zusammen mit dem Patch des festgestellten Lecks gepatcht.

  • Möglicherweise verloren: Die Nadel zeigt nicht auf die Speicher-Header-Adresse, sondern auf die Stelle im Speicher. Valgrind vermutet oft, dass ein Leck vorliegen könnte, weil die Hände bereits voreingenommen sind und nicht auf den Gedächtniskopf, sondern auf die inneren Teile des Gedächtnisses. In einigen Fällen handelt es sich hierbei nicht um ein Leck, da dieses Programm so konzipiert ist. Um beispielsweise eine Speicherausrichtung zu erreichen, wird zusätzlicher Anwendungsverarbeitungsspeicher an die ausgerichtete Speicheradresse zurückgegeben.

  • Immer noch erreichbar: Der Zeiger ist immer vorhanden und zum oberen Rand des Speichers geneigt, und der Speicher wird erst freigegeben, wenn das Programm beendet wird.

Valgrind-Parametereinstellungen

  • --leak-check=<no|summary|yes|full> Wenn auf „yes“ oder „full“ gesetzt, beschreibt valgrind nach Beendigung des aufgerufenen Programms jeden Speicherverlust im Detail. Der Standardwert ist die Zusammenfassung, die nur mehrere Speicherverluste meldet.

  • --log-fd= [Standard: 2, stderr] valgrind druckt Protokolle und speichert sie in der angegebenen Datei oder im Dateideskriptor. Ohne diesen Parameter werden die Protokolle von Valgrind zusammen mit den Protokollen des Benutzerprogramms ausgegeben, was sehr chaotisch erscheint.

  • --trace-children=<yes | no> [Standard: no] Ob untergeordnete Prozesse verfolgt werden sollen. Wenn es sich um ein Multiprozessprogramm handelt, wird die Verwendung dieser Funktion empfohlen. Die Aktivierung eines einzelnen Prozesses hat jedoch keine großen Auswirkungen.

  • --keep-debuginfo=<yes | no> [Standard: no] Wenn das Programm eine dynamisch geladene Bibliothek verwendet (dlopen), werden die Debug-Informationen gelöscht, wenn die dynamische Bibliothek entladen wird (dlclose). Nach Aktivierung dieser Option bleiben die Aufrufstapelinformationen auch dann erhalten, wenn die dynamische Bibliothek entladen wird.

  • --keep-stacktraces=<alloc | free | alloc-and-free | alloc-then-free | none> [Standard: alloc-and-free] Speicherlecks sind nichts anderes als eine Nichtübereinstimmung von Anwendung und Release sowie dem Funktionsaufruf Der Stapel ist nur Aufzeichnen bei der Anwendung oder Aufzeichnen bei der Beantragung der Veröffentlichung. Wenn wir uns nur auf Speicherlecks konzentrieren, besteht eigentlich keine Notwendigkeit, beides bei der Beantragung der Veröffentlichung aufzuzeichnen, da dies viel zusätzlichen Speicher belegt und mehr CPU-Verbrauch verursacht Die ohnehin schon langsame Ausführung des Programms macht das Ganze noch schlimmer.

  • --freelist-vol= Wenn ein Client-Programm free oder delete verwendet, um einen Speicherblock freizugeben, steht der Speicherblock nicht sofort für eine Neuzuweisung zur Verfügung. Er wird nur in eine Warteschlange für freie Blöcke (Freelist) gestellt und als nicht verfügbar markiert. Zugriff , was hilfreich ist, um Fehler zu erkennen, wenn das Client-Programm nach einer sehr wichtigen Zeitspanne auf den freigegebenen Block zugreift. Diese Option gibt die von der Warteschlange belegte Byteblockgröße an. Der Standardwert ist 20 MB. Durch Erhöhen dieser Option erhöht sich der Speicheraufwand von Memcheck, aber auch die Fähigkeit, solche Fehler zu erkennen, wird verbessert.

  • --freelist-big-blocks= Wenn verfügbare Speicherblöcke aus der Freelist-Warteschlange zur Neuzuweisung entnommen werden, entnimmt Memcheck einen Block entsprechend der Priorität aus den Speicherblöcken, die größer als number sind. Diese Option verhindert häufige Aufrufe kleiner Speicherblöcke in der Freelist. Diese Option erhöht die Wahrscheinlichkeit, Wild-Pointer-Fehler für kleine Speicherblöcke zu erkennen. Wenn diese Option auf 0 gesetzt ist, werden alle Blöcke nach dem First-In-First-Out-Prinzip neu zugewiesen. Der Standardwert ist 1M. Referenz: Einführung in Valgrind (Speicherprüftool)

Empfohlene Kompilierungsparameter

Um die Entstapelungsinformationen im Problemfall detailliert auszudrucken, ist es tatsächlich am besten, beim Kompilieren des Programms die Option -g hinzuzufügen. Wenn eine dynamisch geladene Bibliothek vorhanden ist, muss diese hinzugefügt werden  --keep-debuginfo=yes . Andernfalls kann die Symboltabelle nicht gefunden werden, wenn festgestellt wird, dass die dynamisch geladene Bibliothek durchgesickert ist, da die dynamische Bibliothek deinstalliert wurde. Für die Code-Compiler-Optimierung wird die Verwendung von -O2 und höher nicht empfohlen. -O0 verlangsamt wahrscheinlich den Vorgang, daher wird die Verwendung von -O1 empfohlen.

Beschreibung des Erkennungsbeispiels

Beantragen Sie Speicher, ohne ihn freizugeben

#include <stdlib.h>
#include <stdio.h>
void func()
{
  //只申请内存而不释放
    void *p=malloc(sizeof(int));
}
int main()
{
    func();
    return 0;
}

Verwenden Sie den Befehl valgrind, um das Programm auszuführen und das Protokoll in eine Datei auszugeben

valgrind --log-file=valReport --leak-check=full --show-reachable=yes --leak-resolution=low ./a.out

Parameterbeschreibung:

  • –log-file=valReport gibt an, eine Analyseprotokolldatei im aktuellen Ausführungsverzeichnis zu generieren, und der Dateiname lautet valReport

  • –leak-check=full zeigt Details zu jedem Leck an

  • –show-reachable=yes Gibt an, ob Lecks außerhalb des Kontrollbereichs erkannt werden sollen, z. B. globale Zeiger, statische Zeiger usw., und alle Speicherlecktypen anzuzeigen

  • –leak-resolution=Geringe Zusammenführungsstufe des Speicherverlustberichts

  • –track-origins=yes bedeutet, die Erkennungsfunktion „Verwendung von nicht initialisiertem Speicher“ zu aktivieren und detaillierte Ergebnisse zu öffnen. Wenn kein solcher Satz vorhanden ist, wird die Erkennung in diesem Bereich standardmäßig durchgeführt, detaillierte Ergebnisse werden jedoch nicht gedruckt. Nach der Ausführung der Ausgabe wird der Bericht interpretiert. 54017 bezieht sich auf die Prozessnummer. Wenn das Programm mit mehreren Prozessen ausgeführt wird, werden die Inhalte mehrerer Prozesse angezeigt.

==54017== Memcheck, a memory error detector
==54017== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==54017== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==54017== Command: ./a.out
==54017== Parent PID: 52130

Der zweite Absatz ist eine Zusammenfassung der Heap-Speicherzuweisung. Darin wird erwähnt, dass das Programm einmal Speicher beantragt hat, von dem 0 freigegeben und 4 Bytes zugewiesen wurden ( 1 allocs, 0 frees, 4 bytes allocated).

In der Kopfzusammenfassung sind die Gesamtmenge des vom Programm verwendeten Heap-Speichers, die Anzahl der Speicherzuweisungszeiten und die Anzahl der Speicherfreigabezeiten angegeben. Wenn die Anzahl der Speicherzuweisungszeiten und Speicherfreigabezeiten inkonsistent ist, bedeutet dies, dass dies der Fall ist ein Speicherleck.

==54017== HEAP SUMMARY:
==54017==   in use at exit: 4 bytes in 1 blocks
==54017==   total heap usage: 1 allocs, 0 frees, 4 bytes allocated

Der dritte Absatz beschreibt die spezifischen Informationen des Speicherverlusts. Es gibt einen Speicherbereich, der 4 Bytes belegt ( 4 bytes in 1 blocks). Er wird durch Aufrufen von malloc zugewiesen. Sie können im Aufrufstapel sehen, dass die Funktion func schließlich malloc aufgerufen hat, also diese Informationen ist relativ genau. Dies lokalisiert, wo unser verlorener Speicher beantragt wird.

==54017== 4 bytes in 1 blocks are definitely lost in loss record 1 of 1
==54017==    at 0x4C29F73: malloc (vg_replace_malloc.c:309)
==54017==    by 0x40057E: func() (in /home/oceanstar/CLionProjects/Share/src/a.out)
==54017==    by 0x40058D: main (in /home/oceanstar/CLionProjects/Share/src/a.out)

Der letzte Absatz ist eine Zusammenfassung, ein Speicherverlust von 4 Bytes.

==54017== LEAK SUMMARY:
==54017==    definitely lost: 4 bytes in 1 blocks  // 确立泄露
==54017==    indirectly lost: 0 bytes in 0 blocks  // 间接性泄露
==54017==    possibly lost: 0 bytes in 0 blocks   // 很有可能泄露
==54017==    still reachable: 0 bytes in 0 blocks // 仍可访达
==54017==    suppressed: 0 bytes in 0 blocks

Lesen und Schreiben über die Grenzen hinaus

#include <stdio.h>
#include <iostream>
int main()
{
    int len = 5;
    int *pt = (int*)malloc(len*sizeof(int)); //problem1: not freed
    int *p = pt;
    for (int i = 0; i < len; i++){
        p++;
    }
    *p = 5; //problem2: heap block overrun
    printf("%d\n", *p); //problem3: heap block overrun
    // free(pt);
    return 0;
}

Problem1: Der Zeiger pt beantragte Speicherplatz, wurde aber nicht freigegeben; Problem2: pt beantragte den Speicherplatz von 5 Ints, und als p nach 5 Zyklen die Position von p[5] erreichte, war der Zugriff außerhalb der Grenzen (der Schreibvorgang war …). außerhalb der Grenzen)  *p = 5. (Ungültiger Eintrag der Größe 4 im Valgrind-Bericht unten)

==58261== Invalid write of size 4
==58261==    at 0x400707: main (main.cpp:12)
==58261==  Address 0x5a23054 is 0 bytes after a block of size 20 alloc'd
==58261==    at 0x4C29F73: malloc (vg_replace_malloc.c:309)
==58261==    by 0x4006DC: main (main.cpp:7)

Problem 1: Lesen außerhalb der Grenzen (Ungültiges Lesen von Größe 4 im Valgrind-Bericht unten)

==58261== Invalid read of size 4
==58261==    at 0x400711: main (main.cpp:13)
==58261==  Address 0x5a23054 is 0 bytes after a block of size 20 alloc'd
==58261==    at 0x4C29F73: malloc (vg_replace_malloc.c:309)
==58261==    by 0x4006DC: main (main.cpp:7)

Wiederholte Veröffentlichung

#include <stdio.h>
#include <iostream>
int main()
{
    int *x;
    x = static_cast<int *>(malloc(8 * sizeof(int)));
    x = static_cast<int *>(malloc(8 * sizeof(int)));
    free(x);
    free(x);
    return 0;
}

Der Bericht lautet wie folgt:Invalid free() / delete / delete[] / realloc()

==59602== Invalid free() / delete / delete[] / realloc()
==59602==    at 0x4C2B06D: free (vg_replace_malloc.c:540)
==59602==    by 0x4006FE: main (main.cpp:10)
==59602==  Address 0x5a230a0 is 0 bytes inside a block of size 32 free'd
==59602==    at 0x4C2B06D: free (vg_replace_malloc.c:540)
==59602==    by 0x4006F2: main (main.cpp:9)
==59602==  Block was alloc'd at
==59602==    at 0x4C29F73: malloc (vg_replace_malloc.c:309)
==59602==    by 0x4006E2: main (main.cpp:8)

Die Anwendungsversionsschnittstelle stimmt nicht überein

Der Bericht über die Diskrepanz zwischen Anwendungs- und Release-Schnittstelle lautet wie folgt: Der Zeiger zum Beantragen von Speicherplatz mit malloc wird mit free freigegeben; der für die Verwendung von new beantragte Speicherplatz wird mit delete( ) freigegeben Mismatched free() / delete / delete []:

==61950== Mismatched free() / delete / delete []
==61950==    at 0x4C2BB8F: operator delete[](void*) (vg_replace_malloc.c:651)
==61950==    by 0x4006E8: main (main.cpp:8)
==61950==  Address 0x5a23040 is 0 bytes inside a block of size 5 alloc'd
==61950==    at 0x4C29F73: malloc (vg_replace_malloc.c:309)
==61950==    by 0x4006D1: main (main.cpp:7)

Speicher überschreiben

int main()
{
    char str[11];
    for (int i = 0; i < 11; i++){
        str[i] = i;
    }
    memcpy(str + 1, str, 5);
    char x[5] = "abcd";
    strncpy(x + 2, x, 3);
}

Das Problem liegt in memcpy. Das Kopieren von 5 Zeichen beginnend mit der str-Zeigerposition in das Leerzeichen, auf das str+1 zeigt, führt zum Überschreiben des Speichers. Das Gleiche gilt für strncpy. Der Bericht lautet wie folgt Source and destination overlap:

==61609== Source and destination overlap in memcpy(0x1ffefffe31, 0x1ffefffe30, 5)
==61609==    at 0x4C2E81D: memcpy@@GLIBC_2.14 (vg_replace_strmem.c:1035)
==61609==    by 0x400721: main (main.cpp:11)
==61609== 
==61609== Source and destination overlap in strncpy(0x1ffefffe25, 0x1ffefffe23, 3)
==61609==    at 0x4C2D453: strncpy (vg_replace_strmem.c:552)
==61609==    by 0x400748: main (main.cpp:14)

3. Zusammenfassung

Es gibt zwei Methoden zur Speichererkennung:

1. Pflegen Sie eine verknüpfte Liste mit Speicheroperationen. Wenn eine Speicheranwendungsoperation vorhanden ist, wird sie dieser verknüpften Liste hinzugefügt. Wenn eine Freigabeoperation vorhanden ist, wird sie aus der verknüpften Liste der Anwendungsoperation entfernt. Wenn nach Programmende noch Inhalt in der verknüpften Liste vorhanden ist, liegt ein Speicherverlust vor. Wenn der freizugebende Speichervorgang den entsprechenden Vorgang in der verknüpften Liste nicht findet, bedeutet dies, dass er mehrmals freigegeben wurde . Verwenden Sie diese Methode mit integrierten Debugging-Tools, Visual Leak Detecter, mtrace, memwatch, debug_new.

2. Simulieren Sie den Adressraum des Prozesses. Nach der Verarbeitung von Prozessspeicheroperationen durch das Betriebssystem wird im Benutzermodus eine Adressraumzuordnung verwaltet. Diese Methode erfordert ein tiefes Verständnis der Verarbeitung von Prozessadressräumen. Da die Prozessadressraumverteilung von Windows nicht Open Source ist, ist sie schwer zu simulieren und wird daher nur unter Linux unterstützt. Derjenige, der diesen Ansatz verfolgt, ist Valgrind.

Supongo que te gusta

Origin blog.csdn.net/weixin_41114301/article/details/132907079
Recomendado
Clasificación