Forum: Mikrocontroller und Digitale Elektronik Frage zum compilen und linken


von Thomas (Gast)


Lesenswert?

Hallo Forum,

ich wollte mal sehen wie eine Arduino Umgebung eigentlich das *.bin-File 
zum flashen aus dem C-Code erzeugt. Jetzt weiß ich schon dass der 
Compiler Position Independent Objectcode (*.o) erzeugt und der Linker 
aus diesem und allen aufgerufenen Lib-Routinen ein Binary macht.

Nur was macht denn bei diesem Ablauf das erzeugen eines Archive Files 
firmware.a? Und warum der Umweg über ELF und nicht direkt ein Binary?

Kann mir das jemand knapp erklären?

Hier der Ablauf:
1
# Compiler
2
arm-none-eabi-gcc -c -Wall --param max-inline-insns-single=500 -mcpu=cortex-m3 -mthumb -mlong-calls -ffunction-sections -fdata-sections -nostdlib -std=c99 -Os -I/root/duebuildnew/install/due/include -I/root/duebuildnew/install/due/sam -I/root/duebuildnew/install/due/sam/libsam -I/root/duebuildnew/install/due/sam/CMSIS/CMSIS/Include -I/root/duebuildnew/install/due/sam/CMSIS/Device/ATMEL main.c -o main.o
3
4
# Erzeugt archive-File?
5
arm-none-eabi-ar rcs firmware.a main.o
6
7
# Inhalt des archives ausgeben, wohl nur zu Infozwecken
8
arm-none-eabi-nm firmware.a > firmware.a.txt
9
10
# Linker - linkt archive-File, nicht o-File?
11
arm-none-eabi-g++ -Os -Wl,--gc-sections -mcpu=cortex-m3 "-T/root/duebuildnew/install/due/sam/linker_scripts/gcc/flash.ld" "-Wl,-Map,./firmware.map" -o firmware.elf "-L" -lm -lgcc -mthumb -Wl,--cref -Wl,--check-sections -Wl,--gc-sections -Wl,--entry=Reset_Handler -Wl,--unresolved-symbols=report-all -Wl,--warn-common -Wl,--warn-section-align -Wl,--warn-unresolved-symbols -Wl,--start-group firmware.a /root/duebuildnew/install/due/lib/libsam_sam3x8e_gcc_rel.a -Wl,--end-group
12
13
# Gelinktes ELF-Format in "flaches" Binary umwandeln
14
arm-none-eabi-objcopy -O binary firmware.elf firmware.bin

von Axel S. (a-za-z0-9)


Lesenswert?

Thomas schrieb:
> ich wollte mal sehen wie eine Arduino Umgebung eigentlich das *.bin-File
> zum flashen aus dem C-Code erzeugt
...

> was macht denn bei diesem Ablauf das erzeugen eines Archive Files
> firmware.a?

Das scheint in der Tat eine Marotte der Arduino-Leute zu sein. Es gibt 
keinen offensichtlichen Grund, warum man erst alle .o Files in eine 
statische Library verfrachten sollte, um dann nachher das Executable 
daraus zu linken.

> Und warum der Umweg über ELF und nicht direkt ein Binary?

Weil der Linker nur ELF kann. ELF ist das universelle Fileformat für 
Executables und (dynamische) Libraries. Normalerweise sorgt ein 
Laufzeitsystem dafür, den Inhalt des ELF Files in den Speicher zu laden 
und Referenzen zu shared Libraries aufzulösen.

Da der µC kein Laufzeitsystem hat, wird in einem zweiten Schritt (per 
objcopy) das ELF-File so in den Speicher geladen wie es das 
Laufzeitsystem auch machen würde, und dann der Speicherinhalt als .hex 
gedumpt.

von Hannes J. (Firma: _⌨_) (pnuebergang)


Lesenswert?

Thomas schrieb:
> Nur was macht denn bei diesem Ablauf das erzeugen eines Archive Files
> firmware.a?

