历史上的今天
今天是:2024年11月25日(星期一)
2021年11月25日 | ARMv8-异常处理
2021-11-25 来源:eefocus
ARM异常处理分为同步(synchronous)和异步异常(asynchronous)
满足下面条件为同步异常:
1. 异常是由于直接执行或尝试执行指令而生成的。
2. 提供给异常处理程序的返回地址确定保存着指示引起异常的指令。
3. 异常是精确的。
(一)同步异常分类及可能产生的原因
(1) 未定义异常:UNDEFINED exceptions
产生的原因:a)在不当的exception level执行指令;b)尝试执行未定义的指令位模式;
(2)非法执行状态异常:Illegal Execution State exceptions
产生的原因:尝试执行指令的时候,而PSTATE.IL 被设置为 1,PSTATE.IL为非法执行标志位;
(3)未对齐异常:misaligned Stack Pointer/ PC
产生的原因: SP和PC在执行使用中,未对齐;
(4)系统调用异常:SVC, HVC, or SMC
产生的原因:SVC, HVC, or SMC 指令产生的异常;SVC通常被EL0(user mode)的软件用来申请 操作系统上EL1(OS service)请求特权操作或访问系统资源。HVC主要被guest OS用来请求hypervisor的服务; SMC表示:Secure monitor Call用于secure与none-secure切换;
(5)陷阱执行异常:Traps execute Exception
产生的原因:陷阱试图执行系统控制寄存器定义的指令导致被困到更高等级EL的异常。
(6)指令数据终止异常: Instruction/Data abort Exception
产生的原因:指令异常:CPU根据一个地址预取指令,发现地址取不出数据或者无法访问,就会触发预取指异常;数据异常:当程序试图读或者写一个不合法的内存地址时发生(没有权限访问或者不存在的地址)
(7)debug异常:debug exception
产生的原因:打开调试模式时候,软件断点指令/断点/观察点/向量捕获/软件单步 等Debug产生的异常;
异步异常分类
异步异常分为外部物理异常和虚拟异常;SError or vSError:系统错误类型,包括外部数据终止;IRQ or vIRQ:外部中断 or 虚拟外部中断;
FIQ or vFIQ:快速中断 or 虚拟快速中断;
异常处理过程
保存PSTATE 数据到SPSR_ELx,(x = 1,2,3),在返回异常现场的时候,可以使用SPSR_ELx来恢复PE的状态
保存异常进入地址到ELR_ELx,同步异常(und/abt等)是当前地址,而异步异常(irq/fiq等)是下一条指令地址,在返回异常现场的时候,可以使用ELR_ELx来恢复PC值
保存异常原因信息到ESR_ELx;
PE根据目标EL的异常向量表中定义的异常地址强制跳转到异常处理程序;
堆栈指针SP的使用由目标EL决定;
用户态(EL0)不能处理异常,当异常发生在用户态时,异常级别(EL)会发生切换,默认切换到EL1(内核态),所以大部分的异常都被路由到EL1来处理;
(二)异常向量表
arch/arm64/kernel/entry.S:
199 /*
200 * Exception vectors.
201 */
202
203 .align 11
204 ENTRY(vectors)
205 ventry el1_sync_invalid // Synchronous EL1t
206 ventry el1_irq_invalid // IRQ EL1t
207 ventry el1_fiq_invalid // FIQ EL1t
208 ventry el1_error_invalid // Error EL1t
209
210 ventry el1_sync // Synchronous EL1h 常发生在内核态(EL1)并且系统配置为内核处理这些异常(这些异常导致PE迁移到EL1)时候的异常向量;
211 ventry el1_irq // IRQ EL1h
212 ventry el1_fiq_invalid // FIQ EL1h
213 ventry el1_error_invalid // Error EL1h
214
215 ventry el0_sync // Synchronous 64-bit EL0异常发生在了用户态(EL0)并且该异常需要在内核态(EL1)中处理 ;
216 ventry el0_irq // IRQ 64-bit EL0
217 ventry el0_fiq_invalid // FIQ 64-bit EL0
218 ventry el0_error_invalid // Error 64-bit EL0
219
220 #ifdef CONFIG_COMPAT
221 ventry el0_sync_compat // Synchronous 32-bit EL0
222 ventry el0_irq_compat // IRQ 32-bit EL0
223 ventry el0_fiq_invalid_compat // FIQ 32-bit EL0
224 ventry el0_error_invalid_compat // Error 32-bit EL0
225 #else
226 ventry el0_sync_invalid // Synchronous 32-bit EL0
227 ventry el0_irq_invalid // IRQ 32-bit EL0
228 ventry el0_fiq_invalid // FIQ 32-bit EL0
229 ventry el0_error_invalid // Error 32-bit EL0
230 #endif
231 END(vectors)
align 11:EL1的异常向量表保存在VBAR_EL1寄存器中(Vector Base Address Register (EL1)),该寄存器的低11bit是reserve的,11~63表示了Vector Base Address,因此这里的异常向量表是2K对齐的。
各个exception level的Vector Base Address Register (VBAR)寄存器,该寄存器保存了各个exception level的异常向量表的基地址。该寄存器有三个,分别是VBAR_EL1,VBAR_EL2,VBAR_EL3。
具体的exception handler是通过vector base address + offset得到
根据上面的异常向量表可以分为4组:
1. SError
2. FIQ
3. IRQ
4. Synchronous
4个组的分类根据发生异常时是否发生异常级别切换、和使用的堆栈指针来区别。分别对应于如下4组:
异常发生在当前级别且使用SP_EL0(EL0级别对应的堆栈指针),即发生异常时不发生异常级别切换,可以简单理解为异常发生在内核态(EL1),且使用EL0级别对应的SP。 这种情况在Linux内核中未进行实质处理,直接进入bad_mode()流程。
异常发生在当前级别且使用SP_ELx(ELx级别对应的堆栈指针,x可能为1、2、3),即发生异常时不发生异常级别切换,可以简单理解为异常发生在内核态(EL1),且使用EL1级别对应的SP。 这是比较常见的场景。
异常发生在更低级别且在异常处理时使用AArch64模式。可以简单理解为异常发生在用户态,且进入内核处理异常时,使用的是AArch64执行模式(非AArch32模式)。 这也是比较常见的场景。
异常发生在更低级别且在异常处理时使用AArch32模式。可以简单理解为异常发生在用户态,且进入内核处理异常时,使用的是AArch32执行模式(非AArch64模式)。 这种场景基本未做处理。
比如el1_error_invalid:异常发生在EL1内核态,EL1t使用SP_EL0(用户态栈),EL1h使用SP_EL1(内核态栈);而el0_error_invalid:异常发生在用户态System Error ,使用SP_EL1(内核态栈);
(三)invalid类异常处理函数接口
带invalid后缀的向量都是Linux做未做进一步处理的向量,默认都会进入bad_mode()流程,说明这类异常Linux内核无法处理,只能上报给用户进程(用户态,sigkill或sigbus信号)或die(内核态)
带invalid后缀的向量最终都调用了inv_entry,inv_entry实现如下:
233 /*
234 * Invalid mode handlers
235 */
236 .macro inv_entry, el, reason, regsize = 64
//用.MACRO伪指令定义一个宏,可以把需要重复执行的一段代码或者是一组指令缩写成一个宏;
237 kernel_entry el, regsize //(a)
238 mov x0, sp
239 mov x1, #reason
240 mrs x2, esr_el1
//保存三个参数到x0,x1,x2;
241 b bad_mode //(b)
242 .endm
244 el0_sync_invalid:
245 inv_entry 0, BAD_SYNC
246 ENDPROC(el0_sync_invalid)
(a)异常进入压栈准备 kernel_entry
59 /*
60 * Bad Abort numbers
61 *-----------------
62 */
63 #define BAD_SYNC 0
64 #define BAD_IRQ 1
65 #define BAD_FIQ 2
66 #define BAD_ERROR 3
67
68 .macro kernel_entry, el, regsize = 64
69 sub sp, sp, #S_FRAME_SIZE - S_LR // room for LR, SP, SPSR, ELR //SP指针满递减;
70 .if regsize == 32
71 mov w0, w0 // zero upper 32 bits of x0
72 .endif
73 push x28, x29
74 push x26, x27
75 push x24, x25
76 push x22, x23
77 push x20, x21
78 push x18, x19
79 push x16, x17
80 push x14, x15
81 push x12, x13
82 push x10, x11
83 push x8, x9
84 push x6, x7
85 push x4, x5
86 push x2, x3
87 push x0, x1 //pair 寄存器存放
88 .if el == 0
89 mrs x21, sp_el0 //根据EL,取出相应的栈指针;
90 get_thread_info tsk // Ensure MDSCR_EL1.SS is clear,
91 ldr x19, [tsk, #TI_FLAGS] // since we can unmask debug
92 disable_step_tsk x19, x20 // exceptions when scheduling.
93 .else
94 add x21, sp, #S_FRAME_SIZE
//如果异常级不是el0,把sp指针指向的地方加上pt_regs大小后的地址放入x21,//即指向没进入kernel_entry函数钱的sp指向的位置;
95 .endif
96 mrs x22, elr_el1 //把el1的elr寄存器内容给x22;
97 mrs x23, spsr_el1 //把el1的spsr寄存器内容给x23;
98 stp lr, x21, [sp, #S_LR]
99 stp x22, x23, [sp, #S_PC]
100 //把sp_el0,elr,lr,spsr这些内容都压入栈,用于异常返回;
101 /*
102 * Set syscallno to -1 by default (overridden later if real syscall).
103 */
104 .if el == 0
105 mvn x21, xzr
106 str x21, [sp, #S_SYSCALLNO]
107 .endif
108
109 /*
110 * Registers that may be useful after this macro is invoked:
111 *
112 * x21 - aborted SP
113 * x22 - aborted PC
114 * x23 - aborted PSTATE
115 */
116 .endm
S_FRAME_SIZE表示sizeof(structpt_regs);S_LR表示offsetof(structpt_regs, regs[30]即31号寄存器在结构体pt_regs中的偏移量;两者相减我们就知道SP,PC,PSTATE的所占字节的大小了;
107 struct pt_regs {
108 union {
109 struct user_pt_regs user_regs;
110 struct {
111 u64 regs[31];
112 u64 sp;
113 u64 pc;
114 u64 pstate;
115 };
116 };
117 u64 orig_x0;
118 u64 syscallno;
119 };
arrch64当中也不存在pop和push命令,而是通过宏来定义stp和ldp来实现,所以push x0, x1进行pair 存放到栈中;
33 .macro push, xreg1, xreg2
34 stp xreg1, xreg2, [sp, #-16]!
35 .endm
36
37 .macro pop, xreg1, xreg2
38 ldp xreg1, xreg2, [sp], #16
39 .endm
(b) arch/arm/kernel/traps.c : bad_mode()
494 /*
495 * bad_mode handles the impossible case in the vectors. If you see one of
496 * these, then it's extremely serious, and could mean you have buggy hardware.
497 * It never returns, and never tries to sync. We hope that we can at least
下一篇:ARMv8-中断处理接口
史海拾趣
|
请问Windows Embedded CE 6.0 为什么只有评估版? 我在网上到处找Windows Embedded CE 6.0的正式版,但是没有。全都是评估版。请问各位哪里有正式版下载?谢谢。… 查看全部问答> |
|
uint16 moniliangruanjianjizhun(uint16 a,uint16 b) //模拟量软件基准计算 { unsigned long int c,d; if(a>=b) a=0xffff; else { //_asm("sim"); //禁止中断后计算正常 c=a; d=c<<16; d-=c; //==d ...… 查看全部问答> |
|
一个在linux2.6.26下关于加入devfs的奇怪问题。 本人在linux2.6.26下想加入devfs。在fs/Kconfig里更改了编译选项,即定义了CONFIG_DEVFS_FS,但是在编译内核的时候,出现错误: driver/built-in.o:In function \'at91_spidev_init\'; hid-debug.c:undefined reference&nb ...… 查看全部问答> |
|
我在STM32外面接了一个ADC-TLC2543,用STM32的SPI传输ADC转换之后的结果。现在我用示波器观察STM32中MOSI(PA7)的输出,示波器中没有波形(片选信号和时钟信号正常),把MOSI接到ADC中,ADC的输出用示波器看有波形,但输入STM32中读出的数据很小, ...… 查看全部问答> |
|
关于数模模数转换的问题?可能是我对单片机理解还不够深,请问谁能解答一下? 比如用单片机时我直接置高电平是外接发光二极管点亮,与我用数模转换后输出一个电压点亮发光二极管 请问这两个过程有区别么,为什么要加数模转换?… 查看全部问答> |
|
多功能调试测试助手-精密电压源AD5791 环境说明:CubeSuites+ 文件: AD5791.c AD5791.h 函数: AD57XX_Init(AD5791); void AD57XX_SetRegisterValue(unsigned char registerAddress, unsigned long registerValue) unsigned ...… 查看全部问答> |




