2016-02-07 10 views
5

questo il mio programma:rifiuti in allocazione di memoria per le variabili locali

void test_function(int a, int b, int c, int d){ 
    int flag; 
    char buffer[10]; 

    flag = 31337; 
    buffer[0] = 'A'; 
} 

int main() { 
    test_function(1, 2, 3, 4); 
} 

compilo questo programma con l'opzione di debug:

gcc -g my_program.c 

Io uso gdb e smontare il test_function con la sintassi Intel:

(gdb) disassemble test_function 
Dump of assembler code for function test_function: 
0x08048344 <test_function+0>: push ebp 
0x08048345 <test_function+1>: mov ebp,esp 
0x08048347 <test_function+3>: sub esp,0x28 
0x0804834a <test_function+6>: mov DWORD PTR [ebp-12],0x7a69 
0x08048351 <test_function+13>: mov BYTE PTR [ebp-40],0x41 
0x08048355 <test_function+17>: leave 
0x08048356 <test_function+18>: ret  
End of assembler dump. 

E io smontare il principale:

(gdb) disassemble main 
Dump of assembler code for function main: 
0x08048357 <main+0>: push ebp 
0x08048358 <main+1>: mov ebp,esp 
0x0804835a <main+3>: sub esp,0x18 
0x0804835d <main+6>: and esp,0xfffffff0 
0x08048360 <main+9>: mov eax,0x0 
0x08048365 <main+14>: sub esp,eax 
0x08048367 <main+16>: mov DWORD PTR [esp+12],0x4 
0x0804836f <main+24>: mov DWORD PTR [esp+8],0x3 
0x08048377 <main+32>: mov DWORD PTR [esp+4],0x2 
0x0804837f <main+40>: mov DWORD PTR [esp],0x1 
0x08048386 <main+47>: call 0x8048344 <test_function> 
0x0804838b <main+52>: leave 
0x0804838c <main+53>: ret  
End of assembler dump. 

Inserisco un punto di interruzione in questo indirizzo: 0x08048355 (lascia istruzioni per la funzione test) e eseguo il programma.

guardo la pila così:

(gdb) x/16w $esp 
0xbffff7d0:  0x00000041  0x08049548  0xbffff7e8  0x08048249 
0xbffff7e0:  0xb7f9f729  0xb7fd6ff4  0xbffff818  0x00007a69 
0xbffff7f0:  0xb7fd6ff4  0xbffff8ac  0xbffff818  0x0804838b 
0xbffff800:  0x00000001  0x00000002  0x00000003  0x00000004 

0x0804838b rappresenta l'indirizzo di ritorno, 0xbffff818 è il puntatore fotogramma salvato (EBP principale) e variabile di flag è fornito ulteriore 12 byte. Perché 12?

Non capisco questa istruzione:

0x0804834a <test_function+6>: mov DWORD PTR [ebp-12],0x7a69 

perché non magazzino il contenuto della variabile 0x00007a69 in EBP-4 invece di 0xbffff8ac?

Stessa domanda per buffer. Perché 40?

Non sprechiamo la memoria? 0xb7fd6ff4 0xbffff8ac e 0xb7f9f729 0xb7fd6ff4 0xbffff818 0x08049548 0xbffff7e8 0x08048249 non vengono utilizzati?

Questo l'output del comando gcc -Q -v -g my_program.c:

Reading specs from /usr/lib/gcc-lib/i486-linux-gnu/3.3.6/specs 
Configured with: ../src/configure -v --enable-languages=c,c++ --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --with-gxx-include-dir=/usr/include/c++/3.3 --enable-shared --enable-__cxa_atexit --with-system-zlib --enable-nls --without-included-gettext --enable-clocale=gnu --enable-debug i486-linux-gnu 
Thread model: posix 
gcc version 3.3.6 (Ubuntu 1:3.3.6-15ubuntu1) 
/usr/lib/gcc-lib/i486-linux-gnu/3.3.6/cc1 -v -D__GNUC__=3 -D__GNUC_MINOR__=3 -D__GNUC_PATCHLEVEL__=6 notesearch.c -dumpbase notesearch.c -auxbase notesearch -g -version -o /tmp/ccGT0kTf.s 
GNU C version 3.3.6 (Ubuntu 1:3.3.6-15ubuntu1) (i486-linux-gnu) 
     compiled by GNU C version 3.3.6 (Ubuntu 1:3.3.6-15ubuntu1). 
