使用r8寄存器作为循环计数器会导致无限循环 - 为什么?

Den*_*nis 2 linux assembly x86-64 nasm system-calls

以下代码使用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;
Run Code Online (Sandbox Code Playgroud)

如果我试图使用r8寄存器而不是rsi作为循环计数器,则会导致无限循环.这里的r8寄存器只是一个例子.它也适用于寄存器r9,r10.

有人可以解释一下,因为我认为这些都是通用寄存器,你应该被允许使用它们吗?

Mic*_*tch 6

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


在2.6.32-rc1版本之后,您正在体验Linux内核的特性.对于Linux内核版本<= 2.6.32-rc1,您可能会得到您期望的行为.由于信息泄漏错误(和漏洞),当内核从a返回时,寄存器R8,R9,R10和R11现在归零int 0x80.

您可能认为这些寄存器在兼容模式(32位代码)中无关紧要,因为那些较新的寄存器不可用.这是一个错误的假设,因为32位应用程序可以切换到64位长模式并访问这些寄存器.确定此问题的Linux内核邮件列表帖子有这样的说法:

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

虽然32位进程无法直接访问R8 ... R15,但它们可以通过暂时切换到64位模式来访问这些寄存器.

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

漏洞

导致此漏洞的根本问题是在从系统调用返回时缺少将几个x86-64寄存器清零.32位应用程序可能能够切换到64位模式并访问r8,r9,r10和r11寄存器以泄漏其先前的值.这个问题是Jan Beulich发现的,并于10月1日修补. 修复显然是将这些寄存器归零,以避免将任何信息泄露给用户空间.


如果您在GDB之类的调试器中单步执行代码,您应该会发现R8事后设置为零int 0x80.由于它是你的循环计数器,你的程序最终会进行无限循环打印Hello world!.