Ich arbeite derzeit an einem eingebetteten Projekt (STM32F103RB, CooCox CoIDE v.1.7.6 mit arm-none-eabi-gcc 4.8 2013q4) und versuche zu verstehen, wie malloc() verhält sich auf Ebene C wenn der RAM voll ist.
Mein STM32 hat 20kB = 0x5000Bytes RAM, 0x200 werden für den Stack verwendet.
#include <stdlib.h>
#include "stm32f10x.h"
struct list_el {
char weight[1024];
};
typedef struct list_el item;
int main(void)
{
item * curr;
// allocate until RAM is full
do {
curr = (item *)malloc(sizeof(item));
} while (curr != NULL);
// I know, free() is missing. Program is supposed to crash
return 0;
}
Ich würde erwarten malloc() zurückgeben NULL sobald der Heap zu klein zum Zuordnen ist:
Gibt es eine Möglichkeit, die Größe des verbleibenden Speichers abzurufen?
– Déjà-vu
15. März 2014 um 11:04 Uhr
@BernhardSchlegel uClibc ist eine bestimmte Implementierung der Standard-C-Bibliothek, Sie könnten Ihre C-Bibliothek mit Debug-Symbolen kompilieren und dann einen Debugger verwenden, um einzugreifen malloc und sehen Sie genau, welche Zeile den Aufruf des Hardfault-Handlers verursacht. Sie können GCC mit verschiedenen Implementierungen der C-Bibliothek verwenden, sodass die Aussage, dass Sie GCC verwenden, nicht wirklich sagt, welche Implementierung der C-Bibliothek Sie verwenden. Wir können nur davon ausgehen, dass Sie die Standardeinstellung verwenden.
– Etienne
15. März 2014 um 16:38 Uhr
Es sieht nicht danach aus malloc macht überhaupt keine Kontrollen. Der Fehler, den Sie erhalten, kommt von der Hardware, die einen Schreibvorgang an eine ungültige Adresse erkennt, von der wahrscheinlich ausgegangen wird malloc selbst.
Wann malloc weist Speicher zu, nimmt einen Teil aus seinem internen Pool und gibt ihn an Sie zurück. Es müssen jedoch einige Informationen für die gespeichert werden free Funktion, um die Aufhebung der Zuordnung abzuschließen. Normalerweise ist das die tatsächliche Länge des Chunks. Um diese Informationen zu speichern, malloc nimmt ein paar Bytes vom Anfang des Chunks selbst, schreibt die Informationen dort und gibt Ihnen die Adresse hinter der Stelle zurück, an der es seine eigenen Informationen geschrieben hat.
Angenommen, Sie haben nach einem 10-Byte-Chunk gefragt. malloc würde einen verfügbaren 16-Byte-Chunk, sagen wir, bei Adressen abrufen 0x3200..0x320Fschreiben Sie die Länge (dh 16) in die Bytes 1 und 2 und kehren Sie zurück 0x3202 zurück zu dir. Jetzt kann Ihr Programm zehn Bytes verwenden 0x3202 zu 0x320B. Die anderen vier Bytes sind auch verfügbar – wenn Sie anrufen realloc und nach 14 Bytes fragen, würde es keine Neuzuweisung geben.
Der entscheidende Punkt kommt wann malloc schreibt die Länge in den Speicherabschnitt, den er Ihnen zurückgeben wird: Die Adresse, an die er schreibt, muss gültig sein. Es scheint, dass nach der 18. Iteration die Adresse des nächsten Blocks negativ ist (was zu einem sehr großen positiven Ergebnis führt), sodass die CPU den Schreibvorgang abfängt und den harten Fehler auslöst.
In Situationen, in denen der Heap und der Stack aufeinander zuwachsen, gibt es keine zuverlässige Möglichkeit, einen Speichermangel zu erkennen und gleichzeitig das letzte Byte des Speichers zu nutzen, was oft sehr wünschenswert ist. malloc kann nicht vorhersagen, wie viel Stack Sie nach der Zuweisung verwenden werden, also versucht es es nicht einmal. Deshalb liegt die Bytezählung in den meisten Fällen bei Ihnen.
Im Allgemeinen vermeiden Sie es auf eingebetteter Hardware, wenn der Speicherplatz auf einige Dutzend Kilobyte begrenzt ist malloc Anrufe an “beliebigen” Orten. Stattdessen weisen Sie Ihren gesamten Speicher im Voraus zu, indem Sie einige vorberechnete Grenzen verwenden, und verteilen Sie ihn auf Strukturen, die ihn benötigen, und rufen Sie ihn nie auf malloc wieder.
Die letzte erfolgreiche Zuordnung gibt 0x20004908 zurück – was meiner Meinung nach schon nicht möglich sein sollte. Der Grund, warum ich Strukturen verwende, ist, dass ich Strukturen von einer SD-Karte mit variabler Größe (100 Byte bis 2 kByte) gelesen habe.
– Börn
15. März 2014 um 12:14 Uhr
barak manos
Ihr Programm stürzt höchstwahrscheinlich wegen eines ab illegaler Speicherzugriffwas fast immer ein indirektes (späteres) Ergebnis von a ist legaler Speicherzugriffaber eine, die Sie nicht ausführen wollten.
Zum Beispiel (was auch meine Vermutung ist, was auf Ihrem System passiert):
Ihr Heap beginnt höchstwahrscheinlich direkt nach dem Stack. Angenommen, Sie haben einen Stapelüberlauf main. Dann eine der Operationen, die Sie in ausführen mainwas für Sie natürlich eine legale Operation ist, überschreibt den Anfang des Heaps mit einigen “Junk” -Daten.
Wenn Sie das nächste Mal versuchen, Speicher aus dem Heap zuzuweisen, ist der Zeiger auf den nächsten verfügbaren Speicherabschnitt daher nicht mehr gültig, was schließlich zu einer Speicherzugriffsverletzung führt.
Daher empfehle ich dringend, zunächst die Stack-Größe von 0x200 Bytes auf 0x400 Bytes zu erhöhen. Dies wird normalerweise in der Linker-Befehlsdatei oder über die IDE in den Linker-Einstellungen des Projekts definiert.
Wenn sich Ihr Projekt auf IAR befindet, können Sie es in ändern icf Datei:
define symbol __ICFEDIT_size_cstack__ = 0x400
Abgesehen davon schlage ich vor, dass Sie Code in Ihre hinzufügen HardFault_Handler, um den Call-Stack zu rekonstruieren und Werte vor dem Absturz zu registrieren. Auf diese Weise können Sie möglicherweise den Laufzeitfehler nachverfolgen und genau herausfinden, wo er aufgetreten ist.
Stellen Sie in der Datei „startup_stm32f03xx.s“ sicher, dass Sie den folgenden Code haben:
EXTERN HardFault_Handler_C ; this declaration is probably missing
__tx_vectors ; this declaration is probably there
DCD HardFault_Handler
Fügen Sie dann in derselben Datei den folgenden Interrupt-Handler hinzu (wo sich alle anderen Handler befinden):
Wenn Sie nicht verwenden können printf An dem Punkt in der Ausführung, an dem dieser spezielle Hard-Fault-Interrupt auftritt, speichern Sie stattdessen alle oben genannten Daten in einem globalen Puffer, damit Sie sie nach Erreichen von anzeigen können while (1).
Lesen Sie dann den Abschnitt „Cortex-M-Fehlerausnahmen und -register“ unter http://www.keil.com/appnotes/files/apnt209.pdf um das Problem zu verstehen, oder veröffentlichen Sie die Ausgabe hier, wenn Sie weitere Hilfe benötigen.
AKTUALISIEREN:
Stellen Sie außerdem sicher, dass die Basisadresse des Heapspeichers richtig definiert ist. Es ist möglicherweise in den Projekteinstellungen fest codiert (normalerweise direkt nach dem Datenabschnitt und dem Stapel). Sie kann aber auch zur Laufzeit, in der Initialisierungsphase Ihres Programms, ermittelt werden. Im Allgemeinen müssen Sie die Basisadressen des Datenabschnitts und des Stacks Ihres Programms (in der nach dem Erstellen des Projekts erstellten Map-Datei) überprüfen und sicherstellen, dass der Heap keinen von beiden überlappt.
Ich hatte einmal einen Fall, in dem die Basisadresse des Heaps auf eine konstante Adresse gesetzt war, was zunächst in Ordnung war. Aber dann habe ich die Größe des Datenbereichs schrittweise erhöht, indem ich dem Programm globale Variablen hinzugefügt habe. Der Stapel befand sich direkt nach dem Datenabschnitt und “bewegte sich vorwärts”, als der Datenabschnitt größer wurde, sodass es mit keinem von ihnen Probleme gab. Aber schließlich wurde der Haufen “oben auf” einem Teil des Stapels zugewiesen. Irgendwann begannen also Heap-Operationen, Variablen auf dem Stack zu überschreiben, und Stack-Operationen begannen, den Inhalt des Heaps zu überschreiben.
Der Ausdruck, nach dem Sie suchen, lautet “Stapel-Heap-Kollision”. Sehr seltene Bedingung auf einem modernen Full-Service-Betriebssystem, aber sie waren früher ein Problem auf vielen Plattformen und sind immer noch ein Problem in eingeschränkteren Umgebungen.
– dmckee — Ex-Moderator-Kätzchen
15. März 2014 um 17:44 Uhr
@dmckee: Danke für die Terminologie. Ich habe dieses Problem bei der Verwendung von ThreadX OS erlebt, das Ihnen die first unused memory Adresse in einer Callback-Funktion (dh während der Laufzeit) und ermöglicht Ihnen, den Heap an dieser Adresse zuzuweisen. Das Problem trat auf, weil ich stattdessen eine konstante Adresse verwendete, vorausgesetzt, dass sie “gut genug” war.
– barak manos
15. März 2014 um 17:50 Uhr
Das arm-none-eabi-*Werkzeugkette Vertrieb umfasst die neuelib C-Bibliothek. Wenn newlib für ein eingebettetes System konfiguriert wird, muss das Benutzerprogramm bieten ein _sbrk() Funktion damit es richtig funktioniert.
malloc() stützt sich ausschließlich auf _sbrk() um herauszufinden, wo der Heap-Speicher beginnt und wo er endet. Der allererste Anruf bei _sbrk() gibt den Anfang des Heaps und nachfolgende Aufrufe zurück sollte zurückkehren -1 wenn die erforderliche Speichermenge nicht verfügbar ist, dann malloc() würden wiederum zurückkehren NULL zur Bewerbung. Dein _sbrk() sieht kaputt aus, weil Sie anscheinend mehr Speicher zuweisen können, als verfügbar ist. Sie sollten in der Lage sein, es zu beheben, damit es zurückkehrt -1Vor Es wird erwartet, dass der Haufen mit dem Stapel kollidiert.
Jayesh Bhoi
Standard verwenden c malloc es ist sehr schwer zu unterscheiden und malloc Das scheint aus meiner Sicht fehlerhaft zu sein. So können Sie den Speicher verwalten, indem Sie einige benutzerdefinierte implementieren malloc Verwenden Sie Ihre RAM-Adresse.
Ich bin mir nicht sicher, ob Ihnen das helfen kann, aber ich habe einige Gewohnheiten gemacht malloc In meinem Controller-bezogenen Projekt ist es wie folgt
Dieses Makro definiert im Grunde die RAM-Adresse und hat manuell mehr Blocknummern für die Blockgröße ausgewählt, die häufig zugewiesen werden müssen. 36 Bytes erforderten mehr, also nehme ich mehr Nummern dafür.
Im Allgemeinen habe ich den Speicher zuerst vorberechnet und dann gegeben, wie ich ihn habe.
Hier finden Sie, wie ich malloc() “zwingen” könnte, NULL zurückzugeben, wenn der Heap zu klein für die Zuweisung ist, basierend auf Berendis vorheriger Antwort. Ich habe die maximale Menge an STACK geschätzt und daraus die Adresse berechnet, an der der Stack im schlimmsten Fall beginnen kann.
#define STACK_END_ADDRESS 0x20020000
#define STACK_MAX_SIZE 0x0400
#define STACK_START_ADDRESS (STACK_END_ADDRESS - STACK_MAX_SIZE)
void * _sbrk_r(
struct _reent *_s_r,
ptrdiff_t nbytes)
{
char *base; /* errno should be set to ENOMEM on error */
if (!heap_ptr) { /* Initialize if first time through. */
heap_ptr = end;
}
base = heap_ptr; /* Point to end of heap. */
#ifndef STACK_START_ADDRESS
heap_ptr += nbytes; /* Increase heap. */
return base; /* Return pointer to start of new heap area. */
#else
/* End of heap mustn't exceed beginning of stack! */
if (heap_ptr <= (char *) (STACK_START_ADDRESS - nbytes) ) {
heap_ptr += nbytes; /* Increase heap. */
return base; /* Return pointer to start of new heap area. */
} else {
return (void *) -1; /* Return -1 means that memory run out */
}
#endif // STACK_START_ADDRESS
}
13764600cookie-checkmalloc-Verhalten auf einem eingebetteten Systemyes
Ich habe keine Antworten, aber danke für eine interessante und gut artikulierte Frage. (+1)
– NPE
15. März 2014 um 10:52 Uhr
Ich habe auch keine Antworten, aber dies würde auf die der C-Bibliothek hindeuten
malloc()
Funktion hat einen Fehler.– Licht
15. März 2014 um 10:56 Uhr
Benutzt du uClibc?
– Michael Foukarakis
15. März 2014 um 11:03 Uhr
Gibt es eine Möglichkeit, die Größe des verbleibenden Speichers abzurufen?
– Déjà-vu
15. März 2014 um 11:04 Uhr
@BernhardSchlegel uClibc ist eine bestimmte Implementierung der Standard-C-Bibliothek, Sie könnten Ihre C-Bibliothek mit Debug-Symbolen kompilieren und dann einen Debugger verwenden, um einzugreifen
malloc
und sehen Sie genau, welche Zeile den Aufruf des Hardfault-Handlers verursacht. Sie können GCC mit verschiedenen Implementierungen der C-Bibliothek verwenden, sodass die Aussage, dass Sie GCC verwenden, nicht wirklich sagt, welche Implementierung der C-Bibliothek Sie verwenden. Wir können nur davon ausgehen, dass Sie die Standardeinstellung verwenden.– Etienne
15. März 2014 um 16:38 Uhr