1. 程式人生 > >韋東山ARM第一期作業(五)異常和中斷

韋東山ARM第一期作業(五)異常和中斷

文章目錄


01 - 作業所在路徑

  ARM裸機1期加強版\原始碼文件圖片\文件圖片\第014課_異常與中斷

02 - 作業描述

2.1 - 作業1

  014_und_exception_014_004/001有一個BUG,把以下字串多加一個字元,看看程式還能否執行。

und_string:
	.string "undefined instruction exception"

  嘗試分析反彙編,找到原因(實在找不到的話,下一節視訊有講)

2.2 - 作業2

  014_und_exception_014_004/002有一個BUG,把start.S"bl print1"去掉,看看未定義指令異常會不會發生,如果能自己解決這個BUG,那麼絕對學到家了。我用了整整一個上午才發現原因(哈,我先不說原因,後面的視訊也沒有講)。這BUG是一個同學發現的,原因是我找出來的。

2.3 - 作業3

  實際上LINUX系統中app呼叫的open, read等函式就是通過執行swi命令觸發異常,在異常處理函式中實現檔案的開啟、讀寫功能。
   我們可以實現類似的功能,寫一個led_ctrl彙編函式:
  a. 它可以接收1個引數
  b. 它會在棧中儲存引數
  c. 它呼叫swi #val,這個val來自所接收的引數
  d. 恢復引數、返回
    修改swi異常處理函式,
  a. 根據val來點燈、滅燈
    修改main函式,呼叫 led_ctrl(0),led_ctrl(1)

2.4 - 作業4

  對於按鍵S2,使用快中斷支援它。
  a. 編寫FIQ的中斷處理函式,實現儲存環境、恢復環境的功能
  b. 編寫按鍵的中斷處理函式,實現點燈、滅燈功能
  c. 修改中斷控制器,把S2對應的INTMOD

設定為FIQ

2.5 - 作業5

  最後一個程式用到了函式指標、註冊中斷等概念。這對C語言的要求越來越高。main函式中用到這3個初始化函式,
  led_init();
  interrupt_init();
  key_eint_init();
  把它們放在一個函式指標數組裡,用一個for迴圈逐個呼叫

03 - 作業解答

3.1 - 作業1解答

