Il programma si arresta in modo anomalo solo durante la build di rilascio: come eseguire il debug?


95

Ho un problema di tipo "Schroedinger's Cat" qui - il mio programma (in realtà la suite di test per il mio programma, ma comunque un programma) si arresta in modo anomalo, ma solo quando viene creato in modalità di rilascio e solo quando viene avviato dalla riga di comando . Attraverso il debug di caveman (cioè, messaggi di printf () dappertutto), ho determinato il metodo di test in cui il codice si blocca, anche se sfortunatamente il crash effettivo sembra accadere in qualche distruttore, poiché gli ultimi messaggi di traccia che vedo sono in altri distruttori che vengono eseguiti in modo pulito.

Quando tento di eseguire questo programma all'interno di Visual Studio, non si blocca. Lo stesso vale per l'avvio da WinDbg.exe. Il crash si verifica solo quando si avvia dalla riga di comando. Questo sta accadendo in Windows Vista, btw, e sfortunatamente non ho accesso a una macchina XP in questo momento per testare.

Sarebbe davvero bello se potessi fare in modo che Windows stampi una traccia dello stack, o qualcosa di diverso dalla semplice chiusura del programma come se fosse uscito in modo pulito. Qualcuno ha qualche consiglio su come ottenere alcune informazioni più significative qui e, si spera, correggere questo bug?

Modifica: il problema è stato effettivamente causato da un array fuori limite, che descriverò di più in questo post . Grazie a tutti per il vostro aiuto nella ricerca di questo problema!


Puoi fornire un campione di quel metodo di prova?
akalenuk

No mi dispiace, il codice è troppo complesso per incollarlo facilmente qui e, come ho detto, non sta accadendo nel metodo di test stesso, ma piuttosto in un distruttore in seguito. Tuttavia, non ci sono puntatori non inizializzati o qualcosa di simile in questo metodo.
Nik Reiman

3
La maggior parte delle risposte sono poco più che supposizioni. Esistono alcune tecniche comuni per analizzare le build di rilascio in crash senza allegare un debugger: stackoverflow.com/a/18513077/214777?stw=2
Sebastian

Risposte:


127

Nel 100% dei casi che ho visto o di cui ho sentito parlare, in cui un programma C o C ++ funziona correttamente nel debugger ma fallisce quando viene eseguito all'esterno, la causa è stata la scrittura oltre la fine di un array locale di funzioni. (Il debugger mette più in pila, quindi è meno probabile che tu sovrascriva qualcosa di importante.)


31
Qualcuno dia un sigaro a quest'uomo! Nel mio caso, stavo passando uno StringBuilder che non aveva una capacità sufficiente per una funzione P / Invoke. Immagino sia come qualcuno che ti scrive in faccia con un pennarello magico quando dormi: sotto il debugger, finiscono per scarabocchiare sulla tua fronte, quindi non te ne accorgi, ma senza il debugger finiscono per pugnalarti nel occhio ... qualcosa del genere. Grazie per questo suggerimento!
Nicholas Piasecki

1
Nel mio caso si è rivelato essere un problema di allineamento su un processore ARM che utilizza Obj-C.
Almo

1
11 anni dopo e questo suona ancora vero ... non dimenticare di prenotare i tuoi vettori.
David Tran

1
ok, allora come si cambia il comportamento della modalità debug in modo da poter effettivamente eseguire il debug.
Paul Childs

1
"Ora sapendo dove cercare" ma come funziona tutto il lavoro in debug ti dice dove si trova il problema. Anche se penso che la tua risposta sia corretta nella maggior parte dei casi, e sapere cosa cercare è un buon inizio, esplorare una grande base di codice per individuare esattamente dove si trova il problema può essere proibitivo.
Paul Childs

54

Quando ho riscontrato problemi come questo prima, generalmente è stato dovuto all'inizializzazione delle variabili. In modalità debug, variabili e puntatori vengono inizializzati automaticamente a zero ma in modalità di rilascio non lo fanno. Pertanto, se hai un codice come questo

