Forum: Compiler & IDEs GCC Startet nicht main() auf Mega168


von Stefan (Gast)


Angehängte Dateien:

Lesenswert?

Hallo!

Ich wollte den seed "generator" von Johann L. in meinem Projekt mit 
benutzen.

Jedoch startet der Controller die Main Schleife nicht, da er wie in

debug/testinit.lss

in Zeile a2: mit dem rjmp immer wieder nach oben springt und dort nie 
wieder rauskommen kann.

Ineressanter weise höhrt dies auf wenn mann in

__init_seed()

p >= & __noinit_start + 1

gegen z.b.

p >0xff(warnung wegen falschen vergleichs)

tauscht?

jemand eine Idee wie man den Compiler das abgewöhnen könnte?

Gruß Stefan

von Stefan E. (sternst)


Lesenswert?

Sieht tatsächlich nach einem Fehler des Compilers aus. Bei mir sieht das 
Ergebnis übrigens so aus (also richtig):
1
  74:  20 e0         ldi  r18, 0x00  ; 0
2
  76:  30 e0         ldi  r19, 0x00  ; 0
3
  78:  e0 e0         ldi  r30, 0x00  ; 0
4
  7a:  f5 e0         ldi  r31, 0x05  ; 5
5
  7c:  04 c0         rjmp  .+8        ; 0x86 <__init_seed+0x12>
6
  7e:  92 91         ld  r25, -Z
7
  80:  82 91         ld  r24, -Z
8
  82:  28 27         eor  r18, r24
9
  84:  39 27         eor  r19, r25
10
  86:  81 e0         ldi  r24, 0x01  ; 1
11
  88:  e2 30         cpi  r30, 0x02  ; 2
12
  8a:  f8 07         cpc  r31, r24
13
  8c:  c0 f7         brcc  .-16       ; 0x7e <__init_seed+0xa>
14
  8e:  30 93 01 01   sts  0x0101, r19
15
  92:  20 93 00 01   sts  0x0100, r18
16
17
  96:  02 d0         rcall  .+4        ; 0x9c <main>
18
  98:  06 c0         rjmp  .+12       ; 0xa6 <_exit>

Bevor wir uns den Fehler aber jetzt im Detail anschauen, erst noch eine 
Frage. Du benutzt Eclipse, vielleicht auf Linux und vielleicht mit einem 
fertigen AVR-GCC-Paket (z.B. von Ubuntu)?

von Stefan (Gast)


Lesenswert?

Hallo Stefan!


Compiler läuft auf Windows XP SP 3

avr-gcc -v gibt aus:
Using built-in specs.
Target: avr
Configured with: ../gcc-4.3.2/configure 
--enable-win32-registry=WinAVR-20090313
--with-gmp=/usr/local --with-mpfr=/usr/local --prefix=/c/WinAVR 
--target=avr --enable-languages=c,c++,objc --with-dwarf2 --enable-doc 
--disable-shared--disable
-libada --disable-libssp --disable-nls --with-pkgversion='WinAVR 
20090313' 
--with-bugurl='URL:http://sourceforge.net/tracker/?atid=520074&group_id=68108&func=browse';
Thread model: single
gcc version 4.3.2 (WinAVR 20090313)

Ich nutze Eclipse 3.4.2 mit AVR Plugin von Thomas Holland Version 
2.3.1.20081204PRD

Gruß Stefan

von Stefan E. (sternst)


Lesenswert?

Mein Ergebnis oben stammt vom selben GCC (WinAVR 20090313). Wie sieht 
die Kommandozeile für den Compileraufruf genau aus.

von Stefan (Gast)


Lesenswert?

Hallo Stefan!

Folgender Aufruf wird generiert.

avr-gcc -Wall -g2 -gstabs -O2 -fpack-struct -fshort-enums 
-funsigned-char -funsigned-bitfields -mmcu=atmega168 -DF_CPU=1000000UL 
-MMD -MP -MF"main.d" -MT"main.d" -c -o"main.o" "../main.c"

Habe bei Eclipse neues Projekt angelegt, und nur den AVR Typ und die 
Optimierungsstufe angepasst.

Mein generiertes Makefile liegt in dem Archiv unter Debug, erzeugt dann 
diesen Aufruf.

Gruß Stefan

von Stefan E. (sternst)


Lesenswert?

Die Optimierungsstufe macht den Unterschied. Hast du einen bestimmten 
Grund für O2? Os ist meist die bessere Wahl.

von Stefan (Gast)


Lesenswert?

Hallo Stefan!

Habs gerade nochmal laufen lassen, tritt bei O2 und O3 auf.

Habe eignetlich keine konktreten grund für O2.

Ist aber Trotzdem nicht schön. Aber mann kann ja drumm schiffen.

Is das BUG Report Wert?


Gruß Stefan

von Stefan E. (sternst)


Lesenswert?

Die Ursache liegt in dem "naked". Als normale Funktion sieht das nämlich 
so aus:
1
0000007a <init_seed>:
2
  7a:  82 e0         ldi  r24, 0x02  ; 2
3
  7c:  91 e0         ldi  r25, 0x01  ; 1
4
  7e:  81 50         subi  r24, 0x01  ; 1
