【问题标题】:Does a segmentation fault in gdb show the physical or virtual address?gdb 中的分段错误是否显示物理地址或虚拟地址?
【发布时间】:2020-01-29 20:27:42
【问题描述】:

我试图粉碎堆栈:

int main (void) {

    int ar[5] = {1,2,3,4,5};
    for(int i =0; i<255 ; i++)
        ar[i] = 10;

    return 0;
}

gcc -fno-stack-protector somefile.c。第一个问题:为什么(SIGABRT)和没有(SIGSEGV)保护器的故障存在差异,当两者都访问非法内存时(我认为应该有相同的故障)。 第二当objdump

0000000000001125 <main>:
    1125:   55                      push   %rbp
    1126:   48 89 e5                mov    %rsp,%rbp
    1129:   c7 45 e0 01 00 00 00    movl   $0x1,-0x20(%rbp)
...

主地址的开头是虚拟0000000000001125

但是在没有保护器的情况下编译:

Program received signal SIGSEGV, Segmentation fault.
0x0000000af7be5b6b in ?? ()

地址(0x0000000af7be5b6b)是什么?虚拟的,物理的?我在反汇编文件中看不到它(如上所示),那么地址来自哪里?

编辑: 带保护器(因此有故障SIGABRT),我也不明白的输出:

Program received signal SIGABRT, Aborted.
__GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50

__GI_raise 是什么宏?括号中的 sig=sig@entry=6 是什么,gcc 是否添加了故障表,或者链接器中的这些标记是什么?

【问题讨论】:

  • SIGABRT 由一些检测违规的库函数发送(并且此检测是通过使用堆栈保护器启用的)。 SIGSEGV 由操作系统发送,当 检测到进程正在触摸它不应该触摸的东西。
  • SIGSEGV 没有被任何库处理?还是内核中断器?两者在实现上有何不同?
  • 您尝试过完整的回溯吗?因为你不在主要你在“??”
  • 对于普通进程和通用多用户操作系统,您在进程和调试器中看到的所有地址都是虚拟地址。除非您正在使用内核代码,包括一些设备驱动程序和类似的特权代码,否则您将看不到物理地址,或者已经进行了特殊安排。调试器不会报告物理地址,因为在您不可见的各种系统活动之后,程序中的相同虚拟地址可能会映射到不同的物理地址,因此物理地址通常不可用。
  • 那为什么内核和设备驱动程序(或模块)需要访问物理地址呢?举一些特定(具体)的例子,内核在 IO 操作或启动模式下会使用物理地址端口吗?

标签: c stack buffer-overflow virtual-address-space stack-smash


【解决方案1】:

当两者都访问非法内存时,为什么使用 (SIGABRT) 和没有 (SIGSEGV) 保护器的故障存在差异(我认为应该有相同的故障)。

当你编译堆栈保护器时,你最终只会覆盖你不允许的内存部分,并且程序会通过SIGSEGV信号被操作系统杀死。

但是,当您使用堆栈保护程序进行编译时,libc 可以检测到错误,在这种情况下会调用 __stack_chk_fail() 函数。你可以通过objdump看到这个:

 72d:   b8 00 00 00 00          mov    eax,0x0
 732:   48 8b 55 f8             mov    rdx,QWORD PTR [rbp-0x8]
 736:   64 48 33 14 25 28 00    xor    rdx,QWORD PTR fs:0x28
 73d:   00 00
 73f:   74 05                   je     746 <main+0x76>
 741:   e8 3a fe ff ff          call   580 <__stack_chk_fail@plt>
 746:   c9                      leave
 747:   c3                      ret

然后__stack_chk_fail()函数调用__fortify_fail_abort(),它调用__libc_message()打印错误信息,最后执行abort(),它通过raise()向进程发送SIGABRT信号,杀死它。

主地址的开头是虚拟0000000000001125

错误。这不是一个虚拟地址,它只是二进制文件中的一个偏移,这意味着你看到的代码从二进制文件本身的字节0x1125开始。然后执行二进制文件时,会为程序创建一个虚拟内存区域,该区域从某个(通常是随机的)基本虚拟地址开始。然后,基本虚拟地址将确定main() 和其他所有内容的位置。例如main() 将位于base_virtual_addr + 0x1125。您无法确定程序在 RAM 中加载的真实物理地址,只有操作系统(即内核)知道这一点,而用户空间程序不需要。 p>

