Osservando le istruzioni stantie recuperando su x86 con codice auto-modificante

Mi è stato detto e ho letto dai manuali di Intel che è ansible scrivere istruzioni in memoria, ma la coda di prefetch delle istruzioni ha già recuperato le istruzioni obsolete ed eseguirà quelle vecchie istruzioni. Non ho avuto successo nell’osservare questo comportamento. La mia metodologia è la seguente.

Il manuale di sviluppo del software Intel afferma dalla sezione 11.6 che

Una scrittura in una posizione di memoria in un segmento di codice attualmente memorizzato nella cache del processore causa l’annullamento della riga (o delle linee) della cache associata. Questo controllo si basa sull’indirizzo fisico dell’istruzione. Inoltre, la famiglia P6 ei processori Pentium verificano se una scrittura su un segmento di codice può modificare un’istruzione che è stata precaricata per l’esecuzione. Se la scrittura interessa un’istruzione prefetch, la coda di prefetch viene invalidata. Quest’ultima verifica si basa sull’indirizzo lineare dell’istruzione.

Quindi, sembra che se spero di eseguire istruzioni obsolete, ho bisogno di avere due indirizzi lineari diversi che si riferiscono alla stessa pagina fisica. Quindi, la memoria mappa un file in due indirizzi diversi.

int fd = open("code_area", O_RDWR | O_CREAT, S_IRWXU | S_IRWXG | S_IRWXO); assert(fd>=0); write(fd, zeros, 0x1000); uint8_t *a1 = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_FILE | MAP_SHARED, fd, 0); uint8_t *a2 = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_FILE | MAP_SHARED, fd, 0); assert(a1 != a2); 

Ho una funzione assembly che accetta un singolo argomento, un puntatore all’istruzione che voglio cambiare.

 fun: push %rbp mov %rsp, %rbp xorq %rax, %rax # Return value 0 # A far jump simulated with a far return # Push the current code segment %cs, then the address we want to far jump to xorq %rsi, %rsi mov %cs, %rsi pushq %rsi leaq copy(%rip), %r15 pushq %r15 lretq copy: # Overwrite the two nops below with `inc %eax'. We will notice the change if the # return value is 1, not zero. The passed in pointer at %rdi points to the same physical # memory location of fun_ins, but the linear addresses will be different. movw $0xc0ff, (%rdi) fun_ins: nop # Two NOPs gives enough space for the inc %eax (opcode FF C0) nop pop %rbp ret fun_end: nop 

In C, copio il codice nel file mappato in memoria. Invoco la funzione dall’indirizzo lineare a1 , ma passo un puntatore a a2 come destinazione della modifica del codice.

 #define DIFF(a, b) ((long)(b) - (long)(a)) long sz = DIFF(fun, fun_end); memcpy(a1, fun, sz); void *tochange = DIFF(fun, fun_ins); int val = ((int (*)(void*))a1)(tochange); 

Se la CPU ha prelevato il codice modificato, val == 1. Altrimenti, se sono state eseguite le istruzioni stantie (due nops), val == 0.

L’ho eseguito su un Intel Core i5 da 1.7GHz (macbook air 2011) e una CPU Intel Xeon (R) X3460 a 2.80GHz. Ogni volta, tuttavia, vedo val == 1 che indica che la CPU nota sempre la nuova istruzione.

Qualcuno ha esperienza del comportamento che voglio osservare? Il mio ragionamento è corretto? Sono un po ‘confuso riguardo al manuale che menziona i processori P6 e Pentium, e quale sia la mancanza di menzionare il mio processore Core i5. Forse sta succedendo qualcos’altro che fa sì che la CPU svuoti la sua coda di prefetch delle istruzioni? Qualsiasi intuizione sarebbe molto utile!

Penso che dovresti controllare il MACHINE_CLEARS.SMC prestazioni MACHINE_CLEARS.SMC (parte dell’evento MACHINE_CLEARS ) della CPU (è disponibile in Sandy Bridge 1 , che è usato nel tuo libretto di Air, e anche disponibile su Xeon, che è Nehalem 2 – cerca “smc”). Puoi usare oprofile , perf o Intel Vtune per trovare il suo valore:

http://software.intel.com/sites/products/documentation/doclib/iss/2013/amplifier/lin/ug_docs/GUID-F0FD7660-58B5-4B5D-AA9A-E1AF21DDCA0E.htm

Macchina Cancella

Descrizione metrica

Alcuni eventi richiedono che l’intera pipeline venga cancellata e riavviata subito dopo l’ultima istruzione ritirata. Questa metrica misura tre eventi di questo tipo: violazioni di ordini di memoria, codice auto-modificante e determinati carichi a intervalli di indirizzi illegali.

