Forum: Mikrocontroller und Digitale Elektronik STM32 Grund für Hardfault herausfinden


von Guast (Gast)


Lesenswert?

Ich habe eine Funktion, nach dem diese x Mal problemlos aufgerufen wird, 
springt das Programm nach einem x+n Aufruf zum HardFault Handler. Und 
zwar beim Verlassen der der Funktion. Gibt es eine Möglichkeit raus zu 
finden, woran das liegt? Kann man irgendwie die Rücksprungabdresse vom 
Stack überwachen, um zu sehen, wohin nach dem Funktionsaufruf gesprungen 
wird?

von holger (Gast)


Lesenswert?

>Gibt es eine Möglichkeit raus zu
>finden, woran das liegt?

Funktion zeigen.

von Marcus H. (mharnisch) Benutzerseite


Lesenswert?

Das Hard Fault Status Register lesen (HFSR), dann CFSR, etc. Ist alles 
im ARM ARM beschrieben.

--
Marcus

von Guast (Gast)


Lesenswert?

Hier ist die Funktion:
1
char 
2
*strrpl(const char *s, const char *old, const char *new) {
3
  char *ret;
4
  int i, count = 0;
5
  int newlen = strlen(new);
6
  int oldlen = strlen(old);
7
8
  for (i = 0; s[i] != '\0'; i++) {
9
    if (strstr(&s[i], old) == &s[i]) {
10
      count++;
11
      i += oldlen - 1;
12
    }
13
  }
14
15
  ret = malloc(1+ (i + count * (newlen - oldlen)) * sizeof(char));
16
  if (ret == NULL)
17
    return 0;
18
19
  i = 0;
20
  while (*s) {
21
    if (strstr(s, old) == s) {
22
      strcpy(&ret[i], new);
23
      i += newlen;
24
      s += oldlen;
25
    } else {
26
      ret[i++] = *s++;
27
    }
28
  }
29
  ret[i] = '\0';
30
31
  return ret;
32
}

Aufgerufen wird diese folgendermaßen:
1
static void
2
send_response (struct ip_addr *to, int to_port) {
3
4
  struct pbuf *pkt_buf;
5
  err_t err;
6
  struct udp_pcb *upnp_pcb;
7
  char *notify_data;
8
9
  notify_data = malloc(sizeof(SSDP_M_SEARCH_RESPONSE) + 1);
10
  if (!notify_data)
11
  return ERR_MEM;
12
  
13
  sprintf(notify_data, "%s", SSDP_M_SEARCH_RESPONSE);
14
  notify_data = strrpl(notify_data, "UUID", device_id);
15
16
  pkt_buf = pbuf_alloc(PBUF_TRANSPORT, strlen(notify_data) + 1, PBUF_POOL);
17
  if (!pkt_buf)
18
  return ERR_MEM;
19
20
  sprintf(pkt_buf->payload, "%s", "\0");
21
  memcpy(pkt_buf->payload, notify_data, strlen(notify_data));
22
  pkt_buf->len = pkt_buf->tot_len = strlen(pkt_buf->payload);
23
  upnp_pcb = udp_new();  // create new udp pcb
24
  if (!upnp_pcb) return 0;
25
  // Bind SSDP Port
26
  err = udp_bind(upnp_pcb, IP_ADDR_ANY, SSDP_MULTICAST_PORT);
27
  if(err == ERR_OK) {
28
    // Sending packet by UDP protocol
29
    err = udp_sendto(upnp_pcb, pkt_buf, to, to_port);
30
  }
31
  else {
32
    printf("\r\nsend failed");
33
  }
34
  udp_remove(upnp_pcb);
35
  
36
37
  //  free buffer after sent
38
  pbuf_free(pkt_buf);
39
  free(notify_data);
40
}


send_response funktionier allein für sich wunderbar, ohne die Zeile:
notify_data = strrpl(notify_data, "UUID", device_id);


Sobald die Zeile drin ist und die Fkt x Mal aufgerufen wird, kommt der 
Hardfault Handler. Habe jetzt auf die Schnelle die Ausgabe der Register 
nach dem Handler  Einsprung gemacht:

CFSR = 0x400
HFSR = 0x40000000

