Forum: Mikrocontroller und Digitale Elektronik C-Source-Code Problem mit ATMega 128


von U. B. (ub007)


Lesenswert?

Hallo !

Ich muss eine Aufgabe übernehmen den Source-Code von einem Ex-Kollegen 
zu analysieren und versuchen das Programm wieder in einen stabilen 
Zustand zu bringen, es ist allerdings etwas komplex um es hier zu 
posten.
Ich möchte es mal kurz umreisen:
Entw.-Umgebung ist AVR-Studio5. Das Programm selbst für den ATMega ist 
zeitunkritisch.
Von einer ISR-Routine (nach sleep Modus - Power down) aus werden einige 
Funktionen aufgerufen sodass eine maximale Verschachtelungstiefe von ca. 
5 erreicht wird. Es werden allerdings einige Variablen in den Funktionen 
definiert.
Es werden dann drei Array-Variablen eingelesen und dann wird entschieden 
ob eine SMS gesendet wird oder nicht.
Kurz vor dem senden der SMS habe ich die drei Variablen nochmals mir 
ausgeben lassen und stellte fest das die zweite Feld-Variable auf einmal 
einen falschen Wert hat. Zwischen diesen paar Zeilen wird zwar eine 
Funktion aufgerufen, aber in dieser wird nichts an Variablen verändert. 
Zumal dürfte dieses Verhalten nicht stattfinden, da im Scope-Bereich der 
Funktion nicht auf die 3 Feld-Variablen zugegriffen werden können.
Das Programm läuft zwar schon einigermaßen stabil nur an der oben 
erwähnten Stelle wird eine Variablen verändert, was eigentl. nicht sein 
kann.
Was könnte man den machen um hier rauszufinden was hier schief läuft. 
Man könnte zwar mit dem Simulator mal durchsteppen aber ich weiß jetzt 
nicht ob das so ohne weiteres geht und zwar weil das eigentl. Progamm 
nach einem Interrupt vom sleep-Modus aufwacht und aus der ISR-Routine 
gestartet wird.

Gruß U

von Knut (Gast)


Lesenswert?

Dann zeig mal diesen Codeausschnitt.

Knut

von g457 (Gast)


Lesenswert?

> [..] wird eine Variablen verändert, was eigentl. nicht sein kann.

Doch, kanns: Heap-Overflow, Pufferüberlauf allgemein, amok gelaufener 
Zeiger, fehlendes 'volatile', ..

Letzeres ändert den Wert nur scheinbar (genau genommen bekommt man eine 
tatsächliche Änderung nicht mit, hat also einen alten Wert, keinen 
'beliebigen').

HTH

von Peter II (Gast)


Lesenswert?

U. B. schrieb:
> Was könnte man den machen um hier rauszufinden was hier schief läuft.
> Man könnte zwar mit dem Simulator mal durchsteppen aber ich weiß jetzt
> nicht ob das so ohne weiteres geht und zwar weil das eigentl. Progamm
> nach einem Interrupt vom sleep-Modus aufwacht und aus der ISR-Routine
> gestartet wird.
das sollte egal sein, wenn eine Variable geändert wird ohne das du das 
willst, dann wird es wohl ein speicher überschreiber sein oder der STack 
reicht bis in dem Ram rein. Das sollte sich aber im Debugger 
nochvollziehen lassen an welcher stelle genau sich as array ändert.

von U. B. (ub007)


Lesenswert?

Hallo !

Das ging aber schnell !
Ich hatte mir schon so etwas gedacht, dass es an einer dieser Sachen 
liegt.
Ich denke wir machen das mal so - ich werde heute Abend mal den 
Simulator anwerfen und dann mal durchsteppen.
Ich werd mich dann noch heut Abend wieder melden und mal berichten.
Event. stell ich dann mal den Source-Code rein - aber halt nicht alles 
sonst wird mich der Moderator noch verklagen ;-)  - sondern halt so dass 
man einen Überblick bekommt.

Gruß U

von U. B. (ub007)


Lesenswert?

Hallo !

Sorry das ich mich erst jetzt melde, aber ich war leider unterwegs.
Ja, den Fehler konnte man sogar im Simulator nachvollziehen !
Dort trat sogar das gleiche Ergebnis auf !
Ich werde jetzt versuchen die Verschachtelungstiefe der Aufrufe zu 
verringern - das geht zwar auf die Lesbarkeit des Source-Codes aber ich 
denke damit müsste sich event. eine Besserung einstellen.
Gibt es event. noch eine Möglichkeit wie man das programm straffen kann.
Z.B. Funktionsaufrufe die nur einen Befehl haben(Port setzen/löschen) - 
habe ich durch eine Define Anweisung ersetzt. Ich denke das sollte 
event. auch etwas dazu beitragen, oder ?

