Forum: Compiler & IDEs shared libraries auf µC?


von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

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

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

D.h. du willst dein Projekt gegen den Debugger als "Bibliothek" linken?

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Ja, hier ein kleines Beispiel:

debugger:
1
void led_on() { ... }
2
void led_off() { ... }
3
void delay(int) { ... }
4
static void load_and_execute() { ... }
5
6
int main()
7
{
8
  led_on();
9
  delay(1000);
10
  led_off();
11
12
  load_and_execute();
13
}

Nach-Lade-Code:
1
void start()
2
{
3
  for ( int i = 0; i != 10; ++i )
4
  {
5
    led_on();
6
    delay(1000);
7
    led_off();
8
  }
9
}

Ä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.

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Johann L. schrieb:
> D.h. du willst dein Projekt gegen den Debugger als "Bibliothek" linken?

Man könnte es auch Plugins nennen ;-)

von Max D. (max_d)


Lesenswert?

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

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Aber dann würde das Beispiel oben wie folgt aussehen:
1
/*
2
 * Tool generated header from reading debugger.elf
3
 */
4
....
5
#define led_on 0x10000056
6
....

Nach-Lade-Code:
1
void start()
2
{
3
  for ( int i = 0; i != 10; ++i )
4
  {
5
    (void(*)())led_on();
6
    (void(*)(int))delay(1000);
7
    (void(*)())led_off();
8
  }
9
}

Das ist nicht schön zu lesen / schreiben. Schaltet dann auch noch das 
Type-system aus und funktioniert nicht gut mit C++.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Kann man auch so machen (GCC + Binutils):

C-Code:
1
extern void led_on (void);
2
3
// Use led_on().

Als globales asm oder in einem Assembler-Modul:
1
.global led_on
2
led_on = 0x10000056

Für C++ muss man Mangling dann aber händisch regeln oder per extern "C":
1
extern "C" void led_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.

: Bearbeitet durch User
von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

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.

von Markus F. (mfro)


Lesenswert?

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.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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

von Strubi (Gast)


Lesenswert?

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

von Jim M. (turboj)


Lesenswert?

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.

von Bauform B. (bauformb)


Lesenswert?

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
4
5
void __attribute__ ((naked, section (".syslib_table")))
6
lib (void)
7
{
8
 __asm__ (" SYSLIB_VERSION = 0x5A1EE1CB\n .global SYSLIB_VERSION");
9
 __asm__ ("lib: .word 0x5A1EE1CB");
10
 __asm__ ("     .word 0xFFFFFFFF");
11
12
 __asm__ ("        ldr pc, [pc]\n              .word   abs        ");
13
 __asm__ ("        ldr pc, [pc]\n              .word   atoi       ");
14
 __asm__ ("        ldr pc, [pc]\n              .word   beep       ");
15
//...
16
 __asm__ ("sleep_ms: svc  7\n bx lr\n .word -1\n .global sleep_ms ");
17
//...
18
 __asm__ ("        ldr pc, [pc]\n              .word   vprintf    ");
19
}
Als Bonus ist es für den Aufrufer transparent, ob z.B. sleep_ms als 
Funktion oder als syscall implementiert ist.

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

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.

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

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.

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

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!

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

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:
1
#include "firmware/led.hpp"
2
3
extern "C" void support()
4
{
5
    feedback_led led;
6
    led.on( blink_pattern::started );
7
}

binary:
1
$ arm-none-eabi-objdump -S ./source/driver/arm/support_cortexm4.elf
2
3
./source/driver/arm/support_cortexm4.elf:     file format elf32-littlearm
4
5
6
Disassembly of section .text.support:
7
8
00000000 <support>:
9
#include "firmware/led.hpp"
10
11
extern "C" void support()
12
{
13
   0:  b580        push  {r7, lr}
14
   2:  b0a0        sub  sp, #128  ; 0x80
15
   4:  af00        add  r7, sp, #0
16
    feedback_led led;
17
    led.on( blink_pattern::started );
18
   6:  463b        mov  r3, r7
19
   8:  2100        movs  r1, #0
20
   a:  4618        mov  r0, r3
21
   c:  f027 fe36   bl  27c7c <_ZN12feedback_led2onE13blink_pattern>
22
  10:  bf00        nop
23
  12:  3780        adds  r7, #128  ; 0x80
24
  14:  46bd        mov  sp, r7
25
  16:  bd80        pop  {r7, pc}

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.

: Bearbeitet durch User
von Wumpus (Gast)


Lesenswert?

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.

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

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.

von Joerg W. (joergwolfram)


Lesenswert?

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

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.