From c232c0c0d5d97ad4d8e68f0e0082aaeca3d05832 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Wed, 14 Oct 2026 18:56:40 +0800 Subject: [PATCH] =?UTF-8?q?fix(vkernel/intr):=20syscall=20=E6=9D=A5?= =?UTF-8?q?=E6=BA=90=E6=A0=87=E8=AE=B0=E6=94=B9=E6=94=BE=E5=86=85=E6=A0=B8?= =?UTF-8?q?=E6=A0=88=EF=BC=88=E5=8F=AF=E5=B5=8C=E5=A5=97=EF=BC=89=EF=BC=8C?= =?UTF-8?q?=E4=BF=AE=20app=20=E8=B0=83=E5=AE=8C=20.so=20=E5=90=8E=E8=B7=91?= =?UTF-8?q?=E9=A3=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 现象:app 在用户态成功调 .so(“[so] call returned”)后立刻跑飞:用户态 #BP,rip=0x7c0b9(既不在 app 映像 0x400000-0x41c824、也不在 app 栈 0xbb11000)、rsp=0 - 根因:来源标记原来放在全局 _syscall_from_kernel,但 syscall 会**嵌套**:app 的 SO_CALL1 → 内核侧执行 .so → .so 内部又发 3 次 syscall(SERIAL_PUTS,内核态发起)→ 内层把标记改成 1 → 外层(用户态)返回时误走内核返回分支:jmp rcx 回到用户 rip 却不换回用户栈 → app 用内核栈继续跑 → 栈错位跑飞 - 改为把来源标记 push 在内核栈上(入口 push 0/1,出口 cmp [rsp] 判定),每层各压各的、天然可嵌套;内核返回路径 add rsp,8 弹掉本层标记槽 - 对齐保持:and rsp,~15 之后的深度 = 补齐8 + arg6 8 + call 8 = 24 ≡ 8 (mod 16) ✓(标记槽在 and 之前,不参与) --- VKernel/Kernel/intr/isr.s | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/VKernel/Kernel/intr/isr.s b/VKernel/Kernel/intr/isr.s index 3b0b984..c4b5420 100644 --- a/VKernel/Kernel/intr/isr.s +++ b/VKernel/Kernel/intr/isr.s @@ -11,9 +11,6 @@ .comm _fault_rsp, 8, 8 .comm _fault_rbp, 8, 8 .comm _fault_rax, 8, 8 -# syscall 入口来源标记(1 = 内核态发起):出口据此决定 sysretq(用户)还是 jmp rcx(内核)。 -# 刻意不用 gs:[…]:引导早期内核可能还没 swapgs 过(GS ≠ &_per_cpu),gs 相对写会打偏地址。 -.comm _syscall_from_kernel, 8, 8 # ISR 入口暂存 caller-saved 寄存器(rax/rcx/r8-r11):本宏会用它们当临时寄存器 # (读 RIP/CS/RFLAGS、算 is_user),若不保存就会破坏被中断代码在这些寄存器里的 # 活跃值 → 概率性内存破坏/异常。出口(iretq 前)恢复。 @@ -440,10 +437,10 @@ syscall_from_user: swapgs mov gs:[0x0], rsp mov rsp, gs:[0x8] - mov qword ptr [_syscall_from_kernel], 0 # 标记:用户态发起(出口据此选 sysretq) + push 0 # 来源标记(用户态发起):出口据此选 sysretq jmp syscall_common syscall_from_kernel: - mov qword ptr [_syscall_from_kernel], 1 # 标记:内核态发起(出口据此 jmp rcx 返回,不 swapgs) + push 1 # 来源标记(内核态发起):出口据此 jmp rcx 返回,且不 swapgs syscall_common: push rcx push r11 @@ -461,9 +458,11 @@ syscall_common: push r15 mov rbp, rsp and rsp, ~15 - # 对齐补齐:被调者入口必须满足 SysV(rsp ≡ 8 (mod 16),栈上 1 个参数时要求调用前 rsp ≡ 8)。 - # 只 `and rsp,~15`(≡0) 再 push arg6 会让 handler 入口 ≡ 0 → 一旦其调用树里出现 - # rsp 相对的 16 字节对齐 SSE(内核里 printf 就有 movaps %xmm0,0x60(%rsp))即 #GP。 + # 对齐补齐:被调者入口必须满足 SysV(rsp ≡ 8 (mod 16);栈上 1 个参数时要求调用前 rsp ≡ 8)。 + # 注意 `and rsp,~15` 已把 rsp 归零,所以只有**它之后**的深度才影响对齐: + # 补齐(8) + arg6(8) + call(8) = 24 ≡ 8 ✓。标记槽与 14 个寄存器都在 and 之前,不参与。 + # (去掉这里的 `sub rsp,8` 会让 handler 入口 ≡ 0 → 其调用树里 rsp 相对的 16 字节对齐 SSE + # 例如内核 printf 的 movaps %xmm0,0x60(%rsp) 就 #GP。) sub rsp, 8 push r9 mov r9, r8 @@ -489,18 +488,21 @@ syscall_common: pop rbx pop r11 pop rcx - # 返回去向改用**入口记下的 _syscall_from_kernel 标记**。原来用 `mov rax,rcx; shr rax,47` - # 判:① 会把 rax 里的返回值覆盖掉(app 的 trampoline 就靠它取结果);② 用户 rip 在 0x40xxxx、 - # 内核 rip 在 0x10xxxx,两者都 < 2^47,根本无法区分,内核态发起的 syscall 会误走 sysretq。 - cmp qword ptr [_syscall_from_kernel], 0 + # 返回去向用**栈上的来源标记**([rsp],入口 push 0/1)。必须是栈上而不是全局:syscall 会 + # 嵌套(app 的 SO_CALL1 → 内核调 .so → .so 内部又 syscall 打日志),全局标记会被内层覆盖, + # 外层(用户态)就会误走内核返回分支 → jmp rcx 回用户 rip 却不换回用户栈,app 用内核栈继续跑。 + # 原来用 `mov rax,rcx; shr rax,47` 判还有第二个错:覆盖 rax 里的返回值,且用户 rip(0x40xxxx) + # 与内核 rip(0x10xxxx) 都 < 2^47 无法区分。 + cmp qword ptr [rsp], 0 jne syscall_ret_kernel syscall_ret_user: - mov rsp, gs:[0x0] + mov rsp, gs:[0x0] # 用户路径直接切回用户栈(本层标记槽留在内核栈,下次入口由 TSS.RSP0 重置) swapgs push r11 popfq sysretq syscall_ret_kernel: + add rsp, 8 # 内核路径:弹掉本层标记槽,恢复到入口 rsp push r11 popfq jmp rcx