unsigned int one (unsigned int, unsigned int);
unsigned int two (unsigned int, unsigned int);
unsigned int myfun (unsigned int x, unsigned int y, unsigned int z)
{
unsigned int a,b;
a=one(x,y);
b=two(a,z);
return(a+b);
}
compilazione e smontare
arm-none-eabi-gcc -c fun.c -o fun.o
arm-none-eabi-objdump -D fun.o
codice creato dal compilatore
00000000 <myfun>:
0: e92d4800 push {fp, lr}
4: e28db004 add fp, sp, #4
8: e24dd018 sub sp, sp, #24
c: e50b0010 str r0, [fp, #-16]
10: e50b1014 str r1, [fp, #-20]
14: e50b2018 str r2, [fp, #-24]
18: e51b0010 ldr r0, [fp, #-16]
1c: e51b1014 ldr r1, [fp, #-20]
20: ebfffffe bl 0 <one>
24: e50b0008 str r0, [fp, #-8]
28: e51b0008 ldr r0, [fp, #-8]
2c: e51b1018 ldr r1, [fp, #-24]
30: ebfffffe bl 0 <two>
34: e50b000c str r0, [fp, #-12]
38: e51b2008 ldr r2, [fp, #-8]
3c: e51b300c ldr r3, [fp, #-12]
40: e0823003 add r3, r2, r3
44: e1a00003 mov r0, r3
48: e24bd004 sub sp, fp, #4
4c: e8bd4800 pop {fp, lr}
50: e12fff1e bx lr
risposta breve è la memoria viene "assegnata", sia in fase di compilazione e in fase di esecuzione. In fase di compilazione, nel senso che il compilatore in fase di compilazione determina la dimensione del frame dello stack e chi va dove. Tempo di esecuzione nel senso che la memoria stessa è in pila, che è una cosa dinamica. Il frame dello stack viene preso dalla memoria dello stack in fase di esecuzione, quasi come un malloc() e free().
È utile conoscere la convenzione di chiamata, x immette in r0, y in r1, z in r2. allora x ha la sua sede in fp-16, y in fp-20 e z in fp-24. quindi la chiamata a one() ha bisogno di xey quindi tira quelli dalla pila (xey). il risultato di uno() va in uno che viene salvato in fp-8, quindi quella è la casa di a. e così via.
la funzione uno non è realmente all'indirizzo 0, questo è uno smontaggio di un file oggetto non un binario collegato. una volta che un oggetto è collegato con il resto degli oggetti e delle librerie, le parti mancanti, come le funzioni esterne, vengono applicate dal linker e le chiamate a uno() e due() otterranno indirizzi reali. (e il programma probabilmente non inizierà all'indirizzo 0).
ho barato un po 'qui, sapevo che con nessuna ottimizzazione abilitati sul compilatore e relativamente semplice funzione come questa c'è davvero alcun motivo per uno stack frame:
compilare con solo un po' di ottimizzazione
arm-none-eabi-gcc -O1 -c fun.c -o fun.o
arm-none-eabi-objdump -D fun.o
e il frame dello stack è scomparso, le variabili locali rimangono nei registri.
00000000: 0: e92d4038 spinta {R3, R4, R5, lr} 4: e1a05002 mov r5, r2 8: ebfffffe bl 0 c: e1a04000 MOV r4, r0 10: e1a01005 MOV r1, r5 14: ebfffffe bl 0 18: e0800004 aggiungere R0, R0, R4 1c: e8bd4038 pop {R3, R4, R5, lr} 20: e12fff1e BX lr
ciò che il compilatore ha deciso di fare, invece, è darsi più registri con cui lavorare salvandoli in pila. Perché ha salvato r3 è un mistero, ma questo è un altro argomento ...
immettendo la funzione r0 = x, r1 = ye r2 = z per la convenzione di chiamata, possiamo lasciare solo r0 e r1 (riprova con uno (y, x) e vedere cosa succede) poiché cadono direttamente in uno() e non vengono mai più usati. La convenzione di chiamata dice che r0-r3 può essere distrutto da una funzione, quindi è necessario preservare z per dopo, quindi salvarlo in r5. Il risultato di one() è r0 per la convenzione di chiamata, dato che due() possono distruggere r0-r3 che dobbiamo salvare per dopo, dopo la chiamata a due(), inoltre, abbiamo bisogno di r0 per la chiamata a due, quindi r4 ora detiene un Abbiamo salvato z in r5 (era in r2 spostato in r5) prima della chiamata a uno, abbiamo bisogno del risultato di uno() come primo parametro su due(), ed è già lì, abbiamo bisogno di z come secondo, quindi sposta r5 dove abbiamo salvato z in r1, quindi chiamiamo due(). il risultato di due() per la convenzione di chiamata. Poiché b + a = a + b dalle proprietà matematiche di base l'addizione finale prima di ritornare è r0 + r4 che è b + a, e il risultato va in r0 che è il registro usato per restituire qualcosa da una funzione, secondo la convenzione. ripulire lo stack e ripristinare i registri modificati, fatto.
Poiché myfun() ha effettuato chiamate ad altre funzioni utilizzando bl, bl modifica il registro di collegamento (r14), per poter tornare da myfun() è necessario che il valore nel registro di collegamento sia conservato dalla voce nella funzione fino al ritorno finale (bx lr), quindi lr viene inserito nello stack. La convenzione afferma che possiamo distruggere r0-r3 nella nostra funzione, ma non altri registri in modo che r4 e r5 siano messi in pila perché li abbiamo usati. perché r3 è spinto in pila non è necessario da una prospettiva di convenzione di chiamata, mi chiedo se è stato fatto in previsione di un sistema di memoria a 64 bit, rendendo due scritture a 64 bit più economiche di una scrittura a 64 bit e una a 32 bit a destra. ma è necessario conoscere l'allineamento dello stack in modo che sia solo una teoria. Non c'è motivo di conservare r3 in questo codice.
Ora prendere questa conoscenza e disassemblare il codice assegnato (arm -...- objdump -D qualcosa. Qualcosa) e fare lo stesso tipo di analisi. in particolare con le funzioni denominate main() vs funzioni non nominate main (non ho usato main() di proposito) il frame dello stack può essere una dimensione che non ha senso, o ha meno senso di altre funzioni. Nel caso non ottimizzato sopra abbiamo dovuto memorizzare 6 cose in totale, x, y, z, a, b e il registro di collegamento 6 * 4 = 24 byte che ha provocato sub sp, sp, # 24, ho bisogno di pensare al stack pointer vs frame pointer cosa per un po '. Penso che ci sia un argomento della linea di comando per dire al compilatore di non usare un puntatore del frame. -fomit-frame-pointer e salva un paio di istruzioni
00000000 <myfun>:
0: e52de004 push {lr} ; (str lr, [sp, #-4]!)
4: e24dd01c sub sp, sp, #28
8: e58d000c str r0, [sp, #12]
c: e58d1008 str r1, [sp, #8]
10: e58d2004 str r2, [sp, #4]
14: e59d000c ldr r0, [sp, #12]
18: e59d1008 ldr r1, [sp, #8]
1c: ebfffffe bl 0 <one>
20: e58d0014 str r0, [sp, #20]
24: e59d0014 ldr r0, [sp, #20]
28: e59d1004 ldr r1, [sp, #4]
2c: ebfffffe bl 0 <two>
30: e58d0010 str r0, [sp, #16]
34: e59d2014 ldr r2, [sp, #20]
38: e59d3010 ldr r3, [sp, #16]
3c: e0823003 add r3, r2, r3
40: e1a00003 mov r0, r3
44: e28dd01c add sp, sp, #28
48: e49de004 pop {lr} ; (ldr lr, [sp], #4)
4c: e12fff1e bx lr
ottimizzazione consente di risparmiare un sacco di più anche se ...
Penso che il codice sorgente da solo non sia di troppo uso qui. Sarebbe meglio di voi pubblicare il codice binario smontato insieme ad esso. In sostanza dovresti provare a scansionare il codice generato per i compiti che hai effettuato. Tuttavia, se si è in cattiva sorte, non ci sarà alcuna posizione di archiviazione, poiché il compilatore è libero di mantenere le variabili locali nei registri senza memorizzarli nella memoria principale. Ma puoi provare almeno. – mikyra
Oh, tutto va per il caso in cui il tuo binario non è stato costruito con i simboli di debug, altrimenti il lavoro dovrebbe essere molto più semplice. – mikyra
"Quello che devo trovare è l'offset nel frame dello stack" - questo è praticamente ciò che devi fare. I locali saranno in pila o in registri. Utilizzare objdump per disassemblare il binario, quindi andare alla funzione principale e cercare gli operandi che sono offset dal puntatore del frame (R11) o dal puntatore dello stack (R13), se il puntatore del frame non viene utilizzato .. – mkfs