Hallo Forum, ich habe verwende den AVR-GCC in der Version 3.4.6
(Optimierungsgrad "S"). Das Programm wird für ein Atmega AT90CAN128
compiliert. Ja, ich weiss, dass dies nicht mehr der aktuellste ist, er
hat sich bei uns aber bewährt und wird daher weiterhin eingesetzt. Bis
heute konnte ich alle Fehler noch auf irgendwelche "Schweinerein" im
Quelltext zurückführen. Ab heute bin ich mir aber fast sicher, dass es
sich hierbei um einen Fehler im Compiler handeln muss.
Problem:
Springt das Programm in die Unterfunktion "Handle_Device" und soll dann
wieder zu seinem ursprünglichen Aufruf zurückkehren, ist die
Rücksprungadresse beschädigt und der Prozessor startet quasi neu.
Nun liegt es nahe, dass durch ein Speicherüberlauf dieser innerhalb der
Funktion beschädigt wurde. Aber so ist es definitiv nicht!
Funktion (stark vereinfacht):
1
unsignedintCRCcheck(TBuffer*aBuffer)
2
{
3
unsignedcharstream[PAYLOAD_LEN+7];
4
unsignedchari=0;
5
unsignedintcrc;
6
stream[0]=0x02;// STX
7
stream[1]=(unsignedchar)(aBuffer->Cmd>>8);// CMD HIGH
8
stream[2]=(unsignedchar)(aBuffer->Cmd);// CMD LOW
9
stream[3]=aBuffer->Len;// Length of payload
10
11
for(i=0;((i<stream[3])&&(i<PAYLOAD_LEN));i++)
12
{
13
stream[4+i]=aBuffer->Payload[i];// payload
14
}
15
16
17
stream[4+i]=aBuffer->Seq;// sequence number
18
stream[5+i]=(unsignedchar)(aBuffer->CRC>>8);// CRC HIGH
crc=0;// < zum test, gleicher fehler wie: CRC16(stream, i+7);
22
23
returncrc;
24
25
}
26
27
unsignedintHandle_Device(TDevice*aDevice)
28
{
29
unsignedintretval=0x0000;
30
// ...
31
id=0;
32
retval=CRCcheck(&aDevice->Buffer[id]);
33
// ...
34
returnretval;
35
}
Problembehebung ("work around")
- Anderen Optimierungsgrad
- Statt "return retval;" > "return 0;"
- Die Funktion CRCcheck kürzen (egal wie)
Wenn ich einen dieser Punkte ausführe, funktioniert alles wie gewollt.
Um nun dem Fehler zu finden, habe ich mir den Assembler-Quelltext
angeschaut. Und es gibt einen entscheidenden Unterschied zwischen den
Fällen, dass es funktioniert und dass es nicht funktioniert. Ich habe
die entscheidenden Stellen mal rauskopiert:
Assembler FUNKTIONIERT NICHT!!! (Register sichern):
1
+0000115F: 922F PUSH R2 Push register on stack
2
+00001160: 923F PUSH R3 Push register on stack
3
+00001161: 924F PUSH R4 Push register on stack
4
+00001162: 925F PUSH R5 Push register on stack
5
+00001163: 926F PUSH R6 Push register on stack
6
+00001164: 927F PUSH R7 Push register on stack
7
+00001165: 929F PUSH R9 Push register on stack
8
+00001166: 92AF PUSH R10 Push register on stack
9
+00001167: 92BF PUSH R11 Push register on stack
10
+00001168: 92CF PUSH R12 Push register on stack
11
+00001169: 92DF PUSH R13 Push register on stack
12
+0000116A: 92EF PUSH R14 Push register on stack
13
+0000116B: 92FF PUSH R15 Push register on stack
14
+0000116C: 930F PUSH R16 Push register on stack
15
+0000116D: 931F PUSH R17 Push register on stack
16
+0000116E: 93CF PUSH R28 Push register on stack
17
+0000116F: 93DF PUSH R29 Push register on stack
18
+00001170: B7CD IN R28,0x3D In from I/O location
19
+00001171: B7DE IN R29,0x3E In from I/O location
20
+00001172: 9724 SBIW R28,0x04 Subtract immediate from word
21
+00001173: B60F IN R0,0x3F In from I/O location
22
+00001174: 94F8 CLI Global Interrupt Disable
23
+00001175: BFDE OUT 0x3E,R29 Out to I/O location
24
+00001176: BE0F OUT 0x3F,R0 Out to I/O location
25
+00001177: BFCD OUT 0x3D,R28 Out to I/O location
26
+00001178: 018C MOVW R16,R24 Copy register pair
27
+00001179: 012A MOVW R4,R20 Copy register pair
28
+0000117A: 013B MOVW R6,R22 Copy register pair
Assembler FUNKTIONIERT NICHT!!! (Register wieder aus dem Stack holen):
1
+0000127F: 01C5 MOVW R24,R10 Copy register pair
2
+00001280: 9624 ADIW R28,0x04 Add immediate to word
3
+00001281: B60F IN R0,0x3F In from I/O location
4
+00001282: 94F8 CLI Global Interrupt Disable
5
+00001283: BFDE OUT 0x3E,R29 Out to I/O location
6
+00001284: BE0F OUT 0x3F,R0 Out to I/O location
7
+00001285: BFCD OUT 0x3D,R28 Out to I/O location
8
+00001286: 91DF POP R29 Pop register from stack
9
+00001287: 91CF POP R28 Pop register from stack
10
+00001288: 911F POP R17 Pop register from stack
11
+00001289: 910F POP R16 Pop register from stack
12
+0000128A: 90FF POP R15 Pop register from stack
13
+0000128B: 90EF POP R14 Pop register from stack
14
+0000128C: 90DF POP R13 Pop register from stack
15
+0000128D: 90CF POP R12 Pop register from stack
16
+0000128E: 90BF POP R11 Pop register from stack
17
+0000128F: 90AF POP R10 Pop register from stack
18
+00001290: 909F POP R9 Pop register from stack
19
+00001291: 907F POP R7 Pop register from stack
20
+00001292: 906F POP R6 Pop register from stack
21
+00001293: 905F POP R5 Pop register from stack
22
+00001294: 904F POP R4 Pop register from stack
23
+00001295: 903F POP R3 Pop register from stack
24
+00001296: 902F POP R2 Pop register from stack
25
+00001297: 9508 RET Subroutine return
Man beachte, 00001170 bis 00001172 und 00001281 bis 00001285. Hier wird
der Stack "manipuliert" und genau dieser Part ist im Assembler Quelltext
nicht vorhanden, wenn ich eines der "Work-Around" nutze.
Außerdem passiert folgendes: Nach meiner Meinung nutzt der Compiler die
Register R28 und R29 zum sichern des Stackpointers. Soweit so gut,
jedoch verwendet er diese Register auch noch für andere Dinge innerhalb
der Funktion.
Und genau hier liegt das Problem! Nachdem die Funktion beendet werden
soll, wird das Registerpaar genommen und zurück auf den Stackpointer
geschrieben. Da sich diese Werte aber verändert haben, funktioniert das
ganze nicht mehr.
Work-Arounds habe ich viele gefunden (ganz unterschiedlicher Art).
Selbst ein paar zusätzliche Debug-Ausgaben führten dazu, dass das
genannte Registerpaar innerhalb der Funktion nicht mehr angefasst wird.
ABER WARUM MACHT DER COMPILER SOWAS?!?!
Vielen Dank für eure Hilfe!
Daniel
Zunächst einmal ist das der seit jeher übliche Prolog/Epilog vom
avr-gcc. R28/R29 bilden den Framepointer, den GCC aber nur verwendet
wenn er ihn benötigt oder man die Optimierung abschaltet. Er benötigt
ihn genau dann, wenn er lokale Daten in den Speicher und nicht in
Register legt, wegen Anzahl, Adressierung oder weil wie hier ein Array.
Mehr lässt sich schlecht sagen, da du genau den Teil, der dir angeblich
diesen Framepointer zerlegt, weggelassen hast.
Jedenfalls ist der präsentierte Code völlig ok.
Die von Dir beklagten Zeilen sind o.k., sie legen 4 Byte Variablen auf
dem Stack an.
Dein Problem ist, daß Du im Interrupt eine Unterfunktion aufrufst.
Der AVR-GCC macht aber keine Registeranalyse, d.h. er geht immer davon
aus, daß jede Funktion alle Scratchpadregister zerstört.
Also sicher er unnötig nen Haufen Register und legt zusätzlich eigene
Variablen auf dem Stack an.
Warscheinlich läuft dadurch Dein Stack über und zerschießt Dir
Variablen.
Unterfunktionen in Interrupts sind als ober-BÄH.
Peter
Die vermutung von A.K. ist naheliegend, weshalb ich die erweiterte
Bedingung in die For-Schleife mit aufenommen habe.
Das Auskommentieren dieser Zeile bringt aber auch keinen Erfolg!
Hier die Stelle, an der mir das Register R28 versaut wird:
1
598: if (aDevice->State == DEVICESTATE_Error)
2
+0000117D: 01EC MOVW R28,R24 Copy register pair
3
+0000117E: 8188 LDD R24,Y+0 Load indirect with displacement
4
+0000117F: 8199 LDD R25,Y+1 Load indirect with displacement
5
+00001180: 308A CPI R24,0x0A Compare with immediate
Bist du eigentlich sicher, dass dieser Prolog zum der richtige ist? In
einem Punkt muss ich mich nämlich korrigieren: er ist zwar in sich
korrekt, passt aber nicht zum gezeigten Quellcode. PAYLOAD_LEN+7 wird
kaum 4 sein, es sei denn PAYLOAD_LEN ist -3. Denn die 4 Bytes auf dem
Stack sind klar zu wenig für stream[].
Bring mal ein compilierfähiges Beispiel.
Daniel S. schrieb:
> @Peter:> Diese Funktionen werde nicht in einem Interrupt aufgerufen.
Stimmt, die PUSH/POP Orgie hat mich zu der Annahme verleitet, es wäre
ein Interrupt.
Deine Funktion benutzt aber nen ziemlichen Haufen Variablen, die der
Compiler nicht optimieren kann. Das bewirkt dann die PUSH/POP Orgie und
Stackvariablen.
Irgendwas scheint darin zu sein, was den Compiler total die Kontrolle
verlieren läßt. Vielleicht kannst Du die Funktion etwas übersichtlicher
schreiben, damit er besser optimieren kann.
Ich glaube eigentlich nicht, daß wirklich soviel Variablen gleichzeitg
benötigt werden.
Notfalls machen Variablen statisch. Sie belegen dann zwar ständig SRAM,
ist aber allemal besser als Stackvariablen. Der Compiler kann sie dann
direkt zugreifen, statt einen Pointer zu belegen.
Peter
A. K. schrieb:
> Bist du eigentlich sicher, dass dieser Prolog zum der richtige ist? In> einem Punkt muss ich mich nämlich korrigieren: er ist zwar in sich> korrekt, passt aber nicht zum gezeigten Quellcode. PAYLOAD_LEN+7 wird> kaum 4 sein, es sei denn PAYLOAD_LEN ist -3. Denn die 4 Bytes auf dem> Stack sind klar zu wenig für stream[].
Doch, das ist dem AVR-GCC zuzutrauen.
Er sieht, daß nur stream[3] gelesen wird und optimiert die anderen
einfach weg.
Aber wofür die vielen Register benötigt werden, das fehlt in dem
Quelltext.
Peter
Die Nummer mit R28:R29 steckt in Code, der oben im präsentierten Code
garnicht drinsteht. Drum frage ich ja nach was compilierbarem, dieses
Fragment reicht nicht.
Ok, leider darf ich die Funktion so komplett nicht posten, da es sich
dabei teilweise um "Firmeneigentum" handelt.
Ich werde die Funktion jetzt mal umschreiben, so dass der Fehler noch
immer auftritt, diese aber überschaubar und compilierbar wird.
Kann natürlich gut sein, dass du hier wirklich über einen Bug
gestolpert bist. Das könnte man nur sagen, wenn du uns den
Quelltext zum Selbstcompilieren gibst (kannst ihn ja anonymisieren).
Aber wenn dem so wäre: Pech gehabt, diese alte Compilerversion pflegt
halt auch niemand mehr.
Daniel S. schrieb:
> Ok, leider darf ich die Funktion so komplett nicht posten, da es sich> dabei teilweise um "Firmeneigentum" handelt.>> Ich werde die Funktion jetzt mal umschreiben, so dass der Fehler noch> immer auftritt, diese aber überschaubar und compilierbar wird.
Am besten erstellst du ein Precompilat mit -save-temps (und ohne -pipe).
Da sind schon mal alle include-Abhängigkeiten, Defines und -DXXX
aufgelöst. Dann wirfst du unbeteiligte Funktionen raus, d.h. solche, die
nicht im Call-Graph des Problems auftauchen. Ebenso wirfst du ungenutzte
Typedefs etc. raus. Schliesslich, wenn du willst, kannst noch nen
C-Obfuscator drüberlaufen lassen (funktionieren aber nicht alle mit
Inline-Assembler).
Das ganze muss nicht linkfähig sein; es genügt, wenn der fehlerhafte
Code mit -S (assemble only) erzeugt wird.
Das Ganze sieht wirklich so aus, als ob da ISR-Prolog und Epiloge
erzeugt werden für ne normale Funktion.
Veränderst du das ABI indem du Register fixierst? Verwendest du globale
oder lokale Registervariablen?
Johann
Daniel S. schrieb:
> Funktion (stark vereinfacht):> [...]>> ABER WARUM MACHT DER COMPILER SOWAS?!?!>> Vielen Dank für eure Hilfe!
Der Compiler macht was komplett anderes. Ich hab mir mal die Mühe
gemacht dein Code in was übersetzbares zu wandeln.
1
#include<stdint.h>
2
3
#define PAYLOAD_LEN 16
4
5
typedefstruct
6
{
7
uint8_tCmd;
8
uint8_tLen;
9
uint16_tCRC;
10
uint8_tSeq;
11
uint8_tPayload[PAYLOAD_LEN];
12
}TBuffer;
13
14
typedefstruct
15
{
16
TBufferBuffer[10];
17
}TDevice;
18
19
unsignedintCRCcheck(TBuffer*aBuffer)
20
{
21
unsignedcharstream[PAYLOAD_LEN+7];
22
unsignedchari=0;
23
unsignedintcrc;
24
stream[0]=0x02;// STX
25
stream[1]=(unsignedchar)(aBuffer->Cmd>>8);// CMD HIGH
26
stream[2]=(unsignedchar)(aBuffer->Cmd);// CMD LOW
27
stream[3]=aBuffer->Len;// Length of payload
28
29
for(i=0;((i<stream[3])&&(i<PAYLOAD_LEN));i++)
30
{
31
stream[4+i]=aBuffer->Payload[i];// payload
32
}
33
34
35
stream[4+i]=aBuffer->Seq;// sequence number
36
stream[5+i]=(unsignedchar)(aBuffer->CRC>>8);// CRC HIGH
Fazit
Das ist komplett anders als das asm-Zeug, das du oben gepostet hast.
Wenn du wirklich ernsthaft Interesse daran hast, Hilfe bei einem
(angeblichen) gcc-Bug zu bekommen, dann gibt es hier einige Leute, die
Ahnung haben und dir weiterhelfen können, die sich aber möglicherweise
zeimlich veräppelt fühlen, wenn du Märchen-Code postest so wie oben.
Das konkrete Verhalten eines agressiv optimierenden Compilers wie GCC
ist ohne die Quelle nicht nachvollziehbar!
Nutzloser Code ist zB
-- Code der ... enthält (ausser natürlich in varargs-Funktionen)
-- Code, der externe Header verwendet
-- Unbekannte Optionen
-- etc.
Also noch mal von vorne: Ohne konkreten Testfall kann dir hier niemand
helfen mit deinem Problem. Was die Erstellung eines Testfalles angeht
sei dir
http://gcc.gnu.org/bugs.html#need
ans Herz gelegt. Du verdienst deine Brötchen mit dem Zeug, also sollte
das Englisch dort keine Hürde sein. Falls doch, steht es schon in
Beitrag "Re: Fehler in AVR-GCC 3.4.6"
bzw. du kannst auch nachfragen, wenn da was unklar ist oder warum ... =
Märchenstunde ist ;-)
Johann