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 :-)
Im Linkerscript Symbole für alle Funktionen definieren a la: basisprogramm_funktion1 = 0xdeadbeef;
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.
:
Bearbeitet durch User
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.
Der Ansatz von Bernd K. kligt doch vielversprechwend. Im Bootloader hast du eine Tabelle mit Funktionsadressen:
1 | void* export_functions[N] { = (void*) function1, (void*) function2, ... } |
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:
1 | #define function1 ((void (*) (int)) export_functions[0]) // void function1(int)
|
2 | |
3 | #define function2 ((int (*) (int, int)) export_functions[1]) // int function2(int, int)
|
4 | |
5 | ...
|
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 | void foo(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 | extern funktionstabelle_t meineFunktionen; /* liegt immer an der selben Adresse */ |
2 | |
3 | void Uart_Write(uint8_t c) |
4 | meineFunktionen.UartSchreibeFunktion(c); |
5 | }
|
6 | |
7 | void Dio_SetPin(uint8_t number) |
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.
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.