Bit 10 IMPRECISERR: Imprecise data bus error
When the processor sets this bit to 1, it does not write a fault address 
to the BFAR.
This is an asynchronous fault. Therefore, if it is detected when the 
priority of the current
process is higher than the bus fault priority, the bus fault becomes 
pending and becomes
active only when the processor returns from all higher priority 
processes. If a precise fault
occurs before the processor enters the handler for the imprecise bus 
fault, the handler detects
both IMPRECISERR set to 1 and one of the precise fault status bits set 
to 1.
0: No imprecise data bus error
1: A data bus error has occurred, but the return address in the stack 
frame is not related to
the instruction that caused the error.


Aha interessant. Was kann ich damit anfangen?

von Jim M. (turboj)


Lesenswert?

1
  notify_data = strrpl(notify_data, "UUID", device_id);
2
3
  pkt_buf = pbuf_alloc(PBUF_TRANSPORT, strlen(notify_data) + 1, PBUF_POOL);

Wenn strrpl (wegen OOM) NULL zurück gibt, machst Du einen strlen(NULL). 
Das könnte Deinen Bus Error verursachen.

Außerdem sehe ich das Du sowas machst:
1
notify_data = malloc(...);
2
[...]
3
notify_data = strrpl(...);

Da fehlt ein free() für das malloc(), denn strrpl() allokiert ja selbst 
Speicher. Du überschreibst einfach den Pointer ohne den Block 
freizugeben.

von Guast (Gast)


Lesenswert?

>Da fehlt ein free() für das malloc(), denn strrpl() allokiert ja selbst
>Speicher. Du überschreibst einfach den Pointer ohne den Block
>freizugeben.

Was soll da free() bewirken? Dann würden ja die Daten verloren gehen, 
bevor strrpl ausgeführt wird.

von Stefan W. (dl6dx)


Lesenswert?

Der Aufruf von free() für den zuerst allokierten Block muss natürlich 
nach dem Aufruf von strrepl() erfolgen. Im Moment wird der Zeiger auf 
den zuerst allokierten Block schlicht "weggeworfen" und der Block nie 
freigegeben (memory leak).
1
char *notify_data;
2
char *to_free;
3
notify_data = malloc(...);
4
[...]
5
to_free = notify_data; /* Pointer für free() aufheben */
6
notify_data = strrpl(...);
7
free(to_free); /* Jetzt ist der erste Block auf wieder im Pool */

Grüße

Stefan

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Guast schrieb:
> Gibt es eine Möglichkeit raus zu
> finden, woran das liegt?

Du musst weitere faults zulassen, dann kannst du in den Statusregistern
besser die Ursache finden.  Das hier steht in einem alten Beispielcode
drin, auf dem ich mal für einen SAM3S debuggt habe:
1
    /*
2
     * Enable additional exceptions, so they don't escalate to
3
     * HardFault immediately.
4
     */
5
    SCB->SHCSR |= SCB_SHCSR_USGFAULTENA_Msk |
6
        SCB_SHCSR_BUSFAULTENA_Msk |
7
        SCB_SHCSR_MEMFAULTENA_Msk;

Diese faults sind initial nach einem Reset alle verboten, was dazu
führt, dass sie letztlich in einen hardfault münden.

von Guast (Gast)


Lesenswert?

Hmm, bewirkt irgendwie nichts. Kann man sich irgendwie anzeigen lassen, 
wieviel Platz noch im Heap frei ist? So könnte ich einfach 
kontrollieren, ob es sich tatsächlich um memory leaks handelt.

von Guast (Gast)


Lesenswert?

Ich habe das Gefühl, dass der Fehler ganz wo anders ist. Wenn ich den 
Block auslasse:
1
 upnp_pcb = udp_new();  // create new udp pcb
2
  if (!upnp_pcb) return 0;
3
  // Bind SSDP Port
4
  err = udp_bind(upnp_pcb, IP_ADDR_ANY, SSDP_MULTICAST_PORT);
5
  if(err == ERR_OK) {
6
    // Sending packet by UDP protocol
7
    err = udp_sendto(upnp_pcb, pkt_buf, to, to_port);
8
  }
9
  else {
10
    printf("\r\nsend failed");
11
  }
