c - 为什么每次在 GDB 中构建 + 反汇编函数时都会得到相同的地址?

标签 c gcc assembly gdb disassembly

为什么每次反汇编一个函数,总是得到相同的指令地址和常量地址?

例如,执行以下命令后,

gcc -o hello hello.c -ggdb
gdb hello
(gdb) disassemble main

转储代码将是:

dump code

当我退出 gdb 并重新反汇编 main 函数时,我会得到和以前一样的结果。对于 gdb 中的每个反汇编命令,指令地址甚至常量地址总是相同的。这是为什么?编译后的文件 hello 是否包含关于每条汇编指令的地址以及常量地址的某些信息?

最佳答案

如果您创建了一个与位置无关的可执行文件(例如,使用 gcc -fpie -pie, which is the default for gcc in many recent Linux distros ),内核将随机化它映射您的可执行文件的地址。 (在 GDB 下运行时除外:GDB disables ASLR by default,即使对于共享库和 PIE 可执行文件也是如此。)


但是您正在制作一个位置相关可执行文件,它可以利用静态地址作为链接时常量(通过将它们用作立即数等等,而不需要运行时重定位修复)。例如您或编译器可以使用 mov $msg, %edi (如您的代码)而不是 lea msg, %rdi (使用 -fpie) .

常规(位置相关)可执行文件在 ELF header 中设置了加载地址:使用 readelf -a ./a.out 查看 ELF 元数据。

即使不在 GDB 下运行,非 PIE 可执行文件也会在每次同时加载,位于 ELF 程序头中指定的地址。 (gcc/ld 在 x86-64-linux-elf 上默认选择 0x400000;您可以使用链接器脚本更改此设置)。硬编码到代码+数据中的所有静态地址的重定位信息不可用,因此加载程序即使想要修复地址也无法修复。

例如在一个简单的可执行文件中(只有一个文本段,没有数据或 bss)我用 -no-pie 构建(这似乎是你的 gcc 中的默认值):

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
                 0x00000000000000c5 0x00000000000000c5  R E    0x200000

 Section to Segment mapping:
  Segment Sections...
   00     .text 

所以 ELF 头请求将文件中的偏移量 0 映射到虚拟地址 0x0000000000400000。 (并且 ELF 入口点是 0x400080;这就是 _start 所在的位置。)我不确定 PhysAddr = VirtAddr 的相关性是什么;用户空间可执行文件不知道也不能轻易找出内核用于支持其虚拟内存的 RAM 页面的物理地址,并且它可以在页面换入/换出时随时更改。

注意 readelf 会自动换行;注意有两行列标题。 0x200000 是那个 LOADed 段的对齐列。

关于c - 为什么每次在 GDB 中构建 + 反汇编函数时都会得到相同的地址?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48366077/

相关文章:

c++ - GCC 中的元组模板

检查 C 库的版本(动态加载)

c++ - 我可以将 stoi 与 GCC 4.4.7 编译器一起使用吗?

linux - 创建一个 ELF 文件,其中 .text 部分之后的部分与 .text 部分非常远

assembly - 在引导加载程序中使用的正确调用约定是什么?

assembly - x86_64 程序集 %rsp 与 %esp

c - 尽可能快地在C中提取两个相似(或不同)字符串之间的字符串

c - 在 C 中,(真的) '<' 比 '!=' 快吗?

c++ - 是否有适用于 C 和 C++ 的 Dart 引擎?

c - scanf 是否保证在失败时不会更改值?