Uboot學習筆記(一)ARM彙編

碎碎念

從五月份開始忙了很多事,電賽、找實習、實習、離職,可能自己有點飄飄然了吧,剛剛大三覺得自己的能力已經足夠去工作了,但是經過一個多月的工作還是覺得自己有不少的差距,不想繼續做MCU,但是Linux也僅僅是剛入門的水平,還要學習不少的東西,所以離職回學校了,繼續開始學習Linux開發。當實力撐不起你的野心的時候就應該學習。
博客也斷更了好久,應該拾起來了,會堅持記錄自己從現在直到明年秋招拿到offer這段時間的學習、複習、面試過程。

簡介

在四月份時學習了Linux的驅動的框架以及plantform總線驅動、字符驅動、中斷的知識點(當然到現在已經忘了好多了-.-),現在要開始鑽研一下Uboot,Uboot是一個十分常用的跨平臺BootLoader,可以在X86、ARM、PowerPC、Risc-V等平臺進行開機的引導,BootLoader的工作就類似於我們電腦的BIOS,在開機的時候完成硬件的初始化、時鐘系統的配置、內存管理單元mmu的初始化以及堆棧的設置,然後它會從各種存儲介質(USB otg、emmc、硬盤、sd卡)中加載操作系統內核,所以說BootLoader就是一個系統從上電開始執行的第一個程序,對於嵌入式工程師來說,會編譯燒錄BootLoader、瞭解BootLoader的工作機制、瞭解修改的方法是最最基本的能力。
爲什麼會選取Uboot來學習呢,正時因爲它開源、廣泛使用、資料豐富,對學習者十分友好,BootLoader的工作基本是類似的,所以說學會了一種別的上手起來也是觸類旁通的。
正因爲BootLoader使命的特殊性(上電開始執行),而在剛剛上電的時候是沒有C語言的運行環境的,沒有MMU不能使用虛擬內存、堆棧未進行初始化,所以說要學習Uboot,首先學習ARM的彙編是必須的,只有能讀懂ARM彙編才能去理解Uboot的工作機制。
彙編是比較低級的程序語言,也可以被叫做機器碼的助記碼,彙編代碼會因爲所使用編譯器的不同而有不同的語法,我們的開發環境是Linux,使用的是GCC編譯器,所以學習的是符合GNU語法的彙編

可以參考的文檔

學習和ARM彙編可以參考如下兩個文檔:

  • Arm cortex-A的編程手冊《ARM Cortex-A(armV7)編程手冊V4.0》、《ARM Cortex-A(armV8)編程手冊V1.0》
  • ARM指令集開發手冊《ARM ArchitectureReference Manual ARMv7-A and ARMv7-R edition》

基本語法

每條語句包含三個可選的部分
label: instruction @ comment

  • label:即標號,表示地址位置,有些指令前面可能會有標號,這樣可以通過這個標號得到指令的地址,標號也可以用來表示數據地址。注意:任何以冒號“:”結尾的標識符都會被認識是一個標號
  • instruction:即指令,也就是彙編指令或僞指令
  • comment:註釋內容

示例:

add:
	MOVS R0,#0X12 @設置R0=0X12

注意! ARM中的指令、僞指令、未操作、寄存器名等可以全部使用大寫,也可以全部使用小寫,但是不能大小寫混用

用戶可以使用.section僞操作來定義一個段,彙編系統預定義了一些段名

  • .text 代碼段
  • .data 初始化的數據段
  • .bss 未初始化的數據段
  • .rodata 只讀數據段

我們可以自己使用.section 來定義一個段,每個段以段名開始,以下一段名或者文件結束爲結束

常見的僞操作:

  • .byte 定義單字節操作,比如.byte 0x12
  • .short 定義雙字節數據,比如.byte 0x1234
  • .long 定義4字節數據,比如.long 0x12345678
  • .equ 賦值語句,格式爲.equ 變量名,表達式,比如:.equ num,0x12
  • .align 數據字節對齊,比如:.align 4 表示4字節對齊
  • .end 表示源文件結束
  • .global 定義一個全局符號,格式爲:.global symbol,比如:.global _start

GNU彙編同樣支持函數,函數格式如下:

函數名:
	函數體
	返回語句

bx語句是返回指令,函數返回語句不是必須的

常用匯編指令

常用的彙編指令基本可以分爲六類:數據處理指令、特殊寄存器操作指令、加載/存儲操作指令、棧操作指令、跳轉指令、協處理器操作指令