12
  udp_remove(upnp_pcb);

Läßt sich die Funktion ohne Probleme aufrufen. Wenn ich den Block 
reinnehme aber die Funktionen mit strrpl auslasse, läßt sich die 
Funktion ebenfalls problemlos aufrufen. Sobald ich aber strrpl und die 
Sendefuntionen benutze, kracht es.

von Roland H. (batchman)


Lesenswert?

malloc/free auf einem µC ist eher ungewöhnlich. Unabhängig davon ist die 
"wer macht wann den free()?" fehleranfällig. Erschwerend kommt hinzu, 
dass in diesem Fall das Alloziieren und die Freigabe nicht in einem 
Block/Funktion, sondern über zwei Funktionen verteilt geschieht.

Mir erscheint das unnötig kompliziert:
Versuche komplett ohne strrpl() und ohne malloc/free auszukommen, auch 
die vielen String-Funktionsaufrufe sind vermutlich nicht nötig. Das 
memcpy() sieht auch verdächtig aus.

Nach dem Motto:
1
char notify_data[MAX];
2
snprintf(notify_data, max, SSDP_M_SEARCH_RESPONSE, device_id);

wobei SSDP_M_SEARCH_RESPONSE die nötigen Platzhalter %s etc. enthält. 
Ich würde die Konstante direkt einfügen, damit die Anzahl der 
Platzhalter und der Parameter "zusammen" sind.

Wenn es unbedingt mit malloc() sein soll, dann würde ich versuchen, die 
Zuweisung so umzuschreiben, dass der Compiler eine erneute Zuweisung des 
Zeigers verhindert (habe das im Zusammenhang mit malloc() noch nie 
probiert).
1
char * const notify_data = malloc(...);

Wenn es unbedingt mit strrpl() erfolgen soll, dann würde ich den Puffer 
für die Rückgabe als Parameter mitgeben, so wie bei snprintf(). Das 
verhindert die verteilte Zuständigkeit für malloc() und free().

Wenn strrpl() noch einen längeren Namen hätte, könnten auch andere den 
Zweck der Funktion schneller erkennen ;-) Das sechs-Zeichen-Limit stammt 
aus der C-Steinzeit.

Irgendwie werden hier Puffer mehrfach angelegt. Warum wird nicht nur der 
pkt_buf alloziiert, dann dann die Antwort direkt hineingeschrieben? D. 
h. es braucht kein notify_data. Das spart auch das memcpy(). Aber auch 
hier sehe ich keinen Grund, den Speicher für pkt_buf dynamisch 
anzufordern.

Hier wird ordentlich geprüft:
1
if (!notify_data)
2
  return ERR_MEM;
3
4
...
5
6
if (!pkt_buf)
7
  return ERR_MEM;

aber hier fehlt diese Prüfung:
1
notify_data = strrpl(notify_data, "UUID", device_id);

Das ist nicht konsequent, weil im zweiten Fall nicht auf "out of memory" 
geprüft wird. Dann gäbe es auch keinen "hard fault". Da der aufrufende 
Programmteil sicherlich ordentlich programmiert ist, würde im Falle 
eines "out of memory"-Fehlers dies protokolliert ;-)

Das
1
sprintf(pkt_buf->payload, "%s", "\0");

ist auch ein großes Kaliber für
1
pkt_buf->payload[0] = '\0';

Wenn schon
1
memcpy(pkt_buf->payload, notify_data, strlen(notify_data));
2
pkt_buf->len = pkt_buf->tot_len = strlen(pkt_buf->payload);

dann doch gleich
1
int const len = strlen(notify_data));
2
memcpy(pkt_buf->payload, notify_data, len);
3
pkt_buf->len = pkt_buf->tot_len = len;

Mich persönlich verwirrt dies ohnehin - memcpy() weist eher auf ein 
"byte array" hin, ansonsten würde ich hier strcpy() einsetzen. Ist es 
ein "byte array", dann finde ich die Anwendung von strlen() auf das 
"byte array" etwas unglücklich.

Wobei das ein guter Grund wäre, eine C++-Klasse einzusetzen, die das 
intern regelt. Zusätzlich würde C++ vermutlich auch die Verwendung von 
"casts" erzwingen, welche die Lesbarkeit enorm verbessern würden.

