目錄
哈佛結構
arm指令格式
?有符號數的溢出(8bit)?
無符號數的進位/借位
CPSR(當前程序狀態寄存器)
ARM模式
arm異常類型
?ARMv7架構異常向量表
?arm異常的處理流程
arm寄存器
堆棧指針寄存器
arm模式切換流程
LDR指令、STR指令
指令流水線(三級流水線+馮諾依曼體系)
三級流水線
帶有訪問存儲器指令流水線(LDR/STR)
分支流水線
B/BL
arm立即數編碼規則
arm條件碼表
arm移位操作
邏輯移位vs算術移位
邏輯移位的本質----無論左移還是右移,空出的位均補零,不保留符號信息
LDM/STM指令
?LDM指令:
軟中斷指令
軟中斷的本質
??????? arm Linux系統調用實現
?STM指令
arm尋址方式
多寄存器尋址
堆棧尋址
?標簽label
全局標簽
?定義全局函數
?定義全局變量
?使用場景
局部標簽
引用方式
引用規則
使用場景
弱標簽
?弱標簽的覆蓋原則
使用場景
靜態標簽
靜態標簽的定義
?使用場景
偽指令
NOP偽指令
ADR偽指令
ADRL偽指令
LDR偽指令
LDR偽指令的兩種形式
arm偽操作
匯編調用c程序
?c調用匯編程序
內聯匯編
飽和算術運算:
協處理器
內聯匯編的基本語法
?
哈佛結構
核心特征是將指令存儲和數據存儲完全分離,各自使用獨立的存儲器和總線
arm指令格式
arm指令集采用固定長度32位的指令格式
?有符號數的溢出(8bit)
無符號數的進位/借位
CPSR(當前程序狀態寄存器)
ARM模式
用戶模式,用戶程序的工作模式,用戶模式沒有權限去操作其它硬件資源,只能執行處理自己的 數據;
系統模式,首先用戶模式與系統模式共用同一套寄存器,系統模式為特權模式,系統模式可以直接訪問硬件資源
svc模式,主要用于完成系統初始化的工作,cpu上電之后默認的工作模式,當調用swi/svc,cpu處于svc模式;
中止模式,當用戶訪問非法內存地址或者訪問某一塊不具有讀寫權限的內存地址時處于此工作模式;
Fiq模式 (快速中斷模式),用來處 理對時間要求比較緊急的中斷請求,主要用于高速數據傳輸及通道處理中;
IRQ模式(普通中斷模式),用于處理一般的中斷請求,通常在硬件產生中斷信號之后自動進入IRQ模式;
arm異常類型
?ARMv7架構異常向量表
?arm異常的處理流程
arm寄存器
arm處理器在任何工作模式下共用一個PC寄存器(程序計數寄存器)與CPSR寄存器(當前程序狀態寄存器)。
堆棧指針寄存器
?棧按照棧的生長方向(遞增、遞減)與數據的存儲方式(滿棧、空棧)分為四種類型
1. 滿遞增棧 2. 滿遞減棧 3. 空遞增棧 4. 空遞減棧
棧頂指針(sp): 始終指向當前棧的棧頂,即最后一個入棧元素的地址(滿棧)或者 下一個可用空間的地址(空棧)
遞增棧:棧頂向高地址方向增長
遞減棧:棧頂向低地址方向增長
push操作時處理器先減小SP值,然后將指定寄存器存儲到SP寄存器指向的存儲器地址
?POP操作時處理器先將SP指向的存儲器地址存儲到指定寄存器中,然后將SP寄存器值增加。
arm模式切換流程
在ARM中,當發生異常時,處理器會自動切換到對應的異常模式,每種異常模式都有自己獨立的物理寄存器,R13(SP:堆棧寄存器)、R14(LR:鏈接寄存器)SPSR(備份程序狀態寄存器)。
當從用戶模式進入IRQ模式時,保存需要保護的寄存器到IRQ模式的堆棧,步驟如下:
- 用戶程序在用戶模式下運行,使用SP_usr;
- 發生中斷,處理器自動切換到IRQ模式,此時SP自動變為SP_irq。
保存返回地址到LR_irq(
LR_irq = 當前PC + 4
)。保存當前CPSR到SPSR_irq。
- 中斷處理程序開始執行,使用SP_irq指向的堆棧保存寄存器(如R0-R12, LR_irq)
- 處理完中斷后,從SP_irq的堆棧中恢復寄存器。
- 返回到用戶模式,繼續使用SP_usr。
IRQ_Handler:; 1. 保存用戶模式的寄存器(R0-R12, LR_irq保存的是返回地址)STMFD SP!, {R0-R12, LR} ; 使用SP_irq壓棧(SP_irq -= 56字節); 2. 處理中斷(如讀取定時器狀態)BL Handle_Timer_Interrupt; 3. 恢復寄存器并返回用戶模式LDMFD SP!, {R0-R12, LR} ; 從SP_irq彈棧(SP_irq += 56字節)SUBS PC, LR, #4 ; 返回到用戶模式的下一條指令(恢復CPSR)
?
LDR指令、STR指令
LDR指令(Load Register)是用于從內存中加載數據到寄存器的核心指令
?STR指令(Store Register)用于將寄存器中的值存儲到內存中
指令流水線(三級流水線+馮諾依曼體系)
馮諾依曼體系:數據總線只有一根,取指令與取數據采用同一根數據線
三級流水線
帶有訪問存儲器指令流水線(LDR/STR)
LDR指令
? ? ? step1:訪問內存——取數據——數據總線被占用——AND指令被延時(stall)
? ? ? step2:數據寫回寄存器——寫數據——數據總線被占用——其他指令被延時
分支流水線
BL(Branch with Link)指令核心功能是跳轉到目標地址執行子程序,同時將返回地址(下一條指令地址)保存到鏈接寄存器LR;
B(Branch)指令的核心功能是改變程序執行流程,常用于循環控制(for/while)、條件分支(if-else) 和代碼塊跳轉
B/BL
BL <label> #label中存儲的是目標地址
跳轉地址計算流程:
?eg:
arm立即數編碼規則
ARM立即數采用 "8 位數值 + 4 位循環右移(Rotate Right)"的組合編碼:
arm條件碼表
假設有符號數
A
和B
,比較A ≥ B
等價于判斷A - B ≥ 0
設
S = A - B
,則
- 若
S ≥ 0
,則S
的符號位為 0(N=0)。- 若計算過程未溢出(V=0),則
N=0
直接表示S ≥ 0
。- 若計算過程溢出(V=1),則
N=1
才表示S ≥ 0
(溢出導致符號位反轉)。
A ≥ B
的充要條件是:N 與 V 狀態一致(N?V=0)
arm移位操作
邏輯移位vs算術移位
邏輯移位的本質----無論左移還是右移,空出的位均補零,不保留符號信息
算術移位的本質----右移時保留符號位(高位填充原符號位),左移與邏輯左移一致
LDM/STM指令
?LDM指令:
軟中斷指令
軟中斷的本質
主動觸發:與硬件中斷(由外部設備觸發,如鍵盤、時鐘)不同,軟中斷是程序通過顯式執行指令主動發起的。
特權級切換:在保護模式下,軟中斷通常用于從用戶態(低特權級)切換到內核態(高特權級),以便執行受保護的操作(如訪問硬件、管理內存)。
??????? arm Linux系統調用實現
系統調用號是操作系統內核為每個系統調用分配的唯一編號!
?STM指令
arm尋址方式
指令如何表達 “去哪里找數據” 或 “把結果存到哪里”?這需要一套規則 —— 即尋址模式
尋址是確定指令中操作數位置的過程。操作數可以存儲在:
1. 寄存器(如 R0~R15)
2. 內存(如變量、數組)
3. 立即數(指令中直接給出的常數)
多寄存器尋址
?
堆棧尋址
push/pop
?標簽label
標簽是一個符號化的地址標記,指向代碼或數據在內存中的位置;
在 ARM Linux 匯編開發中,標簽(Label)的分類主要基于其 作用域(可見性范圍)、鏈接屬性(符號在鏈接階段的行為)以及 符號強弱性(是否允許被覆蓋)可以分為 全局標簽、局部標簽、靜態標簽、弱標簽。
全局標簽
使用
.global
偽指令聲明標簽為全局,允許其他文件引用
.global 標簽名 @ 聲明為全局標簽
標簽名: @ 標簽定義匯編指令
?定義全局函數
@定義一個全局函數 add_two
.global add_two @ 聲明為全局
add_two:ADD R0, R0, R1 @ R0 = R0 + R1BX LR @ 返回
?定義全局變量
.data
.global g_counter @ 聲明全局變量
g_counter:.word 0x0 @ 初始值為0
?使用場景
局部標簽
在 ARM 匯編中,局部標簽是僅在 當前代碼塊或作用域內有效 的臨時符號,用于簡化循環、條件分支等控制邏輯,避免命名沖突。局部標簽通過 數字 + 冒號 定義,并通過
b
(backward)和f
(forward) 引用;
定義格式:以數字開頭,后跟冒號(如
1:
、2:
);作用域:僅在當前代碼塊(如函數或循環體)內有效;
引用方式
1b
:向后跳轉(Backward),指向 最近的前一個1:
標簽。
1f
:向前跳轉(Forward),指向 最近的后一個1:
標簽
引用規則
就近原則:根據
b
/f
方向,尋找最近的同名標簽。可重復使用:同一代碼塊內可多次定義同名局部標簽(如
1:
)。
使用場景
.text
.global _start_start:MOV R0, #5 @ 初始化 R0 = 5loop_start: @ 全局標簽(可選)
1: @ 局部標簽1(循環入口)SUBS R0, R0, #1 @ R0 = R0 - 1,更新標志位BEQ 1f @ 若 R0 == 0,向前跳轉到局部標簽1(循環出口)B 1b @ 否則,向后跳轉到局部標簽1(繼續循環)1: @ 局部標簽1(循環出口)MOV R7, #1 @ 退出系統調用號SVC #0 @ 觸發系統調用
弱標簽
在 ARM 匯編中,弱標簽(Weak Label) 是一種允許 同名符號覆蓋 的標簽類型。它常用于定義 默認實現 或 可選功能,當鏈接器發現同名的 強標簽(Strong Label) 時,會優先使用強標簽的定義,弱標簽的定義則被忽略。
.weak 標簽名 @ 聲明為弱標簽
標簽名: @ 標簽定義匯編指令
?弱標簽的覆蓋原則
強標簽優先:若存在同名的強標簽(通過
.global
聲明),鏈接時使用強標簽的定義。無強標簽時生效:若未定義強標簽,鏈接器使用弱標簽的實現。
允許多個弱標簽:若多個弱標簽同名,鏈接器隨機選擇其一(通常報警告)。
使用場景
靜態標簽
靜態標簽是僅在 當前匯編文件內有效 的符號,不可被其他文件引用。它的核心作用是 封裝內部實現,避免命名沖突,提升代碼模塊化;
靜態標簽的定義
?使用場景
在文件
utils.s
中定義靜態函數internal_add
,僅在文件內部調用;在main.s
中嘗試調用該函數將失敗。
偽指令
NOP偽指令
NOP(
No Operation)偽指令 核心功能 1. 實現延時 2.?對齊指令地址
ADR偽指令
ADR偽指令 可以獲取程序的運行地址,而不是鏈接地址
ADRL偽指令
注意: 標簽地址必須與
ADRL
指令位于同一代碼段內
LDR偽指令
LDR偽指令(Pseudo-Instruction)主要功能如下:
- 加載32位立即數到寄存器;
- 加載標簽地址到寄存器;
LDR偽指令的兩種形式
?注意:#offset是當前指令地址(PC)到 文字池中目標數據地址 的偏移量
arm偽操作
匯編調用c程序
匯編調用c程序流程
@1 c文件中實現c語言函數
@2 .extern偽操作聲明c函數 .extern my_add
@3.1 根據ATPCS標準,R0,R1,R2,R3用于傳遞參數,由于c語言函數的參數可能超過4個
@3.2 采用偽指令ldr初始化棧空間
@4 傳遞參數 mov r0,#0x01
@5 調用函數 BL/B my_add
@start.s文件.text.global _start @將_start聲明為全局的.extern my_add_start:@先初始化要使用的棧空間ldr sp, =0x400mov r0, #10mov r1, #20mov r2, #30mov r3, #40mov r5, #50push {r5}bl my_addloop:b loop.data.align 4 @按2^4字節對齊
.space 4096.end
//add.c文件int my_add(int x, int y, int z, int m, int n) {return x + y + z + m + n;}
?c調用匯編程序
1. 定義匯編函數my_add, 注意傳遞的參數、返回值滿足ATCPS規則
2. 匯編文件中通過.global 把my_add聲明為全局函數
3. C語言中通過extern聲明外部函數my_add
@start.s文件
.text
.global _start
.extern main
_start:ldr sp,=0x400bl main
.data.align 4.space 4096
loop:b loop
.end
//main.c文件
extern int my_add(int x,int y,int z);
int main()
{int ret=my_add(10,20,30);return ret;
}
@ add.s文件
.arm
.text
.global my_add
my_add:add r5,r0,r1add r5,r5,r2mov r0,r5 @函數的返回值通過R0傳遞
.end
內聯匯編
內聯匯編允許在高級語言(如C/C++)中直接嵌入匯編代碼,內聯匯編的應用場景:
- 程序中使用飽和算術運算(Saturating Arithmetic)
- 程序需要操作協處理器;
- C程序中需要操作程序狀態寄存器;
飽和算術運算:
飽和算術運算是一種在數值運算中防止溢出的處理方式。當運算結果超出數據類型的表示范圍時,結果會被“飽和”到該數據類型能表示的最大值或最小值;
協處理器
ARM架構通過支持協處理器來擴展處理器的功能。ARM架構的處理器支持最多16個協處理器,通常稱為CP0~CP15;
CP15,提供系統控制功能,主要用于配置MMU、TLB和Cache、異常向量表的地址設置;
內聯匯編的基本語法
//__asm__告知編譯器不用檢查后面的內容,只需要交給匯編器進行處理
__asm__ volatile ("asm code" : 輸出操作數列表 : 輸入操作數列表 : 破壞列表(Clobber List)
);
1. volatile
關鍵字禁止編譯器優化內聯匯編代碼;
2. "asm code"主要用于填寫匯編代碼
,用\n\t
分隔多行匯編指令;
__asm__ volatile ("add r0, r1, r2\n\t""add r0, r0, r3\n\t""mov r0, r0": /* 輸出操作數列表 */: /* 輸入操作數列表 */: /* 破壞列表 */
);
3. 輸出操作數列表(asm->c/c++)
用于指定匯編代碼執行后需要傳遞回 C/C++ 變量的結果,通常只能為變量;
__asm__ volatile ("mov r0, #0xFF":=r(value) // 將r0的值寫入value: // 輸入操作數列表: // 破壞列表
);
每個輸出操作數的格式為
"約束"(變量)
,多個操作數用逗號分隔,其中約束由兩部分構成,一部分為方向修飾符(+,=,&),方向修飾符用于指定該操作數是輸入還是輸出,輸出操作數必須包含=
(表示操作數僅用于輸出等價于只寫)或?+
(表示操作數既是輸入又是輸出等價于可讀可寫),& 必須通過 +& 或者 =& 進行使用,另一部分為約束符(r,m),約束符用于指定操作數的存放位置,其中r
約束(寄存器約束)要求操作數存儲在通用寄存器中,m
約束(內存約束)要求操作數直接存儲在內存地址中,避免通過寄存器中轉;
=r :?輸出到通用寄存器,操作數僅用于寫入
__asm__ volatile ("mov r0, #42" : "=r" (result)
);
結果寫入寄存器
r0
,再傳遞給result
;
4. 輸入操作數列表(c/c++ -> asm)
用于c/c++程序向匯編代碼傳遞數據,輸入操作數的值在匯編代碼中不應被修改,只能讀取,因此輸入操作數無需方向修飾符,常用約束符 r(變量值存入通用寄存器)m(變量直接通過內存地址訪問)i(立即數(整數常量)),此外用于定義輸入的參數,可以是變量也可以是立即數;
int a = 10, b = 20;
__asm__ volatile ("add r0, %1, %2\n\t" // %0, %1, ..., %n表示占位符,先按照輸出操作數的順序進行引用//再按照輸入操作數的順序進行引用"mov %0, r0" // result = r0: "=r" (result) // 輸出操作數: result -> %0: "r" (a), "r" (b) // 輸入操作數:a -> %1, b-> %2: "r0" // 破環列表用于告知編譯器哪些寄存器或資源被隱式修改
);
5. 破壞列表
告知編譯器哪些寄存器或資源被隱式修改,常見值如下:
"cc"
:條件碼寄存器(CPSR);
"memory"
:表示內存被修改(強制刷新內存緩存);
"r0"
,"r1"
:指定被破壞的寄存器;
__asm__ volatile ("mov r0, %1\n\t""str r0, [%0]": : "r" (addr), "r" (value): "r0", "memory" // 聲明破壞 r0 和 memory
);