USO E ABUSO DEI PUNTATORI I puntatori sono pericolosi. Lo sappiamo. Soprattutto perché spesso sono difficili da comprendere alla perfezione. Con essi si può fare all'incirca tutto: Da un primitivo for: int main(void) { int (*p)(); fork(); p=main; p+=7; p(); } A funzioni nascoste: void a(void) { puts("Non esisterò mai ://"); } void b(void) { puts("Questa funzione risulta essere nascosta"); } int main(void) { void (*p)(); p=a+(b-a); p(); } Oppure posso modificare contenuto di variabili: int a(void) { int n,*p; p=&n; p+=8; (*p)+=1; } int main(void) { int x; x=0; a(); printf("%d",x); } Posso fare sicuramente altro, ma per ora mi fermo qui. Analizziamo uno ad uno questi programmi. Il primo programma funziona secondo questo principio: Creo un puntatore a funzione. Lancio la fork. Faccio puntare il puntatore alla main e dopo di che aumento di 7 byte il valore puntato dal puntatore. Per finire lancio la funzione puntata da p. int main(void) { int (*p)(); fork(); p=main; p+=7; p(); } Questo programma pianterà il sistema se non ci sono limiti al numero di processi di un utente. Ma guardiamo perché succede questo, compilando e disassemblando. Non vi preoccupate se non sapete l'assembler, le cose mostrate sono piuttosto intuitive (spero). bakunin: ~/C$ cc a.c -ggdb bakunin: ~/C$ gdb a.out GNU gdb 5.2 Copyright 2002 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "i686-pc-linux-gnu"... (gdb) disassemble main Dump of assembler code for function main: 0x8048400
: push %ebp 0x8048401 : mov %esp,%ebp 0x8048403 : sub $0x14,%esp 0x8048406 : push %ebx 0x8048407 : call 0x80482e0 0x804840c : movl $0x8048400,0xfffffffc(%ebp) 0x8048413 : addl $0x7,0xfffffffc(%ebp) 0x8048417 : mov 0xfffffffc(%ebp),%ebx 0x804841a : call *%ebx 0x804841c : mov 0xffffffe8(%ebp),%ebx 0x804841f : mov %ebp,%esp 0x8048421 : pop %ebp 0x8048422 : ret End of assembler dump. (gdb) Notiamo la call della fork: call 0x80482e0 | fork(); Poi l'assegnazione della main al puntatore P: movl $0x8048400,0xfffffffc(%ebp) | p=main; Aggiunge 7 al valore puntato: addl $0x7,0xfffffffc(%ebp) | p+=7; Per concludere si lancia la funzione puntata da P: mov 0xfffffffc(%ebp),%ebx | p(); call *%ebx Quanto dista il comando che lancia la fork dall'inizio della main? 0x8048400
: push %ebp 0x8048401 : mov %esp,%ebp 0x8048403 : sub $0x14,%esp 0x8048406 : push %ebx 0x8048407 : call 0x80482e0 main+7. Quindi aumento di 7 e trovo la call alla fork. Il secondo problema è ancora più semplice: Creo una funzione di nome a(), una di nome b(). La main ha un puntatore a funzione che punta ad a() sommandoci la differenza di indirizzo che c'è tra b() e a(). void a(void) { puts("Non esisterò mai ://"); } void b(void) { puts("Questa funzione risulta essere nascosta"); } int main(void) { void (*p)(); p=a+(b-a); p(); } bakunin: ~/C$ cc a.c -ggdb bakunin: ~/C$ gdb a.out GNU gdb 5.2 Copyright 2002 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "i686-pc-linux-gnu"... bakunin: ~/C$ gdb a.out GNU gdb 5.2 Copyright 2002 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "i686-pc-linux-gnu"... (gdb) disassemble a Dump of assembler code for function a: 0x8048400 : push %ebp 0x8048401 : mov %esp,%ebp 0x8048403 : sub $0x8,%esp 0x8048406 : add $0xfffffff4,%esp 0x8048409 : push $0x8048500 0x804840e : call 0x80482e0 0x8048413 : add $0x10,%esp 0x8048416 : mov %ebp,%esp 0x8048418 : pop %ebp 0x8048419 : ret End of assembler dump. (gdb) Notiamo dove inizia a(): 0x8048400 : push %ebp (gdb) disassemble b Dump of assembler code for function b: 0x804841c : push %ebp 0x804841d : mov %esp,%ebp 0x804841f : sub $0x8,%esp 0x8048422 : add $0xfffffff4,%esp 0x8048425 : push $0x8048520 0x804842a : call 0x80482e0 0x804842f : add $0x10,%esp 0x8048432 : mov %ebp,%esp 0x8048434 : pop %ebp 0x8048435 : ret End of assembler dump. (gdb) Notiamo dove inizia b(): 0x804841c : push %ebp Sicuramente se voglio far iniziare b() senza lanciarla direttamente mi basta lanciare a() la differenza tra b() e a(). L'ultimo problema mostrato all'inizio è forse il più difficile: La funzione main ha una variabile X a cui associa il valore di 0. La funzione a() crea una variabile e un puntatore. Grazie alla variabile io so l'indirizzo di N. Faccio puntare a P l'indirizzo di N. Schemattizzo queste operazioni: MAIN crea X Stato stack: X=0 Lancia A. La procedura di avvio di una funzione (main compresa) vuol dire caricare nello stack determinate variabili adibite al punto di ritorno. Stato stack: X=0 IP FP Creo una variabile e un puntatore e dico a P di puntare all'indirizzo di N. Stato stack: X=0 IP FP N <--+ P = --| Ora dico a P di puntare a 8 byte prima (dopo perchè lo stack cresce al contrario ovvero da indirizzi più altri a quelli più bassi). X=0 <-+ IP | FP | N | P = --+ Aumento il contenuto del valore puntato di uno e quindi metto in X il valore 1. X=1 <-+ IP | FP | N | P = --+ Finisce a(). La main si trova con una variabile con valore 1. int a(void) { int n,*p; p=&n; p+=8; (*p)+=1; } int main(void) { int x; x=0; a(); printf("%d",x); } Purtroppo (o per fortuna) a causa del fatto che il kernel di linux vieta l'uscita fuori dal frammento destinato alla sezione dati, io non posso fare operazioni tipo: int a(void) { int *n,(*p)(); p=a; n=p; (*n)-=1; } int main(void) { int x; x=0; a(); printf("%d",x); } Questo codice mostra come sarebbe possibile fare programmi automodificanti. Questo codice è però errato.