【问题标题】:x86_64 Linux 3.0: Invalid Memory Addressesx86_64 Linux 3.0:无效的内存地址
【发布时间】:2012-01-25 02:06:45
【问题描述】:

x86_64 架构上的 Linux 3.0 上的进程具有 64 位虚拟地址空间。

很明显,0 在这个地址空间中被保证是一个无效的内存地址[见下面的定义],因为它被用来指示一个NULL 指针。

还有哪些其他 64 位数字(如果有)可以保证永远不会是有效的内存地址,为什么?

例如,1 可以是有效地址吗? 2^64-1呢?

定义:“保证是无效的内存地址”是什么意思?

void deref_and_assign(uint64_t i)
{
    char* p = (char*) i;
    *p = 42;
}

就本问题而言,保证的无效内存引用意味着函数deref_and_assign 将始终引发SIGSEGV

【问题讨论】:

  • 出于好奇,您为什么要查找此信息?它可能不是您应该根据它编写代码的信息。对于研究,当然,任何事情都会发生。 ;)
  • 好奇心是好的。我正在实现一个不可移植的动态类型(对于 Linux/x86_64 目标),并且我正在考虑我的选项,以重载前导 64 位值“代码单元”。一种方法是将代码单元划分为 (a) 立即值和 (b) 指向更多信息的指针。作为一个简单的例子,您可以如下表示一个动态数字/字符串类型: (0-4095) 它是一个立即表示为整数 0,1,2...4095 的数字; (4096-2^64) 它是一个指向以空字符结尾的字符串的指针。有关更真实的示例,请查看 ruby​​s VALUE 类型的工作原理。 ruby.tgz 中的 ruby​​.h

标签: linux gcc x86-64


【解决方案1】:

在 x86/64 上,如果启用了页面转换并且虚拟地址 0 处的内存不可访问(因为物理内存映射到虚拟地址空间的方式),则 1 ... 4095 也将不可访问,因为所有这 4096 个地址对应一个内存页,它只能作为一个整体可用或不可用。永远不要在虚拟地址 0 上映射内存是个好主意。不映射它将有助于捕获许多 NULL 指针取消引用。此处的 CPU 将在未映射的位置或需要比当前执行代码更高权限的位置上生成页面错误(又名 #PF)。

在 64 位模式下,CPU 实现的虚拟地址位可能少于 (48+) 个,并且 64 位地址必须在未实现的位中包含全零或全一(值,0 或 1 , 必须与最高有效地址位的值相同,所有这些都可以解释为地址符号扩展)。这样的地址被称为规范地址。如果您尝试使用非规范地址读取或写入内存,则会出现一般保护错误 (AKA #GP)。

因此,根据操作系统(实际上,取决于其内存布局)和实际 CPU,您可能会提出“无效”内存地址范围。如果您尝试从用户模式应用程序读取/写入内核的内存,您将得到#PF。如果您尝试读/写未映射的内存(例如,在地址 0 到 4095 处),您将得到#PF。如果您尝试在非规范地址读取/写入,您将获得#GP。

这就是你要找的东西吗?

【讨论】:

  • 是的,0..4095明确表示首页不存在,谢谢。 48位模式不清楚。你能举一个 x86_64/Linux CPU 的例子吗?例如,在我的机器上,我在 /proc/*/maps 中看到 vsyscall 映射到 ffffffffff600000-ffffffffff601000,这几乎是 vm 的顶部。另外:您如何“尝试”并从用户态的内核内存中读取?在用户空间中用作内存地址的所有单词都取消对 vm 的引用。
  • ffffffffff600000 不一定是顶部。我已经提到最高未实现位是最高已实现位的副本。因此,如果只实现了 48 位,则只有 ffffff600000 部分是“有意义的”,其余的前 16 位(ffff)只是最重要的“有意义”位的副本,等于 1。跨度>
  • 您可以在内核和用户模式下使用任何您喜欢的地址。但是内存保护机制不会让你从用户模式代码中真正读/写内核内存。没有什么可以阻止您将任意值(地址)加载到例如rbx 并尝试执行mov al, [rbx] 以将rbx 中包含的地址处的字节值读入al。你可以试试。但是如果没有足够的权限,处理器将不会执行该指令。相反,它会生成一个#GP 或#PF,操作系统将处理它,这里唯一有意义的操作是终止应用程序。
  • 我如何知道在我的目标平台(Linux 3.0 / x86_64)上实现了多少虚拟地址位?
  • 我对内核模式与用户模式的理解不同(可能不正确),因为它与内存寻址有关。我认为当一个进程处于用户态(系统调用之外)时,任何取消引用的内存(例如 mov al,[rbx])都被视为进程虚拟地址空间中的虚拟地址,并且 CPU 会查找页表.当进程进入系统调用时,它进入内核模式,然后内存地址是真实的物理地址,但现在我把它写下来,我想我错了/困惑。
【解决方案2】:

您检查了一个 Linux 进程不能 mmapMAP_FIXED 一个从 (void*)0 开始的段。

因此,出于实际目的,您可以放心地假设第一页 0 - 0xfff 永远不会是 mmap-ed(页面的 4Kb 大小取决于处理器和系统,但通常是 4kb)。然后,您可以假设在第一页中取消引用指针(来自 Linux 应用程序内部)给出 SIGSEGV

同样适用于以0xffffffffffffffff 结尾的最后一页(即 2^64-1)

【讨论】:

  • mmap(0, 4096, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_FIXED | MAP_ANONYMOUS, -1, 0) 返回 -1 和 EPERM,所以你错了对不起。
  • 好吧,我相信我更正确,因为我确实写过你可以假设第一页不是mmap-ed。我编辑了我的答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-13
相关资源
最近更新 更多