arm64之linux kernel的stack


研究了一下基于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

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值