64位linux内核如何启动ELF的32位进程 [英] How does the 64 bit linux kernel kick off a 32 bit process from an ELF
问题描述
通过查看 binfmt_elf.c 作为内核源代码,我无法弄清生成32位进程与64位进程时内核(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?
(此问题与我的另一个问题有关,即关于32位指令与64位指令在同一过程中使用(
(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.c 在内核源代码中将strong>插入到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(..)
- 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.
因此,应在文件 fs/compat_binfmt_elf.c 中找到解析32位ELF与64位Linux中的64位ELF的所有区别.
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);
在这里,我们已经看到与体系结构相关的代码在 CS 方面的处理方式与64位ELF和32位ELF有所不同.
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 = 0x33So
__USER_CS
is 6*8 + 3 = 51 = 0x33__USER32_CS
是4 * 8 + 3 = 35 = 0x23And
__USER32_CS
is 4*8 + 3 = 35 = 0x23这些数字与这些示例中用于CS的数字匹配:
These numbers match what is used for CS in these examples:
- For going from 64 bit mode to 32 bit in the middle of a process
- For going from 32 bit mode to 64 bit in the middle of a process
由于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:
来自Wikipedia(受保护的模式):
From Wikipedia (Protected mode):
在保护模式下,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屋!
- exec_binprm(..)
- do_execveat_common(..)
- exec_binprm(..)
- do_execveat_common(..)