1. 程式人生 > >arm64 linux bad mode Serror 系統異常

arm64 linux bad mode Serror 系統異常

最近在linux4.9  arm64遇到了bad mode的kernel oops

oops的內容大概如下,而且oops過後看起來系統並沒有異常,oops出現的概率比較小,而且每次oops列印的資訊中task 都是不同的,oops之後,看起來大部分時候系統還是可以正常執行的

[ 1259.654597] Bad mode in Error handler detected, code 0xbf000002 -- SError
[ 1259.661357] CPU: 12 PID: 2293 Comm: mate-settings-d Not tainted 
                4.1.15-1.el7.aarch64 #2
[ 1259.669320] Hardware name: xxxx
[ 1259.675209] task: ffffffc8c9bd1700 ti: ffffffc8c9e4c000 
                task.ti: ffffffc8c9e4c000
[ 1259.682665] PC is at 0x7f9c51abd8
[ 1259.685961] LR is at 0x7f942f9828
[ 1259.689259] pc : [<0000007f9c51abd8>] 
                lr : [<0000007f942f9828>] pstate: 80000000
[ 1259.696616] sp : ffffffc8c9e4fff0

首先先定位出現這段LOG的程式碼位置在traps.c,arm64的異常向量表中才有對這個函式的呼叫,如下圖

bad_mode 的列印資訊SError 和 code 都是從register  esr_el1中獲取的,能得到SError的只有el1 和el0的invalid_error列印的

/arch/arm64/kernel/traps.c
/*
 * bad_mode handles the impossible case in the exception vector. This is always
 * fatal.
 */
asmlinkage void bad_mode(struct pt_regs *regs, int reason, unsigned int esr)
{
	console_verbose();

	pr_crit("Bad mode in %s handler detected on CPU%d, code 0x%08x -- %s\n",
		handler[reason], smp_processor_id(), esr,
		esr_get_class_string(esr));

	die("Oops - bad mode", regs, 0);
	local_irq_disable();
	panic("bad mode");
}

/arch/arm64/kernel/entry.s

/*
 * Bad Abort numbers
 *-----------------
 */
#define BAD_SYNC	0
#define BAD_IRQ		1
#define BAD_FIQ		2
#define BAD_ERROR	3

//el1_sync 函式
el1_sync:
el1_inv:
	// TODO: add support for undefined instructions in kernel mode
	enable_dbg
	mov	x0, sp
	mov	x2, x1
	mov	x1, #BAD_SYNC
	b	bad_mode
ENDPROC(el1_sync)
//inv_entry 函式
/*
 * Invalid mode handlers
 */
	.macro	inv_entry, el, reason, regsize = 64
	kernel_entry \el, \regsize
	mov	x0, sp  
	mov	x1, #\reason
	mrs	x2, esr_el1//bad_mode 函式的引數傳遞
	b	bad_mode
	.endm

el0_sync_invalid:
	inv_entry 0, BAD_SYNC
ENDPROC(el0_sync_invalid)

el0_irq_invalid:
	inv_entry 0, BAD_IRQ
ENDPROC(el0_irq_invalid)

el0_fiq_invalid:
	inv_entry 0, BAD_FIQ
ENDPROC(el0_fiq_invalid)

el0_error_invalid:
	inv_entry 0, BAD_ERROR
ENDPROC(el0_error_invalid)

#ifdef CONFIG_COMPAT
el0_fiq_invalid_compat:
	inv_entry 0, BAD_FIQ, 32
ENDPROC(el0_fiq_invalid_compat)

el0_error_invalid_compat:
	inv_entry 0, BAD_ERROR, 32
ENDPROC(el0_error_invalid_compat)
#endif

el1_sync_invalid:
	inv_entry 1, BAD_SYNC
ENDPROC(el1_sync_invalid)

el1_irq_invalid:
	inv_entry 1, BAD_IRQ
ENDPROC(el1_irq_invalid)

el1_fiq_invalid:
	inv_entry 1, BAD_FIQ
ENDPROC(el1_fiq_invalid)

el1_error_invalid:
	inv_entry 1, BAD_ERROR
ENDPROC(el1_error_invalid)

