我喜欢示例,所以我在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 似乎比那聪明多了。
我猜测当指令指针进行较大跳跃(例如调用上面的 mmapped 存储器),CPU 会将 RAM(假设c
甚至驻留在 RAM 中)与指令缓存进行比较,并在不匹配(全部?)时使缓存失效,但我希望能获得更精确的信息。特别地,我想知道这种行为是否可以被认为是可预测的(除了硬件和操作系统的差异),并且是否可靠?
(我可能应该查阅英特尔手册,但那本书有成千上万页,我往往会迷失其中...)
mmap
和奇怪的0b...
二进制语法(不是有效的C语法)正常工作? - R.. GitHub STOP HELPING ICEmmap
是纯 POSIX,但0b...
这个东西看起来像是一些遗留的 DOS 编译器的东西... 我不知道 GCC 居然也有它。 - R.. GitHub STOP HELPING ICE-D_POSIX_C_SOURCE=200809L
或-D_XOPEN_SOURCE=700
的形式指定)才能获得POSIX接口。 - R.. GitHub STOP HELPING ICE