我喜欢例子,所以我用c写了一些自修改代码......
#include <stdio.h>
#include <sys/mman.h> // linux
int main(void) {
unsigned char *c = mmap(NULL, 7, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|
MAP_ANONYMOUS, -1, 0); // get executable memory
c[0] = 0b11000111; // mov (x86_64), immediate mode, full-sized (32 bits)
c[1] = 0b11000000; // to register rax (000) which holds the return value
// according to linux x86_64 calling convention
c[6] = 0b11000011; // return
for (c[2] = 0; c[2] < 30; c[2]++) { // incr immediate data after every run
// rest of immediate data (c[3:6]) are already set to 0 by MAP_ANONYMOUS
printf("%d ", ((int (*)(void)) c)()); // cast c to func ptr, call ptr
}
putchar('\n');
return 0;
}
...显然有效:
>>> gcc -Wall -Wextra -std=c11 -D_GNU_SOURCE -o test test.c; ./test
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
但老实说,我根本没想到它会起作用。我希望包含的指令c[2] = 0
在第一次调用时被缓存c
,之后所有连续调用c
都会忽略对的重复更改c
(除非我以某种方式明确地使缓存无效)。幸运的是,我的 cpu 似乎比这更聪明。
c
我猜每当指令指针进行较大的跳转(如上面对 mmapped 内存的调用)时,cpu 都会将 RAM(假设甚至驻留在 RAM 中)与指令缓存进行比较,并在缓存不匹配时使缓存无效(全部?),但我希望能得到更准确的信息。特别是,我想知道这种行为是否可以被认为是可预测的(除非硬件和操作系统有任何差异),并且可以依赖?
(我可能应该参考英特尔手册,但那东西长达数千页,我往往会迷失其中......)