【问题标题】:Data always being stored in the same address in elf64 NASM?数据总是存储在 elf64 NASM 中的相同地址?
【发布时间】:2022-12-14 11:46:51
【问题描述】:

我在 NASM 中编写了一个简单的 Hello world 程序,然后出于好奇查看使用 objdump -d。程序如下:

BITS 64
SECTION .text
  GLOBAL _start

_start:
  mov rax, 0x01
  mov rdi, 0x00
  mov rsi, hello_world
  mov rdx, hello_world_len
  syscall

  mov rax, 0x3C
  syscall

SECTION .data
  hello_world: db "Hello, world!", 0x0A
  hello_world_len: equ $-hello_world

当我检查这个程序时,我发现这个程序的实际实现使用 movabs 和十六进制值 0x402000 代替名称,这是有道理的,除了这肯定意味着它知道 'Hello,世界!'每次程序运行时都会存储在0x402000,并且没有引用“Hello, world!” objdump -d hello_world 输出中的任何位置(我在下面提供的输出)。

我试着重写程序;这次我用 mov rsi, 0x402000 替换了第 8 行的 hello_world 并且程序仍然可以编译并完美运行。

我认为这可能是名称的某种编码,但是更改SECTION .data 中的文本“hello_world”也没有改变结果。

我比任何事情都更困惑 - 它如何在编译时知道地址,为什么它永远不会改变,即使在重新编译时也是如此?

objdump -d hello_world 的输出)

./hello_world:   file format elf64-x86-64

Disassembly of section .text:

0000000000401000 <_start>:
  401000: b8 01 00 00 00       mov    $0x1,%eax
  401005: bf 00 00 00 00       mov    $0x0,%edi
  40100a: 48 be 00 20 40 00 00 movabs $0x402000,%rsi
  401011: 00 00 00
  401014: ba 0e 00 00 00       mov    $0xe,%edx
  401019: 0f 05                syscall
  40101b: b8 3c 00 00 00       mov    $0x3c,%eax
  401020: bf 00 00 00 00       syscall

(如您所见,没有“.data 部分的反汇编”,这让我更加困惑)

【问题讨论】:

  • 该字符串在编译时也是已知的。它静态存在于您的可执行文件中。编译器一开始就放在了地址,当然知道地址了! (在 ASLR 或 dylib 环境中这仍然适用,因为全部相对于模块的地址会根据需要移动,编译器会放置一个重定位条目,这样加载器就知道那里有一个地址引用需要修复,但它们之间的相对关系仍然保持不变。)
  • 数据段的反汇编是矛盾的,数据段通常不包含可被反汇编的指令。
  • 这是虚拟内存,所讨论的内存页面不必物理存在于内存中,它可以根据需要调入和调出,操作系统的内存管理器的工作是决定什么时候在物理内存中保留什么。尝试访问属于物理上不在内存中的页面的地址将使它在那个时间点透明地被内核调入页面。但是对于这么小的程序,很可能整个程序从一开始就在内存中。
  • 在用户模式代码中,您通常永远不会看到物理内存地址。这完全被内核抽象掉了。
  • 也使用objdump -s 转储数据部分。您应该在预期地址找到该字符串。

标签: pointers assembly x86-64 nasm reverse-engineering


【解决方案1】:

该字符串在编译时也是已知的。它静态存在于您的可执行文件中。编译器一开始就放在了地址,当然知道地址了!

(在 ASLR 或 dylib 环境中这仍然适用,因为全部相对于模块的地址会根据需要移动,编译器会放置一个重定位条目,这样加载器就知道那里有一个地址引用需要修复,但它们之间的相对位置仍然相同。)

这并不意味着每个存在的程序都将具有唯一的内存位置,也不意味着程序的所有内容都必须闲置并耗尽所有内存,即使它们很少需要,因为这是虚拟内存.

该地址仅在您自己的进程中有意义,并且所讨论的内存页面不必物理存在于内存中,可以根据需要将其调入和调出,操作系统的内存管理器的工作是决定保留什么物理内存在什么时候。尝试访问属于物理上不在内存中的页面的地址将使它在那个时间点透明地被内核调入页面。但是对于这么小的程序,很可能整个程序从一开始就在内存中。

在用户模式代码中,您通常永远不会看到物理内存地址。这完全被内核抽象掉了。

【讨论】:

  • OP 使用ld 制作非 PIE 可执行文件,因此用于执行运行时修复的信息消失了(并且绝对地址是固定的)。但是,是的,Linux 确实允许在 PIE 可执行文件和共享库中进行文本重定位,因此可以使用 64 位绝对地址。但是使用相对寻址 lea rsi, [rel hello_world] 更有效(尤其是对于 ASLR),因为如您所说,相对的代码和数据之间的距离是一个链接时间常数,与图像基地址的 ASLR 无关。 How to load address of function or label into register
猜你喜欢
  • 2012-05-27
  • 2021-07-11
  • 1970-01-01
  • 2012-11-18
  • 2011-12-29
  • 1970-01-01
  • 1970-01-01
  • 2018-10-12
  • 1970-01-01
相关资源
最近更新 更多