5
  80:  95 40         sbci  r25, 0x05  ; 5
6
  82:  88 f4         brcc  .+34       ; 0xa6 <init_seed+0x2c>
7
  84:  20 e0         ldi  r18, 0x00  ; 0
8
  86:  30 e0         ldi  r19, 0x00  ; 0
9
  88:  e0 e0         ldi  r30, 0x00  ; 0
10
  8a:  f5 e0         ldi  r31, 0x05  ; 5
11
  8c:  92 91         ld  r25, -Z
12
  8e:  82 91         ld  r24, -Z
13
  90:  28 27         eor  r18, r24
14
  92:  39 27         eor  r19, r25
15
  94:  81 e0         ldi  r24, 0x01  ; 1
16
  96:  e2 30         cpi  r30, 0x02  ; 2
17
  98:  f8 07         cpc  r31, r24
18
  9a:  c0 f7         brcc  .-16       ; 0x8c <init_seed+0x12>
19
  9c:  30 93 01 01   sts  0x0101, r19
20
  a0:  20 93 00 01   sts  0x0100, r18
21
  a4:  08 95         ret
22
  a6:  20 e0         ldi  r18, 0x00  ; 0
23
  a8:  30 e0         ldi  r19, 0x00  ; 0
24
  aa:  f8 cf         rjmp  .-16       ; 0x9c <init_seed+0x22>
Und dieser Code ist korrekt. Durch das "naked" fällt dann das "ret" weg 
und du hast die Endlosschleife am Ende.

> Is das BUG Report Wert?

Ich bin mir gar nicht mal sicher, ob man das überhaupt als Bug 
bezeichnen kann.

von Sepp (Gast)


Lesenswert?

Es ist doch Sinn und Zweck des naked-Attributs, daß das ret wegfällt...

von Stefan E. (sternst)


Lesenswert?

Sepp schrieb:
> Es ist doch Sinn und Zweck des naked-Attributs, daß das ret wegfällt...

Ja und?

Die Frage ist: Kann man es als Bug bezeichnen, wenn der Compiler dem 
Code eine solche Struktur gibt, dass beim Wegfall des "ret" (eben naked) 
eine Endlosschleife entsteht?

Ich tendiere eher zu Nein.

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


Lesenswert?

Stefan Ernst schrieb:

> Ich bin mir gar nicht mal sicher, ob man das überhaupt als Bug
> bezeichnen kann.

Naja, etwas seltsam ist das schon, dass er für eine Funktion, die
als naked deklariert ist, dann trotzdem noch versucht, einen
Epilog zu erzeugen.  Ich würde das schon als Bug bezeichnen.
1
.L6:
2
        sts (seedram)+1,r19
3
        sts seedram,r18
4
/* epilogue start */
5
.L11:
6
        ldi r18,lo8(0)
7
        ldi r19,hi8(0)
8
        rjmp .L6

(Der Kommentar für "epilogue end" fehlt übrigens; auch ein Hinweis
darauf, dass hier im Compiler was schief gelaufen ist.)

Ich habe das Ganze mal mit einem GCC 4.2.2 übersetzt, der generiert
auch bei -O3 noch korrekten Code:
1
...
2
        ldi r24,hi8(__noinit_start+2)
3
        cpi r30,lo8(__noinit_start+2)
4
        cpc r31,r24
5
        brsh .L9
6
        rjmp .L8
7
.L13:
8
        ldi r18,lo8(0)
9
        ldi r19,hi8(0)
10
.L8:
11
        sts (seedram)+1,r19
12
        sts seedram,r18
13
/* epilogue: frame size=0 */
14
/* epilogue: naked */
15
/* epilogue end (size=0) */
16
/* function __init_seed size 25 (25) */

Wäre die Frage, ob der Bug insbesondere in GCC 4.4.x noch drin ist.

Zwei Bemerkungen zu Stefans Code:

. Bitte #include <avr/io.h> schreiben, damit man das nicht extra
  noch editieren muss, wenn man nicht auf Windows arbeitet.  Der
  Backslash ist da völlig unnötig.

. Die beiden extrem wichtigen Deklarationen der Attribute in ein
  main.h auszulagern, widerspricht der Erwartungshaltung und hat
  keinen wirklichen Sinn.  Sowas gehört möglichst nahe an die
  Definition der entsprechenden Funktion oder Variablen heran, damit
  man es sofort beim Lesen bemerkt.

von Stefan E. (sternst)


Lesenswert?

> Naja, etwas seltsam ist das schon, dass er für eine Funktion, die
> als naked deklariert ist, dann trotzdem noch versucht, einen
> Epilog zu erzeugen.

Inwieweit versucht er es denn? Das fragliche Stückchen Code ist doch 
Bestandteil des eigentlichen Funktionscodes, es liegt nur hinter dem 
Epilog. Das ist das Problem, dass der Epilog quasi mitten im restlichen 
Code liegt.

von Stefan (Gast)


Lesenswert?

Hallo!

Ich kann es nicht an 4.4 testen, bin halt nur unter Win unterwegs.

@Jörg:

werds mit mit den "/" anehmen, grundsätzlich.

das andere auch.

