Linux:执行手动加载到内存的代码

时间:2015-12-28 08:01:37

标签: c linux

我正在使用Linux上的函数指针并尝试执行此C程序:

#include <stdio.h>
#include <string.h>

int myfun() 
{
    return 42;
}

int main()
{
    char data[500];
    memcpy(data, myfun, sizeof(data));
    int (*fun_pointer)() = (void*)data;
    printf("%d\n", fun_pointer());

    return 0;
}

不幸的是,它会在fun_pointer()电话上发生段错误。我怀疑它与一些内存标志有关,但我没有找到有关它的信息。

你能解释为什么这段代码会出现错误吗?不要看到固定的data数组大小,没关系,无需调用函数即可成功。

UPD :最后我发现应使用mprotect系统调用PROT_EXEC标记将内存段标记为可执行文件。此外,内存段应由mmap函数返回,如POSIX规范中所述。 有mmap内存使用PROT_EXEC标志分配的相同代码(并且有效):

#include <stdio.h>
#include <string.h>
#include <sys/mman.h>

int myfun() 
{
    return 42;
}

int main()
{
    size_t size = (char*)main - (char*)myfun;
    char *data = mmap(NULL, size, PROT_EXEC | PROT_READ | PROT_WRITE,
        MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
    memcpy(data, myfun, size);

    int (*fun_pointer)() = (void*)data;
    printf("%d\n", fun_pointer());

    munmap(data, size);
    return 0;
}

此示例应符合-fPIC gcc选项,以确保函数中的代码与位置无关。

3 个答案:

答案 0 :(得分:5)

那里有几个问题:

答案 1 :(得分:2)

除了Diask's answer之外,你可能还想使用一些JIT compilation技术(在内存中生成可执行代码),你应该确保包含代码的内存区域是可执行的(参见{{ 3}}和mprotect(2);出于安全原因,调用堆栈通常不可执行。您可以使用NX bit(快速发出慢速机器代码),GNU lightningasmjitlibjitLLVM(能够慢慢发出快速优化的机器代码)。您还可以在某个临时文件/tmp/emittedcode.c中发出一些C代码,分叉编译命令gcc -Wall -O -fPIC -shared /tmp/emittedcode.c -o /tmp/emittedcode.so然后GCCJIT共享对象/tmp/emittedcode.so并使用dlopen(3)查找功能在那里用他们的名字指示。

另请参阅dlsym(3)thisthisthisthis个答案。阅读thattrampoline codeclosures&amp; continuations

当然,将代码从一个区域复制到另一个区域通常不起作用(必须CPS才能使其工作,或者您需要自己的position independent code机制,有点像{ {3}}确实如此。

答案 2 :(得分:0)

这是因为这条线错了:

memcpy(data, myfun, sizeof(data));

您正在复制函数的代码(已编译)而不是函数的地址。

myfun和&amp; myfun将拥有相同的地址,因此要进行memcpy操作,您必须使用函数指针,然后从其地址复制。

示例:

int (*p)(); 
p = myfun; 
memcpy(data, &p, sizeof(data));