Forum: Compiler & IDEs gcc-avr: sprintf_P kracht (mit 3 oder mehr Parameter)


von m_bedded (Gast)


Lesenswert?

Hallo Leute,

ich brauche eine frische Idee!

uCOS-II läuft auf mega128, WinAVR-20070525. Das Gerät hat 
Command-Line-Interface, da hantiere ich ziemlich viel mit printf und 
ROM-strings rum.

warum soll
1
sprintf_P(&PrnBuf[0], FMT_TABLEHEADER);
wunderbar laufen

und
1
sprintf_P(&PrnBuf[0], FMT_NAMEVALUE, value); /* name=%d */
das System abstürzen?

Es gibt ausreichend Stack (sieht man mit JTAG ICE). Es gibt nur 1 Task 
(bei diesem Test) und Rx/Tx UART-Fifos (Interrupt-gesteuert), die 
scheinbar ok sind, immerhin kann ich Tabellen-Köpfe und sonstigen 
multi-Kb ASCII-Art-Kram ganz ok printen.

Gibt es was besonderes über sprintf_P? Nutzt diese Funktion vielleicht 
den HEAP, den ich nicht organiziert habe? Oder einen globalen Bereich 
statt Task-Stack, womit mein uCOS-II nicht mehr klar kommt?

von Stefan B. (stefan) Benutzerseite


Lesenswert?

Wie sind FMT_TABLEHEADER und FMT_NAMEVALUE und value definiert? Wie 
gross ist PrnBuf?

von m_bedded (Gast)


Lesenswert?

1
char PrnBuf[256];
2
char FMT_TABLEHEADER[] PROGMEM ="*********\r\n";
3
char FMT_NAMEVALUE  [] PROGMEM =" PARAM1=%d\r\n";
4
uint8_t value;

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Warum nimmst du sprintf_P() wenn dein format string doch im RAM steht?
Nimm sprintf().  Wenn der Ziel-String im ROM steht, nimm %S statt %s
(und ignoriere GCCs Warnung über einen angeblichen wchar_t *, ich habe
noch nicht rausgefunden, wie man ihm das abgewöhnen kann).

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

p.s.: &prnBuf[0] ist eine umständliche und schwer verständliche
Ausdrucksform für prnBuf (zumindest in diesem Kontext).

von Karl H. (kbuchegg)


Lesenswert?

Jörg Wunsch wrote:
> Warum nimmst du sprintf_P() wenn dein format string doch im RAM steht?

Wie meinst du das?
Der OP schrieb:

char FMT_NAMEVALUE  [] PROGMEM =" PARAM1=%d\r\n";
uint8_t value;

sprintf_P(&PrnBuf[0], FMT_NAMEVALUE, value); /* name=%d */

ist m.M. doch in Ordnung? Oder hab ich grade ein Blackout?

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Ahem, sorry, ich habe das `s' in `sprintf' irgendwie überlesen...

Dann ist aber das Problem nicht nachvollziehbar.

von m_bedded (Gast)


Lesenswert?

Danke für eure Antworten!

Nun eine kleine Egänzung: werden die Interrupts vor sprintf_P()
gesperrt, funktioniert es einwandfrei:
1
OS_ENTER_CRITICAL();
2
sprintf_P(&PrnBuf[0], FMT_NAMEVALUE, value); /* name=%d */
3
OS_EXIT_CRITICAL();

Ich weiss, dass es ga-a-anz viel bei einem RTOS passieren kann. Aber wo 
ist es diesmal??

Wenn der Context-Switch alle Register und RAMPZ und dazu noch SPH und 
SPL speichert und wiederherstellt, wenn ich über 256 bytes Reserve im 
Task Stack habe, was passiert dieser Funktion (sodass sie kracht):
1
sprintf_P(&PrnBuf[0], FMT_NAMEVALUE, value); /* name=%d */
was dieser Funktion NICHT passiert:
1
sprintf_P(&PrnBuf[0], FMT_TABLEHEADER);
?

von Stefan B. (stefan) Benutzerseite


Lesenswert?

Interessant. Möglicherweise ist da ein Bug in der Codeerzeugung.

Aus diesem C-Code
1
#include <avr/io.h>
2
#include <avr/pgmspace.h>
3
#include <stdio.h>
4
5
char PrnBuf[256];
6
char FMT_TABLEHEADER[] PROGMEM ="*********\r\n";
7
char FMT_NAMEVALUE  [] PROGMEM =" PARAM1=%d\r\n";
8
uint8_t value = 42;
9
10
int main(void)
11
{
12
   sprintf_P(&PrnBuf[0], FMT_NAMEVALUE, value); /* name=%d */
13
   while(1);
14
}