數據處理指令

數據處理指令包括:數據傳輸指令、算數邏輯運算指令、條件判斷指令
(1)數據傳送指令用於在寄存器和存儲器之間進行數據的雙向傳輸;
(2)算術邏輯運算指令完成常用的算術與邏輯的運算,該類指令不但將運算結果保存在目的寄存器中,同時更新CPSR中的相應條件標誌位;
(3)比較指令不保存運算結果,只更新CPSR中相應的條件標誌位。

數據傳輸指令

MOV
格式:MOV{條件}{S} 目的寄存器,源操作數
MOV指令可完成從另一個寄存器、被移位的寄存器或將一個立即數加載到目的寄存器。其中S選項決定指令的操作是否影響CPSR中條件標誌位的值,當沒有S 時指令不更新CPSR中條件標誌位的值。
指令示例:

MOV R1,R0            @將寄存器R0的值傳送到寄存器R1
MOV PC,R14           @將寄存器R14的值傳送到 PC,常用於子程序返回
MOV R1,R0,LSL#3    @將寄存器R0的值左移3位後傳送到R1

算數邏輯運算指令

在這裏插入圖片描述
在這裏插入圖片描述

比較指令

CMP
格式:CMP{條件} 操作數1,操作數2
CMP指令用於把一個寄存器的內容和另一個寄存器的內容或立即數進行比較,同時更新CPSR中條件標誌位的值。該指令進行一次減法運算,但不存儲結果,只更改條件標誌位。 標誌位表示的是操作數1與操作數2的關係(大、小、相等),例如,當操作數1大於操作操作數2,則此後的有GT後綴的指令將可以執行。
指令示例:

CMP   R1,R0       @將寄存器R1的值與寄存器R0的值相減,並根據 結果設置CPSR的標誌位
CMP R1,#100      @將寄存器R1的值與立即數100相減,並根 據結果設置CPSR的標誌位

特殊寄存器操作指令

1、MRS
格式: MRS{條件} 通用寄存器 程序狀態寄存器(CPSR或SPSR)
MRS指令用於將程序狀態寄存器的內容傳送到通用寄存器中。該指令一般用在以下兩種情況:

  • 當需要改變程序狀態寄存器的內容時,可用MRS將程序狀態寄存器的內容讀入通用寄存器,修改後再寫回程序狀態寄存器。
  • 當在異常處理或進程切換時,需要保存程序狀態寄存器的值,可先用該指令讀出程序狀態寄存器的值,然後保存。

指令示例:

MRS R0,CPSR                        @傳送CPSR的內容到R0
MRS R0,SPSR                        @傳送 SPSR的內容到R0

2、MSR
格式: MSR{條件} 程序狀態寄存器(CPSR或SPSR)_<域>,操作數
MSR指令用於將操作數的內容傳送到程序狀態寄存器的特定域中。其中,操作數可以爲通用寄存器或立即數。<域>用於設置程序狀態寄存器中需要 操作的位,32位的程序狀態寄存器可分爲4個域:

  • 位[31:24]爲條件位域,用f表示;
  • 位[23:16]爲狀態位域,用s表示;
  • 位[15:8] 爲擴展位域,用x表示;
  • 位[7:0] 爲控制位域,用c表示;
    該指令通常用於恢復或改變程序狀態寄存器的內容,在使用時,一般要在MSR指令中指明將要操作的域。
    指令示例:
MSR CPSR,R0        @傳送R0的內容到CPSR
MSR SPSR,R0        @傳送R0的內容到SPSR
MSR CPSR_c,R0     @傳送R0的內容到SPSR,但僅僅修改CPSR中的控制位域

加載/存儲指令

