2013-03-02 14 views
7

Per un compito a casa mi sono stati dati alcuni file c, e li ho compilati usando arm-linux-gcc (avremo scelto come target le schede gumstix, ma per questi esercizi abbiamo lavorato con qemu ed ema).Local variable location in memory

Una delle domande mi confonde un bit-- ci viene detto di:

Usa braccio-linux-objdump per trovare la posizione di variabili dichiarate in main() nel binario eseguibile.

Tuttavia, queste variabili sono locali e quindi non dovrebbero avere indirizzi fino al runtime, corretto?

Sto pensando che forse quello che devo trovare è l'offset nel frame dello stack, che in effetti può essere trovato usando objdump (non che io sappia come).

In ogni caso, qualsiasi comprensione della questione sarebbe molto apprezzata, e sarei felice di pubblicare il codice sorgente, se necessario.

+0

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

+1

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

+1

"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

risposta

1

Dipenderà dal programma e da come esattamente vogliono la posizione delle variabili. La domanda vuole quale sezione di codice sono memorizzati? .const .bss ecc.? Vuole indirizzi specifici? In entrambi i casi un buon inizio sta usando objdump -S bandiera

objdump -S myprogram > dump.txt 

Questo è bello perché si stamperà un mescolamento del codice sorgente e l'assemblea con gli indirizzi. Da qui basta fare una ricerca per il tuo int main e questo dovrebbe iniziare.

+0

Grazie mille, questo è stato il posto di partenza più utile per me. La domanda richiede diverse informazioni (quale riga main() inizia, ecc.) Ma ho capito che la comprensione in particolare avrebbe aiutato la mia comprensione generale di objdump. Per questa istanza, ho bisogno di un indirizzo esadecimale (o valore esadecimale per l'offset dal puntatore dello stack, come indicato diligentemente da mkfs) – bkane521

+0

Questo solleva in realtà un'altra domanda; dopo aver seguito un articolo che ho trovato su http://linuxgazette.net/issue84/hawk.html, sono stato indotto a credere di aver trovato l'indirizzo corretto in cui risiede "main", ma questo non è d'accordo con l'indirizzo esadecimale in cui inizia il main in l'output di objdump -S mybin .. qualche pensiero? – bkane521

+2

Bene, è necessario conoscere un po 'come il compilatore costruisce l'eseguibile. GCC, ad esempio, crea una funzione _start() che alla fine richiama main(). L'indirizzo di _start() sarà il punto di ingresso dell'eseguibile. Assicurati di sapere qual è il tuo insegnante sta chiedendo: il punto di ingresso dell'eseguibile o la funzione con il simbolo "principale". – mkfs

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

+0

Estremamente accurato, grazie per un esempio così ricco. Spero solo che il mio professore abbia avuto la stessa cura nel dimostrare questi principi :) – bkane521