Hallo zusammen,
am Cortex M3/M4 kann ich ja eine begrenzte Anzahl von
Hardware-Breakpoints setzen. Hochwertigere Programmer à la J-Link oder
Lauterbach können zusätzlich noch Breakpoints erzeugen, indem sie
bestimmte Stellen des Binaries garnieren, und die entsprechende Stelle
per Prozessort-Simulation füllen.
Wie sieht es eigentlich mit reinen Software Breakpoints aus? Dazu würde
kein "hochwertiger" Debugger benötigt, weil aus dem Binary nichts
gelöscht wird.
Was nicht funktioniert:
1
#include<signal.h>
2
3
uint32_tfoo(uint8_tv,into)
4
{
5
if(o>=0)
6
{
7
returnv<<o;
8
}
9
else
10
{
11
raise(SIGINT);
12
return-1;
13
}
14
}
Wäre ja auch zu einfach.
Aber gibt es einen ähnlichen Mechanismus, der funktioniert, und den ich
bis jetzt einfach übersehen habe? (Klar - ich könnte meine normalen
Breakpoints einfach auf die beiden Fault-Handler setzen - aber das ist
nicht gemeint.)
Gibt es eigentlich irgendetwas, was dagegen spricht, die Fault-Handler
fest mit Breakpoints zu versehen?
Solange der Debugger angesteckt ist, muß man nicht mehr stoppen, um zu
sehen, daß er angesprungen wurde (was vorteilhaft ist, weil es
mittlerweile sehr, sehr selten passiert), wenn nicht, passiert nichts.
Walter T. schrieb:> Gibt es eigentlich irgendetwas, was dagegen spricht, die Fault-Handler> fest mit Breakpoints zu versehen?
Nö, mache ich immer so.
Walter T. schrieb:> wenn nicht, passiert nichts.
Doch, ohne Debugger dran bewirkt BKPT dass der Prozessor stehen bleibt.
Man könnt sich auch eine schöne Ausgabe über den SWO oder Debuguart
bauen wenn die Kiste im Faulthandler hängt.
zB ein Registerdump und ein paar der Bits aus dem SCB Abfragen.
Dann weiste auch schon wer den explodierenden Speicherzugriff verursacht
hat (wenn die MPU aktiviert ist)
> Man könnt sich auch eine schöne Ausgabe über den SWO oder Debuguart> bauen wenn die Kiste im Faulthandler hängt.
Ja, kann man machen. Sowas mache ich z.B bei meinen alten H8.
Gelegentlich sehr praktisch.
[Achtung schlimmer Autor]
> Ja zeig mal du Schwätzer.> Da kommt doch nur heiße Luft.
Man zeigt nicht immer alles weil es schoener ist wenn die dummen so wie
du nicht immer klauen koennen.
Olaf
> damit es Standard konform ist und nicht> wegoptimiert wird.
Ohne Unterstriche funktioniert es in den -std=gnuXXX Einstellungen, mit
überall.
"Wegoptimiert" kann es nicht werden, aber ohne volatile kann es ggf.
verschoben werden.
Markus F. schrieb:> Sieht irgendwie nach Endlosrekursion aus ...
Oh! Es muss natürlich "bkpt" sein. ?♂️
Das "__asm__" funktioniert in Gegensatz zu "asm" auch wenn man mit
-std=cXY und nicht mit -std=gnuXY kompiliert.
... schrieb im Beitrag #5952904:
> Ja zeig mal du Schwätzer.>> Da kommt doch nur heiße Luft.
Was hast du denn für Probleme. Das ist sehr wohl machbar. Nur mal eben
schnell aus dem Ärmel schütteln eher nicht.
Walter T. schrieb:> Klauen? Die Ausgabe per SWO ist doch schon in SysCalls implementiert.
Wenn das RTOS, welches die SysCalls implementiert, nicht abgestürzt
ist... so ein FaultHandler sollte komplett unabhängig vom Zustand des
Programms sein. Damit fällt auch das normale printf flach.
Jörg W. schrieb:> Dr. Sommer schrieb:>> _asm_ volatile ("asm");>> damit es Standard konform ist und nicht wegoptimiert wird.> Ohne Unterstriche funktioniert [...]
Ich denke der weiße Elefant im Raum über den gestern den ganzen Tag lang
kein einziger auch nur ein Sterbenswörtchen verloren hat (was mich schon
ein bisschen verwunderte hier im Forum) ist dieses hier:
Error: bad instruction `asm'
Folgendes verwende ich. Ist natürlich nur relevant bei verbundenem
Debugger. Im Grunde: Wenn Debugger connected, setze die Traps.
Funktioniert für die Faults.
1
#include: Kette zu "Pack\\ARM\\CMSIS\\5.5.2\\CMSIS\\Core\\Include\\core_cm3.h"
2
3
#ifndef __FINAL_RELEASE
4
// Early Trap Faults when debugging (Hard Fault, ...)
> Wenn das RTOS, welches die SysCalls implementiert, nicht abgestürzt> ist... so ein FaultHandler sollte komplett unabhängig vom Zustand des> Programms sein. Damit fällt auch das normale printf flach.
Genau da ist das Problem. Man muss sich einen eigenen Stack aufsetzen,
an Daten sichern was man uebertragen will. Und darf danach natuerlich
keine Funktionen des restlichen Systems verwenden weil man ja nicht
weiss in welchem Zustand das System ist. Ausserdem sollte man darauf
achten moeglichst wenig Resourcen zu brauchen weil sowas ja mit beliebig
vielen zukuenftigen Anwendungen laufen soll.
Alles keine grosse Sache, man muss es nur machen. .-)
Olaf
Olaf schrieb:> Man muss sich einen eigenen Stack aufsetzen,
Auf dem Stack werden beim Exception-Eintritt allerdings einige Dinge
gesichert. Die möchte man wahrscheinlich auch ausgeben. Jetzt kann es
sein, dass der SP irgendwo in den RAM zeigt, da zwar die gesicherten
Register drüber geschrieben wurden, aber man nichts weiteres PUSHen
möchte, weil man dann in einen ungültigen Bereich kommen könnte. Daher
müsste man sich den SP merken (z.B. in R1), den SP auf einen extra dafür
reservierten Bereich setzen (globales Array), die Daten des alten SP
auslesen sofern dieser in einen gültigen Bereich zeigt, und ausgeben.
Etwas fummelig...
Interessant sollten die Inhalte der gesicherten Register sein, dazu LR,
und das letzte Stück des Stacks. Zusätzlich könnte man heuristisch
prüfen, ob die Register Pointer enthalten (d.h. sie in einen gültigen
Speicherbereich zeigen) und dann ggf. einen Teil des dort zu findenden
Speichers ausgeben. Der Linux Kernel macht das bei internen Fehlern so,
das kann enorm praktisch sein um Fehler zu finden ohne gleich den
Debugger anzuwerfen.
Olaf schrieb:> Und darf danach natuerlich> keine Funktionen des restlichen Systems verwenden weil man ja nicht> weiss in welchem Zustand das System ist
Nur wenn die irgendwelche globalen Zustände nutzen. Sauber gekapselter
Code sollte das sowieso möglichst nicht tun...
Olaf schrieb:> Alles keine grosse Sache, man muss es nur machen. .-)
Jo!
Dr. Sommer schrieb:> Damit fällt auch das normale printf flach.
Ich gebe zu, daß da eine gewisse Polemik drin war. Ich wollte auch nur
darauf hinaus: In der Implementierung von syscalls.c, die als Template
von vielen Umgebungen mitgebracht wird, kann man sich das Senden über
SWO gemütlich abschauen, ohne daß man sich großartig mit einer weiteren
Peripherie auseinandersetzen muß.
#include: Kette zu "Pack\\ARM\\CMSIS\\5.5.2\\CMSIS\\Core\\Include\\core_cm3.h"
Kette zu? Vorsicht beim Anwenden von ÜbersetzungTools auf Quelltexten!
\\ ist falsch, es muss / heissen (auch unter Windows).
Der Pfad von solchen Standardpaketen sollte im Makefile bzw. Projekt
konfiguriert sein, so dass man hier schreiben würde:
> Auf dem Stack werden beim Exception-Eintritt allerdings einige Dinge> gesichert.
Natuerlich. Deshalb braucht man auch einen eigenen Stack damit man sich
nichts zerstoert.
> Etwas fummelig...
Ja. Entweder ein paar Zeilen Assembler oder man muss sich sehr genau
anschauen was der Compiler da macht!
Wie gesagt, ich hab das fuer einen H8 gemacht. Natuerlich wird es da bei
jedem Controller ein paar spezielle Feinheiten geben.
> Nur wenn die irgendwelche globalen Zustände nutzen. Sauber gekapselter> Code sollte das sowieso möglichst nicht tun...
Aber du musst dich dann auch darauf verlassen das diese Funktion da ist.
Ich hab dafuer meine eigene kleine printf-Funktion. Und ich benutze auch
zur Ausgabe einen Port-Pin mit SoftwareUART weil ich ja nie weiss ob die
dedizierter Hartware in dem Projekt nicht schon anderswo verwendet wird.
Olaf
>> Kette zu? Vorsicht beim Anwenden von ÜbersetzungTools auf Quelltexten!>> \\ ist falsch, es muss / heissen (auch unter Windows).>> Der Pfad von solchen Standardpaketen sollte im Makefile bzw. Projekt> konfiguriert sein, so dass man hier schreiben würde:>>
1
>#include:<core_cm3.h>
2
>
Nix Übersetzungstool, hier ist Anwendergrips gefragt. Ich hab den Pfad
einfach aus dem Dateisystem kopiert :-)
Die "core_cm<x>.h" wird normalerweise vom Device Header eingebunden, wie
z.B. sam3x.h
Das direkte include geht natürlich auch, idR. ist im Modul mit main()
aber bereits der Device Header includiert.
Dr. Sommer schrieb:> Wenn das RTOS, welches die SysCalls implementiert, nicht abgestürzt> ist... so ein FaultHandler sollte komplett unabhängig vom Zustand des> Programms sein. Damit fällt auch das normale printf flach.
Daher hat meine Faulthandler Implementierung ein eigenes kleines und
sehr billiges printf.
Es heißt deprintf, weil dprintf gabs schon in der libc. (Debug printf).
Das is so billig, das kann keine Formatierungen, nut char, string, hex,
(u)int.
Da eben nichts aus dem restlichen programm genutzt werden darf.
Dr. Sommer schrieb:> Auf dem Stack werden beim Exception-Eintritt allerdings einige Dinge> gesichert. Die möchte man wahrscheinlich auch ausgeben. Jetzt kann es> sein, dass der SP irgendwo in den RAM zeigt, da zwar die gesicherten> Register drüber geschrieben wurden, aber man nichts weiteres PUSHen> möchte, weil man dann in einen ungültigen Bereich kommen könnte. Daher> müsste man sich den SP merken (z.B. in R1), den SP auf einen extra dafür> reservierten Bereich setzen (globales Array), die Daten des alten SP> auslesen sofern dieser in einen gültigen Bereich zeigt, und ausgeben.> Etwas fummelig...
Wenn der Faulthandler, sagen wir Usagefault, nicht auf den Stack pushen
kann, dann wird das zum hardfault und im SCB ist Stackerr gesetzt.
Dem Hardfault ist egal ob er pushen kann oder nicht.
Daher schrieb ich ja oben, dass man die SCB Bits lesbar ausgeben muss,
dann sieht man auch, dass man dem Regiosterdump nicht trauen kann.
Was ich in der Tat noch mal machen müsste ist den sp neu zu setzen bevor
ich nach C gehe. Bisher hab ich mir aber den Stack noch nie so übel
zerballert auf nem Cortex-M.
Den Stack könnt man sich auch einfach auf den Stack aus der IVT setzen,
das sollte weit genug am Anfang sein um sich nicht das heißbegehrte zu
überschreiben.
Dr. Sommer schrieb:> Zusätzlich könnte man heuristisch> prüfen, ob die Register Pointer enthalten (d.h. sie in einen gültigen> Speicherbereich zeigen) und dann ggf. einen Teil des dort zu findenden> Speichers ausgeben. Der Linux Kernel macht das bei internen Fehlern so,> das kann enorm praktisch sein um Fehler zu finden ohne gleich den> Debugger anzuwerfen.
Klingt nach einem interessanten Vorschlag, sollte ich bei Gelegenheit
mal einbauen.
Bisher versucht mein Faulthandler nur die letzten Zeichen aus der UART
FIFO zu kratzen, da könnt ja noch was interessantes drinnestehen.
@Bernd K:
Das sieht jetzt aber nicht nach SWO aus.
Das packt ne Zahl und ein Pointer in Register, dann wird der Debugger
angestoßen.
Da macht deine IDE dann irgendwas im Hintergrund.
Ich muss am WE mal mein Minimalbeispiel der Faulthandler fürs Discovery
zusammenfegen.
Kommt dann ins Code UNterforum und wir kölnne daraus den "perfekten"
Cortex-M Faulthandler bauen?
Mw E. schrieb:> Das sieht jetzt aber nicht nach SWO aus.
Oh ja, Du hast Recht. Ist schon ziemlich lange her, das was ich gepostet
habe ist die Semihosting-Schnittstelle. 1 ist das Filehandle für stdout,
der Debugger spuckt den String dann auf der Konsole aus und lässt sofort
wieder weiterlaufen. Sorry, verwechselt.
ITM Trace ist auch ne coole Sache, man muss nur im Debugger bei ITM
Stimulus Port (so heißts bei Keil uVision) Port 0 aktivieren, den trace
Takt dem CPU Takt anpassen und dann in etwa sowas einbinden (kommt z.B.
aus der retarget_io.c vom Keil "Compiler Software Pack"):
> ITM_SendChar
Die Funktion befindet sich bereits in der CMSIS-Core Library.
Nur die anderen beiden müsste man selbst hinzufügen. Ich kenne das aber
etwas anders in Kombination mit der newlib-nano:
1
// Redirect standard output to the trace SWO output
Da fehlt aber dann auchnoch der Init der Tracecell und Konsorten.
Im Anhang mal noch die Datei für SWO Init, SWO Ausgabe und printf
überschreiben für die newlib nano.
Es sind noch ein paar magic Numbers drinne, die kommen aber noch raus.
Die Doku zu dem Tracegedöhns ist etwas schwer zu lesen.
Mw E. schrieb:> Da fehlt aber dann auchnoch der Init der Tracecell und Konsorten.
Ging bei mir (STM32F1, STM32F3 und STM32L0) bisher immer ohne diesen
Aufwand. Die Schnittstelle konfiguriert und öffnet doch der Debugger -
dachte ich.
Der Debugger selber nicht, aber eventuell ein Debugscript was von der
IDE vorher geladen wird.
(man muss eben seine IDE kennen)
In diese Falle biste doch schonmal bei einer PLL Einstellung getappt ;)
Also wenn du bei einer IDE ansagst, dass du per SWO was empfangen
willst, dann wird da vllt so ein Script ausgeführt.
Wenn dus nur per STlink draufkopierst und dann im ST utility den SWO
Viewer aktivierst, dann brauchst du den Initcode.
Mw E. schrieb:> Wenn dus nur per STlink draufkopierst und dann im ST utility den SWO> Viewer aktivierst, dann brauchst du den Initcode.
Nee, das habe ich ausprobiert. Mit den "Serial Wire Viewer" im ST-Link
Utility. Vielleicht haben die das innerhalb der letzten Jahre verändert.
Ich befasse mich ja noch nicht lange damit.
Walter T. schrieb:> Gibt es eigentlich irgendetwas, was dagegen spricht, die Fault-Handler> fest mit Breakpoints zu versehen?
Beim Durchgucken des ARMv7-M Architecture Reference Manual, bin ich
Gestern über das vector catch feature gestolpert. Ich bin mir noch nicht
100% sicher, ob es so funktioniert, wie ich es verstehe, aber wenn dem
so wäre, dann könnte man durch setzen von flags im Register DEMCR eine
"halting debug trap" für bestimmte Ereignisse setzen.
Das hätte dann den Vorteil, dass keine break points dafür verwendet
werden müssten.
Torsten R. schrieb:> Walter T. schrieb:>> Gibt es eigentlich irgendetwas, was dagegen spricht, die Fault-Handler>> fest mit Breakpoints zu versehen?>> Beim Durchgucken des ARMv7-M Architecture Reference Manual, bin ich> Gestern über das vector catch feature gestolpert. Ich bin mir noch nicht> 100% sicher, ob es so funktioniert, wie ich es verstehe, aber wenn dem> so wäre, dann könnte man durch setzen von flags im Register DEMCR eine> "halting debug trap" für bestimmte Ereignisse setzen.>> Das hätte dann den Vorteil, dass keine break points dafür verwendet> werden müssten.
Hättest du mal meinen Beitrag gelesen, der macht nämlich genau das
(fragt vorher noch, ob ein Debugger connected ist) :-)
"
Folgendes verwende ich. Ist natürlich nur relevant bei verbundenem
Debugger. Im Grunde: Wenn Debugger connected, setze die Traps.
Funktioniert für die Faults.
1
#include: (idr. durch device header include chain) "Pack\\ARM\\CMSIS\\5.5.2\\CMSIS\\Core\\Include\\core_cm3.h"
2
3
#ifndef __FINAL_RELEASE
4
// Early Trap Faults when debugging (Hard Fault, ...)
Random .. schrieb:> Folgendes verwende ich. Ist natürlich nur relevant bei verbundenem> Debugger. Im Grunde: Wenn Debugger connected, setze die Traps.> Funktioniert für die Faults.
Die Abfrage müsstest Du Dir sparen können: "If DHCSR.C_DEBUGEN is set to
0, the processor ignores the value of this bit."
Ich würde das Register evtl. auch einfach in der Konfiguration des
Debuggers setzen.