Cosa significa “rep ret”?

Stavo testando alcuni codici su Visual Studio 2008 e ho notato security_cookie . Posso capirne il significato, ma non capisco quale sia lo scopo di questa istruzione.

  rep ret /* REP to avoid AMD branch prediction penalty */ 

Certo che posso capire il commento 🙂 ma che cosa fa esattamente questo prefisso nel contesto del ret e cosa succede se ecx è! = 0? Apparentemente il conteggio dei ecx da ecx viene ignorato quando ecx debug, il che è ecx .

Il codice in cui ho trovato questo era qui (iniettato dal compilatore per sicurezza):

 void __declspec(naked) __fastcall __security_check_cookie(UINT_PTR cookie) { /* x86 version written in asm to preserve all regs */ __asm { cmp ecx, __security_cookie jne failure rep ret /* REP to avoid AMD branch prediction penalty */ failure: jmp __report_gsfailure } } 

C’è un intero blog che prende il nome da questa istruzione. E il primo post descrive il motivo alla base: http://repzret.org/p/repzret/

Fondamentalmente, c’era un problema nel predittore di ramo di AMD quando un ret singolo byte seguiva immediatamente un salto condizionato come nel codice che hai citato (e in alcune altre situazioni), e la soluzione era aggiungere il prefisso di rep , che viene ignorato da CPU ma corregge la penalità di predittore.

Apparentemente, alcuni predittori di ramificatori di processori AMD si comportano male quando l’objective di un ramo o il falltion è un’istruzione ret , e l’aggiunta del prefisso di rep evita.

Per quanto riguarda il significato di rep ret , non vi è alcuna menzione di questa sequenza di istruzioni in Intel Instruction Set Reference , e la documentazione di rep non è molto utile:

Il comportamento del prefisso REP non è definito quando viene utilizzato con istruzioni non stringa.

Questo significa almeno che il rep non deve comportarsi in modo ripetitivo.

Ora, dal riferimento alla serie di istruzioni AMD (1.2.6 Ripeti prefissi):

I prefissi dovrebbero essere usati solo con tali istruzioni per le stringhe.

In generale, i prefissi di ripetizione dovrebbero essere usati solo nelle istruzioni per le stringhe elencate nelle tabelle 1-6, 1-7 e 1-8 sopra [che non contengono ret].

Quindi sembra davvero un comportamento indefinito, ma si può presumere che, in pratica, i processori ignorino semplicemente i prefissi dei rep sulle istruzioni ret .

Come la risposta di Trillian fa notare, AMD K8 e K10 hanno un problema con la previsione del ramo quando ret è un objective filiale, o segue un ramo condizionale.

La guida all’ottimizzazione di AMD per K10 (Barcellona) raccomanda in questi casi il valore 3-byte ret 0 , che fa scoppiare zero byte dallo stack e restituire. Quella versione è significativamente peggiore del rep ret Intel. Ironia della sorte, è anche peggio che rep ret sui successivi processori AMD (Bulldozer e in poi.) Quindi è una buona cosa che nessuno ha cambiato usando ret 0 basato sull’aggiornamento della guida all’ottimizzazione della famiglia 10 di AMD.


I manuali del processore avvertono che i futuri processori potrebbero interpretare diversamente una combinazione di un prefisso e un’istruzione che non modifica. Questo è vero in teoria, ma nessuno produrrà una CPU che non può eseguire molti binari esistenti.

gcc usa ancora rep ret di default (senza -mtune=intel , o -march=haswell o qualcosa del genere). Quindi molti binari di Linux hanno un repz ret in loro da qualche parte.

gcc probabilmente smetterà di usare rep ret in pochi anni, una volta che K10 è completamente obsoleto. Dopo altri 5 o 10 anni, quasi tutti i binari verranno creati con un gcc più recente di quello. Altri 15 anni dopo, un produttore di CPU potrebbe pensare a riproporre la sequenza di byte f3 c3 come (parte di) un’istruzione diversa.

Ci saranno ancora binari closed-source legacy che usano rep ret che non hanno più build recenti disponibili e che qualcuno ha bisogno di continuare a correre, comunque. Quindi qualunque sia la nuova funzionalità f3 c3 != rep ret è parte dovrebbe essere disabilitata (ad esempio con un’impostazione BIOS), e avere quell’impostazione in realtà cambia il comportamento dell’istruzione-decodificatore per riconoscere f3 c3 come rep ret . Se quella retrocompatibilità per i binari legacy non è ansible (perché non può essere eseguita in modo efficiente in termini di potenza e transistor), IDK quale tipo di struttura temporale si dovrebbe guardare. Molto più di 15 anni, a meno che questa fosse una CPU solo per una parte del mercato.

Quindi è sicuro usare rep ret , perché tutti gli altri lo stanno già facendo. Usare ret 0 è una ctriggers idea. Nel nuovo codice, potrebbe essere comunque una buona idea usare rep ret per un altro paio di anni. Probabilmente non ci sono ancora troppe CPU AMD PhenomII ancora in giro, ma sono abbastanza lente senza errori di ritorno di indirizzo extra o il problema è.


Il costo è piuttosto piccolo. Nella maggior parte dei casi non occupa spazio extra, perché in genere è seguito da nop padding. Tuttavia, nei casi in cui ciò comporta un riempimento extra, sarà il caso peggiore in cui occorrono 15B di padding per raggiungere il limite successivo di 16B. gcc potrebbe solo allineare di 8B in quel caso. (con .p2align 4,,10; per allineare a 16B se impiegheranno 10 o meno byte nop, quindi a .p2align 3 per allineare sempre a 8B. Usa gcc -S -o- per produrre l’output di asm su stdout per vedere quando lo fa.)

Quindi se pensiamo che quel rep ret 16 finisca per creare un padding extra in cui un ret avrebbe appena colpito l’allineamento desiderato, e che il padding extra va a un limite di 8B, questo significa che ogni rep ha un costo medio di 8 * 1 / 16 = mezzo byte.

rep ret non è usato abbastanza spesso per sumre gran parte di qualcosa. Ad esempio, firefox con tutte le librerie che ha mappato ha solo ~ 9k di rep ret . Quindi è circa 4k byte, su molti file. (E meno RAM di quella, dal momento che molte di quelle funzioni nelle librerie dinamiche non vengono mai chiamate.)

 # disassemble every shared object mapped by a process. ffproc=/proc/$(pgrep firefox)/ objdump -d "$ffproc/exe" $(sudo ls -l "$ffproc"/map_files/ | awk '/\.so/ {print $NF}' | sort -u) | grep 'repz ret' -c objdump: '(deleted)': No such file # I forgot to restart firefox after the libexpat security update 9649 

Questo vale per tutte le funzioni di tutte le librerie che firefox ha mappato, non solo per le funzioni che chiama. Ciò è piuttosto rilevante, poiché una minore densità del codice tra le funzioni significa che le chiamate sono distribuite su più pagine di memoria. ITLB e L2-TLB hanno solo un numero limitato di voci. La densità locale è importante per L1I $ (e la cache uop di Intel). Ad ogni modo, il rep ret ha un impatto molto piccolo.

Mi ci è voluto un minuto per pensare a una ragione per cui /proc//map_files/ non è accessibile al proprietario del processo, ma /proc//maps è. Se un UID = processo root (ad esempio da un file binario suid-root) mmap(2) sa 0666 che si trova in una directory 0700, quindi setuid(nobody) , chiunque esegua quel file binario potrebbe bypassare la restrizione di accesso imposta dalla mancanza di x for other permessi sulla directory.