Macht WinAVR-20070525 dieses Listing (hier für Atmega32). No Panic, 
nicht alles ist wichtig, aber man beachte die Zeilen 113 bis 120
1
   1                   .file  "test.c"
2
   2                   .arch atmega32
3
   3                 __SREG__ = 0x3f
4
   4                 __SP_H__ = 0x3e
5
   5                 __SP_L__ = 0x3d
6
   6                 __tmp_reg__ = 0
7
   7                 __zero_reg__ = 1
8
   8                   .global __do_copy_data
9
   9                   .global __do_clear_bss
10
  11                   .text
11
  12                 .Ltext0:
12
  91                 .global  main
13
  93                 main:
14
   1:test.c        **** #include <avr/io.h>
15
   2:test.c        **** #include <avr/pgmspace.h>
16
   3:test.c        **** #include <stdio.h>
17
   4:test.c        ****
18
   5:test.c        **** char PrnBuf[256];
19
   6:test.c        **** char FMT_TABLEHEADER[] PROGMEM ="*********\r\n";
20
   7:test.c        **** char FMT_NAMEVALUE  [] PROGMEM =" PARAM1=%d\r\n";
21
   8:test.c        **** uint8_t value = 42;
22
   9:test.c        ****
23
  10:test.c        **** int main(void)
