v74.01 鴻蒙內核源碼分析(編碼方式篇) | 機器指令是如何編碼的 | 百篇部落格分析OpenHarmony源碼
本篇關鍵詞:指令格式、條件域、類型域、操作域、數據指令、訪存指令、跳轉指令、SVC(軟體中斷)
內核彙編相關篇為:
- 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-489
00x
中的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 中回復 百文 可方便閱讀。
按功能模組:
百萬注源碼 | 處處扣細節
-
百萬漢字註解內核目的是要看清楚其毛細血管,細胞結構,等於在拿放大鏡看內核。內核並不神秘,帶著問題去源碼中找答案是很容易上癮的,你會發現很多文章對一些問題的解讀是錯誤的,或者說不深刻難以自圓其說,你會慢慢形成自己新的解讀,而新的解讀又會碰到新的問題,如此層層遞進,滾滾向前,拿著放大鏡根本不願意放手。
-
< gitee | github | coding | gitcode > 四大碼倉推送 | 同步官方源碼,鴻蒙研究站 | weharmonyos 中回復 百萬 可方便閱讀。
據說喜歡點贊分享的,後來都成了大神。😃