Forum: Compiler & IDEs Programm auf Funktionen eines anderen Programms zugreifen lassen


von linker1 (Gast)


Lesenswert?

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 :-)

von Dr. Sommer (Gast)


Lesenswert?

Im Linkerscript Symbole für alle Funktionen definieren a la:
basisprogramm_funktion1 = 0xdeadbeef;

von linker1 (Gast)


Lesenswert?

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?

von Stefan E. (sternst)


Lesenswert?

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.

von Bernd K. (prof7bit)


Lesenswert?

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
von Stefan E. (sternst)


Lesenswert?

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?

von Ben (Gast)


Lesenswert?

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.

von Student (Gast)


Lesenswert?

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.

von linker1 (Gast)


Lesenswert?

@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!

von Ben (Gast)


Lesenswert?

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
Noch kein Account? Hier anmelden.