014_und_exception_014_004/001有一個BUG,把以下字串多加一個字元,看看程式還能否執行。
und_string:
.string “undefined instruction exception”
嘗試分析反彙編,找到原因

  不能正常執行,檢視反彙編碼,當字串為undefined instruction的時候,und_string段及後段彙編A為:

   30000020 <und_string>:
	30000020:	65646e75 	strvsb	r6, [r4, #-3701]!
	30000024:	656e6966 	strvsb	r6, [lr, #-2406]!
	30000028:	736e6920 	cmnvc	lr, #524288	; 0x80000
	3000002c:	63757274 	cmnvs	r5, #1073741831	; 0x40000007
	30000030:	6e6f6974 	mcrvs	9, 3, r6, cr15, cr4, {3}
		...
	30000035 <reset>:

  當字串為undefined instruction exception的時候,und_string段及後段彙編B為:

	30000020 <und_string>:
	30000020:	65646e75 	strvsb	r6, [r4, #-3701]!
	30000024:	656e6966 	strvsb	r6, [lr, #-2406]!
	30000028:	6e692064 	cdpvs	0, 6, cr2, cr9, cr4, {3}
	3000002c:	75727473 	ldrvcb	r7, [r2, #-1139]!
	30000030:	6f697463 	swivs	0x00697463
	30000034:	7865206e 	stmvcda	r5!, {r1, r2, r3, r5, r6, sp}^
	30000038:	74706563 	ldrvcbt	r6, [r0], #-1379
	3000003c:	006e6f69 	rsbeq	r6, lr, r9, ror #30
	30000040 <reset>:

  明顯,彙編A中,由於字串的長度問題(22位元組),導致彙編指令無法自動按4位元組對齊,於是reset段的首地址不是4位元組對齊,導致調用出錯。而彙編B中,字串長度剛剛好是32位元組,彙編指令能夠按照4位元組自動對齊,reset段首地址就是4位元組對齊,因此沒有問題

3.2 - 作業2解答

014_und_exception_014_004/002有一個BUG,把start.S中"bl print1"去掉,看看未定義指令異常會不會發生,如果能自己解決這個BUG。

  參看ARM指令集手冊,發現ARM的所有指令都可以是條件執行,指令格式如下

  cond的含義如下   原來的命令:
  .word 0xdeadc0de  /* 未定義指令 */

  這個指令中高4位bit[31:28]=1101,對應圖中黃色塊,只有當Z set, or N set and V clear, or N clear and V set (Z == 1 or N != V)的時候才會被執行,而上面的bl uart0_init改變了狀態位,使得條件不成立,沒有執行這個指令,於是沒有undefine異常發生
  根據手冊,修改bit[31:28] = 1110,對應圖中紅色塊,就是無條件執行語句,undefine異常就可以發生

 .word 0xe3000000  /* 未定義指令 */

3.3 - 作業3解答

實際上LINUX系統中app呼叫的open, read等函式就是通過執行swi命令觸發異常,在異常處理函式中實現檔案的開啟、讀寫功能。
我們可以實現類似的功能,寫一個 led_ctrl 彙編函式:
a. 它可以接收1個引數
b. 它會在棧中儲存引數
c. 它呼叫swi #val,這個val來自所接收的引數
d. 恢復引數、返回
修改swi異常處理函式,
a. 根據val來點燈、滅燈
修改main函式,呼叫 led_ctrl(0),led_ctrl(1)

  需要編寫彙編的函式,可以反編譯先看看有一個int函式引數的彙編程式是怎樣的,然後參考它的寫法,小白測試後,發現有一個int引數的C函式對應的彙編大概是這樣的:

funtion:
	mov	ip, sp						/* IP=SP;儲存SP */
	stmdb	sp!, {fp, ip, lr, pc}	/* 先對SP減4,再對fp,ip,lr,pc壓棧 */
	sub	fp, ip, #4					/* fp=ip-4;此時fp指向棧裡面的“fp” */
	sub	sp, sp, #4					/* 開闢一個int記憶體 */
	str	r0, [fp, #-16]				/* 引數壓棧,-16是跳過ip/lr/pc,到第4個位置 */
	ldr	r3, [fp, #-16]				/* 取出剛剛壓入的引數 */
	
	/* r3存放著引數,然後進行具體操作  */
	
	ldmia	sp, {r3,fp, sp, pc}	

  有了參考,led_ctrl就好寫了,不過還是不熟悉彙編,功能是實現了,不知道有沒有bug,程式碼如下

.global led_ctrl
led_ctrl:
	mov	ip, sp						/* IP=SP;儲存SP */
	stmdb	sp!, {fp, ip, lr, pc}	/* 先對SP減4,再對fp,ip,lr,pc壓棧 */
	sub	fp, ip, #4					/* fp=ip-4;此時fp指向棧裡面的“fp” */
	sub	sp, sp, #4					/* 開闢一個int記憶體 */
	str	r0, [fp, #-16]				/* 引數壓棧,-16是跳過ip/lr/pc,到第4個位置 */
	ldr	r3, [fp, #-16]				/* 取出剛剛壓入的引數 */
	cmp r3, #0						/* r3和0比較 */
	beq swi_zero					/* 如果相等,觸發swi中斷,傳參0 */
swi_one:
	swi 0x1
	b ctrl_flag
swi_zero:
	swi 0x0
ctrl_flag:
	ldmia	sp, {r3,fp, sp, pc}		

  然後在Start.S彙編do_swi中新增bl led_set

……					
mrs r0,cpsr						/* 傳參1 */
	ldr r1,=swi_string			/* 傳參2 */
	bl print_exception			/* 處理異常 */
	mov r0, r4					/* 傳參 */
	bl led_set
……

  在led.c中新增led_set()函式如下

void led_set(unsigned int *p_swi)
{
	unsigned int val = *p_swi & ~0xff000000;
	if(val == 0)
		GPFDAT &= ~(GP_ONE_ROTATE<<GPF4_DATA);
	else
		GPFDAT |= GP_ONE_ROTATE<<GPF4_DATA;
}

  先取出swi呼叫號數,然後根據號數去開關LED(GP_ONE_ROTATE和GPF4_DATA是自己寫的巨集,和老師寫的數值一樣)
  最後就可以在main()函式中呼叫led_ctrl(1)開啟LED,led_ctrl(0)關閉LED

3.4 - 作業4解答

對於按鍵S2,使用快中斷支援它。
a. 編寫FIQ的中斷處理函式,實現儲存環境、恢復環境的功能
b. 編寫按鍵的中斷處理函式,實現點燈、滅燈功能
c. 修改中斷控制器,把S2對應的INTMOD設定為FIQ

  先開啟FIQ,然後設定按鍵,再新增do_fiq和handle_fiq即可
  修改Start.S

	bic r0,r0,#(1<<6)			/* 對FIQ清0,開啟CPU中斷總開關 */

  修改interrupt_init()函式,S2為FIQ

	INTMOD = 0x01;				//設定按鍵s2為FIQ模式

  Start.S中新增do_fiq

do_fiq:
	/*
	 * 1.lr_fiq儲存了被中斷模式中下一條即將被執行的指令的地址
	 * 2.SPSR_fiq儲存有被中斷模式下的CPSR程式狀態
	 * 3.CPSR中的模式被設定為10001,進入fiq模式
	 * 4.跳到0x1C的地方執行程式do_fiq
	 */
			
	ldr sp, =0x33c00000			/* sp_fiq未設定,先設定棧 */
	sub lr, lr, #4				/* 根據手冊,返回地址lr需要減4 */
	stmdb sp!, {r0-r12,lr}		/* 儲存現場 異常處理中可能修改r0~r12以及返回地址lr先儲存*/
	mrs r0, cpsr				/* 傳參1 */
	ldr r1, =fiq_string			/* 傳參2 */
	bl handle_fiq				/* 處理異常 */
	ldmia sp!, {r0-r12,pc}^		/* 恢復現場 ^會把spsr恢復到cpsr*/
fiq_string:
	.string "fiq instruction exception"
.align 4						/* 為了讓後面的程式4位元組對齊 */

  key.c中新增handle_fiq()

/**
  * @brief  傳送按鍵中斷後的服務函式,用於區分中斷源並執行對應函式
  * @param  cpsr:儲存現場時的備份暫存器
  * @param  str:待列印的字串
  * @retval None
  */
void handle_fiq(unsigned int cpsr, char* str)
{
	unsigned int val = GPFDAT;
	//需要讀SRCPND。因為FIQ下,INTOFFSET和INTPND不會被影響
	int bit = SRCPND;

	//1.資訊輸出
	puts("[FALSE] Exception instruction!\n\r");
	puts("CPSR=");
	printHex(cpsr);
	puts(" ");
	puts(str);
	puts("\n\r");

	//2.FIQ只有一箇中斷源
	//處理中斷, 清中斷源EINTPEND
	if (val & (1<<0)) /* s2 --> gpf6 */
	{
		/* 鬆開 */
		GPFDAT |= (1<<6);
	}
	else
	{
		/* 按下 */
		GPFDAT &= ~(1<<6);
	}

	//3.清位,INTPND不會被FQI影響,從源頭開始清
	SRCPND = (1<<(bit-1));
}

3.5 - 作業5解答

最後一個程式用到了函式指標、註冊中斷等概念。這對C語言的要求越來越高。main函式中用到這3個初始化函式,
led_init();
interrupt_init();
key_eint_init();
把它們放在一個函式指標數組裡,用一個for迴圈逐個呼叫

  在main函式上方宣告型別,定義初始化陣列

typedef void(*init_func)(void);
irq_func init_array[32];
unsigned char num_init = 0;

  然後main函式中使用

int i;
init_array[num_init++] = key_eint_init;
init_array[num_init++] = leds_init;
init_array[num_init++] = timer0_init;
for(i = 0; i<num_init; i++)
	init_array[i]();