我想编写自己的ld.so,并且要逐步进行。我找不到有关如何编写ld.so的任何“指南”,所以我想自己做。我以为我会先尝试在内存中加载一个简单的二进制文件,如下所示;然后叫它。这非常简单,并且已经无法正常工作。
二进制文件是:
section .text
global _start
_start:
mov edi, 123
mov eax, 60
syscall
呼叫出口(123):
$ nasm -f elf64 bin.asm && ld bin.o && ./a.out; echo $?
$ 123
加载程序:
FILE *fp = fopen(argv[1], "r");
if (!fp) {
fprintf(stderr, "cannot open file %s", argv[1]);
return 1;
}
fseek(fp, 0L, SEEK_END);
size_t sz = ftell(fp) + 1;
rewind(fp);
char *contents = malloc(sizeof(char) * sz);
size_t pagesize = getpagesize();
void *base_addr = (void*) (pagesize * (1 << 20));
char *region = mmap(
base_addr,
pagesize,
PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_ANON | MAP_PRIVATE,
0, 0
);
if (region == MAP_FAILED) {
fprintf(stderr, "could not mmap");
return 1;
}
for (int i = 1, nread = 0; nread != sz * sizeof(char) && i > 0; nread += i) {
i = fread(contents, sizeof(char), sz, fp);
}
contents[sz - 1] = 0;
if (ferror(fp)) {
fprintf(stderr, "error reading file %s", argv[1]);
return 1;
}
memcpy(region, contents, sz);
if (mprotect(region, pagesize, PROT_READ | PROT_EXEC)) {
fprintf(stderr, "mprotect failed");
return 1;
}
return ((int (*)()) base_addr)();
我认为会发生什么:my_linker->内存中的二进制文件->调用mov edi, 123
,返回123。
会发生什么:“ SIGSEGV位于地址0x0”
我正在Linux x86_64上运行它。
编辑:响应@Ctx。 memcpy
,而不是strncpy
。
我应该声明已清除。我正在运行nasm -f elf...
,以表明它可以达到预期的效果。作为程序参数,请nasm -f bin -o prog.bin ...
二进制文件。
答案 0 :(得分:0)
两个主要问题:
不当使用strncpy()
在这里,您使用strncpy()
将二进制代码复制到您的mmap()
页面中:
strncpy(region, contents, sz);
但是strncpy()
在第一个零字节处停止复制,并且二进制文件中可能有一个很早的字节。您必须使用memcpy()
来完成此任务!
第二个问题:
ELF格式
您假设代码从二进制文件的开头开始。但是这里
$ nasm -f elf64 bin.asm && ld bin.asm && ./a.out; echo $?
您正在将其链接到ELF格式的二进制文件。因此,它从ELF标头开始,而不是代码。本质上有两种可能性:从ELF标头计算偏移量,或使用objcopy
从二进制文件中提取纯代码:
objcopy -O binary -j text a.out bin
编辑:您尝试使用
nasm -f bin -o prog.bin bin.asm
但是默认情况下会生成 16位代码。您必须明确声明
bits 64
在汇编源文件中以获取64位代码。
为什么要使用fread()/ memcpy()
在缓存中使用fread()
并在之后使用memcpy()
并没有多大意义,您可以直接将二进制文件mmap()
放入内存中而不读取它。
char *region = mmap(
base_addr,
sz,
PROT_READ | PROT_EXEC,
MAP_PRIVATE | MAP_FIXED,
fileno(fp), 0
);