Forum: Mikrocontroller und Digitale Elektronik ARM printf geht nicht


von Star K. (starkeeper)


Lesenswert?

Hi,
ich bin auf den ARM "gekommen" und experimentiere nun schon einige 
Wochen damit rum. Ich nutze einen STR710 von STM. Als 
Entwiklcungsumgebung kommt Windows mit Yagarto und Codeblocks zum 
Einsatz.

Jetzt wollte ich mich dran machen die C Funktionen wie printf zu 
benutzen. Dazu habe ich der libc.a die syscalls entzogen und diese in 
meinem Code deklariert. Alles lässt sich auch wunderbar kompilieren und 
linken. Er meldet keine nicht aufgelösten Funktionen, also alles 
wunderbar.

Doch wenn ich nun printf aufrufe hängt sich der Debugger bzw. der IC 
auf. Dabei ist es unerheblich, wie kurz der Text ist, den printf 
ausgeben soll. Ich habe dann mal den arm-elf-insight debugger genommen, 
dieser kann auch durch den ASM-Code der libc steppen und habe 
herausgefunden das nach vielen Unterfunktionsaufrufen das printf in der 
funktion exit landet, die ja bekanntlich eine endlosschleife ist.

Ich bin nicht der ASM freak, und frage mich nun warum diese Funktion 
aufgerufen wird?

mfg starkeeper

von Stefan B. (stefan) Benutzerseite


Lesenswert?

Du könntest den printf() Aufruf bzw. deine Source angeben - dann könnte 
man nachsehen, ob da eine Fehlbedienung deinerseits zu einer 
Abbruchbedingun führt.

Oder - wenn dein Code hochgeheim ist - könntest du die libc mit 
Debuginfos erstellen und ein Debugging auf C-Sourcelevel machen. Dabei 
sollte man sehen, wo und warum printf in exit landet.

von Star K. (starkeeper)


Lesenswert?

Also mit sourcen kann ich gerade nicht dienen, da ich nicht an meinem 
Heim-Rechner sitze.
Aber der Fehler taucht auf jeden Fall auch dann auf, wenn ich nur eine 
main-Funktion habe und dort dann printf("A"); drin steht.

void main(void)
{
 printf("A");
 while(1);
}

Bis zu der while-Schleife gelangt der Controller nicht.

Ich benutze ein Linker-Script von STR, aber soweit ich das gesehen habe, 
werden darin alle notwendigen bereiche angelegt, die man benötigt für 
stack usw..

Die Library mit Debug-Infos zu kompilieren habe ich auch schon geplant. 
Dazu muss ich mir erstmal ein Linux laden. Oder kannst du mir eine 
fertige config-Datei geben, mit der ich unter Windows das ganz 
kompilieren kann?

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

> Dazu habe ich der libc.a die syscalls entzogen und diese in
> meinem Code deklariert.

Klingt das nicht irgendwie suspekt?

von Star K. (starkeeper)


Lesenswert?

Rufus t. Firefly wrote:
>> Dazu habe ich der libc.a die syscalls entzogen und diese in
>> meinem Code deklariert.
>
> Klingt das nicht irgendwie suspekt?

Also das scheint gängige Praxis zu sein, das hab ich aus diesem Thread 
hier:
http://forum.sparkfun.com/viewtopic.php?p=31660&sid=59221e0599e04e799d88abe680327037

Entweder man strippt die libc oder was wohl auch manchmal geht, dass man 
dem Linker nen Parameter übergibt, der sagt das die alten syscalls nicht 
gelinkt werden sollen.

von Star K. (starkeeper)


Angehängte Dateien:

Lesenswert?

Also ich habe das Problem nun einkreisen können.
Es dreht sich um die Funktion sbrk.

register char *stack_ptr asm ("sp");
caddr_t _sbrk_r(void *reent, size_t incr)
{
    extern char end asm ("end"); // Defined by the linker
    static char *heap_end;
    char *prev_heap_end;

    if( heap_end == NULL )
        heap_end = &end;

    prev_heap_end = heap_end;

    if(( heap_end + incr ) > stack_ptr )
    {
        exit(1);
        return (caddr_t) -1;
    }

    heap_end += incr;
    return (caddr_t) prev_heap_end;
}

Das Problem ist das kein Stack bzw Heap mehr frei zu sein scheint. Was 
aber nicht sein kann, da der noch ungenutzt ist zu der zeit. Auch das 
vergrössern des Stacks und des Heap bringt keinen erfolg, sollte es aber 
ja.

Ich vermute das Problem steckt in meinem linker script, das ich mal 
angehängt habe. Die Pointer von Heap_end und stack liegen nur 40byte 
auseinander, sodass kein platz mehr zu sein scheint! Vergrössere ich nun 
die stack grösse, verscheiben sich zwar die pointer aber die entfernung 
von beiden zueinander ändert sich nicht. Ich vermute das entweder SP 
oder END nicht korrekt sind.

von Stefan B. (stefan) Benutzerseite


Lesenswert?

> Heap_end und stack liegen nur 40byte auseinander

Kannst du genauer ausführen, welche Symbole du meinst. Die mit obiger 
Schreibweise finde ich nicht.

Wenn du _heap_end__ und __stack_start_ meinst, ist das ja nicht 
kritisch.

Mir scheint aber, dass deine angegebene sbrk Funktion nicht zum 
angegebenen Linkercontrolscript passt. Woher stammen diese?

Das Skript baut einen HEAPSIZE grossen HEAP zwischen Datenbereich und 
Stackbereich auf. Die Schlüsselsymbole sind _heap_end_ und 
_heap_start_ als Endadresse und Anfangsadresse dieses Bereichs. Ich 
hätte erwartet, dass eine passende sbrk Funktion diese Symbole 
verwendet.

Die Funktion meint die Endadresse der letzten Varuiable zu kennen (end) 
und den aktuellen Stackpointer (sp). Den Platz dazwischen sieht die 
Funktion als potentiellen HEAP an. Aus dem Bauch raus, würde ich sagen: 
Keine gute Idee.

von Star K. (starkeeper)


Lesenswert?

Stefan B. wrote:
>> Heap_end und stack liegen nur 40byte auseinander
>
> Kannst du genauer ausführen, welche Symbole du meinst. Die mit obiger
> Schreibweise finde ich nicht.
>
> Wenn du _heap_end__ und __stack_start_ meinst, ist das ja nicht
> kritisch.
>
> Mir scheint aber, dass deine angegebene sbrk Funktion nicht zum
> angegebenen Linkercontrolscript passt. Woher stammen diese?
>
> Das Skript baut einen HEAPSIZE grossen HEAP zwischen Datenbereich und
> Stackbereich auf. Die Schlüsselsymbole sind _heap_end_ und
> _heap_start_ als Endadresse und Anfangsadresse dieses Bereichs. Ich
> hätte erwartet, dass eine passende sbrk Funktion diese Symbole
> verwendet.
>
> Die Funktion meint die Endadresse der letzten Varuiable zu kennen (end)
> und den aktuellen Stackpointer (sp). Den Platz dazwischen sieht die
> Funktion als potentiellen HEAP an. Aus dem Bauch raus, würde ich sagen:
> Keine gute Idee.

Das Linker script kommt von STM. Die sbrk Funktion kommt aus dem 
Internet.. Ich hab das auch gerade gesehen, das mein Linkerskript einen 
heap deklariert. Ich hab nun die symbole verwendet und sbrk 
umgeschrieben:
register char *stack_ptr asm ("sp");
caddr_t _sbrk_r(void *reent, size_t incr)
{
    extern char start asm ("__heap_start__"); // Defined by the linker
    extern char end asm ("__heap_end__"); // Defined by the linker
    static char *heap_data_end;
    char *prev_heap_data_end;
    char *heap_end = &end;

    if( heap_data_end == NULL )
        heap_data_end = &start;

    prev_heap_data_end = heap_data_end;

    if(( heap_data_end + incr ) > heap_end )
    {
        // Some of the libstdc++-v3 tests rely upon detecting
        // out of memory errors, so do not abort here.
        exit(1);
        return (caddr_t) -1;
    }

    heap_data_end += incr;
    return (caddr_t) prev_heap_data_end;
}

Jetzt funktioniert das, den Heap musste ich aber auf 2048Byte erhöhen, 
damit überhaupt nur eine Anfrage bearbeitet werden konnte.

Das Problem ist jetzt das immer mehr vom Heap genutzt wird. Der eine 
Printf Aufruf: printf("A"); soll schon mehr als 20048Byte heap 
verbrauchen!! Ich kenne das so das Heap und stack maximal 2K brauchen. 
Was läuft denn da nun schief?

Die erste Anfrage an den Heap klaut 0x408Bytes die nächst will dann 
schon 0xc8cBytes haben! Und da hat printf nichtmal ein Zeichen 
ausgegeben?!

von Stefan B. (stefan) Benutzerseite


Lesenswert?

Schwierig, schwierig. Ich sehe in der printf der NEWLIB eigentlich nur 
an zwei Stellen mallocs.

Beide Stellen sind, glaube ich, nur aktiv, wenn ein Formatbezeichner 
(nach dem % im Formatstring) geparst wird.

Bei deinem Beispiel printf("A") sollte überhaupt kein malloc aufgerufen 
werden. Mir ist das schleierhaft.

Je nach Debugmöglichkeiten ggf. einen Haltepunkt auf malloc oder sbrk 
legen und per Stacktrace anschauen, wer der Aufrufer ist...

von Star K. (starkeeper)


Lesenswert?

Wie wird denn bei dir das printf aufgerufen?

Bei mir ist das etwas komisch finde ich. Der Compiler ersetzt printf mit 
putchar und das wird dann ausgeführt? Es scheint kein echtes printf zu 
geben..

von holger (Gast)


Lesenswert?

printf benötigt eine Ausgaberoutine putchar.
In putchar wird festgelegt wohin die Ausgabe
gehen soll. Auf UART0, UART1, LCD oder sonst wo hin.
Dafür benötigt putchar die Adresse einer Ausgaberoutine !

