In questo output del compilatore, sto cercando di capire come codificare il codice macchina di nopw
l'istruzione funziona:
00000000004004d0 <main>:
4004d0: eb fe jmp 4004d0 <main>
4004d2: 66 66 66 66 66 2e 0f nopw %cs:0x0(%rax,%rax,1)
4004d9: 1f 84 00 00 00 00 00
C'è qualche discussione su "nopw" su http://john.freml.in/amd64-nopl. Qualcuno può spiegare il significato di 4004d2-4004e0? Dall'elenco dei codici operativi, sembra che 66 ..
i codici sono espansioni multibyte. Sento che probabilmente potrei ottenere una risposta migliore a questo qui di quanto non farei a meno che non provassi a modificare l'elenco di codici operativi per alcune ore.
Quell'output di asm proviene dal seguente (folle) codice in C, che ottimizza fino a un semplice ciclo infinito:
long i = 0;
main() {
recurse();
}
recurse() {
i++;
recurse();
}
Quando compilato con gcc -O2
, il compilatore riconosce la ricorsione infinita e la trasforma in un ciclo infinito; lo fa così bene, infatti, che effettivamente scorre nel main()
senza chiamare il recurse()
funzione.
nota del redattore:le funzioni di riempimento con NOP non sono specifiche per i loop infiniti. Ecco un insieme di funzioni con una gamma di lunghezze di NOP, sull'esploratore del compilatore Godbolt.
Risposte:
Il 0x66
i byte sono un prefisso "Operand-Size Override". Avere più di uno di questi equivale ad averne uno.
Il 0x2e
è un "prefisso nullo" in modalità a 64 bit (è un CS:segment override in caso contrario, motivo per cui viene visualizzato nel mnemonico dell'assembly).
0x0f 0x1f
è un codice operativo a 2 byte per un NOP che accetta un byte ModRM
0x84
è il byte ModRM che in questo caso codifica per una modalità di indirizzamento che utilizza 5 byte in più.
Alcune CPU sono lente a decodificare le istruzioni con molti prefissi (ad esempio più di tre), quindi un byte ModRM che specifica un SIB + disp32 è un modo molto migliore per utilizzare 5 byte in più rispetto a cinque byte di prefisso in più.
In sostanza, quei byte sono una lunga istruzione NOP che non verrà mai eseguita comunque. È lì per garantire che la funzione successiva sia allineata su un limite di 16 byte, perché il compilatore ha emesso un .p2align 4
direttiva, quindi l'assembler ha riempito con un NOP. L'impostazione predefinita di gcc per x86 è
-falign-functions=16
. Per i NOP che verranno eseguiti, la scelta ottimale del NOP lungo dipende dalla microarchitettura. Per una microarchitettura che soffoca su molti prefissi, come Intel Silvermont o AMD K8, due NOP con 3 prefissi ciascuno potrebbero essere decodificati più velocemente.
L'articolo del blog a cui è collegata la domanda ( http://john.freml.in/amd64-nopl ) spiega perché il compilatore utilizza una singola istruzione NOP complicata invece di un gruppo di istruzioni NOP 0x90 a byte singolo.
Puoi trovare i dettagli sulla codifica delle istruzioni nei documenti di riferimento tecnico di AMD:
- http://developer.amd.com/documentation/guides/pages/default.aspx#manuals
Principalmente nel "Manuale del programmatore di architettura AMD64 Volume 3:Istruzioni generali e di sistema". Sono sicuro che i riferimenti tecnici di Intel per l'architettura x64 avranno le stesse informazioni (e potrebbero anche essere più comprensibili).