到底SError是ARM的一種什麼異常,AArch64(ARM64)架構中,主要包括如下4中型別的異常:

  1. Synchronous exception(同步異常),“同步”可以理解為:發生異常的指令為導致異常的指令,即當導致異常發生的指令執行時能立即觸發異常。 包括ARM架構中定義的所有Aborts異常,如:指令異常、資料異常、對齊異常等。
  2. SError,System Error,是一種非同步異常,後面再仔細說明。
  3. IRQ,普通的中斷,是非同步異常。
  4. FIQ,高優先順序的中斷,是非同步異常。

SError本質上是一種非同步外部abort(asynchronous external abort)。所謂非同步,就說是發生異常時硬體(相關的暫存器)不能提供有效資訊用於分析定位,異常發生時的指令,並不是導致異常的指令。外部意味著異常來自於外部儲存系統(相較於CPU來說,MMU是內部的)。通常是硬體觸發的問題,比如硬體的clock出問題或者硬體本身的問題導致的bus訪問硬體時出現問題。

Linux核心中,對SError進行了捕獲,設定了相應的中斷向量,當並未做實際的處理,只是上報異常,並終止程序會核心,因為對於核心來說,SError是致命的,核心自身無法做相應的修復操作,核心不知道具體原因,也不知道如何修復。

分析錯誤列印: Bad mode in Error handler detected, code 0xbf000002 – SError

可以知道,發生了SError(也就是System Error異常),錯誤碼(ESR暫存器內容)為:0xbf000002

從ARM developer上查到的ESR暫存器的描述

The ESR_EL1 holds syndrome information for an exception taken to EL1.

ESR_EL1 is a 32-bit register, and is part of the Exception and fault handling registers functional group.

Figure B2-34 ESR_EL1 bit assignments

EC, [31:26]

Exception Class. Indicates the reason for the exception that this register holds information about.

IL, [25]

Instruction Length for synchronous exceptions. The possible values are:

0 16-bit.
1 32-bit.

This field is 1 for the SError interrupt, instruction aborts, misaligned PC, Stack pointer misalignment, data aborts for which the ISV bit is 0, exceptions caused by an illegal instruction set state, and exceptions using the 0x00 Exception Class.

ISS, [24:0]

Syndrome information.

由以上資訊得出前六位bit[31:26]為101111,對應具體的異常型別,檢視ArmV8手冊:

101111 SError interrupt即系統異常中斷。

ISV, bit [24] Instruction syndrome valid. Indicates whether the rest of the syndrome information in this register is valid. 0 No valid instruction syndrome. ISS[23:0] are RES0. 1 ISS[23:0] hold a valid instruction syndrome.

本code中,該位為1,說明bit[23:0]中存放了instruction syndrome(出錯指令的具體資訊)

IS, bits [23:0] IMPLEMENTATION DEFINED syndrome information that can be used to provide additional information about the SError interrupt. Only valid if bit[24] of this register is 1. If bit[24] is 0, this field is RES0.

ELR_ELn  n代表不同的執行優先順序,這個暫存器儲存著觸發異常時候的指令

如何dump 該register嘗試改了下entry.s,只有inv_entry 這裡會有BAD_ERROR的異常會走到,所以做了如下修改,發現show_regs列印的異常發生的時候的PC就是從elr_el1的copy。

所以bad_mode dump的資訊就是all kernel have了,沒有想到有效的方法,只能壓測看下error附近的LOG試著夾下code

/*
 * Invalid mode handlers
 */
	.macro	inv_entry, el, reason, regsize = 64
	kernel_entry \el, \regsize
	mov	x0, sp
	mov	x1, #\reason
	mrs	x2, esr_el1

​​​​​​​    mrs x3, elr_el1  
//add this line to pass para to bad_mode function
//SError always call this function 
	b	bad_mode
	.endm


asmlinkage void bad_mode(struct pt_regs *regs, int reason, unsigned int esr, u64 elr_regs)
//add a para
{
	siginfo_t info;
	void __user *pc = (void __user *)instruction_pointer(regs);
	console_verbose();

	pr_crit("Bad mode in %s handler detected on CPU%d, code 0x%08x -- %s\n",
		handler[reason], smp_processor_id(), esr,
		esr_get_class_string(esr));
    pr_cirt("dump elr %llx\n",elr_regs);
//add printk to dump this register
	__show_regs(regs);