int* p;
....
if (p == 0) { // do stuff }

In modalità debug il codice in if non viene eseguito, ma in modalità release p contiene un valore indefinito, che è improbabile che sia 0, quindi il codice viene eseguito spesso causando un crash.

Vorrei controllare il tuo codice per le variabili non inizializzate. Questo può essere applicato anche al contenuto degli array.


I casi tipici stanno dimenticando di mettere una variabile membro in (uno di) l'elenco di inizializzazione dei membri dei costruttori. Ha lo stesso effetto ma è più difficile da trovare se non sai che dovresti cercare anche la corretta inizializzazione dei membri.
steffenj

1
In modalità debug, le variabili vengono solitamente inizializzate su una "costante definita dal compilatore" che può essere utilizzata nel debug per indicare in quale stato si trova la variabile. Ad esempio: i puntatori NULL o 0xDeadBeef sono popolari.
Martin York,

I runtime di debug tipicamente inizializzano la memoria su un valore diverso da zero, in modo specifico in modo che i test del puntatore NULL facciano sì che il codice si comporti come se il puntatore non fosse NULL. Altrimenti hai codice che viene eseguito correttamente in modalità di debug che blocca la modalità di rilascio.
Michael Burr,

1
No, le variabili non sono affatto inizializzate ed è ancora UB a "usarle" fino a quando non vengono assegnate. Tuttavia, i contenuti della memoria sottostante sono spesso precompilati con 0x0000000 o 0xDEADBEEF o altri modelli riconoscibili.
Gare di leggerezza in orbita il

26

Nessuna risposta finora ha cercato di fornire una panoramica seria sulle tecniche disponibili per il debug delle applicazioni di rilascio:

  1. Le build di rilascio e di debug si comportano in modo diverso per molti motivi. Ecco un'eccellente panoramica. Ciascuna di queste differenze potrebbe causare un bug nella build di rilascio che non esiste nella build di debug.

  2. La presenza di un debugger può modificare anche il comportamento di un programma , sia per le build di rilascio che per quelle di debug. Vedi questa risposta. In breve, almeno il debugger di Visual Studio utilizza automaticamente l'heap di debug quando è collegato a un programma. È possibile disattivare l'heap di debug utilizzando la variabile di ambiente _NO_DEBUG_HEAP. È possibile specificarlo nelle proprietà del computer o nelle Impostazioni del progetto in Visual Studio. Ciò potrebbe rendere riproducibile il crash con il debugger collegato.

    Maggiori informazioni sul debug della corruzione dell'heap qui.

  3. Se la soluzione precedente non funziona, è necessario rilevare l'eccezione non gestita e allegare un debugger post mortem all'istanza in cui si verifica l'arresto anomalo. Puoi usare ad esempio WinDbg per questo, dettagli sui debugger post-mortem disponibili e la loro installazione su MSDN

  4. È possibile migliorare il codice di gestione delle eccezioni e, se si tratta di un'applicazione di produzione, è necessario:

    un. Installa un gestore di terminazione personalizzato utilizzandostd::set_terminate

    Se si desidera eseguire il debug di questo problema localmente, è possibile eseguire un ciclo infinito all'interno del gestore di terminazione e inviare del testo alla console per notificare che std::terminateè stato chiamato. Quindi collega il debugger e controlla lo stack di chiamate. Oppure stampa la traccia dello stack come descritto in questa risposta.

    In un'applicazione di produzione potresti voler inviare un rapporto di errore a casa, idealmente insieme a un piccolo dump della memoria che ti consente di analizzare il problema come descritto qui.

    b. Utilizza il meccanismo di gestione delle eccezioni strutturato di Microsoft che ti consente di rilevare le eccezioni sia hardware che software. Vedi MSDN . È possibile proteggere parti del codice utilizzando SEH e utilizzare lo stesso approccio di a) per eseguire il debug del problema. SEH fornisce ulteriori informazioni sull'eccezione che si è verificata che è possibile utilizzare quando si invia un rapporto di errore da un'app di produzione.


16

Cose a cui prestare attenzione:

Overrun degli array: il debugger di Visual Studio inserisce un riempimento che potrebbe arrestare i crash.

Condizioni di competizione: sono coinvolti più thread in caso affermativo una condizione di competizione molti si presentano solo quando un'applicazione viene eseguita direttamente.

Collegamento: la build del tuo rilascio inserisce le librerie corrette.

Cose da provare:

Minidump - davvero facile da usare (basta cercarlo in msdn) ti darà un dump completo per ogni thread. Basta caricare l'output in Visual Studio ed è come se si stesse eseguendo il debug al momento del crash.


