Forum: Compiler & IDEs ARM Startup Code mit GNU Toolchain


von Holger J. (trac3r)


Lesenswert?

Hallo,

ich suche schon seit Wochen eine vernünftige Grundlage, um zu verstehen, 
wie ARM Startup Code in GNU Assembler oder mit dem GCC geschrieben wird. 
Ich finde zwar diverse Beispiele und Tutorials, aber leider wird mir 
daraus nicht klarer, warum der Code funktioniert und welche Anpassungen 
ich für z.B. einen STM32 vornehmen muss.

Prinzipiell bin mir zwar darüber bewusst, was alles zu tun ist. 
Allerdings ist mein Problem, dass die richtigen Anleitungen von ARM und 
ST selbst, sich komplett auf ARM Assembler beziehen. Und scheinbar kann 
man das auch nicht 1:1 portieren, das hab ich nämlich schon versucht. 
Obwohl lt. dem Mapping der Stack und Einsprungpunkt an der richtigen 
Stelle zu liegen scheinen, läuft ein mit GNU übersetztes Discovery-Board 
Beispiel für Keil, bei dem außer dem Startcode und Linkerscript nichts 
geändert wurde, bei meinen Versuchen leider nicht. Darin wollte ich 
erstmal ein paar grundlegende Sachen ausprobieren, bevor hier Sachen in 
Hardware gegossen werden.

Bei meiner Suche bin ich bisher natürlich gestolpert über
* 
http://www.embedded.com/design/mcus-processors-and-socs/4026075/Building-Bare-Metal-ARM-Systems-with-GNU-Part-2
* den STM32-Artikel (Abschnitt Startup code & Linker Script)
* den Beitrag "ARM startup Code und Linkerscripte" im Forum, wegen dem 
ich u.a. auch lieber den Code selber schreiben möchte.

Das hat mich alles schon weiter gebracht, allerdings gibt es halt einen 
Unterschied, zwischen "Codeblöcke zusammenklauben" und verstehen, was 
man da eigentlich macht. Und ich würde es wirklich gern verstehen, schon 
um den Fehler in meinem Startup Code finden zu können.

Also nochmal konkret die Frage: Gibt es irgendwo ein/e 
Sachbuch/Anleitung, welches das schreiben von Startcode in GNU Assembler 
oder via GCC so erklärt, dass es jemand, der schon ein paar Jahre C/C++ 
programmiert hat und bisher nur für AVR hardwarenah programmiert hat? Zu 
welcher Plattform diese Anleitung ist, wäre egal (x86 oder so), sofern 
die ARM spezifischen Anweisungsblöcke auch noch irgendwo anders 
behandelt werden.

Schönen Gruß und danke für Anworten.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Holger J. schrieb:
> Gibt es irgendwo ein/e
> Sachbuch/Anleitung, welches das schreiben von Startcode in GNU Assembler
> oder via GCC so erklärt, dass es jemand, der schon ein paar Jahre C/C++
> programmiert hat und bisher nur für AVR hardwarenah programmiert hat?
Das wäre ein dünnes Buch...

Der startupcode von ST ist aus irgendwelchen Gründen in Assembler, kann 
aber auch genauso gut wunderbar in C geschrieben werden:
1
extern uint32_t _sidata, _sdata, _edata, _sbss, _ebss;
2
3
void __attribute__((naked)) Reset_Handler () {
4
  memcpy (&_sdata, &_sidata, &_edata-&_sdata); // Globale Variablen initialisieren
5
  memset (&_sbss, &_ebss-&_sbss, 0); // Globale 0-Variablen mit 0 initialisieren
6
  __libc_init_array (); // Konstruktoren globaler Objekte aufrufen
7
  main (); // Programm starten.
8
}
Verstanden?

Die lustigen Symbole kommen aus dem Linkerscript, so wie in dem von ST.

von Adib (Gast)


Lesenswert?

Erlkönig,

das ist ja mal ein kurzer Code.
In den Beispielen der ST Library sind noch FPU initialisierung und der 
Aufruf von SystemInit();

my 2ct.

Adib.
--

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Adib schrieb:
> In den Beispielen der ST Library sind noch FPU initialisierung und der
> Aufruf von SystemInit();
Stimmt, das gehört aber meiner Meinung nach nicht zum Startupcode 
sondern zur main()... Aber ein Funktionsaufruf mehr und die 
FPU-Initialisierung (siehe ARM GCC) machen den Braten jetzt auch 
nicht fett.

PS: Oben im Code ist noch was falsch, man muss die Pointer erst nach 
char* casten und dann subtrahieren, sonst ist das Ergebnis nicht in 
bytes sondern Words...

Allgemein ist der ARMv7M so gebaut dass man direkt mit C Code starten 
kann und keinen Assembler braucht; man muss eben nur den Programmstatus 
(globale Variablen, Konstruktoren) initialisieren.