>
1
> # Erzeugt archive-File?
2
> arm-none-eabi-ar rcs firmware.a main.o
3
>

Ich habe einen Verdacht, kann den aber nicht belegen. Ich arbeite nicht 
mit Arduino und habe keine Lust mir einen kleinen Test zusammenzubauen. 
Wenn mein Verdacht stimmt ist das ziemlich abgefuckt.

Aus der ar man-Page:

>> ar creates an index to the symbols defined in relocatable object
>> modules in the archive when you specify the modifier s. Once created,
>> this index is updated in the archive whenever ar makes a change to its
>> contents (save for the q update operation). An archive with such an
>> index speeds up linking to the library, and allows routines in the
>> library to call each other without regard to their placement in the
>> archive.

Es könnte sein, dass die ar benutzen um sich nicht um die Reihenfolge 
der .o-Dateien auf der Linker-Kommandozeile kümmern zu müssen. So frei 
nach dem Motto "Was interessieren uns Abhängigkeiten von .o Dateien, 
geschweige denn zirkulare Referenzen?". Hauptsache alles linkt 
irgendwie. Wie gesagt, abgefuckt.

von asdfasd (Gast)


Lesenswert?

Die o-Files sind keine fertigen Binaries - die endgültigen Adressen sind 
noch nicht zugewiesen (wie auch, der Compiler weiß ja nicht, wo das 
gerade übersetzte File später landet), das macht erst der Linker. Mit 
Position-Independant-Code (PIC) hat das nichts zu tun - das ist ein 
zusätzliches (optionales) Feature der Codegenerierung (z.B. statt 
absolute Sprünge, relative Sprünge).

Die a-Files sind wirklich "Archive" - wie zip oder tar. Kannst du mit 
"ar vt foo.a" reinschauen. Ein a-File kann einen zusätzlichen Index von 
in den o-Files vorhanden Symbolen enthalten.  Dieser Index ist aber 
optional, kann nachträglich angelegt werden (s. ranlib) und dient nur 
der Performance (und ein paar Nebensächlichkeiten).

Der Linker "linkt" alle angegeben o-Files zusammen (und weist dabei 
Adressen zu). Wenn undefinierte Symbole übrig bleiben (z.B. strlen, 
printf, ...), werden die angegebenen Libraries (a-Files) nach o-Files 
durchsucht, in denen diese Symbole definiert sind. Das erste o-File, in 
dem ein Symbol gefunden wird, wird aus dem Library extrahiert und 
ebenfalls dazugelinkt.

Und hier kommt jetzt der Trick, warum einige Projekte (insb. größere mit 
mehrern Unterverzeichnissen) ihre o-Files erstmal in ein Archive packen: 
sie wissen nicht, welche o-Files alle vorhanden sind bzw welche im 
endgültigen Programm wirklich benötigt werden!

Würde sie alle o-Files direkt in der Kommandozeile angeben, würden die 
immer alle dazugelinkt, auch wenn sie nicht gebraucht werden. Sind sie 
in einem a-File, werden vom Linker automatisch nur die rauspickt, die 
benötigt werden. Als Bonus braucht der endgültige Linkerschritt nicht zu 
wissen, welche o-Files alle vorhanden sind - ein a-File pro 
(Unter-)Verzeichnis reicht.

Im Extremfall gibt es in der Kommandozeile gar keine o-Files mehr und 
nur eine Referenz auf ein Startsymbol (z.B. "Reset_Handler"). Dieses 
"undefinierte" Startsymbol führt dazu, dass der Startup-Code (in dem 
"Reset_Handler" definiert ist) aus einem Library geholt wird, der 
wiederrum hat eine Referenz auf "main", das auf "Setup" und "Loop", ...

von Thomas (Gast)


Lesenswert?

Super. Das hab ich soweit verstanden!

Es scheint wohl zu sein als wird man damit auch das Problem von 
mehrfachen Defintionen der gleichen Symbole los.