24
  11:test.c        **** {
25
  94                 abn  68,0,12,.LM1-main
26
  95                 .LM1:
27
  96                   lds r24,value
28
  97                   clr r25
29
  12:test.c        ****    sprintf_P(&PrnBuf[0], FMT_NAMEVALUE, value); /* name=%d */
30
  98                 tabn  68,0,12,.LM1-main
31
  99                 .LM1:
32
 100 0000 8091 0000     lds r24,value
33
 101 0004 9927          clr r25
34
 102 0006 9F93          push r25
35
 103 0008 8F93          push r24
36
 104 000a 80E0          ldi r24,lo8(FMT_NAMEVALUE)
37
 105 000c 90E0          ldi r25,hi8(FMT_NAMEVALUE)
38
 106 000e 9F93          push r25
39
 107 0010 8F93          push r24
40
 108 0012 80E0          ldi r24,lo8(PrnBuf)
41
 109 0014 90E0          ldi r25,hi8(PrnBuf)
42
 110 0016 9F93          push r25
43
 111 0018 8F93          push r24
44
 112 001a 0E94 0000     call sprintf_P
45
 113 001e 8DB7          in r24,__SP_L__
46
 114 0020 9EB7          in r25,__SP_H__
47
 115 0022 0696          adiw r24,6
48
 116 0024 0FB6          in __tmp_reg__,__SREG__
49
 117 0026 F894          cli
50
 118 0028 9EBF          out __SP_H__,r25
51
 119 002a 0FBE          out __SREG__,__tmp_reg__
52
 120 002c 8DBF          out __SP_L__,r24
53
 121                 .L2:
54
 122 002e 00C0          rjmp .L2
55
 123                 /* epilogue: frame size=0 */
56
 124                 /* epilogue: noreturn */
57
 125                 /* epilogue end (size=0) */
58
 126                 /* function main size 24 (24) */
59
 128                 .Lscope0:
60
 129                 .global  FMT_TABLEHEADER
61
 130                   .section  .progmem.data,"a",@progbits
62
 133                 FMT_TABLEHEADER:
63
 134 0000 2A2A 2A2A     .string  "*********\r\n"
64
 134      2A2A 2A2A
65
 134      2A0D 0A00
66
 135                 .global  FMT_NAMEVALUE
67
 138                 FMT_NAMEVALUE:
68
 139 000c 2050 4152     .string  " PARAM1=%d\r\n"
69
 139      414D 313D
70
 139      2564 0D0A
71
 139      00
72
 140                 .global  value
73
 141                   .data
74
 144                 value:
75
 145 0000 2A            .byte  42
76
 146                   .comm PrnBuf,256,1
77
 151                   .text
78
 153                 .Letext0:
79
 154                 /* File "test.c": code   24 = 0x0018 (  24), prologues   0, epilogues   0 */
80
DEFINED SYMBOLS
81
                            *ABS*:00000000 test.c
82
  f:\temp/ccLURB1Z.s:3      *ABS*:0000003f __SREG__
83
  f:\temp/ccLURB1Z.s:4      *ABS*:0000003e __SP_H__
84
  f:\temp/ccLURB1Z.s:5      *ABS*:0000003d __SP_L__
85
  f:\temp/ccLURB1Z.s:6      *ABS*:00000000 __tmp_reg__
86
  f:\temp/ccLURB1Z.s:7      *ABS*:00000001 __zero_reg__
87
  f:\temp/ccLURB1Z.s:93     .text:00000000 main
88
  f:\temp/ccLURB1Z.s:144    .data:00000000 value
89
  f:\temp/ccLURB1Z.s:138    .progmem.data:0000000c FMT_NAMEVALUE
90
                            *COM*:00000100 PrnBuf
91
  f:\temp/ccLURB1Z.s:133    .progmem.data:00000000 FMT_TABLEHEADER
92
93
UNDEFINED SYMBOLS
94
__do_copy_data
95
__do_clear_bss
96
sprintf_P

In den Zeilen 113 bis 120 soll der Stack aufgeräumt werden, indem der 
Stackpointer auf den Wert gesstzt wird, den er vor dem Ablegend der 
Argumente von sprintf_P hatte.

Da es sich um einen atomaren Zugriff auf SPH und SPL handeln muss, 
müssen die Interrupts gesperrt werden.

ABER die Interrupts werden IMHO eine Anweisung zu früh freigegeben. 
Das untere Byte des Spackpointers ist noch nicht restauriert.

So sähe es für mich sinnvoller aus
1
 116 0024 0FB6          in __tmp_reg__,__SREG__
2
 117 0026 F894          cli
3
 118 0028 9EBF          out __SP_H__,r25
4
 119 002c 8DBF          out __SP_L__,r24
5
 120 002a 0FBE          out __SREG__,__tmp_reg__

Eigentlich ist IMHO schon das Sperren der Interrupts (Zeile 116-117) vor 
dem Auslesen des 16-Bit Stackpointers (Zeile 113-115) sinnvoll, wenn man 
AVR072: Accessing 16-Bit Registers beachtet.

von Stefan B. (stefan) Benutzerseite


Angehängte Dateien:

Lesenswert?

ADD: Das Makefile zum vorhergehenden Post

von Oliver (Gast)


Lesenswert?

Du suchst an der falchen Ecke. sprintf, und die avr-libc funktionieren 
ohne das RTOS einwandfrei.

Dein Problem der Zeilen 113ff ist keins:
Beitrag "Re: Compilerbug bei AVR-GCC ?: Stack Pointer und Interrupts"

Um das Problem zu lösen, wäre ein anständiger Simulator hilfreich. 
Leider sind die sourcen des RTOS nicht im Netz zu finden, sonst könnte 
man es mal in VMLAB laufen lassen.

Oliver

von Stefan B. (stefan) Benutzerseite


Lesenswert?

Du meinst den Abschnitt von Jörg
1
Nein, das ist kein Problem.  Auch wenn es nirgends dokumentiert ist,
2
hat Atmel dem Compilerschreiber mal irgendwann bestätigt, dass die
3
Interruptfreigabe in der Tat und garantiert erst einen Befehl
4
verspätet erfolgt, d.h. der Befehl direkt nach einer Zuweisung an das
5
SREG, bei dem das I-Bit wieder gesetzt wird, wird selbst bei einem
6
anhängigen Interrupt noch ausgeführt.
Beitrag "Re: Compilerbug bei AVR-GCC ?: Stack Pointer und Interrupts"

Und "Why are interrupts re-enabled in the middle of writing the stack 
pointer?" in der FAQ der avr-libc?
http://www.nongnu.org/avr-libc/user-manual/FAQ.html#faq_spman

Beim SEI ist das Verhalten bzgl. des unmittelbar folgenden ASM-Befehls 
im Manual "8-Bit AVR Instruction Set" dokumentiert (SEI - Sets the 
Global Interrupt Flag (I) in SREG (Status Register). The instruction 
following SEI will be executed before any pend­ing interrupts.)

Eine entsprechende Doku von Atmel beim OUT SREG,... habe ich nicht 
gesehen und so bleibt meine Grundskepsis ;-)

Es wäre mal interessant, ob ein gepatchtes Binary (out 
_SREG_,__tmp_reg__ zu SEI gepatcht) die gleichen Probleme zeigt. 
Leider habe ich kein Binary, welches die Probleme hat, aber vielleicht 
kann m_bedded den Test machen?

von Stefan B. (stefan) Benutzerseite


