Hallo,
ich arbeite an einem Projekt für einen ARM9 mit gcc. Es treten
merkwürdige Phänomene auf:
1. Globale Variablen lassen sich nicht mit 0 initialisieren, sie
enthalten stattdessen einen undefinierten Wert. Andere Werte gehen aber.
2. Eine For-Schleife zählt manchmal bis ins Unendliche. Wenn ich eine
Code-Zeile davor setze, egal was, geht es teilweise. Hier ein Beispiel:
1
unsignedshortip_chksum(intheader_length)
2
{
3
unsignedshortu16;
4
unsignedlongsum=0;
5
inti;
6
7
for(i=0;i<10;i++){
8
sum+=endians(ip.shorts[i]);
9
}
10
11
while(sum>>16)
12
sum=(sum&0xFFFF)+(sum>>16);
13
14
return(unsignedshort)~sum;
15
}
Habe diese Funktion schon mehrfach umgeschrieben und immer wieder bleibt
das Programm darin hängen. In der Ursprünglichen Variante dieser
Funktion funktionierte sie nur, wenn ich das IP-Checksum Feld des
IP-Header-Structs nicht vor der Funktion auf null setzte. Somit wurde
zwar eine falsche Checksumme berechnet, aber immerhin lief das Programm
weiter.
Das umfangreiche Programm funktioniert sonst gut, nur manchmal stolpere
ich über die genannten Probleme. Ich hoffe jemand hat einen Tipp für
mich.
Viele Grüße
Daniel
Probleme der genannten Art sind des öfteren Folgen von Out-Of-Bounds
Array Zugriffen, also Fehlern in deinem Code
zb
1
inti;
2
intj[5];
3
intk;
4
5
intmain()
6
{
7
i=4;
8
k=5;
9
j[5]=8;
10
}
Hier wird auf j out-of-bounds zugegriffen. j ist ein Array mit 5
Elementen, durchnummeriert von 0 bis 4. Ein Element 5 existiert schlicht
und ergreifend nicht. Wird es im obigen Code trotzdem beschrieben,
ändert entweder i oder k (je nachdem wie sie der Compiler im Speicher
angeordnet hat) scheinbar unmotiviert seinen Wert.
Nun ist obiger Fall trivial zu detektieren. Interessanter wird es, wenn
derartige Fehler bei funktionslokal angelegten Arrays passieren. Dann
kann manchmal alles mögliche passieren, weil man sich unter Umständen
den Return-Stack komplett zerschiesst.
Eine andere Variante (oder eigentlich dasselbe Problem nur anders
verpackt) ist ein Zugriff über Pointer die 'in den Wald zeigen'.
Kurz und gut: Solche Phenomäne wie du sie bechreibst sind meistens ein
Hinweis darauf, dass man Speicher beschreibt, an denen man nichts zu
suchen hat.
Viel Spass beim Suchen. Solche Probleme sind schwer zu finden.
Erster Schritt: reproduzierbar machen. Die Aussage 'manchmal passiert
dieses' muss ersetzt werden durch 'wenn ich zuerst A mache, dann B, dann
C, dann passiert dieses und jenes'
Hi,
ich beobachte diese Phänomene seit ich vor Monaten mit dem Projekt
begonnen habe. Leider konnte ich trotz vielen rumprobierens bisher
nichts eingrenzen. Das IP-Feld besteht aus 20 bytes, also 10
16-bit-worten. Die angegebene For-Schleife sollte also nicht über das
Array hinaus laufen.
Ich habe schon etwas programmiererfahrung in C, kenne die Probleme die
du beschrieben hast und bin daher darauf sensibilisiert. Im gesamten
Programm wird ständig mit Pointern hantiert und empfangene
Netzwerkheader werden auseinandergenommen um an gewünschte Werte zu
kommen. Alles funktioniert sonst.
Aber warum werden dann globale Variablen nicht richtig mit 0
initialisiert? Folgender Code würde ein Error liefern:
1
inti=0;
2
inttest(){
3
if(i!=0)
4
returnerror;
5
}
Eine 1 statt einer 0 geht wiederrum. Dabei wäre ja nicht mal das '=0'
nötig bei globalen Variablen, wie ich im Forum gelesen habe.
Vielleicht gibt es noch andere Ursachen für so ein Problem. Wenn nicht
muss ich eben weiter und weiter suchen...
Mögliche Gründe gibt es im Dutzend. Stacküberlauf ist einer davon.
Wenn du mal einen Code beisammen hast, bei dem du genau identifizieren
kannst welche Variable offensichtlich irgendwann versehentlich
überschrieben wird, dann hilft dir ein Data Watchpoint im Debugger.
Daniel G. schrieb:
> 1. Globale Variablen lassen sich nicht mit 0 initialisieren, sie> enthalten stattdessen einen undefinierten Wert. Andere Werte gehen aber.
Klingt nach einem Problem mit dem Startup-Code deiner Bibliothek
(oder wo auch immer er her kommt -- ich kenne mich mit ARMs nicht
so aus).
Eine nicht initialisierte globale (oder statische) Variable wird
implizit mit 0 initialisiert. Dies erfolgt, indem der Compiler
sie in eine section namens .bss steckt. Alle diese Variablen werden
vom Linker dann zusammengefasst, und es ist die Verantwortung des
Startup-Codes, den gesamten BSS-Bereich beim Start auszunullen.
Eine initialisierte globale Variable landet in der section .data.
Der Startup-Code muss beim Start hier die Initialwerte aus externem
Speicher (Festplatte, ROM, ...) laden.
Eine globale Variable, die überflüssigerweise mit 0 initialisiert
wird, wird vom Compiler (als kleine Optimierung) ebenfalls nach .bss
gesteckt.
Der Stack Pointer liegt etwa 62MB hinter dem letzten Image-Byte. Der
läuft nicht so schnell über. Zudem benutzt die Funktion den Stack gar
nicht, wie ich eben aus dem Assembler Code entnommen habe.
Was den Startup Code angeht: ich werde jetzt erstmal meine Toolchain neu
installieren und dabei alle Optionen überprüfen. vielleicht ist etwas
nicht richtig eingestellt oder installiert.
Daniel G. schrieb:
> Der Stack Pointer liegt etwa 62MB hinter dem letzten Image-Byte. Der> läuft nicht so schnell über. Zudem benutzt die Funktion den Stack gar> nicht, wie ich eben aus dem Assembler Code entnommen habe.
Du solltest auch berücksichtigen, dass du nur die Symptome siehst und
nicht die Ursache. Die Ursache muss nicht in der Funktion zu finden
sein, die Probleme macht. Eigentlich ist die Ursache für solche Probleme
meistens ganz weit von der Stelle entfernt an der man auf das Problem
aufmerksam wird. Auch deshalb sind solche Probleme oft so schwer zu
identifizieren.
Es gibt auf ARMs mehrere Stacks. Einen für das Hauptprogramm, einen für
normale Interrupts, usw. Je nachdem wie die konfiguriert werden, können
die also auch bei massig Speicher schnell ineinander laufen.
Wenn beispielsweise der IRQ-Stack direkt hinter dem normalen Stack liegt
und zu klein konfiguriert ist, dann plättet dir jeder Interrupt die
lokalen Daten der obersten Level des Hauptprogramms weg. Allerdings auch
die Return-Adressen, weshalb man das meistens daran merkt, dass er bei
Return über die Wupper geht.
Für sowas hilft eine Analyse des Mapfiles in Verbindung mit dem
Verständnis des Startup-Codes.
Eins kann man aber wohl in Stein meißeln: Es ist (gerade beim gcc) mit
ganz ganz großer Wahrscheinlichkeit kein Compiler-Fehler oder etwas
ähnliches. Umformulieren von Code wird das Problem daher nur
verschleiern.
Ich würde folgendes versuchen:
1. Am Anfang von main() müssen alle globalen Daten korrekt initialisiert
sein. Die, die nicht explizit initialisiert wurden, müssen auf 0 stehen.
Das zuerst im Debugger prüfen. Wenn nicht, dann ist der Startupcode
faul. Woher stammt der überhaupt?
2. Wenn OK, dann Schritt für Schritt weitergehen und darauf achten,
durch welche Aktion sich die globalen Daten unerwartet ändern. Diesen
Punkt möglichst eng eingrenzen.
3. In was für einem Speicher liegen die fraglichen Daten? Internes RAM
oder externer Speicher, für den ein Businterface konfiguriert werden
muss? Wenns extern ist, ist das Interface evtl. falsch aufgesetzt und
liefert Unsinn.
tuppes schrieb:
> Eins kann man aber wohl in Stein meißeln: Es ist (gerade beim gcc) mit> ganz ganz großer Wahrscheinlichkeit kein Compiler-Fehler oder etwas> ähnliches.
Mal abgesehen davon, dass man mit der Fehlerbeschreibung kaum erraten
kann, wo der Fehler liegen könnte (bedeutet "immer wieder" das gleiche
wie "ausnahmslos, jedes Mal"?), entbehrt die Betonung auf "gerade beim
gcc" nicht einer gewissen Ironie :-)
Wenn man den Fehler mit der vom OP genannten Funktion zuverlässig
reproduzieren kann, dann würde sich ein Disassembly durchaus lohnen.
Gruß
Marcus
http://www.doulos.com/arm/
Vielen Dank für die vielen Antworten!
Die Antwort von A.K. kam gerade noch rechtzeitig um mich vor einer
Neuinstallation der Toolchain zu bewahren. :-)
Folgende Dinge sind auf jeden Fall schief gelaufen:
1. Ich dachte den Code für die Variableninitialisierung gegeriert der
Compiler von sich aus. Somit hatte ich in meinem (selbstgeschriebenen)
Startupcode keinen derartigen Code.
2. Der Hinweis, dass ARMs mehrere Stacks haben, war ein Treffer! Ich
habe gar nicht daran gedacht auch die Stackpointer der anderen
Prozessormodi zu initialisieren.
Wenn ich jetzt so drüber nachdenke, passen diese Versäumnisse genau auf
die aufgetretenen Verhaltensweisen des Programms.
Ich werde nun entweder meinen Startupcode erweitern oder einen fertigen
nehmen. Libgloss soll, wie ich gestern gelesen habe, Startupcode für
verschiedene Targets beinhalten.
Wenn ich Resultate habe melde ich mich wieder. Viele Grüße,
Daniel
So, habe nun Folgendes in meinen Startup Code aufgenommen:
1. .bss und COMMON Section wird mit Nullen überschrieben
2. Stackpointer werden korrekt initialisiert
Bisher traten keine weiteren Probleme (der beschiebenen Art) auf. Danke
nochmal für eure Hilfe!
Ich habe aber noch Fragen:
1. Kann es sein, dass die Option -nostartfiles für den Linker
verhindert, dass der Compiler eine C-Runtime mit einbringt?
2. Wieso liegen meine globalen uninitialisierten Variablen in COMMON und
nicht in .bss?
3. Wenn ich ein Struct einer Funktion übergebe, wird es dann wirklich im
Stack übergeben? Wenn das Struct sehr groß ist, dann ist der Stack ja
schnell voll, meiner beispielsweise hat eine Größe von 4kB. Laut C-Buch
soll es so sein, und ich nutze daher nur Pointer auf Structs als
Parameter.
Viele Grüße
Daniel
Daniel G. schrieb:
> 1. Kann es sein, dass die Option -nostartfiles für den Linker> verhindert, dass der Compiler eine C-Runtime mit einbringt?
Ja.
> 3. Wenn ich ein Struct einer Funktion übergebe, wird es dann wirklich im> Stack übergeben? Wenn das Struct sehr groß ist, dann ist der Stack ja> schnell voll, meiner beispielsweise hat eine Größe von 4kB.
Weshalb man komplette structs auch nur selten als Wert übergibt,
allenfalls wenn sehr klein.
Daniel G. schrieb:
> 2. Wieso liegen meine globalen uninitialisierten Variablen in COMMON und> nicht in .bss?
Historisches Unix-C-Compiler-Verhalten. Damit musste man eine
Deklaration einer nicht initialisierten globalen Variablen nicht
von einer Definition unterscheiden, sondern kann bspw. in einer
Headerdatei stehen haben:
1
intsomeglobal;
Alle Quelldateien, die sich das reinziehen, legen dann eine identische
Deklaration dafür im Objektcode ab, die als COMMON deklariert ist.
Wer noch FORTRAN lernen durfte weiß, wofür der Linker sowas mal können
musste. :-) Der Linker überlagert nämlich all diese COMMON-Blöcke,
sodass gleichartige Definitionen auch wirklich auf das gleiche Objekt
zeigen.
Dieses Verhalten ist vom C-Standard explizit gestattet und als eine
von zwei (glaub' ich) möglichen Alternativen spezifiziert.
Mittels -fno-common schaltet man beim GCC auf die andere Alternative
um.
Normalerweise sollte der Linkerscript die COMMON-Dinger auflösen und
dann mit .bss zusammenführen.
Zuvor hatte ich Folgendes im Linker Script stehen:
/* collect all uninitialized .bss sections */
.bss (NOLOAD) : {
. = ALIGN(4);
_sbss = .;
*(.bss)
_ebss = .;
}>ram
Damit lagen alle COMMON Objekte hinter .bss und eben nicht zwischen
_sbss und _ebss (laut map file).
Nach hinzufügen von *(COMMON) hinter *(.bss) wurden dann alle COMMON
Objekte in die Section .bss aufgenommen.
Leider muss ich den Thread fortsetzen: Zwar konnte ich die Probleme
größtenteils lösen aber ein weiteres und ähnliches Problem tauchte auf.
Ich habe mehrere verschachtelte if-Abfragen. Je nachdem ob ich eine
Codezeile vor einer bestimmten if-Abfrage einfüge, wird die nachfolgende
if-Abfrage korrekt behandelt oder (beides, if und else) einfach
übersprungen.
1. Das dürfte wohl auch auf Speichersalat zurückzuführen sein, aber ich
weiß nicht wo ich noch suchen soll oder wo zuerst. Die beiden
Stackpointer (SVC und IRQ) sind initialisiert, andere Prozessormodi
nutzt das Programm nicht. Die Fehler tritt im IRQ Modus auf.
2. Die Stacks sind riesig und ich habe sie testweise noch riesiger
gemacht. Zudem übergeben ich höchstens 32Bit Variablen an Funktionen.
Also Stacküberlauf schließe ich aus.
3. Natürlich habe ich vielfach den C-Code auf fehlerhafte Pointer,
Kopierroutinen etc. durchgesehen und verschiedenes testweise
auskommentiert.
Ich komme langsam in Zeitnot und bin über jeden Hinweis dankbar, der
mich den Fehler schneller finden lässt!
Ich hatte noch zwei Vermutungen:
V1. Linker Script nicht korrekt. Dazu habe ich mir das ld manual
durchgelesen und das Linkerscript nach meinem ermessen umgeschrieben. Es
hat sich nichts verändert.
V2. Irgendein Problem mit THUMB und ARM Code bzw. Interworking. Kann das
sein?
Grüße,
Daniel
Ich habe gerade noch rausgefunden, dass je nach Optimierungseinstellung
des Compilers sich das Verhalten des Codes ändert. Vielleicht deutet das
ja auf etwas hin?
Danke und Gruß,
Daniel
Das habe ich aus anderen Gründen schon mehrmals überprüft. Alle von
Exceptions und Task-Code gemeinsam verwendeten Variablen sind als
volatile gekennzeichnet.
Aber dieses shared data Problem kann doch nicht solch fehlerhaftes
Verhalten herbeirufen!?
Vielleicht fehlt ja noch was in meinem Startup File. Ich lese immer
wieder, dass initialisierte Variablen geladen oder kopiert werden
müssen. Entsprechend meines Linker Scripts sollten die aber bereits im
Image liegen:
.data : {
_sdata = .;
*(.vectors)
*(.data)
_edata = .;
} > ram
Oder habe ich was falsch verstanden?
Nebenbei, weiss jemand, was .vectors zu bedeuten hat?
Daniel G. schrieb:
> Aber dieses shared data Problem kann doch nicht solch fehlerhaftes> Verhalten herbeirufen!?
Klar. Wenn du z.B. in dem if eine Variable abfragst, die eigentlich
volatile sein müsste, dann kann zusätzlicher Code vor diesem if dazu
führen, dass die Variable nicht mehr dauerhaft in Registern gehalten
werden kann, und somit beim if neu aus dem Speicher gelesen werden muss.
Ich habe noch etwas rausgefunden:
Der Fehler tritt auf, egal an welcher Stelle die Funktion im Speicher
liegt. Es muss also etwas mit der Funktion an sich zu tun haben.
Ich habe langsam den Verdacht, dass mit lokalen Variablen nicht richtig
umgegangen wird. Muss ich im Linker Script irgendetwas beachten, dass
lokalen Variablen auch Platz eingeräumt wird?
Daniel G. schrieb:
> Ich habe gerade noch rausgefunden, dass je nach Optimierungseinstellung> des Compilers sich das Verhalten des Codes ändert. Vielleicht deutet das> ja auf etwas hin?
Würdest Du uns auch verraten, wie sich das Verhalten ändert?
Funktioniert das Programm wenn Du optimierst, oder wenn Du nicht
optimierst? Im letzteren Fall, könnte das auf weitere Probleme mit dem
Stack hinweisen.
Bei geringer Optimierung scheint GCC keine Register für lokale Variablen
zu verwenden, sondern nur den Stack. Wenn der nicht richtig aufgesetzt
wurde, dann knallts. Sobald bei höheren Optimierungsstufen für lokale
Variablen Register verwendet werden, kann das Problem verschwinden.
Ich denke an weitere Stackprobleme, da Du erwähntest, dass nur SVC und
IRQ stack verwendet werden. Läuft Dein Programm nur im SVC mode oder
wird irgendwann auch mal in den USR/SYS mode umgeschaltet? Dann musst Du
dessen Stack natürlich auch noch initialisieren.
Gruß
Marcus
http://www.doulos.com/arm/
Hi,
habs vergessen dazu zu schreiben: in allen Optimierungsroutinen läuft
der Code fehlerhaft, nur eben unterschiedlich fehlerhaft. Ohne
Optimierung geht garnichts mehr.
Ich habe die Initialisierung bereits für alle anderen Modi in meinen
Startup Code übernommen. Nun Arbeitet der Prozessor im User-Mode und
IRQ.
Wenn ich den Stack verschiebe oder die Anordnung der Unterprogramme im
Speicherimage, ändert sich nichts, gleiche Fehler.
Reicht es, wenn ich die Stackpointer initialisiere oder muss ich mehr
machen? Habe mal was gelesen, dass der Stackbereich mit Nullen
überschrieben wird. Halte das bisher für unnötig.
Gruß,
Daniel
Ich bin leider immer noch nicht weiter und hoffe auf Hilfe! Vielleicht
kann sich ja jemand meine c-runtime ansehen ob dort ein Fehler ist. Ich
wäre sehr dankbar!
Der Vectortable liegt nicht am Anfang des Speichers. Start ist .start
von wo aus der table dann kopiert wird und den Vectortable der Bootstrap
routine überschreibt. Das funktioniert immerhin.
Wie gesagt: unterschiedliches fehlerhaftes Verhalten je nach
Optimierungsgrad. Ohne Optimierung geht nichts mehr, da wird nicht mal
die erste Zeile der main-funktion ausgeführt.
Viele Grüße
Daniel
Nein, es ist leider nicht gelöst. Ich habe daher kürzlich einen neuen
Thread gestartet "Ohne Optimierung gehts nichts mehr" oder so ähnlich.
Aber auch dort leider kein Ergebnis.
Das Image mit allen Daten wird durch AT91Bootstrap in RAM kopiert.
Grüße
Daniel
Ich habe das Problem möglicherweise gelöst:
Ich habe mir die Stackpointer Initialisierung von der Atmel
at91bootstrap Routine abgeschaut, wo der benutzte Stackpointer auf
0x23ffffff, also Ende des Speichers gesetzt wird.
Ist das OK? Es ist ein full stack und somit wird der erste Wert
unaligned mit drei bytes im Nirgendwo gespeichert. Wie verhält sich STM
bei unaligned data? Wird überhaupt etwas gespeichert? Und wieso macht
Atmel sowas?
Grüße,
Daniel Gering
"Full" heisst, dass SP auf das unterste belegte Byte zeigt, also erst
dekrementiert und dann gespeichert wird. Daher wäre 0x24000000 kein
Problem, sondern vielmehr der klassische Anfangswert eines
Stackpointers.
Aber 0x23ffffff für einen Stackpointer wäre wirklich äusserst seltsam,
denn das wäre eben unaligned und sowas ist bei Stacks ziemlich verboten,
selbst wenn eine Maschine sowas erlauben sollte, was beim ARM m.W. nicht
der Fall ist. Allerdings meckert ARM zumindest in Standardeinstellung
wohl auch nicht, was zu allerlei Unfug führen kann.
Wenn das tatsächlich der Fall ist, dass besteht möglicherweise ein
Missverständnis zwischen Startup-Code und den Werten/Namen vom
Linker-Script.
Oh ja, bei full stack hab ich mich geirrt. Dann liegt es wohl nur am
alignment. Und es war auch vorschnell und falsch zu sagen, dass Atmel es
unaligned gemacht hat. Das muss ich wohl wo anders her oder selbst
falsch gemacht haben.
Ich nehme an, dass mit steigender Optimierung der Stack weniger benutzt
wird und das Programm somit immer besser lief.
Compiler (codesourcery) hat übrigens nicht gewarnt bei -Wall.
Danke A.K., ihre kompetenten Beiträge sind wirklich ein Gewinn für
dieses Forum!
Dann kann ich mich ja jetzt endlich wieder um die eigentliche
Programmentwicklung kümmern.
Viele Grüße
Daniel Gering
Daniel G. schrieb:
> Compiler (codesourcery) hat übrigens nicht gewarnt bei -Wall.
Der Stackpointer wird in deinem handgebauten Startupcode definiert und
aus einer externen Adresse abgeleitet, die wohl dem Linker-Script
entstammt.
Also woher sollte der Compiler die exakte Adresse des Stacks kennen?
Die wird auch in dieser Form nicht dem Compiler mitgeteilt, sondern dem
Präprozessor vor dem Assembler, der deinen Startup-Code verarbeitet.
Assemblerprogrammierung heisst bekanntlich Schrott-rein-Schrott-raus.
Sorry, aber du kannst nicht den Compiler für deinen Fehler in Haftung
nehmen, der hat von solchen absoluten Adressen keine Ahnung.
> Allerdings meckert ARM zumindest in Standardeinstellung> wohl auch nicht, was zu allerlei Unfug führen kann.
Ich habe den Satz wohl falsch verstanden, ich dachte du meintest den
Compiler und dessen Warnungen. Natürlich ist es schön wenn ein Compiler
bei Bugs warnt, aber ich geben selbstverständlich nicht dem Compiler
schuld wenn ich einen Fehler mache und er nicht warnt.
Was meinst du mit dem obigen Satz? Wie soll er meckern? Exception?
Also bekommt der Compiler nichts weiter als eine Referenz auf die
Speicheradresse in der der TOP_OF_MEM Wert steht?
Daniel G. schrieb:
> Was meinst du mit dem obigen Satz? Wie soll er meckern? Exception?
Es gibt Prozessoren mit unaligned trap. In ARMv6 (ARM11) besteht
offenbar die Möglichkeit, solche Zugriffe entweder einen Trap laufen zu
lassen, oder korrekt auszuführen.
> Also bekommt der Compiler nichts weiter als eine Referenz auf die> Speicheradresse in der der TOP_OF_MEM Wert steht?
Der Compiler kriegt davon garnichts mit. Der verlässt sich darauf, dass
am Anfang einer Funktion der SP einen sinnvollen Wert enthält und sorgt
dafür, dass was ihn angeht aus einem korrekten SP kein unkorrekter wird.
Optional wird Code eingebaut, der auf Stacküberlauf testet. Das wär's
dann aber auch. Den absoluten Wert kennt er nicht. Der
Präprozessor-Makro TOP_OF_MEM ist für den Compiler völlig uninteressant.
Daniel G. schrieb:
> Ich nehme an, dass mit steigender Optimierung der Stack weniger benutzt> wird und das Programm somit immer besser lief.
Sach' ich ja.
A. K. schrieb:
> Es gibt Prozessoren mit unaligned trap. In ARMv6 (ARM11) besteht> offenbar die Möglichkeit, solche Zugriffe entweder einen Trap laufen zu> lassen, oder korrekt auszuführen.
Geht auch bei einigen ARM9E basierten Systemen. Siehe z.B.
http://infocenter.arm.com/help/topic/com.arm.doc.ddi0198e/I1039296.html
Allerdings bieten ARMv6/7 noch mehr Konfigurationsmöglichkeiten.
Gruß
Marcus
http://www.doulos.com/arm/