64位内核不支持使用.code64汇编程序的IA-32应用程序吗?

时间:2019-08-23 01:17:04

标签: c assembly linux-kernel signals x86-64

x86应用程序(使用gcc -m32构建)支持具有 .code64 的Assembly 64位代码,这意味着x86应用程序可以使用64位寄存器。但是从内核方面来说,该应用程序只是一个IA-32应用程序。

例如,我可以在符号64bit_test下链接到x86应用程序。

ENTRY(64bit_test)
    .code64;
    push %r8
    push %r12
END(64bit_test)

在内核设置信号处理程序中,内核仅保存32位寄存器而没有64位寄存器,是否缺少64位寄存器上下文?我认为这是不正确的,因为使用了64位寄存器,以后应保存并还原。

if (is_ia32_frame(ksig)) {
        if (ksig->ka.sa.sa_flags & SA_SIGINFO)
            return ia32_setup_rt_frame(usig, ksig, cset, regs);
        else
            return ia32_setup_frame(usig, ksig, cset, regs);
    } else if (is_x32_frame(ksig)) {
        return x32_setup_rt_frame(ksig, cset, regs);
    } else {
        return __setup_rt_frame(ksig->sig, ksig, set, regs);
    }


static int ia32_setup_sigcontext(struct sigcontext_32 __user *sc,
                 void __user *fpstate,
                 struct pt_regs *regs, unsigned int mask)
{
    int err = 0;

    put_user_try {
        put_user_ex(get_user_seg(gs), (unsigned int __user *)&sc->gs);
        put_user_ex(get_user_seg(fs), (unsigned int __user *)&sc->fs);
        put_user_ex(get_user_seg(ds), (unsigned int __user *)&sc->ds);
        put_user_ex(get_user_seg(es), (unsigned int __user *)&sc->es);

        put_user_ex(regs->di, &sc->di);
        put_user_ex(regs->si, &sc->si);
        put_user_ex(regs->bp, &sc->bp);
        put_user_ex(regs->sp, &sc->sp);
        put_user_ex(regs->bx, &sc->bx);
        put_user_ex(regs->dx, &sc->dx);
        put_user_ex(regs->cx, &sc->cx);
        put_user_ex(regs->ax, &sc->ax);
        put_user_ex(current->thread.trap_nr, &sc->trapno);
        put_user_ex(current->thread.error_code, &sc->err);
        put_user_ex(regs->ip, &sc->ip);
        put_user_ex(regs->cs, (unsigned int __user *)&sc->cs);
        put_user_ex(regs->flags, &sc->flags);
        put_user_ex(regs->sp, &sc->sp_at_signal);
        put_user_ex(regs->ss, (unsigned int __user *)&sc->ss);

        put_user_ex(ptr_to_compat(fpstate), &sc->fpstate);

        /* non-iBCS2 extensions.. */
        put_user_ex(mask, &sc->oldmask);
        put_user_ex(current->thread.cr2, &sc->cr2);
    } put_user_catch(err);

    return err;
}

我希望将64位寄存器r8r15保存在sigcontext中,以后再恢复,但是从代码r8r15是丢失。

1 个答案:

答案 0 :(得分:2)

TL:DR:不,这不是.code64所做的事情,并且没有Linux 支持跳到64位用户空间的32位进程。 / p>


.code64仅允许您将64位机器代码放入32位目标文件/可执行文件中。例如如果您想编写一个修补64位可执行文件的32位程序,并且希望汇编器为您生成该数据,即使它永远不会在32位程序中执行。

或者,如果您正在编写以16或32位模式开始并切换到64位模式的内核,则可以使用.code64作为内核与CS一起跳转的部分,即64位代码段。


将机器代码解码为64位而不是32位需要将CPU置于不同的模式。 x86机器代码不支持在没有模式切换的情况下混合32位和64位机器代码。没有足够的编码空间可用于此。编码非常相似,但有些操作码在64位模式下具有不同的默认操作数大小(例如堆栈操作),例如push %eaxpush %rax具有相同的1字节操作码。

您的.code64;push %r8测试实际上为inc %eax(REX前缀)和push %eax 创建了32位机器代码。是的,它可以组装并运行,但是按照不同的说明。在layout reg中与GDB一起单步执行,以根据CPU所在的实际模式(而非源)查看反汇编。

区别包括64位长模式将1字节的inc / dec(0x40..4f)操作码用作REX前缀。例如x86-32 / x86-64 polyglot machine-code fragment that detects 64bit mode at run-time?


请注意,这与16 vs. 32完全不同。 16位代码可以在16位模式下使用操作数大小的前缀 来访问32位寄存器和寻址模式。例如mov eax, 1234可以在.code16(带有操作数大小的前缀)或.code32(不带前缀)中很好地组装。

但是您不能在add rax, rdx之外进行.code64 ,因为如果不将CPU切换到其他模式,就无法运行它。 (模式由CS指向的GDT / LDT条目进行选择。)


理论上,您可以将用户空间中的jmpl(远jmp)切换到用户空间过程中的其他代码段,以从“ compat模式”(64位内核下的32位模式)切换到到完整的64位模式。您必须知道要使用哪个CS值,但是大多数操作系统的32位和64位用户空间(CPL = 3)代码段都有一些“众所周知的”常量值。

如果这听起来令人难以置信且复杂,是的,这就是我的意思。

从根本上说,零(从OS系统调用和上下文切换,动态链接器和工具链)支持在进程内部切换模式。通常这是一个可怕的主意,不要这样做。

例如正如您所注意到的,内核仅为传递信号时以32位开始的进程保存/恢复旧版IA32状态,因此,如果该进程已跳到64位用户空间,则信号处理程序将破坏高位寄存器。 (在x86-64 System V ABI中,r8..r11被呼叫了)。

半相关:What happens if you use the 32-bit int 0x80 Linux ABI in 64-bit code?