Probier ich nämlich statt dem firmware.a-File direkt das main.o File zu 
linken bekomme ich folgende Fehler:
1
main.o: In function `_init':
2
:(.text+0x318): multiple definition of `_init'
3
/usr/lib/gcc/arm-none-eabi/4.9.3/armv7-m/crti.o:(.init+0x0): first defined here
4
5
main.o: In function `_fini':
6
:(.text+0x32c): multiple definition of `_fini'
7
/usr/lib/gcc/arm-none-eabi/4.9.3/armv7-m/crti.o:(.fini+0x0): first defined here
8
/usr/lib/gcc/arm-none-eabi/4.9.3/armv7-m/crtend.o:(.tm_clone_table+0x0): multiple definition of `__TMC_END__'
9
main.o::(.relocate+0xffffffffe001033c): first defined here
10
collect2: error: ld returned 1 exit status

Ich denke also meine main.o hat über ihr inkludiertes Header-File 
('due_sam3x.init.h') schon ein Symbol '_init' gebraucht. Nun kommt aber, 
woher auch immer, nochmal das Symbol '_init' vorbei. Da bricht dann der 
Linker ab und verweigert seinen Dienst.

Mit dem a-File ist das wohl nicht passiert weil er das Symbol nur einmal 
holt wenn er es braucht, korrekt?

Hier übrigens Programm:
1
#include "/root/duebuildnew/install/due/include/due_sam3x.init.h"
2
3
/**
4
 * Simply blink the amber LED on the DUE with 2Hz:
5
 */
6
int main(void)
7
{
8
  /* The general init (clock, libc, watchdog ...) */
9
  init_controller();
10
11
  /* Board pin 13 == PB27 */
12
  PIO_Configure(PIOB, PIO_OUTPUT_1, PIO_PB27, PIO_DEFAULT);
13
14
  /* Main loop */
15
  while(1) {
16
    Sleep(250);
17
    if(PIOB->PIO_ODSR & PIO_PB27) {
18
      /* Set clear register */
19
      PIOB->PIO_CODR = PIO_PB27;
20
    } else {
21
      /* Set set register */
22
      PIOB->PIO_SODR = PIO_PB27;
23
    }
24
  }
25
  return 0;
26
}

Und Befehle:
1
# Compiler, erzeugt aus den *.c-Files *.o-Files mit ARM Programmcode
2
arm-none-eabi-gcc -c -Wall --param max-inline-insns-single=500 -mcpu=cortex-m3 -mthumb -mlong-calls -ffunction-sections -fdata-sections -nostdlib -std=c99 -Os -I/root/duebuildnew/install/due/include -I/root/duebuildnew/install/due/sam -I/root/duebuildnew/install/due/sam/libsam -I/root/duebuildnew/install/due/sam/CMSIS/CMSIS/Include -I/root/duebuildnew/install/due/sam/CMSIS/Device/ATMEL main.c -o main.o
3
4
# Linker - linkt alle nötigen o-Files (auch die aus Librarys) zu einer ausführebaren Datei (*.elf)
5
arm-none-eabi-g++ -Os -Wl,--gc-sections -mcpu=cortex-m3 "-T/root/duebuildnew/install/due/sam/linker_scripts/gcc/flash.ld" "-Wl,-Map,./firmware.map" -o firmware.elf "-L" -lm -lgcc -mthumb -Wl,--cref -Wl,--check-sections -Wl,--gc-sections -Wl,--entry=Reset_Handler -Wl,--unresolved-symbols=report-all -Wl,--warn-common -Wl,--warn-section-align -Wl,--warn-unresolved-symbols -Wl,--start-group main.o /root/duebuildnew/install/due/lib/libsam_sam3x8e_gcc_rel.a -Wl,--end-group
6
7
# Gelinktes ELF-Format in "flaches" Binary umwandeln (quasie 'loaden', denn der uc hat ja keinen Loader wie Linux)
8
arm-none-eabi-objcopy -O binary firmware.elf firmware.bin

Irgendjemand eine Idee wie ich jetzt um das Problem herum komme?

Für jemanden der simple 8-Bit AVR gewohnt ist, ist diese ganze ARM 
Umgebung mit gefühlten tausenden Librarys die nötig sind ziemlich 
kompliziert zu durchschauen.

Danke schonmal für die Hilfe übrigens!

Thomas

von Sheeva P. (sheevaplug)


Lesenswert?

Thomas schrieb:
> Hier übrigens Programm:#include
> "/root/duebuildnew/install/due/include/due_sam3x.init.h"

Du hast die Arduino-IDE doch nicht unter root installiert, oder?

von Thomas (Gast)


Lesenswert?

Ich habe die IDE garnicht installiert, sondern nur Stück für Stück die 
bbhängigen Archive aus dem Boardmanager zusammengesucht :-)

Das ganze ist ein vServer der in kürze wieder platt gemacht wird.
Es gibt also dort:
a) Garkeine GUI für Arduino,
b) Keinerlei Serverprozesse die Verbindungen erlauben (bis auf SSH), und
c) ein sicheres root-Passwort

Ich denke das Risiko ist relativ gering, trotzdem danke für den Hinweis!

Thomas

von Jim M. (turboj)


Lesenswert?

Thomas schrieb:
> und warum der Umweg über ELF und nicht direkt ein Binary?

Das .elf kann man debuggen, wenn Debug Symbole mit eingelinkt wurden.

Außerdem ist das das default Target für ld. ELF hätte auch den Vorteil, 
dass man "Löcher" - also Lücken im Addressraum - benutzen kann. Das geht 
z.B. auch mit Intel Hex, aber nicht mit binary.

Übrigens sollte man in der Linker Befehlszeile auch den CPU Typ mit 
angeben, das braucht er zum Auswählen der richtigen Multilib-Variante.

von Thomas (Gast)


Lesenswert?

Ich konnte das Problem jetzt lösen indem ich in der main.c aus:

>>> init_controller();

die entsprechenden einzelnen Kommandos gemacht habe:

>>> SystemInit();
>>> if (SysTick_Config(SystemCoreClock / 1000)) while (1);
>>> WDT_Disable(WDT);
>>> __libc_init_array();

Nun linkt es und funktioniert auch !

Vielen Dank hier nochmal für die Hilfe!

Thomas

von Sheeva P. (sheevaplug)


Lesenswert?

Thomas schrieb:
> Ich habe die IDE garnicht installiert, sondern nur Stück für Stück die
> bbhängigen Archive aus dem Boardmanager zusammengesucht :-)
>
> Das ganze ist ein vServer der in kürze wieder platt gemacht wird.
> Es gibt also dort:
> a) Garkeine GUI für Arduino,
> b) Keinerlei Serverprozesse die Verbindungen erlauben (bis auf SSH), und
> c) ein sicheres root-Passwort
>
> Ich denke das Risiko ist relativ gering, trotzdem danke für den Hinweis!

Dazu vielleicht noch als kleine Tipps: per SSH weder root- noch Logins 
mit einem Paßwort erlauben, sondern nur als Benutzer per 
zertifikatsbasierter Authentifizierung und sich dann erst mit "su -" 
oder "sudo su -" eventuell notwendige Privilegien verschaffen.

Außerdem gibt es in Ubuntu (zumindest in der 16.04 LTS) ein Paket namens 
"arduino-mk", das eine Reihe Beispiel-Makefiles für die Programmierung 
von Arduinos auf der Kommandozeile enthält und alle nötigen 
Abhängigkeiten mit (arduino-core mit den C++-Libraries, der Referenz und 
den Beispielen, und über weitere Abhängigkeiten den gcc sowie gcc-avr) 
installiert, da mußt Du nichts manuell zusammensuchen. Vermutlich findet 
man das auch in Debian, Linux Mint und anderen Debian-basierten 
Linux-Distributionen.

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.