【问题标题】:Linux: executing code that is loaded to memory manuallyLinux:执行手动加载到内存的代码
【发布时间】:2016-04-02 01:55:48
【问题描述】:

我正在 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:最后我发现应该使用带有PROT_EXEC 标志的mprotect 系统调用将内存段标记为可执行。此外,内存段应由 POSIX 规范中所述的mmap 函数返回。 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 选项,以确保函数中的代码与位置无关。

【问题讨论】:

  • 几乎可以肯定与以下错误相同:stackoverflow.com/a/7432634/168175
  • 您能否解释一下为什么您希望它不会出现段错误?对我来说很明显,如果您尝试执行一个不指向代码的随机指针,它将出现段错误。
  • 指针指向myfun的代码副本,因为我将此代码复制到data数组中。
  • 编译时,始终启用所有警告。 (对于 gcc,至少使用:-Wall -Wextra -pedantic)然后修复这些警告。发布的代码会发出一整串警告。
  • 我使用了类似的代码,这使我的程序几乎可以运行。我将它移植到 c++,我可以编辑变量并在函数中返回它们,但我无法从“myfun”函数执行其他函数。它们会导致段错误。有什么办法吗?

标签: c linux


【解决方案1】:

除了Diask's answer,您可能还想使用一些JIT compilation 技术(在内存中生成可执行代码),并且您应该确保包含代码的内存区域是可执行的(参见mprotect(2) 和@ 987654324@;出于安全原因,调用堆栈通常不可执行)。您可以使用GNU lightning(快速发出慢速机器码)、asmjitlibjitLLVMGCCJIT(能够缓慢发出快速优化的机器码)。您还可以在某个临时文件 /tmp/emittedcode.c 中生成一些 C 代码,派生编译命令 gcc -Wall -O -fPIC -shared /tmp/emittedcode.c -o /tmp/emittedcode.so 然后 dlopen(3) 共享对象 /tmp/emittedcode.so 并使用 dlsym(3) 在那里按名称查找函数指针。

另请参阅thisthisthisthisthat 答案。了解trampoline codeclosurescontinuationsCPS

当然,将代码从一个区域复制到另一个区域通常是行不通的(它必须是 position independent code 才能使其工作,或者您需要自己的 relocation 机器,有点像 linker 那样) .

【讨论】:

    【解决方案2】:

    有几个问题:

    【讨论】:

    • 我想到了第二个和第三个问题,认为在这个玩具示例中这些问题应该不会影响。数据和代码段之间的区别是我想了解的。如果我将堆栈数组替换为由malloc 分配的结果是相同的。所以我想知道Linux中有没有办法在运行时将内存段标记为“代码”,而不是“数据”。
    • @a-rodin;有一种方法可以在运行时将内存段标记为代码,但肯定不是您尝试的方式。
    • 我想我在问题更新中解决了所有这三个问题。唯一的问题是我不确定可执行文件中的函数是否与源文件中的函数具有相同的顺序,并且我没有找到任何可以确保它的文档。
    【解决方案3】:

    因为这行写错了:

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

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

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

    例子:

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

    【讨论】:

    • 是的,它有固定的地址,这个地址应该指向myfun代码的副本。这不是一个现实生活中的例子,我只是想弄清楚为什么我不能执行程序自行放入内存的任意代码。
    猜你喜欢
    • 2021-04-28
    • 2017-03-12
    • 1970-01-01
    • 1970-01-01
    • 2015-04-11
    • 2017-06-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多