【问题标题】:Is it possible to use both 64 bit and 32 bit instructions in the same executable in 64 bit Linux?是否可以在 64 位 Linux 的同一个可执行文件中同时使用 64 位和 32 位指令?
【发布时间】:2018-07-29 00:04:44
【问题描述】:

64 位 CPU (amd64) 在兼容模式下支持 32 位 Intel 指令。此外,如果 ELF 标头显示它是 32 位可执行文件,则 64 位 Linux 安装允许运行包含 32 位指令的 ELF。

我想知道是否可以在 ELF 中放入一些汇编指令,在程序中间将 CPU 切换到 32 位兼容模式(然后再返回)?如果内核不允许这些汇编指令,是否有某种方法可以让内核将已经运行的进程切换到 32 位?

这主要是出于好奇,因为我看不到任何用例。

【问题讨论】:

  • 您实际上可以在 64 位程序集中使用所有 32 位指令而无需做任何事情,因为所有 64 位指令都需要有一个 rex 前缀。您必须记住的唯一棘手的事情是地址和它们自身的 rex 前缀,因为它们是有效的 32 位指令。应该没有问题,只要你控制了32bit.code。在 ›real‹ 32 位和 64 位之间切换是 afaik ring0-stuff,所以这取决于操作系统。但是将 32 位代码转换为 64 位代码并没有那么困难,并且可能是一种选择?
  • @sivizius : 您可以使用 LDT 中现有的描述符 FAR Jmp 在兼容模式和长模式之间跳转。
  • 虽然不是完全重复,但这些 SO 答案是相关的:stackoverflow.com/questions/34467092/…
  • 不是每个 64b 的 linux 都包含 32b 兼容性回退,例如 Win10 中的 Ubuntu 是纯 64b。这意味着仅支持 64b syscall。在 64b 模式下使用 32b 寄存器编码的指令是有效的,但是你不能运行 elf32 二进制,比如只在 32 位模式下运行一些代码。
  • @sivizius 这完全错了!以push eax为例,它存在于保护模式但不存在于长模式。

标签: linux assembly x86 x86-64 32bit-64bit


【解决方案1】:

长模式和兼容模式之间的切换是通过更改 CS 来完成的。用户模式代码不能修改描述符表,但它可以对描述符表中已经存在的代码段执行远跳转或远调用。我认为在 Linux(例如)中存在所需的兼容模式描述符。

这里是 Linux (Ubuntu) 的示例代码。构建与

$ gcc -no-pie switch_mode.c switch_cs.s

switch_mode.c:

#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>

extern bool switch_cs(int cs, bool (*f)());
extern bool check_mode();

int main(int argc, char **argv)
{
    int cs = 0x23;
    if (argc > 1)
        cs = strtoull(argv[1], 0, 16);
    printf("switch to CS=%02x\n", cs);

    bool r = switch_cs(cs, check_mode);

    if (r)
        printf("cs=%02x: 64-bit mode\n", cs);
    else
        printf("cs=%02x: 32-bit mode\n", cs);

    return 0;
}

switch_cs.s:

        .intel_syntax noprefix
        .code64
        .text
        .globl switch_cs
switch_cs:
        push    rbx
        push    rbp
        mov     rbp, rsp
        sub     rsp, 0x18

        mov     rbx, rsp
        movq    [rbx], offset .L1
        mov     [rbx+4], edi

        // Before the lcall, switch to a stack below 4GB.
        // This assumes that the data segment is below 4GB.
        mov     rsp, offset stack+0xf0
        lcall   [rbx]

        // restore rsp to the original stack
        leave
        pop     rbx
        ret

        .code32
.L1:
        call    esi
        lret


        .code64
        .globl check_mode
// returns false for 32-bit mode; true for 64-bit mode
check_mode:
        xor     eax, eax
        // In 32-bit mode, this instruction is executed as
        // inc eax; test eax, eax
        test    rax, rax
        setz    al
        ret

        .data
        .align  16
stack:  .space 0x100

【讨论】:

  • 您不能修改 GDT,但可以修改进程 LDT(有限制)。使用 modify_ldt 。但是,您可以(按照您的建议)在长模式和兼容模式之间使用现有的描述符 far jump back and forth
  • 中断/上下文切换是否总是保存/恢复cs,或者内核是否可以使用它认为适合以64位模式启动的进程的CS值返回用户空间?相关:我认为在 64 位模式下使用 sysenter 将在 Linux 上以 32 位模式返回用户空间:stackoverflow.com/questions/46087730/…。并且 64 位 syscall 将在返回用户空间时将 CS 设置为 __user_cs,即使您已创建自定义 LDT 条目并将 CS 设置为 @MichaelPetch 建议的那样。
  • @MichaelPetch:我不认为modify_ldt 可以在LDT 条目中设置R 位,因此您无法创建64 位代码段。手册页说“即使在 64 位内核上,modify_ldt() 也不能用于创建长模式(即 64 位)代码段”。但是一个 64 位的进程可以创建一个 32 位的代码段,并在它和它的初始代码段之间来回跳转。
  • @PeterCordes :我从来没有说过可以。请注意我是如何提到限制的。我的评论与答案中的评论有关,即用户模式无法更改描述符表。它可以更改有限制的LDT,但不能更改GDT。正如我在回答中所说的,您可以使用现有的描述符在两种模式之间进行远距离跳转。这不需要更改描述符表。
  • @MichaelPetch:当我开始写那条评论时,我想到一个 32 位进程想要创建一个 64 位代码段,在这种情况下@ 987654335@ 不允许您进行必要的修改。这就是为什么第一部分的措辞听起来我不同意你所说的。无论如何,这是一个重大限制,IMO。
猜你喜欢
  • 1970-01-01
  • 2013-10-04
  • 2011-07-13
  • 1970-01-01
  • 1970-01-01
  • 2011-03-14
  • 2011-06-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多