Forum: Mikrocontroller und Digitale Elektronik ARM mit newlib-Startup benutzen


von Nase (Gast)


Lesenswert?

Hallo,

ich verwende auf einem ARM die newlib zusammen mit der gcc-basierten 
Toolchain direkt von ARM. Das funktioniert soweit.

Aktuell arbeite ich mit einem eigenen Linkerskript und eigenem 
Startup-Code  und binde das Ganze entsprechend mit -nostartfiles 
zusammen. Den konkreten Compiler binde ich aktuell so ein:
* In meinem Linkerskript baue ich die generischen .init/.fini-Sektionen 
zusammen, aus crti.o/crtn.o und so weiter.
* Ich packe die .preinit_array, .init_array und .fini_array-Sektionen 
mit ein.
* Im Startup-Code wird die .data-Sektion ins RAM reloziert und die 
.bss-Sektion genullt.
* Danach werden die Funktionen aus der .preinit_array-Sektion 
aufgerufen.
* Dann wird _init() aufgerufen.
* Dann die Funktionen aus der .init_array-Sektion.
* Schließlich ein Sprung in die main()-Funktion.

Nun gibt es in der newlib ja schon __libc_init_array, die sich um die 
Initialisierer kümmert, und auch ein Startup-Code und ein Linkerskript 
ist dabei. Grundsätzlich finde ich es nämlich reizvoll, nicht händisch 
den Compiler zu integrieren, sondern möglichst viel von der Toolchain zu 
verwerten.

Wie macht man das denn mit newlib?
Ich frage mich z.B., wie ich das mitgelieferte Linkerskript benutze, 
denn irgendwie müsste ich ja zumindest ein Speicherlayout anpassen 
(MEMORY{}-Abschnitt)...

Soweit ich weiß reloziert der Startup-Code aus der newlib auch die 
.data-Sektion nicht ins RAM. Die .bss-Sektion dagegen wird genullt. 
Ratio dahinter war, dass die .data-Sektion ja im Programmabbild drin ist 
und daher vom dynamischen Linker mit geladen wird. Für .bss steht aber 
nichts im Programmabbild, daher nullt der Startup das selbst. Den Linker 
gibts natürlich bei bare-metal-ARM so nicht.

Danke und Grüße,
Nase

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Nase schrieb:
> Soweit ich weiß reloziert der Startup-Code aus der newlib auch die
> .data-Sektion nicht ins RAM. Die .bss-Sektion dagegen wird genullt.
> Ratio dahinter war, dass die .data-Sektion ja im Programmabbild drin ist
> und daher vom dynamischen Linker mit geladen wird.

hmmm.  Beim default Linker-Script kümmert sich eigentlich der Linker ums 
richtige Script; deshalb gibt's auch diesen Zoo von .x, .xu, .xr, .xe, 
.xn, .xbn  die abhängig davon, welcher Link erfolgt (z.B. -r) verwendet 
werden.  Eine Übersicht, worin die sich unterscheiden, gibt das Template 
bei Binutils: abhängig z.B. von $RELOCATING und $CONSTRUCTING

http://sourceware.org/git/gitweb.cgi?p=binutils-gdb.git;a=blob;f=ld/scripttempl/armbpabi.sc;h=a12cd3d2343ecad9f66cda8179811f71a26a09b9;hb=HEAD

Wenn man hart ein Script vorgibt, kann so ein Mechanismus natürlich 
nicht mehr wirken; da muss dann der Anwender wissen, was in seinem 
Script steht und wann er es verwendet oder nicht.

Was durchaus praktikabel sein kann, ist ein Linker-Script zu 
verwenden, und dies dann durch verschiedene memory.x zu parametrisieren. 
Wenn der Compiler(treiber) mit ner Datei nix anfangen kann, gibt er sie 
dem Linker weiter, und dieser interpretiert sie dann.  Hier ein Beispiel 
für avr, einfach das memory.x ohne weiteres bei arm-gcc mit angeben:
1
OUTPUT_FORMAT("elf32-avr","elf32-avr","elf32-avr")
2
OUTPUT_ARCH(avr:103)
3
4
FOO = 23;
5
__RODATA_PM_OFFSET__ = 0x9000;
Die so definierten Symbole können dann im ld-Script verwendet werden, 
oder gleich ne
1
MEMORY
2
{
3
}
ins memory.x.  So ist es dann übersichtlicher, wenn man mehrere 
unterschiedliche aber hinreichend gleiche Devices und Links hat.

von Nase (Gast)


Lesenswert?

Johann L. schrieb:
> So ist es dann übersichtlicher, wenn man mehrere
> unterschiedliche aber hinreichend gleiche Devices und Links hat.
Das war schonmal ein wichtiger Hinweis...