Ich ich denke das der Compiler auch bei unterschiedlichen 
Optimierungsstuffen dann trotzdem immer zum selben Verhalten des 
Programmes kommen müsste, oder?


Gruß Stefan

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


Lesenswert?

Stefan Ernst schrieb:
>> Naja, etwas seltsam ist das schon, dass er für eine Funktion, die
>> als naked deklariert ist, dann trotzdem noch versucht, einen
>> Epilog zu erzeugen.
>
> Inwieweit versucht er es denn?

Indem der Compiler davor schreibt: "epilogue start".  Ich habe ja
den Vergleich mit dem GCC 4.2.2 gebracht, da steht auch beim
Epilog sauber drin "epilogue: naked".  GCC 4.3.x schreibt das beim
Prolog auch so hin, aber beim Epilog verheddert er sich offenbar.

Ich finde auf Anhieb keinen Bug in GCCs Bugzilla, dessen Beschreibung
das Wort "naked" enthält und der irgendwie annähernd auf dieses
Symptom zu passen scheint.  Scheint also ein neuer Bug zu sein.

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


Lesenswert?

Stefan schrieb:

> Ich ich denke das der Compiler auch bei unterschiedlichen
> Optimierungsstuffen dann trotzdem immer zum selben Verhalten des
> Programmes kommen müsste, oder?

Ja, zumindest grundlegend.  Im Zeit- und Platzbedarf darf sich
natürlich schon mal etwas ändern.

von Peter D. (peda)


Lesenswert?

Jörg Wunsch schrieb:
> . Die beiden extrem wichtigen Deklarationen der Attribute in ein
>   main.h auszulagern, widerspricht der Erwartungshaltung und hat
>   keinen wirklichen Sinn.  Sowas gehört möglichst nahe an die
>   Definition der entsprechenden Funktion oder Variablen heran, damit
>   man es sofort beim Lesen bemerkt.

Stimmt, mit sowas rechnet man nicht.

Der Compiler hat also recht, bei naked muß er das RET weglassen und der 
Autor hätte es händisch einfügen müssen.

Das es manchmal trotzdem geht, liegt daran, daß das RET zufällig am Ende 
stünde und dann ohne RET in die nächste Funktion reingelaufen wird.

Der Compiler ist aber nicht verpflichtet, die Funktionen so zu 
schreiben, daß das (virtuelle) RET immer am Ende steht, bzw. nur bei 
-O0.


Peter

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


Lesenswert?

Jörg Wunsch schrieb:

> Indem der Compiler davor schreibt: "epilogue start".  Ich habe ja
> den Vergleich mit dem GCC 4.2.2 gebracht, da steht auch beim
> Epilog sauber drin "epilogue: naked".  GCC 4.3.x schreibt das beim
> Prolog auch so hin, aber beim Epilog verheddert er sich offenbar.

In GCC 4.3 wurde die Generierung von Prolog und Epilog auf RTL-Code
umgebaut, dadurch sieht der Code völlig anders aus.  Beim Prolog steht:
1
  /* Prologue: naked.  */
2
  if (cfun->machine->is_naked)
3
    {
4
      return;
5
    }

d.h. die Funktion zum Generieren des Prologs wird sofort verlassen,
wenn eine "naked" Funktion generiert wird.  Beim Epilog dagegen
steht:
1
  /* epilogue: naked  */
2
  if (cfun->machine->is_naked)
3
    {
4
      emit_jump_insn (gen_return ());
5
      return;
6
    }

Dieses "emit_jump_insn (gen_return ());" scheint irgendwie die Ursache
des Problems zu sein.  Ich finde auf Anhieb nicht, wie gen_return()
definiert ist.  Man könnte natürlich mal probieren, diese Zeile ganz
auszukommentieren, und danach die GCC-Testsuite durchzuleiern um zu
sehen, ob daraus weitere regressions entstehen.

Ich würde auf jeden Fall empfehlen, einen GCC-Bugreport zu schreiben.
Der Bug ist übrigens rein AVR-spezifisch.

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


Lesenswert?

Peter Dannegger schrieb:

> Der Compiler hat also recht, bei naked muß er das RET weglassen und der
> Autor hätte es händisch einfügen müssen.

Nein, in diesem Falle ist es Absicht, dass das RET fehlt, denn das
Ganze soll ja in die Startup-Sequenz eingebaut werden.  Da darf
es kein CALL/RET geben, denn es gibt u. U. noch gar keinen initiali-
sierten Stack.

von Stefan E. (sternst)


Lesenswert?

Jörg Wunsch schrieb:

>> Inwieweit versucht er es denn?
>
> Indem der Compiler davor schreibt: "epilogue start".  Ich habe ja
> den Vergleich mit dem GCC 4.2.2 gebracht, da steht auch beim
> Epilog sauber drin "epilogue: naked".  GCC 4.3.x schreibt das beim
> Prolog auch so hin, aber beim Epilog verheddert er sich offenbar.

Gut, überzeugt. ;-)

Aber ich sehe noch nicht den direkten Zusammenhang. Der Epilog ist ja 
mit naked leer (wenn auch unzureichend kommentiert). Er ist nur von 
vornherein schlecht positioniert.

