0xDEADBEEF sembra un indirizzo di memoria e non lo è. Vale
3735928559 in decimale, occupa quattro byte, si legge in un hex dump
come una costante e non mappa quasi certamente nessuna pagina
accessibile. È un valore sentinella: un numero scelto perché non
dovrebbe comparire mai, in modo che se lo vedi qualcosa non sta
funzionando. In questo è un parente stretto del canary token che
abbiamo descritto in Canary token: la trappola che parla al posto tuo:
non protegge un asset, rende visibile un errore.
Le quattro regole per scegliere un valore così
Prima della storia, la tecnica. Un buon valore "veleno" per il debug deve fallire in modo rumoroso. I requisiti sono quasi aritmetici:
Non deve essere un indirizzo valido. Se i bit alti sono posti, servono milioni di allocazioni prima che l'indirizzo diventi valido in un processo qualsiasi.
Non deve essere allineato. Su x86 l'allineamento è 4 byte, quindi un valore divisibile per 4 sarebbe plausibile come puntatore.
0xDEADBEEFnon lo è.Deve avere tutti i byte diversi. Se usato come stack canary serve a capire quanto è stato sovrascritto: con un byte solo, non distingui la sovrascrittura da un singolo byte errato.
Non deve essere
0x00o0xFF. Sono i valori più probabili dopo un singolo byte scribagliato, e servono a distinguere un valore "non inizializzato" da un valore "azzerato per scelta".
0xDEADBEEF soddisfa tutti e quattro. Per questo l'hanno usato
sistemi operativi, allocatori di memoria e compilatori, ognuno per
ragioni un po' diverse.
Non è un indirizzo: la differenza che conta
Questo è il punto su cui vale la pena essere precisi, perché la
confusione è frequente. 0xDEADBEEF non è un indirizzo di memoria
nel senso di un puntatore valido: non c'è nessun processo in cui quel
numero corrisponda a una pagina mappata e leggibile. È un valore
usato dentro la memoria, che代表a "qui non c'è niente di
utilizzabile".
La differenza pratica è già tutta nella parola. In un crash dump del 1980, stampato su carta, gli operatori IBM cercavano un "eyecatcher": un valore a tutta parola che saltava all'occhio in mezzo a dati omotenei, permettendo di posizionarsi nell'offset giusto del buffer senza leggerlo. Da lì l'uso come pattern di riempimento per memoria debolmente inizializzata, e l'espressione "your program is DEADBEEF" nel gergo informatico, con il significato di *andato, abortito, svuotato*.
Da dove viene: ambiente IBM, anni '70-'80
L'origine non è documentata con certezza, e va detto: chi lo usa oggi ha spesso letto più di quanto la storia consenta di affermare. Quello che risulta solido:
La pratica risale agli anni '70, quando in alcuni ambienti IBM si usava
0xDEADBEEFper indicare un valore non valido, riempiendo la memoria liberata dopo una deallocazione o come surrogato diNULL.È documentato e diffuso soprattutto nelle macchine midrange IBM, inclusa la famiglia RS/6000, dove compare in dump di sistema.
È presente in implementazioni di VMS su DEC VAX, nei Mac classici, in OPENSTEP e nel Commodore Amiga.
- Su Solaris marca la memoria kernel liberata:
#define KMEM_FREE_PATTERN 0xdeadbeefdeadbeefULL
Quel dettaglio è interessante per due motivi. Primo, il valore è ripetuto due volte per riempirere un intero blocco da 64 bit allineato, mantenendo l'allineamento. Secondo, in un'epoca in cui dumps di sistema erano carta, il pattern rendeva immediatamente riconoscibile un'area "morta" in mezzo a dati utili.
Perché è sparito, e perché non del tutto
Con i sistemi a memoria paginata moderni l'uso principale è progressivamente sparito. Le ragioni sono tecniche ed esatte:
Non serve più inizializzare. Ogni kernel azzera le pagine quando le mappa in uno spazio di indirizzamento. Azzerare con
0xDEADBEEFcosterebbe fault su tutte le pagine toccate, per un vantaggio che non si paga più.Cache e costo. Scrivere un pattern non-zero alloca memoria fisica che un processo potrebbe non usare mai, e fa perdere cache.
Ma il concatto è rimasto nei compilatori e nel debug: malloc e
free di molte librerie moderni us ancora valori sentinella per
distinguere memoria inizializzata da memoria liberata, e la scena
debugger/analisi forense continua a usare pattern di questo tipo per
riconososcere aree "avvelenate" nelle immagini di memoria.
Vale la pena accennare alle varianti, perché mostrano che la convenzione è più ampia di un solo valore:
| Valore | Uso tipico |
|---|---|
0xDEADBEEF | memoria liberata, non inizializzata |
0xBBADBEEF | usato da iOS nelle crash report |
0xCAFEBABE | magic number dei file .class Java |
0xDEADBABE | costante di controllo nel Jikes JVM |
0xBAADF00D | valore non valido, altro nome della stessa idea |
Questi pattern hanno un nome collettivo: hexspeak, valori esadecimali che formano parole inglesi leggibili.
Perché oggi è una cattiva idea nel codice di produzione
Il valore è splendido nel debug e sconsigliato altrove, e il motivo è preciso. Molti debugger e runtime hanno già riservato quel valore per i propri scopi: se lo usi nel tuo codice, in un ambiente che alloca con pattern sentinella, il tuo valore è indistinguibile dalla memoria libera. Nel caso migliore i dump diventano meno utili, nel peggiore ricevi warning fuorvianti dal debugger.
Il punto più interessante è un altro: se un valore magico compare in
tutto il codice, diventa una bomba a orologeria. Chi legge
if (p != 0xDEADBEEF) ventimila righe più avanti deve ricordarsi cosa
significa, e nessun contesto nel codice lo aiuta. Esiste una funzione
già pronta che fa lo stesso lavoro, e ha pure un nome.
Scrivere il numero a mano ha anche un costo di review: 0xDAEDBEEF e
0xDEADBEFF sono errori che passano in una code review e che il
compilatore segnala solo al momento dell'errore a runtime, nel caso
migliore.
In sintesi
0xDEADBEEF non è un indirizzo: è un valore che non è un
indirizzo, scelto per essere memorabile e per fallire rumorosamente.
È nato nell'era dei dump su carta degli anni '70-'80, ha attraversato
VMS, Mac, Amiga e Solaris, e sopravvive oggi nel debug dei compilatori.
La cosa che vale la pena portare a casa non è il numero, è il principio: un valore sentinella vale quanto la facilità con cui lo riconosci quando qualcosa è andato storto. Lo stesso ragionamento vale per un honeytoken, per un canary in un test, per un campo obbligatorio che nessuno verifica. Non protegge nulla da solo: rende rende il silenzio un errore invece di una normalità.