mikrocontroller.net

Forum: Compiler & IDEs Lösung des Rätsels


Autor: Stefan Frings (sfrings)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Die Problembeschreibung war von gestern (habe sie lediglich in diesen 
neuen Thread kopiert), heute habe ich die Lösung gefunden.

Durch lesen des Souce Codes von scanf bin ich auf folgendem Haken 
gestoßen:

scanf liest von stdin solange, bis es ein falsches (in meinem Fall nicht 
numerisches) Zeichen findet. Dann bricht scanf ab und schreibt das 
falsche Zeichen wieder zurück in den Stream.

Da ich stdin mit RXD vom seriellen Port verbunden habe, ist dies ein 
read-only stream. Es gibt nur eine getc() Methode, und keine putc() 
Methode.

An dieser Stelle ruft scanf einen null-pointer auf. Und genau dieser 
Aufruf führt zum Programmabsturz.

Daraus habe ich gelernt: scanf kann man nur auf Streams anwenden, die 
das "zurücklegen" von Zeichen unterstützen. Diese Anforderung wird in 
der Library Doku nicht erwähnt.

Ich dachte mir, dass andere Leute (z.B. Max, falls der hier noch 
mitliest) diese Erkenntnis ebenfalls gebrauchen können.

Autor: ... ... (docean) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Wenn das wirklich nicht erwähnt wird, solltest du vlt. einen bugreport 
eröffnen.

Nur so kann die Doku verbessert werden.

Autor: Michael Appelt (micha54)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Stefan Frings wrote:
> Da ich stdin mit RXD vom seriellen Port verbunden habe, ist dies ein
> read-only stream. Es gibt nur eine getc() Methode, und keine putc()
> Methode.
>
> An dieser Stelle ruft scanf einen null-pointer auf. Und genau dieser
> Aufruf führt zum Programmabsturz.
>
> Daraus habe ich gelernt: scanf kann man nur auf Streams anwenden, die
> das "zurücklegen" von Zeichen unterstützen. Diese Anforderung wird in
> der Library Doku nicht erwähnt.

Hallo,

ich denke, Deine Darstellung ist nicht korrekt: es ist ungetc(), welches 
1 einzelnens Zeichen zurück in das unsigned char __file->unget legt, und 
die steht in det stdio.lib zur Verfügung.
Wichtig ist, daß selbstgeschriebene getc() zuerst dieses Zeichen lesen 
und das zugehörige Statusbit im stream löschen.
Das alles geht auch mit Readonly-streams.

Gruß,
Michael

Autor: Jörg Wunsch (dl8dtl) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Stefan Frings wrote:
> Die Problembeschreibung war von gestern (habe sie lediglich in diesen
> neuen Thread kopiert), heute habe ich die Lösung gefunden.

Ist es wirklich so schwer, einfach im alten Thread zu antworten?

Wenn in zwei Monaten jemand hier drauf guckt, steht der Thread
komplett zusammenhanglos da.

xref: Beitrag "scanf stürzt bei seriellem Port ab"

> scanf liest von stdin solange, bis es ein falsches (in meinem Fall nicht
> numerisches) Zeichen findet. Dann bricht scanf ab und schreibt das
> falsche Zeichen wieder zurück in den Stream.

Das liegt in der Natur von scanf(), der C-Standard hat es
explizit so definiert, dass man zumindest 1 Zeichen wieder
in den Stream zurück schieben können muss.

> Da ich stdin mit RXD vom seriellen Port verbunden habe, ist dies ein
> read-only stream. Es gibt nur eine getc() Methode, und keine putc()
> Methode.

Das hat damit nichts, aber rein gar nichts, zu tun.  Dafür wird
ungetc() benutzt (ich entnehmen deinem Codeschnipsel, dass du mit
einem AVR und damit wohl mit einer avr-libc arbeitest).  Die
Struktur für den Typ FILE besitzt extra ein eigenes Feld, um
dieses eine Zeichen für ein einfaches ungetc() aufzunehmen.

> An dieser Stelle ruft scanf einen null-pointer auf.

Sourcecode, welche Datei, welche Zeile?  Ich sehe dafür nichts in
der avr-libc.

Autor: Jörg Wunsch (dl8dtl) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Gootverdammich, mit welcher Bibliothek arbeitest du denn nun
eigentlich?  Warum lässt du uns das raten, statt das mal zu
schreiben?  (Sowas gehört eigentlich mit ins Ursprungsposting.)

Außerdem darf ich an meine Frage erinnern:

> Sourcecode, welche Datei, welche Zeile?

Autor: Stefan Frings (sfrings)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hi,
ich habe nochmal in den Source geschaut, und konnte den putc() Aufruf 
nun nicht mehr finden. Da habe ich wohl in die Falsche Datei geschaut - 
so ein Scheiß.

PS: ich habe einen neuen Thread aufgemacht, weil Du mich genau darum 
gebeten hast. Ich verstehe die Beschwerde nicht.

Jedenfalls ist jetzt wohl klar, warum mein \n Zeichen verschwindet (denn 
ungetc() unterstüzt meine serielle Routine tatsächlich nicht. War in den 
Beispielen auch nicht drin :-)

Offen ist dann wohl noch die Frage, warum mein Programm abstürzt und 
sich selbst löscht. Aber das bekomme ich sicher auch noch heraus.

Danke für den Hinweis, dass meine Schlußfolgerung falsch war.

Autor: Jörg Wunsch (dl8dtl) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Stefan Frings wrote:

> PS: ich habe einen neuen Thread aufgemacht, weil Du mich genau darum
> gebeten hast. Ich verstehe die Beschwerde nicht.

Sorry, dann habe ich die nun in der Folge falsch zusammen gebracht.
Ich bitte vielmals um Entschuldigung.

> Jedenfalls ist jetzt wohl klar, warum mein \n Zeichen verschwindet (denn
> ungetc() unterstüzt meine serielle Routine tatsächlich nicht. War in den
> Beispielen auch nicht drin :-)

Das mit dem ungetc() sollte doch Sache der Bibliothek sein (wobei
du halt immer noch nicht sagst, welche du denn nimmst), denn die
muss ja den Stream fürs stdio abstrahieren.  Dass man ein Zeichen
nicht in den FIFO der UART zurückstopfen kann, ist ja normal. ;-)
ungetc() sollte das Zeichen also irgendwie ,,beiseite'' legen, damit
es später erneut lesbar ist.

Das mit dem \n-Zeichen ist eine andere Sache.  Whitespace wird per
definitionem von scanf() überlesen, damit wird es u. U. bei der
Suche nach dem Ende des aktuellen Elements dein \n lesen, sollte es
aber mit ungetc() zurück stecken, sodass das nächste getc() (oder
was auch immer, jedenfalls irgendeine Funktion von stdio muss es
sein!) es von dort wieder lesen kann.

Allerdings fand ich scanf() noch nie wirklich einfach zu benutzen.
Meiner Meinung nach sinnvoller ist es, mit fgets() eine ganze
Zeile in einem Puffer zu lesen (dann weiß man ganz genau, dass man
alles bis zum nächsten \n da drin hat) und diesen dann mit sscanf()
zu bearbeiten.  Reines scanf() macht selbst auf PCs nicht richtig
viel Spaß.

Autor: Stefan Frings (sfrings)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ich nutze avr-libc und gcc.

Meine Anwendung habe ich auch schon auf die Kombination fgets+char[] 
buffer+sscanf() umgestellt. Ist wirklich besser zu handhaben.

Aber dieses Abstürze gehen mir nicht aus dem Kopf, deren Ursache möchte 
ich schon herausfinden. Ich kann das Problem zwar locker umgehen, doch 
Probleme zu lösen oder zumindest vollständig zu verstehen ist schon 
interessanter.

Besonder coll finde ich auch den Effekt, daß beim Absturz mein flash 
verändert wird, obwohl ich in meinem Source Code keinen Einzigen 
schreibenden Zugriff vorgesehen habe und auch auch keinen Bootloader 
einsetze. Immerhin müssen ja eine ganze Reihe vopn Bedingungen erfüllt 
sein, um überhaupt in den flash schreiben zu können - das ist schon ein 
interessanter Zufall. Oder vieleicht ist es doch kein Zufall?

Das möchte ich noch weiter untersuchen - die Zeit habe ich. Elektronik 
ist schliesslich mein Hobby, nicht Beruf.

