1) Die kompilierte Binärdatei wird in prom / flash yes geschrieben. USB, seriell, i2c, jtag usw. hängen von dem Gerät ab, was von diesem Gerät unterstützt wird. Dies ist für das Verständnis des Startvorgangs unerheblich.
2) Dies gilt normalerweise nicht für einen Mikrocontroller. Der primäre Anwendungsfall besteht darin, Anweisungen in ROM / Flash und Daten in RAM zu haben. Egal welche Architektur. Für einen Nicht-Mikrocontroller, Ihren PC, Ihren Laptop, Ihren Server wird das Programm von nichtflüchtig (Festplatte) auf RAM kopiert und von dort ausgeführt. Bei einigen Mikrocontrollern können Sie auch RAM verwenden, auch wenn dies gegen die Definition zu verstoßen scheint. Es gibt nichts an Harvard, was Sie daran hindert, RAM auf die Anweisungsseite zuzuordnen. Sie benötigen lediglich einen Mechanismus, um die Anweisungen nach dem Einschalten abzurufen (was gegen die Definition verstößt, aber Harvard-Systeme müssten dies tun, um andere zu unterstützen als als Mikrocontroller).
3) irgendwie.
Jede CPU "bootet" auf deterministische, wie vorgesehene Weise. Der gebräuchlichste Weg ist eine Vektortabelle, in der sich die Adresse für die ersten Befehle, die nach dem Einschalten ausgeführt werden, im Rücksetzvektor befindet. Diese Adresse wird von der Hardware gelesen und verwendet, um mit der Ausführung zu beginnen. Die andere allgemeine Möglichkeit besteht darin, den Prozessor ohne Vektortabelle an einer bekannten Adresse mit der Ausführung zu beginnen. Manchmal hat der Chip "Bänder", einige Stifte, die Sie hoch oder niedrig binden können, bevor Sie das Zurücksetzen freigeben, das die Logik verwendet, um auf verschiedene Weise zu starten. Sie müssen die CPU selbst, den Prozessorkern, vom Rest des Systems trennen. Verstehen Sie, wie die CPU funktioniert, und stellen Sie dann fest, dass die Chip- / Systementwickler Adressdecoder an der Außenseite der CPU installiert haben, sodass ein Teil des CPU-Adressraums mit einem Flash kommuniziert. und einige mit RAM und einige mit Peripheriegeräten (Uart, I2C, SPI, GPIO, etc.). Sie können denselben CPU-Kern auf Wunsch auch anders verpacken. Das bekommen Sie, wenn Sie etwas kaufen, das auf Arm oder Mips basiert. Arm und Mips stellen CPU-Kerne her, die die Leute kaufen und ihre eigenen Sachen herumwickeln, aus verschiedenen Gründen machen sie diese Sachen nicht kompatibel von Marke zu Marke. Aus diesem Grund kann eine generische Arm-Frage nur selten gestellt werden, wenn es um etwas außerhalb des Kerns geht.
Ein Mikrocontroller versucht, ein System auf einem Chip zu sein. Daher befinden sich sein nichtflüchtiger Speicher (Flash / Rom), sein flüchtiger Speicher (SRAM) und seine CPU zusammen mit einer Mischung aus Peripheriegeräten auf demselben Chip. Der Chip ist jedoch intern so konzipiert, dass der Flash-Speicher in den Adressraum der CPU abgebildet wird, der den Boot-Eigenschaften dieser CPU entspricht. Wenn zum Beispiel die CPU einen Rücksetzvektor an der Adresse 0xFFFC hat, muss es ein Flash / ROM geben, das auf diese Adresse reagiert, die wir über 1) programmieren können, zusammen mit genügend Flash / ROM im Adressraum für nützliche Programme. Ein Chipdesigner kann sich dafür entscheiden, ab 0xF000 0x1000 Flash-Bytes zu haben, um diese Anforderungen zu erfüllen. Und vielleicht haben sie etwas RAM an eine niedrigere Adresse oder vielleicht 0x0000 und die Peripherie irgendwo in der Mitte gelegt.
Eine andere CPU-Architektur wird möglicherweise ab Adresse Null ausgeführt. Sie müssten also das Gegenteil tun und den Flash so platzieren, dass er auf einen Adressbereich um Null antwortet. sagen Sie zum Beispiel 0x0000 bis 0x0FFF. und dann irgendwo anders rammen.
Die Chip-Designer wissen, wie die CPU bootet, und haben dort nichtflüchtigen Speicher (Flash / Rom) abgelegt. Es liegt dann an der Software, den Boot-Code so zu schreiben, dass er dem bekannten Verhalten dieser CPU entspricht. Sie müssen die Rücksetzvektoradresse im Rücksetzvektor und Ihren Startcode an der Adresse platzieren, die Sie im Rücksetzvektor definiert haben. Die Toolchain kann Ihnen hier sehr helfen. manchmal, vor allem mit Point-and-Click-IDs oder anderen Sandboxes, erledigen sie die meiste Arbeit für Sie. Alles, was Sie tun, ist, apis in einer höheren Sprache (C) aufzurufen.
Das in das Flash / Rom geladene Programm muss jedoch dem festverdrahteten Startverhalten der CPU entsprechen. Vor dem C-Teil Ihres Programms main () und wenn Sie main als Einstiegspunkt verwenden, müssen einige Dinge getan werden. Der AC-Programmierer geht davon aus, dass die Deklaration einer Variablen mit einem Anfangswert tatsächlich funktioniert. Nun, andere Variablen als Konstanten sind im RAM, aber wenn Sie eine mit einem Anfangswert haben, muss dieser Anfangswert im nichtflüchtigen RAM sein. Dies ist also das .data-Segment, und der C-Bootstrap muss .data-Dateien von Flash auf RAM kopieren (was normalerweise von der Toolchain festgelegt wird). Globale Variablen, die Sie ohne Anfangswert deklarieren, werden vor dem Start Ihres Programms als Null angenommen, obwohl Sie das eigentlich nicht annehmen sollten. Zum Glück beginnen einige Compiler, vor nicht initialisierten Variablen zu warnen. Dies ist das .bss-Segment, und die C-Bootstrap-Nullen, die im RAM ausgegeben werden, der Inhalt, Nullen, müssen nicht im nichtflüchtigen Speicher gespeichert werden, sondern die Startadresse und wie viel. Auch hier hilft Ihnen die Toolchain sehr. Als letztes müssen Sie einen Stapelzeiger einrichten, da C-Programme erwarten, lokale Variablen zu haben und andere Funktionen aufzurufen. Dann werden vielleicht noch andere chipspezifische Sachen gemacht, oder wir lassen den Rest der chipspezifischen Sachen in C passieren. muss nicht im nichtflüchtigen Speicher abgelegt werden, sondern die Startadresse und wie viel. Auch hier hilft Ihnen die Toolchain sehr. Als letztes müssen Sie einen Stapelzeiger einrichten, da C-Programme erwarten, lokale Variablen zu haben und andere Funktionen aufzurufen. Dann werden vielleicht noch andere chipspezifische Sachen gemacht, oder wir lassen den Rest der chipspezifischen Sachen in C passieren. muss nicht im nichtflüchtigen Speicher abgelegt werden, sondern die Startadresse und wie viel. Auch hier hilft Ihnen die Toolchain sehr. Als letztes müssen Sie einen Stapelzeiger einrichten, da C-Programme erwarten, lokale Variablen zu haben und andere Funktionen aufzurufen. Dann werden vielleicht noch andere chipspezifische Sachen gemacht, oder wir lassen den Rest der chipspezifischen Sachen in C passieren.
Die Kerne der cortex-m-Serie von arm erledigen einen Teil davon für Sie. Der Stapelzeiger befindet sich in der Vektortabelle. Es gibt einen Rücksetzvektor, der auf den Code verweist, der nach dem Zurücksetzen ausgeführt werden soll, sodass Sie nichts weiter tun müssen Um die Vektortabelle zu generieren (für die Sie normalerweise sowieso asm verwenden), können Sie C ohne asm verwenden. Jetzt werden Ihre .data nicht mehr kopiert und Ihre .bss nicht mehr auf Null gesetzt. Sie müssen dies also selbst tun, wenn Sie versuchen möchten, auf etwas Kortex-M-basiertem zu verzichten. Die größere Funktion ist nicht der Rücksetzvektor, sondern die Interruptvektoren, bei denen die Hardware der von Arms empfohlenen C-Aufrufkonvention folgt und die Register für Sie beibehält und die korrekte Rückgabe für diesen Vektor verwendet, damit Sie nicht den richtigen Asm um jeden Handler wickeln müssen ( oder haben Toolchain-spezifische Anweisungen für Ihr Ziel, damit die Toolchain sie für Sie umschließt).
Chipspezifisches Material kann beispielsweise sein, dass in batteriebasierten Systemen häufig Mikrocontroller verwendet werden. Daher ist der Stromverbrauch so gering, dass einige Geräte nach dem Zurücksetzen bei größtenteils ausgeschaltetem Peripheriegerät ausfallen und Sie jedes dieser Subsysteme einschalten müssen, damit Sie sie verwenden können . Uarts, gpios usw. Oft wird eine niedrige Taktrate verwendet, direkt von einem Quarz oder einem internen Oszillator. Und Ihr Systemdesign zeigt möglicherweise, dass Sie eine schnellere Uhr benötigen. Initialisieren Sie diese. Ihre Uhr ist möglicherweise zu schnell für den Blitz oder den RAM, sodass Sie möglicherweise den Wartezustand ändern müssen, bevor Sie die Uhr erhöhen. Möglicherweise müssen Sie den Uart oder USB oder andere Schnittstellen einrichten. dann kann Ihre Anwendung ihre Sache tun.
Ein Computer-Desktop, ein Laptop, ein Server und ein Mikrocontroller unterscheiden sich nicht darin, wie sie starten / arbeiten. Abgesehen davon, dass sie meistens nicht auf einem Chip sind. Das BIOS-Programm befindet sich häufig auf einem separaten Chip-Flash / ROM von der CPU. Zwar ziehen in letzter Zeit x86-CPUs immer mehr der früher unterstützten Chips in dasselbe Paket (PCIE-Controller usw.), aber Sie haben immer noch den größten Teil Ihres RAM- und ROM-Off-Chips, aber es ist immer noch ein System und es funktioniert immer noch genau das gleiche auf hohem niveau. Der CPU-Boot-Prozess ist allgemein bekannt. Die Board-Designer platzieren das Flash / ROM im Adressraum, in dem die CPU bootet. Dieses Programm (Teil des BIOS auf einem x86-PC) erledigt alle oben genannten Aufgaben, startet verschiedene Peripheriegeräte, initialisiert dram, zählt die PCIE-Busse auf und so weiter. Ist für den Benutzer oft recht konfigurierbar, basierend auf den BIOS-Einstellungen oder den so genannten CMOS-Einstellungen. Egal, es gibt Benutzereinstellungen, die Sie ändern können, um dem BIOS-Boot-Code mitzuteilen, wie er sich ändern soll.
Verschiedene Leute verwenden unterschiedliche Terminologie. Ein Chip bootet, das ist der erste Code, der ausgeführt wird. manchmal Bootstrap genannt. Ein Bootloader mit dem Wort Loader bedeutet häufig, dass Sie, wenn Sie nichts tun, um zu stören, einen Bootstrap ausführen, der Sie vom allgemeinen Booten in etwas Größeres, Ihre Anwendung oder Ihr Betriebssystem, abhält. Der Loader-Teil impliziert jedoch, dass Sie den Startvorgang unterbrechen und dann möglicherweise andere Testprogramme laden können. Wenn Sie uboot zum Beispiel jemals auf einem Embedded-Linux-System verwendet haben, können Sie eine Taste drücken und den normalen Startvorgang beenden. Anschließend können Sie einen Testkernel in den RAM-Speicher herunterladen und ihn anstelle des Flash-Kernels starten oder Ihren Kernel herunterladen eigene Programme, oder Sie können den neuen Kernel herunterladen und ihn dann vom Bootloader zum Flashen schreiben lassen, damit er beim nächsten Start das neue Zeug ausführt.
Was die CPU selbst anbelangt, so ist dies der Kernprozessor, der keinen RAM von Flash von Peripheriegeräten kennt. Es gibt keine Vorstellung von Bootloader, Betriebssystem, Anwendung. Es ist nur eine Folge von Befehlen, die in die auszuführende CPU eingegeben werden. Dies sind Software-Begriffe, um verschiedene Programmieraufgaben voneinander zu unterscheiden. Softwarekonzepte voneinander.
Einige Mikrocontroller verfügen über einen separaten Bootloader, der vom Chip-Hersteller in einem separaten Flash oder einem separaten Flash-Bereich bereitgestellt wird, den Sie möglicherweise nicht ändern können. In diesem Fall gibt es oft einen Stift oder eine Reihe von Stiften (ich nenne sie Bänder), die, wenn Sie sie vor dem Zurücksetzen hoch oder niedrig binden, der Logik und / oder dem Bootloader mitteilen, was zu tun ist, z. B. eine Bandkombination Sagen Sie dem Chip, dass er diesen Bootloader ausführen soll, und warten Sie auf dem UART, bis die Daten in den Flash programmiert wurden. Stellen Sie die Gurte in die andere Richtung und Ihr Programm bootet nicht den Bootloader des Chipherstellers, wodurch der Chip vor Ort programmiert oder ein Programmabsturz behoben werden kann. Manchmal ist es nur reine Logik, mit der Sie den Blitz programmieren können. Das ist heutzutage ziemlich üblich,
Der Grund, warum die meisten Mikrocontroller viel mehr Flash als RAM haben, ist, dass der primäre Anwendungsfall darin besteht, das Programm direkt von Flash aus auszuführen und nur genügend RAM zu haben, um Stapel und Variablen abzudecken. Obwohl Sie in einigen Fällen Programme von RAM ausführen können, die Sie direkt kompilieren und in Flash speichern müssen, kopieren Sie diese vor dem Aufruf.
BEARBEITEN
flash.s
.cpu cortex-m0
.thumb
.thumb_func
.global _start
_start:
stacktop: .word 0x20001000
.word reset
.word hang
.word hang
.word hang
.thumb_func
reset:
bl notmain
b hang
.thumb_func
hang: b .
notmain.c
int notmain ( void )
{
unsigned int x=1;
unsigned int y;
y = x + 1;
return(0);
}
flash.ld
MEMORY
{
bob : ORIGIN = 0x00000000, LENGTH = 0x1000
ted : ORIGIN = 0x20000000, LENGTH = 0x1000
}
SECTIONS
{
.text : { *(.text*) } > bob
.rodata : { *(.rodata*) } > bob
.bss : { *(.bss*) } > ted
.data : { *(.bss*) } > ted AT > bob
}
Dies ist also ein Beispiel für einen Cortex-m0, die Cortex-ms funktionieren nach diesem Beispiel alle gleich. Der spezielle Chip für dieses Beispiel hat einen Anwendungs-Flash bei der Adresse 0x00000000 im Armadressraum und einen RAM bei 0x20000000.
Die Art und Weise, wie ein Cortex-m bootet, ist das 32-Bit-Wort an der Adresse 0x0000, die Adresse zum Initialisieren des Stapelzeigers. Ich brauche nicht viel Stack für dieses Beispiel, also wird 0x20001000 ausreichen. Offensichtlich muss es einen RAM unterhalb dieser Adresse geben (wie der Arm drückt, subtrahiert er zuerst und drückt dann, wenn Sie 0x20001000 einstellen, befindet sich das erste Element auf dem Stack an der Adresse 0x2000FFFC Sie müssen 0x2000FFFC nicht verwenden). Das 32-Bit-Wort an Adresse 0x0004 ist die Adresse für den Rücksetz-Handler, im Grunde der erste Code, der nach einem Zurücksetzen ausgeführt wird. Dann gibt es mehr Interrupt- und Event-Handler, die spezifisch für diesen Cortex-Kern und -Chip sind, möglicherweise bis zu 128 oder 256. Wenn Sie sie nicht verwenden, brauchen Sie die Tabelle nicht für sie einzurichten, ich habe ein paar zur Demonstration reingeworfen Zwecke.
In diesem Beispiel muss ich weder mit .data noch mit .bss umgehen, da ich bereits weiß, dass in diesen Segmenten nichts vorhanden ist, wenn ich den Code betrachte. Wenn ich es gäbe, würde ich mich darum kümmern und werde es in einer Sekunde tun.
Der Stack ist also eingerichtet, geprüft, .data gepflegt, geprüft, .bss, geprüft, damit das C-Bootstrap-Zeug fertig ist, und kann zur Eingabefunktion für C verzweigen. Einige Compiler fügen zusätzlichen Müll hinzu, wenn sie die Funktion sehen main () und auf dem Weg nach main verwende ich nicht genau diesen Namen. Ich habe notmain () hier als C-Einstiegspunkt verwendet. Der Reset-Handler ruft also notmain () auf, und wenn notmain () zurückgibt, bleibt er hängen. Dies ist nur eine Endlosschleife, die möglicherweise einen schlechten Namen hat.
Ich bin fest davon überzeugt, die Tools zu beherrschen, viele Leute tun das nicht, aber Sie werden feststellen, dass jeder Bare-Metal-Entwickler aufgrund der nahezu vollständigen Freiheit sein eigenes Ding macht, das nicht im entferntesten so eingeschränkt ist, als würden Sie Apps oder Webseiten erstellen . Sie machen wieder ihr eigenes Ding. Ich bevorzuge es, meinen eigenen Bootstrap-Code und ein eigenes Linker-Skript zu haben. Andere verlassen sich auf die Toolchain oder spielen in der Vendors Sandbox, in der der Großteil der Arbeit von jemand anderem erledigt wird (und wenn etwas kaputt geht, sind Sie in einer Welt voller Verletzungen und mit Bare-Metal-Dingen geht es oft und dramatisch kaputt).
Zusammenstellen, Kompilieren und Verknüpfen mit Gnu-Werkzeugen, die ich bekomme:
00000000 <_start>:
0: 20001000 andcs r1, r0, r0
4: 00000015 andeq r0, r0, r5, lsl r0
8: 0000001b andeq r0, r0, fp, lsl r0
c: 0000001b andeq r0, r0, fp, lsl r0
10: 0000001b andeq r0, r0, fp, lsl r0
00000014 <reset>:
14: f000 f802 bl 1c <notmain>
18: e7ff b.n 1a <hang>
0000001a <hang>:
1a: e7fe b.n 1a <hang>
0000001c <notmain>:
1c: 2000 movs r0, #0
1e: 4770 bx lr
Woher weiß der Bootloader also, wo sich das Zeug befindet? Weil der Compiler die Arbeit gemacht hat. Im ersten Fall hat der Assembler den Code für flash.s generiert und weiß dabei, wo sich die Beschriftungen befinden (Beschriftungen sind nur Adressen wie Funktions- oder Variablennamen usw.), sodass ich keine Bytes zählen und den Vektor ausfüllen musste Tabelle manuell, ich habe einen Markennamen verwendet und der Assembler hat es für mich getan. Nun fragt man sich, ob Reset die Adresse 0x14 ist, warum der Assembler 0x15 in die Vektortabelle eingetragen hat. Nun, das ist ein Cortex-m, der bootet und nur im Daumenmodus läuft. Wenn Sie mit ARM zu einer Adresse verzweigen, wenn Sie in den Daumenmodus verzweigen, muss das lsbit gesetzt werden, wenn der Scharfschaltmodus zurückgesetzt wird. Das Bit muss also immer gesetzt sein. Ich kenne die Werkzeuge und setze .thumb_func vor ein Label, wenn dieses Label so wie es ist in der Vektortabelle verwendet wird oder zum Verzweigen nach oder was auch immer. Die Toolchain kann das lsbit setzen. Also hat es hier 0x14 | 1 = 0x15. Ebenso zum Aufhängen. Jetzt zeigt der Disassembler nicht 0x1D für den Aufruf von notmain (), aber keine Sorge, die Tools haben die Anweisung korrekt erstellt.
Nun, da der Code nicht mehr vorhanden ist, werden diese lokalen Variablen nicht verwendet, sondern es handelt sich um toten Code. Der Compiler kommentiert diese Tatsache sogar, indem er sagt, dass y gesetzt, aber nicht verwendet wird.
Beachten Sie den Adressraum, diese Dinge beginnen alle bei der Adresse 0x0000 und gehen von dort aus, damit die Vektortabelle richtig platziert wird, der Text- oder Programmraum auch richtig platziert wird, wie ich flash.s vor notmain.cs Code habe Wenn man die Werkzeuge kennt, ist ein häufiger Fehler, dass man das nicht richtig hinbekommt und abstürzt und hart brennt. IMO müssen Sie zerlegen, um sicherzustellen, dass die Dinge direkt vor dem ersten Start platziert werden. Sobald Sie die Dinge an der richtigen Stelle haben, müssen Sie sie nicht jedes Mal überprüfen. Nur für neue Projekte oder wenn sie hängen.
Nun, einige Leute wundern sich, dass es keinen Grund gibt, zu erwarten, dass zwei Compiler dieselbe Ausgabe von derselben Eingabe erzeugen. Oder sogar derselbe Compiler mit unterschiedlichen Einstellungen. Mit clang, dem llvm-Compiler, bekomme ich diese beiden Ausgaben mit und ohne Optimierung
llvm / clang optimiert
00000000 <_start>:
0: 20001000 andcs r1, r0, r0
4: 00000015 andeq r0, r0, r5, lsl r0
8: 0000001b andeq r0, r0, fp, lsl r0
c: 0000001b andeq r0, r0, fp, lsl r0
10: 0000001b andeq r0, r0, fp, lsl r0
00000014 <reset>:
14: f000 f802 bl 1c <notmain>
18: e7ff b.n 1a <hang>
0000001a <hang>:
1a: e7fe b.n 1a <hang>
0000001c <notmain>:
1c: 2000 movs r0, #0
1e: 4770 bx lr
nicht optimiert
00000000 <_start>:
0: 20001000 andcs r1, r0, r0
4: 00000015 andeq r0, r0, r5, lsl r0
8: 0000001b andeq r0, r0, fp, lsl r0
c: 0000001b andeq r0, r0, fp, lsl r0
10: 0000001b andeq r0, r0, fp, lsl r0
00000014 <reset>:
14: f000 f802 bl 1c <notmain>
18: e7ff b.n 1a <hang>
0000001a <hang>:
1a: e7fe b.n 1a <hang>
0000001c <notmain>:
1c: b082 sub sp, #8
1e: 2001 movs r0, #1
20: 9001 str r0, [sp, #4]
22: 2002 movs r0, #2
24: 9000 str r0, [sp, #0]
26: 2000 movs r0, #0
28: b002 add sp, #8
2a: 4770 bx lr
Das ist eine Lüge. Der Compiler hat den Zusatz zwar optimiert, aber zwei Elemente auf dem Stack für die Variablen zugewiesen, da es sich um lokale Variablen handelt, die sich im RAM befinden, aber auf dem Stack nicht an festen Adressen Änderungen. Der Compiler erkannte jedoch, dass es möglich war, y zur Kompilierungszeit zu berechnen, und es gab keinen Grund, es zur Laufzeit zu berechnen. Daher platzierte er einfach eine 1 im für x zugewiesenen Stapelspeicher und eine 2 für den für y zugewiesenen Stapelspeicher. Der Compiler "reserviert" diesen Platz mit internen Tabellen. Ich deklariere Stack plus 0 für Variable y und Stack plus 4 für Variable x. Der Compiler kann tun, was er will, solange der implementierte Code dem C-Standard oder den Erklärungen eines C-Programmierers entspricht. Es gibt keinen Grund, warum der Compiler x für die Dauer der Funktion auf Stack + 4 belassen muss.
Wenn ich einen Funktionsdummy in Assembler hinzufüge
.thumb_func
.globl dummy
dummy:
bx lr
und dann nenne es
void dummy ( unsigned int );
int notmain ( void )
{
unsigned int x=1;
unsigned int y;
y = x + 1;
dummy(y);
return(0);
}
die Ausgabe ändert sich
00000000 <_start>:
0: 20001000 andcs r1, r0, r0
4: 00000015 andeq r0, r0, r5, lsl r0
8: 0000001b andeq r0, r0, fp, lsl r0
c: 0000001b andeq r0, r0, fp, lsl r0
10: 0000001b andeq r0, r0, fp, lsl r0
00000014 <reset>:
14: f000 f804 bl 20 <notmain>
18: e7ff b.n 1a <hang>
0000001a <hang>:
1a: e7fe b.n 1a <hang>
0000001c <dummy>:
1c: 4770 bx lr
...
00000020 <notmain>:
20: b510 push {r4, lr}
22: 2002 movs r0, #2
24: f7ff fffa bl 1c <dummy>
28: 2000 movs r0, #0
2a: bc10 pop {r4}
2c: bc02 pop {r1}
2e: 4708 bx r1
Jetzt, da wir verschachtelte Funktionen haben, muss die notmain-Funktion ihre Rücksprungadresse beibehalten, damit sie die Rücksprungadresse für den verschachtelten Aufruf blockieren kann. Dies liegt daran, dass der Arm ein Register für Rückgaben verwendet, wenn er den Stapel wie z. B. ein x86 oder einige andere gut verwendet hat. Er würde den Stapel immer noch verwenden, aber anders. Jetzt fragst du, warum es r4 gedrückt hat? Nun, die Aufrufkonvention wurde vor kurzem geändert, um den Stapel an 64-Bit-Grenzen (zwei Wörter) anstatt an 32-Bit-Grenzen (ein Wort) auszurichten. Sie müssen also etwas verschieben, um den Stapel ausgerichtet zu halten, sodass der Compiler aus irgendeinem Grund willkürlich r4 auswählte, egal warum. Das Aufrufen von r4 wäre ein Fehler, obwohl wir gemäß der Aufrufkonvention für dieses Ziel bei einem Funktionsaufruf nicht r4 blockieren, sondern r0 bis r3 blockieren können. r0 ist der Rückgabewert. Sieht aus wie es vielleicht eine Schwanzoptimierung tut,
Wir sehen jedoch, dass die x- und y-Mathematik auf einen fest codierten Wert von 2 optimiert ist, der an die Dummy-Funktion übergeben wird (Dummy wurde speziell in einer separaten Datei codiert, in diesem Fall asm, damit der Compiler den Funktionsaufruf nicht vollständig optimiert. Wenn ich eine Dummy-Funktion hätte, die einfach in notmain.c in C zurückgegeben wird, hätte der Optimierer den x-, y- und Dummy-Funktionsaufruf entfernt, da sie alle tot / nutzloser Code sind.
Beachten Sie auch, dass Flash.s-Code nicht mehr wichtig ist und die Toolchain sich darum gekümmert hat, alle Adressen für uns zu patchen, sodass wir dies nicht manuell tun müssen.
nicht optimiertes Klirren als Referenz
00000020 <notmain>:
20: b580 push {r7, lr}
22: af00 add r7, sp, #0
24: b082 sub sp, #8
26: 2001 movs r0, #1
28: 9001 str r0, [sp, #4]
2a: 2002 movs r0, #2
2c: 9000 str r0, [sp, #0]
2e: f7ff fff5 bl 1c <dummy>
32: 2000 movs r0, #0
34: b002 add sp, #8
36: bd80 pop {r7, pc}
optimiertes Klirren
00000020 <notmain>:
20: b580 push {r7, lr}
22: af00 add r7, sp, #0
24: 2002 movs r0, #2
26: f7ff fff9 bl 1c <dummy>
2a: 2000 movs r0, #0
2c: bd80 pop {r7, pc}
Dieser Compiler-Autor hat r7 als Dummy-Variable zum Ausrichten des Stapels ausgewählt. Außerdem erstellt er mit r7 einen Frame-Zeiger, obwohl sich nichts im Stack-Frame befindet. Grundsätzlich hätte der Unterricht optimiert werden können. Aber es wurde der Pop verwendet, um nicht drei Anweisungen zurückzugeben. Das lag wahrscheinlich an mir. Ich wette, ich könnte gcc dazu bringen, dies mit den richtigen Befehlszeilenoptionen (unter Angabe des Prozessors) auszuführen.
Dies sollte hauptsächlich die restlichen Fragen beantworten
void dummy ( unsigned int );
unsigned int x=1;
unsigned int y;
int notmain ( void )
{
y = x + 1;
dummy(y);
return(0);
}
Ich habe jetzt Globals. Also gehen sie entweder in .data oder .bss, wenn sie nicht optimiert werden.
Bevor wir uns die endgültige Ausgabe ansehen, wollen wir uns das itermediate-Objekt ansehen
00000000 <notmain>:
0: b510 push {r4, lr}
2: 4b05 ldr r3, [pc, #20] ; (18 <notmain+0x18>)
4: 6818 ldr r0, [r3, #0]
6: 4b05 ldr r3, [pc, #20] ; (1c <notmain+0x1c>)
8: 3001 adds r0, #1
a: 6018 str r0, [r3, #0]
c: f7ff fffe bl 0 <dummy>
10: 2000 movs r0, #0
12: bc10 pop {r4}
14: bc02 pop {r1}
16: 4708 bx r1
...
Disassembly of section .data:
00000000 <x>:
0: 00000001 andeq r0, r0, r1
Jetzt fehlen Informationen, aber es gibt eine Vorstellung davon, was vor sich geht. Der Linker ist derjenige, der Objekte aufnimmt und sie mit den bereitgestellten Informationen verknüpft (in diesem Fall flash.ld), die angeben, wo .text und. Daten und so geht. Der Compiler kennt solche Dinge nicht, er kann sich nur auf den Code konzentrieren, den er präsentiert, und jeder Externe muss eine Lücke hinterlassen, damit der Linker die Verbindung ausfüllt. Alle Daten müssen einen Weg lassen, um diese Dinge miteinander zu verknüpfen, daher basieren die Adressen für alles hier auf Null, nur weil der Compiler und dieser Dissassembler es nicht wissen. Es werden hier weitere Informationen angezeigt, die der Linker zum Platzieren von Dingen verwendet. Der Code hier ist positionsunabhängig genug, damit der Linker seine Arbeit erledigen kann.
Wir sehen dann zumindest eine Zerlegung der verknüpften Ausgabe
00000020 <notmain>:
20: b510 push {r4, lr}
22: 4b05 ldr r3, [pc, #20] ; (38 <notmain+0x18>)
24: 6818 ldr r0, [r3, #0]
26: 4b05 ldr r3, [pc, #20] ; (3c <notmain+0x1c>)
28: 3001 adds r0, #1
2a: 6018 str r0, [r3, #0]
2c: f7ff fff6 bl 1c <dummy>
30: 2000 movs r0, #0
32: bc10 pop {r4}
34: bc02 pop {r1}
36: 4708 bx r1
38: 20000004 andcs r0, r0, r4
3c: 20000000 andcs r0, r0, r0
Disassembly of section .bss:
20000000 <y>:
20000000: 00000000 andeq r0, r0, r0
Disassembly of section .data:
20000004 <x>:
20000004: 00000001 andeq r0, r0, r1
Der Compiler hat grundsätzlich nach zwei 32-Bit-Variablen im RAM gefragt. Eins ist in .bss, weil ich es nicht initialisiert habe, also wird angenommen, dass es als Null initialisiert wird. Das andere ist .data, weil ich es bei der Deklaration initialisiert habe.
Da es sich um globale Variablen handelt, wird davon ausgegangen, dass andere Funktionen sie ändern können. Der Compiler geht nicht davon aus, wann notmain aufgerufen werden kann, sodass er nicht mit dem optimieren kann, was er sehen kann, nämlich y = x + 1 math. Daher muss er diese Laufzeit ausführen. Es muss aus dem RAM gelesen werden, die beiden Variablen hinzufügen und speichern.
Nun klar, dieser Code wird nicht funktionieren. Warum? weil mein Bootstrap, wie hier gezeigt, den RAM nicht vorbereitet, bevor er notmain aufruft, also wird für y und x verwendet, welcher Müll auch immer in 0x20000000 und 0x20000004 war, als der Chip aufwachte.
Das werde ich hier nicht zeigen. Sie können mein noch längeres Hin und Her auf .data und .bss lesen und warum ich sie nie in meinem Bare-Metal-Code brauche, aber wenn Sie das Gefühl haben, dass Sie die Werkzeuge beherrschen müssen und wollen, anstatt zu hoffen, dass jemand anderes es richtig gemacht hat. .
https://github.com/dwelch67/raspberrypi/tree/master/bssdata
Linker-Skripte und Bootstraps sind etwas compilerspezifisch, sodass alles, was Sie über eine Version eines Compilers erfahren, in die nächste Version oder in einen anderen Compiler verschoben werden kann. Dies ist ein weiterer Grund, warum ich nicht viel Aufwand in die Vorbereitung von .data und .bss stecke nur um so faul zu sein:
unsigned int x=1;
Ich würde das viel lieber tun
unsigned int x;
...
x = 1;
und lassen Sie den Compiler es für mich in .text setzen. Manchmal spart es Blitz, manchmal brennt es mehr. Es ist definitiv viel einfacher, von einer Toolchain-Version oder einem Compiler auf einen anderen zu programmieren und zu portieren. Viel zuverlässiger, weniger fehleranfällig. Ja, entspricht nicht dem C-Standard.
Was ist nun, wenn wir diese statischen Globalen erstellen?
void dummy ( unsigned int );
static unsigned int x=1;
static unsigned int y;
int notmain ( void )
{
y = x + 1;
dummy(y);
return(0);
}
Gut
00000020 <notmain>:
20: b510 push {r4, lr}
22: 2002 movs r0, #2
24: f7ff fffa bl 1c <dummy>
28: 2000 movs r0, #0
2a: bc10 pop {r4}
2c: bc02 pop {r1}
2e: 4708 bx r1
Offensichtlich können diese Variablen nicht durch anderen Code geändert werden, sodass der Compiler jetzt zur Kompilierungszeit den toten Code wie zuvor optimieren kann.
nicht optimiert
00000020 <notmain>:
20: b580 push {r7, lr}
22: af00 add r7, sp, #0
24: 4804 ldr r0, [pc, #16] ; (38 <notmain+0x18>)
26: 6800 ldr r0, [r0, #0]
28: 1c40 adds r0, r0, #1
2a: 4904 ldr r1, [pc, #16] ; (3c <notmain+0x1c>)
2c: 6008 str r0, [r1, #0]
2e: f7ff fff5 bl 1c <dummy>
32: 2000 movs r0, #0
34: bd80 pop {r7, pc}
36: 46c0 nop ; (mov r8, r8)
38: 20000004 andcs r0, r0, r4
3c: 20000000 andcs r0, r0, r0
Dieser Compiler, der den Stack für Locals verwendet hat, verwendet jetzt RAM für Globals und dieser Code ist wie geschrieben kaputt, da ich weder mit .data noch .bss richtig umgegangen bin.
und eine letzte Sache, die wir in der Demontage nicht sehen können.
:1000000000100020150000001B0000001B00000075
:100010001B00000000F004F8FFE7FEE77047000057
:1000200080B500AF04480068401C04490860FFF731
:10003000F5FF002080BDC046040000200000002025
:08004000E0FFFF7F010000005A
:0400480078563412A0
:00000001FF
Ich änderte x, um mit 0x12345678 pre-init zu sein. Mein Linker-Skript (das ist für Gnu ld) hat dieses Problem. Das teilt dem Linker mit, dass ich möchte, dass der letzte Ort im Adressraum von Ted liegt, aber speichere ihn in der Binärdatei im Adressraum von Ted und jemand wird ihn für Sie verschieben. Und wir können sehen, dass das passiert ist. Dies ist das Intel Hex-Format. und wir können die 0x12345678 sehen
:0400480078563412A0
befindet sich im Flash-Adressraum der Binärdatei.
readelf zeigt das auch
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
EXIDX 0x010040 0x00000040 0x00000040 0x00008 0x00008 R 0x4
LOAD 0x010000 0x00000000 0x00000000 0x00048 0x00048 R E 0x10000
LOAD 0x020004 0x20000004 0x00000048 0x00004 0x00004 RW 0x10000
LOAD 0x030000 0x20000000 0x20000000 0x00000 0x00004 RW 0x10000
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RWE 0x10
Die LOAD-Zeile, in der die virtuelle Adresse 0x20000004 und die physische Adresse 0x48 ist