GGC heuristics: --param ggc-min-expand=99 --param ggc-min-heapsize=129473 
options passed: -v -D__GNUC__=3 -D__GNUC_MINOR__=3 -D__GNUC_PATCHLEVEL__=6 
-auxbase -g 
options enabled: -fpeephole -ffunction-cse -fkeep-static-consts 
-fpcc-struct-return -fgcse-lm -fgcse-sm -fsched-interblock -fsched-spec 
-fbranch-count-reg -fcommon -fgnu-linker -fargument-alias 
-fzero-initialized-in-bss -fident -fmath-errno -ftrapping-math -m80387 
-mhard-float -mno-soft-float -mieee-fp -mfp-ret-in-387 
-maccumulate-outgoing-args -mcpu=pentiumpro -march=i486 
ignoring nonexistent directory "/usr/local/include/i486-linux-gnu" 
ignoring nonexistent directory "/usr/i486-linux-gnu/include" 
ignoring nonexistent directory "/usr/include/i486-linux-gnu" 
#include "..." search starts here: 
#include <...> search starts here: 
/usr/local/include 
/usr/lib/gcc-lib/i486-linux-gnu/3.3.6/include 
/usr/include 
End of search list. 
gnu_dev_major gnu_dev_minor gnu_dev_makedev stat lstat fstat mknod fatal ec_malloc dump main print_notes find_user_note search_note 
Execution times (seconds) 
preprocessing   : 0.00 (0%) usr 0.01 (25%) sys 0.00 (0%) wall 
lexical analysis  : 0.00 (0%) usr 0.01 (25%) sys 0.00 (0%) wall 
parser    : 0.02 (100%) usr 0.01 (25%) sys 0.00 (0%) wall 
TOTAL     : 0.02    0.04    0.00 
as -V -Qy -o /tmp/ccugTYeu.o /tmp/ccGT0kTf.s 
GNU assembler version 2.17.50 (i486-linux-gnu) using BFD version 2.17.50 20070103 Ubuntu 
/usr/lib/gcc-lib/i486-linux-gnu/3.3.6/collect2 --eh-frame-hdr -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 /usr/lib/gcc-lib/i486-linux-gnu/3.3.6/../../../crt1.o /usr/lib/gcc-lib/i486-linux-gnu/3.3.6/../../../crti.o /usr/lib/gcc-lib/i486-linux-gnu/3.3.6/crtbegin.o -L/usr/lib/gcc-lib/i486-linux-gnu/3.3.6 -L/usr/lib/gcc-lib/i486-linux-gnu/3.3.6/../../.. /tmp/ccugTYeu.o -lgcc --as-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed -lgcc_s --no-as-needed /usr/lib/gcc-lib/i486-linux-gnu/3.3.6/crtend.o /usr/lib/gcc-lib/i486-linux-gnu/3.3.6/../../../crtn.o 

NOTA: ho letto il libro "The art of exploitation" e io uso la VM fornisce con il libro.

+2

Allineamento pila ... – Macmade

+0

Sebbene tu abbia accettato una risposta, sono ancora molto curioso del _GCC_ che stai utilizzando. Potresti dirci il tuo sistema operativo (e la versione del sistema operativo) e puoi aggiungere l'output di questo comando alla fine della tua domanda 'gcc -Q -v -g my_program.c' per fornirci informazioni che potrebbero essere utili per sapere esattamente perché hai ottenuto questo particolare codice assembly dal compilatore? –

+0

Grazie per aver fornito le informazioni aggiuntive. Quando avrò la possibilità farò delle indagini. –

risposta

6

Il compilatore sta cercando di mantenere l'allineamento 16 byte sullo stack . Questo vale anche per il codice a 32 bit in questi giorni (non solo a 64 bit). L'idea è che al punto prima di eseguire un'istruzione CALL lo stack deve essere allineato a un limite di 16 byte.

Poiché non sono state eseguite ottimizzazioni, sono presenti alcune istruzioni estranee.

0x0804835a <main+3>: sub esp,0x18  ; Allocate local stack space 
0x0804835d <main+6>: and esp,0xfffffff0 ; Ensure `main` has a 16 byte aligned stack 
0x08048360 <main+9>: mov eax,0x0   ; Extraneous, not needed 
0x08048365 <main+14>: sub esp,eax   ; Extraneous, not needed 

ESP è ora a 16 byte allineato dopo l'ultima istruzioni di cui sopra. Spostiamo i parametri per la chiamata iniziando in cima allo stack a ESP. Questo viene fatto con:

0x08048367 <main+16>: mov DWORD PTR [esp+12],0x4 
0x0804836f <main+24>: mov DWORD PTR [esp+8],0x3 
0x08048377 <main+32>: mov DWORD PTR [esp+4],0x2 
0x0804837f <main+40>: mov DWORD PTR [esp],0x1 

Il CALL poi spinge un indirizzo di ritorno 4 byte sullo stack. Raggiungiamo quindi queste istruzioni dopo la chiamata:

0x08048344 <test_function+0>: push ebp  ; 4 bytes pushed on stack 
0x08048345 <test_function+1>: mov ebp,esp ; Setup stackframe 

Questo spinge altri 4 byte in pila. Con i 4 byte dell'indirizzo di ritorno ora siamo disallineati di 8 byte. Per raggiungere nuovamente l'allineamento a 16 byte, dovremo sprecare altri 8 byte nello stack. Ecco perché in questa affermazione c'è un ulteriore 8 byte allocato:

0x08048347 <test_function+3>: sub esp,0x28 
  • 0x08 byte già sulla pila causa di indirizzo di ritorno (4 byte) e EBP (4 byte)
  • 0x08 byte di padding necessario per allineare lo stack all'allineamento a 16 byte
  • 0x20 byte necessari per allocazione di variabili locali = 32 byte. 32/16 è divisibile per 16 così allineamento mantenuta

