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

Doesn't 64-bit kernel support IA-32 application with .code64 assembler?

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

例如,我可以 link 符号 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 不见了。

TL:DR: 不,那不是 .code64 所做的,不 Linux 支持远跳的 32 位进程到 64 位用户-space.


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

或者,如果您正在编写自己的内核,该内核以 16 位或 32 位模式启动,然后切换到 64 位模式,您将使用 .code64 作为您的内核使用 CS 引用跳转的部分到 64 位代码段。


将机器代码解码为 64 位而不是 32 位需要将 CPU 置于不同的模式。 x86 机器代码 不支持混合 32 位和 64 位机器代码而无需模式切换。 编码不足 space 就这样离开了。编码非常相似,但一些操作码在 64 位模式下具有不同的默认操作数大小(例如堆栈操作),例如push %eaxpush %rax 具有相同的 1 字节操作码。

你的.code64;; push %r8 测试实际上为 inc %eax(REX 前缀)和 push %eax 创建了 32 位机器代码。是的,它汇编 运行s,但 作为不同的指令 。在 layout reg 中使用 GDB 单步执行它以根据 CPU 所在的实际模式而不是源代码查看反汇编。

差异包括 64 位长模式将 1 字节 inc/dec (0x40..4f) 操作码重新用作 REX 前缀。例如


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

但是你不能在 .code64 之外做 add rax, rdx 因为 如果不切换 [=80] 就无法 运行 =] 到不同的模式。 (模式由 CS 指向的 GDT/LDT 条目选择)。


理论上你可以 jmpl (far jmp) in user-space to a different code segment in your user-space process to switch from "compat mode" (32 64 位内核下的位模式)到完整的 64 位模式。您必须知道要使用什么 CS 值,但大多数 OSes 的 32 位和 64 位用户都有一些“众所周知”的常数值-space (CPL=3)代码段。

如果这听起来令人难以置信的神秘和复杂,是的,这就是我的观点。

对在进程内切换模式的支持基本上为零(来自 OS 系统调用和上下文切换、动态链接器和工具链)。这通常是一个糟糕的想法,不要这样做。

例如正如您所注意到的,内核仅保存/恢复在传递信号时以 32 位启动的进程的遗留 IA32 状态,因此如果它跳到 64 位用户-space,信号处理程序将腐败的高寄存器。 (r8..r11 在 x86-64 System V ABI 中被调用破坏)。

半相关: