我們在學習 Linux 嵌入式開發的時候,了解 ARM 匯編是很有必要的,雖然我們使用匯編編寫代碼的情況很少,但是有些情況下我們需要簡單編寫一些匯編程序來協助我們調試板子(因為我們的 i.MX6 UL 終結者開發板使用的 cpu 是 ARM Cortex-A7 架構的,cpu 剛上電必須要運行匯編代碼,來初始化 cpu 的一些內部功能,然后設置好 C 語言的環境,才能運行 C 語言),比如一塊板子焊接回來,我們燒寫鏡像沒有問題,但是燒寫完,串口沒有打印信息,不能正常啟動,這時我們應該不能確定是軟件問題還是硬件問題,我們可以在匯編里面通過點燈(點亮板子上的 led)的方式來確定下 cpu 有沒有運行,此時我們就要用到匯編,因為 C 語言還沒有被執行。所以我們需要了解并掌握下 ARM 匯編。 81 .1 GNU 匯編語法 匯編語法 GNU 匯編語法適用于所有的架構,并不是 ARM 獨享的,GNU 匯編由一系列的語句組成,每行一條語句, 每條語句有三個可選部分,如下: label:instruction @ comment label 即標號,表示地址位置,有些指令前面可能會有標號,這樣就可以通過這個標號得到指令的地址,標號也可以用來表示數據地址。注意 label 后面的“:”,任何以“:”結尾的標識符都會被識別為一個標號。 instruction 即指令,也就是匯編指令或偽指令。 @符號,表示后面的是注釋,就跟 C 語言里面的“/*”和“*/”一樣,其實在 GNU 匯編文件中我們也可以 使用“/*”和“*/”來注釋。 comment 就是注釋內容。 比如下面的代碼: sum: MOVS R0, #0X5a @設置 R0=0X5a 上面代碼中“add”就是標號,“MOVS R0, #0X5a”就是指令,后面的“@設置 R0=0X5a”就是注釋。( 注意 :ARM 匯編中的指令 、 偽指令 、 偽操作 、 寄存器等 , 都可以全部使用大寫 , 也可以全部使用小寫 , 但是不能大小寫混用) 我們也可以使用.section 偽操作來定義一個段名,匯編系統中預定義了一些段名,如下: .text //代碼段 .bss //未初始化的數據段 .data //初始化的數據段 .rodata //只讀數據段 我們自己也可以使用.section 定義一個段,如下: .section .test_section @定義一個 test_setcion 段 我們知道在 C 語言中,程序的入口是 main 函數,在匯編中程序的入口標號是“_start”,下面的代碼就是使用_start 作為入口標號: .global _start _start: ldr r0, =0x5a @r0=0x5a 上面代碼中.global 是偽操作,表示_start 是一個全局標號,類似 C 語言里面的全局變量,常見的偽操作有: .byte //定義單字節數據,比如.byte 0x5a .short //定義雙字節數據,比如.short 0x5a5a .long //定義 4 字節數據,比如.long 0x5a5a5a5a .equ //賦值語句,格式為: .equ 變量名,表達式,比如.equ num, 0x5a,表 示 num=0x5a .align //數據字節對齊,比如:.align 4 表示 4 字節對齊 .end //表示源文件結束 .global //定義一個全局符號,格式為:.global symbol,比如:.global _start 上面這些是最常見的偽操作,GNU 匯編還有其他的偽操作,如果想更箱子的了解偽操作可以參考《ARM Cortex-A(armV7)編程手冊 V4.0》(光盤目錄:i.MX6UL 終結者光盤資料\10_其它參考資料) GNU 匯編也支持函數,格式如下: .type fun_name, @function fun_name: #content ret .type 指令指定 fun_name 為其它匯編程序調用此函數時的地址 fun_name 也為函數名 @function 表示函數內容開始 ret 指令表示函數結束,返回到父函數調用子函數處 定義函數的例子如下: .type add_fun, @function add_fun: add %ebx, %ebx movl %ebx, %eax ret 8.2 ARM 匯編指令 下面我們來學習下 ARM 的常用匯編指令,這里我們參考了文檔《ARM ArchitectureReference Manual ARMv7-Aand ARMv7-R edition》(光盤目錄:i.MX6UL 終結者光盤資料\10_其它參考資料)。 8.2.1 MOV 指令 MOV 指令用于將數據從一個寄存器拷貝到另外一個寄存器,或者將一個立即數傳遞到寄存器里面,使用示例如下: MOV R0,R1 @將寄存器 R1 中的數據傳遞給 R0,即 R0=R1 MOV R0, #0X12 @將立即數 0X12 傳遞給 R0 寄存器,即 R0=0X12 8.2.2 MRS 指令 MRS 指令用于將特殊寄存器(如 CPSR 和 SPSR)中的數據傳遞給通用寄存器,要讀取特殊寄存器的數據只能 使用 MRS 指令!使用示例如下: MRS R0, CPSR @將特殊寄存器 CPSR 里面的數據傳遞給 R0,即 R0=CPSR 8.2.3 MSR 指令 MSR 指令和 MRS 剛好相反,MSR 指令用來將普通寄存器的數據傳遞給特殊寄存器,也就是寫特殊寄存器,寫 特殊寄存器只能使用 MSR,使用示例如下: MSR CPSR, R0 @將 R0 中的數據復制到 CPSR 中,即 CPSR=R0 8.2.4 LDR 指令 LDR 指令用于從存儲器中將一個 32 位的字數據傳送到目的寄存器中。該指令通常用于從存儲器中讀取32 位的字數據到通用寄存器,然后對數據進行處理。當程序計數器 PC 作為目的寄存器時,該指令從存儲器中讀取的字數據被當作目的地址,從而可以實現程序流程的跳轉。該指令在程序設計中比較常用,其尋址 方式靈活多樣。使用示例如下: LDR R0,[R1] //將存儲器地址為 R1 的字數據讀入寄存器 R0 LDR R0,[R1,R2] //將存儲器地址為 R1+R2 的字數據讀入寄存器 R0 LDR R0,[R1,#8] //將存儲器地址為 R1+8 的字數據讀入寄存器 R0 LDR R0,[R1],R2 //將存儲器地址為 R1 的字數據讀入寄存器 R0,幵將新地址 R1 +R2 寫入 R1 8.2.5 STR 指令 STR 是將數據寫入到存儲器中,示例代碼如下: STR R1, [R0] //將 R1 中的值寫入到 R0 中所保存的地址中 8.2.6 入棧,出棧指令 棧被定義為一種先進后出的數據結構,即最后進棧的元素將被最先彈出來.這很像許多人進入一條窄得只能 容納一個人通過的小道,如果要從這條道往回退出來的話,那么最先退出來的人是最后一個進入小道的人.所以棧具有后進先出的性質(LIFO)。我們使用 push 指令實現入棧,示例代碼如下: PUSH {R0~R3, R12} @將 R0~R3 和 R12 壓棧 出棧指令使用 POP,示例代碼如下: POP {R0~R3,R12} @在恢復 R0~R3,R12 8.2.7 跳轉指令 跳轉指令用于實現程序流程的跳轉,在 ARM 程序中有兩種方法可以實現程序流程的跳轉: 1. 使用專門的跳轉指令(B B 、 BL 、 BX 、 BLX ) 2. 器 直接向程序計數器 C PC 寫入跳轉地址值 一般我們常用專門跳轉指令實現程序的跳轉,下面我們來看下跳轉指令的使用: B 指令 B 指令是最簡單的跳轉指令。一旦遇到一個 B 指令,ARM 處理器將立即跳轉到給定的目標地址,從那里繼續執行。注意存儲在跳轉指令中的實際值是相對當前 PC 值的一個偏移量,而不是一個絕對地址,它的值由匯編器來計算(參考尋址方式中的相對尋址)。它是 24 位有符號數,左移兩位后有符號擴展為 32 位,表示的有效偏移為 26 位(前后 32MB 的地址空間)。以下指令: B Label ;程序無條件跳轉到標號 Label 處執行 BL 指令 跳轉之前,會在寄存器 R14 中保存 PC 的當前內容,因此,可以通過將 R14 的內容重新加載到 PC 中,來返回到跳轉指令之后的那個指令處執行。該指令是實現子程序調用的一個基本但常用的手段。以下指令: BL Label ;當程序無條件跳轉到標號 Label 處執行時,同時將當前的 PC 值保存到 R14 中 BLX 指令 從 ARM 指令集跳轉到指令中所指定的目標地址,并將處理器的工作狀態由 ARM 狀態切換到 Thumb 狀態,該指令同時將 PC 的當前內容保存到寄存器 R14 中。因此,當子程序使用 Thumb 指令集,而調用者使用 ARM 指令集時,可以通過 BLX 指令實現子程序的調用和處理器工作狀態的切換。同時,子程序的返回可以通過將寄存器 R14 值復制到 PC 中來完成。指令格式如下: BLX 目標地址 X BX 指令 跳轉到指令中所指定的目標地址,目標地址處的指令既可以是 ARM 指令,也可以是 Thum 指令,指令格式如下: BX{條件} 目標地址 8.2.8 邏輯運算指令 常用的邏輯運算符如下: AND a, b //a = a & b 按位與 AND a, b, #c //a = b & #c 按位與 AND a, b, c //a = b & c 按位與 ORR a, b //a = a | b 按位或 ORR a, b, #c //a = b | #c 按位或 ORR a, b, c //a = b | c 按位或 BIC a, b //a = a & (~b) 位清除 BIC a, b, #c //a = b & (~#c) 位清除 BIC a, b, c //a = b & (~c) 位清除 ORN a, b //a = a | (b) 按位或非 ORN a, b, #c //a = b |(#c) 按位或非 ORN a, b, c //a = b |(c) 按位或非 EOR a, b //a = a ^b 按位異或 EOR a, b, #c //a = b ^ #c 按位異或 EOR a, b, c //a = b ^ c 按位異或 8.2.9 算數運算符 ADD, a, #b // a = a + #b 加法運算 ADD a, b, c //a = b + c 加法運算 ADD a, b, #c //a = b + #c 加法運算 SUB a, #b //a = a - #b 減法運算 SUB a, b, c //a = b - c 減法運算 SUB a, b, #c //a = b - #c 減法運算 MUL a, b, c //a = b * c 懲罰運行算(32 位) UDIV a, b, c //a = b/c 無符號除法 SDIV a, b, c //a = b/c 有符號除法 本節主要講解了一些最常用的指令,還有很多不常用的指令沒有講解,但是夠我們后續學習用了。要想詳細的學習 ARM 的所有指令請參考《 ARM ArchitectureReference Manual ARMv7-A and ARMv7-Redition.pdf》和《ARM Cortex-A(armV7)編程手冊 V4.0.pdf》這兩份文檔,他們分別在光盤資料的目錄:i.MX6UL 終結者光盤資料\10_其它參考資料。
|