首先,栈 (stack) 是一种串列形式的数据结构。这种数据结构的特点是后入先出 (LIFO, Last In First Out),数据只能在串列的一端 (称为:栈顶 top) 进行 推入 (push) 和 弹出 (pop) 操作。根据栈的特点,很容易的想到可以利用数组,来实现这种数据结构。但是本文要讨论的并不是软件层面的栈,而是硬件层面的栈。
进程在创建时,内核会为进程创建一系列的数据结构,其中最重要的就是进程描述task_struct结构。同时内核会为进程创建两个栈,一个是用户栈,一个是内核栈。分别处于用户态和内核态使用的栈。而本文的主线就是内核栈!
内核栈
在每个进程生命周期内,会通过系统调用或者中断进入内核。此时,这些内核代码使用的栈并不是原先用户空间的栈,而这个在内核空间的栈被称为进程内核栈
用户态切到内核态,内核将用户态时的堆栈寄存器的值保存在内核中,这样从内核栈中可以快速切换回用户态。
struct task_struct {
#ifdef CONFIG_THREAD_INFO_IN_TASK
/*
* For reasons of header soup (see current_thread_info()), this
* must be the first element of task_struct.
*/
struct thread_info thread_info;
#endif
unsigned int __state;
/* saved state for "spinlock sleepers" */
/* moved to ANDROID_KABI_USE(1, unsigned int saved_state) */
/*
* This begins the randomizable portion of task_struct. Only
* scheduling-critical items should be added above here.
*/
randomized_struct_fields_start
void *stack;
refcount_t usage;
/* Per task flags (PF_*), defined further below: */
unsigned int flags;
//...
}
此stack指针就是内核栈的指针
而在6.1内核中thread_info直接作为了结构体成员嵌入到task_struct
中,通CONFIG_THREAD_INFO_IN_TASK
启用(我司项目默认启用)
这里也需要标注一下thread_info
在6.1内核中也不再有stack指针了!可能网上的一些文章已经过时了(老内核)。
ARM64 内核通过 sp_el0
寄存器直接存储当前任务的 task_struct
指针(而非栈指针),因此不再需要通过栈指针反向查找 thread_info
。thread_info
作为 task_struct
的成员,其地址可通过 task_struct
直接计算,无需依赖栈指针。
static __always_inline struct task_struct *get_current(void)
{
unsigned long sp_el0;
asm ("mrs %0, sp_el0" : "=r" (sp_el0));
return (struct task_struct *)sp_el0;
}
pt_regs和cpu_context
pt_regs
位于内核栈的高端,主要用于在用户内核模式切换时保存用户寄存器。因此,返回用户空间后,执行的第一条指令位于pt_regs->pc
处。cpu_context
位于 task_struct->thread_struct 中,主要用于保存上下文切换的寄存器。因此,当上下文切换到一个进程后,它的cpu_context->pc
就会被执行
pt_regs
定义:
pt_regs
存储的是 用户态进程陷入内核态时的寄存器快照(如系统调用、中断、异常发生时)。用途:
保存用户态上下文,用于返回到用户态时恢复现场。
作为系统调用参数传递的载体(如通过
x0-x7
传递参数)。核心路径:
entry.S
中的异常向量表处理逻辑会填充pt_regs
。
关键字段(ARM64 示例):
struct pt_regs { u64 regs[31]; // 通用寄存器 x0-x30 u64 sp; // 用户态栈指针 u64 pc; // 用户态程序计数器(触发异常的地址) u64 pstate; // 处理器状态(如 PSTATE_DAIF 标志) u64 orig_x0; // 原始 x0(用于 syscall 重启) // ... 其他架构特定字段 };
cpu_context
定义:
cpu_context
是struct thread_struct
的成员,保存 任务切换时需保留的内核态 CPU 寄存器(如任务调度时的上下文)。用途:
在任务切换(
context_switch
)时保存/恢复内核态执行流。仅包含任务切换必需的寄存器(如
x19-x30
等被调用者保存的寄存器)。
关键字段(ARM64 示例):
struct cpu_context { u64 x19, x20, x21, x22, x23, x24, x25, x26, x27, x28; // 被调用者保存寄存器 u64 fp; // 帧指针(x29) u64 lr; // 链接寄存器(x30) u64 sp; // 内核态栈指针 u64 tpidr_el0; // 线程 ID 寄存器(用户态 TLS) };
核心区别
关联与协作
(1) 任务调度时的协作
当发生任务切换时:
保存当前任务上下文:将当前任务的
x19-x30
、sp
等保存到task_struct->thread.cpu_context
。恢复新任务上下文:从新任务的
cpu_context
加载寄存器,切换到新任务的内核栈。返回到用户态(若新任务从用户态被切换出去):通过
pt_regs
恢复用户态寄存器(如pc
、x0-x7
)。
(2) 系统调用流程示例
用户态触发 syscall:
CPU 自动保存
pc
和pstate
到elr_el1
和spsr_el1
,跳转到内核异常向量。
构造
pt_regs
:内核在栈顶分配
pt_regs
,保存用户态的x0-x30
、sp
、pc
、pstate
等。
执行系统调用:
使用
pt_regs->x0-x7
获取参数,处理完成后将返回值写入pt_regs->x0
。
返回用户态:
从
pt_regs
恢复用户态寄存器,通过eret
指令返回到用户态pc
。
(3) 中断上下文切换
若中断发生时当前任务处于用户态:
保存用户态上下文到
pt_regs
,中断处理完毕后恢复。
若中断发生时当前任务处于内核态:
仅保存必要的内核态寄存器(如
cpu_context
),无需完整pt_regs
。
sp_el0里存的是什么?
通过get_current函数我们可以直接获得当前task_struct
,所以sp_el0里肯定存的是这个!那什么时候存储的呢?
//kernel_platform/common/arch/arm64/kernel/process.c
__notrace_funcgraph __sched
struct task_struct *__switch_to(struct task_struct *prev,
struct task_struct *next)
{
struct task_struct *last;
fpsimd_thread_switch(next);
tls_thread_switch(next);
hw_breakpoint_thread_switch(next);
contextidr_thread_switch(next);
entry_task_switch(next);
ssbs_thread_switch(next);
erratum_1418040_thread_switch(next);
ptrauth_thread_switch_user(next);
/*
* vendor hook is needed before the dsb(),
* because MPAM is related to cache maintenance.
*/
trace_android_vh_mpam_set(prev, next);
/*
* Complete any pending TLB or cache maintenance on this CPU in case
* the thread migrates to a different CPU.
* This full barrier is also required by the membarrier system
* call.
*/
dsb(ish);
/*
* MTE thread switching must happen after the DSB above to ensure that
* any asynchronous tag check faults have been logged in the TFSR*_EL1
* registers.
*/
mte_thread_switch(next);
/* avoid expensive SCTLR_EL1 accesses if no change */
if (prev->thread.sctlr_user != next->thread.sctlr_user)
update_sctlr_el1(next->thread.sctlr_user);
trace_android_vh_is_fpsimd_save(prev, next);
/* the actual thread switch */
last = cpu_switch_to(prev, next); //进程切换
return last;
}
上面是arm64进程切换时会执行的一个函__switch_to
,最后会调cpu_switch_to
// kernel_platform/common/arch/arm64/kernel/entry.S
SYM_FUNC_START(cpu_switch_to)
mov x10, #THREAD_CPU_CONTEXT
add x8, x0, x10
mov x9, sp
stp x19, x20, [x8], #16 // store callee-saved registers
stp x21, x22, [x8], #16
stp x23, x24, [x8], #16
stp x25, x26, [x8], #16
stp x27, x28, [x8], #16
stp x29, x9, [x8], #16
str lr, [x8]
add x8, x1, x10
ldp x19, x20, [x8], #16 // restore callee-saved registers
ldp x21, x22, [x8], #16
ldp x23, x24, [x8], #16
ldp x25, x26, [x8], #16
ldp x27, x28, [x8], #16
ldp x29, x9, [x8], #16
ldr lr, [x8]
mov sp, x9
msr sp_el0, x1
ptrauth_keys_install_kernel x1, x8, x9, x10
scs_save x0
scs_load_current
ret
SYM_FUNC_END(cpu_switch_to)
NOKPROBE(cpu_switch_to)
这段汇编的意思是将prev进程的x19到x29, x9, lr寄存器都存储在内核堆栈中,然后将next进程的x19-x29, x9, lr寄存器从堆栈中恢复。
我们关心msr sp_el0, x1
。其中x1就是next进程的struct task_struct结构。则sp_el0存储的是当前进程的task_struct结构。
PS: 为啥x1是next指针?因为next指针是__switch_to函数的第二个参数,被保存在x1里
栈的配置
// kernel_platform/common/arch/arm64/include/asm/memory.h
#define MIN_THREAD_SHIFT (14 + KASAN_THREAD_SHIFT)
/*
* VMAP'd stacks are allocated at page granularity, so we must ensure that such
* stacks are a multiple of page size.
*/
#if defined(CONFIG_VMAP_STACK) && (MIN_THREAD_SHIFT < PAGE_SHIFT)
#define THREAD_SHIFT PAGE_SHIFT
#else
#define THREAD_SHIFT MIN_THREAD_SHIFT
#endif
#if THREAD_SHIFT >= PAGE_SHIFT
#define THREAD_SIZE_ORDER (THREAD_SHIFT - PAGE_SHIFT)
#endif
#define THREAD_SIZE (UL(1) << THREAD_SHIFT)
从上面的定义我们可以获知:
MIN_THREAD_SHIFT
在没有打开Kasan的情况下为14THREAD_SIZE_ORDER
是衡量栈大小占用几个页,值THREAD_SHIFT-PAGE_SHIFT
THREAD_SIZE
为 1<<THREAD_SHIFT,即16K,占用4个4k的pageIRQ_STACK_SIZE
同样为16K
通过task_struct找到stack
static __always_inline void *task_stack_page(const struct task_struct *task)
{
return task->stack;
}
通过task_struct找到pt_regs
#define task_pt_regs(p) \
((struct pt_regs *)(THREAD_SIZE + task_stack_page(p)) - 1)
所以我们可以通过task_struct,就可以轻松得到内核栈和内核寄存器
内核栈的作用
栈作用可以从两个方面体现:函数调用 和 多任务支持
函数调用
我们知道一个函数调用有以下三个基本过程:
调用参数的传入
局部变量的空间管理
函数返回
函数的调用必须是高效的,而数据存放在 CPU通用寄存器 或者 RAM 内存 中无疑是最好的选择。以传递调用参数为例,我们可以选择使用 CPU通用寄存器 来存放参数。但是通用寄存器的数目都是有限的,当出现函数嵌套调用时,子函数再次使用原有的通用寄存器必然会导致冲突。因此如果想用它来传递参数,那在调用子函数前,就必须先 保存原有寄存器的值,然后当子函数退出的时候再 恢复原有寄存器的值 。
函数的调用参数数目一般都相对少,因此通用寄存器是可以满足一定需求的。但是局部变量的数目和占用空间都是比较大的,再依赖有限的通用寄存器未免强人所难,因此我们可以采用某些 RAM 内存区域来存储局部变量。但是存储在哪里合适?既不能让函数嵌套调用的时候有冲突,又要注重效率。
这种情况下,栈无疑提供很好的解决办法。
对于通用寄存器传参的冲突,我们可以再调用子函数前,将通用寄存器临时压入栈中;在子函数调用完毕后,在将已保存的寄存器再弹出恢复回来。
局部变量的空间申请,也只需要向下移动下栈顶指针;将栈顶指针向回移动,即可就可完成局部变量的空间释放
对于函数的返回,也只需要在调用子函数前,将返回地址压入栈中,待子函数调用结束后,将函数返回地址弹出给 PC 指针,即完成了函数调用的返回
上述函数调用的三个基本过程,就演变记录一个栈指针的过程。每次函数调用的时候,都配套一个栈指针。即使循环嵌套调用函数,只要对应函数栈指针是不同的,也不会出现冲突。
函数栈帧 (Stack Frame) 函数调用经常是嵌套的,在同一时刻,栈中会有多个函数的信息。每个未完成运行的函数占用一个独立的连续区域,称作栈帧(Stack Frame)。栈帧存放着函数参数,局部变量及恢复前一栈帧所需要的数据等
多任务支持
栈的意义还不只是函数调用,有了它的存在,才能构建出操作系统的多任务模式。我们以 main 函数调用为例,main 函数包含一个无限循环体,循环体中先调用 A 函数,再调用 B 函数。
void B()
{
return;
}
void A()
{
B();
}
void main()
{
while(1)
{
A();
}
}
试想在单处理器情况下,程序将永远停留在此 main 函数中。即使有另外一个任务在等待状态,程序是没法从此 main 函数里面跳转到另一个任务。因为如果是函数调用关系,本质上还是属于 main 函数的任务中,不能算多任务切换。此刻的 main 函数任务本身其实和它的栈绑定在了一起,无论如何嵌套调用函数,栈指针都在本栈范围内移动。
由此可以看出一个任务可以利用以下信息来表征:
main 函数体代码
main 函数栈指针
当前 CPU 寄存器信息
假如我们可以保存以上信息,则完全可以强制让出 CPU 去处理其他任务。只要将来想继续执行此 main 任务的时候,把上面的信息恢复回去即可。有了这样的先决条件,多任务就有了存在的基础,也可以看出栈存在的另一个意义。在多任务模式下,当调度程序认为有必要进行任务切换的话,只需保存任务的信息(即上面说的三个内容)。恢复另一个任务的状态,然后跳转到上次运行的位置,就可以恢复运行了。
可见每个任务都有自己的栈空间,正是有了独立的栈空间,为了代码重用,不同的任务甚至可以混用任务的函数体本身,例如可以一个main函数有两个任务实例。至此之后的操作系统的框架也形成了,譬如任务在调用 sleep() 等待的时候,可以主动让出 CPU 给别的任务使用,或者分时操作系统任务在时间片用完是也会被迫的让出 CPU。不论是哪种方法,只要想办法切换任务的上下文空间,切换栈即可。
栈回溯
在稳定性死机问题分析中,栈回溯是一个经常会遇到的问题!一般情况下工具会自动把栈回溯好,但是在一些情况下,我们需要手动回溯栈!
这里已一个案例作为演示:
[103516.808566][T10675] ------------[ cut here ]------------
[103516.808579][T10675] kernel BUG at include/linux/swapops.h:548!
[103516.808597][T10675] Internal error: Oops - BUG: 00000000f2000800 [#1] PREEMPT SMP
[103516.808693][T10675] Dumping ftrace buffer:
[103516.808710][T10675] (ftrace buffer empty)
[103516.809484][T10675] Hardware name: Qualcomm Technologies, Inc. Spring QRD (DT)
[103516.809488][T10675] pstate: 60400005 (nZCv daif +PAN -UAO -TCO -DIT -SSBS BTYPE=--)
[103516.809494][T10675] pc : unmap_page_range+0x8dc/0x8ec
[103516.809510][T10675] lr : unmap_page_range+0x5a8/0x8ec
[103516.809515][T10675] sp : ffffffc0427f38d0
[103516.809518][T10675] x29: ffffffc0427f3950 x28: fffffffe09cd5880 x27: ffffff81b13a5dc0
[103516.809527][T10675] x26: ffffffc0427f3938 x25: 00000071a66fb000 x24: ffffff80d9d547d8
[103516.809535][T10675] x23: 00000071a67cd000 x22: ffffff8093052e10 x21: 0000000017062de0
[103516.809542][T10675] x20: ffffffc0427f39f8 x19: ffffffc0427f3ad8 x18: ffffffc01f247058
[103516.809550][T10675] x17: ffffff8159c53400 x16: ffffff8159c53418 x15: 0000000000000001
[103516.809557][T10675] x14: ffffffc0095b0d70 x13: 0000000000000002 x12: fffffffe00000000
[103516.809564][T10675] x11: 000000000000013e x10: fffffffe03c18b08 x9 : 4000000000000034
[103516.809571][T10675] x8 : fffffffe03c18b40 x7 : 0000000000000001 x6 : fffffffdef7defe8
[103516.809579][T10675] x5 : 000000000000748e x4 : ffffffc0427f39f8 x3 : 00000071a67cd000
[103516.809586][T10675] x2 : 0000000000001000 x1 : fffffffe09cd5880 x0 : 0000000000000000
[103516.809593][T10675] Call trace:
[103516.809597][T10675] unmap_page_range+0x8dc/0x8ec
[103516.809604][T10675] unmap_vmas+0xf4/0x164
[103516.809609][T10675] exit_mmap+0x104/0x4c8
[103516.809616][T10675] __mmput+0x38/0x150
[103516.809624][T10675] mmput+0x38/0xe4
[103516.809629][T10675] do_exit+0x260/0xa98
[103516.809635][T10675] do_group_exit+0x70/0x9c
[103516.809640][T10675] get_signal+0x75c/0x874
[103516.809645][T10675] do_notify_resume+0x114/0x24b8
[103516.809653][T10675] el0_svc+0x64/0x90
[103516.809662][T10675] el0t_64_sync_handler+0x68/0xb4
[103516.809666][T10675] el0t_64_sync+0x1a4/0x1a8
死在unmap_page_range+0x8dc/0x8ec
-> 使用bt指令获取backtrace
crash> bt
PID: 10675 TASK: ffffff81b13a5dc0 CPU: 2 COMMAND: "binder:29838_8"
bt: WARNING: cannot determine starting stack frame for task ffffff81b13a5dc0
无法显示(这是因为在cpu上running的任务的调用栈)
但是已知
x29 0xffffffc0427f33b0 ---> FP
x30 0xffffffc008fb9698 ---> LR
pc 0xffffffc008fb9678 ---> PC
根据当前FP里存的是上一栈帧的FP,当前FP+8存的是上一个栈帧的LR
crash> rd -x 0xffffffc0427f33b0 0x40 ffffffc0427f33b0: ffffffc0427f33e0 ffffffc008fb9754 ffffffc0427f33c0: 00000000000006ea 00000000000005c4 ffffffc0427f33d0: 00000000000004ec ffffffffffffd8f1 ffffffc0427f33e0: ffffffc0427f3460 ffffffc001343890
可以得到上一个栈帧FP: ffffffc0427f33e0 ,上一个栈帧LR: ffffffc008fb9754
dis -l ffffffc008fb97 crash> dis -l ffffffc008fb9754 out/android14-6.1/common/arch/arm64/lib/delay.c: 56 0xffffffc008fb9754 <__const_udelay+40>: ldp x29, x30, [sp], #16
继续朝前推导栈帧
crash> rd -x ffffffc0427f33e0 0x20 ffffffc0427f33e0: ffffffc0427f3460 ffffffc001343890
可以得到栈帧FP: ffffffc0427f3460 LR: ffffffc001343890
crash> dis -l ffffffc001343890 0xffffffc001343890 <qcom_wdt_trigger_bite+1060>: adds x19, x19, #0x1
继续朝前推导
crash> rd -x ffffffc0427f3460 0x2 ffffffc0427f3460: ffffffc0427f34c0 ffffffc0013442c0
可以得到栈帧FP: ffffffc0427f34c0 LR: ffffffc0013442c0
crash> dis -l ffffffc0013442c0 0xffffffc0013442c0 <restart_wdog_handler+52>: b 0xffffffc0013442a0 <restart_wdog_handler+20>
前一个栈帧
crash> rd -x ffffffc0427f34c0 0x2 ffffffc0427f34c0: ffffffc0427f34d0 ffffffc0080e3118
可以得到栈帧FP: ffffffc0427f34d0 LR: ffffffc0080e3118
crash> dis -l ffffffc0080e3118 out/android14-6.1/common/kernel/notifier.c: 87 0xffffffc0080e3118 <atomic_notifier_call_chain+104>: mov w21, w0
crash> rd -x ffffffc0427f34d0 0x2 ffffffc0427f34d0: ffffffc0427f3510 ffffffc0080e5604 crash> dis -l ffffffc0080e5604 out/android14-6.1/common/kernel/reboot.c: 230 0xffffffc0080e5604 <do_kernel_restart+36>: ldp x29, x30, [sp], #16
可以得到栈帧FP: ffffffc0427f3510 LR: ffffffc0080e3118
crash> rd -x ffffffc0427f3510 0x2 ffffffc0427f3510: ffffffc0427f3520 ffffffc0080198dc crash> dis -l ffffffc0080198dc out/android14-6.1/common/arch/arm64/kernel/process.c: 147 0xffffffc0080198dc <machine_restart+72>: adrp x0, 0xffffffc0094df000
可以得到栈帧FP: ffffffc0427f3520 LR: ffffffc0080198dc
crash> rd -x ffffffc0427f3520 0x2 ffffffc0427f3520: ffffffc0427f3540 ffffffc0080e5404 crash> dis -l ffffffc0080e5404 out/android14-6.1/common/kernel/reboot.c: 82 0xffffffc0080e5404 <emergency_restart+40>: ldp x29, x30, [sp], #16
可以得到栈帧FP: ffffffc0427f3540 LR: ffffffc0080e5404
crash> rd -x ffffffc0427f3540 0x2 ffffffc0427f3540: ffffffc0427f35d0 ffffffc008fe755c crash> dis -l ffffffc008fe755c out/android14-6.1/common/kernel/panic.c: 440 0xffffffc008fe755c <panic+692>: adrp x0, 0xffffffc00959a000
可以得到栈帧FP: ffffffc0427f35d0 LR: ffffffc008fe755c
crash> rd -x ffffffc0427f35d0 0x2 ffffffc0427f35d0: ffffffc0427f3670 ffffffc008023114 crash> dis -l ffffffc008023114 out/android14-6.1/common/arch/arm64/kernel/traps.c: 240 0xffffffc008023114 <die+656>: mov w0, #0xb // #11
可以得到栈帧FP: ffffffc0427f3670 LR: ffffffc008023114
crash> rd -x ffffffc0427f3670 0x2 ffffffc0427f3670: ffffffc0427f36b0 ffffffc00802451c crash> dis -l ffffffc00802451c out/android14-6.1/common/arch/arm64/kernel/traps.c: 363 0xffffffc00802451c <bug_handler+72>: ldp x9, x8, [x19, #256]
可以得到栈帧FP: ffffffc0427f36b0 LR: ffffffc00802451c
crash> rd -x ffffffc0427f36b0 0x2 ffffffc0427f36b0: ffffffc0427f36d0 ffffffc0080166c0 crash> dis -l ffffffc0080166c0 out/android14-6.1/common/arch/arm64/kernel/debug-monitors.c: 332 0xffffffc0080166c0 <brk_handler+148>: cbz w0, 0xffffffc00801670c <brk_handler+224>
可以得到栈帧FP: ffffffc0427f36d0 LR: ffffffc0080166c0
crash> rd -x ffffffc0427f36d0 0x2 ffffffc0427f36d0: ffffffc0427f3700 ffffffc00803be60 crash> dis -l ffffffc00803be60 out/android14-6.1/common/arch/arm64/mm/fault.c: 954 0xffffffc00803be60 <do_debug_exception+164>: cbz w0, 0xffffffc00803be84 <do_debug_exception+200>
可以得到栈帧FP: ffffffc0427f3700 LR: ffffffc00803be60
crash> rd -x ffffffc0427f3700 0x2 ffffffc0427f3700: ffffffc0427f3740 ffffffc008fee644 crash> dis -l ffffffc008fee644 out/android14-6.1/common/arch/arm64/kernel/entry-common.c: 411 0xffffffc008fee644 <el1_dbg+88>: bl 0xffffffc008fef88c <arm64_exit_el1_dbg>
可以得到栈帧FP: ffffffc0427f3740 LR: ffffffc008fee644
crash> rd -x ffffffc0427f3740 0x2 ffffffc0427f3740: ffffffc0427f3770 ffffffc008fee448 crash> dis -l ffffffc008fee448 out/android14-6.1/common/arch/arm64/kernel/entry-common.c: 450 0xffffffc008fee448 <el1h_64_sync_handler+60>: b 0xffffffc008fee484 <el1h_64_sync_handler+120>
可以得到栈帧FP: ffffffc0427f3770 LR: ffffffc008fee448
crash> rd -x ffffffc0427f3770 0x2 ffffffc0427f3770: ffffffc0427f38b0 ffffffc008011298 crash> dis -l ffffffc008011298 out/android14-6.1/common/arch/arm64/kernel/entry.S: 580 0xffffffc008011298 <el1h_64_sync+104>: b 0xffffffc008012130 <ret_to_kernel>
可以得到栈帧FP: ffffffc0427f38b0 LR: ffffffc008011298
crash> rd -x ffffffc0427f38b0 0x2 ffffffc0427f38b0: ffffffc0427f3950 ffffffc00830a454 crash> dis -l ffffffc00830a454 out/android14-6.1/common/mm/memory.c: 1670 0xffffffc00830a454 <unmap_page_range+2268>: brk #0x800
可以得到栈帧FP: ffffffc0427f3950 LR: ffffffc00830a454
到了这儿我们起始就已经推导到dmesg中显示的死机的地方:unmap_page_range+0x8dc/0x8ec
0x8dc == 2268
证明我们推导的是正确的!
再朝前推导一次
crash> rd -x ffffffc0427f3950 0x2
ffffffc0427f3950: ffffffc0427f3a40 ffffffc00830a558
crash> dis -l ffffffc00830a558
out/android14-6.1/common/mm/memory.c: 1755
0xffffffc00830a558 <unmap_vmas+244>: add x0, sp, #0x8
可以得到栈帧FP: ffffffc0427f3a40 LR: ffffffc00830a558
此时处于unmap_vmas+0xf4处,0xf4 == 244
unmap_vmas+0xf4/0x164
关于栈回溯部分,详细可以查看OPPO内核工匠出的一篇文章,本站也转载了:crash实战:手把手教你使用crash分析内核dump
参考资料
https://wenboshen.org/posts/2015-12-18-kernel-stack