von Stefan E. (sternst)


Lesenswert?

Peter Dannegger schrieb:

> Der Compiler hat also recht, bei naked muß er das RET weglassen und der
> Autor hätte es händisch einfügen müssen.

Aber bei diesem Code hätte der Author nicht mal die Möglichkeit, das RET 
selbst einzufügen.

von Peter D. (peda)


Lesenswert?

Jörg Wunsch schrieb:
> Nein, in diesem Falle ist es Absicht, dass das RET fehlt, denn das
> Ganze soll ja in die Startup-Sequenz eingebaut werden.  Da /darf/
> es kein CALL/RET geben, denn es gibt u. U. noch gar keinen initiali-
> sierten Stack.

Dann müßte das naked aber eine Umsortierung des Codes verhindern.

Diese Konstrukte mit dem RET mittendrin sind mir auch schon oft 
aufgefallen beim AVR-GCC 4.3.x.
Der Optimizer hält Indexoperationen mit Autoincrement für extrem teuer 
und trennt das Increment ab, um das letzte (unnötige) Increment zu 
sparen.


Peter

von Stefan E. (sternst)


Lesenswert?

Stefan Ernst schrieb:
> Peter Dannegger schrieb:
>
>> Der Compiler hat also recht, bei naked muß er das RET weglassen und der
>> Autor hätte es händisch einfügen müssen.
>
> Aber bei diesem Code hätte der Author nicht mal die Möglichkeit, das RET
> selbst einzufügen.

Bitte streichen. :-)

Ein händisches RET am Ende der Funktion taucht auch mitten drin auf.

von (prx) A. K. (prx)


Lesenswert?

In diesem Fall endet die Funktion an 0xA2. Der Optimizer hat die 
while-Schleife in die zeitlich geringfügig effizientere aber deutlich 
längere if-do-while Form optimiert und hat den abweisenden Fall aus dem 
sequentiellen Codeablauf rausoperiert:
1
   if (p >= & __noinit_start + 1) {
2
      do {
3
         s ^= * (--p);
4
      } while (p >= & __noinit_start + 1)
5
      seedram = s;
6
      return;
7
   }
8
   seedram = 0;
Der "seedram = 0;" Schwanz beginnt nun hinter dem nicht vorhandenen 
Epilog, teilt aber einen Teil der Zuweisung mit dem Schluss der 
Hauptsequenz.

Sinn dieser Optimierung ist die Vermeidung von ausgeführten 
Sprungbefehlen, die bei den meisten Zielarchitekturen recht teurer sind.

Das ist eine typische Optimierung for -O2/3, die bei der für AVRs m.E. 
ohnehin empfehlenswerteren Optimierungsstufe -Os vermutlich verschwinden 
wird.

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


Lesenswert?

Peter Dannegger schrieb:

>> Nein, in diesem Falle ist es Absicht, dass das RET fehlt, denn das
>> Ganze soll ja in die Startup-Sequenz eingebaut werden.  Da /darf/
>> es kein CALL/RET geben, denn es gibt u. U. noch gar keinen initiali-
>> sierten Stack.
>
> Dann müßte das naked aber eine Umsortierung des Codes verhindern.

Nein, das RET irgendwo zwischendrin müsste wohl zu einem Sprung
hinter das Ende umgeformt werden.  Möglicherweise ist das ja der
Sinn des "emit_jump_insn (gen_return ());", an der Stelle blicke
ich dann nicht mehr durch.

von Stefan (Gast)


Lesenswert?

Hallo!

Bug ist aufgenommen

http://gcc.gnu.org/bugzilla/show_bug.cgi?id=42240


Gruß Stefan

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


Lesenswert?

Stefan schrieb:

> Bug ist aufgenommen

Danke.

> http://gcc.gnu.org/bugzilla/show_bug.cgi?id=42240

Einen Hinweis noch: die GCC-Leute möchten gern den Code nach dem
Präprozessor sehen.  Dinge wie #include <avr/io.h> sollten da also
nicht mehr drin stehen.  Du brauchst aber sowieso nicht viel davon,
den uint16_t kannst du so obendrüber selbst definieren:
1
typedef unsigned int uint16_t;

RAMEND kannst du durch eine beliebige Zahl ersetzen.  Die deutschen
Kommentare braucht da sicher auch keiner. ;-)  Ergebnis:
1
int main(void){
2
        while(1);
3
}
4
typedef unsigned int uint16_t;
5
6
static uint16_t seedram __attribute__ ((section (".noinit")));
7
void __init_seed(void) __attribute__ ((naked, section (".init8")));
8
void __init_seed (void)
9
{
10
        uint16_t s = 0;
11
        uint16_t *p = (uint16_t*) (42);
12
        extern uint16_t __noinit_start;
13
14
        while (p >= &__noinit_start + 1)
15
                s ^= *(--p);
16
17
        seedram = s;
18
}

Wenn du jetzt noch das generierte Assemblerfile als zweites Attachment
bringst:

avr-gcc -O2 -S main.c

dann freuen sich die Leute, weil der Bug dann offensichtlich erkennbar
ist.

von Stefan (Gast)


Lesenswert?