1
Ciao, ho ricevuto un voto anonimo su questa risposta. Mi piacerebbe capire perché?
morechilli

12

Puoi impostare WinDbg come debugger post-mortem. Questo avvierà il debugger e lo collegherà al processo quando si verifica l'arresto anomalo. Per installare WinDbg per il debug post-mortem, usa l'opzione / I (nota che è in maiuscolo ):

windbg /I

Maggiori dettagli qui .

Quanto alla causa, è molto probabilmente una variabile unitializzata come suggeriscono le altre risposte.


2
E non dimenticare che puoi fare in modo che il compilatore generi file PDB anche per le build di rilascio, sebbene non sia l'impostazione predefinita.
Michael Burr,

L'unica vera risposta alla domanda davvero.
Sebastian

10

Dopo molte ore di debug, ho finalmente trovato la causa del problema, che era effettivamente causato da un buffer overflow, che causava una differenza di un singolo byte:

char *end = static_cast<char*>(attr->data) + attr->dataSize;

Questo è un errore di fencepost (errore off-by-one) ed è stato corretto da:

char *end = static_cast<char*>(attr->data) + attr->dataSize - 1;

La cosa strana è che ho inserito diverse chiamate a _CrtCheckMemory () in varie parti del mio codice, e hanno sempre restituito 1. Sono stato in grado di trovare l'origine del problema inserendo "return false;" chiama nel caso di test, e quindi alla fine determina attraverso tentativi ed errori dove si trovava l'errore.

Grazie a tutti per i vostri commenti: oggi ho imparato molto su windbg.exe! :)


8
Oggi ho eseguito il debug di un problema simile e _CrtCheckMemory () restituiva sempre 1. Ma poi ho capito perché: in modalità Release, _CrtCheckMemory è # definito come ((int) 1).
Brian Morearty,

7

Anche se hai creato il tuo exe come release, puoi comunque generare file PDB (Database di programma) che ti permetteranno di impilare la traccia ed eseguire una quantità limitata di ispezione delle variabili. Nelle impostazioni di build c'è un'opzione per creare i file PDB. Attivalo e ricollega. Quindi prova a eseguire prima dall'IDE per vedere se si verifica un arresto anomalo. Se è così, allora fantastico: sei pronto per guardare le cose. In caso contrario, quando esegui dalla riga di comando puoi fare una delle due cose:

  1. Esegui EXE e prima dell'arresto anomalo fai un collegamento al processo (menu Strumenti in Visual Studio).
  2. Dopo l'arresto, seleziona l'opzione per avviare il debugger.

Quando ti viene chiesto di puntare ai file PDB, sfoglia per trovarli. Se i PDB sono stati messi nella stessa cartella di output del tuo EXE o DLL, probabilmente verranno raccolti automaticamente.

I PDB forniscono un collegamento alla fonte con sufficienti informazioni sui simboli per rendere possibile vedere le tracce dello stack, le variabili ecc. Puoi ispezionare i valori normalmente, ma tieni presente che puoi ottenere letture false poiché il passaggio di ottimizzazione può significare solo cose compaiono nei registri o le cose accadono in un ordine diverso da quello che ti aspetti.

NB: sto assumendo un ambiente Windows / Visual Studio qui.


3

Arresti anomali come questo sono quasi sempre causati perché un IDE di solito imposta il contenuto di una variabile non inizializzata su zero, null o qualche altro valore "sensibile", mentre quando si esegue in modo nativo otterrai qualsiasi spazzatura casuale che il sistema raccoglie.

Il tuo errore è quindi quasi certamente che stai usando qualcosa come se stessi usando un puntatore prima che sia stato inizializzato correttamente e lo stai facendo franca nell'IDE perché non punta in nessun punto pericoloso - o il valore è gestito dal tuo controllo degli errori - ma in modalità di rilascio fa qualcosa di brutto.


3

Per avere un crash dump che puoi analizzare:

  1. Genera file pdb per il tuo codice.
  2. Rebase per avere il tuo exe e dll caricati nello stesso indirizzo.
  3. Abilita il debugger post mortem come Dr. Watson
  4. Controlla l'indirizzo degli errori di arresto anomalo utilizzando uno strumento come la ricerca di arresti anomali .