Ich werde mich erstmal mit ungetc() auseinander setzen, und dann mal 
genau untersuchen, was beim Absturz passiert. Vieleicht kann ich dieses 
Verhalten mit einem Simulator reproduzieren - das wäre am einfachsten.

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Stefan Frings wrote:

> Besonder coll finde ich auch den Effekt, daß beim Absturz mein flash
> verändert wird, obwohl ich in meinem Source Code keinen Einzigen
> schreibenden Zugriff vorgesehen habe und auch auch keinen Bootloader
> einsetze. Immerhin müssen ja eine ganze Reihe vopn Bedingungen erfüllt
> sein, um überhaupt in den flash schreiben zu können - das ist schon ein
> interessanter Zufall. Oder vieleicht ist es doch kein Zufall?

Oder vielleicht passiert es in Wirklichkeit auch gar nicht.
Und das halte ich für wesentlich wahrscheinlicher, als das das Flash 
hops gegangen ist.
Aber probiers doch einfach aus: Wen der µC streikt, lies das Flash aus 
und vergleich mit dem was du vorher reingeschrieben hast als es noch 
ging. Würde mich wundern, wenns da einen Unterschied gäbe.

Autor: Jörg Wunsch (dl8dtl) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Stefan Frings wrote:

> Ich werde mich erstmal mit ungetc() auseinander setzen, ...

Das sollte für dich komplett transparent sein, solange du danach
wieder mit stdio-Mitteln auf die Daten zugreifst.  Das nächste
fgetc() (auf das greifen alle letztendlich zu) stellt dann fest,
dass im unget-Puffer ein Zeichen liegt und liest dieses, statt
die Backend-Funktion zum Lesen von der Hardware neu anzuwerfen.

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Stefan Frings wrote:

> Besonder coll finde ich auch den Effekt, daß beim Absturz mein flash
> verändert wird, obwohl ich in meinem Source Code keinen Einzigen
> schreibenden Zugriff vorgesehen habe und auch auch keinen Bootloader
> einsetze.

Dann verrate doch erstmal, um welche Architektur und um welches Derivat 
es überhaupt geht.

Falls es um einen AVR geht, könnte Brownout-Reset nicht schaden.
Und falls AVR mit externem Quarz ist full-swing Oszillator ne gute Idee.


Peter

Autor: Stefan Frings (sfrings)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ich nutze ein industriell gefertigtes Modul mit AVR ATmega168, 20Mhz 
Quartz, Brown-Out ist auf 2,7V eingestellt.

Und ja: Nach dem Absturz stimmt der Flash Speicher tatsächlich nicht 
mehr mit dem Ursprünglichen Inhalt überein.

Sobald ich scanf("%d",&i); durch dies ersetzte, tritt der Fehler nicht 
auf:

int i;
char buffer[20];
fgets(buffer,sizeof(buffer),stdin);
sscanf(buffer,"%d",&i);

Ich habe auch ein zweites Modul probiert - gleiches Ergebnis.

Autor: Benedikt K. (benedikt) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Befindet sich auf dem Modul zufällig ein Bootloader?
Ein Sprung in diesen durch einen nicht initialisierten Funktionspointer 
gefolgt von Daten über den UART könnte den Flash überschreiben.

Autor: Jörg Wunsch (dl8dtl) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Stefan Frings wrote:
> Ich nutze ein industriell gefertigtes Modul mit AVR ATmega168, 20Mhz
> Quartz, Brown-Out ist auf 2,7V eingestellt.

20 MHz sind aber nur bei 5 V (-10 % Toleranz) definiert, du müsstest
den Brownout also auf 4,3 V stellen.  Aber das sollte natürlich
trotzdem keine flash corruption bringen.

> Und ja: Nach dem Absturz stimmt der Flash Speicher tatsächlich nicht
> mehr mit dem Ursprünglichen Inhalt überein.

Was genau wird denn geändert?

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Stefan Frings wrote:
> Und ja: Nach dem Absturz stimmt der Flash Speicher tatsächlich nicht
> mehr mit dem Ursprünglichen Inhalt überein.

Dem sollte man auf den Grund gehen, wenn es sogar auf beiden AVRs 
passiert.

Schick dochmal einen kompletten Hex-Dump (alle 16kB) vom AVR vor und 
nach der Selbstzerstörung.


Peter

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.