韋東山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
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的所有指令都可以是條件執行,指令格式如下
![](https://img-blog.csdnimg.cn/20190104155259263.png)
![](https://img-blog.csdnimg.cn/20190104155400736.png)
.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]();