Dovresti anche controllare gli strumenti in Strumenti di debug per Windows . Puoi monitorare l'applicazione e vedere tutte le eccezioni di prima possibilità che erano prima della tua eccezione di seconda possibilità.

Spero che sia d'aiuto...


3

Un ottimo modo per eseguire il debug di un errore come questo è abilitare le ottimizzazioni per la build di debug.


2

Una volta ho avuto un problema quando l'app si comportava in modo simile alla tua. Si è rivelato essere un brutto sovraccarico del buffer in sprintf. Naturalmente, ha funzionato se eseguito con un debugger collegato. Quello che ho fatto è stato installare un filtro di eccezione non gestito ( SetUnhandledExceptionFilter ) in cui ho semplicemente bloccato all'infinito (usando WaitForSingleObject su un handle fasullo con un valore di timeout di INFINITE).

Quindi potresti qualcosa sulla falsariga di:

lungo __stdcall MyFilter (EXCEPTION_POINTERS *)
{
    HANDLE hEvt = :: CreateEventW (0,1,0,0);
    if (hEvt)
    {
        if (WAIT_FAILED == :: WaitForSingleObject (hEvt, INFINITE))
        {
            // errore di registro
        }
    }

}
// da qualche parte nel tuo wmain / WinMain:
SetUnhandledExceptionFilter (MyFilter);

Ho quindi collegato il debugger dopo che il bug si era manifestato (il programma gui ha smesso di rispondere).

Quindi puoi eseguire un dump e lavorarci più tardi:

.dump / ma path_to_dump_file

Oppure esegui subito il debug. Il modo più semplice è tenere traccia del punto in cui il contesto del processore è stato salvato dal meccanismo di gestione delle eccezioni di runtime:

gamma sd esp 1003f

Il comando cercherà nello spazio degli indirizzi dello stack i record CONTEXT a condizione della lunghezza della ricerca. Di solito uso qualcosa come "l? 10000" . Nota, non utilizzare numeri insolitamente grandi come record che stai cercando di solito vicino al frame del filtro delle eccezioni non gestito. 1003f è la combinazione di flag (credo corrisponda a CONTEXT_FULL) utilizzata per catturare lo stato del processore. La tua ricerca sarebbe simile a questa:

0: 000> sd esp l1000 1003f
0012c160 0001003f 00000000 00000000 00000000? ...............

Una volta recuperati i risultati, utilizza l'indirizzo nel comando cxr:

.cxr 0012c160

Questo ti porterà a questo nuovo CONTESTO, esattamente al momento del crash (otterrai esattamente la traccia dello stack nel momento in cui la tua app si è bloccata). Inoltre, usa:

.exr -1

per scoprire esattamente quale eccezione si era verificata.

Spero che sia d'aiuto.


2

A volte questo accade perché hai inserito operazioni importanti nella macro "assert". Come forse saprai, "assert" valuta le espressioni solo in modalità debug.


1

Per quanto riguarda i tuoi problemi nell'ottenere informazioni diagnostiche, hai provato a utilizzare adplus.vbs come alternativa a WinDbg.exe? Per collegarti a un processo in esecuzione, usa

adplus.vbs -crash -p <process_id>

Oppure per avviare l'applicazione nel caso in cui il crash si verifichi rapidamente:

adplus.vbs -crash -sc your_app.exe

Informazioni complete su adplus.vbs sono disponibili all'indirizzo: http://support.microsoft.com/kb/286350


1

Ntdll.dll con debugger allegato

Una piccola differenza tra l'avvio di un programma dall'IDE o WinDbg rispetto all'avvio dalla riga di comando / desktop è che quando si avvia con un debugger collegato (cioè IDE o WinDbg) ntdll.dll utilizza un'implementazione heap diversa che esegue una piccola convalida sull'allocazione / liberazione della memoria.

Si può leggere alcune informazioni rilevanti in punto di interruzione utente imprevisto in ntdll.dll . Uno strumento che potrebbe aiutarti a identificare il problema è PageHeap.exe .

Analisi degli incidenti

