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位寄存器r8
到r15
应该保存在sigcontext
中然后恢复,但是从代码来看,r8
到r15
不见了。
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 %eax
和 push %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 中被调用破坏)。
半相关:
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位寄存器r8
到r15
应该保存在sigcontext
中然后恢复,但是从代码来看,r8
到r15
不见了。
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 %eax
和 push %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 中被调用破坏)。
半相关: