Hi,
ich stehe zurzeit vor folgendem Problem:
Ich habe eine Platine mit Controller drauf, welcher ein Programm
ausführt. Dieses Basisprogramm liegt im Flash.
Nun wird von diesem Programm ein zweites Programm (bzw. eine einzelne
Funktion) über eine Schnittstelle nachgeladen und ebenfalls im Flash
abgelegt. Diese Funktion wird dann angesprungen (sie liegt an einer
konstanten, vorher bekannten Adresse).
Wenn ich von dieser Funktion nun andere Funktionen aus dem Basisprogramm
anspringen möchte, dann benötige ich deren Adresse. "Manuell" (Adresse
selbst eintippen) ist das alles kein Problem. Aber gibt es auch eine
Möglichkeit dem Linker (automatisiert) zu sagen, auf welche Adresse er
bei den Funktionsaufrufen springen soll?
Also ich würde ihm gerne die Symboltabelle oder die Memory Map oder
vergleichbares des Basisprogramms übergeben, damit er die Adressen, die
er im zweiten Programm benutzen muss, selbst erkennt! Wie ist das
möglich?
Zur Verfügung habe ich alles, vom Quellcode, über die Objectfiles bis
hin zum ELF.
Ich habe schon probiert ihm das ELF des ersten Programms zu übergeben,
aber dann ist da "zu viel" dabei, er erstellt dann ein ELF mit beiden
Programmen. Wenn ich per objcopy zuvor die .text section herauslösche,
dann fällt auch die Debuginfo/Symboltabelle mit raus und ich kann es
wieder nicht mehr linken.
Ich kann die beiden Programme übrigens nicht zur selben Zeit kompilieren
und linken, das zweite soll auch nachträglich noch veränderbar sein.
Mir ist bekannt, dass in diesem Fall unter anderem die Parameter beim
Kompilieren gleich sein müssen, damit die Programme kompatibel bleiben.
Mir ist auch bewusst, dass ich aufpassen muss, dass sich die benutzten
RAM Bereiche nicht überlappen. Also bitte keine Hinweise und
Argumentation hierüber :-)
Ja, das wäre eben die "manuelle" Methode. Ich hätte auch kein Problem
damit, das per Zusatzprogramm erledigen zu lassen.
Aber lässt sich das nicht auch automatisch erledigen? Dass er sich
selbst die jeweiligen Symbole und Adressen aus einer anderen (ELF)-Datei
holt?
linker1 schrieb:> Ich habe schon probiert ihm das ELF des ersten Programms zu übergeben,> aber dann ist da "zu viel" dabei, er erstellt dann ein ELF mit beiden> Programmen.
Aber ansonsten funktioniert das so, sprich das ELF wird wirklich so
verwendet wie es ist, und nicht womöglich "neu gelinkt"?
Dann die Funktion in eine eigene Sektion packen, und nur die aus dem
Gesamtergebnis exportieren.
Das Stichwort zu diesem Themenkomplex ist "Callback" und das Übergeben
der Adresse der Callbackfunktion an das Unterprogramm zur Laufzeit. Du
rufst beim Start eine dafür vorgesehene Funktion des Unterprogramms auf
und übergibst ihr die Adresse(n) der Callbackfunktion(en) des
Hauptprogramms, das Unterprogramm merkt sich diese im RAM für die Dauer
der restlichen Laufzeit.
Ne alternative Möglichkeit wäre daß das Hauptprogramm eine Sprungtabelle
mit Einsprungpunkten an einer einmal festgelegten genau definierten
festen Stelle im Flash errichtet.
linker1 schrieb:> Mir ist bekannt, dass in diesem Fall unter anderem die Parameter beim> Kompilieren gleich sein müssen, damit die Programme kompatibel bleiben.> Mir ist auch bewusst, dass ich aufpassen muss, dass sich die benutzten> RAM Bereiche nicht überlappen. Also bitte keine Hinweise und> Argumentation hierüber :-)
Du meinst also, du wärst dir aller möglichen Schwierigkeiten dabei
bewusst? Und du möchtest daher also auch keinen Hinweis, dass z.B. auch
für die Verwendung von switch/case in der Funktion spezielle
Vorkehrungen nötig sind?
Klingt nach einem Bootloader, der aber gleichzeitig der Applikation
bestimme Funktionalitäten (Treiber für Kommunikationsschnittstellen,
Hardwareabstraktion etc.) zur Verfügung stellen soll. Fast schon wie ein
BIOS.
Das Thema kam hier schon öfter auf und wurde imho noch nicht
zufriedenstellend gelöst. Sehr schade eigentlich, denn das wäre echt mal
nützlich.
Dem Anwendungsprogramm übergibst du dann beim Start die Adresse von
export_functions. Die Anwendung kann dann entweder die Adresse speichern
oder die Funktionstabelle in eine eigene Tabelle kopieren, damit später
der Aufruf schneller funktioniert.
Jetzt musst du in der Anwendung nur noch die Funktionsnamen entsprechend
definieren:
Natürlich musst du aufpassen, dass die Signaturen in beiden Programmen
gleich sind.
Der Ansatz hat auch den Vorteil, dass bei Neukompilieren des Bootloaders
die Anwendung gleich bleiben kann.
@sternst: Du meinst aufgrund der Umsetzung, dass das Switch-Statement in
manchen Fällen ein Array mit den Ausgabewerten in .rodata anlegt? Ja,
das hab ich schon beachtet, das wird mit reingepackt.
@prof7bit und @student: Ja, das hatte ich mir auch überlegt, aber
letztendlich verworfen. Da müsste ich alle Funktionen (und ggf. auch
Variablen), die ich mal aufrufen könnte, in diese Liste hineinpacken.
@Ben: Du triffst es ziemlich genau was ich vorhabe! Um sowas soll es
gehen.
Dank eurer Antworten bin ich nun auf die richtigen Ideen gekommen!
So funktionierts (vermutlich noch optimierbar, aber vollkommen
ausreichend!):
- .data und .bss der Basissoftware umbenennen (objcopy)
- .text und die umbenannten .data und .bss in eine andere ELF-Datei
kopieren inkl. Symbole (objcopy) [kann ggf. entfallen]
- Kompilieren und Assemblieren der c-Datei mit der Funktion (gcc), noch
nicht linken -> o-Datei
- Linken, indem an ld sowohl die zuvor vorbereitete ELF Datei übergeben
wird, als auch die .o-Datei. Hierbei -nostartfiles angeben, um Initcode
nicht einzubauen. Ein Linkerskript wird natürlich auch benötigt, mit den
jeweiligen Angaben wohin das Zeug soll. Ich verwende für das Daten und
Textsegment auch erstmal noch alternative Bezeichnungen (siehe
übernächster Schritt)
- Nun werden nur die NEUEN Sections aus der ELF herauskopiert (objcopy).
- Diese Sections werden dann in die üblichen Namen (.text, .data, .bss)
umbenannt (objcopy)
Ein paar kleinere Sachen muss ich noch optimieren, insgesamt sieht es
aber schon sehr gut aus!
Puh...
Erstmal, find ich großartig dass du dich da reinhängst, und wenn die
Lösung für dich arbeitet ist ja alles gut.
Ich selbst würde auch eine Lösung bevorzugen in der der Linker alles
automatisch macht und keine Änderungen am Code notwendig sind.
Mir ist bewusst, dass man deine Tool-Orgien im makefile automatisieren
kann.
Dennoch, ich halte deine Lösung eher für einen Hack. Da besteht auch die
Gefahr, dass du selber mal den Überblick verlierst, auch wenn jetzt noch
alles logisch klingt.
Ich hab im Kopf mal den Ansatz mit den Funktionszeigern weitergesponnen.
Im Grunde besteht ja ein "Projekt" aus mehreren Softwaremodulen.
In der Regel hat man (wenn man nicht alles in ein c-File wurschtelt) ein
oder mehrere Softwaremodule für die eigentliche Funktionalität
(Algorithmen, Zeiten berechnen, Statemachines etc.).
Darunter sitzt die Hardwareabstraktion, also Module für UART, Timer,
DIO, SPI etc.
Diese sollten ein allgemein gehaltenes Interface bieten. Sagen wir mal,
5-10 Funktionen pro Modul.
Eigentlich reicht es doch, diese Funktionen (~50 Stück) in einer
Struktur zu sammeln.
Die Struktur wird vom Bootloader befüllt und im RAM an eine feste
Adresse gelegt (der Bootloader wird bei jedem StartUp eh durchlaufen).
Deine Anwendungsmodule inkludieren nun den Header der
Hardwareabstraktion:
1
#include"Hrwrabs.h"
2
3
voidfoo(void)
4
/* hochkomplexe Berechnung */
5
6
Uart_Write(result);
7
8
/* noch eine hochkomplexe Berechnung */
9
10
Dio_SetPin(WARNLAMPE);
11
}
Deine Hardwareabstraktion sieht so aus:
1
externfunktionstabelle_tmeineFunktionen;/* liegt immer an der selben Adresse */
2
3
voidUart_Write(uint8_tc)
4
meineFunktionen.UartSchreibeFunktion(c);
5
}
6
7
voidDio_SetPin(uint8_tnumber)
8
meineFunktionen.PinSetzenFunktion(number);
9
}
Damit ist für die obere Schichten der Trick komplett verborgen.
Zugegeben, du hast einen leichten Overhead durch den relativen
Funktionsaufruf.
Ist bestimmt noch nicht fertig gedacht. Der RAM muss beispielsweise per
Linkerscript noch aufgeteilt werden, damit die Applikation nicht den
UART-Puffer wegbügelt.
Mal sehn, glaub ich werd sowas mal ausprobieren, als kleine Spielerei.