Non hai scritto qual è il "crash" che stai vivendo. Una volta che il programma si arresta in modo anomalo e ti offre di inviare le informazioni sull'errore a Microsoft, dovresti essere in grado di fare clic sulle informazioni tecniche e di controllare almeno il codice di eccezione, e con un po 'di sforzo puoi persino eseguire un'analisi post mortem (vedi Heisenbug : Il programma WinApi si arresta in modo anomalo su alcuni computer) per istruzioni)


1

Vista SP1 ha effettivamente un simpatico generatore di crash dump integrato nel sistema. Sfortunatamente, non è attivato per impostazione predefinita!

Vedi questo articolo: http://msdn.microsoft.com/en-us/library/bb787181(VS.85).aspx

Il vantaggio di questo approccio è che non è necessario installare alcun software aggiuntivo sul sistema interessato. Afferralo e strappalo, piccola!


1

Secondo la mia esperienza, si tratta principalmente di problemi di corruzione della memoria.

Per esempio :

char a[8];
memset(&a[0], 0, 16);

: /*use array a doing some thing */

è molto possibile essere normali in modalità debug quando si esegue il codice.

Ma nel rilascio, quello sarebbe / potrebbe essere un crash.

Per me, rovistare dove la memoria è fuori limite è troppo faticoso.

Utilizzare alcuni strumenti come Visual Leak Detector (Windows) o Valgrind (Linux) sono la scelta più saggia.


1

Ho visto molte risposte giuste. Tuttavia, non c'è nessuno che mi abbia aiutato. Nel mio caso, si è verificato un utilizzo errato delle istruzioni SSE con la memoria non allineata . Dai un'occhiata alla tua libreria matematica (se ne usi una) e prova a disabilitare il supporto SIMD, ricompila e riproduci il crash.

Esempio:

Un progetto include mathfu e utilizza le classi con STL vector: std :: vector <mathfu :: vec2> . Tale utilizzo probabilmente causerà un arresto anomalo al momento della costruzione dell'elemento mathfu :: vec2 poiché l'allocatore predefinito STL non garantisce l'allineamento a 16 byte richiesto. In questo caso, per provare l'idea, si può definire #define MATHFU_COMPILE_WITHOUT_SIMD_SUPPORT 1prima di ogni inclusione del mathfu , ricompilare nella configurazione di Release e ricontrollare.

Le configurazioni Debug e RelWithDebInfo hanno funzionato bene per il mio progetto, ma non per quello di rilascio . La ragione di questo comportamento è probabilmente perché il debugger elabora le richieste di allocazione / deallocazione e fa un po 'di contabilità della memoria per controllare e verificare gli accessi alla memoria.

Ho sperimentato la situazione negli ambienti Visual Studio 2015 e 2017.


0

Qualcosa di simile mi è successo una volta con GCC. Si è rivelata un'ottimizzazione troppo aggressiva che è stata abilitata solo durante la creazione della versione finale e non durante il processo di sviluppo.

Bene, a dire il vero è stata colpa mia, non di gcc, poiché non ho notato che il mio codice si basava sul fatto che quella particolare ottimizzazione non sarebbe stata fatta.

Mi ci è voluto molto tempo per rintracciarlo e ci sono arrivato solo perché ho chiesto a un newsgroup e qualcuno mi ha fatto riflettere. Quindi, permettimi di ricambiare il favore nel caso in cui questo stia accadendo anche a te.


0

Ho trovato questo articolo utile per il tuo scenario. ISTR le opzioni del compilatore erano un po 'obsolete. Esamina le opzioni del tuo progetto Visual Studio per vedere come generare file pdb per la build di rilascio, ecc.


0

È sospetto che succeda all'esterno del debugger e non all'interno; l'esecuzione nel debugger normalmente non modifica il comportamento dell'applicazione. Vorrei controllare le differenze di ambiente tra la console e l'IDE. Inoltre, ovviamente, compilare la versione senza ottimizzazioni e con le informazioni di debug e vedere se questo influisce sul comportamento. Infine, controlla gli strumenti di debug post-mortem che altre persone hanno suggerito qui, di solito puoi ottenere qualche indizio da loro.


0

Il debug delle build di rilascio può essere un problema a causa delle ottimizzazioni che cambiano l'ordine in cui le righe del codice sembrano essere eseguite. Può davvero creare confusione!

