Category Archives: Research Blogging

Comportamenti indefiniti

ResearchBlogging.org

Il quiz dello scorso post era un pochino a trabocchetto, lo ammetto, ma Alessandro ha fatto un buon lavoro ed è quasi arrivato alla soluzione. Il quiz era il seguente:

Qual’è il comportamento del seguente programmino C?

#include <stdio.h>

int d = 5;

int set(int x)
{
    return d = x;
}

int main()
{
    int r = set(0) + 7 / d;
    printf("risultato = %d\n", r);
    return 1; 
}

E la risposta è

(rullo di tamburi)

il programma ha un comportamento indefinito!

Come, non sapete cos’è un comportamento indefinito? Beh, se programmate in C, è il momento di fare un corso di aggiornamento.

Benvenuti nel fantasmagorico mondo degli Undefined Behaviours!

Se volete una introduzione veloce, al solito c’è wikipedia. Se non ci avete capito molto, vi consiglio questa serie di tre post del bravissimo John Regher (tra l’altro, John ha anche un corso su Udacity, sul testing: se avete tempo di seguirvi un intero corso e volete studiare software testing, il corso di John è quello che fa per voi!).

Se invece volete continuare a leggere, vi spiego in quattro e quattr’otto di cosa stiamo parlando. La semantica del C/C++ è un po lasca, e in certi punti lascia libertà al compilatore. L’esempio classico è quello dell’accesso a un array al di là della sua dimensione. Per esempio, se avete un array di 10 interi, e accedete all’11-esima posizione, che succede? Lo standard C vi dice che, in tal caso, il comportamento del programma è “undefined”.

Che deve fare un compilatore quando si trova di fronte a un comportamento indefinito? Beh, l’interpretazione più diffusa è che il compilatore è libero di fare quello che gli pare. Qualunque cosa, e per “qualunque cosa” intendo proprio qualsiasi. Infatti, un programma che ha un “undefined behaviour” è semanticamente incorretto, e quindi da una cosa scorretta si può tirar fuori qualunque altra cosa, no? Persino dei diavoletti dal naso (nasal demons!).

Torniamo al quiz e vediamo perché il programma ha un comportamento indefinito. In C, l’ordine con cui vengono valutati gli operandi di una espressione non è sempre ben definito. Ci sono regole un pochino complicate, che hanno a che fare con i cosidetti sequence points. In pratica, un sequence point è un punto del programma in cui, prima di proseguire bisogna acertarsi di aver risolto tutti i “side effects” delle istruzioni precedenti. Il problema del nostro programma è che nella fattispecie l’operatore di somma non è un sequence point. Quando scriverte (x + y), il compilatore è libero di calcolare prima x e poi y, oppure viceversa. Di solito, l’ordine di valutazione degli argomenti non è importante: dopo tutto, in matematica l’addizione gode della proprietà commutativa, quindi ci si aspetta che cambiando l’ordine degli addendi il risultato non cambi! Per cui, chi ha scritto lo standard ha lasciato libero il compilatore di eseguire la valutazione degli addendi nell’ordine che preferiva. Per esempio, se per ragioni di ottimizzazione, è meglio valutare prima y e poi x, il compilatore lo farà, perché è libero di farlo, secondo lo standard.

Purtroppo, nel nostro programma, valutare prima la chiamata set(0) e poi 7/d, oppure prima 7/d e poi set(0) cambia il risultato: nel primo caso si ha una bella divisione per 0; nel secondo caso invece il risultato dovrebbe essere 1.
Poiché il risultato non è univoco, ma dipende dall’ordine in cui il compilatore decide di valutare i due operandi, allora il nostro programma ha un “Undefined Behaviour”. In pratica, è scorretto e non aderente allo standard. Tenete anche presente che una cosa è la precedenza fra operatori, e una cosa completamente diversa è l’ordine di valutazione degli operandi: la differenza è spiegata piuttosto bene qui.

Nella pratica, in questo caso il compilatore gcc segue sempre l’ordine di valutazione da sinistra verso destra, per cui se compilate ed eseguite il programma con gcc avrete come risultato un bell’errore di divisione per zero.

Più interessante è il comportamento di un altro compilatore che ultimamente sembra andare per la maggiore: si tratta di clang. Se compilate con il comando:

$ clang prova.c
$ ./a.out
Floating point exception (core dumped)

