【问题标题】:Using r8 register as a loop counter results in endless loop - why?使用 r8 寄存器作为循环计数器会导致无限循环 - 为什么?
【发布时间】:2016-11-25 02:15:58
【问题描述】:

以下代码使用 rsi 寄存器作为循环计数器打印 hello world 10 次。

section .data
    hello:     db 'Hello world!',10   
    helloLen:  equ $-hello             

section .text
    global _start

_start:
    mov rsi, 0                 ;<--- use r8 here

do_loop:
    inc rsi                    ;<--- use r8 here

    ;print hello world
    mov eax,4             
    mov ebx,1            
    mov ecx,hello       
    mov edx,helloLen     

    int 80h              

    cmp rsi, 10                ;<--- use r8 here
    jnz do_loop

    ;system exit
    mov eax,1            ; The system call for exit (sys_exit)
    mov ebx,0            ; Exit with return code of 0 (no error)
    int 80h;

如果我尝试使用 r8 寄存器而不是 rsi 作为循环计数器,则会导致无限循环。这里的 r8 寄存器只是一个例子。它也发生在寄存器 r9、r10 上。

谁能解释一下,因为我认为这些都是通用寄存器,应该允许你使用它们?

【问题讨论】:

  • 您在 64 位模式下使用 32 位系统调用。坏主意。
  • 有趣。当从 64 位代码调用时,也许 Linux 的 int 0x80 32 位 ABI 会破坏高位寄存器?在 Linux 中,syscall 破坏了 rcx 和 r11,但保留了其他所有内容(当然,返回值的 rax 除外)。通常人们在尝试传递不适合 32 位的指针时会遇到int 0x80 ABI 的问题。 (导致-EFAULT
  • 尝试使用 layout reg 在 gdb 中单步执行以突出显示更改的 regs。 (见底部x86 tag wiki
  • 不仅高位,而且 64 位额外寄存器作为一个整体没有被 32 位系统调用保存,原因很明显。几乎没有“奇怪”。
  • 好吧,情况似乎和我想象的不一样,compatibility int 80 handler 实际上将r8r11 的寄存器显式归零,并保留了其他寄存器的全部 64 位(rax 除外这当然是返回值)。

标签: linux assembly x86-64 nasm system-calls


【解决方案1】:

TL;DR : int 0x80 隐式归零 R8, R9R10R11 在 64 位 Linux 系统上返回用户态代码之前。此行为发生在 2.6.32-rc1 之后的内核上。对于首选的 64 位 SYSCALL 调用约定,情况并非如此。


您正在体验 2.6.32-rc1 版本之后的 Linux 内核的特殊性。对于 Linux 内核版本 R8、R9R10R11 现在是当内核从int 0x80 返回时归零。

您可能认为这些寄存器在兼容模式(32 位代码)下无关紧要,因为这些较新的寄存器不可用。这是一个错误的假设,因为 32 位应用程序可以切换到 64 位长模式并访问这些寄存器。发现此问题的Linux Kernel Mailing List post 表示:

x86:不要将 64 位内核寄存器值泄露给 32 位进程

虽然 32 位进程不能直接访问 R8...R15,但它们可以获得 通过暂时将自己切换到这些寄存器来访问 64 位模式。

Jon Oberheide 提供了在早期内核上演示寄存器泄漏的代码。它创建一个 32 位应用程序以在启用 IA32 兼容性的 x86-64 系统上运行。程序切换到 64 位长模式,然后将寄存器 R8-R11 存储到兼容模式(32 位模式)下可用的通用寄存器中。 John 在article 中讨论了具体细节。他在这段摘录中很好地总结了漏洞和内核修复:

漏洞

导致此漏洞的根本问题是缺乏 从一个返回时清零几个 x86-64 寄存器 系统调用。 32 位应用程序可能能够切换到 64 位模式 并访问 r8、r9、r10 和 r11 寄存器以泄露它们之前的 价值观。此问题由 Jan Beulich 发现并修复 10 月 1 日。 解决方法显然是将这些寄存器归零以 避免向用户空间泄露任何信息


如果您要在像 GDB 这样的调试器中单步调试您的代码,您应该会发现 R8 实际上在int 0x80 之后设置为零。由于它是您的循环计数器,因此您的程序最终会无限循环打印Hello world!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-06-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多