von Holger J. (trac3r)


Lesenswert?

Niklas Gürtler schrieb:
> Holger J. schrieb:
>> Gibt es irgendwo ein/e Sachbuch/Anleitung
> Das wäre ein dünnes Buch...

Das wär mir egal, solange es verständlich ist und sich nicht 
irgendwelchen fertigen Bibliotheken bedient. Denn dann hab ich wieder 
nix gewonnen.

>
> Der startupcode von ST ist aus irgendwelchen Gründen in Assembler, kann
> aber auch genauso gut wunderbar in C geschrieben werden:
> [...]
> Verstanden?

Nur bedingt. Die Zusammenarbeit von Linkerscript und deinem Code ist mir 
im Grunde klar, aber mir geht es ja gerade darum, beides anhand der 
Prozessordokumentation selbst schreiben zu können. Egal ob in Assembler 
oder C. Aber eben unabhängig von etwaigen fertigen Toolchains. Zur Not 
möchte ich es mit make selbst zusammengebaut bekommen und als einziges 
notweniges Übel auf einen USB Treiber zum Flashen zurückgreifen müssen, 
der nicht von mir stammt. Das spielt nämlich keine Rolle, da sich die 
Schnittstelle bei eigener Hardware sowieso zu JTAG ändern wird und dafür 
gibt es hier schon eine Lösung.

> Die lustigen Symbole kommen aus dem Linkerscript, so wie in dem von ST.

Was ist "das ST Linkerscript"? Ich habe mir die Standard Peripherals 
Library von ST heruntergeladen und dort finde ich zwar diverse 
Linkerscripts für IAR, RealView, Ride und das im STM32 Artikel erwähnte 
TrueStudio, aber nichts für den GNU ld/cc selbst. So bringt mir das auch 
wieder nichts, weil es teilweise nicht frei verwendet werden darf (oder 
nur eingeschränkt), s. oben, und plattformunabhängig bin ich damit auch 
nicht. Ohne auf irgendwas fertiges zurückzugreifen, steh ich momentan 
wie vor einer Wand. Das ist ziemlich frustrierend.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

> Das wär mir egal, solange es verständlich ist und sich nicht
> irgendwelchen fertigen Bibliotheken bedient. Denn dann hab ich wieder
> nix gewonnen.
Mein Code benutzt nur die Standard C library, ich denke die kann man als 
bekannt vorrausetzen. Wenn du memcpy und memset nicht kennst, kannst du 
danach googlen und eine einfache Beispiel-Implementation finden. 
__libc_init_array () ist eine Funktion der C Library die durch ein 
globales Array aus Funktionenpointern geht und diese Aufruft; dieses 
Array wird vom Compiler & Linker gefüllt und enthält globale 
Konstruktoren. Du kannst dir im newlib Source die Implementation davon 
ansehen.
> Nur bedingt. Die Zusammenarbeit von Linkerscript und deinem Code ist mir
> im Grunde klar, aber mir geht es ja gerade darum, beides anhand der
> Prozessordokumentation selbst schreiben zu können.
Die Prozessordokumentation bringt dir hier praktisch gar nichts, sondern 
eher die von ld, binutils und GCC.
> Egal ob in Assembler
> oder C. Aber eben unabhängig von etwaigen fertigen Toolchains.
Ohne Compiler und Linker machen Dinge wie "C Source" und "Linkerscript" 
keinen Sinn, also kannst du die auch so nicht verfassen. Ohne toolchain 
kannst du nur im Hexeditor das Flash-Image von Hand schreiben, dann 
brauchst du auch keinen Startup Code und kein Linkerscript.
> Was ist "das ST Linkerscript"?
ST's Beispiel-Linkerscript für den GCC.
> Ich habe mir die Standard Peripherals
> Library von ST heruntergeladen und dort finde ich zwar diverse
> Linkerscripts für IAR, RealView, Ride und das im STM32 Artikel erwähnte
> TrueStudio, aber nichts für den GNU ld/cc selbst.
Das TrueStudio ist nur eine GUI die den GCC aufruft. Das Linkerscript 
dafür ist also das was du brauchst.
> So bringt mir das auch
> wieder nichts, weil es teilweise nicht frei verwendet werden darf (oder
> nur eingeschränkt),
Musst halt danach selber nochmal schreiben...
> s. oben, und plattformunabhängig bin ich damit auch
> nicht. Ohne auf irgendwas fertiges zurückzugreifen, steh ich momentan
> wie vor einer Wand. Das ist ziemlich frustrierend.
Wenn du nichts fertiges willst, musst du GNU ld's Doku über 
Linkerscripte lesen. ST's Linkerscript für den GCC ist von der 
Zielplattform abhängig, aber die Compiler-Plattform ist egal.
> Ohne auf irgendwas fertiges zurückzugreifen, steh ich momentan
> wie vor einer Wand. Das ist ziemlich frustrierend.
Wenn du unbedingt dein eigenes Linkerscript schreiben willst (aber die 
Doku dazu nicht findest) stell dich auf noch viel mehr Frust ein. 
Weniger Frust ist es, das fertige zu verwenden.