你可以通过info proc mappings命令查看你的程序在GDB下运行时的虚拟内存映射,结果如下:

(gdb) info proc mappings
process 11084
Mapped address spaces:

          Start Addr           End Addr       Size     Offset objfile
      0x555555554000     0x555555555000     0x1000        0x0 /home/marco/Desktop/a.out
      0x555555754000     0x555555755000     0x1000        0x0 /home/marco/Desktop/a.out
      0x555555755000     0x555555756000     0x1000     0x1000 /home/marco/Desktop/a.out
      0x7ffff7a3a000     0x7ffff7bcf000   0x195000        0x0 /lib/x86_64-linux-gnu/libc-2.24.so
      0x7ffff7bcf000     0x7ffff7dcf000   0x200000   0x195000 /lib/x86_64-linux-gnu/libc-2.24.so
      0x7ffff7dcf000     0x7ffff7dd3000     0x4000   0x195000 /lib/x86_64-linux-gnu/libc-2.24.so
      0x7ffff7dd3000     0x7ffff7dd5000     0x2000   0x199000 /lib/x86_64-linux-gnu/libc-2.24.so
      0x7ffff7dd5000     0x7ffff7dd9000     0x4000        0x0
      0x7ffff7dd9000     0x7ffff7dfc000    0x23000        0x0 /lib/x86_64-linux-gnu/ld-2.24.so
      0x7ffff7fcf000     0x7ffff7fd1000     0x2000        0x0
      0x7ffff7ff8000     0x7ffff7ffa000     0x2000        0x0 [vvar]
      0x7ffff7ffa000     0x7ffff7ffc000     0x2000        0x0 [vdso]
      0x7ffff7ffc000     0x7ffff7ffd000     0x1000    0x23000 /lib/x86_64-linux-gnu/ld-2.24.so
      0x7ffff7ffd000     0x7ffff7ffe000     0x1000    0x24000 /lib/x86_64-linux-gnu/ld-2.24.so
      0x7ffff7ffe000     0x7ffff7fff000     0x1000        0x0
      0x7ffffffde000     0x7ffffffff000    0x21000        0x0 [stack]
  0xffffffffff600000 0xffffffffff601000     0x1000        0x0 [vsyscall]

在这种情况下,您可以看到我的程序(/home/marco/Desktop/a.out)从虚拟基地址0x555555554000开始。

什么是 __GI_raise 宏?括号中的 sig=sig@entry=6 是什么,gcc 是否添加了故障表,或者链接器中的这些标记是什么?

__GI_raise()raise() 函数的实现。记住?我刚才在谈论堆栈保护器时提到了它。在您的情况下,gdb 在程序被 raise(SIGABRT) 杀死后停止,并显示程序死亡的确切点,该点位于 libc 用于传递信号的 __GI_raise() 内部函数中。

字符串 sig=sig@entry=6 是 GDB 告诉您调用函数时唯一的参数设置为 6 的好方法,这是 SIGABRT 的信号号。

【讨论】:

  • 如何按照 cmets 中的建议在 gdb 中进行完整的回溯(为了查看所有地址,这些地址都被 smacked)?
  • @Herdsman 您可以使用调试符号(-g 标志)进行编译,然后在 GDB 中使用命令 backtrace
  • 我只是想看看,在缓冲区之后的“base + 10 (0xa)”处会出现粉碎(即缓冲区的基础 + 10) - 那应该是那个时候,什么时候出现错误(在这十个字节之后),但我如何在 gdb 和虚拟内存地址中看到它?我没有看到与info proc mappings 的地址有十个差异,那么在从基数偏移 10 个后如何查看故障?
  • @Herdsman 您必须一次一步地逐一阅读组装说明才能看到。见here。没那么简单,程序运行没有问题,然后当它检查堆栈金丝雀时,它意识到它被缓冲区溢出修改并调用__stack_chk_fail()。使用回溯无法轻松返回。
猜你喜欢
  • 1970-01-01
  • 2015-05-04
  • 2013-05-05
  • 1970-01-01
  • 1970-01-01
  • 2019-02-11
  • 2018-10-21
  • 2021-01-17
  • 1970-01-01
相关资源
最近更新 更多