APPROFONDIMENTI E' possibile usare un semplicissimo programma per capire il meccanismo di base del load di un'esegubile da parte del sistema operativo. $ gcc -o main main.c $ objdump -f main main: file format elf64-x86-64 architecture: i386:x86-64, flags 0x00000112: EXEC_P, HAS_SYMS, D_PAGED start address 0x0000000000400400 cosa e' esattamente quello start address puo' essere compreso usando l'objdump. $ objdump --disassemble main ...cut 0000000000400370 <_start>: 400370: 31 ed xor %ebp,%ebp 400372: 49 89 d1 mov %rdx,%r9 400375: 5e pop %rsi 400376: 48 89 e2 mov %rsp,%rdx 400379: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp 40037d: 50 push %rax 40037e: 54 push %rsp 40037f: 49 c7 c0 40 04 40 00 mov $0x400440,%r8 400386: 48 c7 c1 50 04 40 00 mov $0x400450,%rcx 40038d: 48 c7 c7 28 04 40 00 mov $0x400428,%rdi 400394: e8 c7 ff ff ff callq 400360 <__libc_start_main@plt> 400399: f4 hlt 40039a: 90 nop 40039b: 90 nop cut... Questa e' il punto di "aggancio" del loader, "_start" si trova appunto all starting address 0000000000400370. Senza scendere troppo nei dettagli e' interessante notare che tra i vari indirizzi che vengono "chiamati in gioco" da tale funzione c'e' anche quello della funzione main 0x400428: 0000000000400428
: 400428: 55 push %rbp 400429: 48 89 e5 mov %rsp,%rbp 40042c: b8 00 00 00 00 mov $0x0,%eax 400431: c9 leaveq 400432: c3 retq 400433: 90 nop 400434: 90 nop 400435: 90 nop 400436: 90 nop 400437: 90 nop 400438: 90 nop 400439: 90 nop 40043a: 90 nop 40043b: 90 nop 40043c: 90 nop 40043d: 90 nop 40043e: 90 nop 40043f: 90 nop Altra cosa interessate, le due istruzioni:
400375:       5e                      pop    %rsi
400376:       48 89 e2                mov    %rsp,%rdx
servono a prendere i valori di argc ed argv. Senza dettagliare ulteriormente vediamo descrittivamente cosa succede durante l'eseciuzione di un "programma": - La shell chiama ls system call del kernel "execve" passando argc/argv. - Il kernel una volta stabilito il tipo di eseguibile, ad esempio a.out o elf, da luogo ai setup del caso ed all'allocazione delle pagine di memoria necessarie (text/data/bss/stack). Preprara ed inizializza tutte le strutture dati necessarie, prepara tutte le informazioni necessarie all'eseguibile nello stack. - A questo punto e' la "_start" dell'eseguibile che prende il controllo dell'esecuzione. Come prima cosa recupera i valori di argc ed argv. Prepara alcune informazioni necesserie nello stack e chiama la __libc_start_main. - __libc_start_main fa altre inizializzazioni e chiama il nostro main.