Lesenswert?

BTW. Was passiert eigentlich bei sprintf_P mit nur zwei Parametern?

Bei nur zwei Parametern wird obige Methode zur Stackkorrektur nicht 
verwendet. Stattdessen werden klassische POPs gemacht (Zeile 107-110).
1
   1                   .file  "test.c"
2
   2                   .arch atmega32
3
   3                 __SREG__ = 0x3f
4
   4                 __SP_H__ = 0x3e
5
   5                 __SP_L__ = 0x3d
6
   6                 __tmp_reg__ = 0
7
   7                 __zero_reg__ = 1
8
   8                   .global __do_copy_data
9
   9                   .global __do_clear_bss
10
  11                   .text
11
  12                 .Ltext0:
12
  91                 .global  main
13
  93                 main:
14
   1:test.c        **** #include <avr/io.h>
15
   2:test.c        **** #include <avr/pgmspace.h>
16
   3:test.c        **** #include <stdio.h>
17
   4:test.c        ****
18
   5:test.c        **** char PrnBuf[256];
19
   6:test.c        **** char FMT_TABLEHEADER[] PROGMEM ="*********\r\n";
20
   7:test.c        **** char FMT_NAMEVALUE  [] PROGMEM =" PARAM1=%d\r\n";
21
   8:test.c        **** uint8_t value = 42;
22
   9:test.c        ****
23
  10:test.c        **** int main(void)
24
  11:test.c        **** {
25
  94                 abn  68,0,12,.LM1-main
26
  95                 .LM1:
27
  96                   ldi r24,lo8(FMT_TABLEHEADER)
28
  97                   ldi r25,hi8(FMT_TABLEHEADER)
29
  12:test.c        ****    sprintf_P(&PrnBuf[0], FMT_TABLEHEADER); /* name=%d */
30
  98                 T_TABLEHEADER)
31
  99                   ldi r25,hi8(FMT_TABLEHEADER)
32
 100 0000 80E0          push r25
33
 101 0002 90E0          push r24
34
 102 0004 9F93          ldi r24,lo8(PrnBuf)
35
 103 0006 8F93          ldi r25,hi8(PrnBuf)
36
 104 0008 80E0          push r25
37
 105 000a 90E0          push r24
38
 106 000c 9F93          call sprintf_P
39
 107 000e 8F93          pop __tmp_reg__
40
 108 0010 0E94 0000     pop __tmp_reg__
41
 109 0014 0F90          pop __tmp_reg__
42
 110 0016 0F90          pop __tmp_reg__
43
 111 0018 0F90        .L2:
44
 112 001a 0F90          rjmp .L2
45
 113                 /* epilogue: frame size=0 */
46
 114 001c 00C0        /* epilogue: noreturn */
47
 115                 /* epilogue end (size=0) */
48
 116                 /* function main size 15 (15) */
49
 118                 .Lscope0:
50
 119                 .global  FMT_TABLEHEADER
51
 120                   .section  .progmem.data,"a",@progbits
52
 123                 FMT_TABLEHEADER:
53
 124                   .string  "*********\r\n"
54
 125                 .global  FMT_NAMEVALUE
55
 128                 FMT_NAMEVALUE:
56
 129                   .string  " PARAM1=%d\r\n"
57
 130                 .global  value
58
 131 000c 2050 4152     .data
59
 131      414D 313D
60
 131      2564 0D0A
61
 131      00
62
 134                 value:
63
 135                   .byte  42
64
 136                   .comm PrnBuf,256,1
65
 141                   .text
66
 143                 .Letext0:
67
 144                 /* File "test.c": code   15 = 0x000f (  15), prologues   0, epilogues   0 */
68
 145                 ...
69
DEFINED SYMBOLS
70
                            *ABS*:00000000 test.c
71
  f:\temp/ccV2wNZ4.s:3      *ABS*:0000003f __SREG__
72
  f:\temp/ccV2wNZ4.s:4      *ABS*:0000003e __SP_H__
73
  f:\temp/ccV2wNZ4.s:5      *ABS*:0000003d __SP_L__
74
  f:\temp/ccV2wNZ4.s:6      *ABS*:00000000 __tmp_reg__
75
  f:\temp/ccV2wNZ4.s:7      *ABS*:00000001 __zero_reg__
76
  f:\temp/ccV2wNZ4.s:93     .text:00000000 main
77
  f:\temp/ccV2wNZ4.s:125    .progmem.data:00000000 FMT_TABLEHEADER
