动态链接一个简单的二进制文件

时间:2019-03-31 18:37:48

标签: c linker

我想编写自己的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 ...二进制文件。

1 个答案:

答案 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
        );