Possibili problemi

Una parte significativa del tempo di esecuzione viene spesa per la gestione dei clear della macchina. Esaminare gli eventi MACHINE_CLEARS per determinare la causa specifica.

SMC: http://software.intel.com/sites/products/documentation/doclib/stdxe/2013/amplifierxe/win/win_reference/snb/events/machine_clears.html

Codice evento MACHINE_CLEARS: 0xC3 Maschera SMC: 0x04

Rilevato codice auto-modificante (SMC).

Rilevato numero di cancellazioni di codice auto-modificanti.

Intel dice anche su smc http://software.intel.com/en-us/forums/topic/345561 (collegato dalla tassonomia di Intel Performance Bottleneck Analyzer

Questo evento si triggers quando viene rilevato un codice auto-modificante. Questo può essere in genere utilizzato da persone che eseguono il binary editing per costringerlo a intraprendere un determinato percorso (ad esempio, gli hacker). Questo evento conta il numero di volte in cui un programma scrive in una sezione di codice. Il codice auto-modificante causa una severa penalizzazione in tutti i processori Intel 64 e IA-32. La linea cache modificata viene riscritta nelle cache L2 e LLC. Inoltre, le istruzioni dovrebbero essere nuovamente caricate, causando quindi una penalizzazione delle prestazioni.

Penso che vedrete alcuni di questi eventi. Se lo sono, la CPU è stata in grado di rilevare l’atto di auto-modifica del codice e ha sollevato il “Machine Clear” – riavvio completo della pipeline. Le prime fasi sono Fetch e chiederanno al cache L2 un nuovo opcode. Sono molto interessato al conteggio esatto degli eventi SMC per l’esecuzione del tuo codice – questo ci darà una stima delle latenze .. (SMC è conteggiato in alcune unità in cui si presume che 1 unità sia di 1,5 cicli CPU – B.6.2. 6 del manuale di ottimizzazione di Intel)

Possiamo vedere che Intel dice “riavviato subito dopo l’ultima istruzione ritirata”, quindi penso che le ultime istruzioni ritirate saranno mov ; e i tuoi nops sono già in cantiere. Ma la SMC verrà sollevata durante il ritiro di MOV e ucciderà tutto in conduttura, compresi i nops.

Questo SMC ha indotto il riavvio della pipeline non è economico, Agner ha alcune misurazioni in Optimizing_assembly.pdf – “17.10 Codice auto-modificante (tutti i processori)” (Penso che qualsiasi Core2 / CoreiX sia come PM qui):

La penalità per l’esecuzione di un pezzo di codice immediatamente dopo la sua modifica è di circa 19 orologi per P1, 31 per PMMX e 150-300 per PPro, P2, P3, PM. Il P4 eliminerà l’intera cache di traccia dopo il codice auto-modificante. I processori 80486 e precedenti richiedono un salto tra la modifica e il codice modificato per svuotare la cache del codice. …

Il codice auto-modificante non è considerato una buona pratica di programmazione. Dovrebbe essere usato solo se il guadagno di velocità è notevole e il codice modificato viene eseguito tante volte che il vantaggio supera le penalità per l’utilizzo del codice auto-modificante.

È stato consigliato l’uso di diversi indirizzi lineari per il failover del rilevatore SMC: https://stackoverflow.com/a/10994728/196561 – Proverò a trovare la vera documentazione Intel … Non posso rispondere alla tua vera domanda ora.

Potrebbero esserci alcuni suggerimenti qui: Manuale di ottimizzazione, 248966-026, aprile 2012 “3.6.9 Codice e dati di miscelazione”:

Inserire i dati scrivibili nel segmento di codice potrebbe essere imansible da distinguere dal codice auto-modificante. I dati scrivibili nel segmento di codice potrebbero subire la stessa penalizzazione delle prestazioni del codice auto-modificante.

e la prossima sezione

Il software dovrebbe evitare di scrivere su una pagina di codice nella stessa sottopagina da 1 KB in esecuzione o recuperare il codice nella stessa sottopagina da 2 KB di quello che viene scritto. Inoltre, la condivisione di una pagina contenente codice direttamente o speculativamente eseguito con un altro processore come una pagina di dati può triggersre una condizione SMC che causa l’eliminazione dell’intera pipeline della macchina e della cache di traccia. Ciò è dovuto alla condizione del codice auto-modificante.

Quindi, ci sono forse alcuni schemi che controllano le intersezioni delle sottopagine scrivibili ed eseguibili.

Puoi provare a modificare l’altro thread (codice di modifica incrociata), ma è necessaria la sincronizzazione molto attenta del thread e il flushing della pipeline (potresti voler includere un brute-forzamento dei ritardi nel thread del writer; CPUID subito dopo la sincronizzazione è desiderato). Ma dovresti sapere che hanno già risolto questo problema usando ” nukes ” – controlla il brevetto US6857064 .

