Esempi di errori a tempo di esecuzione

Esempio 1:

Supponiamo che la pagina dei dettagli inizi così:

Segmentation fault

Core was generated by `es2'.
Program terminated with signal 11, Segmentation fault.

La prima riga riassume l'errore che si è verificato. In questo caso una "violazione della segmentazione"[1]. Le righe successive sono state generate dal debugger simbolico (gdb, su questo sistema). La seconda riga ci dice che il file 'core'[2] è stato creato da un processo che stava eseguendo un programma di nome 'es2'. Il file 'core' è un file che contiene l'immagine della memoria del processo, fotografata nel momento in cui il processo è stato interrotto. Nella terza riga, il debugger ci conferma che il processo è stato interrotto a causa di una violazione della segmentazione. Se siamo fortunati, la riga seguente ci dirà il numero di riga (nel file sorgente) corrispondente all'istruzione macchina che il processo stava eseguendo quando ha causato l'errore. Se siamo ancora più fortunati, tale riga sarà all'interno di uno dei file scritti da voi, piuttosto che in qualche file della libreria del C++. Supponiamo, ad esempio, di leggere:

#0 avanti () at work/esrun/es2.s:15

In questo caso è andata relativamente bene. Sappiamo infatti che il processo è stato fermato mentre eseguiva l'istruzione alla riga 15 del file es2.s (work/esrun è la directory in cui il vostro file viene assemblato dal sistema). Non fatevi confondere dall'etichetta ('avanti', in questo caso) che trovate scritta prima di 'at'. gdb assume che l'ultima etichetta che avete definito, prima dell'istruzione che ha causato l'errore, sia il nome della funzione all'interno della quale l'istruzione si trova (assunzione che normalmente è vera per i listati assembler prodotti da gcc, ma è generalmente falsa nei vostri).
Di seguito, gdb ci mostra la riga in questione e, subito dopo, anche 10 righe attorno a quella che ci interessa (per aiutarci a capire il punto del programma in cui ci troviamo). Supponiamo che la riga 15 sia:

15    movl (%ebx), %eax

Il primo operando di questa istruzione accede in memoria, quindi è lui la causa del problema. Subito dopo le 10 righe di contesto, gdb ci mostra il contenuto dei registri della ALU e poi di quelli della FPU. Di entrambi mostra il contenuto sia in esadecimale che in decimale. Supponiamo, ad esempio, che la riga corrispondente al registro %ebx sia:

ebx    0x0     0

Come possiamo vedere, %ebx contiene il valore 0, ma all'indirizzo 0 di memoria non si può normalmente accedere, perchè il valore 0 viene tipicamente usato nei linguaggi di alto livello per indicare un puntatore non valido.

Esempio 2 (importante):

Molti studenti lamentano il fatto che il loro programma veniva eseguito correttamente durante la prova pratica, mentre produce un errore di "Segmentation fault" quando eseguito su questo sistema. Il problema è noto e nella valutazione se ne terrà conto. Rassegnatevi comunque al fatto che l'errore è nel vostro programma. La spiegazione del perchè l'errore non veniva rilevato nella prova pratica è la seguente: la prova pratica viene svolta su Windows XP, con il compilatore DJGPP, mentre questo sistema è in esecuzione su FreeBSD[6]. A causa di alcune limitazioni del server DPMI[5] di Windows XP, DJGPP non può proteggere gli accessi agli indirizzi di memoria inferiori a 0x1000 (quindi, nell'esempio precedente, non avrebbe impedito l'accesso alla locazione 0). Inoltre, il codice macchina del vostro programma viene caricato a partire dall'indirizzo 0x1000. Poichè, nei processori Intel, il meccanismo della paginazione prevede che le pagine eseguibili debbano essere per forza anche leggibili, il vostro programma può accedere in lettura ad una zona di memoria che va dall'indirizzo zero fino a 64K o più (in altre parole, gli indirizzi che contengono il suo stesso codice). Questo vuol dire che se, per sbaglio, usate quello che nelle vostre intenzioni era un numero (tipicamente minore di 65536) come un indirizzo di memoria, il processore e, di conseguenza, il sistema operativo, non se ne accorgeranno. In alcuni casi, per pura coincidenza, il vostro programma, eseguito durante la prova pratica, potrebbe persino produrre un'uscita corretta!
Un tipico errore nei vostri programmi, che porta a questa situazione, è il seguente frammento di codice, risultato della traduzione di una istruzione if che coinvolge numeri reali:

fstsw %ax
testw 0x4100, %ax
jz else

Come avrete sicuramente notato, manca il '$' prima della costante 0x4100, che quindi viene interpretata come un indirizzo di memoria a cui accedere per prelevare la maschera con cui testare il contenuto di %ax. Se siete fortunati, al'indirizzo 0x4100 troverete una word con i bit 14 e 8 (i bit che volevate controllare in %ax con la maschera $0x4100) pari ad uno. Il valore degli altri bit della word, prelevata dalla memoria, importa poco, perchè, tipicamente, i bit di %ax diversi dal n. 14 e dal n. 8 sono già pari a zero dopo una fstsw %ax. In altre parole, non solo il vostro programma prosegue come se niente fosse (accede in lettura all'indirizzo 0x4100 che contiene codice e non dati, ma il sistema non lo interrompe), ma sembra anche funzionare correttamente (per caso, esegue il test sui bit giusti di %ax).
Su FreeBSD (come su tutti i sistemi che adottano il formato ELF[7]), gli eseguibili vengono invece caricati a partire dall'indirizzo 0x08048000 (134512640 in decimale) e gli indirizzi precedenti sono inaccessibili (protetti da lettura e scrittura). Ecco perchè il vostro programma causerà un errore di protezione a tempo di esecuzione.
NOTA: questo non vuol dire che FreeBSD è meglio di Windows XP (almeno, non per questo motivo). Infatti, si può verificare benissimo la situazione opposta, se per sbaglio il vostro programma accede ad un indirizzo di memoria di poco superiore a 0x08048000.

Esempio 3:

Una situazione abbastanza frequente, nonostante io cerchi di sconsigliarla durante le esercitazioni, è la seguente:

# siamo all'inizio del secondo esercizio
movl 8(%ebp), %eax

Lo studente, sapendo che alla fine della funzione dovrà caricare 8(%ebp) in %eax, e che, nella traduzione della funzione, avrà bisogno di un registro che contenga l'indirizzo memorizzato in 8(%ebp), si chiede: "Perchè non uso direttamente %eax per memorizzare 8(%ebp)? così non corro il rischio di dimenticare il caricamento a fine funzione e il mio codice è anche più efficiente!". Purtroppo, pur avendo risparmiato 2 neuroni e 2 nanosecondi, ne perderà molti di più, di entrambi, nel tentativo di capire perchè il suo programma non funziona (suggerimento: a metà funzione si troverà a tradurre, in genere meccanicamente, una istruzione if che coinvolge numeri reali...).
Powered by apache Powered by Gentoo Linux Powered by MariaDB Powered by PHP Powered by Perl Site written in vi