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

9

我正在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数组大小,它是没问题的,并且在不调用该函数的情况下拷贝是成功的。

更新:最终我发现应该使用带有PROT_EXEC标志的mprotect系统调用将内存段标记为可执行。此外,根据POSIX规范,内存段应通过mmap函数返回。

#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选项编译此示例。


1
几乎可以确定是与以下错误相同:https://dev59.com/MlvUa4cB1Zd3GeqPsE7S#7432634 - Flexo
1
你能解释一下为什么你认为它不会出现段错误吗?在我看来,如果你尝试执行一个不指向代码的随机指针,它肯定会出现段错误。 - David Schwartz
指针指向myfun的代码副本,因为我将这段代码复制到了data数组中。 - Alexander Rodin
编译时,请始终启用所有警告(对于gcc,至少使用:-Wall -Wextra -pedantic),然后修复这些警告。发布的代码会发出一整串警告。 - user3629249
我使用了类似的代码,这使得我的程序几乎可以工作。我将它移植到了C++,我可以编辑变量并在函数中返回它们,但是我无法从“myfun”函数中执行其他函数。它们会导致段错误。有什么解决方法吗? - Nuclear_Man_D
3个回答

5

以下是几个问题:


1
我考虑了第二个和第三个问题,认为在这个玩具示例中,这些问题不应该影响。我想了解数据和代码段之间的区别。如果我用 malloc 分配代替堆栈数组,则结果相同。因此,我想找出在 Linux 中是否有一种方法可以在运行时将内存段标记为“代码”,而不是“数据”。 - Alexander Rodin
@a-rodin; 有一种方法可以在运行时将内存段标记为代码,但肯定不是你尝试的方式。 - haccks
我认为我已经解决了问题更新中的这三个问题。唯一的问题是,我不确定可执行文件中的函数顺序是否与源文件中的顺序相同,而且我没有找到任何确保它的文档。 - Alexander Rodin

2

除了Diask的答案,您可能还想使用一些JIT编译技术(在内存中生成可执行代码),并确保包含代码的内存区域是可执行的(参见mprotect(2)NX位;通常调用堆栈出于安全原因不可执行)。您可以使用GNU lightning(快速发出慢机器代码)、asmjitlibjitLLVMGCCJIT(能够缓慢地发出经过优化的快速机器代码)。您还可以在某个临时文件/tmp/emittedcode.c中发出一些C代码,fork一个编译命令gcc -Wall -O -fPIC -shared /tmp/emittedcode.c -o /tmp/emittedcode.so,然后dlopen(3)该共享对象/tmp/emittedcode.so并使用dlsym(3)按名称查找函数指针。
另请参见 thisthisthisthisthat 的答案。阅读关于 弹簧代码闭包、以及 延续 & CPS 的内容。
当然,将代码从一个区域复制到另一个区域通常是行不通的(必须是位置无关代码才能实现,或者您需要自己的重定位机制,有点像链接器所做的那样)。

0

这是因为这一行代码有误:

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

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

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

例如:

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

1
是的,它有固定的地址,这个地址应该指向 myfun 代码的副本。这不是一个真实的例子,我只是试图弄清楚为什么不能执行程序自己放到内存中的任意代码。 - Alexander Rodin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接