1) il file binario compilato viene scritto su prom / flash sì. USB, seriale, i2c, jtag, ecc. Dipendono dal dispositivo da ciò che è supportato da quel dispositivo, irrilevante per comprendere il processo di avvio.
2) Questo in genere non è vero per un microcontrollore, il caso d'uso principale è avere istruzioni in rom / flash e dati in ram. Non importa quale sia l'architettura. per un non microcontrollore, il tuo pc, il tuo laptop, il tuo server, il programma viene copiato da non volatile (disco) in ram e quindi eseguito da lì. Alcuni microcontrollori ti consentono di usare anche ram, anche quelli che dichiarano harvard anche se sembra violare la definizione. Non c'è nulla in Harvard che ti impedisce di mappare ram nel lato istruzioni, devi solo avere un meccanismo per ottenere le istruzioni lì dopo l'accensione (il che viola la definizione, ma i sistemi Harvard dovrebbero farlo per essere utili altri che come microcontrollori).
3) sorta di.
Ogni CPU "si avvia" in modo deterministico, come progettato. Il modo più comune è una tabella vettoriale in cui l'indirizzo per le prime istruzioni da eseguire dopo l'accensione si trovano nel vettore di ripristino, un indirizzo che l'hardware legge quindi utilizza quell'indirizzo per iniziare l'esecuzione. L'altro modo generale è far iniziare l'esecuzione del processore senza una tabella vettoriale in un indirizzo ben noto. A volte il chip avrà "cinturini", alcuni pin che è possibile collegare in alto o in basso prima di rilasciare il reset, che la logica utilizza per l'avvio in modi diversi. Devi separare la cpu stessa, il core del processore dal resto del sistema. Comprendere come funziona la CPU, quindi capire che i progettisti di chip / sistema hanno decodificatori di indirizzi di installazione all'esterno della CPU in modo che una parte dello spazio degli indirizzi della CPU comunichi con un flash, e alcuni con ram e alcuni con periferiche (uart, i2c, spi, gpio, ecc.). Se lo desideri, puoi prendere lo stesso core della CPU e avvolgerlo in modo diverso. Questo è ciò che ottieni quando acquisti qualcosa basato su braccio o mips. arm e mips creano core in cpu, che i chip comprano e avvolgono le proprie cose, per vari motivi che non rendono compatibili quelle cose da marchio a marchio. Ecco perché raramente è possibile porre una domanda generica sul braccio quando si tratta di qualcosa al di fuori del nucleo.
Un microcontrollore tenta di essere un sistema su un chip, quindi la sua memoria non volatile (flash / rom), volatile (sram) e cpu sono tutti sullo stesso chip insieme a una combinazione di periferiche. Ma il chip è progettato internamente in modo tale che il flash sia mappato nello spazio degli indirizzi della CPU che corrisponda alle caratteristiche di avvio di quella CPU. Se ad esempio la cpu ha un vettore di reset all'indirizzo 0xFFFC, allora ci deve essere flash / rom che risponde a quell'indirizzo che possiamo programmare tramite 1), insieme a abbastanza flash / rom nello spazio degli indirizzi per programmi utili. Un progettista di chip può scegliere di avere 0x1000 byte di flash a partire da 0xF000 per soddisfare tali requisiti. E forse hanno messo una certa quantità di RAM ad un indirizzo inferiore o forse 0x0000, e le periferiche da qualche parte nel mezzo.
Un'altra architettura di cpu potrebbe iniziare a essere eseguita all'indirizzo zero, quindi dovrebbero fare il contrario, posizionare il flash in modo che risponda a un intervallo di indirizzi attorno allo zero. ad esempio, da 0x0000 a 0x0FFF. e poi metti qualche montone altrove.
I progettisti di chip sanno come si avvia la CPU e hanno messo lì spazio di archiviazione non volatile (flash / rom). Spetta quindi alla gente del software scrivere il codice di avvio in modo che corrisponda al comportamento ben noto di quella CPU. Devi posizionare l'indirizzo del vettore di ripristino nel vettore di ripristino e il codice di avvio all'indirizzo che hai definito nel vettore di ripristino. La toolchain può aiutarti molto qui. a volte, esp con id point and click o altri sandbox possono fare la maggior parte del lavoro per te, tutto ciò che fai è chiamare API in un linguaggio di alto livello (C).
Tuttavia, tuttavia, il programma caricato nel flash / rom deve corrispondere al comportamento di avvio cablato della cpu. Prima della parte C del programma main () e su se usi main come punto di ingresso, alcune cose devono essere fatte. Il programmatore AC presuppone che quando dichiarano una variabile con un valore iniziale, si aspettano che funzioni effettivamente. Bene, le variabili, oltre a quelle const, sono in ram, ma se ne hai una con un valore iniziale quel valore iniziale deve essere in ram non volatile. Quindi questo è il segmento .data e il bootstrap C deve copiare roba .data da flash a ram (dove di solito è determinato per te dalla toolchain). Si presume che le variabili globali dichiarate senza un valore iniziale siano pari a zero prima dell'avvio del programma, anche se in realtà non si dovrebbe presumere che e per fortuna alcuni compilatori stanno iniziando a mettere in guardia su variabili non inizializzate. Questo è il segmento .bss, e gli zeri di bootstrap C che escono in ram, il contenuto, gli zeri, non devono essere archiviati nella memoria non volatile, ma l'indirizzo iniziale e quanto fa. Ancora una volta la toolchain ti aiuta molto qui. E infine il minimo indispensabile è che è necessario impostare un puntatore dello stack poiché i programmi C prevedono di essere in grado di avere variabili locali e chiamare altre funzioni. Quindi forse vengono fatte altre cose specifiche per chip, o lasciamo che il resto delle cose specifiche per chip accada in C. non deve essere memorizzato nella memoria non volatile, ma l'indirizzo iniziale e quanto. Ancora una volta la toolchain ti aiuta molto qui. E infine il minimo indispensabile è che è necessario impostare un puntatore dello stack poiché i programmi C prevedono di essere in grado di avere variabili locali e chiamare altre funzioni. Quindi forse vengono fatte altre cose specifiche per chip, o lasciamo che il resto delle cose specifiche per chip accada in C. non deve essere memorizzato nella memoria non volatile, ma l'indirizzo iniziale e quanto. Ancora una volta la toolchain ti aiuta molto qui. E infine il minimo indispensabile è che è necessario impostare un puntatore dello stack poiché i programmi C prevedono di essere in grado di avere variabili locali e chiamare altre funzioni. Quindi forse vengono fatte altre cose specifiche per chip, o lasciamo che il resto delle cose specifiche per chip accada in C.
I core della serie cortx-m di arm faranno un po 'di questo per te, il puntatore dello stack si trova nella tabella dei vettori, c'è un vettore di reimpostazione che punta al codice da eseguire dopo il reset, in modo che diverso da quello che devi fare per generare la tabella vettoriale (che di solito usi asm per comunque) puoi passare alla C pura senza asm. ora non riesci a copiare i tuoi .data né a zero i tuoi .bss, quindi devi farlo da solo se vuoi provare ad andare senza asm su qualcosa basato sulla corteccia-m. La funzione più grande non è il vettore di ripristino ma interrompe i vettori in cui l'hardware segue le convenzioni di chiamata C raccomandate dai bracci e conserva i registri per te e utilizza il ritorno corretto per quel vettore, in modo da non dover avvolgere l'asm giusto attorno a ciascun gestore ( o avere direttive specifiche sulla toolchain per il tuo target affinché la toolchain lo avvolga per te).
Potrebbero essere ad esempio elementi specifici del chip, i microcontrollori sono spesso utilizzati nei sistemi basati su batteria, quindi a bassa potenza quindi alcuni escono dal reset con la maggior parte delle periferiche spente e devi accendere ciascuno di questi sottosistemi in modo da poterli usare . Uarts, gpios, ecc. Spesso viene utilizzata una velocità di clock bassa, direttamente da un oscillatore a cristallo o interno. E la progettazione del tuo sistema potrebbe mostrare che hai bisogno di un orologio più veloce, quindi lo inizializzi. l'orologio potrebbe essere troppo veloce per il flash o il ram, quindi potrebbe essere necessario modificare gli stati di attesa prima di aumentare l'orologio. Potrebbe essere necessario configurare uart, o usb o altre interfacce. quindi l'applicazione può fare la sua cosa.
Un desktop di computer, laptop, server e un microcontrollore non sono diversi nel modo in cui si avviano / funzionano. Solo che non si trovano principalmente su un chip. Il programma di bios è spesso su un chip flash / rom separato dalla CPU. Anche se recentemente x86 cpus sta estraendo sempre più di quelli che erano i chip di supporto nello stesso pacchetto (controller per pc, ecc.) Ma hai ancora la maggior parte dei tuoi ram e rom off chip, ma è ancora un sistema e funziona ancora esattamente lo stesso ad alto livello. Il processo di avvio della cpu è ben noto, i progettisti della scheda posizionano il flash / rom nello spazio degli indirizzi in cui la cpu si avvia. quel programma (parte del BIOS su un PC x86) fa tutte le cose sopra menzionate, avvia varie periferiche, inizializza il gioco, enumera i bus dei pcie e così via. Spesso è abbastanza configurabile dall'utente in base alle impostazioni del BIOS o a quelle che chiamavamo impostazioni dei cmos, perché al momento è quella tecnologia utilizzata. Non importa, ci sono impostazioni utente che puoi andare e modificare per dire al codice di avvio del BIOS come variare ciò che fa.
persone diverse useranno una terminologia diversa. si avvia un chip, questo è il primo codice che viene eseguito. a volte chiamato bootstrap. un bootloader con il caricatore di parole spesso significa che se non si fa nulla per interferire, si tratta di un bootstrap che ti porta dall'avvio generico a qualcosa di più grande, l'applicazione o il sistema operativo. ma la parte del caricatore implica che è possibile interrompere il processo di avvio e quindi caricare altri programmi di test. se hai mai usato Uboot per esempio su un sistema Linux incorporato, puoi premere una chiave e fermare il normale avvio quindi puoi scaricare un kernel di prova in ram e avviarlo invece di quello che è in flash, oppure puoi scaricare il tuo propri programmi, oppure è possibile scaricare il nuovo kernel, quindi fare in modo che il bootloader lo scriva in flash in modo che al prossimo avvio esegua il nuovo materiale.
Per quanto riguarda la CPU stessa, il core processor, che non conosce il ram dal flash delle periferiche. Non esiste alcuna nozione di bootloader, sistema operativo, applicazione. È solo una sequenza di istruzioni che vengono inserite nella CPU da eseguire. Questi sono i termini del software per distinguere tra loro diverse attività di programmazione. Concetti di software l'uno dall'altro.
Alcuni microcontrollori hanno un bootloader separato fornito dal fornitore del chip in un flash separato o in un'area separata del flash che potresti non essere in grado di modificare. In questo caso c'è spesso un pin o un set di pin (li chiamo cinturini) che se li leghi in alto o in basso prima di rilasciare il reset stai dicendo alla logica e / o al bootloader cosa fare, ad esempio una combinazione di cinturini può dire al chip di eseguire quel bootloader e attendere in uart la programmazione dei dati nel flash. Impostare le cinghie dall'altra parte e il programma non avvia il bootloader dei produttori di chip, consentendo la programmazione sul campo del chip o il ripristino dal crash del programma. A volte è solo la pura logica che ti permette di programmare il flash. Questo è abbastanza comune in questi giorni,
Il motivo per cui la maggior parte dei microcontrollori ha molta più memoria flash rispetto alla RAM è che il caso d'uso principale è quello di eseguire il programma direttamente dalla memoria flash e avere RAM sufficiente per coprire stack e variabili. Anche se in alcuni casi è possibile eseguire programmi da ram che è necessario compilare correttamente e archiviare in flash, quindi copiare prima di chiamare.
MODIFICARE
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
}
Quindi questo è un esempio di corteccia-m0, la corteccia-ms funziona allo stesso modo per quanto riguarda questo esempio. Il chip particolare, per questo esempio, ha l'applicazione flash all'indirizzo 0x00000000 nello spazio degli indirizzi arm e ram a 0x20000000.
Il modo in cui un cortex-m si avvia è la parola a 32 bit all'indirizzo 0x0000 è l'indirizzo per inizializzare il puntatore dello stack. Non ho bisogno di molto stack per questo esempio, quindi 0x20001000 sarà sufficiente, ovviamente ci deve essere una ram sotto quell'indirizzo (il modo in cui il braccio spinge, viene sottratto prima quindi spinge quindi se si imposta 0x20001000 il primo elemento nello stack è all'indirizzo 0x2000FFFC non è necessario utilizzare 0x2000FFFC). La parola a 32 bit all'indirizzo 0x0004 è l'indirizzo del gestore di reset, in pratica il primo codice che viene eseguito dopo un reset. Poi ci sono più gestori di interrupt ed eventi specifici di quel core e chip di corteccia m, possibilmente fino a 128 o 256, se non li usi, non hai bisogno di impostare la tabella per loro, ne ho lanciati alcuni per dimostrazione scopi.
Non ho bisogno di trattare con .data né .bss in questo esempio perché so già che non c'è nulla in quei segmenti guardando il codice. Se ci fosse, me ne occuperei, e lo farò in un secondo.
Quindi lo stack è impostato, controlla, .data curato, check, .bss, check, quindi le cose C bootstrap sono fatte, possono diramarsi alla funzione entry per C. Perché alcuni compilatori aggiungeranno junk extra se vedono la funzione main () e sulla strada per main, non uso quel nome esatto, ho usato notmain () qui come punto di ingresso C. Quindi il gestore di reset chiama notmain () quindi se / quando notmain () ritorna va in blocco che è solo un ciclo infinito, probabilmente mal chiamato.
Credo fermamente nel padroneggiare gli strumenti, molte persone non lo fanno, ma quello che scoprirai è che ogni sviluppatore bare metal fa le sue cose, a causa della libertà quasi completa, non lontanamente vincolata come faresti per creare app o pagine web . Fanno di nuovo le loro cose. Preferisco avere il mio codice bootstrap e script linker. Altri si affidano alla toolchain o giocano nella sandbox dei fornitori in cui la maggior parte del lavoro viene eseguita da qualcun altro (e se qualcosa si rompe sei in un mondo ferito, e con le parti metalliche nude si rompono spesso e in modo drammatico).
Quindi assemblando, compilando e collegando con strumenti gnu ottengo:
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
Quindi, come fa il bootloader a sapere dove sono le cose. Perché il compilatore ha fatto il lavoro. Nel primo caso l'assemblatore ha generato il codice per flash.s, e così facendo sa dove si trovano le etichette (le etichette sono solo indirizzi proprio come i nomi delle funzioni o i nomi delle variabili, ecc.), Quindi non ho dovuto contare i byte e compilare il vettore tabella manualmente, ho usato un nome di etichetta e l'assemblatore l'ha fatto per me. Ora chiedi, se reset è l'indirizzo 0x14 perché l'assemblatore ha inserito 0x15 nella tabella vettoriale. Bene, questa è una corteccia-m e si avvia e funziona solo in modalità pollice. Con ARM quando si dirama verso un indirizzo se si ramifica in modalità pollice, è necessario impostare lsbit, se si ripristina la modalità arm. Quindi hai sempre bisogno di quel bit impostato. Conosco gli strumenti e mettendo .thumb_func prima di un'etichetta, se quell'etichetta viene utilizzata così com'è nella tabella vettoriale o per ramificarsi o altro. La toolchain sa come impostare lsbit. Quindi ha qui 0x14 | 1 = 0x15. Allo stesso modo per appendere. Ora il disassemblatore non mostra 0x1D per la chiamata a notmain () ma non ti preoccupare, gli strumenti hanno costruito correttamente l'istruzione.
Ora, quel codice in notmain, quelle variabili locali non sono usate, sono codice morto. Il compilatore commenta anche questo fatto dicendo che y è impostato ma non utilizzato.
Nota lo spazio degli indirizzi, tutte queste cose iniziano all'indirizzo 0x0000 e vanno da lì in modo che la tabella vettoriale sia posizionata correttamente, anche lo spazio .text o del programma sia posizionato correttamente, come ho flash.s davanti al codice di notmain.c conoscendo gli strumenti, un errore comune è quello di non farlo bene, di schiantarsi e bruciare duramente. IMO devi disassemblare per assicurarti che le cose siano posizionate proprio prima di avviare la prima volta, una volta che hai le cose nel posto giusto che non devi necessariamente controllare ogni volta. Solo per nuovi progetti o se si bloccano.
Ora qualcosa che sorprende alcune persone è che non c'è motivo di aspettarsi che due compilatori producano lo stesso output dallo stesso input. O anche lo stesso compilatore con impostazioni diverse. Usando clang, il compilatore llvm ottengo questi due output con e senza ottimizzazione
llvm / clang ottimizzato
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
non ottimizzato
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
quindi è una bugia che il compilatore ha ottimizzato l'aggiunta, ma ha allocato due elementi nello stack per le variabili, poiché si tratta di variabili locali che sono in ram ma sullo stack non a indirizzi fissi, vedrà con i globali che quello i cambiamenti. Ma il compilatore si è reso conto che poteva calcolare y in fase di compilazione e non c'era motivo di calcolarlo in fase di esecuzione, quindi ha semplicemente inserito un 1 nello spazio dello stack allocato per xe un 2 per lo spazio dello stack allocato per y. il compilatore "alloca" questo spazio con tabelle interne Dichiaro stack più 0 per la variabile y e stack più 4 per la variabile x. il compilatore può fare quello che vuole fintanto che il codice che implementa è conforme allo standard C o alle aspettative di un programmatore C. Non vi è alcun motivo per cui il compilatore debba lasciare x nello stack + 4 per la durata della funzione,
Se aggiungo una funzione fittizia in assembler
.thumb_func
.globl dummy
dummy:
bx lr
e poi chiamalo
void dummy ( unsigned int );
int notmain ( void )
{
unsigned int x=1;
unsigned int y;
y = x + 1;
dummy(y);
return(0);
}
l'uscita cambia
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
ora che abbiamo funzioni nidificate, la funzione notmain deve preservare il suo indirizzo di ritorno, in modo da poter bloccare l'indirizzo di ritorno per la chiamata nidificata. questo perché il braccio usa un registro per i ritorni, se usasse lo stack come dire un x86 o alcuni altri bene ... avrebbe comunque usato lo stack ma in modo diverso. Ora chiedi perché ha spinto r4? Bene, la convenzione di chiamata non molto tempo fa è cambiata per mantenere lo stack allineato su limiti a 64 bit (due parole) anziché a 32 bit, limiti di una parola. Quindi devono spingere qualcosa per mantenere lo stack allineato, quindi il compilatore ha scelto arbitrariamente r4 per qualche motivo, non importa perché. Fare una pausa in r4 sarebbe un bug sebbene secondo la convenzione di chiamata per questo obiettivo, non ostruiamo r4 su una chiamata di funzione, possiamo ostruire da r0 a r3. r0 è il valore restituito. Sembra che stia facendo un'ottimizzazione della coda, forse
Ma vediamo che la matematica xey è ottimizzata per un valore hardcoded di 2 che viene passato alla funzione fittizia (fittizio era specificamente codificato in un file separato, in questo caso asm, in modo che il compilatore non ottimizzasse completamente la funzione, se avessi una funzione fittizia che è semplicemente ritornata in C in notmain.c l'ottimizzatore avrebbe rimosso la chiamata x, ye la funzione fittizia perché sono tutti codici morti / inutili).
Nota anche che, poiché il codice flash.s è diventato più grande, notmain è altrimenti e la toolchain si è occupata della correzione di tutti gli indirizzi per noi, quindi non dobbiamo farlo manualmente.
clang non ottimizzato per riferimento
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}
clang ottimizzato
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}
quell'autore del compilatore ha scelto di usare r7 come variabile fittizia per allineare lo stack, inoltre sta creando un puntatore a frame usando r7 anche se non ha nulla nel frame dello stack. sostanzialmente le istruzioni avrebbero potuto essere ottimizzate. ma ha usato il pop per non restituire tre istruzioni, che probabilmente era su di me Scommetto che avrei potuto ottenere gcc per farlo con le giuste opzioni della riga di comando (specificando il processore).
questo dovrebbe rispondere principalmente al resto delle tue domande
void dummy ( unsigned int );
unsigned int x=1;
unsigned int y;
int notmain ( void )
{
y = x + 1;
dummy(y);
return(0);
}
Adesso ho i globi. quindi vanno in .data o .bss se non vengono ottimizzati.
prima di guardare l'output finale, guardiamo l'oggetto itermediate
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
ora mancano informazioni da questo, ma danno un'idea di ciò che sta succedendo, il linker è quello che prende gli oggetti e li collega insieme alle informazioni fornite (in questo caso flash.ld) che le dice dove .text e. dati e così via. il compilatore non conosce queste cose, può solo concentrarsi sul codice che viene presentato, qualsiasi esterno deve lasciare un buco affinché il linker riempia la connessione. Tutti i dati che deve lasciare un modo per collegare queste cose insieme, quindi gli indirizzi per tutto sono basati su zero qui semplicemente perché il compilatore e questo disassemblatore non lo sanno. ci sono altre informazioni non mostrate qui che il linker usa per posizionare le cose. il codice qui è abbastanza indipendente dalla posizione in modo che il linker possa fare il suo lavoro.
vediamo quindi almeno uno smontaggio dell'output collegato
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
il compilatore ha sostanzialmente chiesto due variabili a 32 bit in ram. Uno è in .bss perché non l'ho inizializzato, quindi si presume che init sia zero. l'altro è .data perché l'ho inizializzato su dichiarazione.
Ora, poiché si tratta di variabili globali, si presume che altre funzioni possano modificarle. il compilatore non fa ipotesi su quando può essere chiamato notmain, quindi non può ottimizzare con ciò che può vedere, la matematica y = x + 1, quindi deve fare quel runtime. Deve leggere da ram le due variabili aggiungerle e salvarle.
Ora chiaramente questo codice non funzionerà. Perché? perché il mio bootstrap come mostrato qui non prepara il ram prima di chiamare notmain, quindi qualunque spazzatura fosse in 0x20000000 e 0x20000004 quando il chip si è svegliato sarà quello che verrà usato per y e x.
Non lo mostrerò qui. puoi leggere il mio divagare ancora più prolisso su .data e .bss e perché non ne ho mai bisogno nel mio codice bare metal, ma se senti che devi e vuoi padroneggiare gli strumenti piuttosto che sperare che qualcun altro lo abbia fatto bene .. .
https://github.com/dwelch67/raspberrypi/tree/master/bssdata
gli script dei linker e i bootstrap sono in qualche modo specifici del compilatore, quindi tutto ciò che apprendi su una versione di un compilatore potrebbe essere lanciato sulla versione successiva o con qualche altro compilatore, un altro motivo per cui non mi sforzo molto nella preparazione di .data e .bss solo per essere così pigro:
unsigned int x=1;
Preferirei di gran lunga fare questo
unsigned int x;
...
x = 1;
e lascia che il compilatore lo metta in .text per me. A volte salva il flash in questo modo a volte brucia di più. È sicuramente molto più facile programmare e trasferire dalla versione della toolchain o da un compilatore all'altro. Molto più affidabile, meno soggetto a errori. Sì, non è conforme allo standard C.
ora cosa succede se creiamo questi globi statici?
void dummy ( unsigned int );
static unsigned int x=1;
static unsigned int y;
int notmain ( void )
{
y = x + 1;
dummy(y);
return(0);
}
bene
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
ovviamente quelle variabili non possono essere modificate da altri codici, quindi il compilatore può ora in fase di compilazione ottimizzare il codice morto, come prima.
non ottimizzato
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
questo compilatore che utilizzava lo stack per i locali, ora utilizza ram per i globali e questo codice come scritto è rotto perché non ho gestito correttamente .data né .bss.
e un'ultima cosa che non possiamo vedere nello smontaggio.
:1000000000100020150000001B0000001B00000075
:100010001B00000000F004F8FFE7FEE77047000057
:1000200080B500AF04480068401C04490860FFF731
:10003000F5FF002080BDC046040000200000002025
:08004000E0FFFF7F010000005A
:0400480078563412A0
:00000001FF
Ho cambiato x per essere pre-init con 0x12345678. Il mio script per il linker (questo è per gnu ld) ha questa storia su Bob. che dice al linker che voglio che l'ultimo posto sia nello spazio degli indirizzi di ted, ma memorizzalo nel binario nello spazio degli indirizzi di ted e qualcuno lo sposterà per te. E possiamo vedere che è successo. questo è intel formato esadecimale. e possiamo vedere lo 0x12345678
:0400480078563412A0
è nello spazio degli indirizzi flash del binario.
anche questo mostra questo
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
la riga LOAD in cui l'indirizzo virtuale è 0x20000004 e il fisico è 0x48