Forum: Mikrocontroller und Digitale Elektronik ARM: Funktion in SRAM kopieren und ausführen


von Christopher C. (Gast)


Lesenswert?

Hi,
ich möchte gerne einen einfachen Bootloader für mein STM32F4Discovery 
Board schreiben, natürlich nur zum Lernzweck. Ich nutze die Embedded ARM 
GNU Toolchain und CoIDE. Zunächst wollte ich einfach nur eine Funktion 
in den SRAM kopieren und dann dort hin springen, was mir leider nicht 
gelingt. Ich komme nicht weiter, ich habe Google befragt und in den 
Datenblättern nachgesehen, finde allerdings den Fehler nicht. Immer wenn 
er zur Funktion springt, endet es damit, dass eine unbehandelte 
Exception auslöst und im default Handler hängen bleibt.

main.c:
1
#include "stm32f4xx.h"
2
3
#define APPLICATION_ADDRESS (void*)0x20000000
4
5
void memcpy(void* dst, void* src, uint32_t len)
6
{
7
  uint8_t* dest_ptr = dst, *src_ptr = src;
8
  uint32_t i;
9
  for (i=0;i<len;i++)
10
  {
11
    *dest_ptr = *src_ptr;
12
    dest_ptr++;
13
    src_ptr++;
14
  }
15
}
16
17
void doSomething(void)
18
{
19
  while (1)
20
  { }
21
}
22
23
int main(void)
24
{
25
  SystemInit();
26
27
  //enableLED();
28
  memcpy(APPLICATION_ADDRESS, &doSomething, 4*32);
29
30
  void (*func)(void) = APPLICATION_ADDRESS +1;
31
  func();
32
33
    while(1)
34
    {
35
    }
36
}
Die Vektor Tabelle muss ich erst in den SRAM verschieben, wenn ich sie 
nachträglich bearbeiten will, also einen Handler auch im SRAM haben 
will, oder?

Ich weiß auch, dass ich wahrscheinlich in den User Stack schreibe, 
allerdings am Ende und der Stack dürfte groß genug sein. Wo finde ich 
den die Einstellung dazu? In der CoIDE finde ich dazu nichts, muss 
wahrscheinlich dem Linker das passende Argument liefern.

Auch ist dieses 4*32 unschön, gibt es eine Möglichkeit im GCC die Größe 
einer Funktion herauszubekommen ohne den Assemblercode ausgeben zu 
lassen und die Maschinenbefehle zu zählen?

von Dr. Sommer (Gast)


Lesenswert?

Christopher C. schrieb:
> #define APPLICATION_ADDRESS (void*)0x20000000
Du möchtest also die automatische RAM-Verwaltung vom Linker nicht und 
stattdessen alle Adressen selber verwalten? Wenn du so etwas machst 
darfst du keinerlei "static" und globale Variablen mehr verwenden! 
Meines Wissens verwendet die StdPeriphal lib bzw. die Clock Templates 
aber mindestens schon 1 globale Variable, SystemCoreClock. Die müsstest 
du rausnehmen.
Einfacher ist es natürlich ein Array zu reservieren und dort 
hineinzukopieren:
1
char applicationCode [4*32];

Christopher C. schrieb:
> Die Vektor Tabelle muss ich erst in den SRAM verschieben, wenn ich sie
> nachträglich bearbeiten will, also einen Handler auch im SRAM haben
> will, oder?
Du kannst die Tabelle mithilfe des VTOR Registers in den RAM 
verschieben, wenn du die Inhalte zur Laufzeit ändern willst. Ob die 
auszuführenden Handler im RAM sind oder nicht hat nichts damit zu tun 
ob die Tabelle im RAM ist oder nicht.

Christopher C. schrieb:
> Ich weiß auch, dass ich wahrscheinlich in den User Stack schreibe,
> allerdings am Ende und der Stack dürfte groß genug sein.
Ich sehe nicht wo du dein Stack 128kB verbraucht...

Christopher C. schrieb:
> Wo finde ich
> den die Einstellung dazu?
Im Linkerscript (*.ld). Ist "etwas" kryptisch.

