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



x86应用程序(使用gcc -m32构建)支持带有.code64的汇编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;
}

我希望r8r15的 64 位寄存器应该保存在sigcontext中,然后恢复,但从代码中,缺少r8r15

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


.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 位机器代码。 是的,它组装和运行,但作为不同的指令。 使用 GDB 进行单步执行layout reg,根据 CPU 所处的实际模式(而不是源)查看反汇编。

区别包括64位长模式将1字节inc/dec(0x40..4f)操作码重新用作REX前缀。 例如,在运行时检测 64 位模式的 x86-32/x86-64 多语言机器代码片段?


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

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


理论上,您可以在用户空间中jmpl(远JMP)到用户空间进程中的不同代码段,以从"兼容模式"(64位内核下的32位模式)切换到完整的64位模式。 您必须知道要使用什么 CS 值,但大多数操作系统的 32 位和 64 位用户空间 (CPL=3) 代码段都有一些"众所周知"的常量值。

如果这听起来非常神秘和复杂,是的,这就是我的观点。

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

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

半相关:如果在 64 位代码中使用 32 位 int 0x80 Linux ABI,会发生什么?

最新更新