Una tecnica per restringere almeno il problema consiste nell'usare MessageBox () per visualizzare istruzioni rapide che affermano quale parte del programma ha avuto il tuo codice ("Starting Foo ()", "Starting Foo2 ()"); inizia a metterle all'inizio delle funzioni nell'area del tuo codice che sospetti (cosa stavi facendo nel momento in cui si è bloccato?). Quando puoi dire quale funzione, modifica le finestre di messaggio in blocchi di codice o anche singole righe all'interno di quella funzione finché non la restringi a poche righe. Quindi puoi iniziare a stampare il valore delle variabili per vedere in quale stato si trovano al momento del crash.


Ha già provato a spolverare con printfs, quindi le finestre di messaggio non hanno portato nulla di nuovo alla festa.
Greg Whitfield,

0

Prova a utilizzare _CrtCheckMemory () per vedere in quale stato si trova la memoria allocata. Se tutto va bene, _CrtCheckMemory restituisce TRUE , altrimenti FALSE .


0

Potresti eseguire il software con i flag globali abilitati (guarda in Debugging Tools for Windows). Molto spesso aiuterà a risolvere il problema.


0

Fai in modo che il tuo programma generi un mini dump quando si verifica l'eccezione, quindi aprilo in un debugger (ad esempio, in WinDbg). Le funzioni chiave da esaminare: MiniDumpWriteDump, SetUnhandledExceptionFilter


0

Ecco un caso che ho avuto che qualcuno potrebbe trovare istruttivo. Si è bloccato solo durante il rilascio in Qt Creator, non nel debug. Stavo usando file .ini (poiché preferisco le app che possono essere copiate su altre unità, rispetto a quelle che perdono le impostazioni se il registro viene danneggiato). Questo vale per tutte le app che memorizzano le proprie impostazioni nella struttura di directory delle app. Se le build di debug e di rilascio si trovano in directory diverse, puoi anche avere un'impostazione diversa tra loro. Avevo preferito controllare uno che non fosse registrato nell'altro. È risultata essere la fonte del mio incidente. Meno male che l'ho trovato.

Odio dirlo, ma ho diagnosticato il crash solo in MS Visual Studio Community Edition; dopo aver installato VS, aver lasciato la mia app in crash in Qt Creator e aver scelto di aprirla nel debugger di Visual Studio . Sebbene la mia app Qt non avesse informazioni sui simboli, risulta che le librerie Qt ne avevano alcuni. Mi ha portato alla linea incriminata; poiché ho potuto vedere quale metodo veniva chiamato. (Tuttavia, penso che Qt sia un framework LGPL conveniente, potente e multipiattaforma.)


-3

Ho avuto questo errore e vs si è bloccato anche durante il tentativo di! Clean! il mio progetto. Quindi ho cancellato manualmente i file obj dalla directory Release, dopodiché è stato costruito correttamente.


-6

Sono d'accordo con Rolf. Poiché la riproducibilità è così importante, non dovresti avere una modalità non di debug. Tutte le tue build dovrebbero essere debuggabili. Avere due obiettivi per il debug più che raddoppia il carico di debug. Invia la versione "modalità debug", a meno che non sia inutilizzabile. In tal caso, rendilo utilizzabile.


Questo può funzionare per il 10% delle applicazioni ma certamente non per tutte. Vorresti giocare ai giochi rilasciati come build DEBUG? Dare via il codice di sicurezza segreto con marchio registrato in modalità facile da smontare, magari anche insieme ai PDB? Non credo.
steffenj

Steffenj: Voglio che gli sviluppatori di giochi trovino i bug. Idealmente, prima della spedizione, ma se è dopo, voglio che siano in grado di ottenere informazioni sufficienti per riprodurle e rintracciarle. se è un codice segreto, il marchio non si applica. PDB? Banca dati delle proteine? debugger Python?
wnoise

IMHO, questa è una cattiva idea. Gli eseguibili sono più grandi, non sono ottimizzati e funzionano molto più lentamente. Questi casi sono davvero piuttosto rari; anche se particolarmente esasperanti quando accadono. Non dovresti fornire un prodotto costantemente inferiore, preoccupandoti di un debug estremamente raro nel caso peggiore. (Il mio non era uno dei tanti voti negativi.) Ho fatto un po 'di programmazione per la NASA; e abbiamo detto che come minimo, ogni riga di codice dovrebbe essere testata una volta. Anche i test unitari possono aiutare.
CodeLurker
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.