Bei WinARM:

 rprintf_devopen( uart_sendchar ); /* init rprintf */

uart_sendchar wird dann von putchar benutzt um Zeichen
auszugeben. Wenn für putchar keine Ausgaberoutine
festgelegt wird könnte das einen Sprung auf NULL,
also ins leere bedeuten. Hangman.

von Star K. (starkeeper)


Lesenswert?

holger wrote:
> printf benötigt eine Ausgaberoutine putchar.
> In putchar wird festgelegt wohin die Ausgabe
> gehen soll. Auf UART0, UART1, LCD oder sonst wo hin.
> Dafür benötigt putchar die Adresse einer Ausgaberoutine !
>
> Bei WinARM:
>
>  rprintf_devopen( uart_sendchar ); /* init rprintf */
>
> uart_sendchar wird dann von putchar benutzt um Zeichen
> auszugeben. Wenn für putchar keine Ausgaberoutine
> festgelegt wird könnte das einen Sprung auf NULL,
> also ins leere bedeuten. Hangman.

Welche version der newlib wird bei WinARM genutzt? Das sollte doch 
eigentlich immer gleich funktionieren.

Nach meinem wissen von anderen Librarys, wird doch normal printf 
aufgerufen was dann den string parst und die ersetzungen macht und dann 
mittels putchar die einzelnen buchstaben ausgegeben, wohin man sie haben 
will. Das muss man natürlich noch angeben.

Das Rätsel um die ersetzung von printf habe ich fast gelöst, also immer 
wenn ich nur einen Buschtaben ausgebe wird printf ersetzt durch putch. 
Bei mehreren Zeichen wird printf genommen. Das printf funktioniert nun 
auch korrekt, nachdem ja nun der heap korrekt angegeben ist in sbrk.

Der Speicherverbrauch vom heap ist immer noch enorm. Beim aufruf von 
printf("ABC") wird einmal 0x418 speicher angefordert und ein zweites mal 
0x48.

von holger (Gast)


Lesenswert?

>Welche version der newlib wird bei WinARM genutzt?

Für rprintf gar keine. Das ist eine simple printf
Routine die man als C Datei einbindet. Kann kein
float, aber das meiste was man sonst so braucht.

Sollte nur ein Tip sein :(

Das printf durch puts ersetzt wird wenn kein
Formatstring vorliegt habe ich ja auch schon gesehen,
aber direkt auf putchar ? Nö, das kann nicht klappen.

von Martin T. (mthomas) (Moderator) Benutzerseite


Lesenswert?

Das genannte rprintf hat nichts mit newlib-stdio zu tun. Es wird nur in 
einigen Beispielen von mir genutzt (die ich auch WinARM beilege). Es 
handelt sich dabei um eine "selbstgebastelte" Funktion, die ich in 
AVR-Code (wenn richtig erinnert von Holger Klabunde und Volker Oth) 
gefunden und an ARM angepasst habe (eigentlich ist nichts 
ARM-spezifisches an der Routine). Inspiriert durch die in der avr-libc 
verwendete Methodik habe ich später dann noch rprintf_devopen ergänzt. 
Für viele Anwendungen reicht ein solche Funktion aus und dynamische 
Speicherverwaltung wird im Gegensatz zu newlibs printf/iprintf nicht 
benötigt.

Dass printf Anweisungen für kurze Zeichenketten zu putchar werden, kann 
eine Optimierung des GNU Compilers sein. Neuere Versionen von arm-*-gcc 
scheinen sich printf "genauer anzuschauen" und möglicherweise dabei 
diese Optimierungsmöglichkeit erkennen. Habe das in eigenen Projekten 
allerdings noch nicht beobachtet (aber auch nicht danach gesucht).

"soll schon mehr als 20048Byte heap verbrauchen!!"
Das bezweifle ich. Vielleicht ist hier .text gemeint. Das kann schon gut 
sein, denn die newlib stdio-Funktionen brauchen recht viel 
Programmspeicher. Etwas lindern kann man das, wenn man statt printf 
iprintf verwendet, welches aber keine Floating-Point-Ausgabe unterstützt 
(i für integer).

Der Bedarf an Speicher auf dem Heap kann auch daher rühren, dass die 
newlib stdio-Funktionen Buffering unterstützen. Testweise könnte man 
dies deaktivieren (mit ioctrl(?)). Evtl. wird dann ein malloc 
"ausgelassen".

Martin Thomas

von Martin T. (mthomas) (Moderator) Benutzerseite


Lesenswert?

Hoppla, mal wieder zu langsam getippt...

von Star K. (starkeeper)


Lesenswert?

Jo also das mit dem Heapverbrauch ist mir immernoch schleierhaft.. Bei 
verwendung von iprintf wird es aber deutlich weniger.

Da ich vorerst nur printf nutzen wollte, werde ich mir wohl mal dein 
rprintf ansehen.

Die Ersetzung von printf durch putch geschieht nur bei einem Zeichen, 
sobald im printf zwei zeichen ausgegeben werden sollen wird printf 
verwendet. Ich dachte aber wenn die Optimierung abgeschaltet ist werden 
solche Ersetzungen nicht gemacht.

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.