Sono un po ‘confuso riguardo al manuale che menziona i processori P6 e Pentium

Questo è ansible se hai recuperato, decodificato ed eseguito una versione obsoleta del manuale di istruzioni di Intel. È ansible reimpostare la pipeline e controllare questa versione: Numero ordine: 325462-047US, giugno 2013 “11.6 CODICE AUTO-MODIFICANTE”. Questa versione non dice ancora nulla sulle nuove CPU, ma menziona che quando si modifica usando diversi indirizzi virtuali, il comportamento potrebbe non essere compatibile tra le microarchitetture (potrebbe funzionare su Nehalem / Sandy Bridge e potrebbe non funzionare su .. Skymont)

11.6 CODIFICA SELF-MODIFICANTE Una scrittura in una posizione di memoria in un segmento di codice attualmente memorizzato nella cache del processore causa l’invalidazione della riga (o delle linee) della cache associata. Questo controllo si basa sull’indirizzo fisico dell’istruzione. Inoltre, la famiglia P6 ei processori Pentium verificano se una scrittura su un segmento di codice può modificare un’istruzione che è stata precaricata per l’esecuzione. Se la scrittura interessa un’istruzione prefetch, la coda di prefetch viene invalidata. Quest’ultima verifica si basa sull’indirizzo lineare dell’istruzione. Per i processori Pentium 4 e Intel Xeon, una scrittura o uno snoop di un’istruzione in un segmento di codice, in cui l’istruzione di destinazione è già decodificata e residente nella cache di traccia, invalida l’intera cache di traccia. Quest’ultimo comportamento significa che i programmi che auto-modificano il codice possono causare un grave deterioramento delle prestazioni quando vengono eseguiti sui processori Pentium 4 e Intel Xeon.

In pratica, il controllo sugli indirizzi lineari non dovrebbe creare problemi di compatibilità tra i processori IA-32. Le applicazioni che includono il codice auto-modificante utilizzano lo stesso indirizzo lineare per la modifica e il recupero dell’istruzione.

Il software di sistema, come un debugger, che potrebbe eventualmente modificare un’istruzione utilizzando un indirizzo lineare diverso da quello utilizzato per recuperare l’istruzione, eseguirà un’operazione di serializzazione, come un’istruzione CPUID, prima dell’esecuzione dell’istruzione modificata, che verrà risincronizzata automaticamente la cache delle istruzioni e la coda di precaricamento. (Per ulteriori informazioni sull’uso del codice auto-modificante, consultare la Sezione 8.1.3, “Gestione del codice di auto-modifica e modifica incrociata”.)

Per i processori Intel486, una scrittura su un’istruzione nella cache la modificherà sia nella cache che nella memoria, ma se l’istruzione è stata precaricata prima della scrittura, la versione precedente dell’istruzione potrebbe essere quella eseguita. Per evitare che la vecchia istruzione venga eseguita, svuota l’unità di prefetch dell’istruzione codificando un’istruzione di salto immediatamente dopo ogni scrittura che modifica un’istruzione

Aggiornamento REAL , googled per “SMC Detection” (con virgolette) e ci sono alcuni dettagli su come il moderno Core2 / Core iX rileva SMC e anche molte liste di errata con Xeons e Pentium appesi nel rilevatore SMC:

  1. http://www.google.com/patents/US6237088 Sistema e metodo per il tracciamento delle istruzioni in volo in una pipeline @ 2001

  2. DOI 10.1535 / itj.1203.03 (google per esso, c’è la versione gratuita a citeseerx.ist.psu.edu) – il “FILTRO INCLUSIONE” è stato aggiunto in Penryn a un numero inferiore di falsi rilevamenti SMC; il “meccanismo di rilevamento dell’inclusione esistente” è illustrato nella figura 9

  3. http://www.google.com/patents/US6405307 – brevetto precedente sulla logica di rilevamento SMC

Secondo il brevetto US6237088 (FIG5, riassunto) c’è “Buffer di indirizzo di linea” (con molti indirizzi lineari un indirizzo per istruzione recuperata – o in altre parole il buffer pieno di IP recuperati con precisione della cache-line). Ogni negozio, o più esatta fase “store address” di ogni negozio, verrà inserito nel comparatore parallelo per controllare, memorizzerà gli intersechi con una qualsiasi delle istruzioni attualmente in esecuzione o meno.

