Hallo,
für ein SWD-Debugger-Project, würde ich mir wünschen, auf den Debugger
(Cortex-M4 µController), je nach erkannten, zu debuggenden Controller
Code nach zu laden (z.B. lesen aller Register der CPU).
Die Firmware des Debuggers steht inklusive Symbolen in dem finalen elf
file (GCC). Nun würde ich gerne zusätzlichen Quellcode so übersetzen und
linken, dass der Linker bei Funktionsaufrufen die Adressen der
Funktionen aus dem elf file der Firmware des Debuggers nimmt, so dass
aus dem zusätzlichen Quellcode ein binary endsteht, dass sich in das RAM
des Debuggers laden läßt und Ausführen läßt. Bei der Ausführung dieses
nachgeladenen Teils, soll dieser nachgeladene Teil dann auch Funktionen,
die in der Firmware des Debuggers liegen aufrufen können.
Hat jemand von euch soetwas schon mal gemacht? Ideen? Anmerkungen?
schöne Grüße,
Torsten
Torsten R. schrieb:> Ähnliche Probleme müssen eigentlich auftreten, wenn man z.b. gegen code> in Masken-ROMs linken möchte und nur die Adressen der Funktionen hat.
In C steht es dir frei Funktionspointer zu definieren die an eine
beliebige Adresse zeigen. Das Mitgeben von Parametern auf Stack oder im
RAM muss halt zwischen den beiden Teilen identisch sein (oder man
pfuscht es manuell so hin, böse).
Für C++ muss man Mangling dann aber händisch regeln oder per extern "C":
1
extern"C"voidled_on();
Das ist aber kein nachladbarer Code.
Nachladbaren Code müsste dann ein dynamischer Linker lokatieren.
Oder man generiert / lädt relokatiblen Code, was aber einiges an
Einschränkungen mit sich bringt, z.B. was Daten in Static Storage angeht
oder Adressen in Initializern. Und um relokatiblen Code / Daten zu
erzeugen braucht's i.d.R Compiler-Erweiterungen, so dass kein Code mit
absoluten Adressen erzeugt wird.
Johann L. schrieb:> Kann man auch so machen (GCC + Binutils):>> Als globales asm oder in einem Assembler-Modul:>>
1
> .global led_on
2
> led_on = 0x10000056
3
>
Ja, das könnte funktionieren! Ich linke firmware.elf mit
-export-dynamic, dann kann ich mit nm alle externen symbole, die im
Code-Bereich liegen in das Assembler-Modul generieren. Der nachladbare
Teil linkt dann gegen das übersetze Assembler-Modul (und dadurch sind
dann hoffentlich alle externene Symbole aufgelöst).
> Für C++ muss man Mangling dann aber händisch regeln oder per extern "C":
Glaube ich nicht. nm würde die gemangelten Namen auflisten und ich würde
dann die gemangelten Namen in das Assembler-Modul eintragen.
> Das ist aber kein nachladbarer Code.
Naja, ich habe vor, an der selben Stelle im RAM (oder evtl. flash) vom
DEbug-Target abhängigen Code zu laden und auszuführen.
> Oder man generiert / lädt relokatiblen Code, was aber einiges an> Einschränkungen mit sich bringt, z.B. was Daten in Static Storage angeht> oder Adressen in Initializern.
Ne, das würde weit über das Ziel hinaus gehen. Sowohl die Firmware, als
auch der nachzuladene Code muss statisch gelinkt werden. Wenn ich die
Firmware zuerst linke, dann stehen die Adresse der Funktion in der
Firmware fest. Danach linke ich den nachladbaren Code und muss dabei die
unresolved symbols durch die Adressen der Funktionen in der Firmware
ersetzen.
Das setzt dann aber auch zwingen vorraus, dass die nachladbaren Teile
nur mit exakt einer Version der Firmware zusammen arbeitet.
Wenn Du die Funktionspointer nicht haben willst, definiere am Anfang
deines nachzuladenden Blocks einen Satz "Trampolin-Funktionen", die
nichts weiter tun, als deine eigentlichen Funktionen aufzurufen und die
Parameter durchzureichen.
Diese Funktionen sind so in allen Versionen deines Codes gleich lang und
immer an der gleichen Stelle.
Torsten R. schrieb:> Wenn ich die Firmware zuerst linke, dann stehen die Adresse> der Funktion in der Firmware fest. Danach linke ich den nachladbaren> Code und muss dabei die unresolved symbols durch die Adressen der> Funktionen in der Firmware ersetzen.
Klingt so, als wäre -R firmware.elf das was du suchst?
http://sourceware.org/binutils/docs-2.32/ld/Options.html#index-_002dR-file
Moin,
Dir fehlt vermutlich ein OS und entsprechend sowas wie der ld.so.
Also wenn du den ganzen Aufwand willst:
https://github.com/bogdanm/udynlink
Geht allenfalls auch leichtgewichtiger (weiss aber nicht, ob auf deinem
Cortex):
- Per Linker-Script 'externe' Symbole in einen speziellen (virtuellen)
Memory-Bereich legen, in dem bei Zugriff eine Exception geworfen wird
- Aus dieser Exception heraus das Nachladen behandeln, in die
nachgeladene Funktion springen ("Trampolin").
So kann man auch z.B. auf einem 8051 grössere Programme als 'Overlay'
ins SPI-Flash auslagern. Mit objcopy musst du allerdings das ELF in
Flash-Image und Overlay-Image auftrennen.
Wenn du aus irgend einem Grund über fixe Adressen gehen musst/willst
(Funktionspointer-API, o.ä.) ist die 'saubere' Variante, das per
Linkerscript zu machen. Typischerweise kann man das aber mit genau einem
Einstiegspunkt abhaken, die init() (die z.B. immer an Anfang einer
Overlay-Page resp. Flash-Sektor steht) macht den Rest).
Torsten R. schrieb:> Hat jemand von euch soetwas schon mal gemacht?
Schau Dir mal an wie die CCC R0ket das macht:
https://github.com/r0ket/r0ket
Da dürfen die Programme nur 2,5KB groß sein, weil der µC nur insgesamt
8KB RAM (und 32KB Flash + 512KB SPI Flash) hatte.
Das läuft auch hier auf (Funktions-) Pointer an vordefinierter Stelle
heraus.
Markus F. schrieb:> Wenn Du die Funktionspointer nicht haben willst, definiere am Anfang> deines nachzuladenden Blocks einen Satz "Trampolin-Funktionen", die> nichts weiter tun, als deine eigentlichen Funktionen aufzurufen und die> Parameter durchzureichen.
Die packe ich nicht in die Module sondern in die library an eine feste
Adresse. Dadurch kann ich die library in gewissen Grenzen ändern ohne
die Module neu linken zu müssen. Die Trampolin-"Funktionen" bestehen nur
aus einem Befehl:
1
// lib-vectors.c 2017-11-29 17:35:23
2
3
// DO NOT EDIT -- generated from ../include/syslib.h by ../tools/mk-lib-tables
Hi Markus,
Markus F. schrieb:> Wenn Du die Funktionspointer nicht haben willst, definiere am Anfang> deines nachzuladenden Blocks einen Satz "Trampolin-Funktionen", die> nichts weiter tun, als deine eigentlichen Funktionen aufzurufen und die> Parameter durchzureichen.
das wäre mit zu aufwändig. Und das runtime dispatching ist ja
überflüssig.
> Diese Funktionen sind so in allen Versionen deines Codes gleich lang und> immer an der gleichen Stelle.
Ja, das wäre dann der einzige Vorteil. Wenn ich statisch linke, dann
müssen Firmware und nachzuladene Teile exakt zusammen passen.
Strubi schrieb:> Moin,
Moin, Moin,
> Dir fehlt vermutlich ein OS und entsprechend sowas wie der ld.so.
nein, wirklich nicht. Dir Firmware liegt in einem festen
Speicherbereicht, alle Sprünge etc. sind aufgelöst. Ein nachzuladenes
Stück Code soll auch an eine fest definierte Stelle geladen werden
(sprich auch innerhalb des Codes müssen keine Sprünge korregiert werden
etc.) von diesem nachladbaren Stück code aus, sollen beliebige (meist
C++) Funktionen aufgerufen werden, die in der Firmware liegen. Dazu muss
ich den Linker halt "nur" dazu bringen, die Adressen aus der Firmware zu
nutzen.
Johann L. schrieb:> Klingt so, als wäre -R firmware.elf das was du suchst?
Ja, sieht so aus, als würde das funktionieren. Zumindest bekomme ich
keine undefined symbols. Ich werde das mal weiter untersuchen und
berichten!
Hi,
Johann L. schrieb:> Klingt so, als wäre -R firmware.elf das was du suchst?
das funktioniert funderbar. firmware.elf wird mit -export-dynamic
gelinkt. Die nachladbaren Teile werden mit -R firmware.elf gelinkt:
Code:
Und 0x27c7c ist laut map file tatsächlich die aufgerufene Funktion :-)
Schönen Dank an Alle!
schöne Grüße,
Torsten
Nachtrag: das -export-dynamic beim Linkes der Firmware scheint garnicht
nötig zu sein.
Ist es nicht ggf. viel einfacher eine Laufzeitumgebung/Interpreter für
eine Zwischensprache zu schreiben der eine Schnittstelle zur Hardware
bildet?
Grob gesagt etwa so, wie bei SPS`en üblich? Wo dieser Code dann liegt
ist auch völlig egal denn der Interpreter holt sich Zeile für Zeile
(oder Block) das Anwenderprogramm in den RAM und arbeitet ihn dann ab.
Wumpus schrieb:> Ist es nicht ggf. viel einfacher eine Laufzeitumgebung/Interpreter für> eine Zwischensprache zu schreiben der eine Schnittstelle zur Hardware> bildet?
Hatte ich auch überlegt. Vielleicht mache ich das auch noch, wenn ich
einen besseren Überblick habe, welche device spezifischen Funktionen ich
auf dem Debugger brauche. Einen byte-code Interpreter und den dazu
passenden compiler zu schreiben, ist auch nicht trivial und ohne einen
sehr konkreten Überblick über die Anforderungen, stehen die Chancen
nicht schlecht, dass man eine Sprache entwirft, die später nicht passt.
Mit geringem Overhead (immer ein Sprung zusätzlich) habe ich das schon
mehrfach realisiert. Dabei nutze ich ein Assembler-File, in dem einfach
nur Sprünge zu den ROM-Funktionen stehen.
1
.globl meine_funktion_im_rom
2
.globl noch_eine_funktion_im_rom
3
4
meine_funktion_im_rom:
5
6
jmp 0xFC00
7
8
noch_eine_funktion_im_rom:
9
10
jmp 0xFC68
11
12
...
Das File wird beim Projekt einfach mit übersetzt, in der Header-Datei
(.h) sind aber die Funktionen so definiert, wie sie auch in die Firmware
compiliert wurden. Bei einigen Plattformen (z.B. RL78) muss man noch
aufpassen, dass die Funktionen im ASM-File einen zusätzlichen
Unterstrich am Anfang brauchen.
Die Assemblerdatei kann man sich auch per (b.B. Perl-) Script aus dem
Objektdump der Firmware erstellen lassen, das spart die Mühe, sich die
Adressen selbst raus zu suchen.
Wenn absolute Sprünge nicht möglich sind, kann man das auch indirekt
über ein Register machen und das beim GCC über -ffixed freihalten.
Jörg