Gruß U

von Guest (Gast)


Lesenswert?

> Gibt es event. noch eine Möglichkeit wie man das programm straffen kann.
> Z.B. Funktionsaufrufe die nur einen Befehl haben(Port setzen/löschen) -
> habe ich durch eine Define Anweisung ersetzt.
inline nennt sich das. Geht halt dann auf Kosten der Programmgröße.

> Ich denke das sollte event. auch etwas dazu beitragen, oder ?
Nicht zwingend. Wenn das problem z.B. von einem falschen Pointer kommt 
wohl eher nicht.

von Peter D. (peda)


Lesenswert?

U. B. schrieb:
> Ich werde jetzt versuchen die Verschachtelungstiefe der Aufrufe zu
> verringern

Das versteckt eventuell den Fehler, behebt ihn aber nicht. D.h. der 
kommt garantiert wieder!

Du mußt schon die Variable finden, die Du zu klein dimensioniert hast 
oder falsch zugreifst.

Da ein Aufruf sie überschreibt, wird sie vermutlich zu klein sein und 
damit schreibt sich die Returnadresse da hinein, weil der Compiler 
denkt, die Variable ist zu ende.


Peter

von Oliver (Gast)


Lesenswert?

Returnadressen landen auf dem Stack, und der Compiler hat keinerlei 
Ahnung davon, wo und wie der eventuell mit anderen Datensegmenten 
kollidiert.

Simulator starten, und Stack prüfen.

Oliver

von Peter D. (peda)


Lesenswert?

Oliver schrieb:
> Returnadressen landen auf dem Stack

Also genau nach den lokalen Variablen, zumindest beim AVR-GCC ist das 
so:
1
void foo()
2
{
3
  int array[8];
4
5
  array[8] = 12345;
6
  faa();
7
  if( array[8] != 12345 ){
8
    printf("Array to small !");
9
  }
10
}


Peter

von U. B. (ub007)


Angehängte Dateien:

Lesenswert?

Hallo Peter !