Entrambi i brevetti non dicono chiaramente, utilizzeranno l’indirizzo fisico o logico nella logica SMC … L1i in Sandy bridge è VIPT ( Virtualmente indicizzato, contrassegnato fisicamente , indirizzo virtuale per l’indice e indirizzo fisico nel tag.) In base a http : //nick-black.com/dankwiki/index.php/Sandy_Bridge quindi abbiamo l’indirizzo fisico al momento in cui la cache L1 restituisce i dati. Penso che Intel possa utilizzare gli indirizzi fisici nella logica di rilevamento SMC.

Inoltre, http://www.google.com/patents/US6594734 @ 1999 (pubblicato nel 2003, ricorda che il ciclo di progettazione della CPU è di circa 3-5 anni) nella sezione “Riepilogo” indica che SMC ora è in TLB e utilizza indirizzi fisici (o in altre parole – per favore, non cercare di ingannare il rilevatore SMC):

Il codice auto-modificante viene rilevato usando un buffer lookaside di traduzione .. [in cui] sono memorizzati gli indirizzi di pagina fisici su cui è ansible eseguire gli snoops utilizzando l’ indirizzo di memoria fisica di un archivio in memoria. … Per fornire una granularità più fine di una pagina di indirizzi, i bit FINE HIT sono inclusi in ogni voce della cache che associa le informazioni nella cache a porzioni di una pagina all’interno della memoria.

(parte della pagina, indicata come quadranti nel brevetto US6594734, suona come pagine secondarie 1K, non è vero?)

Quindi dicono

Pertanto , gli snoop, triggersti ​​dalle istruzioni del negozio in memoria , possono eseguire il rilevamento SMC confrontando l’indirizzo fisico di tutte le istruzioni memorizzate nella cache delle istruzioni con l’indirizzo di tutte le istruzioni memorizzate nella pagina o nelle pagine di memoria associate. Se c’è una corrispondenza di indirizzo, indica che una posizione di memoria è stata modificata. Nel caso di una corrispondenza di indirizzo, che indica una condizione SMC, la cache delle istruzioni e la pipeline di istruzioni vengono scaricate dall’unità di ritiro e le nuove istruzioni vengono recuperate dalla memoria per l’archiviazione nella cache delle istruzioni.

Poiché gli snoop per il rilevamento SMC sono fisici e l’ITLB accetta normalmente come input un indirizzo lineare da tradurre in un indirizzo fisico, l’ITLB è inoltre formato come memoria indirizzabile ai contenuti sugli indirizzi fisici e include un’ulteriore porta di confronto dell’input (riferita a come porta snoop o porta di traduzione inversa)

– Quindi, per rilevare SMC, costringono i negozi ad inoltrare l’indirizzo fisico al buffer delle istruzioni tramite snoop (snoop simili verranno consegnati da altri core / cpus o dalle scritture DMA alle nostre cache ….), se snoop è fisico. conflitti di indirizzo con le linee della cache, memorizzati nel buffer delle istruzioni, riavviare la pipeline tramite il segnale SMC fornito da iTLB all’unità di pensionamento. Può immaginare quanti clock della CPU saranno sprecati in tale loop di snoop da dTLB tramite iTLB e all’unità di pensionamento (non può ritirarsi dopo l’istruzione “nop”, sebbene sia stata eseguita in anticipo rispetto a mov e non ha effetti collaterali). Ma WAT? ITLB ha l’input di indirizzo fisico e la seconda CAM (grande e calda) solo per supportare e difendere il codice auto-modificante pazzo e imbroglione.

PS: E se lavorassimo con pagine enormi (4M o 1G)? Il L1TLB ha enormi voci di pagina e potrebbero esserci molti falsi rilevamenti SMC per 1/4 di 4 MB di pagina …

PPS: C’è una variante, che l’errata gestione di SMC con indirizzi lineari diversi era presente solo nei primi P6 / Ppro / P2 …

Mi è stato detto e ho letto dai manuali di Intel che è ansible scrivere istruzioni in memoria, ma la coda di prefetch delle istruzioni [potrebbe avere] già recuperato le istruzioni obsolete e [potrebbe] eseguire quelle vecchie istruzioni. Non ho avuto successo nell’osservare questo comportamento.

Sì, lo saresti.

Tutti o quasi tutti i moderni processori Intel sono più rigidi rispetto al manuale:

Snoop la pipeline basata su indirizzo fisico, non solo lineare.

Le implementazioni del processore possono essere più severe dei manuali.

Possono scegliere di essere così perché hanno incontrato codice che non rispetta le regole nei manuali, che non vogliono rompere.

Oppure … perché il modo più semplice per aderire alle specifiche architettoniche (che nel caso di SMC era ufficialmente “fino alla prossima istruzione di serializzazione” ma in pratica, per il codice legacy, era “fino al prossimo ramo preso che è più di ??? bytes away “) potrebbe essere più rigoroso.