che è quello che ci si aspetta. Mentre se compilate con le ottimizzazioni:

$ clang -O2 prova.c
$ ./a.out
risultato = 894604808

Il numero che tira fuori è assolutamente casuale! Come mai? Beh, semplice: come detto prima, in presenza di “Undefined Behaviour” tutto può succedere, anche che vi escano i demoni dal naso! Più seriamente: Se il programma è non corretto (ha dei comportamenti indefiniti), il compilatore può tirare fuori eseguibili non corretti, non necessariamente nel modo che ci si aspetta.

Soluzioni?

Adesso la parte seria. Recentemente due ricercatori dell’University of Illinois at Urbana-Champaign (UIUC), Ellison e Rosu, hanno scritto un articolo molto interessante in cui si sono presi la briga di specificare in maniera formale la semantica del linguaggio C, utilizzando un altro linguaggio formale chiamato K. A differenza di altre specifiche formali, quella proposta da Ellison e Rosu è eseguibile, e questo significa che possono prendere un programma C e analizzarlo formalmente, ad esempio alla ricerca di undefined behaviours. Il loro codice è disponibile su googlecode, e teoricamente avrei potuto provarlo sul nostro programmino di esempio. (Ci ho provato per un po’ ma purtroppo il processo di compilazione mi si ferma a metà, devo aver sbagliato qualcosa. Se ci riesco ve lo faccio sapere.) Comunque, il programma dei nostri due ricercatori dovrebbe identificare la presenza di un undefined behaviour nel nostro programmino di quiz e metterlo bene in evidenza.

Il lavoro di Ellison e Rosu è molto importante: dato che molto codice per sistemi embedded e sistemi critici è ancora scritto in C, spesso in maniera euristica o artigianale, avere a disposizione strumenti automatici di verifica è essenziale per l’eliminazione di errori che potrebbero addirittura mettere a rischio la nostra sicurezza. La mia previsione è che nel futuro sentiremo parlare sempre di più di strumenti di verifica formale come assistenza alla programmazione.

Chucky Ellison, Grigore Rosu (2012). An executable formal semantics of C with applications ACM SIGPLAN Notices, 47 (1), 533-544 : 10.1145/2103621.2103719

Annunci

Tre numeri a caso

ResearchBlogging.org

Nel post precedente ho enunciato quello che sembra un problemino abbastanza semplice. Talmente semplice che non è che uno ci stia a pensare più di tanto, perché la soluzione sembra ultrabanale. Ecco il problema:

Estrarre tre numeri reali positivi a caso in modo che la loro somma sia pari a 1. Fare in modo che le triplette di numeri siano distribuite uniformemente, ovvero ognuna delle possibili triplette deve avere la stessa densità di probabilità di essere estratta a caso.

Sembra talmente semplice che di solito uno applica un metodo molto intuitivo e semplice senza stare a pensarci più di tanto:

Si estraggono a caso tre numeri reali positivi tra 0 e 1, siano x1, x2 e x3, con densità di probabilità uniforme (ovvero ogni possibile reale tra 0 e 1 ha la stessa probabilità di uscire). Quindi si sommano, sia s=x1+x2+x3. I tre numeri cercati sono U1=x1/s, U2=x2/s, U3=x3/s.

Semplice ed intuitivo. Peccato che sia sbagliato: facendo così, certe triplette vengono fuori più frequentemente di altre. Come è possibile? E’ quello che si sono chiesti i miei colleghi e co-autori Enrico e Giorgio nel paper citato in fondo a questo post.

Il problema si presenta spesso nelle nostre ricerche: ci occupiamo di algoritmi per la schedulazione dei processi in sistemi in tempo reale, e spesso dobbiamo misurare e comparare le loro performance. Per fare queste comparazioni si generano a caso dei processi e si cerca di applicare un test di schedulabilità per ogni algoritmo. Spesso, il parametro più importante di un processo per noi è il carico di lavoro che impone sulla CPU. Il carico si esprime come una percentuale, quindi un numero reale tra 0 e 1. Inoltre, una CPU non puà essere caricata per più del 100%, per cui ecco che salta fuori il problema: dobbiamo generare a caso 3 (o più) numeri, uno per ogni processore, in modo che la loro somma sia un valore predeterminato e fisso non superiore a uno. Per semplicità, assumiamo qui che tale valore deve essere esattamente pari a 1.