CPU通過使用加載/存儲指令來實現寄存器和存儲器的數據存取
1、LDR
格式: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+R2的字數據讀入寄存器R0,並將新地址R1+R2寫入R1。
LDR R0,[R1,#8] !  @將存儲器地址爲R1+8的字數據讀入寄存器R0,並將新地址 R1+8寫入R1。
LDR R0,[R1],R2        @將存儲器地址爲R1的字數據讀入寄存器R0,並將新地址 R1+R2寫入R1。
LDR R0,[R1,R2,LSL#2]! @將存儲器地址爲R1+R2×4的字數據讀入寄存器R0,並將新地址R1+R2×4寫入R1。
LDR R0,[R1],R2,LSL#2      @將存儲器地址爲R1的字數據讀入 寄存器R0,並將新地址R1+R2×4寫入R1。

2、STR
格式: STR{條件} 源寄存器,<存儲器地址>
STR指令用於從源寄存器中將一個32位的字數據傳送到存儲器中。 該指令在程序設計中比較常用,且尋址方式靈活多樣,使用方式可參考指令LDR。
指令示例:

STR R0,[R1],#8           @將R0中的字數據寫入以R1爲地址的存儲器中,並將新地址R1+8寫入R1。
STR R0,[R1,#8]           @將R0中的字數據寫入以R1+8爲地址的存儲器中。

3、LDR和STR都是按照字讀取和寫入的,也就是操作32位數據,如果要按照自己、半字進行操作的話可以在指令“LDR”後面加上B或H,比如按照自己操作就是LDRB和STRB,按照半字操作指令就是LDRH和STRH

棧操作指令

在A函數中調用B函數,調用完成後返回A函數
在跳轉之前要將當前處理器狀態保存起來(保存R0-R15這些寄存器的值)當B函數執行完成後在用前面保存的寄存器值恢復R0-R15即可
上面的過程叫做現場保護,恢復參數的過程叫做恢復現場
現場保護時進行壓棧,恢復現場時進行出棧操作
壓棧使用PUSH,出棧使用POP
PUSH和POP是一種多存儲和多加載指令,即一次可以操作多個寄存器數據,利用當前的棧指針SP來生成地址
注意處理器的堆棧是向下增長的
1、PUSH
格式:PUSH{條件} 寄存器列表
壓棧
指令示例:

PUSH {R0~R3,R12} @將R0~R3和R12壓棧
PUSH {LR} @將LR壓棧

2、POP
出棧
指令示例:

POP {LR} @先恢復LR
POP {R0~R3,R12} @再恢復R0~R3和R12

注意點:
出棧就是SP從當前位置開始,地址一次減小來提取堆棧中的數據到要恢復的寄存器列表中。
PUSH和POP的另一種寫法是“STMFD SP!”和“LDMFDSP!”
STM和LDM就是多加載和多存儲,可以連續讀寫存儲器中的多個連續數據
FD是Full Descending的縮寫,即滿遞減

跳轉指令

有多種跳轉操作:
1、直接使用跳轉指令B、BL、BX等
2、直接向PC寄存器裏面寫入數據
在這裏插入圖片描述
1、B
指令格式:B

_start:
	ldr sp,=0x80200000 @設置棧指針
	b main @跳轉到main函數

在彙編中初始化C運行環境,然後跳轉到C文件的main函數中運行
因爲跳轉到C就不會再回到彙編了,所以使用B指令
2、BL
指令格式:BL

push {r0,r1} @保存r0,r1
cps #0x13 @進入SVC模式,允許其他中斷再次進入

bl system_irqhandler @加載C語言中斷處理函數到r2寄存器中

cps #0x12 @進入IRQ模式
pop {r0,r1}
str r0, [r1, #0x10] @中斷執行完成,寫EOIR

協處理器指令

Arm有16個協處理器 ,cp15協處理器可以設置,其他的不可以設置
CP15協處理器有16個32位的寄存器 c0-c15
mrc指令將協處理器寄存器中的數據傳送到 ARM 處理器的寄存器中.若協處理器不能完成該操作,產生未定義的異常中斷。
mrc p15, 0, r0, c0, c0, 0 //將cp15協處理器的c0寄存器數值以c0,0的格式讀取到r0
mcr指令將ARM處理器的寄存器中的數據傳送到協處理器的寄存器中.若協處理器不能成功地執行該操作,將產生未定義指令異常中斷.。

異常產生指令

1、SWI
格式:SWI{條件} 24位的立即數
SWI指令用於產生軟件中斷,以便用戶程序能調用操作系統的系統例程。操作系統在SWI的異常處理程序中提供相應的系統服務,指令中24位的立即數指定用戶程序調用系統例程的類型,相關參數通過通用寄存器傳遞,當指令中24位的立即數被忽略時,用戶程序調用系統例程的類型由通用寄存器R0的內容決定,同時,參數通過其他通用寄存器傳遞。
指令示例:

SWI   0x02                @該指令調用操作系統編號位02的系統例程。

2、BKPT
格式:BKPT 16位的立即數
BKPT指令產生軟件斷點中斷,可用於程序的調試。

ARM彙編僞指令

這部分暫時就不寫了,以後看的代碼多了再返回來整理吧!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章