Ich hab die ganzen Verschachtelungen mal rausgenommen und laufen lassen 
und wie erwartet war der Fehler immer noch da.
Peter Dannegger schrieb:
>
> Du mußt schon die Variable finden, die Du zu klein dimensioniert hast
> oder falsch zugreifst.
>
> ...
> Peter
Das ist so gesehen das Problem von meinem Ex-Kollege der das 
programmiert hat und jetzt ist es meins :-(
Ich muss die Nadel im Heuhaufen...
Ich hab mal ein Bild von der Simulation angehängt.
Kann das sein dass der Stack hier schon übergelaufen ist ?
Der Stack fängt am Programmanfang 0x10FB an und bei diesem Bild ist er 
schon bei 0x105D. Die Variablen sind aber höher z.B. NRCODE[0] ist laut 
Sim bei 0x1061, oder ?
Falls ich das Bild falsch interpretiere wie kann ich denn genau 
feststellen wann der Stack überläuft ?
Eine weitere Sache ist diese:
1
void usart_write_str(char *str)
2
{
3
   while (*str)
4
   {  
5
      long_delay(50); // GSM Modul kommt bei langen strings die
6
                      // in der Systemzeit geschickt werden nicht klar
7
                      // und antwortet mit Fehler 314 - Sim Busy
8
                      // Deswegen werden immer 50ms gewartet, bis das 
9
                      // nächste Zeichen gesendet wird!
10
      usart_write_char(*str++);
11
   }
12
}
Ich würde sagen im Prinzip ist der Zeiger hier O.K., aber solange man 
nichts reinschreibt. Hier ist es nur ein Lesevorgang also sollte das 
auch unkritisch sein.
Ich hab schon überlegt ob ich statt des Pointers ein Array machen 
sollte, aber ob das was bringt ?
Der Kollege übergibt manchmal Strings wie z.B. "gesendet" an diese 
Funktion (nur zum darstellen auf dem Bildschirm). Ich frag mich aber 
woher weiß die Funktion wann Ende ist wenn z.B. kein \r\n dahinter kommt 
?

Gruß U

von Krapao (Gast)


Lesenswert?

> Ich frag mich aber woher weiß die Funktion wann Ende ist
> wenn z.B. kein \r\n dahinter kommt?

In C wird das Ende eines Strings durch ein Nullbyte '\0' angezeigt und 
darauf prüft auch die Bedingung der while-Schleife im gezeigten Code. 
Das sind aber C Grundlagen, die in jedem C Buch ganz vorne kommen!

von Krapao (Gast)


Lesenswert?

> NRCODE[]

Bist du sicher, dass dieses Feld überhaupt auf dem Stack liegt?

Ohne dass die Definition im Code gezeigt wird, möchte ich das nicht 
pauschal bejahen.

Wenn du ein Überschreiben der Feldgrenzen von NRDATA[] vermutest, 
könntest du eine Überwachung der Variablen iz einrichten und mit der 
Größenangabe in der Definition von NRDATA[] vergleichen (ggf. als 
assert()).

Du kannst auch ermitteln, wo deine Variablen enden und den Stackpointer 
regelmäßig überwachen, ob der dieser Adresse gefährlich nahe kommt.

Das wäre entweder eine Sache, die in einem regelmäßigen Interrupt 
geschehen kann oder als Prolog/Epilog aller Funktionen. Bei letzterem 
hat die GCC Toolchain Instrumente, um das zu machen. Schau dir die 
Profiling Optionen im GCC Manual an. Oder du kannst auch einer letzten 
Variable eine typischen Wert zuweisen und regelmäßig im (User-)Programm 
prüfen, ob dieser Wert noch vorhanden ist oder bereits durch den 
überlaufenden Stack gekillt wurde (Kanarienvogelverfahren).

Ich erinnere mich auch, dass vor einiger Zeit für die Stackprüfung hier 
im Forum Routinen bereit gestellt wurden. Schau in der Codesammlung oder 
in der Artikelsammlung im Softwarepool nach, wenn dich das interessiert.

von Oliver (Gast)


Lesenswert?

Ich kenne jetzt das Studio 5 nicht, aber schau doch mal nach, ob man 
nicht das SRAM vor Simulaitonsbegin mit z.B. 0xFF vorbelegen kann. Wenn 
dann irgendwann keine 0xFF's mehr drinstehen, ist der Stack 
übergelaufen.

Oliver

von Peter D. (peda)


Lesenswert?

U. B. schrieb:
> Der Stack fängt am Programmanfang 0x10FB an und bei diesem Bild ist er
> schon bei 0x105D. Die Variablen sind aber höher z.B. NRCODE[0] ist laut
> Sim bei 0x1061, oder ?

Lokale Variablen liegen beim AVR-GCC im Stack, das ist also korrekt.


U. B. schrieb:
> Falls ich das Bild falsch interpretiere wie kann ich denn genau
> feststellen wann der Stack überläuft ?

Der Stack läuft über, wenn er in die globalen Variablen reinläuft.

Du wirst allerdings eher einen falschen Zeiger haben und damit den Stack 
zerstören. Das ist kein Überlauf.

Gern gemachte Fehler sind, einen Zeiger auf eine lokale Varieable als 
Returnwert zu übergeben, d.h. der zeigt ins Nirwana.
Oder wie gesagt auf ein zu kleines Array. Ein nicht initialisierter 
Zeiger zeigt auf ein Array der Länge 0, also auch zu klein.
Oder wenn malloc verwendet wird, den Mißerfolg von malloc nicht 
abzufangen.

Peter

von Oliver (Gast)


Lesenswert?

Wenn das Studio 5 die Möglichekit bietet, einen breakpoint auf den 
Zugriff auf eine Speicherstelle zu setzen, dann mach das. Damit bekommst 
du schnell raus, wer oder was dir eine Variable überschreibt.

Oliver

von U. B. (ub007)


Lesenswert?

Krapao schrieb:
> In C wird das Ende eines Strings durch ein Nullbyte '\0' angezeigt und
> darauf prüft auch die Bedingung der while-Schleife im gezeigten Code.
> Das sind aber C Grundlagen, die in jedem C Buch ganz vorne kommen!

Danke für die Ohrfeige.
Stimmt deine Aussage aber noch wenn du nicht mit strings arbeitest, 
sondern vom Telit 862 Modem den Status in Form von Bytes abfragst und in 
ein char Array reinschreibst und dann diese oben gezeigte Funktion 
aufrufst ?
Mein Ex-Kollege hat nach dem letzten empfangenen Byte kein '\0' 
reingesetzt !

@Oliver:
Ich arbeite noch nicht allzulang mit der Studio 5 Umgebung.
Geht das denn überhaupt mit Studio 5 bzw. kannst Du mir bitte sagen wie 
ich das einstellen muss ?

Gruß U

von Oliver (Gast)


Lesenswert?

Isch 'abe gar kein Studio 5...

Insofern kann ich das nicht beantworten. Da aber der Simulator von 
Studio 4 das schon konnte, sollte so etwas auch irgendwo im Studio 5 
versteckt sein. Wenn nicht, nimm halt Studio 4. Da rechts-clickt man 
einfach auf die gewünschte Speicheradresse, und setzt den 
Databreakpoint.

Oliver

von Peter (Gast)


Lesenswert?

Wieviel Speicher hast Dü überhaupt noch frei? Was spuckt der Compiler 
aus, wenn Du das gesammte Projekt mal frisch compilierst? Iregndwelche 
Warnings?

von U. B. (ub007)


Lesenswert?

Hallo Peter !

Also Programmspeicher ist ca. 10% voll aber der Datenspeicher ist fast 
50% voll.
Das kommt wegen den "riesigen" Arrays die er von SD-Karte einliest. 4 
Sensoren insgesamt wobei jeder Sensor ein Array von über 50 Zeichen hat. 
Ein paar dieser Arrays werden am Anfang des programms gesetzt und ein 
paar Arrays werden dann lokal in den Funktionen deklariert.
Mein Kollege sagte mir noch dadurch werden dann die lokalen Variablen 
gelöscht wenn man aus der Funktion wieder rauskommt. Das stimmt ja so 
gesehen, aber er bedachte nicht, dass er aus den Funktionen gar nicht 
mehr rauskommt, sondern erst mit dem Abschluss der übermittelten SMS 
alle Funktionen verläßt. Das bringt so gesehen erstmal recht wenig.

Gruß U

von Peter D. (peda)


Lesenswert?

U. B. schrieb:
> Mein Ex-Kollege hat nach dem letzten empfangenen Byte kein '\0'
> reingesetzt !

Wenn es kein String nach C-Norm ist, dann kannst Du auch keine 
Stringfunktionen verwenden. Ansonsten kracht es.
Das wird bestimmt die Ursache sein für die Stackkorrumption.
Also mußt Du daraus erstmal einen String machen.


Peter

von Peter (Gast)


Lesenswert?

>und ein paar Arrays werden dann lokal in den Funktionen deklariert.

Diese lokalen Arrays sind in den "50% Speicher voll" vom Compiler noch 
nicht enthalten, lokale Arrays landen erst zur Laufzeit auf dem Stack! 
Wenn also nochmals ähnlich viele lokale Variabeln hinzukommen riecht es 
sehr dannach, dass der Speicher überläuft, was auch sehr zum 
Fehlverhalten passen würde!

Lösung => Grössere Datenstrukturen nur einmal global deklarieren und den 
Funktionen einfach bloss den/die Pointer dazu übergeben. Globale 
variabeln sind zwar oft etwas verpöhnt, aber bei wenig RAM (4kByte) 
macht es durchwegs Sinn um Speicher zu sparen!

p.s. Sind printf() mit String-Konstanten als printf_p() angepasst?

von U. B. (ub007)


Lesenswert?

Hallo Peter !

Deine Vermutung hab ich mir auch schon gedacht - also das mit den 
globalen Variablen. Das habe ich heute umgesetzt und konnte doch einiges 
einsparen. Jetzt ist der Datenspeicher nur noch zu 37% voll - hat 
einiges gebracht !

Wegen den printf-Anweisungen... Mein Kollege hat gar keine 
Stringroutinen eingebaut - also strcpy usw.
Es ist so dass nur die Kommunikation vom Telit-Modem auf dem z.B. HTerm 
ausgegeben wird.
D.h. jedes Byte was das Telit-Modem sendet wird in einem Buffer 
zwischengespeichert und dann an die usart-Ausgabe weitergeleitet. Mehr 
kommt an "String-Verarbeitung" nicht vor. Das macht die Sache natürlich 
eher etwas schwieriger sollte aber keinen Abbruch tun.
Ich vermute auch dass die obige Routine für die usart-Ausgabe ins 
Nirvana läuft, und deswegen habe ich heute mal den ganzen Ausgabe-Buffer 
erstmal mit '\0' gefüllt - damit die ankommenden Zeichen vom Modem 
sauber reingeschrieben werden. D.h. ich muss mich nicht um das letzte 
'\0' kümmern.
Nur als ich das laufen lies war der Buffer weder überschrieben noch 
sonst was. Da stand das alte gedöns von der Initialisierung noch drin um 
den Buffer erstmal mit Werten zu befüllen. Ich dachte danach müsste 
irgendwie 0en zusehen sein, aber nichts davon stand drin. Er schreibt 
dann die neuen Werte in den Buffer hinein nur der Rest ist dann nicht 
gelöscht.
Ich werde das heute Abend mit dem Simu nochmal durchsteppen.

Gruß U

von U. B. (ub007)


Lesenswert?

Hallo Peter !

Es ist vollbracht !
Ich hab sämtliche Buffer am Ende mit einem '\0' versehen und geschaut 
dass alle Berechnungen den Buffer am Ende nicht übers Ziel hinaus 
überschrieben werden.
Das hat geholfen und jetzt schnurrt das Ding.

Vielen Dank !

Gruß U

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.