Enrico stava appunto cercando di rifare degli esperimenti comparativi fra i due algoritmi più famosi: Fixed Priority e Earliest Deadine First, e guardando i lavori in letteratura si accorse che tutti utilizzavano il metodo di cui sopra. Al mio amico venne il sospetto che tale metodo non fosse “fair”, cioè tendesse a favorire uno dei due algoritmi contendendi. Per cui si mise a studiare il problema. Per prima cosa, generò un buon numero di triplette con il metodo della normalizzazione. Tali numeri si possono graficare in 3D, e vanno tutti a finire su un triangolo che ha i sui vertici nei punti (1,0,0), (0,1,0), (0,0,1). Ecco il triangolo che venne fuori:

Come vedete con questo metodo i punti tendono a finire più spesso verso il centro del triangolo. Come mai? C’è una spiegazione geometrica semplice: Guardate la seguente sezione in 2D (cioè, cosa succederebbe se i numeri da estrarre fossero solo 2):

I punti x1 e x2 generati a caso tra 0 e 1 stanno nel quadrato. Dopo la normalizzazione U1 e U2 vengono riportati sulla retta retta obliqua che va da (0,1) a (1,0). Quindi, tutti i punti gialli andranno sul segmento in alto; tutti i punti nell’area rossa andranno sul segmento nel mezzo. Poiché l’area rossa è molto maggiore dell’area gialla, ne consegue che molti più punti finiranno sul segmento centrale rispetto a quelli che finiscono sui segmenti laterali. Torna? Beh, ora immaginatelo in 3D.

Qualcun’altro ha proposto questo metodo:

genero solo due numeri: U1 come distribuzione uniforme tra (0,1); U2 come distribuzione uniforme tra (0, 1-x1). Infine, U3 = 1-U1-U2.

Purtroppo non va meglio, ecco cosa esce fuori:

Stanno più frequentemente da una parte! Questo perché, dopo aver generato il primo, il secondo non ha distribuzione propriamente uniforme tra 0 e 1…

Un metodo corretto è quello del rejection sampling, proposto da Alb su friendfeed, e da hronir su okpanico. Si generano punti su un rettangolo nel piano 2D che ha base e altezza pari alla base del triangolo; se il punto finisce fuori dal triangolo, si butta via e se ne genera un altro. Se invece finsce dentro il triangolo, si proietta sui tre assi per ottenere i tre numeri originari. In questo modo siamo sicur di generare uniformemente. Come riprova, ecco la figura ottenuta:

Bella vero?

C’è un solo problema: se invece di generare solo 3 numeri ne vogliamo generare N, allora il numero di punti che cascano fuori dall’iper-triangolo in N-1 dimensioni sono molti di pù di quelli che cascano dentro. Talmente tanti di più che il metodo diventa improponibile. Quindi, al crescere di N tale metodo non è utilizzabile.

E questo era proprio il problema di Enrico e Giorgio: come generare N numeri a caso tali che la somma faccia un numero fisso minore di 1? Ci sono altri due metodi possibili. Il primo è il seguente:

Generiamo N-1 numeri in maniera uniforme compresi tra (0,1), chiamiamoli x(1), x(2), … x(N-1). Poi li ordiniamo dal più piccolo al più grande, e aggiungiamo x(0) = 0 in testa, e x(N)=1 in fondo. A questo punto, i numeri richiesti saranno:
U(1)=x(1)-x(0)=x(1),
U(2)=x(2)-x(1),



U(i) = x(i) – x(i-1),


U(N)=x(N)-x(N-1)=1-x(N-1).

Per capire come funziona, pensate di avere un segmento lungo 1, e di “gettare” N-1 punti a caso su di esso. La lunghezza dei segmenti delimitati tra due punti consecutivi sono i numeri che cerchiamo.

Per N=3 si ottiene un grafico 3D proprio uguale a quello mostrato qui su: provare per credere! Inoltre il metodo ha complessità O(N log(N)), quindi va abbastanza bene. Perché funziona? Non è facilissimo fare la dimostrazione. Bisogna dimostrare che la probabilità che un segmento abbia lunghezza minore di un certo L è proporzionale a L. Facendo i conti con le probabilità nel caso N=3 se ne dovrebbe uscire facilmente. L’altra sera l’abbiamo fatto con un mio studente e ce ne siamo venuti via con un paio di paginette… 🙂 (se qualcuno ha tirato fuori un ragionamento migliore ce lo faccia sapere!)

