我正在为 x86_64 编写一个 JIT 重新编译器,有时发出的代码需要从编译的二进制文件中调用函数。
由于 ASLR,我的程序的 .text 段被放置在某个随机地址。
我是否可以分配 32 MiB 的 JIT 代码缓存,使其地址始终足够接近 .text 段,以便我可以安全地发出相对调用而不是绝对调用?我在Linux下。
就像评论所说,最好不要依赖相对的调用。
您可以使用
mmap
请求某个地址的内存映射。问题是知道哪些地址是免费的。
这样的东西可以工作:
#include <stdio.h>
#include <sys/mman.h>
#include <unistd.h>
extern char __executable_start[];
extern char __etext[];
extern char _end[];
#define OFFSET (32 * 1024 * 1024)
#define SIZE (32 * 1024 * 1024)
int main()
{
printf(".text start = %p\n", (void*)__executable_start);
printf(".text end = %p\n", (void*)__etext);
printf("binary end = %p\n", (void*)_end);
char* buffer = mmap(_end + OFFSET, SIZE, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
printf("buffer = %p\n", buffer);
printf("distance = %d\n", buffer - __etext);
}
但是,它并不能“保证”有效。如果请求的区域已经包含映射,内核可能会忽略您的提示(事实上,Linux 似乎这样做)。 如果你真的想这样做,我建议你尝试分配接近
.text
的内存 - 如果成功,则使用相对调用或绝对调用。
上述程序的更好版本是:#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <sys/mman.h>
#include <unistd.h>
extern char __executable_start[];
extern char __etext[];
extern char _end[];
#define OFFSET (32 * 1024 * 1024)
#define SIZE (32 * 1024 * 1024)
#define MAX_ATTEMPTS 10
int main()
{
printf(".text start = %p\n", (void*)__executable_start);
printf(".text end = %p\n", (void*)__etext);
printf("end = %p\n", (void*)_end);
// align to next page
char* buffer = (char*)(((uintptr_t)_end + 4095) & ~4095);
int attempts;
for (attempts = 0; attempts < MAX_ATTEMPTS; attempts++)
{
char* result = mmap(buffer, SIZE, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE, -1, 0);
if (result != MAP_FAILED)
{
printf("success at attempt #%d\n", attempts + 1);
break;
}
if (errno != EEXIST)
{
return 1;
}
buffer += OFFSET;
}
if (attempts == MAX_ATTEMPTS)
{
printf("failed after %d attempts, falling back to absolute instructions\n", attempts);
buffer = mmap(NULL, SIZE, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
}
printf("buffer = %p\n", buffer);
printf("distance = %d\n", buffer - __etext);
}
现在,我们告诉内核我们希望映射到一个确切的地址。如果那里已经存在映射,我们会尝试另一个地址。如果失败一定次数,我们就可以退回到绝对指令。
或者,您可以解析
/proc/self/maps
并找到靠近
.text
的空闲区域。但是,首先,我会尝试对绝对调用是否实际上比相对调用慢进行基准测试。您可能不需要关心。