Hallo Jörg!

Nach bestem wissen erledigt!

gruß

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


Lesenswert?

Sollte gut sein so.

Du kannst mal noch unter "known to fail" die Zahl eintragen:

4.3.2, 4.3.4

und wenn du willst unter "known to work" die 4.2.2.  Ich darf's
nicht, hab's gerade probiert.

von Stefan (Gast)


Lesenswert?

Hallo Jörg!

auch erledigt

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


Lesenswert?

OK, danke. ;-)

Noch eine orthografische Korrektur, damit man das auch mit der
Suche findet:

Statt

wrong Epilog on nacked function

bitte

wrong epilogue on naked function

von Stefan (Gast)


Lesenswert?

Hallo Jörg!

Auch gemacht ...

von (prx) A. K. (prx)


Lesenswert?

Das Problem ist nicht beschränkt auf AVR.
arm-gcc 4.3.4 mit -fthumb:
1
__init_seed:
2
        ldr     r0, .L12
3
        cmp     r0, #42
4
        bhi     .L11
5
        mov     r1, #0
6
        mov     r2, #42
7
.L7:
8
        sub     r2, r2, #4
9
        ldr     r3, [r2]
10
        eor     r1, r1, r3
11
        cmp     r2, r0
12
        bcs     .L7
13
.L6:
14
        ldr     r3, .L12+4
15
        str     r1, [r3]
16
.L11:
17
        mov     r1, #0
18
        b       .L6
19
.L13:

von (prx) A. K. (prx)


Lesenswert?

Wenn das als "works as designed" geschlossen wird (was ich annehme), 
dann wird man wohl künftig so vorgehen müssen:
1
int main(void){
2
        while(1);
3
}
4
typedef unsigned int uint16_t;
5
6
static uint16_t seedram __attribute__ ((section (".noinit")));
7
void __init_seed2(void) __attribute__ ((noinline));
8
void __init_seed2 (void)
9
{
10
        uint16_t s = 0;
11
        uint16_t *p = (uint16_t*) (42);
12
        extern uint16_t __noinit_start;
13
14
        while (p >= &__noinit_start + 1)
15
                s ^= *(--p);
16
17
        seedram = s;
18
}
19
void __init_seed(void) __attribute__ ((naked, section (".init8")));
20
void __init_seed(void) { __init_seed2(); }

von Stefan (Gast)


Lesenswert?

Hallo A.K.

Nach meinem verständinss muss der Compiller zumindest gleichbleibendes 
Verhalten erzeugen.

Also geht entweder unter gar keiner Optimierungsstufe, oder unter allen.

Gruß

von Peter D. (peda)


Lesenswert?

Stefan schrieb:
> Also geht entweder unter gar keiner Optimierungsstufe, oder unter allen.

Es hängt allein davon ab, ob durch die Optimierung der Code zufällig 
hinten endet oder irgendwo zwischedrin.

Der Compiler ist in einer Zwickmühle:
Einerseits sagst Du ihm mit "naked", er soll keinen Epilog erzeugen, 
gleichzeitig erwartest Du aber, daß er doch einen Epilog erzeugt (Sprung 
zum Ende).

Meiner Meinung nach kann man bei "naked" Funktionen nur dann einen 
zuverlässigen Code erwarten, wenn man ihn in Assembler schreibt oder bei 
C-Code mit abgeschalteter Optimierung (-O0).


Peter

von (prx) A. K. (prx)


Lesenswert?

Stefan schrieb:

> Also geht entweder unter gar keiner Optimierungsstufe, oder unter allen.

Die Doku vom GCC sagt ausdrücklich, dass der Programmierer für 
Prolog/Epilog selbst verantwortlich ist. Der Sinn der "naked" Option 
dürfte ursprünglich in speziellen Interrupt-Handlern und ähnlichem Kram 
liegen, sowas wird gern bei Realtime-Kernels verwendet.

Den Epilog einfach wegzulassen und zu hoffen, dass man wie zufällig 
hinten passend rausfällt, das steht nicht in der Vorgabe. Es steht auch 
nirgends in Stein gemeisselt, dass der Epilog einer C Funktion immer zu 
allerletzt im Code kommt. Der darf auch mitten drin liegen. Was er hier 
tut.

Es ist nicht weiter erstaunlich, dass das optimierungsabhängig ist. GCC 
optimiert bei Fokus auf Laufzeit so, dass der von ihm als Hauptpfad 
angesehene Codepfad mit so wenig Sprüngen wie möglich auskommt. Das kann 
auch mal bedeutet, dass ein Nebenpfad aus dem Weg geräumt wird und dann 
eben hinter dem für den Epilog vorgesehenen Platz landet. Genau das 
geschieht hier. Das hat hier zur Folge, dass der Ablauf eben nicht am 
letzten Byte endet, sondern mitten drin, und man folglich nicht wie 
gewünscht hinten rausfällt. Pech für die Kuh Elsa.

Wenn man das gleiche Spielchen beim arm-gcc macht, dann wird das selbst 
dann in die Hose gehen, wenn der Code wie angenommen tatsächlich hinten 
rausfällt. Denn bei ARMs schliesst sich dort üblicherweise der constant 
pool an, und der macht sich als auszuführender Code nicht wirklich gut.