: Bearbeitet durch User
von Holger J. (trac3r)


Lesenswert?

Niklas Gürtler schrieb:
> __libc_init_array () ist eine Funktion der C Library die durch ein
> globales Array aus Funktionenpointern geht und diese Aufruft; dieses
> Array wird vom Compiler & Linker gefüllt und enthält globale
> Konstruktoren. Du kannst dir im newlib Source die Implementation davon
> ansehen.

Ok, die war mir tatsächlich neu und bisher in den 
Startup-Code-Beispielen nicht bewusst über den Weg gelaufen.

> Die Prozessordokumentation bringt dir hier praktisch gar nichts, sondern
> eher die von ld, binutils und GCC.

Genau das hab ich auch getan. Und damit auch irgendwas zusammengebaut, 
dass dann aber nicht läuft. Aber gut, dann werde ich mich halt mehr mit 
diesem Thema auseinander setzen müssen und hoffen, dass ich es doch noch 
hinbekomme.

> Das TrueStudio ist nur eine GUI die den GCC aufruft. Das Linkerscript
> dafür ist also das was du brauchst.

Ich hab mir das Ding oberflächlich angesehen gehabt. Und zuerst dachte 
ich wegen der Beschreibung, dass es genau wie beim Keil seinen eigenen 
Compiler, etc. mitbringt. Beim Keil kann man zwar auch den GCC als 
andere Toolchain auswählen, aber mit dieser laufen die mitgelieferten 
Beispiele ohne die Anpassungen nicht, nach denen ich bisher erfolglos 
gesucht habe. Ich werf also mal dort einen Blick rein.

Danke.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Holger J. schrieb:
> Ok, die war mir tatsächlich neu und bisher in den
> Startup-Code-Beispielen nicht bewusst über den Weg gelaufen.
Hm? Das ist sogar im Startupcode von ST für Atollic drin.
> Genau das hab ich auch getan. Und damit auch irgendwas zusammengebaut,
> dass dann aber nicht läuft. Aber gut, dann werde ich mich halt mehr mit
> diesem Thema auseinander setzen müssen und hoffen, dass ich es doch noch
> hinbekomme.
Das von 0 auf anzufangen ist wohl ziemlich schwierig; einfacher ist es 
die vorhandenen Linkerscripte & Startupcode mithilfe der ld/GCC Doku zu 
analysieren und zu sehen wie sie arbeiten.
> Ich hab mir das Ding oberflächlich angesehen gehabt. Und zuerst dachte
> ich wegen der Beschreibung, dass es genau wie beim Keil seinen eigenen
> Compiler, etc. mitbringt.
Heh, das machen nur Keil und mikroE, alle anderen sind nur GUI's für 
eben diese Compiler...

PS: Linkerscripte & Startupcode schreiben ist halt nicht das was dem 
gewöhnlichen Anwendungs-Programmierer zugemutet wird - für die gibts 
"Installiere Keil" - deswegen gibts da keine/wenig einfache 
Dokumentation/Literatur. Da muss man halt in der LD doku etc. graben.

: Bearbeitet durch User
von W.S. (Gast)


Lesenswert?

Holger J. schrieb:
> Prinzipiell bin mir zwar darüber bewusst, was alles zu tun ist.

Bist du dir sicher?

Ich bin eher vom Gegenteil überzeugt.

Schau einfach mal, was sich so in einem Startup-Code für einen ARM7TDMI 
und in einem für einen Cortex so befindet.

Beim ARM7TDMI gibt es dort das Aufsetzen de verschiedenen Stacks, den 
Starter für "main" und die eigentlichen Interrupt-Programme, beim Cortex 
den Starter für "main" und die als "weak" gekennzeichneten 
Default-Adressen für die Interrupts.

Eigentlich ist ein Startup-Code ne interessante Sache, aber man sollte 
wirklich wenigstens ein bissel von der Hardware verstehen.

W.S.

von Dr. Sommer (Gast)


Lesenswert?

