【问题标题】:Address canonical form and pointer arithmetic地址规范形式和指针算法
【发布时间】:2016-12-22 23:55:58
【问题描述】:

在兼容 AMD64 的架构上,地址需要采用规范形式才能被取消引用。

来自Intel manual, section 3.3.7.1

在 64 位模式下,地址被认为是规范形式,如果 地址位 63 到最高有效位由 微架构设置为全 1 或全零。

现在,在当前操作系统和架构上实现的最重要的位是第 47 位。这给我们留下了一个 48 位的地址空间。

特别是当ASLR 被启用时,用户程序可以期望接收到第47 位设置的地址。

如果使用了诸如指针标记之类的优化并且使用高位存储信息,则程序必须确保在取消引用地址之前将第 48 位到第 63 位设置回第 47 位。

但考虑一下这段代码:

int main()
{
    int* intArray = new int[100];

    int* it = intArray;

    // Fill the array with any value.
    for (int i = 0; i < 100; i++)
    {
        *it = 20;
        it++;   
    }

    delete [] intArray;
    return 0;
}

现在考虑intArray 是:

0000 0000 0000 0000 0111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1100

it设置为intArray并增加一次it后,考虑sizeof(int) == 4,会变成:

0000 0000 0000 0000 1000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000

第 47 位以粗体显示。这里发生的是指针算术检索到的第二个指针无效,因为不是规范形式。正确的地址应该是:

1111 1111 1111 1111 1000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000

程序如何处理这个问题?操作系统是否保证永远不会分配地址范围不随第 47 位变化的内存?

【问题讨论】:

  • 请不要直接发布你的家庭作业问题,即使是为了掩饰这一事实,这也是显而易见的。 (仅供参考,通常不会向专家发布问题,而是从定义什么是规范开始的问题开始)
  • a) 任何使用指针标记的人都应该受到他们所遭受的所有痛苦。 b) 通常负地址(解释为负的二进制补码)是为操作系统保留的,与用户无关。
  • @Bruce 这个问题来自我与软件公司团队的另一位成员的讨论。我添加了有关规范寻址的信息,因为许多人不熟悉这个概念。我真的不明白你怎么能如此确定这是家庭作业,但你可以在直接指责我“没有努力掩饰这一事实”之前问过。我希望我有这样的功课。

标签: x86-64 access-violation memory-address pointer-arithmetic aslr


【解决方案1】:

规范地址规则意味着 64 位虚拟地址空间中存在一个巨大的漏洞。 2^47-1 与其上方的下一个有效地址连续,因此单个mmap不会包含任何不可用的64位地址范围.

+----------+
| 2^64-1   |   0xffffffffffffffff
| ...      |
| 2^64-2^47|   0xffff800000000000
+----------+
|          |
| unusable |      not to scale: this part is 2^16 times as large
|          |
+----------+
| 2^47-1   |   0x00007fffffffffff
| ...      |
| 0        |   0x0000000000000000
+----------+

还有规范范围内的大多数内核reserve the high half 供自己使用。例如x86-64 Linux's memory map。无论如何,用户空间只能在连续的低范围内分配,因此间隙的存在是无关紧要的。

操作系统是否保证永远不会分配地址范围不随第 47 位变化的内存?

不完全是。当前硬件支持的 48 位地址空间是一个实现细节。规范地址规则确保未来的系统可以支持更多的虚拟地址位,而不会在很大程度上破坏向后兼容性。

您最多只需要一个兼容标志来让操作系统不为进程提供任何具有不同高位的内存区域。 (就像 Linux 当前的 MAP_32BIT flag for mmap 或进程范围的设置)。这可以支持将高位用于标记并手动重做符号扩展的程序。

未来的硬件将不需要支持任何类型的标志来忽略高地址位,因为高位中的垃圾当前是一个错误。 Intel 5-level paging 增加了另外 9 个虚拟地址位,扩大了规范的高和低半部分。 white paper.

另见Why in 64bit the virtual address are 4 bits short (48bit long) compared with the physical address (52 bit long)?


有趣的事实:Linux 默认将堆栈映射到有效地址较低范围的顶部。 (相关:Why does Linux favor 0x7f mappings?

$ gdb /bin/ls
...
(gdb) b _start
Function "_start" not defined.
Make breakpoint pending on future shared library load? (y or [n]) y
Breakpoint 1 (_start) pending.
(gdb) r
Starting program: /bin/ls

Breakpoint 1, 0x00007ffff7dd9cd0 in _start () from /lib64/ld-linux-x86-64.so.2
(gdb) p $rsp
$1 = (void *) 0x7fffffffd850
(gdb) exit

$ calc
2^47-1
              0x7fffffffffff

(现代 GDB 可以在第一条用户空间指令执行之前使用starti 中断,而不是乱用断点命令。)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-06
    • 1970-01-01
    • 2020-12-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多