在内存中复制包含静态变量的函数

问题描述

我弄乱了函数指针,我想要做的是在内存中复制一个函数,因此我使用 mmap 来执行我的内存,然后 {{1 }} 复制我的新指针中的函数,我想通过这样做实现的是在我的函数内部创建一个静态变量的新实例。问题是我在复制函数时做错了,因为当我尝试调用函数时,它会出现段错误代码如下:

memcpy

解决方法

正如评论中所指出的,这行不通,因为只有一个 i 变量实例存储在 .bss segment 中,所有 static 变量都是如此。因此,即使 foo 的代码以这样一种方式复制,使其可以在不导致分段错误的情况下运行,它也会简单地从 1 增加到 10。

这个问题更有趣的部分是那些分段错误,以及它们发生的原因。

为了考虑发生了什么,让我们看看 foo 函数的一些(相对未优化的)x86-64 汇编输出,原始源交错:

00000000000011c9 <foo>:
# void    foo(void)
# {
    11c9:       f3 0f 1e fa             endbr64
    11cd:       55                      push   rbp
    11ce:       48 89 e5                mov    rbp,rsp
#     static int i = 0;
#
#     i++;
    11d1:       8b 05 3d 2e 00 00       mov    eax,DWORD PTR [rip+0x2e3d]        # 4014 <i.3666>
    11d7:       83 c0 01                add    eax,0x1
    11da:       89 05 34 2e 00 00       mov    DWORD PTR [rip+0x2e34],eax        # 4014 <i.3666>
#     printf("%d\n",i);
    11e0:       8b 05 2e 2e 00 00       mov    eax,DWORD PTR [rip+0x2e2e]        # 4014 <i.3666>
    11e6:       89 c6                   mov    esi,eax
    11e8:       48 8d 3d 15 0e 00 00    lea    rdi,[rip+0xe15]        # 2004 <_IO_stdin_used+0x4>
    11ef:       b8 00 00 00 00          mov    eax,0x0
    11f4:       e8 b7 fe ff ff          call   10b0 <printf@plt>
# }
    11f9:       90                      nop
    11fa:       5d                      pop    rbp
    11fb:       c3                      ret

在地址 0x11d1 处,MOV instruction 的相对形式用于将 i 的当前值加载到 eax 寄存器中。如您所见,助记符的形式如下:

mov    eax,DWORD PTR [rip+<offset>]

可以理解为:

  • 根据当前指令指针 (rip) 加上提供的偏移量计算地址,然后
  • 将存储在那里的 32 位值 (DWORD) 复制到 eax 寄存器中

这个偏移值(0x2e3d)是在编译时计算的,所以当指令被执行时,rip正好比静态0x2e3d在内存中的位置小i }} 多变的。当您在动态位置使用 foommap 复制 memcpy 时,复制的偏移量保持不变,但该指令时 rip 的值执行将显着不同。

更重要的是,rip+0x2e3d 的计算地址可能会在未标记为进程可用的内存区域中结束,访问该地址将导致分段错误。在 0x11da0x11e0 的后续访问将导致相同的问题。

继续下去,即使您将静态变量 i 更改为局部堆栈变量,您仍然会遇到问题,如地址 0x11f4 处 {{3} 的相对形式} 用于调用printf,偏移量为0x10b0,同样会导致分段错误。

如果您能够纠正这一点,那么 foo 函数中还有一个相对寻址计算,在地址 0x11e8,它使用 CALL instruction 计算 "%d\n" 的地址{1}} 格式字符串。计算本身没有问题,但是对该计算地址的任何访问也可能导致分段错误。