W.S. schrieb:
> beim Cortex
> den Starter für "main" und die als "weak" gekennzeichneten
> Default-Adressen für die Interrupts.
Unsinn, das "Aufsetzen der Interrupt-Adressen" geschieht durchs Flashen, 
d.h. dadurch, dass man die Tabelle der ISR's an eine bestimmte Stelle in 
den Flash packt (bei STM32 z.B. ab 0x4, aber allgemein 
Implementations-spezifisch). Damit hat der Startupcode gar nichts zu 
tun. Für die main() muss er ebenfalls nichts aufsetzen, sondern die 
lediglich aufrufen.
> Eigentlich ist ein Startup-Code ne interessante Sache, aber man sollte
> wirklich wenigstens ein bissel von der Hardware verstehen.
Wenn die Hardware da bestimmte Bedürfnisse hat ja; gerade bei den ARMv7M 
(Cortex-M3,4) brauchts quasi nur Wissen über den Compiler & Linker, da 
der Prozessor sich selbst initialisiert, man muss nur das eigene 
High-Level C(++) Anwendungs-Gedöns initialisieren.

von Philipp Klaus K. (pkk)


Lesenswert?

Niklas G. schrieb:
> Holger J. schrieb:
>> Gibt es irgendwo ein/e
>> Sachbuch/Anleitung, welches das schreiben von Startcode in GNU Assembler
>> oder via GCC so erklärt, dass es jemand, der schon ein paar Jahre C/C++
>> programmiert hat und bisher nur für AVR hardwarenah programmiert hat?
> Das wäre ein dünnes Buch...
>
> Der startupcode von ST ist aus irgendwelchen Gründen in Assembler, kann
> aber auch genauso gut wunderbar in C geschrieben werden:
>
1
extern uint32_t _sidata, _sdata, _edata, _sbss, _ebss;
2
> 
3
> void __attribute__((naked)) Reset_Handler () {
4
>   memcpy (&_sdata, &_sidata, &_edata-&_sdata); // Globale Variablen 
5
> initialisieren
6
>   memset (&_sbss, &_ebss-&_sbss, 0); // Globale 0-Variablen mit 0 
7
> initialisieren
8
>   __libc_init_array (); // Konstruktoren globaler Objekte aufrufen
9
>   main (); // Programm starten.
10
> }
> Verstanden?
>
> Die lustigen Symbole kommen aus dem Linkerscript, so wie in dem von ST.

Im obigen Code sind ein paar Fehler, die, da der Thread prominent in den 
Suchergebnissen zu STM32 startup-code auftaucht, besser korrigiert 
werden.
1
extern unsigned char _sidata, _sdata, _edata, _sbss, _ebss, _estack;
2
3
void __attribute__((naked)) __libc_init_array ();
4
5
void __attribute__((naked)) Reset_Handler () {
6
  memcpy (&_sdata, &_sidata, &_edata - &_sdata);
7
  memset (&_sbss, 0, &_ebss - &_sbss);
8
  __libc_init_array ();
9
  main ();
10
}

Die vertauschten Parameter bei memset() sind leicht zu bemerken. Aber 
der Typ von _sidata, etc verursacht subtilere Fehler, da das erste 
Viertel der Variablen noch richtig initialisiert wird.

Philipp

von Bernd K. (prof7bit)


Lesenswert?

Das allein reicht aber noch nicht, es fehlt auch noch eine 
Vektortabelle, mindestens eine mit den ersten 8 Bytes (Stack und Reset) 
damit er loslaufen kann.

Hier möch ich auch noch ein ballastfreies aber vollständiges Beispiel 
von mir beitragen: https://github.com/prof7bit/bare_metal_stm32f401xe

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Philipp Klaus K. schrieb:
>   &_edata - &_sdata

Ist das korrektes C?  Standard sagt:

"When two pointers are subtracted, both shall point to elements of the 
same array object, or one past the last element of the array object"

was im Code nicht der Fall ist.  Also besser auch die Differenz im 
ld-Script berechnen und als Symbol verfügbar machen.

von Philipp Klaus K. (pkk)


Lesenswert?

Johann L. schrieb:
> Philipp Klaus K. schrieb:
>>   &_edata - &_sdata
>
> Ist das korrektes C?

So lange es funktioniert würde ich diese Frage für diesen Fall nicht 
als so wichtig erachten.
So etwas wie startup-code ist im C-Standard eh nicht bedacht, 
genausowenig wie ein __attribute__((naked)), oder eine Funktion namens 
__libc_init_array () aufzurufen. All das ist bestenfalls von der 
Implementierung definiertes Verhalten, schlimmstenfalls ein 
"funktioniert halt irgendwie".

Aus Sicht des C-Standards beginnt die Ausführung des Programms bei 
main().

Philipp

von Bernd K. (prof7bit)


Lesenswert?

Wenn man kein C++ machen will kann man den Aufruf von 
__libc_init_array() getrost weglassen. Das ruft nämlich nur die 
Konstruktoren von statischen C++ Objekten auf damit die alle 
initialisiert sind bevor main() beginnt. In C existiert sowas nicht.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Bernd K. schrieb:
> In C existiert sowas nicht.

Ist aber auch in GNU-C nutzbar per __attribute__((constructor))

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.