研究了一下基于arm64的kernel的stack。
栈的配置
arm64的栈参考文件,arch/arm64/include/asm/memory.h
#define MIN_THREAD_SHIFT (14 + KASAN_THREAD_SHIFT)
#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)
#define IRQ_STACK_SIZE THREAD_SIZE
arm64的栈大小:
- MIN_THREAD_SHIFT在没有打卡kasan的情况下是14。
- THREAD_SIZE_ORDER是衡量栈的大小占用几个页,值为THREAD_SHIFT - PAGE_SHIFT(4k大小的页该值为12)等于2,即占用4个page。
- THREAD_SIZE为UL(1) <<THREAD_SHIFT即16K,占用4个4k的page。
- IRQ_STACK_SIZE同样为16K
目前arm64的内核栈,描述如下栈的最顶端是STACK_END_MAGIC-占用 8byte,栈的最底端是pt_regs结构体 占用320 byte。 不用之前的内核栈存储thread_info的结构,使用CONFIG_THREAD_INFO_IN_TASK。当前cpu上运行的task_struct可以通过读sp_el0获取。
线程的栈
task_struct结构体中有stack指针,用来指向线程的栈。thread结构体用来描述该task运行时候cpu的状态。
struct task_struct {
......
void *stack;
struct thread_struct thread;
.....
}
struct thread_struct {
struct cpu_context cpu_context; /* cpu context */
.....
}
cpu_context结构体可以存储寄存器
struct cpu_context {
unsigned long x19;
unsigned long x20;
unsigned long x21;
unsigned long x22;
unsigned long x23;
unsigned long x24;
unsigned long x25;
unsigned long x26;
unsigned long x27;
unsigned long x28;
unsigned long fp;
unsigned long sp;
unsigned long pc;
};
pt_regs结构体可以也可以存储寄存器
/*
* User structures for general purpose, floating point and debug registers.
*/
struct user_pt_regs {
__u64 regs[31]; //存储寄存器x0-x30
__u64 sp; //存储sp
__u64 pc; //存储pc
__u64 pstate; //存储pstate
};
/*
* This struct defines the way the registers are stored on the stack during an
* exception. Note that sizeof(struct pt_regs) has to be a multiple of 16 (for
* stack alignment). struct user_pt_regs must form a prefix of struct pt_regs.
*/
struct pt_regs {
union {
struct user_pt_regs user_regs;
struct {
u64 regs[31];
u64 sp;
u64 pc;
u64 pstate;
};
};
u64 orig_x0;
#ifdef __AARCH64EB__
u32 unused2;
s32 syscallno;
#else
s32 syscallno;
u32 unused2;
#endif
u64 orig_addr_limit;
/* Only valid when ARM64_HAS_IRQ_PRIO_MASKING is enabled. */
u64 pmr_save;
u64 stackframe[2];
};
struct cpu_context & struct pt_regs的区别
cpu_context:
cpu_context结构体主要是在内核态两个进程发生切换时,cpu_context用来保存上一个进程的相关寄存器。
pt_regs:
- pt_regs结构体主要当异常发生的时候,需要使用pt_regs来保存异常打断的线程的寄存器状态。
- 用户态的线程系统调用的时候,利用pt_regs给el0_svc_handler传递参数。代码实现如下。
el0_svc:
gic_prio_kentry_setup tmp=x1
mov x0, sp //在调用el0_svc之前,通过kernel_ventry给sp设置为指向
bl el0_svc_handler
b ret_to_user
ENDPROC(el0_svc)
do_fork的时候会申请内核栈,流程如下:
do_fork
–>_do_fork
---->copy_process
------>dup_task_struct
-------->alloc_thread_stack_node
alloc_thread_stack_node的时候申请了THREAD_SIZE_ORDER的page,并将page的地址赋值给tsk->stack。
static unsigned long *alloc_thread_stack_node(struct task_struct *tsk, int node)
{
struct page *page = alloc_pages_node(node, THREADINFO_GFP,
THREAD_SIZE_ORDER);
if (likely(page)) {
tsk->stack = page_address(page);
return tsk->stack;
}
}
copy_process函数调用了copy_thread_tls来初始化thread.cpu_context和pt_regs
static __latent_entropy struct task_struct *copy_process(
struct pid *pid,
int trace,
int node,
struct kernel_clone_args *args)
{
......
retval = copy_thread_tls(clone_flags, args->stack, args->stack_size, p,
args->tls);
......
}
copy_thread_tls的参数, args是用于创建线程的kernel_clone_args临时变量
args->stack是内核线程的线程的loop函数或者是用户线程的栈开始地址, args->stack_size是内核线程的loop函数的参数或者用户线程的栈打下,p是新创建的task_struct。
asmlinkage void ret_from_fork(void) asm("ret_from_fork");
int copy_thread_tls(unsigned long clone_flags, unsigned long stack_start,
unsigned long stk_sz, struct task_struct *p, unsigned long tls)
{
struct pt_regs *childregs = task_pt_regs(p);//childregs指向新创建的线程的栈底的pt_regs。
memset(&p->thread.cpu_context, 0, sizeof(struct cpu_context));//清空新创建的线程的cpu_context
/*
* In case p was allocated the same task_struct pointer as some
* other recently-exited task, make sure p is disassociated from
* any cpu that may have run that now-exited task recently.
* Otherwise we could erroneously skip reloading the FPSIMD
* registers for p.
*/
fpsimd_flush_task_state(p);
if (likely(!(p->flags & PF_KTHREAD))) {//非内核线程
*childregs = *current_pt_regs();//子线程的pt_regs用父线程的恢复。
childregs->regs[0] = 0;
/*
* Read the current TLS pointer from tpidr_el0 as it may be
* out-of-sync with the saved value.
*/
*task_user_tls(p) = read_sysreg(tpidr_el0);
if (stack_start) {
if (is_compat_thread(task_thread_info(p)))
childregs->compat_sp = stack_start;
else
childregs->sp = stack_start;//存储用户线程的栈开始地址
}
/*
* If a TLS pointer was passed to clone, use it for the new
* thread.
*/
if (clone_flags & CLONE_SETTLS)
p->thread.uw.tp_value = tls;
} else {//内核线程
memset(childregs, 0, sizeof(struct pt_regs));//清空pt_regs
childregs->pstate = PSR_MODE_EL1h;//设置pstate为el1
if (IS_ENABLED(CONFIG_ARM64_UAO) &&
cpus_have_const_cap(ARM64_HAS_UAO))
childregs->pstate |= PSR_UAO_BIT;
if (arm64_get_ssbd_state() == ARM64_SSBD_FORCE_DISABLE)
set_ssbs_bit(childregs);
if (system_uses_irq_prio_masking())
childregs->pmr_save = GIC_PRIO_IRQON;
p->thread.cpu_context.x19 = stack_start;//x19设置为内核线程的线程的loop函数。
p->thread.cpu_context.x20 = stk_sz;//x20设置为内核线程的线程的loop函数的参数。
}
p->thread.cpu_context.pc = (unsigned long)ret_from_fork; //pc指向 ret_from_fork,⼦进程从此处开始执⾏
p->thread.cpu_context.sp = (unsigned long)childregs; //pc指向childregs,当前线程的内核栈。
ptrace_hw_copy_thread(p);
return 0;
}
task_pt_regs宏获取 task_struct的位于栈底的pt_regs地址。current_pt_regs获取当前线程栈底的pt_regs地址。
#define task_stack_page(task) ((void *)(task)->stack)
#define task_pt_regs(p) \
((struct pt_regs *)(THREAD_SIZE + task_stack_page(p)) - 1)
#define current_pt_regs() task_pt_regs(current)
copy_thread_tls配置完成之后,会将新fork的线程添加到调度器,schedule的时候通过cpu_switch_to切换寄存器。
对于内核线程:
- 内核栈sp_el1切换到cpu_context.sp。
- 跳转到ret_from_fork执行。
- 跳转到x19的内核线程的线程的loop函数。
对于用户态的线程: - 内核栈sp_el1切换到cpu_context.sp。
- 跳转到ret_from_fork执行。
- ret_from_fork跳转到ret_to_user,ret_to_user调用kernel_exit,恢复用户态的寄存器,跳转到用户栈childregs->sp。
对于线程的调度,调用流程如下,上下文的切换见__switch_to函数
schedule
–>__schedule
---->__switch_to 后面部分汇编实现如下
.....
ffffffc0102743ac: 97f84245 bl ffffffc010084cc0 <cpu_switch_to> //注意这里面,是bl到cpu_switch_to函数,lr指针指向下一条指令ffffffc0102743b0
ffffffc0102743b0: a9417bfd ldp x29, x30, [sp,#16]
ffffffc0102743b4: a9444ff4 ldp x20, x19, [sp,#64]
ffffffc0102743b8: a94357f6 ldp x22, x21, [sp,#48]
ffffffc0102743bc: f94013f7 ldr x23, [sp,#32]
ffffffc0102743c0: f85f8e5e ldr x30, [x18,#-8]!
ffffffc0102743c4: 910143ff add sp, sp, #0x50
ffffffc0102743c8: d65f03c0 ret
从cpu_switch_to切换到next线程执行,但是prev线程还在__switch_to中,只有等到下次调度到prev线程再跳转__switch_to执行了。
线程切换的时候,cpu_switch_to实现如下:
/*
* Register switch for AArch64. The callee-saved registers need to be saved
* and restored. On entry:
* x0 = previous task_struct (must be preserved across the switch)
* x1 = next task_struct
* Previous and next are guaranteed not to be the same.
*
*/
ENTRY(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
#ifdef CONFIG_SHADOW_CALL_STACK
str x18, [x0, #TSK_TI_SCS]
ldr x18, [x1, #TSK_TI_SCS]
str xzr, [x1, #TSK_TI_SCS] // limit visibility of saved SCS
#endif
ret
ENDPROC(cpu_switch_to)
DEFINE(THREAD_CPU_CONTEXT, offsetof(struct task_struct, thread.cpu_context));
- 参数x0 = previous task_struct以及 x1 = next task_struct。
- 将偏移量 THREAD_CPU_CONTEXT 加载到寄存器 x10 中。根据该偏移,以及第一个参数x0,找到previous task_struct的 cpu_context 结构体的地址,并赋值给寄存器x8。
- 将previous task_struct的 sp 存入 x9 寄存器中,后面存储到previous task_struct的cpu_context。
- 将x19-x29以及lr依次存储到previous task_struct的cpu_context。
- 根据第二个参数x1,以及cpu_context的偏移,找到next task_struct的 cpu_context 结构体的地址,并赋值给寄存器x8。
- 将next task_struct的cpu_context,中的值依次恢复到x19-x29,以及lr寄存器。
- 将next task_struct的cpu_context,提取到寄存器x9中,并恢复寄存器sp。将next task_struc赋值给sp_el0。
- 将x18中存储的previous task_struct的thread_info.shadow_call_stack写回,将next task_struct的thread_info.shadow_call_stack,加载到x18中,函数调用的时候记录执行的函数stack。
- ret(参考arm 手册C6.2.207 RET)。该指令默认返回到lr寄存器中执行,这里的lr已经切换成next task_struct的lr。这里为啥会调到lr执行?lr存的是各个线程(无论是用户线程还是内核线程)做schedule的下一个指令的地址。
ret_from_fork的实现如下:
/*
- This is how we return from a fork.
*/
ENTRY(ret_from_fork)
bl schedule_tail
cbz x19, 1f // not a kernel thread
mov x0, x20
blr x19
1: get_current_task tsk
b ret_to_user
ENDPROC(ret_from_fork)
NOKPROBE(ret_from_fork)
- 如果寄存器x19的值是0,说明当前进程是用户进程,那么跳转到标号1
- x20中的stk_sz,存入x0作为stack_start函数的参数。
- 内核线程跳转到stack_start函数执行。
- 执行ret_to_user到用户空间。
ret_to_user调用了kernel_exit。
ret_to_user
-->kernel_exit 0
kernel_exit
msr elr_el1, x21 // set up the return data
msr spsr_el1, x22
ldp x0, x1, [sp, #16 * 0]
ldp x2, x3, [sp, #16 * 1]
ldp x4, x5, [sp, #16 * 2]
ldp x6, x7, [sp, #16 * 3]
ldp x8, x9, [sp, #16 * 4]
ldp x10, x11, [sp, #16 * 5]
ldp x12, x13, [sp, #16 * 6]
ldp x14, x15, [sp, #16 * 7]
ldp x16, x17, [sp, #16 * 8]
ldp x18, x19, [sp, #16 * 9]
ldp x20, x21, [sp, #16 * 10]
ldp x22, x23, [sp, #16 * 11]
ldp x24, x25, [sp, #16 * 12]
ldp x26, x27, [sp, #16 * 13]
ldp x28, x29, [sp, #16 * 14]
ldr lr, [sp, #S_LR]
add sp, sp, #S_FRAME_SIZE // restore sp
eret
- 将进入异常的kernel_entry的压栈的x0-x29以及lr出栈。
- add sp, sp, #S_FRAME_SIZE适用户线程的内核栈指针sp_el1指向内核栈的栈底部。保证用户线程运行在用户态的时候sp_el1指向内核栈的栈底部。
- 用户线程,进入异常,kernel_entry的时候将elr_el1(这里面保存用户线程的PC值)备份,spsr_el1(这里面保存用户线程的PSTATE值)备份,kernel_exit的时候将备份的寄存器的值恢复到elr_el1, spsr_el1中。最后调用eret,将elr_el1恢复到用户线程的PC指针,spsr_el1恢复到用户线程的pstate,切换到用户空间运行。
中断的栈
参考文件,arch/arm64/kernel/entry.S
/*
* Interrupt handling.
*/
.macro irq_handler
ldr_l x1, handle_arch_irq
mov x0, sp
irq_stack_entry
blr x1
irq_stack_exit
.endm
- 执行el0_irq/el1_irq的时候会调用irq_handler处理中断。
- irq_stack_entry会切换到中断的栈中运行,执行完后,调用irq_stack_exit退出中断的栈。
irq_stack_entry的实现如下
.macro irq_stack_entry
mov x19, sp // preserve the original sp //当前的sp备份到x19
#ifdef CONFIG_SHADOW_CALL_STACK
mov x20, x18 // preserve the original shadow stack //x18备份到x20
#endif
/*
* Compare sp with the base of the task stack.
* If the top ~(THREAD_SIZE - 1) bits match, we are on a task stack,
* and should switch to the irq stack.
*/
//下面这段代码检测我们是不是在线程的中,task_struct结构体的stack跟当前的sp匹配,则可以切换到irq的栈。
ldr x25, [tsk, TSK_STACK] // task_struct结构体的stack加载到x25
eor x25, x25, x19 //x25和x19中的值做异或操作
and x25, x25, #~(THREAD_SIZE - 1) //x25中的值和~(THREAD_SIZE - 1)做与操作,生成的存储到x25中。
cbnz x25, 9998f //x25中的值不为0,跳转到symbol:9998f
ldr_this_cpu x25, irq_stack_ptr, x26 //per cpu的irq_stack_ptr赋值给x25。
mov x26, #IRQ_STACK_SIZE //IRQ_STACK_SIZE赋值给x26。
add x26, x25, x26 //x25+x26赋值给x26,x26中存储irq_stack_ptr的最高地址。
/* switch to the irq stack */
mov sp, x26 //当前的sp设置为irq_stack_ptr
#ifdef CONFIG_SHADOW_CALL_STACK
/* also switch to the irq shadow stack */
ldr_this_cpu x18, irq_shadow_call_stack_ptr, x26 //per cpu的irq_stack_ptr赋值给x25。
#endif
9998:
.endm
中断的栈是per cpu的,在init irq的时候调用init_irq_stacks申请。
static void init_irq_stacks(void)
{
int cpu;
for_each_possible_cpu(cpu)
per_cpu(irq_stack_ptr, cpu) = per_cpu(irq_stack, cpu);
}
irq的irq_shadow_call_stack_ptr是per cpu的,在init irq的时候调用scs_init_irq申请。
void scs_init_irq(void)
{
int cpu;
for_each_possible_cpu(cpu) {
#ifdef CONFIG_SHADOW_CALL_STACK_VMAP
unsigned long *p;
p = __vmalloc_node_range(PAGE_SIZE, SCS_SIZE,
VMALLOC_START, VMALLOC_END,
GFP_SCS, PAGE_KERNEL,
0, cpu_to_node(cpu),
__builtin_return_address(0));
per_cpu(irq_shadow_call_stack_ptr, cpu) = p;
#else
per_cpu(irq_shadow_call_stack_ptr, cpu) =
per_cpu(irq_shadow_call_stack, cpu);
#endif /* CONFIG_SHADOW_CALL_STACK_VMAP */
}
}
irq_stack_exit函数主要恢复sp,以及x18寄存器
* The callee-saved regs (x19-x29) should be preserved between
* irq_stack_entry and irq_stack_exit.
*/
.macro irq_stack_exit
mov sp, x19 //irq_stack_entry备份到x19寄存器中的值恢复到sp
#ifdef CONFIG_SHADOW_CALL_STACK
mov x18, x20 //irq_stack_entry备份到x20寄存器中的值恢复到x18
#endif
.endm
函数调用的栈
看一个kernel的callstack
[66239.207428] pstate: 00400085 (nzcv daIf +PAN -UAO)
[66239.213780] pc : expire_timers+0x14c/0x1bc
[66239.219421] lr : __run_timers+0x218/0x270
[66239.224968] sp : ffffffc010013da0
[66239.229799] x29: ffffffc010013db0 x28: ffffffe3b66833d8
[66239.236683] x27: ffffff8019d0cec0 x26: ffffffe3b77bf058
[66239.243566] x25: 00000000201f7135 x24: dead000000000122
[66239.250450] x23: 0000000000000001 x22: ffffff8d48686e10
[66239.257332] x21: 0000000100fb89a8 x20: ffffff8d7dabe200
[66239.264217] x19: ffffffc010013e20 x18: ffffffc010015038
[66239.271094] x17: 0000000005f5e100 x16: 0000000000000002
[66239.277970] x15: 0000000000000000 x14: 0020000000000000
[66239.284845] x13: ffffff8d48686e10 x12: ffffffc010013e28
[66239.291720] x11: ffffff8d7dabe618 x10: ffffffc010013e28
[66239.298595] x9 : ffffffc010013e20 x8 : dead000000000122
[66239.305472] x7 : 0000000000000000 x6 : ffffffe3b7df7d60
[66239.312348] x5 : ffffff8019d0cec0 x4 : 0000000000000000
[66239.319223] x3 : 00003c3e7e4461a4 x2 : 0000000000000001
[66239.326102] x1 : ffffffc010013e20 x0 : ffffff8d7dabe200
[66239.332979] Call trace:
[66239.336929] expire_timers+0x14c/0x1bc
[66239.342209] __run_timers+0x218/0x270
[66239.347403] run_timer_softirq+0x24/0xe0
[66239.352861] __do_softirq+0x1e8/0x44c
[66239.358057] irq_exit+0xbc/0xc0
[66239.362717] __handle_domain_irq+0xa8/0x100
[66239.368441] gic_handle_irq+0xcc/0x168
[66239.373721] el1_irq+0x104/0x200
[66239.378473] lpm_cpuidle_enter+0x4a4/0x4e0
[66239.384113] cpuidle_enter_state+0x11c/0x2e0
[66239.389925] cpuidle_enter+0x38/0x50
[66239.395033] cpuidle_idle_call+0x130/0x2b4
[66239.400670] do_idle.llvm.12123644424593022367+0xbc/0x11c
[66239.407644] cpu_startup_entry+0x24/0x28
[66239.413099] secondary_start_kernel+0x1a0/0x1b8
其中对于callsatck重要的寄存器有
lr : __run_timers+0x218
sp : ffffffc010013da0
FP:x29: ffffffc010013db0
x18: ffffffc010015038
这里我们主要看__run_timers调用到expire_timers栈的情况。
看看expire_timers的汇编
FFFFFFE3B5571A14: D101C3FF expire_timers: sub sp,sp,#0x70 ; sp,sp,#112
FFFFFFE3B5571A14:F800865E str x30,[x18],#0x8 ; x30,[x18],#8
FFFFFFE3B5571A14:A9017BFD stp x29,x30,[sp,#0x10] ; x29,x30,[sp,#16]
FFFFFFE3B5571A14:A9026FFC stp x28,x27,[sp,#0x20] ; x28,x27,[sp,#32]
FFFFFFE3B5571A14:A90367FA stp x26,x25,[sp,#0x30] ; x26,x25,[sp,#48]
FFFFFFE3B5571A14:A9045FF8 stp x24,x23,[sp,#0x40] ; x24,x23,[sp,#64]
FFFFFFE3B5571A14:A90557F6 stp x22,x21,[sp,#0x50] ; x22,x21,[sp,#80]
FFFFFFE3B5571A14:A9064FF4 stp x20,x19,[sp,#0x60] ; x20,x19,[sp,#96]
FFFFFFE3B5571A14:910043FD add x29,sp,#0x10 ; x29,sp,#16
......
FFFFFFE3B5571BAC:A9417BFD ldp x29,x30,[sp,#0x10] ; x29,x30,[sp,#16]
FFFFFFE3B5571BB0:A9464FF4 ldp x20,x19,[sp,#0x60] ; x20,x19,[sp,#96]
FFFFFFE3B5571BB4:A94557F6 ldp x22,x21,[sp,#0x50] ; x22,x21,[sp,#80]
FFFFFFE3B5571BB8:A9445FF8 ldp x24,x23,[sp,#0x40] ; x24,x23,[sp,#64]
FFFFFFE3B5571BBC:A94367FA ldp x26,x25,[sp,#0x30] ; x26,x25,[sp,#48]
FFFFFFE3B5571BC0:A9426FFC ldp x28,x27,[sp,#0x20] ; x28,x27,[sp,#32]
FFFFFFE3B5571BC4:F85F8E5E ldr x30,[x18,#-0x8]! ; x30,[x18,#-8]!
FFFFFFE3B5571BC8:9101C3FF add sp,sp,#0x70 ; sp,sp,#112
FFFFFFE3B5571BCC:D65F03C0 ret
函数进入时:
- sp减去0x70开辟出函数需要的栈空间。
- 将lr中的值,既__run_timers函数中调用expire_timers函数的下一个指令。存储到x18寄存器中。并且x18的值加上0x8。该功能在CONFIG_SHADOW_CALL_STACK的时候打开。
- 将上一个函数__run_timers的x19-x30压栈,用作恢复__run_timers的栈。
- 最后将x29(FP)指向SP+0x10。expire_timers函数申请的0x70的栈,SP+0x10->SP+0x70用来压栈寄存器,SP->SP+0x10用来存放变量,如下图。
- 最后调用ret指令pc跳转到lr : __run_timers+0x218。
函数退出时候:
- x19-x30压栈出栈。
- 从x18-0x8中恢复lr寄存器。
- sp+0x70销毁expire_timers的栈。
__run_timers的汇编
FFFFFFE3B55717A4:D10303FF __run_timers: sub sp,sp,#0xC0 ; sp,sp,#192
FFFFFFE3B55717A8:F800865E str x30,[x18],#0x8 ; x30,[x18],#8
FFFFFFE3B55717AC:A9067BFD stp x29,x30,[sp,#0x60] ; x29,x30,[sp,#96]
FFFFFFE3B55717B0:A9076FFC stp x28,x27,[sp,#0x70] ; x28,x27,[sp,#112]
FFFFFFE3B55717B4:A90867FA stp x26,x25,[sp,#0x80] ; x26,x25,[sp,#128]
FFFFFFE3B55717B8:A9095FF8 stp x24,x23,[sp,#0x90] ; x24,x23,[sp,#144]
FFFFFFE3B55717BC:A90A57F6 stp x22,x21,[sp,#0xA0] ; x22,x21,[sp,#160]
FFFFFFE3B55717C0:A90B4FF4 stp x20,x19,[sp,#0xB0] ; x20,x19,[sp,#176]
FFFFFFE3B55717C4:910183FD add x29,sp,#0x60 ; x29,sp,#96
.......
FFFFFFE3B55719B4:AA1303E0 mov x0,x19 ; x0,base
FFFFFFE3B55719B8:94000017 bl 0xFFFFFFE3B5571A14 ; expire_timers
FFFFFFE3B55719BC:510006B5 sub w21,w21,#0x1 ; w21,w21,#1
.......
FFFFFFE3B55719EC:A9467BFD ldp x29,x30,[sp,#0x60] ; x29,x30,[sp,#96]
FFFFFFE3B55719F0:A94B4FF4 ldp x20,x19,[sp,#0xB0] ; x20,x19,[sp,#176]
FFFFFFE3B55719F4:A94A57F6 ldp x22,x21,[sp,#0xA0] ; x22,x21,[sp,#160]
FFFFFFE3B55719F8:A9495FF8 ldp x24,x23,[sp,#0x90] ; x24,x23,[sp,#144]
FFFFFFE3B55719FC:A94867FA ldp x26,x25,[sp,#0x80] ; x26,x25,[sp,#128]
FFFFFFE3B5571A00:A9476FFC ldp x28,x27,[sp,#0x70] ; x28,x27,[sp,#112]
FFFFFFE3B5571A04:F85F8E5E ldr x30,[x18,#-0x8]! ; x30,[x18,#-8]!
FFFFFFE3B5571A08:910303FF add sp,sp,#0xC0 ; sp,sp,#192
FFFFFFE3B5571A0C:D65F03C0 ret
这里我们主要看
- expire_timers函数的lr : __run_timers+0x218/0x270,指向__run_timers调用expire_timers的下一条指令。
- __run_timers的栈大小为0xC0。
- 根据expire_timers函数的sp:0xffffffc010013da0,计算出来__run_timers的sp=0xffffffc010013da0+0x70=0xffffffc010013e10。__run_timers的fp=__run_timers的sp + 0x60 =0xffffffc010013e70。
- expire_timers和__run_timers的栈如下:
最后关于x18寄存器,x18指向的shadow_call_stack,保存了线程中调用函数的下一条指令,既线程执行中的lr的值。关于x18寄存器当前的地址是0xffffffc010015038。
我们从expire_timers函数往前看,即0xffffffc010015030-0xffffffc010015000地址中存储的。
0xffffffc010015038:expire_timers之后的执行的先忽略。
0xffffffc010015030:存储 __run_timers调用expire_timers的下一条指令。
0xffffffc010015028:存储run_timer_softirq调用__run_timers的下一条指令。
0xffffffc010015020:存储__do_softirq调用run_timer_softirq的下一条指令。
0xffffffc010015018:存储 irq_exit调用__do_softirq的下一条指令。
0xffffffc010015010:存储 __handle_domain_irq调用irq_exit的下一条指令。
0xffffffc010015008:存储gic_handle_irq调用__handle_domain_irq的下一条指令。
0xffffffc010015000:存储el1_irq调用gic_handle_irq的下一条指令。
关于汇编指令
在BL指令执行的时候会将LR寄存器设置为需要return的address(一般是BL指令上下文的下一个),编码格式如下:
从子程序分支无条件地返回到寄存器中的地址(该指令默认返回到lr寄存器中执行),并提示这是子程序返回,编码格式如下:
使用当前异常级别的ELR和SPSR返回异常。执行时,PE从SPSR恢复PSTATE,分支切换到ELR中保存的地址,编码格式如下:
关于更多指令可以查看armv8手册的
Chapter C6-A64 Base Instruction Descriptions