78
                            *COM*:00000100 PrnBuf
79
  f:\temp/ccV2wNZ4.s:130    .progmem.data:0000000c FMT_NAMEVALUE
80
  f:\temp/ccV2wNZ4.s:136    .data:00000000 value
81
82
UNDEFINED SYMBOLS
83
__do_copy_data
84
__do_clear_bss
85
sprintf_P

von Stefan B. (stefan) Benutzerseite


Lesenswert?

Stefan "stefb" B. wrote:
> Eigentlich ist IMHO schon das Sperren der Interrupts (Zeile 116-117) vor
> dem Auslesen des 16-Bit Stackpointers (Zeile 113-115) sinnvoll, wenn man
> AVR072: Accessing 16-Bit Registers beachtet.

Das ist Unfug. Da habe ich nicht ausreichend überlegt ;-)

von Stefan B. (stefan) Benutzerseite


Lesenswert?

Oliver wrote:
> Um das Problem zu lösen, wäre ein anständiger Simulator hilfreich.
> Leider sind die sourcen des RTOS nicht im Netz zu finden, sonst könnte
> man es mal in VMLAB laufen lassen.

Grundsätzlich kann man die Sourcen zur Evaluation bekommen und zwar 
FREE. Allerdings muss man sich bei http://www.micrium.com/ registrieren 
und kann dann 45 Tage testen.

von Oliver (Gast)


Lesenswert?

>Grundsätzlich kann man die Sourcen zur Evaluation bekommen

Hab mich mal registriert, und mir das mal angesehen. Vielleicht brauch 
ich ja auch mal ein uCOS auf eine AVR, wenn ich bisher auch nicht 
wüsste, wofür.

Das sieht allerdings schlecht aus.
Downloadbar sind ist nur die aktuelle V 2.85, und für die gibt es keinen 
avr-gcc port. Die verfügbaren gcc-ports benötigen v2.70, dafür gibt es 
die sourcen nicht mehr zum download.

Das es vom avr-gcc-port eine aktuellere Version auf
https://www.ee.lut.fi/staff/Julius.Luukko/ucos-ii/avr/download.shtml
gibt, hast du aber mitbekommen? Die Vorversion hatte da einen bug
https://www.ee.lut.fi/staff/Julius.Luukko/ucos-ii/avr/bugs.shtml

Wie gesagt, wenn du alle sourcen hast, wäre ein Versuch mit VMLAB nicht 
verkehrt. Das gibts im Netz, ist nicht ganz fertig, aber das, was geht, 
ist ganz ok. Und weil die VMLAB-Entwickler die Stelle zu OUT SREG in den 
AVR-DOCS auch nicht gefunden haben, simuliert VMLAB das auch nicht 
richtig. Abhilfe schafft

Beitrag "VMLAB Interrupt zu früh, Stackpointer falsch"

Oliver

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

m_bedded wrote:

> ..., was passiert dieser Funktion (sodass sie kracht):
>
1
sprintf_P(&PrnBuf[0], FMT_NAMEVALUE, value); /* name=%d */
> was dieser Funktion NICHT passiert:
>
1
sprintf_P(&PrnBuf[0], FMT_TABLEHEADER);
> ?

Ab da werden die Argumente vom Stack bewertet.  Die ersten beiden
Argumente werden in Registern übergeben.

von Stefan B. (stefan) Benutzerseite


Lesenswert?

Jörg Wunsch wrote:
> Ab da werden die Argumente vom Stack bewertet.  Die ersten beiden
> Argumente werden in Registern übergeben.

Das sehe ich anders, zumindest bei meinen beiden Übersetzungen oben.

In meinen beiden Assemblerlistings für 3 bzw. 2 Argumente werden niemals 
Argumente in den Registern übergeben. Alle Argumente werden per PUSH auf 
den Stack gegeben. Die verwendete Optimierung war -Os.

Bei einer Funktion mit variabler Argumentliste wie es sprintf_P ist, 
macht das IMHO auch Sinn.

Der Unterschied zwischen 3 und 2 Argumenten ist die Art, wie die 
Argumente wieder vom Stack abgereinigt werden - bei 2 Argumenten mit POP 
und bei 3 Argumenten mit einer Stackpointermanipulation inklusive 
Interruptsperrung.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Ich bezog die zwei Argumente auf die Adresse des Puffers und den
Formatstring.  Die sind noch nicht variabel.

Kann aber auch sein, dass es durch die unterschiedliche Methode
der Übertragung auf den Stack ist.

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.