|
|||||||
|
|
|
![]() |
|
|
Strumenti |
|
|
#1 |
|
Senior Member
Iscritto dal: Nov 2005
Città: Texas
Messaggi: 1722
|
[C] mandare in crash l'applicazione
Ciao a tutti,
ci risiamo: il cliente, nella sua applicazione che mi ha chiesto di analizzare (non capisce perche' non funziona), fra le altre cose ha scritto: Codice:
char *p; p = "una stringa qualsiasi" Analogamente, ha scritto una funzione di questo genere: Codice:
char *funzione ()
{
switch(qualcosa)
{
case 1: return "una stringa";
case 2: return "altra stringa";
...
}
}
Ho parlato di questo, spiegando che questo codice e' errato Il cliente mi ha risposto che lo usa da anni e che, se non produco CONTROESEMPI che mostrino che questo codice e' errato, non cambiara' una virgola. L'agilita' mentale del mio cliente, dunque, mi impone di cercare un esempio di utilizzo di questo codice che vada sempre in crash. (in teoria dovrebbe essere lui a mostrare la correttezza dell'applicazione e non io a fare il contrario). E' praticamente una sfida titanica: nel 90% dei casi (direi nel 99.999% nei casi di piccole applicazioni) questo codice non dara' problemi, anche se errato. Qualcuno ha idee/suggerimenti/feeling? L'ambiente e' Windows, il compilatore Visual Studio. Ringrazio anticipatamente chiunque per qualsiasi considerazione di qualsiasi tipo High Flying Sottovento
__________________
In God we trust; all others bring data |
|
|
|
|
|
#2 |
|
Bannato
Iscritto dal: Feb 2005
Città: Roma
Messaggi: 7029
|
scusa ma perché è errato...?
|
|
|
|
|
|
#3 |
|
Bannato
Iscritto dal: Feb 2005
Città: Roma
Messaggi: 7029
|
ipotizzando che sia errato a causa del cast implicito (da const char* a char*), prova a mostrargli questo:
Codice:
char *asd() {
return "lol";
}
.
.
.
asd()[0] = 0;
ma se ricordo bene in realtà crasherà |
|
|
|
|
|
#4 | |
|
Senior Member
Iscritto dal: Nov 2005
Città: Texas
Messaggi: 1722
|
Quote:
Il motivo per cui il codice errato e' che il C (quello ufficiale) dice che queste costanti non hanno scope. Subito dopo il loro utilizzo, la memoria da loro occupata potrebbe essere utilizzata per qualsiasi altra cosa. Il problema e' che praticamente non succede mai. Mi spiego: char *p = "una stringa"; Questo e' legale: il compilatore riconosce che non e' un assegnamento bensi' una inizializzazione, quindi allochera' automaticamente la memoria necessaria. char *p; p = "una stringa"; In questo caso, p punta ad una locazione temporanea di memoria, la cui validita' non e' specificata da nessuna parte. Su altri sistemi operativi con cui mi e' toccato lavorare (non proprio comuni), il compilatore assegnava davvero una locazione temporanea che poi rilasciava immediatamente, con il "sano" risultato di un crash ripetitivo dell'applicazione. L'applicazione attuale, invece, sembra andare in crash raramente, per cui e' difficile stabilire il motivo. Ritengo che uno dei vari motivi sia questo pezzo di codice. Ho passato il codice come il riso, controllando allocazioni/deallocazioni e validita' degli oggetti e puntatori, questo mi sembra il pezzo piu' debole. Naturalmente, con dei crash cosi' rari (l'applicazione gira 24H), e' difficile fare analisi dettagliate, anche con un debugger. Grazie per il suggerimento High Flying Sottovento
__________________
In God we trust; all others bring data |
|
|
|
|
|
|
#5 |
|
Senior Member
Iscritto dal: Nov 2005
Città: Texas
Messaggi: 1722
|
Dimenticavo: grazie anche per il link sul trusted computing
High Flying Sottovento
__________________
In God we trust; all others bring data |
|
|
|
|
|
#6 | ||
|
Member
Iscritto dal: Oct 1999
Messaggi: 111
|
Quote:
Codice:
int * somma(int a, int b){
int c=a+b;
return &c;
}
pensavo che il problema che hai posto potesse essere ricollegato ad una cosa simile quindi ho mandato in esecuzione il seguente frammento di codice per vedere cosa accadeva Codice:
char *p;
i=0;
while (i++<100){
p=funzione(i%3);
printf("%s, %i\n", p,p);
}
sono andato a vedere nella documentazione del visual c++, in relazione alle stringhe letterali ed ho trovato "Microsoft Specific In some cases, identical string literals can be "pooled" to save space in the executable file. In string-literal pooling, the compiler causes all references to a particular string literal to point to the same location in memory, instead of having each reference point to a separate instance of the string literal. /GF enables string pooling. END Microsoft Specific" se non interpreto male, in pratica è come se il compilatore allocasse effettivamente della memoria per le stringhe letterali e dal mio test sembra anche indipendente dallo scope. per provare ho modificato leggermente il codice Codice:
i=0;
while (i++<100){
p=funzione(i%3);
printf("%s, %i\n", p,p);
}
q="altra stringa";
printf("%s, %i\n", q,q);
p è definito nella funzione, q all'esterno... ma hanno lo stesso indirizzo per come la vedo io, in pratica questo mette al riparo il cliente da errori di memoria non allocata perchè il compilatore crea un guscio di protezione Quote:
è ragionevole pensare che quel valore resti invariato per l'intera esecuzione del programma. cosa ne pensi? |
||
|
|
|
|
|
#7 | |
|
Senior Member
Iscritto dal: Dec 2000
Città: bologna
Messaggi: 1309
|
Quote:
ps.è quando vedo questi post, che mi accorgo di quanto sia comodo java
|
|
|
|
|
|
|
#8 | |
|
Senior Member
Iscritto dal: Nov 2005
Città: TO
Messaggi: 5206
|
Quote:
Prendiamo per esempio: Codice:
char *p1 = "hello"; char *p2; p2 = "hello"; Poi dove le stringhe costanti risiedono in memoria ... dipende dal compilatore e sopratutto dal S.O. Nei sistemi a 32 bit dove ci sono tutti i sistemi di protezione, se le stringhe sono allocate in un'area read-only ... non c'è santo che tenga, non puoi alterarne il contenuto. Sui vecchi sistemi MS-DOS, dove non esisteva alcun sistema di protezione, se facevi char *p="ciao"; e poi p[0]='C'; non ottenevi alcun errore di protezione e il primo carattere veniva cambiato senza problemi.
__________________
Andrea, SCJP 5 (91%) - SCWCD 5 (94%) |
|
|
|
|
|
|
#9 | |
|
Bannato
Iscritto dal: Feb 2005
Città: Roma
Messaggi: 7029
|
Quote:
|
|
|
|
|
|
|
#10 | |
|
Bannato
Iscritto dal: Feb 2005
Città: Roma
Messaggi: 7029
|
Quote:
PS: ma perché non fate il debug di quel programma quando crasha? Ultima modifica di 71104 : 11-06-2006 alle 13:11. |
|
|
|
|
|
|
#11 | |||
|
Senior Member
Iscritto dal: Nov 2005
Città: TO
Messaggi: 5206
|
Quote:
Quote:
Quote:
Codice che causa l'errore di protezione: Codice:
#define STRICT
#include <stdio.h>
#include <windows.h>
int main (void)
{
char *str = "hello";
printf ("Prima: %s\n", str);
str[0] = 'H';
printf ("Dopo: %s\n", str);
return 0;
}
Codice:
#define STRICT
#include <stdio.h>
#include <windows.h>
int main (void)
{
BOOL bRet;
DWORD dwOldProtect;
char *str = "hello";
printf ("Prima: %s\n", str);
bRet = VirtualProtect (str, lstrlen (str)+1, PAGE_READWRITE, &dwOldProtect);
str[0] = 'H';
printf ("Dopo: %s\n", str);
return 0;
}
__________________
Andrea, SCJP 5 (91%) - SCWCD 5 (94%) |
|||
|
|
|
|
|
#12 |
|
Senior Member
Iscritto dal: Nov 2005
Città: Texas
Messaggi: 1722
|
Beh, a dirla tutta, ho fatto qualche prova:
char *p = "ciao"; Questa e' una inizializzazione (cosi' la chiamano K&R) ed anche su VS l'istruzione p[0] = 'C' e' valida, senza alcuna modifica. Non sono esperto di questi sistemi, ho fatto un piccolo progetto (mantenendo tutte le opzioni di default) ed ha girato senza alcun problema. In effetti, non vedo alcuna violazione dello standard C in questo. Ho fatto anche l'altra prova: char *p; p = "ciao"; il tentativo di modificare con p[0] = 'C' porta al bollone rosso del crash (la memoria non poteva essere written). Come avete detto (grazie soprattutto ad andbin e crick_pitomba) sembra che davvero Visual Studio produca un codice NON STANDARD (e' bene precisarlo, visto che abbiamo anche la pretesa di far girare il codice su altre piattaforme) in modo da allocare queste stringhe nella heap. Questo, teoricamente, per un motivo di ottimizzazione del codice. Per risparmiare spazio, insomma, ed e' per questo motivo che non si puo' cambiare detta stringa (visto che l'istruzione non e' legale nello standard C, la definisco come voglio. Non fa una piega). A questo punto, confidente delle varie analisi che ho fatto in questi giorni (vale a dire, il resto del software sembra a posto), mi viene una domanda: Cosa succede quando la macchina e' sotto stress? In altri termini: ho un grosso carico di lavoro, memoria tutta occupata e le cpu che lavorano come matte. Che fine fa quest'area? E poi, visto che non e' standard, quanto persiste? Tutta la durata dell'applicazione (beh, bella ottimizzazione! Fa proprio ridere!), la durata di un modulo? (cos'e' un modulo)? La vita di un oggetto? Se questa stringa e' usata in un oggetto richiamato periodicamente, come si comporta? Quanto dura? Come dicevo, il crash avviene ogni 2 o 3 settimane e sembra sia imputabile a questo codice o qualcosa che avviene nello stesso task. Grazie a tutti per il preziosissimo aiuto!!! High Flying Sottovento
__________________
In God we trust; all others bring data |
|
|
|
|
|
#13 |
|
Bannato
Iscritto dal: Feb 2003
Messaggi: 947
|
Si tratta di un comportamento del compilatore oltremodo "subdolo", dovuto ad un eccessiva ottimizzazione del medesimo. Alcuni compilatori assumono che una costante stringa, indipendentemente dal punto del codice dove si trova sia valutata come un array di char "globale". Si analizzi la seguente funzione:
Codice:
char * funzione()
{
return "Stringa";
}
Codice:
.... <<-- Codice specifico dipendente dal sistema operativo
push ebx
push ecx
push edx
push esi
push edi
push ebp
mov ebp,esp
sub esp,4h
mov [ebp-4h],00408004 <<-- valore arbitrario
mov eax,[ebp-4h]
mov esp,ebp
pop ebp
pop edi
pop esi
pop edx
pop ecx
pop ebx
ret
Codice:
.... <<-- Codice specifico dipendente dal sistema operativo
push ebx
push ecx
push edx
push esi
push edi
push ebp
mov ebp,esp
sub esp,4h
mov eax,[_const_001]
mov [ebp-4h],eax
mov eax,[ebp-4h]
mov esp,ebp
pop ebp
pop edi
pop esi
pop edx
pop ecx
pop ebx
ret
Dove puo' scaturire allora l'errore? L'errore nasce da un'ulteriore ottimizzazione di alcuni "balordi" compilatori, che applicano il seguente principio: costanti stringa "uguali" occupano medesime porzioni di memoria. Ad esempio: Codice:
#include <stdio.h>
char * funzione(void);
void main(void)
{
char *p="Stringa";
char *q;
p[0]='A';
q=funzione();
printf("%s",q);
}
char * funzione()
{
return "Stringa";
}
|
|
|
|
|
|
#14 | |
|
Member
Iscritto dal: Oct 1999
Messaggi: 111
|
Quote:
sulla mia versione, con un progetto applicazione console win32 con opzioni di default, sia Codice:
char *q;
p[0]='A';
q=funzione();
printf("%s",q);
Codice:
char *p = "ciao"; p[0] = 'C'; ho ripreso in mano il mio vecchio manuale di c e ho scoperto l'arcano: evidentemente troppo java mi aveva fatto arruginire il C-ervello :P secondo il manuale (kelley/pohl),dichiarare Codice:
char *p="prova"; se voglio un array modificabile di caratteri devo usare la sintassi Codice:
char p[]="prova"; Codice:
char p[]; p="prova"; Alla fine della storia il comportamento del compilatore risulta essere perfettamente coerente con la teoria... Tornando al problema originale che avevi posto. per quanto detto sopra sia Codice:
char *p= "pippo"; Codice:
char *p; p="pippo"; Codice:
int i; i=10; i=20; Potrei avere bisogno di questo codice: Codice:
char *p; //p è un puntatore non inizializzato p="pippo"; //p viene fatto puntare alla stringa "pippo" //... p="paperino"; //p viene fatto puntare alla stringa "paperino" lavorando con la notazione char p[] non ho tutta questa flessibilità. Questo per quanto riguarda le dichiarazioni e definizioni di puntatori a caratteri, diversamente questo tipo di codice: Codice:
char *f(){
return "pippo";
}
void main (){
char* p=f();
}
scusate l'eccessiva lunghezza :P |
|
|
|
|
|
|
#15 | |
|
Bannato
Iscritto dal: Feb 2005
Città: Roma
Messaggi: 7029
|
Quote:
via non fare il modesto, hai dimostrato spesso una esperienza quantomeno non comune |
|
|
|
|
|
|
#16 | ||||
|
Bannato
Iscritto dal: Feb 2005
Città: Roma
Messaggi: 7029
|
Quote:
Quote:
(la rilocazione insomma Quote:
Quote:
Codice:
p[0]='A'; |
||||
|
|
|
|
|
#17 | |
|
Senior Member
Iscritto dal: Nov 2005
Città: TO
Messaggi: 5206
|
Quote:
Comunque volevo precisare una cosa sul codice che ho postato (il primo, quello senza VirtualProtect). L'ho compilato in modalità "Debug" e mi ha dato appunto l'errore di protezione. Ho provato solo ora a compilarlo in modalità "Release". In effetti questo codice:Codice:
char *str = "hello"; str[0] = 'H'; Adesso, perché questo .... non lo so di preciso. Ho provato a guardare le differenze tra Debug e Release nelle proprietà del progetto per capire quale (se c'era) opzione che potesse influire su questo diverso comportamento ma non ho trovato nulla. Il gcc su Linux invece è più coerente ... l'assegnazione crasha in qualunque condizione.
__________________
Andrea, SCJP 5 (91%) - SCWCD 5 (94%) |
|
|
|
|
|
|
#18 | |
|
Bannato
Iscritto dal: Feb 2005
Città: Roma
Messaggi: 7029
|
Quote:
(hmm, no, non sembra che io abbia azzeccato visto che probabilmente le due righe di codice riportate erano consecutive, però io provo non si sa mai in caso avessi toppato: hai usato variabili globali in quel programma? Ultima modifica di 71104 : 12-06-2006 alle 21:31. |
|
|
|
|
|
|
#19 | |
|
Senior Member
Iscritto dal: Nov 2005
Città: TO
Messaggi: 5206
|
Quote:
Comunque guarda ... io non starei a cercare l'ago nel pagliaio ... il VC++ sappiamo tutti com'è fatto ... L'altro giorno ho visto che il gcc ha l'opzione -fwritable-strings (che non ho mai usato). Indovinate fa??? Ma poi il man dice anche ... Writing into string constants is a very bad idea; "constants" should be constant. Quindi perché complicarsi la vita cercando di scrivere su stringhe literal??
__________________
Andrea, SCJP 5 (91%) - SCWCD 5 (94%) |
|
|
|
|
|
|
#20 | ||
|
Bannato
Iscritto dal: Feb 2005
Città: Roma
Messaggi: 7029
|
Quote:
ti spiego il ragionamento che ho fatto io circa le variabili globali, e come mai la stringa diventi magicamente da read-only a read-write cambiando le opzioni del compilatore: in modalità Release l'IDE setta automaticamente le opzioni (comunque modificabili a piacere) in modo tale che il programma sia ottimizzato al massimo, anche dal punto di vista della dimensione dell'eseguibile finale nonché della sua immagine caricata in memoria; di conseguenza quando il compilatore vede che hai usato una intera sezione .rdata (la cui dimensione ammonta come minimo a 4 kb) per soli 6 byte ("hello"), si chiede come mai sprecare 4090 bytes (se non sul disco quantomeno in RAM) piuttosto che mettere quella stringa assieme a tante altre variabili globali in una sezione .data che nota bene, non è read-only; in questo modo sfrutti i bytes di frammentazione interna della sezione .data (sempre a meno di non sforare il page bound ovviamente) piuttosto che creare un'altra frammentazione interna di 4090 bytes. l'ipotesi della variabile globale mi era venuta in mente dal fatto che il ragionamento vale solo se effettivamente esiste una sezione .data, cioè se il programma di fatto usa variabili globali; ma ho toppato a indovinare perché anche se non ne hai usate tu, le usi comunque per forza senza saperlo visto che linki le librerie di runtime. che ci vuoi fare, ormai a causa di vari lavori che sto facendo sono abituato a ridefinire nei miei eseguibili il vero entry point e a rimuovere il runtime del compilatore tutte le volte Quote:
|
||
|
|
|
|
| Strumenti | |
|
|
Tutti gli orari sono GMT +1. Ora sono le: 06:14.











In effetti questo codice:








