64 位 linux 内核如何从 ELF 启动 32 位进程 [英] How does the 64 bit linux kernel kick off a 32 bit process from an ELF

查看:35
本文介绍了64 位 linux 内核如何从 ELF 启动 32 位进程的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

By looking at binfmt_elf.c in the kernel source, I have not been able to figure out what the kernel (64 bit) does differently when spawning a 32 bit process vs a 64 bit process.

有人可以向我解释我遗漏了什么吗?

Can anybody explain to me what I am missing?

(这个问题与我的另一个问题有关,即在与 64 位指令相同的过程中使用 32 位指令(链接),但这可以作为一个单独的问题.)

(This question is related to my other question about having 32 bit instructions in the same process as 64 bit instructions (link), but this qualifies as a separate question.)

推荐答案

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

If the execveat system call is used to start a new process, we first enter fs/exec.c in the kernel source into the SYSCALL_DEFINEx(execveat..) function. This one then calls these functions:

  • 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 本身之前重新定义了许多宏.

        The search_binary_handler iterates over the various binary handlers. In a 64 bit Linux kernel, there will be one handler for 64 bit ELFs and one for 32 bit ELFs. Both handlers are ultimately built from the same source fs/binfmt_elf.c. However, the 32 bit handler is built via fs/compat_binfmt_elf.c which redefines a number of macros before including the source file binfmt_elf.c itself.

        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 位.

        Inside binfmt_elf.c, elf_check_arch is called. This is a macro defined in arch/x86/include/asm/elf.h and defined differently in the 64 bit handler vs the 32 bit handler. For 64 bit, it compares with EM_X86_64 ( 62 - defined in include/uapi/ilnux/elf-em.h). For 32 bit, it compares with EM_386 (3) or EM_486 (6) (defined in the same file). If the comparison fails, the binary handler gives up, so we end up with only one of the handlers taking care of the ELF parsing and execution - depending on whether the ELF is 64 bit or 32 bit.

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

        All differences on parsing 32 bit ELFs vs 64 bit ELFs in 64 bit Linux should therefore be found in the file fs/compat_binfmt_elf.c.

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

        The main clue seems to be compat_start_thread. start_thread is redefined to compat_start_thread. This function definition is found in arch/x86/kernel/process_64.c. compat_start_thread then calls start_thread_common with these arguments:

        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:

        while the normal start_thread function calls start_thread_common with these arguments:

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

        在这里,我们已经看到,对于 64 位 ELF 和 32 位 ELF,依赖于架构的代码对 CS 做了一些不同的事情.

        Here we already see the architecture dependent code doing something with CS differently for 64 bit ELFs vs 32 bit ELFs.

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

        Then we have the definitions for __USER_CS and __USER32_CS in arch/x86/include/asm/segment.h:

        #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

        So __USER_CS is 6*8 + 3 = 51 = 0x33

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

        And __USER32_CS is 4*8 + 3 = 35 = 0x23

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

        These numbers match what is used for CS in these examples:

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

        Since the CPU is not running in real mode, the segment register is not filled with the segment itself, but a 16-bit selector:

        来自维基百科(保护模式):

        在保护模式下,segment_part 被一个 16 位选择器替换,其中高 13 位(第 3 位到第 15 位)包含描述符表内条目的索引.下一位(位 2)指定操作是与 GDT 还是 LDT 一起使用.选择器的最低两位(位 1 和位 0)组合起来定义请求的权限,其中 0 和 3 的值分别代表最高和最低权限.

        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).

        With the CS value 0x23, bit 1 and 0 is 3, meaning "lowest privilege". Bit 2 is 0, meaning GDT, and bit 3 to bit 15 is 4, meaning we get index 4 from the global descriptor table (GDT).

        到目前为止,我已经能够挖掘到这里了.

        This is how far I have been able to dig so far.

        这篇关于64 位 linux 内核如何从 ELF 启动 32 位进程的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