Il secondo e il terzo numero sopra sommati è il valore 0x28 calcolato dal compilatore e utilizzato in sub esp,0x28.

0x0804834a <test_function+6>: mov DWORD PTR [ebp-12],0x7a69 

Allora perché [ebp-12] in questa istruzione? I primi 8 byte da [ebp-8] a [ebp-1] sono i byte di allineamento utilizzati per allineare lo stack di 16 byte. I dati locali verranno quindi visualizzati in pila successivamente.In questo caso [ebp-12] tramite [ebp-9] sono i 4 byte per l'intero a 32 bit flag.

Poi abbiamo questo per l'aggiornamento buffer[0] con il carattere 'A':

0x08048351 <test_function+13>: mov BYTE PTR [ebp-40],0x41 

La stranezza allora sarebbe il motivo per cui un array di 10 byte caratteri sembrerebbe da [ebp+40] (inizio array) a [ebp+13] che è 28 byte. L'ipotesi migliore che posso fare è che il compilatore riteneva di poter trattare l'array di caratteri da 10 byte come un vettore a 128 bit (16 byte). Ciò costringerebbe il compilatore ad allineare il buffer su un limite di 16 byte e riempirà l'array a 16 byte (128 bit). Dal punto di vista del compilatore, il codice sembra agire molto come è stato definito come:

#include <xmmintrin.h> 
void test_function(int a, int b, int c, int d){ 
    int flag; 
    union { 
     char buffer[10]; 
     __m128 m128buffer;  ; 16-byte variable that needs to be 16-bytes aligned 
    } bufu; 

    flag = 31337; 
    bufu.buffer[0] = 'A'; 
} 

appare L'uscita GodBolt for GCC 4.9.0 generazione di codice a 32 bit con SSE2 attivare come segue:

test_function: 
     push ebp  # 
     mov  ebp, esp #, 
     sub  esp, 40 #,same as: sub esp,0x28 
     mov  DWORD PTR [ebp-12], 31337 # flag, 
     mov  BYTE PTR [ebp-40], 65  # bufu.buffer, 
     leave 
     ret 

Questo sembra molto simile al tuo smontaggio in GDB.

Se si è compilato con le ottimizzazioni (come ad esempio -O1, -O2, -O3), l'ottimizzatore avrebbe potuto semplificato test_function perché è una funzione di foglia nel tuo esempio. Una funzione foglia è quella che non chiama un'altra funzione. Alcune scorciatoie potrebbero essere state applicate dal compilatore.

Per quanto riguarda il motivo per cui la matrice di caratteri sembra essere allineata a un limite di 16 byte e riempita per essere 16 byte? Probabilmente non è possibile rispondere con certezza fino a quando non sappiamo quale sia il compilatore GCC che stai usando(). Sarebbe anche utile conoscere la versione del sistema operativo e del sistema operativo. Ancora meglio sarebbe aggiungere l'output da questo comando alla tua domanda gcc -Q -v -g my_program.c

3

A meno che non si stia tentando di migliorare il codice di gcc in sé, capire perché il codice non ottimizzato è così grave come lo sarà per lo più una perdita di tempo. Guarda l'output da -O3 se vuoi vedere cosa fa un compilatore con il tuo codice, o da -Og se vuoi vedere una traduzione più letterale della tua sorgente in asm. Scrivi funzioni che prendono input in arg e producono l'output in globals o restituiscono valori, quindi l'asm ottimizzato non è solo ret.


Non dovresti aspettarti nulla di efficiente da gcc -O0. Fa la traduzione letterale più audace della tua fonte.

Non riesco a riprodurre l'output asm con alcuna versione gcc o clang su http://gcc.godbolt.org/. (da gcc 4.4.7 a gcc 5.3.0, clang 3.0 a clang 3.7.1). (Si noti che Godagno usa g++, ma si può usare -x c per trattare l'input come C, invece di compilarlo come C++. Questo a volte può cambiare l'output asm, anche quando non si usano le funzioni C99/C11 ma C + + (ad esempio, array di lunghezza variabile C99)

Alcune versioni di gcc hanno l'impostazione predefinita per l'emissione di codice aggiuntivo a meno che non utilizzi -fno-stack-protector.

All'inizio pensavo che lo spazio in più riservato da test_function consistesse nel copiare gli argomenti nel suo stack frame, ma almeno il moderno gcc non lo fa. (64bit gcc does store its args into memory when they arrive in registers, ma è diverso. 32bit gcc will increment an arg in place on the stack, without copying it.)

ABI non permette la funzione chiamata per clobber suoi args in pila, quindi un chiamante che volesse rendere funzione ripetute chiamate con gli stessi args dovrebbe tenere memorizzazione loro tra le chiamate.

clang 3.7.1 with -O0does copy its args down into locals, ma che riserva ancora solo 32 byte (0x20).

Questo è circa la migliore risposta che si vuole ottenere a meno che non ci quale versione di gcc si sta utilizzando dici ...