前言:
Linux x64透過qemu的rdinit方式呼叫的C Main,實際上是透過load_elf_binary(載入和解析elf)和start_thread(設定Ip和sp),用缺頁異常來呼叫。關於這點可以看這篇文章:點選檢視。那麼Arm32裡面是如何呼叫C Main的呢?本篇看下。
概括:
Arm32也是透過load_elf_binary載入被 gcc-arm-linux-gnueabi編譯的C Main Demo。同樣是也是透過ret_from_fork呼叫kernel_Init來呼叫elf_load_binary來操縱elf檔案的。這點可以看下堆疊:
* thread #1, stop reason = breakpoint 1.1
* frame #0: 0x802fbc0c vmlinux`load_elf_binary(bprm=0x811cd000) at binfmt_elf.c:824:1
frame #1: 0x8028eeb0 vmlinux`bprm_execve at exec.c:1735:12
frame #2: 0x8028ee38 vmlinux`bprm_execve at exec.c:1776:9
frame #3: 0x8028edf4 vmlinux`bprm_execve at exec.c:1845:11
frame #4: 0x8028ecd8 vmlinux`bprm_execve(bprm=0x811cd000, fd=-100, filename=0x81160000, flags=0) at exec.c:1807:12
frame #5: 0x8028f644 vmlinux`kernel_execve(kernel_filename=<unavailable>, argv=0x80c07714, envp=0x80c077a0) at exec.c:2006:11
frame #6: 0x8083d8d8 vmlinux`run_init_process(init_filename=<unavailable>) at main.c:1438:9 [artificial]
frame #7: 0x80847010 vmlinux`kernel_init(unused=<unavailable>) at main.c:1534:9
frame #8: 0x80100130 vmlinux`ret_from_fork at entry-common.S:146
同樣x64和Arm32的使用者態入口也是Glibc的_start函式。
那麼不同點在哪兒呢?上說過x64設定IP和SP的是start_thread,而Arm32裡面則是START_THREAD這個宏定義來設定IP和SP,實質上做的東西是一樣的,但是程式碼不一樣,他們都是透過返回到ret_from_fork,然後獲取到被設定的regs變數,裡面包含了被start_thread OR START_THREAD宏設定的IP和SP。
看下START_THREAD宏定義:
#define START_THREAD COMPAT_START_THREAD
#define COMPAT_START_THREAD(ex, regs, new_ip, new_sp) \
compat_start_thread(regs, new_ip, new_sp, ex->e_machine == EM_X86_64)
void compat_start_thread(struct pt_regs *regs, u32 new_ip, u32 new_sp, bool x32)
{
start_thread_common(regs, new_ip, new_sp,
x32 ? __USER_CS : __USER32_CS,
__USER_DS, __USER_DS);
}
static inline void start_thread_common(struct pt_regs *regs, unsigned long pc)
{
s32 previous_syscall = regs->syscallno;
memset(regs, 0, sizeof(*regs));
regs->syscallno = previous_syscall;
regs->pc = pc;
if (system_uses_irq_prio_masking())
regs->pmr_save = GIC_PRIO_IRQON;
}
最後的start_thread_common設定了PC也就上面的IP。Glibc庫的入口_start函式。這一點也可以驗證下:
arm-linux-gnueabi-gcc -static -o hello hello.c
readelf -s hello
3127: 00010418 0 FUNC GLOBAL DEFAULT 4 _start
在以下地方下斷點
linux-source-5.15 Version b binfmt_elf.c:1325也可以br s --file binfmt_elf.c --line 1325
c執行到此處,再n幾次讓START_TRHEAD宏執行完
如下:
p/x *regs
(pt_regs) $7 = {
uregs = {
[13] = 0x7e952f10
[14] = 0x00000000
[15] = 0x00010418
[16] = 0x00000010
[17] = 0x00000000
}
我們看到[15]=後面就是10418,也就是上面Glibc的入口_start函式。
結尾:
這裡還有幾個問題:
ret_from_fork是否同xx64一樣被_schedule和schedule排程。
Arm32的_start呼叫是否也是缺頁異常。
關於這兩點下篇再看看。
作者:江湖評談(jianghupt)公眾號同名。歡迎關注我,帶你瞭解高階技術。