Das hier
1
if (!upnp_pcb) return 0;

ist ganz großer Murks. Denn dies verhindert die Freigabe mittels free() 
am Ende.

> Kann man sich irgendwie anzeigen lassen,
> wieviel Platz noch im Heap frei ist? So könnte ich einfach
> kontrollieren, ob es sich tatsächlich um memory leaks handelt.

Lass es einfach mit dem Heap.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Roland H. schrieb:
> malloc/free auf einem µC ist eher ungewöhnlich.

Auf einem ARM keineswegs.  Insbesondere übliche Implementierungen
von stdio brauchen es, dafür bekommt man halt eine gut erprobte
Implementierung.

> Lass es einfach mit dem Heap.

Meine Empfehlung: debugge es.  Früher oder später musst du das sowieso
lernen, wie man das macht.

von Guast (Gast)


Lesenswert?

@ Roland H.

>if (!upnp_pcb) return 0;

>ist ganz großer Murks. Denn dies verhindert die Freigabe mittels free()
>am Ende
Hier hast du natürlich recht, upnp_pcb ist aber jedes Mal initialisiert 
und tut nichts zur Sache, wenn alles top läuft, kann man aufräumen :)

Mit einem dem Char array habe ich es auch bereits probiert. Sogar die 
strrpl Funktion anders gelöst, leider mit dem gleichen Resultat. Nach 
12x Senden kommt ein Absturz. Ich bin ratlos :/

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Guast schrieb:
> Ich bin ratlos :/

Nimm doch endlich den Debugger und sieh' dir an, wo der Fault
auftaucht.  Du hast anschließend Register, in denen du die fault
address lesen kannst, du hast den PC, was brauchst du noch?

Siehe Beitrag "Re: STM32 Grund für Hardfault herausfinden",
nicht vergessen, die memory protection faults freizuschalten.

von Michael A. (mukululul)


Lesenswert?

Is zwar ne weile her aber vielleicht ist es ein Stackproblem. Stack zu 
klein oder überlappt mit dem Heap. Stack wächst normalerweise runter und 
Heap hoch. Wenn die zusammenwachsen weil der code zum beispiel leakt 
könnte sowas passieren.

Gruss
Michael

von ersan (Gast)


Angehängte Dateien:

Lesenswert?

Hi,

hab das gleiche Problem. Wenn meine Funktion nur kurz was berechnet, ist 
es ok, wenn es aber dann mal länger was macht, gibts den Hardfault 
(siehe Screenshot).

Kann es sein, das ich zuwenig RAM habe?

STM32F103RET7 ist mein µC

MfG
Ersan

von ersan (Gast)


Angehängte Dateien:

Lesenswert?

sorry, falscher screenshot. hier der richtige

von Michael A. (mukululul)


Lesenswert?

Hallo ersan,
kannst du noch ein paar details geben?
Allokiert dein Code dynamisch Speicher? (benutzt also malloc und free)?
Sind deine Stacktiefen immer gleich oder hast du rekursive Funktionen?
Bist du dir sicher das im gut Fall alle funktionen bis in die tiefste 
ebene aufgerufen wurden?
Manchmal ist es so das der Stack im gerade aus Pfad reicht, aber wenn 
eine Fehlerbehandlung angesprungen wird nicht mehr weil hier mehr 
variablen auf den Stack gelegt werden. Hier kann ich dir emfehlen alle 
variablen gleich am Funktionsanfang zu definieren und nicht wie auf dem 
PC üblich immer erst in dem Block wo sie gebraucht werden.
Auch ob der code in Debug oder Release gebaut wird spielt eine Rolle in 
der benötigten Stacktiefe.

Benutzt du ein OS?

Gruss
Michael

von Ersan G. (ersan)


Lesenswert?

Hallo Michael,

ich benutze kein OS. Könnte ich dir den Code mal zu per PN zuschicken 
(gerne jeden, der interesse an der Lösung meines Problems hat)?

Im prinzip komme ich bis in die letzte tiefe der Funktion. Mir scheint 
es halt Gefühlsmäßig bei einer Dauer von 4-5Sek. einen Überlauf zu 
geben.