Langer Rede kurzer Sinn: Eine "naked" Funktion komplett ohne explizit 
programmiertem Epilog direkt in die .initN Sektionen reinzupinnen war 
nie der Zweck dieser Option, hat aber eine Weile lang zufällig 
funktioniert. Und funktioniert nun eben nicht mehr.

von (prx) A. K. (prx)


Lesenswert?

Stefan schrieb:

> Nach meinem verständinss muss der Compiller zumindest gleichbleibendes
> Verhalten erzeugen.

Er muss sich an die Vorgabe halten. Das tut er.

Wer sich hier nicht an die Vorgabe hält ist der Programmierer, der es 
unterlässt, selbst für einen passenden Epilog zu sorgen. Was seine 
Pflicht wäre.

Dass der Programmierer damit ein Problem hat ist nicht Sache des 
Compilers. Eine "naked" Funktion ist nun einmal eine Brechstange, die 
dem Compiler ausdrücklich zu verstehen gibt, dass er nicht für einen 
korrekten Abschluss sorgen muss.

von Frank (Gast)


Lesenswert?

Hi,

in dem hier gezeigten speziellen Fall hilft übrigens "cold" als 
zusätzliches Attribut:
1
#include <avr\io.h>
2
3
static uint16_t seedram __attribute__ ((section (".noinit")));
4
5
static void __init_seed(void) __attribute__ ((naked, cold, used, section (".init8")));
6
static void __init_seed(void)
7
{
8
  uint16_t s = 0;
9
  uint16_t *p = (uint16_t*) (RAMEND+1);
10
  extern uint16_t __noinit_start;
11
12
  while (p >= & __noinit_start + 1)
13
    s ^= * (--p);
14
15
  seedram = s;
16
}
17
18
int main(){
19
  while(1);
20
}
Das sorgt dafür, das die Funktion nicht auf Geschwindigkeit sondern auf 
Größe optimiert wird. Das Gegenstück dazu ist "hot". Leider scheint es 
aber kein Attribut zu geben, daß die Optimierung für eine Funktion 
komplett abschaltet.

