v74.01 鴻蒙核心原始碼分析(編碼方式篇) | 機器指令是如何編碼的 | 百篇部落格分析OpenHarmony原始碼
核心彙編相關篇為:
- v74.01 鴻蒙核心原始碼分析(編碼方式) | 機器指令是如何編碼的
- v75.03 鴻蒙核心原始碼分析(彙編基礎) | CPU上班也要打卡
- v76.04 鴻蒙核心原始碼分析(彙編傳參) | 如何傳遞複雜的引數
- v77.01 鴻蒙核心原始碼分析(可變引數) | 正在製作中 ...
- v78.01 鴻蒙核心原始碼分析(開機啟動) | 正在製作中 ...
- v79.01 鴻蒙核心原始碼分析(程序切換) | 正在製作中 ...
- v80.03 鴻蒙核心原始碼分析(任務切換) | 看彙編如何切換任務
- v81.05 鴻蒙核心原始碼分析(中斷切換) | 系統因中斷活力四射
- v82.06 鴻蒙核心原始碼分析(異常接管) | 社會很單純 複雜的是人
- v83.01 鴻蒙核心原始碼分析(缺頁中斷) | 正在製作中 ...
本篇說清楚 ARM
指令是如何被編碼的,機器指令由哪些部分構成,指令有哪些型別,每種型別的語法又是怎樣的 ?
程式碼案例 | C -> 彙編 -> 機器指令
看一段C語言編譯(clang)成的最後的機器指令(armv7)
int main(){
int a = 0;
if( a != 1)
a = 2*a + 1;
return a;
}
生成彙編程式碼如下:
main:
60c: sub sp, sp, #8
610: mov r0, #0
614: str r0, [sp, #4]
618: str r0, [sp]
61c: ldr r0, [sp]
620: cmp r0, #1
624: beq 640 <main+0x34>
628: b 62c <main+0x20>
62c: ldr r1, [sp]
630: mov r0, #1
634: orr r0, r0, r1, lsl #1
638: str r0, [sp]
63c: b 640 <main+0x34>
640: ldr r0, [sp]
644: add sp, sp, #8
648: bx lr
彙編程式碼對應的機器指令如下圖所示:
便於後續分析,將以上程式碼整理成如下表格
彙編程式碼 | 機器指令(十六進位制表示) | 機器指令(二進位制表示) |
---|---|---|
sub sp, sp, #8 | e24dd008 | 1110 0010 0100 1101 1101 0000 0000 1000 |
mov r0, #0 | e3a00000 | 1110 0011 1010 0000 0000 0000 0000 0000 |
str r0, [sp, #4] | e58d0004 | 1110 0101 1000 1101 0000 0000 0000 0100 |
str r0, [sp] | e58d0000 | 1110 0101 1000 1101 0000 0000 0000 0000 |
ldr r0, [sp] | e59d0000 | 1110 0101 1001 1101 0000 0000 0000 0000 |
cmp r0, #1 | e3500001 | 1110 0011 0101 0000 0000 0000 0000 0001 |
beq 640 <main+0x34> | 0a000005 | 0000 1010 0000 0000 0000 0000 0000 0101 |
b 62c <main+0x20> | eaffffff | 1110 1010 1111 1111 1111 1111 1111 1111 |
ldr r1, [sp] | e59d1000 | 1110 0101 1001 1101 0001 0000 0000 0010 |
mov r0, #1 | e3a00002 | 1110 0011 1010 0000 0000 0000 0000 0001 |
orr r0, r0, r1, lsl #1 | e1800081 | 1110 0001 1000 0000 0000 0000 1000 0001 |
str r0, [sp] | e58d0000 | 1110 0101 1000 1101 0000 0000 0000 0000 |
b 640 <main+0x34> | eaffffff | 1110 1010 1111 1111 1111 1111 1111 1111 |
ldr r0, [sp] | e59d1000 | 1110 0101 1001 1101 0001 0000 0000 0000 |
add sp, sp, #8 | e28dd008 | 1110 0010 1000 1101 1101 0000 0000 1000 |
bx lr | e12fff1e | 1110 0001 0010 1111 1111 1111 0001 1110 |
CPSR暫存器
在理解本篇之前需瞭解下CPSR
暫存器的高4
位[31,28]
表達的含義。關於暫存器的詳細介紹可翻看 系列篇的 (暫存器篇)
N、Z、C、V
均為條件碼標誌位。它們的內容可被算術或邏輯運算的結果所改變,並且可以決定某條指令是否被執行!意義重大!
-
CPSR
的第31
位是N
,符號標誌位。它記錄相關指令執行後,其結果是否為負。 如果為負N = 1
,如果是非負數N = 0
。 -
CPSR
的第30
位是Z
,0
標誌位。它記錄相關指令執行後,其結果是否為0
。 如果結果為0
。那麼Z = 1
。如果結果不為0
,那麼Z = 0
。 -
CPSR
的第29位
是C
,進位標誌位(Carry)
。一般情況下,進行無符號數的運算。 加法運算:當運算結果產生了進位時(無符號數溢位),C=1
,否則C=0
。 減法運算(包括CMP
):當運算時產生了借位時(無符號數溢位),C=0
,否則C=1
。 -
CPSR
的第28
位是V
,溢位標誌位(Overflow
)。在進行有符號數運算的時候, 如果超過了機器所能標識的範圍,稱為溢位。
指令格式
ARM
指令流是一連串的字對齊的四位元組指令流。每個 ARM 指令是一個單一的 32
位字(4
位元組),如圖(3):
解讀 圖為ARM
指令的編碼一級格式,所有的指令都必須符合一級格式,分成三部分:
- 條件域:
cond[31:28]
表示,條件域會影響CPSR
的條件碼N、Z、C、V
標誌位。 - 型別域:
op1[27:25]
,op[4]
,arm
將指令分成了六大型別 。 - 操作域: 剩下的
[24:5]
,[4:0]
即圖中的空白位/保留位,這是留給下級自由發揮的,不同的型別對這些保留位有不同的定義。可以理解為因型別變化而變化的二級格式。 - 那有了二級格式會不會有三級格式 ? 答案是必須有, 二級格式只會對保留位定義部分位,會留一部分給具體的指令格式自由發揮。
- 一定要理解這種層次結構才能理解
ARM
指令集的設計總思路,因為RISC(精簡指令集) 的指令長度是固定的16/32/64
位,以32
位為例,所有的指令設計必須全用32
位來表示,如果只有一層結構是難以滿足眾多的指令設計需求的,要靈活有包容就得給適當的空間發揮。
條件域
cond
為條件域,每一條可條件執行的條件指令都有4
位的條件位域,2^4
能表示16
種條件
cond | 助記符 | 含義(整型) | 含義(浮點型) | 條件標誌 |
---|---|---|---|---|
0000 | EQ | 相等 | 相等 | Z == 1 |
0001 | NE | 不等 | 不等或無序 | Z == 0 |
0010 | CS | 進位 | 大於等於或無序 | C == 1 |
0011 | CC | 進位清除 | 小於 | C == 0 |
0100 | MI | 減、負數 | 小於 | N == 1 |
0101 | PL | 加、正數或 0 | 大於等於或無序 | N == 0 |
0110 | VS | 溢位 | 無序 | V == 1 |
0111 | VC | 未溢位 | 有序 | V == 0 |
1000 | HI | 無符號大於 | 大於或無序 | C == 1 and Z == 0 |
1001 | LS | 無符號小於或等於 | 小於或等於 | C == 0 or Z == 1 |
1010 | GE | 有符號大於或等於 | 大於或等於 | N == V |
1011 | LT | 有符號小於 | 小於或無序 | N != V |
1100 | GT | 有符號大於 | 大於 | Z == 0 and N ==V |
1101 | LE | 有符號大於或等於 | 小於等於或無序 | Z == 1 or N != V |
1110 | 無 | 無條件 | 無條件 | 任何 |
- 大部分的指令都是
1110 = e
,無條件執行指令,只要看到e
開頭的機器指令都屬於這類 -
beq 640 <main+0x34> // 機器碼 0a000005 <=> 0000 1010 0000 0000 0000 0000 0000 0101 0000 EQ Equal(相等) Z == 1
型別域
圖(3) 的 op1
域位於 bits[27:25]
,佔三位;op
域位於 bit[4]
,佔一位。它們的取值組合在一起,決定指令所屬的分類(Instruction Class),其值對應的關係如下
op1 op 指令型別
00x - 資料處理以及雜項指令
010 - load/store word型別 或者 unsigned byte
011 0 同上
011 1 媒體介面指令
10x - 跳轉指令和塊資料操作指令,塊資料操作指令指 STMDA 這類,連續記憶體操作。
11x - 協處理器指令和 svc 指令,包括高階的 SIMD 和浮點指令。
操作域
操作域是因型別變化而變化的二級格式 ,作用於保留位。包含
00x | 資料處理類指令
- 上圖為涉及資料處理指令的對應編碼,由
op[佔5位]
和op2[佔2位]
兩項來確定指令的唯一性 - 一般情況下只需
op
指定唯一性,圖中SUB
指令對應為0010x
,而程式碼案例中的第一句
對應sub sp, sp, #8 // 機器碼 e24dd008 <=> 1110 001`0 0100` 1101 1101 0000 0000 1000
[24:20]
位就是0 0100
,從而CPU
在譯碼階段將其解析為SUB
指令執行 - 需要用到
op2
的是MOV
系列指令,包括邏輯/算術左移右移,例如:
中的mov r0, #0 //e3a00000 <=> 1110 0011 1010 0000 0000 0000 0000 0000
op = 1 1010
,op2 = 00
對應 MOV(register,ARM) on page A8-48900x
中的x
表示資料處理分兩種情況-
000
無立即數參與(暫存器之間) ,圖A5.2.1 表示了這種情況[27:25]= 000
-
001
有立即參與的運算,例如mov r0, #0
中的[27:25]= 001
,此處未展示圖,可前往 ARM體系結構參考手冊.pdf 翻看
-
010 | 載入儲存指令
-
Load/store
是一組記憶體訪問指令,用來在ARM
暫存器和記憶體之間進行資料傳送,ARM
指令中有3
種基本的資料傳送指令- 單暫存器
Load/Store
記憶體訪問指令(single register
):這些指令為ARM暫存器和儲存器提供了更靈活的單資料項傳送方式。資料可以使位元組,16位半字或32位字 - 多暫存器
Load/Store
記憶體訪問指令:可以實現大量資料的同時傳送,主要用於程序的進入和退出、儲存和恢復工作暫存器以及複製暫存器中的一片(一塊)資料 - 暫存器交換指令(
single register swap
): 實現暫存器資料和記憶體資料進行交換,而且是在一條指令中完成,執行過程中不會受到中斷干擾
- 單暫存器
-
出現在程式碼案例中的
str r0, [sp, #4] // 機器碼 e58d0004 <=> 1110 0101 1000 1101 0000 0000 0000 0100 str r0, [sp] // 機器碼 e58d0000 <=> 1110 0101 1000 1101 0000 0000 0000 0000 將r0中的字資料寫入以SP為地址的儲存器中 ldr r0, [sp] // 機器碼 e59d0000 <=> 1110 0101 1001 1101 0000 0000 0000 0000 儲存器地址地址為SP的資料讀入r0 暫存器
[27:25] = 010
說明都屬於這類指令,完成對記憶體的讀寫,包括LDR
、LDRB
、LDRH
、STR
、STRB
、STRH
六條指令。ldr
為載入指令,但是載入到記憶體還是暫存器,這該怎麼記 ? 因為主角是CPU
,載入有進來的意思,將內容載入至暫存器中。STR
有出去的意思,將內容儲存到記憶體裡。[sp]
相當於C
語言的*sp
,sp
指向程式執行棧當前位置
010 | 多媒體指令
多媒體指令使用較少,但是它涉及指令卻很多
10x | 跳轉/分支/塊資料處理 指令
- 出現在程式碼案例中的
beq 640 <main+0x34> // 機器碼 0a000005 <=> 0000 1010 0000 0000 0000 0000 0000 0101 b 62c <main+0x20> // 機器碼 eaffffff <=> 1110 1010 1111 1111 1111 1111 1111 1111
[27:25] = 101
說明都屬於這類指令 - 聽得很多的
pop
,push
也屬於這類,成塊的資料操作,例如push
常用於將函式的所有引數一次性入棧。 - 記憶體 <> 暫存器 批量資料搬運指令
STMDA (STMED)
LDMDA/LDMF
。
11x | 軟中斷/協處理器 指令
- 其中最有名的就是
svc 0
,在系列篇中曾多次提及它,此處詳細說下svc
,svc
全稱是Supervisor Call
,Supervisor
是CPU
的管理模式,svc
導致處理器進入管理模式,很多人問的系統呼叫底層是怎麼實現的?svc
就是答案。 - 例如
printf
是個標準庫函式,在標準庫的底層程式碼中會呼叫svc 0
,導致使用者態的ARM
程式通常將系統呼叫號傳入R7
暫存器(也被鴻蒙核心使用),然後用SVC
指令呼叫0
號中斷來直接執行系統呼叫, - 在以前的ARM架構版本中,
SVC
指令被稱為SWI
,軟體中斷。 - 描述
svc
功能的詳細虛擬碼如下,請嘗試讀懂它The TakeSVCException() pseudocode procedure describes how the processor takes the exception: // TakeSVCException() // ================== TakeSVCException() // Determine return information. SPSR is to be the current CPSR, after changing the IT[] // bits to give them the correct values for the following instruction, and LR is to be // the current PC minus 2 for Thumb or 4 for ARM, to change the PC offsets of 4 or 8 // respectively from the address of the current instruction into the required address of // the next instruction, the SVC instruction having size 2bytes for Thumb or 4 bytes for ARM. ITAdvance(); new_lr_value = if CPSR.T == '1' then PC-2 else PC-4; new_spsr_value = CPSR; vect_offset = 8; // Check whether to take exception to Hyp mode // if in Hyp mode then stay in Hyp mode take_to_hyp = (HaveVirtExt() && HaveSecurityExt() && SCR.NS == '1' && CPSR.M == '11010'); // if HCR.TGE is set to 1, take to Hyp mode through Hyp Trap vector route_to_hyp = (HaveVirtExt() && HaveSecurityExt() && !IsSecure() && HCR.TGE == '1' && CPSR.M == '10000'); // User mode // if HCR.TGE == '1' and in a Non-secure PL1 mode, the effect is UNPREDICTABLE preferred_exceptn_return = new_lr_value; if take_to_hyp then EnterHypMode(new_spsr_value, preferred_exceptn_return, vect_offset); elsif route_to_hyp then EnterHypMode(new_spsr_value, preferred_exceptn_return, 20); else // Enter Supervisor ('10011') mode, and ensure Secure state if initially in Monitor // ('10110') mode. This affects the Banked versions of various registers accessed later // in the code. if CPSR.M == '10110' then SCR.NS = '0'; CPSR.M = '10011'; // Write return information to registers, and make further CPSR changes: IRQs disabled, // IT state reset, instruction set and endianness set to SCTLR-configured values. SPSR[] = new_spsr_value; R[14] = new_lr_value; CPSR.I = '1'; CPSR.IT = '00000000'; CPSR.J = '0'; CPSR.T = SCTLR.TE; // TE=0: ARM, TE=1: Thumb CPSR.E = SCTLR.EE; // EE=0: little-endian, EE=1: big-endian // Branch to SVC vector. BranchTo(ExcVectorBase() + vect_offset);
- 這部分內容在系列篇 (暫存器篇) ,(系統呼叫篇) ,(標準庫篇) 中都有提及。
具體指令
細看幾條程式碼案例出現的常用指令
sub sp, sp, #8
sub sp, sp, #8 // 機器碼 e24dd008 < = > 1110 0010 0100 1101 1101 0000 0000 1000
是減法操作指令,減法編碼格式為
圖中除了給出格式語法還有一段虛擬碼用於描述指令的使用條件
-
sp
為13
號暫存器,lr
為14
號暫存器 ,pc
為15
號暫存器。 -
如果是
PC
暫存器(Rn = 15)
且S
等於0
檢視ADR
指令。。 -
如果是
SP
暫存器(Rn = 13)
看SUB
(申請棧空間)。 -
如果是
PC
暫存器(Rd = 15)
且S
等於1
。檢視subs
pc
lr
相關指令 -
套用格式結合原始碼
cond op1 操作碼 S Rn Rd imm12(立即數) 1110 001 0010 0 1101 1101 0000 0000 1000 無條件執行 表示資料處理 SUB sp sp 8
mov r0, #0
mov r0, #0 //e3a00000 1110 0011 1010 0000 0000 0000 0000 0000
bx lr
bx lr e12fff1e 1110 0001 0010 1111 1111 1111 0001 1110
-
Rm = 1110
對應lr
暫存器 ,其相當於高階語言的return
,函式執行完了需切回到呼叫它的函式位置繼續執行,lr
儲存的就是那個位置,從哪裡來就回到哪裡去。
百文說核心 | 抓住主脈絡
- 百文相當於摸出核心的肌肉和器官系統,讓人開始豐滿有立體感,因是直接從註釋原始碼起步,在加註釋過程中,每每有心得處就整理,慢慢形成了以下文章。內容立足原始碼,常以生活場景打比方儘可能多的將核心知識點置入某種場景,具有畫面感,容易理解記憶。說別人能聽得懂的話很重要! 百篇部落格絕不是百度教條式的在說一堆詰屈聱牙的概念,那沒什麼意思。更希望讓核心變得栩栩如生,倍感親切。
- 與程式碼需不斷
debug
一樣,文章內容會存在不少錯漏之處,請多包涵,但會反覆修正,持續更新,v**.xx
代表文章序號和修改的次數,精雕細琢,言簡意賅,力求打造精品內容。 - 百文在 < 鴻蒙研究站 | 開源中國 | 部落格園 | 51cto | csdn | 知乎 | 掘金 > 站點發布,鴻蒙研究站 | weharmonyos 中回覆 百文 可方便閱讀。
按功能模組:
- 基礎知識 >> 雙向連結串列 | 核心概念 | 原始碼結構 | 地址空間 | 計時單位 | 巨集的使用 | 鉤子框架 | 點陣圖管理 | POSIX | main函式 |
- 程序管理 >> 排程故事 | 程序控制塊 | 程序空間 | 線性區 | 紅黑樹 | 程序管理 | Fork程序 | 程序回收 | Shell編輯 | Shell解析 |
- 任務管理 >> 任務控制塊 | 併發並行 | 就緒佇列 | 排程機制 | 任務管理 | 用棧方式 | 軟體定時器 | 控制檯 | 遠端登入 | 協議棧 |
- 記憶體管理 >> 記憶體規則 | 實體記憶體 | 虛擬記憶體 | 虛實對映 | 頁表管理 | 靜態分配 | TLFS演算法 | 記憶體池管理 | 原子操作 | 圓整對齊 |
- 通訊機制 >> 通訊總覽 | 自旋鎖 | 互斥鎖 | 快鎖使用 | 快鎖實現 | 讀寫鎖 | 訊號量 | 事件機制 | 訊號生產 | 訊號消費 | 訊息佇列 | 訊息封裝 | 訊息對映 | 共享記憶體 |
- 檔案系統 >> 檔案概念 | 檔案故事 | 索引節點 | VFS | 檔案控制代碼 | 根檔案系統 | 掛載機制 | 管道檔案 | 檔案對映 | 寫時拷貝 |
- 硬體架構 >> 晶片模式 | ARM架構 | 指令集 | 協處理器 | 工作模式 | 暫存器 | 多核管理 | 中斷概念 | 中斷管理 |
- 核心彙編 >> 編碼方式 | 彙編基礎 | 彙編傳參 | 可變引數 | 開機啟動 | 程序切換 | 任務切換 | 中斷切換 | 異常接管 | 缺頁中斷 |
- 編譯執行 >> 編譯過程 | 編譯構建 | GN語法 | 忍者無敵 | ELF格式 | ELF解析 | 靜態連結 | 重定位 | 動態連結 | 程序映像 | 應用啟動 | 系統呼叫 | VDSO |
- 調測工具 >> 模組監控 | 日誌跟蹤 | 系統安全 | 測試用例 |
- 前因後果 >> 總目錄 | 原始碼註釋 | 靜態站點 | 參考手冊 |