So wie ich das Template da verstehe, eignet es sich aber eigentlich 
garnicht für bare-metal-Anwendungen: Der Linkerscript-Zoo, der mit der 
newlib für ARM mitkommt, geht offenbar davon aus, dass alles im 
.text-Segment landet und vom LD ins RAM geladen wird.
Realistisch wäre ja für meine Anwendung, dass ich z.B. die .data-Sektion 
zweimal einbaue, nämlich einmal als Platzhalter im RAM und einmal mit 
den Daten im FLASH.

von Karl (Gast)


Lesenswert?

Was ist genau die Frage?
Ich hab ein ganz ähnliches Setup. ARM Cortex bare Metal. Linker Skript 
und Start-up Code sind selbst gezimmert. Newlib hat damit erst Mal wenig 
zu tun, oder?

Gehen tut es also und es macht immer dann Sinn wenn man etwas mehr 
Kontrolle über den Aufbau haben möchte, z.b. für bootloader, 
verschlüsselte sections die beim Start-up entschlüsselt werden, 
effektive und effiziente Nutzung von CCM, Nutzung der remap Funktion, 
die RAM im flash einblenden, Speicher mit verschiedenen cache Attributen 
etc.

Mein Start-up Code ist übrigens in c und nicht in asm geschrieben. 
Vereinfacht das ganze doch erheblich.

von Nase (Gast)


Lesenswert?

Karl schrieb:
> Was ist genau die Frage?
Wie integriere ich den Compiler in mein Projekt?

> Ich hab ein ganz ähnliches Setup. ARM Cortex bare Metal. Linker Skript
> und Start-up Code sind selbst gezimmert. Newlib hat damit erst Mal wenig
> zu tun, oder?
Nunja, sie hat etwas damit zu tun, wie der Compiler mit seiner 
Laufzeitumgebung zusammenkommt.

Aktuell arbeite ich auch mit einem eigenen Linkerskript. Das hat aber 
zur Folge, dass ich mir recht mühsam zusammengesucht habe, welche 
Sektionen der Compiler erzeugt und warum. Das z.B. meine ich mit 
Compiler-Integration. Einen Teil gibt das ABI ja vor (bei ARM ist das 
u.a. die .init_array-Sektion). Ein anderer Teil ist aber 
Compiler-spezifisch, etwa dir .ctors-Sektion oder der ganze Relo-Kram.
Schließlich gibt es noch den Architektur- oder Boardspezifische Teil, 
also beispielsweise die Speicherkonfiguration, die ja bestenfalls davon 
abhängt, was auf die Platine gelötet wird.

Ich frage mich nun eben, wie ich wenigstens den Compiler-spezifischen 
Kram loswerde.

von Nase (Gast)


Lesenswert?

Karl schrieb:
> Mein Start-up Code ist übrigens in c und nicht in asm geschrieben.
> Vereinfacht das ganze doch erheblich.
Gibt es da nicht ein Henne-Ei-Problem?

Im Prinzip könnte sich der C-Startup-Code ja schon auf Dinge verlassen, 
die im Startup-Code erledigt werden, sprich genulltes .bss und geladenes 
.data. Wenn der Compiler den C-Startup-Code übersetzt, weiß er ja nichts 
davon, dass diese Zusicherungen noch garnicht erfüllt wurden. Bei der 
Generierung sollte er ja (eigentlich korrekterweise) annehmen, dass die 
Laufzeitumgebung schon steht.

von BLoetmichel (Gast)


Lesenswert?

Wenn dir C zu kompliziert ist, nimm BASCOM.
Mache ich ja auch.

von Christopher J. (christopher_j23)


Lesenswert?

Nase schrieb:
> Gibt es da nicht ein Henne-Ei-Problem?
>
> Im Prinzip könnte sich der C-Startup-Code ja schon auf Dinge verlassen,
> die im Startup-Code erledigt werden, sprich genulltes .bss und geladenes
> .data.

Jein. Theoretisch hast du Recht aber rein praktisch gibt es keinen Grund 
warum man für das nullen von .bss und initialisieren von .data lesenden 
Zugriff auf das RAM brauchen sollte und deshalb wird auch (passender 
C-Code vorrausgesetzt) kein Compiler auf die Idee kommen das zu tun.

von Karl (Gast)


Lesenswert?

Nase schrieb:
> Gibt es da nicht ein Henne-Ei-Problem?
Nicht, dass ich wüsste.
> Im Prinzip könnte sich der C-Startup-Code ja schon auf Dinge verlassen,
> die im Startup-Code erledigt werden, sprich genulltes .bss und geladenes
> .data. Wenn der Compiler den C-Startup-Code übersetzt, weiß er ja nichts
> davon, dass diese Zusicherungen noch garnicht erfüllt wurden. Bei der
> Generierung sollte er ja (eigentlich korrekterweise) annehmen, dass die
> Laufzeitumgebung schon steht.

Ja, aber der Startup Code nutzt nur Variablen auf dem Stack bzw. In 
Registern und Read only Daten die der linker erzeugt. Bei denen ist lma 
gleich vma.

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.