L’ultimo metodo lo prendo dall’articolo, Enrico e Giorgio l’hanno chiamato UUniFast.

La distribuzione di probabilità della somma di N variabili casuali è la convoluzione delle distribuzioni delle variabili sommate. La pdf uniforme è una retta di inclinazione 1: P\{x \leq a\} = a. La convoluzione di N variabili uniformi è un poliomio di grado N: P\{s \leq a\} = a^N. Si può quindi sfruttare questo fatto come segue:

  • Per prima cosa si genera un numero tra 0 e 1 uniformemente, e se ne fa la radice (N-1)-esima. Chiamiamo questo numero s(N-1).
  • Quindi si genera un secondo mumero tra 0 e s(N-1), e se ne fa la radice (N-2)-esima. Chiamiamo questo numero s(N-2).
  • E così via fino a s(1).

I numeri richiesti sono quindi:

U(1)=S(1),

U(2)=S(2)-S(1),

U(i)=S(i)-S(i-1),


U(N)=1-S(N-1).

Ok, panico, se qui non mi avete seguito è perché magari non vi ricordate bene la teoria delle probabilità! Comunque, tutti i dettagli sono nel paper qui sotto.

A proposito: Enrico e Giorgio dimostrarono che il metodo “normalizzato” era a favore dell’algoritmo EDF, e quindi rifacendo le simulazioni con il nuovo metodo di generazione ristabilirono la giustizia tra gli algoritmi! (vince sempre EDF, ma con un distacco minore…)


Bini, E., & Buttazzo, G. (2005). Measuring the Performance of Schedulability Tests Real-Time Systems, 30 (1-2), 129-154 DOI: 10.1007/s11241-005-0507-9

Non accavallatevi! Ovvero: la teoria dei giochi applicata alle telecomunicazioni

ResearchBlogging.org Non sempre la somma degli interessi individuali produce un vantaggio per la comunità, specialmente in presenza di risorse scarse.

E’ quello che ha potuto verificare lo stato maggiore della Apple lo scorso giugno durante la presentazione del nuovo iPhone4 alla Apple developer conference. Steve Jobs voleva appunto mostrare le meravigliose capacità del proprio gioiellino, e come prima cosa cercò di collegarlo alla rete Wi-Fi presente in sala. Ma la sala era piena di centinaia di appassionati, tutti con il loro laptop connesso alla rete, tutti pronti a bloggare/twittare e facebokare le primissime impressioni dalla sala. E come chiunque partecipante ad una qualsiasi conferenza ha potuto sperimentare, semplicemente il Wi-Fi non andava: troppo traffico, troppe stazione che cercano di connettersi contemporaneamente.


Al che Jobs sfodera il suo sorriso e annuncia: “Ci sono 570 apparecchiature Wi-Fi in questa stanza, e semplicemente non c’è abbastanza banda per tutti. Per continuare la dimostrazione, vi invito tutti a spegnere i vostri laptop e a depositarli ai vostri piedi”. Alcuni dei presenti obbedirono, ma molti invece continuarono a tenere acceso il proprio. La demo non fu un quello che si dice un completo successo.

Forse c’è una lezione dietro tutto ciò, e per spiegarla dobbiamo ricorrere a una branca della matematica chiamata “teoria dei giochi“. La situazione è infatti simile al famoso “dilemma del prigioniero“. Tutti i giornalisti in sala avevano un interesse preciso: essere i primi a poter postare notizie e impressioni sul nuovo modello, in tempo reale dalla sala. Ma se tutti tenevano il Wi-Fi acceso, nessuno poteva comunicare. Se una buona parte avesse spento il PC, gli altri avrebbero postato per primi, e quindi ne avrebbero ottenuto un vantaggio. Quindi, se tutti ricercano il loro migliore interesse, nessuno raggiunge l’obiettivo.

Gli autori dell’articolo di cui ci occupiamo oggi sostengono che è possibile applicare risultati della teoria dei giochi per evitare situazioni del genere in presenza di banda limitata. Come sapete, la banda del Wi-Fi è limitata, e va condivisa tra tutti i partecipanti presenti nella stessa area e connessi allo stesso “gateway”.

