显然,Linux 不会为您的进程提供不可用的地址,这会使它在尝试执行来自_start 的代码时引发#GP(0) 异常(并因此引发段错误)。 (或者如果接近截止,当它尝试加载或存储 .data 或 .bss 时)
这实际上会发生在最初尝试将 RIP 设置为非规范值的指令上,可能是 iret 或 sysret1。
在具有 48 位虚拟地址的系统上,零到0000_7fff_ffff_ffff 是当表示为符号扩展的 64 位值时,虚拟地址空间的完整下半部分。
在支持 PML5(并由内核使用)的系统上,虚拟地址为 57 位宽,因此
零到00ff_ffff_ffff_ffff 是规范范围的下半部分。
参见https://www.kernel.org/doc/Documentation/x86/x86_64/mm.txt - 第一行是用户空间范围。 (它谈论“56 位”虚拟地址。这是不正确或具有误导性的,PML5 是 57 位,一个额外的完整级别的页表,每级 9 位。所以低半部分是 56 位,第 57 位为 0,而高半部分为 56 位,第 57 位为 1。)
========================================================================================================================
Start addr | Offset | End addr | Size | VM area description
========================================================================================================================
| | | |
0000000000000000 | 0 | 00007fffffffffff | 128 TB | user-space virtual memory, different per mm
__________________|____________|__________________|_________|___________________________________________________________
| | | |
0000800000000000 | +128 TB | ffff7fffffffffff | ~16M TB | ... huge, almost 64 bits wide hole of non-canonical
| | | | virtual memory addresses up to the -128 TB
| | | | starting offset of kernel mappings.
__________________|____________|__________________|_________|___________________________________________________________
|
| Kernel-space virtual memory, shared between all processes:
...
或者对于 PML5:
0000000000000000 | 0 | 00ffffffffffffff | 64 PB | user-space virtual memory, different per mm
__________________|____________|__________________|_________|___________________________________________________________
| | | |
0000800000000000 | +64 PB | ffff7fffffffffff | ~16K PB | ... huge, still almost 64 bits wide hole of non-canonical
| | | | virtual memory addresses up to the -64 PB
| | | | starting offset of kernel mappings.
脚注 1:
正如 prl 所指出的,这种设计允许实现实际上只有 48 个实际位来存储管道中任何位置的 RIP 值,除了跳转和检测有符号溢出以防执行运行到非规范区域的末端。 (也许在必须存储uop的每个地方都保存晶体管,它需要知道自己的地址。)不像你可以跳转/ iret到任意RIP,然后#GP(0)异常必须推送正确的64 位非规范地址,这意味着 CPU 必须暂时记住它。
调试查看您从哪里跳转也更有用,因此以这种方式设计规则是有意义的,因为没有故意跳转到非规范地址的用例。 (与跳转到未映射的页面不同,#PF 异常处理程序可以修复这种情况,例如通过请求分页,因此您希望故障地址是新的 RIP。)
有趣的事实:在 Intel CPU 上使用带有非规范 RIP 的 sysret 将在环 0 (CPL=0) 中使用 #GP(0),因此 RSP 不会切换并且仍然 = 用户堆栈。如果存在任何其他线程,这会让它们弄乱内核用作堆栈的内存。这是 IA-32e 中的设计缺陷,英特尔的 x86-64 实现。这就是为什么 Linux 使用iret 从 syscall 入口点返回到用户空间的原因,如果在此期间在此进程上使用了 ptrace。内核知道一个新进程会有一个安全的 RIP,所以它实际上可能会使用sysret 更快地跳转到用户空间。