linux - 64 位 linux 内核如何从 ELF 启动 32 位进程

标签 linux linux-kernel x86-64 32bit-64bit

通过查看内核源代码中的 binfmt_elf.c,我无法弄清楚内核(64 位)在生成 32 位进程与 64 位进程时有何不同。

谁能给我解释一下我错过了什么?

(这个问题与我的另一个问题有关,即在与 64 位指令 ( link ) 相同的进程中有 32 位指令,但这可以作为一个单独的问题。)

最佳答案

如果使用execveat系统调用来启动一个新进程,我们首先在内核源码中输入fs/exec.c进入SYSCALL_DEFINEx(execveat..)功能。 这个然后调用这些函数:

  • do_execveat(..)
    • do_execveat_common(..)
      • exec_binprm(..)
        • search_binary_handler(..)

search_binary_handler 迭代各种二进制处理程序。在 64 位 Linux 内核中,将有一个用于 64 位 ELF 的处理程序和一个用于 32 位 ELF 的处理程序。这两个处理程序最终都是从相同的源 fs/binfmt_elf.c 构建的。然而,32 位处理程序是通过 fs/compat_binfmt_elf.c 构建的,它在 包括 源文件 binfmt_elf.c 本身之前重新定义了一些宏.

binfmt_elf.c 中,调用了 elf_check_arch。这是在 arch/x86/include/asm/elf.h 中定义的宏,在 64 位处理程序和 32 位处理程序中定义不同。对于 64 位,它与 EM_X86_64(62 - 在 include/uapi/ilnux/elf-em.h 中定义)进行比较。对于 32 位,它与 EM_386 (3) 或 EM_486 (6)(在同一文件中定义)进行比较。如果比较失败,二进制处理程序将放弃,因此我们最终只有一个处理程序负责 ELF 解析和执行 - 这取决于 ELF 是 64 位还是 32 位。

因此,在 64 位 Linux 中解析 32 位 ELF 与 64 位 ELF 的所有差异都应该在文件 fs/compat_binfmt_elf.c 中找到。

主要线索似乎是compat_start_threadstart_thread 被重新定义为 compat_start_thread。此函数定义位于 arch/x86/kernel/process_64.c 中。 compat_start_thread 然后使用这些参数调用 start_thread_common:

start_thread_common(regs, new_ip, new_sp,
             test_thread_flag(TIF_X32)
             ? __USER_CS : __USER32_CS,
             __USER_DS, __USER_DS);

虽然普通的 start_thread 函数使用这些参数调用 start_thread_common:

start_thread_common(regs, new_ip, new_sp,
             __USER_CS, __USER_DS, 0);

在这里,我们已经看到架构相关代码对 64 位 ELF 和 32 位 ELF 使用 CS 做一些不同的事情。

然后我们在 arch/x86/include/asm/segment.h 中定义了 __USER_CS 和 __USER32_CS:

#define __USER_CS           (GDT_ENTRY_DEFAULT_USER_CS*8 + 3)
#define __USER32_CS         (GDT_ENTRY_DEFAULT_USER32_CS*8 + 3)

和:

#define GDT_ENTRY_DEFAULT_USER_CS   6
#define GDT_ENTRY_DEFAULT_USER32_CS 4

所以 __USER_CS 是 6*8 + 3 = 51 = 0x33

__USER32_CS 是 4*8 + 3 = 35 = 0x23

这些数字与这些示例中用于 CS 的数字相匹配:

由于CPU不是在实模式下运行,段寄存器中填充的不是段本身,而是一个16位的选择器:

来自维基百科(Protected mode):

In protected mode, the segment_part is replaced by a 16-bit selector, in which the 13 upper bits (bit 3 to bit 15) contain the index of an entry inside a descriptor table. The next bit (bit 2) specifies whether the operation is used with the GDT or the LDT. The lowest two bits (bit 1 and bit 0) of the selector are combined to define the privilege of the request, where the values of 0 and 3 represent the highest and the lowest privilege, respectively.

对于 CS 值 0x23,第 1 位和第 0 位是3,意思是“最低权限”。第 2 位是0,表示GDT,第 3 位到第 15 位是 4,表示我们从全局描述符表(GDT)中得到索引 4 ).

这是我到目前为止能够挖掘到的程度。

关于linux - 64 位 linux 内核如何从 ELF 启动 32 位进程,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48874756/

相关文章:

linux - 如何打开新终端并从已激活的 shell 文件运行命令

linux - 如何找到所有可运行的进程

linux-kernel - skb_reserve 注释说明

windows - 程序计数器、栅栏和处理器重新排序

python - iteritems 上的 Pillar jinja 错误

linux - 海湾合作委员会/G++ : building without GNU unique object symbols for older Linux kernels

c - Linux 中的奇怪开关

c - x86-64 上的 Delphi SizeOf(NativeInt) 与 C sizeof(int)。为什么大小不同?

c++ - g++ ld 找不到 x86_64 架构的 RInside 符号

c - Mongodb-c-driver 工作后出现段错误