Dürfte natürlich nicht in allen Fällen helfen, in diesem speziellen aber 
schon (kompilliert mit -O3):
1
00000074 <__init_seed>:
2
static void __init_seed(void) __attribute__ ((naked, cold, used, section (".init8")));
3
static void __init_seed(void)
4
{
5
  74:  82 e0         ldi  r24, 0x02  ; 2
6
  76:  91 e0         ldi  r25, 0x01  ; 1
7
  78:  81 50         subi  r24, 0x01  ; 1
8
  7a:  95 40         sbci  r25, 0x05  ; 5
9
  7c:  68 f4         brcc  .+26       ; 0x98 <__init_seed+0x24>
10
  7e:  20 e0         ldi  r18, 0x00  ; 0
11
  80:  30 e0         ldi  r19, 0x00  ; 0
12
  82:  e0 e0         ldi  r30, 0x00  ; 0
13
  84:  f5 e0         ldi  r31, 0x05  ; 5
14
  uint16_t s = 0;
15
  uint16_t *p = (uint16_t*) (RAMEND+1);
16
  extern uint16_t __noinit_start;
17
18
  while (p >= & __noinit_start + 1)
19
    s ^= * (--p);
20
  86:  92 91         ld  r25, -Z
21
  88:  82 91         ld  r24, -Z
22
  8a:  28 27         eor  r18, r24
23
  8c:  39 27         eor  r19, r25
24
{
25
  uint16_t s = 0;
26
  uint16_t *p = (uint16_t*) (RAMEND+1);
27
  extern uint16_t __noinit_start;
28
29
  while (p >= & __noinit_start + 1)
30
  8e:  81 e0         ldi  r24, 0x01  ; 1
31
  90:  e2 30         cpi  r30, 0x02  ; 2
32
  92:  f8 07         cpc  r31, r24
33
  94:  c0 f7         brcc  .-16       ; 0x86 <__init_seed+0x12>
34
  96:  02 c0         rjmp  .+4        ; 0x9c <__init_seed+0x28>
35
  98:  20 e0         ldi  r18, 0x00  ; 0
36
  9a:  30 e0         ldi  r19, 0x00  ; 0
37
    s ^= * (--p);
38
39
  seedram = s;
40
  9c:  30 93 01 01   sts  0x0101, r19
41
  a0:  20 93 00 01   sts  0x0100, r18
42
  a4:  0e 94 58 00   call  0xb0  ; 0xb0 <main>

CU

Achso: gcc (WinAVR 20090313) 4.3.2

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


Lesenswert?

Peter Dannegger schrieb:

> Der Compiler ist in einer Zwickmühle:
> Einerseits sagst Du ihm mit "naked", er soll keinen Epilog erzeugen,
> gleichzeitig erwartest Du aber, daß er doch einen Epilog erzeugt (Sprung
> zum Ende).

Offenbar erzeugt er ja auch einen minimalen Epilog, nur halt den
falschen (nämlich einen, der in die Funktion zurück springt).

> Meiner Meinung nach kann man bei "naked" Funktionen nur dann einen
> zuverlässigen Code erwarten, wenn man ihn in Assembler schreibt oder bei
> C-Code mit abgeschalteter Optimierung (-O0).

Beides wäre widersinnig.  Im ersten Fall kann man die Funktion gleich
als Assemblerdatei reinklemmen, dann braucht man das "naked" einfach
mal gar nicht.  Dem Linker ist es wurscht, ob der entsprechende
Objektmodul für die .initN-Teile aus C oder Assembler erzeugt worden
war.  Der zweite Fall wäre widersinning, weil das Abschalten der
Optimierung nie eine Lösung ist, sondern nur das Problem kaschiert.
Optimierung soll man höchstens ausschalten müssen, um besser debuggen
zu können, aber nicht für produktiven Code.

Wenn eine naked-Funktion nicht garantieren kann, dass sie am Ende
der Abarbeitung auch am Ende der Funktion rauskommt, dann ist das
Attribut einfach nicht zu gebrauchen, denn es wäre dann auch nicht
möglich, da beispielsweise einen eigenen Epilog anzufügen.

von Peter D. (peda)


Lesenswert?

Jörg Wunsch schrieb:
> Offenbar erzeugt er ja auch einen minimalen Epilog, nur halt den
> falschen (nämlich einen, der in die Funktion zurück springt).

Nö, das ist kein Epilog, es gehört zu Code, der nach hinten verschoben 
wurde.
Wenn man das naked wegläßt, bleibt der Sprung, nur dazwischen kommt noch 
das RET rein.

Der Compiler hat einfach ganz formal das RET gelöscht, mehr nicht.
Der naked-Besen kehrt nach der Optimierung nochmal drüber und löscht 
alle Epiloge.

Das Verhalten ist vielleicht nicht vom Nutzer erwünscht, aber streng 
formal richtig.
Es wird daher sehr sehr schwer sein, einen Ausnahme-Epilog 
reinzubasteln.


Peter

von (prx) A. K. (prx)


Lesenswert?

Jörg Wunsch schrieb:

> Wenn eine naked-Funktion nicht garantieren kann, dass sie am Ende
> der Abarbeitung auch am Ende der Funktion rauskommt, dann ist das
> Attribut einfach nicht zu gebrauchen, denn es wäre dann auch nicht
> möglich, da beispielsweise einen eigenen Epilog anzufügen.

Doch. Das Attribut ist ursprünglich für diese Art Code vorgesehen:
1
void syscall_handler(void)
2
{
3
    SAVE_CONTEXT();
4
    ...was auch immmer...
5
    RESTORE_CONTEXT_AND_RETURN();
6
}
mit entprechenden ASM-Stücken für den handgebauten Prolog/Epilog. Da 
funktioniert es weiterhin.

von (prx) A. K. (prx)


Lesenswert?

Peter Dannegger schrieb:

> Der Compiler hat einfach ganz formal das RET gelöscht, mehr nicht.
> Der naked-Besen kehrt nach der Optimierung nochmal drüber und löscht
> alle Epiloge.

Fast. Mit "naked" werden Prolog und Epilog nicht erst erzeugt und 
gelöscht, sondern überhaupt nicht durchgeführt.

Mein Verdacht ist, dass Jörgs vermisster Sprung
  emit_jump_insn (gen_return ())
nichts anderes als ein "return" ist und daher als Sprung auf den Epilog 
verstanden wird. Und der Compiler einen Sprung auf sich selbst (denn 
genau da steht er ja grad) als Unsinn entsorgt.

Statt dessen müsste der Compiler dort nicht ein "return" einfügen, 
sondern einen Sprung auf ein Label, dass abschliessend nach dem letzten 
Byte Code definiert wird.

In obigem Beispiel würde das allerdings Kritiker auf den Plan rufen, die 
diesen Sprung als unnötigen Unsinn brandmarken. ;-)

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


Lesenswert?

A. K. schrieb:

> Mein Verdacht ist, dass Jörgs vermisster Sprung
>   emit_jump_insn (gen_return ())
> nichts anderes als ein "return" ist und daher als Sprung auf den Epilog
> verstanden wird. Und der Compiler einen Sprung auf sich selbst (denn
> genau da steht er ja grad) als Unsinn entsorgt.

Ja, das ist möglich.  Wenn man an dieser Stelle ein

asm("ret");

einbaut, dann wird es vor der Stelle ausgeführt, die da als
"epilogue" deklariert wird.

> Statt dessen müsste der Compiler dort nicht ein "return" einfügen,
> sondern einen Sprung auf ein Label, dass abschliessend nach dem letzten
> Byte Code definiert wird.

Ja, das wäre die sinnvollste Aktion.

Als Würgaround würde noch sowas helfen:
1
int main(void){
2
        while(1);
3
}
4
typedef unsigned int uint16_t;
5
6
static uint16_t seedram __attribute__ ((section (".noinit")));
7
void init_seed(void) __attribute__ ((naked, section (".init8")));
8
void init_seed (void)
9
{
10
        uint16_t s = 0;
11
        uint16_t *p = (uint16_t*) (42);
12
        extern uint16_t __noinit_start;
13
14
        while (p >= &__noinit_start + 1)
15
                s ^= *(--p);
16
17
        seedram = s;
18
        asm("rjmp init_seed_end");
19
}
20
static void __attribute__((naked, used, section(".init8")))
21
init_seed_end(void)
22
{
23
}

von Peter D. (peda)


Lesenswert?

Jörg Wunsch schrieb:
> Als Würgaround würde noch sowas helfen:
>
1
> ...
2
>         asm("rjmp init_seed_end");
3
> }
4
> static void __attribute__((naked, used, section(".init8")))
5
> init_seed_end(void)
6
> {
7
> }
8
>

Sieht erstmal nicht schlecht aus.
Nun muß man bloß noch die Stelle finden, wo in Stein gemeißelt ist, daß 
Funktionen in der init-Section nicht umsortiert werden dürfen.

In der text-Section stellt mir der Compiler gerne mal die 
Funktionsreihenfolge um.


Peter

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


Lesenswert?

Peter Dannegger schrieb:

> Nun muß man bloß noch die Stelle finden, wo in Stein gemeißelt ist, daß
> Funktionen in der init-Section nicht umsortiert werden dürfen.

Nirgends.  Wenn du paranoid bist, dann:

. schreibst du den ganze Zinnober gleich in Assembler, oder

. schreibst ihn nach .init7 und setzt das Sprungziel in die .init8,
  oder

. schreibst einen Linkerscript, der einen Label am Ende von .init8
  generiert, und springst den an.

Im Übrigen wage ich die Sinnfälligkeit des ganzen Unternehmens zu
bezweifeln.  SRAM hat eine Vorzugslage, die er mit einer gewissen
Wahrscheinlichkeit nach dem Einschalten einnimmt, und die letztlich
produktionsbedingt ist.  Damit ist das sich ergebende seed gar nicht
so zufällig, wie man sich das erhoffen könnte.

von (prx) A. K. (prx)


Lesenswert?

Peter Dannegger schrieb:

> In der text-Section stellt mir der Compiler gerne mal die
> Funktionsreihenfolge um.

Gibt's dafür nicht die Nummerierung der Init-Sektionen?

von Peter D. (peda)


Lesenswert?

Jörg Wunsch schrieb:
> Nirgends.  Wenn du paranoid bist, dann:

Das ist lustig.
Sonst bist Du doch immer derjenige, der einen darauf hinweist, daß ein 
Compiler nicht denken kann, sondern stur formal arbeitet.


> Im Übrigen wage ich die Sinnfälligkeit des ganzen Unternehmens zu
> bezweifeln.

Da stimme ich Dir zu.
Ich hatte mal versucht, einen AT89C2051 mir Warmstarterkennung zu 
machen.
Dazu habe ich vor einem Warmreset ein Muster in den SRAM geschrieben und 
nach dem Reset geprüft. Erstaunt mußte ich feststellen, daß das nicht 
funktionierte.
Selbst nach einer Minute abschalten, VCC kurzschließen, wieder 
anschalten, war der SRAM noch unverändert.

Für ne Zufallszahl würde ich nen Timer ohne Prescaler laufen lassen und 
ne Taste auf den Input-Capture-Eingang.


Peter

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Jörg Wunsch schrieb:
> Peter Dannegger schrieb:
>
>>> Nein, in diesem Falle ist es Absicht, dass das RET fehlt, denn das
>>> Ganze soll ja in die Startup-Sequenz eingebaut werden.  Da /darf/
>>> es kein CALL/RET geben, denn es gibt u. U. noch gar keinen initiali-
>>> sierten Stack.
>>
>> Dann müßte das naked aber eine Umsortierung des Codes verhindern.
>
> Nein, das RET irgendwo zwischendrin müsste wohl zu einem Sprung
> hinter das Ende umgeformt werden.  Möglicherweise ist das ja der
> Sinn des "emit_jump_insn (gen_return ());", an der Stelle blicke
> ich dann nicht mehr durch.

FYI, korrekt wäre beides. Die Lösung ist recht simpel und läuft auf 
Peters Sichtweise hinaus.

@Jörg
Der Fix zu PR42240 ist dort beschrieben, er ist recht einfach und auch 
relevant für 4.6.0. Kannst du ein Patch machen? Ich hab ja kein FSF CA. 
Wenn Fragen sind wo was zu machen ist kann ich's erklären.

gen_foo ist eine Funktion, die während des builds durch gen-tools 
erstellt wird. Die eigentliche Implementierung (afair insn-emit.c) ist 
nicht sonderlich interessant. Die Funktion wird aus RTL aus dem md 
erstellt für define_insn, define_insn_and_split oder define_expand mit 
entsprechendem Name, in diesem Falle
1
(define_insn "return"
2
  [(return)]
3
  ...

Das gen_-Präfix kommt von den gen-tools. RTL-matching geschieht ab dem 
Zeitpunkt über das Pattern und die insn-Condition, hier also
1
(define_insn "return_from_naked_epilogue"
2
  [(return)]
3
  "(reload_completed 
4
    && cfun->machine 
5
    && cfun->machine->is_naked)"
6
  ""
7
  ...

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.