问题描述
我弄乱了函数指针,我想要做的是在内存中复制一个函数,因此我使用 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
}} 多变的。当您在动态位置使用 foo
和 mmap
复制 memcpy
时,复制的偏移量保持不变,但该指令时 rip
的值执行将显着不同。
更重要的是,rip+0x2e3d
的计算地址可能会在未标记为进程可用的内存区域中结束,访问该地址将导致分段错误。在 0x11da
和 0x11e0
的后续访问将导致相同的问题。
继续下去,即使您将静态变量 i
更改为局部堆栈变量,您仍然会遇到问题,如地址 0x11f4
处 {{3} 的相对形式} 用于调用printf
,偏移量为0x10b0
,同样会导致分段错误。
如果您能够纠正这一点,那么 foo
函数中还有一个相对寻址计算,在地址 0x11e8
,它使用 CALL
instruction 计算 "%d\n"
的地址{1}} 格式字符串。计算本身没有问题,但是对该计算地址的任何访问也可能导致分段错误。