Quest’ultimo, detto anche “coordinatore”, si occupa di distribuire la banda tra i vari partecipanti utilizzando un meccanismo a divisione di tempo:  il coordinatore assegna in maniera esclusiva la banda disponibile per un “time slice” di alcuni millisecondi a ciascuno dei trasmettitori a turno. Ad esempio, supponendo un time-slice di 2 millisecondi, e se ci sono 4 dispositivi che desiderano trasmettere, ognuno di loro potrà trasmettere per 2 millisecondi ogni 8, prendendosi quindi effettivamente 1/4 della banda disponibile.

In realtà, questo meccanismo non sempre funziona benissimo per vari motivi. Infatti, non tutti i dispositivi sono in grado di comunicare con la stessa qualità. Se uno dei 4 è più lontano, oppure dietro una parete, sperimenterà una qualità del segnale minore, e quindi comunicherà più lentamente. Se invece uno sta proprio sotto il coordinatore, magari comunica molto meglio. Misuriamo la “qualità” del canale di comunicazione come picco massimo di byte che possono essere trasmessi al secondo. Per esempio, supponiamo che al massimo della qualità si possa comunicare a 10 Mbytes al secondo, e al minimo si possa andare a 1Mbytes al secondo. Purtroppo, per rendere le cose più difficili, la qualità della comunicazione varia grandemente con il tempo: i trasmettitori si possono muovere, all’aperto le condizioni metereologiche possono influire, ecc.

Se la rete in questione (come nel caso del Wi-Fi) ha un coordinatore, per ottimizzare la comunicazione di solito si cerca di far comunicare di più chi ha la banda migliore. Quindi, invece di utilizzare time-slices tutte di dimensioni uguali, si varia la dimensione dando una time-slice proporzionale alla qualità del segnale: migliore qualità, maggiore la time slice. Questo è abbastanza facile da fare, perché il coordinatore, quello che decide, monitora la qualità dei partecipanti e quindi sa chi ha il canale migliore, e decide in maniera ottimale.

Supponiamo ora una rete senza coordinatore. Queste reti sono “peer-to-peer”, o anche dette reti “Mobile Ad-Hoc”. Non essendoci un coordinatore, tutti i partecipanti devono mettersi d’accordo fra di loro per decidere le time-slices usando un regola. Si potrebbe fare così: ognuno annuncia la propria qualità di trasmissione percepita; a questo punto, tutti applicano la formula per decidere la dimensione e l’ordine delle time-slices; poi si comincia a comunicare. Dopo un po’, ci si rimette d’accordo sulla qualità del canale, ecc.

Questo tipo di approccio però è soggetto a un problema: un trasmettitore potrebbe mentire! Potrebbe dichiarare una qualità molto alta, in modo da beccarsi un time-slice il più grande possibile. Se tutti facessero così, l’utilizzo finale della rete sarebbe scarso, sub-ottimale.

Gli autori quindi propongono un meccanismo di punizioni per chi si comporta male. Si monitora la quantità di dati trasmessi effettivamente da una stazione e si confronta con quello dichiarato. Chi ha fatto il furbo viene “bannato” (ovvero non ha diritto a trasmettere) per un po’ di tempo. Questo il succo a grandi linee. L’approccio è naturalmente un po’ più complicato perché bisogna stare attenti ad alcuni dettagli che qui salterò, e che se volete potete andarvi a leggere sull’articolo originale.

La teoria dei giochi è stata quindi applicata a un nuovo campo di applicazione, e mi sa continuerà ad essere utilizzata parecchio nel futuro delle telecomunicazioni!

RIFERIMENTI

Ji, Z., & Liu, K. (2007). COGNITIVE RADIOS FOR DYNAMIC SPECTRUM ACCESS – Dynamic Spectrum Sharing: A Game Theoretical Overview IEEE Communications Magazine, 45 (5), 88-94 DOI: 10.1109/MCOM.2007.358854

La storiella di Jobs è presa da questo articolo divulgativo di uno degli autori.

Un cavallo di troia nel processore

Il cavallo di Troia

Il cavallo di troia

Tutti conoscono la storia del cavallo di Troia, e di come Ulisse riusci a vincere la guerra con un’azione di quella che oggi chiameremo “intelligence”, forse proprio in onore del valoroso Ulisse.