Christopher C. schrieb:
> Auch ist dieses 4*32 unschön, gibt es eine Möglichkeit im GCC die Größe
> einer Funktion herauszubekommen
Das müsstest du über den Linker machen, indem du die Funktion in eine 
eigene "Spezial"-Sektion packst, vor und nach die Funktion ein label 
setzt im Linkerscript und im Code die Differenz errechnest.

Christopher C. schrieb:
> Immer wenn
> er zur Funktion springt, endet es damit, dass eine unbehandelte
> Exception auslöst und im default Handler hängen bleibt.
Schau dir mal die Disassembly vom Sprung an, wo er genau hinspringt, und 
ob das "Ziel" "richtig" aussieht.

von Dr. Sommer (Gast)


Lesenswert?

Christopher C. schrieb:
> memcpy(APPLICATION_ADDRESS, &doSomething, 4*32);
>
>   void (*func)(void) = APPLICATION_ADDRESS +1;
PS: Vermutlich liefert &doSomething bereits eine Adresse zurück, deren 
niedrigstes Bit gesetzt ist, um den Thumb-Modus anzudeuten. Versuch mal:
1
memcpy(applicationCode, (char*) (((uintptr_t) (&doSomething)) & (~((uintptr_t) 3))), 4*32);

Das +1 beim Aufruf ( | 1 sollte genauso tun) brauchst du aber trotzdem.

von Christopher C. (Gast)


Lesenswert?

Tatsächlich es funktioniert! Vielen Dank.

Ich frage mich nur wie diese Rechnung zu stande kommt:

Dr. Sommer schrieb:
> memcpy(applicationCode, (char*) (((uintptr_t) (&doSomething)) &
> (~((uintptr_t) 3))), 4*32);

Ich hab mal im Debugger nachgeschaut, doSomething liegt bei 0x8000244. 
Deine Rechnung ergibt: 0x8000240 also einen Maschinenbefehl zuvor. 
Warum?

Dr. Sommer schrieb:
> Christopher C. schrieb:
>> #define APPLICATION_ADDRESS (void*)0x20000000
> Du möchtest also die automatische RAM-Verwaltung vom Linker nicht und
> stattdessen alle Adressen selber verwalten? Wenn du so etwas machst
> darfst du keinerlei "static" und globale Variablen mehr verwenden!
> Meines Wissens verwendet die StdPeriphal lib bzw. die Clock Templates
> aber mindestens schon 1 globale Variable, SystemCoreClock. Die müsstest
> du rausnehmen.

Das Problem ergibt sich, da der Linker hier relative Adressen angibt, 
oder?
Wenn ich eine Hex Datei in den SRAM kopiere, dürfte sich das Problem 
eigentlich erledigen, da hier die Adressierung wieder stimmt solange das 
User Programm auf seine eigenen globalen Variablen zugreift. Allerdings 
muss ich beim User Programm den Linker mit einem Linkerscript richtig 
einstellen, damit sich keine Speicherbereiche überschneiden.
Habe ich das richtig verstanden?

von Christopher C. (Gast)


Lesenswert?

Nein halt, habe mich verechnet:
Die Rechnung ergibt doch: 0x800244. Wie kann das sein das es da doch 
geht, es kommt doch das gleiche raus. Es muss also am Thumb-Bit liegen. 
Deine Rechnung nimmt es raus, oder? Der GCC setzt in Wirklichkeit das 
Thumb-Bit.

von Kindergärtner (Gast)


Lesenswert?

Christopher C. schrieb:
> doSomething liegt bei 0x8000244.
Ja. Aber wenn du &doSomething eingibst wird der Compiler vermutlich 
0x8000245 als Funktionspointer verwenden, um, wie gesagt, dem 
Sprung-Befehl "BX" bzw. "BLX" mitzuteilen, dass die Funktion den 
Thumb-Instruction Set verwendet (schau dir im Debugger das "src" 
Argument an). Zum umkopieren der Daten musst du aber natürlich die 
"richtige" Adresse verwenden.
> Deine Rechnung ergibt: 0x8000240 also einen Maschinenbefehl zuvor.
> Warum?
Das kann nicht, das sollte die 0x8000245 zurück auf 0x8000244 setzen. 
Die Rechnung ist lediglich ein typesafes auf-0-setzen der unteren 3 
bits.

