gpt4 book ai didi

c - 堆上的函数体

转载 作者:行者123 更新时间:2023-12-03 21:34:19 25 4
gpt4 key购买 nike

一个程序包含三个部分:文本、数据和堆栈。函数体位于文本部分。我们可以让一个函数体存在于堆上吗?因为我们可以更自由地操作堆上的内存,我们可能会获得更多的操作函数的自由度。

在下面的 C 代码中,我将 hello 函数的文本复制到堆上,然后将一个函数指针指向它。该程序可以通过 gcc 正常编译,但在运行时会出现“Segmentation fault”。

你能告诉我为什么吗?如果我的程序无法修复,你能提供一种让函数在堆上存活的方法吗?谢谢!

图灵机器人

#include "stdio.h"
#include "stdlib.h"
#include "string.h"

void
hello()
{
printf( "Hello World!\n");
}

int main(void)
{
void (*fp)();

int size = 10000; // large enough to contain hello()
char* buffer;
buffer = (char*) malloc ( size );
memcpy( buffer,(char*)hello,size );
fp = buffer;
fp();
free (buffer);

return 0;
}

最佳答案

我下面的示例适用于带有 gcc 的 Linux x86_64,但类似的注意事项也适用于其他系统。

Can we let a function body live on heap?

是的,我们绝对可以。但通常这被称为 JIT(即时)编译。参见 this基本思想。

Because we can manipulate memory on heap more freely, we may gain more freedom to manipulate functions.

没错,这就是为什么像 JavaScript 这样的高级语言有 JIT 编译器的原因。

In the following C code, I copy the text of hello function onto heap and then point a function pointer to it. The program compiles fine by gcc but gives "Segmentation fault" when running.

实际上,您在该代码中有多个"Segmentation fault"

第一个来自这一行:

 int size = 10000;     //  large enough to contain hello()

如果您看到 x86_64 机器代码由您的 gcc 生成hello 函数,它编译后只有 17 个字节:

0000000000400626 <hello>:
400626: 55 push %rbp
400627: 48 89 e5 mov %rsp,%rbp
40062a: bf 98 07 40 00 mov $0x400798,%edi
40062f: e8 9c fe ff ff call 4004d0 <puts@plt>
400634: 90 nop
400635: 5d pop %rbp
400636: c3 retq

因此,当您尝试复制 10,000 字节时,您会遇到内存不存在并得到 "Segmentation fault"

其次,您使用 malloc 分配内存,这为您提供了一片受 CPU 保护以防止在 Linux x86_64 上执行的内存,因此这会给你另一个“Segmentation fault”

在底层,malloc 使用brksbrkmmap 等系统调用来分配内存。您需要做的是使用带有PROT_EXEC 保护的mmap 系统调用分配可执行 内存。

第三,当 gcc 编译你的 hello 函数时,你真的不知道它将使用什么优化以及生成的机器代码是什么样的。

例如,如果您看到已编译的 hello 函数的第 4 行

40062f: e8 9c fe ff ff          call  4004d0 <puts@plt>

gcc 优化它以使用 puts 函数而不是 printf,但那是甚至不是主要问题。

x86 架构上,您通常使用 call 调用函数部件助记符,但是,它不是一条指令,call 可以编译成许多不同的机器指令,参见Intel manual页卷。 2A 3-123,供引用。

在您的情况下,编译器已选择对call 汇编指令使用相对 寻址。

你可以看到,因为你的 call 指令有 e8 操作码:

E8 - Call near, relative, displacement relative to next instruction. 32-bit displacement sign extended to 64-bits in 64-bit mode.

这基本上意味着指令指针将从当前指令指针跳转相对字节数。

现在,当您使用memcpy重新定位您的代码到堆中时,您只需复制相关的调用,它现在将跳转指令指针相对于您将代码复制到堆中的位置,并且该内存很可能不存在并且您将得到另一个“Segmentation fault”

If my program can not be repaired, could you provide a way to let a function live on heap? Thanks!

下面是一个工作代码,这是我所做的:

  1. 执行 printf 一次以确保 gcc 将其包含在我们的二进制文件中。
  2. 将正确大小的字节复制到堆中,以免访问不存在的内存。
  3. 使用 mmapPROT_EXEC 选项分配可执行内存。
  4. printf 函数作为参数传递给我们的 heap_function 以确保gcccall 指令使用绝对跳转。

这是一个工作代码:

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


typedef int (*printf_t)(char* format, char* string);
typedef int (*heap_function_t)(printf_t myprintf, char* str, int a, int b);


int heap_function(printf_t myprintf, char* str, int a, int b) {
myprintf("%s", str);
return a + b;
}

int heap_function_end() {
return 0;
}


int main(void) {
// By printing something here, `gcc` will include `printf`
// function at some address (`0x4004d0` in my case) in our binary,
// with `printf_t` two argument signature.
printf("%s", "Just including printf in binary\n");

// Allocate the correct size of
// executable `PROT_EXEC` memory.
size_t size = (size_t) ((intptr_t) heap_function_end - (intptr_t) heap_function);
char* buffer = (char*) mmap(0, (size_t) size,
PROT_EXEC | PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
memcpy(buffer, (char*)heap_function, size);

// Call our function
heap_function_t fp = (heap_function_t) buffer;
int res = fp((void*) printf, "Hello world, from heap!\n", 1, 2);
printf("a + b = %i\n", res);
}

保存在 main.c 中并运行:

gcc -o main main.c && ./main

关于c - 堆上的函数体,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5357371/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com