Leider hab ich nicht das Wissen um zu schauen ob ich irgendwo eine 
Variable oder eine Datenstruktur habe, welche sich aufbläht. -> kann ich 
das im Compiler/Debugger nachverfolgen?

Ich ging immer davon aus, das die Variablen und Konstanten im 
Ram/Speicher vom Compiler auf die größtmögliche Speichernutzung 
vor-reserviert werden.

MfG
Ersan

von Markus M. (Firma: EleLa - www.elela.de) (mmvisual)


Lesenswert?

Um solche Hard-Faults zu lokalisieren habe ich mir ein Array mit 30 
Positionen erstellt, bei jedem Aufruf einer Routine wird der PC in eine 
neue Array-Position gespeichert.
Dazu kann ich noch andere Zahlen noch mit speichern.
Kommt jetzt ein HardFault so schaue ich mir die Zahlen in diesem Array 
an. Die PC Adressen stimmen dann immer ungefähr mit der Adresse der 
Routine überein und ich kann so sehen in welcher Routine das auftrat.

Bei zu viel Stack-Verbrauch hast Du vermutlich eine Routine, die sich 
selbst rekursiv nonstop aufruft.

Ansonsten einzelne Programmteile deaktivieren bis es geht.

von tos und Scans verwe (Gast)


Lesenswert?

Ersan G. schrieb:
> Hallo Michael,

> Leider hab ich nicht das Wissen um zu schauen ob ich irgendwo eine
> Variable oder eine Datenstruktur habe, welche sich aufbläht. -> kann ich
> das im Compiler/Debugger nachverfolgen?
>

Selbstverständlich. Schau' einfach, wo der/die Stackpointer (PSP 
und/oder MSP) im Crashfall steht/stehen.

Die Fault-Adressregister sollten darüber Auskunft geben,
an welcher Codestelle der Crash passiert.

> Ich ging immer davon aus, das die Variablen und Konstanten im
> Ram/Speicher vom Compiler auf die größtmögliche Speichernutzung
> vor-reserviert werden.

Nicht bei Embedded-Systemen.
Woher sollten der Linker und Compiler auch wissen,
was zur Laufzeit passiert.

Die "größtmögliche Speichernutzung" kann auch unbegrenzt sein.

>
> MfG
> Ersan

von Michael A. (mukululul)


Lesenswert?

Hallo Ersan,
schau mal noch in deine .lst Datei die der Linker anlegt. Da sind alle 
Funktionen mit offset abgelegt.
Im LR steht die rücksprung Adresse, diese zeigt zumindest in die Nähe 
des Fehlers. Je nach art des Fehlers genau dort hin oder ein bischen 
vorher.

Der Inhalt der Datei sieht ungefähr so aus:
    uxHighWaterMark = uxTaskGetStackHighWaterMark( NULL );
 80002cc:  f04f 0000   mov.w  r0, #0
 80002d0:  f002 fa02   bl  80026d8 <uxTaskGetStackHighWaterMark>
 80002d4:  4603        mov  r3, r0
 80002d6:  60fb        str  r3, [r7, #12]
    MSG1("Main Task tick...Stack HWM 0x%x",uxHighWaterMark);
 80002d8:  f003 fdbc   bl  8003e54 <trace_reserve>
 80002dc:  f003 fdde   bl  8003e9c <timestamp>
 80002e0:  f64a 607c   movw  r0, #44668  ; 0xae7c
 80002e4:  f6c0 0000   movt  r0, #2048  ; 0x800
 80002e8:  68f9        ldr  r1, [r7, #12]
 80002ea:  f003 fac7   bl  800387c <termf>
 80002ee:  f003 fdc3   bl  8003e78 <trace_release>
    remainingHeap=xPortGetFreeHeapSize();
 80002f2:  f002 fb3d   bl  8002970 <xPortGetFreeHeapSize>

Der Teil der mit 8 anfängt ist die Flashadresse. Also einfach die 
Adresse suchen die am nähesten an der LR adresse ist. Die Funktion nach 
oben ist die Stelle in der aufrufenden Funktion die noch geklappt hat.

Gruss
Michael

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.