Forum: Compiler & IDEs avr gcc, xmega 128a4, switch-Anweisung


von Wolfgang (Gast)


Lesenswert?

Hallo,

ich kämpfe da gerade mit einem 'Absturz' meines C-Codes rum. Die CPU ist 
ein AVR atxmega128a4u, Compilerversion ist 4.9.2 (das was man halt 
aktuell bei Microchip runterladen kann), der Code läuft in der 
App-Section, Füllgrad von Flash und Data unter 50%.

Die fragliche Stelle ist eine switch-Anweisung:
1
int run_bridge_interface(void)
2
  {
3
    unsigned char rxdat;
4
    switch (bridge_if_state)
5
      {
6
        default: 
7
            return(-1);
8
        case BIF_START:
9
            bif_init_uart();
10
            bif_enable_rx();
11
            bridge_if_state = BIF_GET_LENGTH;
12
            return(0);
13
        case BIF_GET_LENGTH:
14
         usw......
wobei bridge_if_state wie folgt definiert ist:
1
typedef enum {BIF_START,      
2
              BIF_GET_LENGTH,
3
              BIF_COLLECT_MESSAGE,
4
              BIF_CHECK_TX,
5
              BIF_WF_TXCOMPLETE,
6
              BIF_CLEAN_UP,
7
              BIF_BROKEN              }  t_bridge_if_state;

Im Debugger sehe ich, dass der Prozessor bei der switch-Anweisung ins 
Nirvana springt. Tante googel flüstert mir was von einem 'alten' Bug in 
tablejump, deshalb habe ich dann mal die Compileroption -fno-jump-tables 
ausprobiert und siehe da, das Switch-Statement geht jetzt.

Kann ich das noch genauer einkreisen / beschreiben, mache ich hier 
selbst einen Fehler (DAU?) und falls es ein Bug in gcc ist, wo kann ich 
das melden?

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


Lesenswert?

Wolfgang schrieb:
> Tante googel flüstert mir was von einem 'alten' Bug in tablejump

Referenz für dieses Geflüster?

Das Einzige, was mir da in den Sinn käme ist das bekannte Problem,
wenn der Linker mitten in eine Sprungtabelle ein "Trampolin"
reinknallt.  Das kann aber erst bei Controllern mit mehr als 128 KiB
Flash passieren (und dort auch nur einmal an jedem Vielfachen von
128 KiB).  Damit fällt diese Variante bei dir aus.

Vorschlag: mach Nägel mit Köpfen und poste einen compilierbaren Code,
an dem man das Problem erkennen kann.  Meistens sitzt das Problem am
Ende doch vor der Tastatur und nicht im Compiler …

von Wolfgang (Gast)


Lesenswert?

Hallo,

ich würde es ja auch annehmen, dass das Problem vor der Tastatur ist ... 
bei google hatte ich nach 'avr bug tablejump' gesucht und Einträge 
gefunden, die auf die 64k Grenze zurückzuführen sind. Das hat mich dazu 
gebracht, einfach mal -fno-jump-tables auszuprobieren.

Das komplette Projekt will ich nicht posten, aber ich kann mal die 
Ausschnitte aus dem lss-File einkopieren:

Mit Jumptable:
1
int run_bridge_interface(void)
2
  {
3
    204c:  cf 93         push  r28
4
    204e:  df 93         push  r29
5
    unsigned char rxdat;
6
    switch (bridge_if_state)
7
    2050:  80 91 c9 20   lds  r24, 0x20C9  ; 0x8020c9 <bridge_if_state>
8
    2054:  90 e0         ldi  r25, 0x00  ; 0
9
    2056:  87 30         cpi  r24, 0x07  ; 7
10
    2058:  91 05         cpc  r25, r1
11
    205a:  08 f0         brcs  .+2        ; 0x205e <run_bridge_interface+0x12>
12
    205c:  5d c1         rjmp  .+698      ; 0x2318 <run_bridge_interface+0x2cc>
13
    205e:  fc 01         movw  r30, r24
14
    2060:  88 27         eor  r24, r24
15
    2062:  e2 50         subi  r30, 0x02  ; 2
16
    2064:  ff 4f         sbci  r31, 0xFF  ; 255
17
    2066:  8f 4f         sbci  r24, 0xFF  ; 255
18
    2068:  0c 94 1f 63   jmp  0xc63e  ; 0xc63e <__tablejump2__>

Ohne:
1
int run_bridge_interface(void)
2
  {
3
    unsigned char rxdat;
4
    switch (bridge_if_state)
5
    1f32:  c5 30         cpi  r28, 0x05  ; 5
6
    1f34:  09 f4         brne  .+2        ; 0x1f38 <run_bridge_interface+0x68>
7
    1f36:  25 c1         rjmp  .+586      ; 0x2182 <run_bridge_interface+0x2b2>
8
    1f38:  08 f4         brcc  .+2        ; 0x1f3c <run_bridge_interface+0x6c>
9
    1f3a:  1a c1         rjmp  .+564      ; 0x2170 <run_bridge_interface+0x2a0>
10
    1f3c:  c6 30         cpi  r28, 0x06  ; 6
11
    1f3e:  09 f0         breq  .+2        ; 0x1f42 <run_bridge_interface+0x72>
12
    1f40:  32 c1         rjmp  .+612      ; 0x21a6 <run_bridge_interface+0x2d6>
13
            set_connect_status(BIF_ERROR);
14
            bridge_if_state = BIF_BROKEN;
15
            return(0);
16
            break;        
17
        case BIF_BROKEN:
Und dann hangelt sich der Compiler halt so durch das switch-Statement 
durch ...

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


Lesenswert?

Wolfgang schrieb:

> ich würde es ja auch annehmen, dass das Problem vor der Tastatur ist ...
> bei google hatte ich nach 'avr bug tablejump' gesucht und Einträge
> gefunden, die auf die 64k Grenze zurückzuführen sind.

Habe mir das nochmal angesehen.  Ja, 64 KiB könnte passen, allerdings
werden die Sprungtabellen aus ebendiesem Grunde vom Linkerscript
normalerweise direkt hinter die Vektortabelle gelegt.  Das wird also
nur dann ein Problem, wenn du insgesamt mehr als knapp 64 KiB an
Sprungtabellen hast.

Das ist bei dir ganz bestimmt nicht der Fall. ;-)

Davon abgesehen: bei mir benutzt tablejump nur r30/r31.  Bei dir
wird auch r24 noch gelöscht.  Das deutet daraufhin, dass es sogar
mehr als 64 KiB behandeln kann.

> Das komplette Projekt will ich nicht posten, aber ich kann mal die
> Ausschnitte aus dem lss-File einkopieren:

(Disassembly ohne eingestreuten Sourcecode ist einfacher zu lesen.)


>     2050:  80 91 c9 20   lds  r24, 0x20C9  ; 0x8020c9 <bridge_if_state>
>     2054:  90 e0         ldi  r25, 0x00  ; 0
>     2056:  87 30         cpi  r24, 0x07  ; 7
>     2058:  91 05         cpc  r25, r1
>     205a:  08 f0         brcs  .+2        ; 0x205e
> <run_bridge_interface+0x12>
>     205c:  5d c1         rjmp  .+698      ; 0x2318
> <run_bridge_interface+0x2cc>
>     205e:  fc 01         movw  r30, r24
>     2060:  88 27         eor  r24, r24
>     2062:  e2 50         subi  r30, 0x02  ; 2
>     2064:  ff 4f         sbci  r31, 0xFF  ; 255
>     2066:  8f 4f         sbci  r24, 0xFF  ; 255

Subtrahieren von 0xFFFF02 => Addieren von 0x0000FE.  Dort steht
deine eigentliche Sprungtabelle.  Diese wird dann vermutlich
Adressen ab 0x206a enthalten, allerdings durch 2 geteilt, also
0x1035 und knapp darüber.  Das sind die Einsprungpunkte für den
eigentlichen Code.

von Wolfgang (Gast)


Lesenswert?

Hallo Jörg,

das 0xFE muß man dann auch mal 2 nehmen, oder. Bei 0xFE liegt noch die 
Interrupt-Sprungtabelle.  Nach dieser Sprungtabelle beginnend ab 0x01FC 
liegt dann der passende Sprungverteiler mit den 7 Adressen:
1
     1f0:  0c 94 f6 0e   jmp  0x1dec  ; 0x1dec <__bad_interrupt>
2
     1f4:  0c 94 f6 0e   jmp  0x1dec  ; 0x1dec <__bad_interrupt>
3
     1f8:  0c 94 f6 0e   jmp  0x1dec  ; 0x1dec <__bad_interrupt>    
4
     1fc:  36 10         cpse  r3, r6
5
     1fe:  61 10         cpse  r6, r1
6
     200:  b5 10         cpse  r11, r5
7
     202:  43 11         cpse  r20, r3
8
     204:  6b 11         cpse  r22, r11
9
     206:  74 11         cpse  r23, r4
10
     208:  82 11         cpse  r24, r2
11
     20a:  72 2d         mov  r23, r2  ....
Das wären dann die Ziele 1036, 1061, 10b5 usw.

Servus Wolfgang

von Curby23523 N. (Gast)


Lesenswert?

Ich hatte noch nie Probleme mit dem Xmega128A4U in dieser Hinsicht.
Ich vermute eher hier den Fehler, oder an einer anderen nict gezeigten 
Stelle:
1
bif_init_uart();
2
bif_enable_rx();

Warnungen eingeschaltet? Funktionen definiert und Header inkludiert? Was 
heißt, ins Nirvana springen? Springst du im Debugger mit F5 durch?

von Wolfgang (Gast)


Lesenswert?

Hallo,

Header sind da, Funktionen sind deklariert.

Ich hatte zum Testen und zum Erhärten der Analyse da schon mal überall 
vor der switch-Anweisung und gleich in jedem case Port-Wackler 
reingebaut (also PORTx.OUTSET, delay_us(n), PORTx.OUTCLR). Mit 
unterschiedlichen n lassen sich da Tracemarken setzen, die man mit dem 
Scope gut in Echtzeit beobachten kann. Da sehe ich den Toggler noch vor 
dem switch, aber in keinem case mehr.

Wie gesagt, compiliere ich mit -fno-jump-tables, dann läuft der Code. 
Und das ist auch alter Code, den ich schon länger in Betrieb habe. Ich 
habe in dem Projekt jetzt halt wieder was gemacht (an anderer Stelle) 
und dann stürzte mir der Code ab - permanenter Reboot. Es hat etwas 
gedauert, bis ich die Stelle mit dem Tschüss gefunden hatte.

Und wie immer halt die Frage, was wurde verändert? Ich hatte wegen einem 
anderen Projekt auf die aktuelle Compilerversion upgedatet. Ich kann 
auch mal versuchen, wieder auf eine ältere Compilerversion zu gehen.

Debugger ist momentan ein bischen schwierig, die Baugruppe ist verbaut 
und die Schaltung hat keinen Erdbezug. Da müßte ich erst umbauen. Und in 
der Zielhardware habe ich keinen JTAG-Zugang.

Servus Wolfgang

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


Lesenswert?

Wolfgang schrieb:
> das 0xFE muß man dann auch mal 2 nehmen, oder

Ja, richtig.  In _tablejump2_ wird der Wert mit 2 multipliziert.

von Wolfgang (Gast)


Lesenswert?

Hallo,

ich hätte mal probiert, eine ältere Version zu installieren - 
Fehlermeldung, man kenne den 128a4u nicht.

Dann habe ich nochmal (zur Sicherheit) den Code mit Port.out statements 
verwanzt, also zu Beginn einen Puls mit 2us, dann 1us, dann in JEDEM 
case zu Beginn einen Puls:
1
t_cr_task run_bridge_interface(void)
2
  {
3
    unsigned char rxdat;
4
    PORTB.OUTSET = (1 << OUT0p);
5
    delay_us(2);
6
    PORTB.OUTCLR = (1 << OUT0p);
7
    delay_us(1);
8
    PORTB.OUTSET = (1 << OUT0p);
9
    delay_us(1);
10
    PORTB.OUTCLR = (1 << OUT0p);
11
            
12
    switch (bridge_if_state)
13
      {
14
        default: 
15
    PORTB.OUTSET = (1 << OUT0p);
16
    delay_us(1);
17
    PORTB.OUTCLR = (1 << OUT0p);
18
            return(-1);
19
        case BIF_START:
20
    PORTB.OUTSET = (1 << OUT0p);
21
    delay_us(1);
22
    PORTB.OUTCLR = (1 << OUT0p);
23
            bif_init_uart();
24
            bif_enable_rx();
25
            bridge_if_state = BIF_GET_LENGTH;
26
            return(0);
27
        case BIF_GET_LENGTH:
28
    PORTB.OUTSET = (1 << OUT0p);
29
    delay_us(1);
30
    PORTB.OUTCLR = (1 << OUT0p);
31
            if (bif_rx_ready())                                 // there are data
32
              {
33
                set_connect_status(BIF_CONNECTED);                
34
                last_bridge_rx = get_systick_int16();
35
                rxdat = bif_rx_read();
36
                if (rxdat)
37
                  { if (rxdat == BRIDGE_PING)
38
                      {                                             // empty message
39
                        bridge_if_state = BIF_CHECK_TX;
40
                        return(0);                                  // sofort umschalten (und hoffen, 
41
                                                                    // dass die StepControl auch schnell ist)
42
                        // return(1100L/SYSTICK_PERIOD);
43
                      }
44
                    else
45
                      {                                             // real message
46
                        #if (DEBUG_BIF_RX == 2)
47
                            pc_send_hex(rxdat);
48
                            #warning testausgabe empfangenes Zeichen
49
                        #endif
50
                        bridge_rx_crc = read_crc_array(rxdat);
51
                        bridge_rx_paket[0] = rxdat;                 // save size
52
                        bridge_rx_total = rxdat + 1;
53
                        bridge_rx_index = 1;
54
                        bridge_if_state = BIF_COLLECT_MESSAGE;
55
                        return(0);
56
                      }
57
                  }
58
              }
59
            else
60
              {
61
                // nothing received, connection broken, we wait infinite
62
                if ((signed int)(get_systick_int16() - last_bridge_rx) > TIMEOUT_BIF_POLL)
63
                  {
64
                    #if (DEBUG_BIF_RX == 2)
65
                        #warning debugausgabe
66
                        pc_send('!');
67
                        pc_send('-');
68
                    #endif
69
                    set_connect_status(BIF_DISCONNECTED);
70
                  }
71
                return(0);
72
              }
73
            break;
74
        case BIF_COLLECT_MESSAGE:
75
    PORTB.OUTSET = (1 << OUT0p);
76
    delay_us(1);
77
    PORTB.OUTCLR = (1 << OUT0p); usw.

Es kommen nur die ersten 2 und dann reboot.

Übersetzen mit Option CFLAGS += -fno-jump-tables - schon sind 3 Pulse 
da.

Soweit ich den Assembler verstehe: der Compiler legt die Sprungtabelle 
bei 1FC an, die Adressen dort zeigen auch auf meine Cases.

Ehrlich gesagt bin ich mit meinem Latein am Ende und ich verstehe nicht, 
warum das passiert. Und ich habe Riesenbammel, irgendwas an einem 
bestehenden Projekt zu pflegen.

Servus Wolfgang

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


Lesenswert?

Kannst du mir das ELF-File zumailen?  Ich unterschreib' dir auch ein
NDA, wenn du willst. ;-)  Mich interessiert das schon.

Beitrag #5308425 wurde von einem Moderator gelöscht.
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Wolfgang schrieb:
> Kann ich das noch genauer einkreisen / beschreiben, mache ich hier

Der Compiler legte die Sprungtabellen nach .progmem.data, wenn diese 
Section "überlief", d.h. nicht mehr per LPM lesbar war, wurde Unsinn 
gelesen.  Das betraf vor allem Code, der per -Ttext an eine andere 
Stelle geschubst wurde (z.B. für einen Bootloader).

Das ist aber seit v4.9.2 behoben [*]

https://gcc.gnu.org/PR63223

Dann gabe es ein Problem wenn der switch-Wert > 16 Bits ist, behoben in 
v7:

https://gcc.gnu.org/PR71676

Wegen eines Problemes mit const-merging wanderten die Sprungtabellen 
dann nach .text. Dies war aber kein Problem mit switch / case sondern 
mit der Ablage bestimmter Konstanten, Fix in v6.2:

https://gcc.gnu.org/PR71151

Schließlich wanderten die Sprungtabellen nach .jumptables, weil sie dort 
besser aufgehoben sind (kein Verbrauch von .progmem.data, keine Sprünge 
über die Tabellen hinweg). Also nur eine Optimierung, behoben in v8:

https://gcc.gnu.org/PR81075

[*] Alle Versionen bezogen auf FSF.

Wolfgang schrieb:
> Und dann hangelt sich der Compiler halt so durch das switch-Statement
> durch ...

In dem von dir gezeigten Code wird keine Tabelle verwendet, d.h. 
zumindest für diese Codestelle hat -fno-jump-tables keinen Effekt: der 
Compiler hat sich da für eine andere Strategie ohne Tabelle entschieden.

: Bearbeitet durch User
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Johann L. schrieb:
> In dem von dir gezeigten Code wird keine Tabelle verwendet,

Das sehe ich nicht so: da ist doch der Sprung nach _tablejump2_
drin.

Ich habe von Wolfgang mittlerweile den kompletten Code und das
generierte ELF-File und werde mir das mal ansehen.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Jörg W. schrieb:
> Johann L. schrieb:
>> In dem von dir gezeigten Code wird keine Tabelle verwendet,
>
> Das sehe ich nicht so: da ist doch der Sprung nach __tablejump2__
> drin.

Ah ja, da:

Wolfgang schrieb:
>     2060:  88 27         eor  r24, r24
>     2062:  e2 50         subi  r30, 0x02  ; 2
>     2064:  ff 4f         sbci  r31, 0xFF  ; 255
>     2066:  8f 4f         sbci  r24, 0xFF  ; 255
>     2068:  0c 94 1f 63   jmp  0xc63e  ; 0xc63e <__tablejump2__>

Die gs() Tabelle befindet sich dann ab Flash-Adresse -2*(0xffff02) = 
0x1fc.  Zu prüfen wäre dann:

* Ob die richtige Multilib-Variante von tablejump2 vorliegt (avrxmega7).

* avrxmega7 hat RAM > 64 KiB, d.h. gcc geht davon aus dass RAMPD/X/Y/Z 
null sind.

* .trampolines nicht-leer ist falls Code > 128KiB indirekt angesprungen 
wird, d.h. Adressen solcher Funktionen genommen werden (was die 
Sprungtabelle u.U. macht).

* Die Lokatierung von .trampolines konsistent mit EIND ist, welches aus 
__vectors initialisiert wird.

Irgendwann gab's mal den Fehler, dass RAMP* in ISR in falscher 
Reihenfolge restauriert wurden; war aber wimre transient und in keiner 
Release.

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.