关于pt_regs中bp与sp差值过大且bp小于sp的技术疑问
关于x86 32位系统调用中pt_regs内ebp/esp异常的问题
背景信息
调试可调试版本的Linux 5.12.10内核时,执行由busybox编译的ls命令并观察进程创建流程,在arch/x86/kernel/process.c的copy_thread函数处设置GDB断点,打印出的pt_regs内容如下:
{bx = 0x1200011, cx = 0x0, dx = 0x0, si = 0x0, di = 0xa0f38e8, bp = 0x8266000, ax = 0xffffffda, ds = 0x7b, __dsh = 0x0, es = 0x7b, __esh = 0x0, fs = 0x0, __fsh = 0x0, gs = 0x33, __gsh = 0x0, orig_ax = 0x78, ip = 0xb7f29549, cs = 0x73, __csh = 0x0, flags = 0x206, sp = 0xbfab35f0, ss = 0x7b, __ssh = 0x0}
其中pt_regs->bp = 0x8266000,pt_regs->sp = 0xbfab35f0,二者存在明显异常。
经代码排查:
pt_regs->sp在arch/x86/entry/common.c的do_SYSENTER_32中赋值:
__visible noinstr long do_SYSENTER_32(struct pt_regs *regs) { /* SYSENTER loses RSP, but the vDSO saved it in RBP. */ regs->sp = regs->bp; /* SYSENTER clobbers EFLAGS.IF. Assume it was set in usermode. */ regs->flags |= X86_EFLAGS_IF; return do_fast_syscall_32(regs); }
pt_regs->bp在__do_fast_syscall_32中通过get_user从用户态获取:
static noinstr bool __do_fast_syscall_32(struct pt_regs *regs) { // do other stuff... /* Fetch EBP from where the vDSO stashed it. */ if (IS_ENABLED(CONFIG_X86_64)) { /* * Micro-optimization: the pointer we're following is * explicitly 32 bits, so it can't be out of range. */ res = __get_user(*(u32 *)®s->bp, (u32 __user __force *)(unsigned long)(u32)regs->sp); } else { res = get_user(*(u32 *)®s->bp, (u32 __user __force *)(unsigned long)(u32)regs->sp); } // do other stuff... return true; }
当前函数调用栈:
#0 copy_thread (clone_flags=clone_flags@entry=18874368, sp=0, arg=0, p=0xc31c0a00, tls=0) at arch/x86/kernel/process.c:133 #1 0xc1058722 in copy_process (pid=pid@entry=0x0, trace=trace@entry=0, node=node@entry=-1, args=<optimized out>) at kernel/fork.c:2122 #2 0xc10593cc in kernel_clone (args=args@entry=0xc68e9f38) at kernel/fork.c:2500 #3 0xc1059807 in __do_sys_clone (child_tidptr=0xa0f38e8, tls=0, parent_tidptr=0x0, newsp=0, clone_flags=<optimized out>) at kernel/fork.c:2617 #4 __se_sys_clone (child_tidptr=168769768, tls=0, parent_tidptr=0, newsp=0, clone_flags=<optimized out>) at kernel/fork.c:2585 #5 __ia32_sys_clone (regs=<optimized out>) at kernel/fork.c:2585 #6 0xc1b04b85 in do_syscall_32_irqs_on (nr=<optimized out>, regs=0xc68e9fb4) at arch/x86/entry/common.c:77 #7 __do_fast_syscall_32 (regs=regs@entry=0xc68e9fb4) at arch/x86/entry/common.c:140 #8 0xc1b04c29 in do_fast_syscall_32 (regs=0xc68e9fb4) at arch/x86/entry/common.c:165 #9 0xc1b04c75 in do_SYSENTER_32 (regs=<optimized out>) at arch/x86/entry/common.c:208 #10 0xc1b0e32f in entry_SYSENTER_32 () at arch/x86/entry/entry_32.S:952 #11 0x01200011 in ?? () #12 0x00000000 in ?? ()
疑问
pt_regs中存储的ebp与esp为何差值极大?- 既然栈向下生长,为何
pt_regs中ebp的值小于esp?
解答
问题1:ebp与esp差值极大的原因
此时pt_regs里的bp和sp并非同一阶段的用户态栈指针:
pt_regs->sp是用户态发起SYSENTER前的原始栈指针(由vDSO暂存到rbp后,在do_SYSENTER_32中赋值给sp),对应32位用户态栈的典型地址范围(0xbfab35f0属于0xbfffffff以下的用户栈区域)。pt_regs->bp是在__do_fast_syscall_32中从用户态内存读取的vDSO暂存的旧ebp值,随着调用栈推进到copy_thread,pt_regs已经经过多次内核态调用修改,这个bp是早期用户态某个栈帧的ebp,甚至可能是vDSO处理SYSENTER时临时存放的无效值,和当前sp已无关联,因此二者差值极大。
问题2:ebp小于esp的原因
x86栈确实向下生长(从高地址向低地址扩展),正常情况下ebp(栈帧基址)应大于等于esp(栈顶指针),但这里的异常源于:
- 当前
pt_regs->bp并非当前用户态栈的有效帧指针,它是从用户态某个内存位置读取的旧值(甚至可能是错误地址),而pt_regs->sp是用户态原始栈顶。当旧ebp对应的栈帧已被销毁,或读取的ebp本身是低地址无效值时,就会出现ebp < esp的情况。 - 结合调用栈来看,此时已进入内核态的
copy_thread函数,用户态栈上下文经过多轮修改,pt_regs里的bp和sp不再是配对的栈帧指针与栈顶,自然不符合用户态正常栈的生长规律。
内容的提问来源于stack exchange,提问作者sunhang
相关产品推荐
相关产品推荐