Christopher C. schrieb:
> Das Problem ergibt sich, da der Linker hier relative Adressen angibt,
> oder?
Nein. Das Problem ist, dass du einfach fröhlich in den RAM ab Adresse 
0x20000000 schreibst, ohne dir darum Sorgen zu machen, was dort liegt. 
zB könnte ich mir vorstellen dass der Linker die globale Variable 
SystemCoreClock genau nach 0x20000000-0x20000003 packt.
> Wenn ich eine Hex Datei in den SRAM kopiere, dürfte sich das Problem
> eigentlich erledigen, da hier die Adressierung wieder stimmt solange das
> User Programm auf seine eigenen globalen Variablen zugreift.
Nein, du würdest immer noch deine globalen Variablen überschreiben.
> Allerdings
> muss ich beim User Programm den Linker mit einem Linkerscript richtig
> einstellen, damit sich keine Speicherbereiche überschneiden.
> Habe ich das richtig verstanden?
Wenn du das User Programm mit -fPIC kompilierst müsste der User code 
komplett unabhängig davon sein, wo er letztendlich landet, d.h. du 
kannst ihn irgendwohin - zB in das "applicationCode " Array - kopieren 
und von dort ausführen. Im Linkerscript des User Codes musst du 
natürlich so oder so dafür sorgen dass Globale Variablen und Code 
einfach hintereinander "flach" in die Ausgabedatei gepackt werden, da ja 
keine Unterscheidung RAM/ROM mehr nötig ist.

von Christopher C. (Gast)


Lesenswert?

Hier ist der Assembler Code:
1
          main:
2
0800024c:   push {r7, lr}
3
0800024e:   sub sp, #8
4
08000250:   add r7, sp, #0
5
08000252:   bl 0x8000278 <SystemInit>
6
08000256:   mov.w r0, #536870912    ; 0x20000000
7
0800025a:   movw r1, #581   ; 0x245
8
0800025e:   movt r1, #2048  ; 0x800
9
08000262:   mov.w r2, #128  ; 0x80
10
08000266:   bl 0x80001f4 <memcpy>
11
0800026a:   mov.w r3, #536870912    ; 0x20000000
12
0800026e:   str r3, [r7, #4]
13
08000270:   ldr r3, [r7, #4]
14
08000272:   blx r3
15
08000274:   b.n 0x8000274 <main+40>
16
08000276:   nop
Ich kann leider kaum ARM Assembler und finde deshalb auch nicht die 
Stelle wo er die Adresse von doSomething lädt.

Eine kleine Frage am Rande, der Cortex M4 kann doch auch normale ARM 
Befehle ausführen und der Thumb Modus ist automatisch drin, oder?

von Kindergärtner (Gast)


Lesenswert?

Christopher C. schrieb:
> 0800025a:   movw r1, #581   ; 0x245
> 0800025e:   movt r1, #2048  ; 0x800
^-- Dort wird die Adresse 0x0800245 geladen und an memcpy übergeben.

Christopher C. schrieb:
> 0800026a:   mov.w r3, #536870912    ; 0x20000000
> 0800026e:   str r3, [r7, #4]
> 08000270:   ldr r3, [r7, #4]
> 08000272:   blx r3
Schön ohne Optimierungen kompiliert... Hier wird direkt an die Adresse 
0x20000000  gesprungen. Was natürlich falsch ist, das unterste Bit muss 
1 sein, denn...

Christopher C. schrieb:
> Eine kleine Frage am Rande, der Cortex M4 kann doch auch normale ARM
> Befehle ausführen und der Thumb Modus ist automatisch drin, oder?
... der Cortex M4 kann keine ARM Instruktionen ausführen, nur Thumb2. 
Daher gibt es auch eine Exception, wenn du versuchst ARM-Code 
auszuführen durch einen Sprung mit "B(L)X" an eine Adresse deren 
unterstes Bit 0 ist.

Christopher C. schrieb:
> Ich kann leider kaum ARM Assembler und finde deshalb auch nicht die
> Stelle wo er die Adresse von doSomething lädt.
Das ist schlecht, wenn man derart hardwarenah programmieren will... Das 
ist doch die Gelegenheit es zu lernen.

von Christopher C. (Gast)


Lesenswert?

Habe deinen (Kindergärtners) Post erst jetzt gelesen, das beantwortet 
meine Frage. Vielen Dank für die ausführliche Erklärung!

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.