In Computer Science, un Trojan è un modulo software apparentemente innocuo, ma che nasconde codice “malizioso”, e a totale insaputa del cliente può modificare il comportamento del programma per svolgere attività non propriamente lecite. Questo tipo di “attacco” informatico è abbastanza pericoloso, perché in certi casi si potrebbe adirittura perdere il controllo del proprio PC.

Pensate a cosa succederebbe se il Trojan fosse addirittura un circuito hardware all’interno del vostro processore: non ci sarebbe verso di rimuoverlo, se non cambiando il processore!

Ma come è possible mettere un Trojan nell’hardware? In un articolo pubblicato sul  numero di Ottobre 2010 di “Computer Magazine“, R. Karri, J. Rajendran, K. Rosenfeld and M. Tehranipoor ci descrivono come sia possibile ritrovarsi con un Trojan nell’hardware.

Una volta (in informatica, 10 anni fa è un’era geologica) i chip vendor come Intel, AMD, Motorola, Texas Instruments, ecc. facevano tutto in casa: dalla progettazione del circuito sotto forma di schema logico; alla verifica e testing; al layout delle maschere; al processo di produzione in fonderia; al testing finale alla ricerca dei difetti. L’evoluzione del settore, alla ricerca della diminuzione dei costi, ha radicalmente mutato questo scenario economico. Infatti, il processo finale di produzione di un chip, dalla maschera al pezzo di silicio, costa moltissimo e cambia spesso con il cambio di tecnologia, tanto che una singola azienda, sebbene gigantesca come Intel, non può più permettersi di farlo in proprio. Esistono quindi fonderie specializzate che stampano chip per tutti i vendor.

I vendor, dal loro canto, si tengono ben stretta la prima parte del processo, ovvero la progettazione degli schematici, i cosidetti IP (Intellectual Property). Inoltre, alcune aziende specializzate si limitano a produrre IP specializzati da vendere alle aziende più grandi, che così sono in grado di abbattere anche i costi di progettazione. In questo senso, ha fatto scuola la ARM Ltd, che vende lo schematico dei suoi processori ad aziende come Samsung, la stessa Intel, ecc., ma non vende in proprio alcun chip.

Il problema è che quasi tutte le fonderie di silicio si trovano in Korea, Hong Kong e Cina. Specialmente in quest’ultimo paese, è molto difficile assicurarsi che i propri schematici siano trattati con la necessaria cautela. E’ molto comune, ad esempi,o che tali schematici finiscano in mano ad aziende che stampano illegamente cloni “non griffati” di famosi chip, da vendere sul mercato nero. In una indagine condotta nel 2004-2006, l’FBI (sì, proprio il famoso Federal Bureau) ha trovato cloni “contraffati” di router Cisco in molte università, banche, e perfino nel dipartimento della difesa americano.

In questo scenario, non è affatto impossibile che nella catena di produzione di un chip riesca ad introdursi un malintenzionato che possa modificare gli schematici originali introducendo delle funzionalità nascoste da attivare al momento opportuno. Non credo ci sia bisogno di dilungarsi su cosa significhi avere la possibilità di disabilitare il funzionamento della rete Internet per un periodo più o meno lungo, oppure riuscire a bypassare le funzionalità crittografiche di un chip per poter inviare i dati in chiaro su un canale separato.

Gli autori dell’articolo, dopo un’introduzione al problema, propongono una tassonomia dei Trojan secondo 5 caratteristiche separate: il momento dell’inserimento, il livello di astrazione, il meccanismo di attivazione, gli effetti indesiderati e la zona che viene attaccata. La tassonomia viene messa alla prova categorizzando alcuni Trojan sviluppati durante l’embedded systems challenge, una gara tra studenti che dovevano provare ad inserire trojan all’interno di un chip. E’ naturalmente impossibile condurre un tale studio su sistemi reali per mancanza di dati pubblici al riguardo.

La tassonomia è un primo indispensabile passo per poter pensare di prendere provvedimenti contro attacchi di questo genere, anche se è ancora molto lontani dal poter proporre soluzioni sistematiche al problema.

Riferimenti

Karri, R., Rajendran, J., Rosenfeld, K., & Tehranipoor, M. (2010). Trustworthy Hardware: Identifying and Classifying Hardware Trojans Computer, 43 (10), 39-46 DOI: 10.1109/MC.2010.299

PS: L’mmagine in apertura è dell’ottimo Giampiero Wallnöfer.