一、匯編語言
① 匯編語言的發展
- 機器語言:是由 0 和 1 組成的機器指令,表示特定的功能,如下所示:
加:0100 0000
減:0100 1000
乘:1111 0111 1110 0000
除:1111 0111 1111 0000
- 匯編語言(Assembly language):由于使用機器語言表示時不方便記憶,于是便開始使用“助記符”來代替機器語言,例如,使用助記符表示的加減乘除:
加:INC EAX 通過編譯器 0100 0000
減:DEC EAX 通過編譯器 0100 1000
乘:MUL EAX 通過編譯器 1111 0111 1110 0000
除:DIV EAX 通過編譯器 1111 0111 1111 0000
- 高級語言(High-level programming language):在后期,為了更加高效的編程,在匯編語言的基礎之上有了更高級的語言,例如 C/C++/Java/OC/Swift 等,這些語言更加接近人類的自然語言,例如,c 語言表示的加減乘除:
加:A+B 通過編譯器 0100 0000
減:A-B 通過編譯器 0100 1000
乘:A*B 通過編譯器 1111 0111 1110 0000
除:A/B 通過編譯器 1111 0111 1111 0000
- 最終的代碼在終端設備上顯示,程序如下所示:

- 說明:
-
- 匯編語言與機器語言是一一對應的,每一潭訓器指令都有與之對應的匯編指令;
-
- 匯編語言可以通過編譯得到機器語言,機器語言可以通過反匯編得到匯編語言;
-
- 高級語言可以通過編譯得到匯編語言/機器語言,但匯編語言/機器語言幾乎不可能還原成高級語言(因為不同的設備,對應不同的 CPU 架構,而 CPU 架構對應不同的指令集),
② 匯編語言的特點
- 可以直接訪問、控制各種硬體設備,例如存盤器、CPU 等,能最大限度的發揮硬體的功能;
- 能夠不受編譯器的限制,對生成的二進制代碼進行完全的控制;
- 目標代碼簡潔,占用記憶體少,執行速度快;
- 匯編指令是機器指令的助記符,同機器指令一一對,每一種 CPU 都有自己的機器指令集/匯編指令集,所以匯編語言不具備可移植性;
- 知識點過多,要求過高,需要開發者對 CPU 等硬體結構有所了解,不易于撰寫、除錯,以及維護;
- 不區分大小寫,如 mov 和 MOV 是一樣的,
③ 匯編語言的種類
- 目前討論較多的匯編語言有:
-
- 8086 匯編(8086 處理器是 16bit 的 CPU);
-
- Win32 匯編;
-
- Win64 匯編;
-
- ARM 匯編(嵌入式、Mac、iOS);
-
- …
- 在 iPhone 中使用的是 ARM 匯編,但是不同的設備之間也會因為 CPU 架構的不同而有所差異,如下所示,iPhone 中不同架構所對應的設備:
| 架構 | 設備 |
|---|---|
| armv6 | iPhone, iPhone2, iPhone3G, 第一代、第二代 iPod Touch |
| armv7 | iPhone3GS, iPhone4, iPhone4S,iPad, iPad2, iPad3(The New iPad), iPad mini, iPod Touch 3G, iPod Touch4 |
| armv7s | iPhone5, iPhone5C, iPad4(iPad with Retina Display) |
| arm64 | iPhone5S 以后 iPhoneX , iPad Air, iPad mini2以后 |
④ 匯編語言的用途
- 任何高級語言最終都會被編譯成匯編,學習了解匯編的相關知識,可以更好的日常開發、學習探索中幫助我們更好的排查問題、理解底層運行的機制,
- 匯編語言可以撰寫驅動程式、作業系統,例如 Linux 內核的某些關鍵字,
- 匯編語言可以對性能要求極高的程式或者代碼片段,可與高級語言混合使用(稱為行內匯編),
- 匯編語言可以用于軟體安全方面:病毒的分析與防治,逆向、加殼、脫殼、破解、外掛、免殺、加解密、黑客等,
- 匯編語言可以幫助我們理解整個計算機系統的最佳起點和最有效途徑,可以為撰寫高效代碼打下基礎,
- 匯編語言可以幫助我們理解代碼的本質,例如:
-
- 函式的本質是什么?
-
- ++a 底層是如何執行的?
-
- 編譯器在底層到底幫我們做了哪些作業?
-
- DEBUG 模式和 RELEASE 模式到底有哪些地方是不同的?有什么關鍵的地方被我們忽略了?
-
- …
二、總線
- App/程式的執行程序:

- 總線是 CPU 與記憶體之間的橋梁,如下圖所示,iPhone X 上的 A11 芯片:

- 每一個 CPU 芯片都有很多管腳,這些管腳和總線相連,CPU 通過總線跟外部器件進行互動,總線就是一根根導線的集合,
- 總線主要分為三類,如下圖所示:
-
- 地址總線:CPU 是通過地址總線來指定存盤單元的;
-
- 資料總線:CPU 與記憶體/其他部件之間的資料傳送通道;
-
- 控制總線:CPU 通過控制總線對外部器件進行控制,

- 如下所示,CPU 從記憶體的 3 號單元讀取資料:
-
- CPU 想操作記憶體中的資料,首先需要找到記憶體地址:CPU 通過地址總線,將 3 這個地址傳遞給記憶體,即尋址到記憶體的 3 號單元;
-
- 需要操作 3 單元的資料,還需要確定是讀還是寫:CPU 通過控制總線告訴記憶體需要進行的操作,假設是讀;
-
- 記憶體知道 CPU 想要進行的操作:記憶體將 3 號單元的資料通過資料線傳遞給 CPU,

① 地址總線
- 地址總線的寬度決定 CPU 的尋址能力,即地址總線決定 CPU 所能訪問的最大記憶體空間的大小,例如:10 根地址線能訪問的最大記憶體是 210 = 1024 位二進制資料(即 1B);
- 地址總線是地址線數量之和,8086 地址總線寬度是 20,所以尋址能力是 1M(即 220);
- 記憶體地址的單元是位元組 byte(簡寫為 B),每個位元組里面可以放 8 位(即 bit),如下所示,地址 0x0001 表示的是 byte,后面的 01001001 表示的是 bit:

- byte 和 bit 是什么?
-
- byte 是位元組的意思,位元組是計算機存盤容量的基本單位,一個位元組由 8 位二進制陣列成,在計算機內部,一個位元組可以表示一個資料,也可以表示一個英文字母,兩個位元組可以表示一個漢字,
-
- bit 是位的意思,位是計算機中存盤資料的最小單位,指二進制數中的一個位數,其值為“0”或“1”,
- byte 和 bit 區別:
-
- 容量大小不同:一個 byte 由 8 bits 組成,是資料存盤的基礎單位,1byte 又稱為一個位元組,用一個位元組 Byte 儲存,可區別 256 個數字;bit (位元) 是表示資訊的最小單位,是二進制數的一位包含的資訊或 2 個選項中特別指定 1 個的需要資訊量,
-
- 存盤資料型別不同:一個 byte 由 8 bits 所組成,可代表一個字元(A ~ Z)、數字(0 ~ 9)、或符號(,.?!%&±*/),是記憶體儲存資料的基本單位,每個中文字則須要兩 bytes;bit 是電腦記憶體中最小的單位,在二進位電腦系統中,每一 bit 可以代表0 或 1 的數位訊號,
② 資料總線
- 資料總線的寬度決定了 CPU 的單次資料傳送量(即吞吐量),也就是資料傳送速度即 CPU 和外界的資料傳送速度;
- 資料總線的每條資料線一次只能傳輸一位二進制資料,例如,8 根資料線一次可傳送一個 8 位二進制資料(即 1 個位元組的資料);
- 資料總線是資料線數量之和,8086 的資料總線寬度是 16,所以單次最大傳遞 2 個位元組的資料;
- 常說的 32 位(4 位元組)、64 位(8 位元組)CPU,這里的 32、64 指的就是資料吞吐量,
③ 控制總線
- 控制總線的寬度決定 CPU 對其他器件的控制能力,能有多少種控制,即 CPU 對外部器件的控制能力;
- 控制總線是控制線數量之和,
④ 記憶體
- CPU 是通過總線和硬體設備連接的,記憶體有 RAM 主存盤器、RAM 主存盤器(記憶體條);

- 按照物理地址劃分的記憶體,有主存盤器、顯存地址、顯卡地址、網卡地址:

- 記憶體中的低地址是供用戶用的,高地址是供系統用的,如下所示:

- 記憶體地址空間的大小受 CPU 地址總線寬度的限制,8086 的地址總線寬度為 20,可以定位 220 個不同的記憶體單元(記憶體地址范圍 0x00000~0xFFFFF),所以 8086 的記憶體空間大小為 1MB;
-
- 0x00000~0x9FFFF:主存盤器,可讀可寫;
-
- 0xA0000~0xBFFFF:向顯存中寫入資料,這些資料會被顯卡輸出到顯示幕,可讀可寫;
-
- 0xC0000~0xFFFFF:存盤各種硬體/系統資訊,只讀,
三、進制
① 進制的定義
- 八進制由 8 個符號組成:0 1 2 3 4 5 6 7 逢八進一;
- 十進制由 10 個符號組成:0 1 2 3 4 5 6 7 8 9 逢十進一;
- N 進制就是由 N 個符號組成:逢 N 進一;
- 示例:
-
- 如果十進制由這樣 10 個符號組成:0 1 3 2 8 A B E S 7(逢十進一),那么 1+1 就會等于 3;
-
- 自定義的十進制和傳統定義的十進制不一樣,如果不告訴別人符號表,別人是無法拿到具體的資料的,這樣的應用場景主要是用于加密,
② 進制的運算
- 八進制加法表:
0 1 2 3 4 5 6 7
10 11 12 13 14 15 16 17
20 21 22 23 24 25 26 27
...
1+1 = 2
1+2 = 3 2+2 = 4
1+3 = 4 2+3 = 5 3+3 = 6
1+4 = 5 2+4 = 6 3+4 = 7 4+4 = 10
1+5 = 6 2+5 = 7 3+5 = 10 4+5 = 11 5+5 = 12
1+6 = 7 2+6 = 10 3+6 = 11 4+6 = 12 5+6 = 13 6+6 =14
1+7 = 10 2+7 = 11 3+7 = 12 4+7 = 13 5+7 = 14 6+7 =15 7+7 = 16
- 八進制乘法表:
0 1 2 3 4 5 6 7 10 11 12 13 14 15 16 17 20 21 22 23 24 25 26 27...
1*1 = 1
1*2 = 2 2*2 = 4
1*3 = 3 2*3 = 6 3*3 = 11
1*4 = 4 2*4 = 10 3*4 = 14 4*4 = 20
1*5 = 5 2*5 = 12 3*5 = 17 4*5 = 24 5*5 = 31
1*6 = 6 2*6 = 14 3*6 = 22 4*6 = 30 5*6 = 36 6*6 = 44
1*7 = 7 2*7 = 16 3*7 = 25 4*7 = 34 5*7 = 43 6*7 = 52 7*7 = 61
- 以 277+333 為例,計算程序如下:

- 實戰四則運算:
277 236 276 234
+ 333 - 54 * 54 / 4
-------- -------- -------- --------
632 162 1370 47
③ 二進制的簡寫形式
二進制: 1 0 1 1 1 0 1 1 1 1 0 0
三個二進制一組: 101 110 111 100
八進制: 5 6 7 4
四個二進制一組: 1011 1011 1100
十六進制: b b c
④ 資料的寬度
- 數學上的數字是沒有大小限制的,可以無限大,但是在計算機中,由于硬體的制約,資料都是有長度限制的,稱為資料寬度,超過最多寬度的資料會被丟棄,
- 可以來做一下測驗:
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int test() {
int cTemp = 0x1FFFFFFFF;
return cTemp;
}
int main(int argc, char * argv[]) {
printf("%x\n",test());
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
- 斷點除錯結果如下,可以發現 1 溢位:

- 也可以通過獲取的地址,然后在 Debug-Debug Workflow-ViewMemory 中輸入地址查看:

⑤ 計算機中常見的資料寬度
- 計算機的資料寬度:
-
- 位(Bit):1 個位就是 1 個二進制位,即 0 或 1;
-
- 位元組(Byte):1 個位元組由 8 個 Bit 組成,記憶體中的最小單元 Byte;
-
- 字(Word):1 個字由兩個位元組組成(16 位),第 2 個位元組分別稱為高位元組和低位元組;
-
- 雙字(DoubleWord):1 個雙字由兩個字組成(32位),
- 計算機存盤資料它會分為有符號數和無符號數,如下圖所示:
-
- 無符號數,直接換算;
-
- 有符號數,符號放在第 1 位,第 1 位是 0 即正數,為 1 即負數,

⑥ 自定義進制符號
- 現有 10 進制數 10個,符號分別是:2,9,1,7,6,5,4, 8,3,A,逢 10 進 1,那么: 123 + 234 = ____(AA6):
十進制: 0 1 2 3 4 5 6 7 8 9
自定義: 2 9 1 7 6 5 4 8 3 A
(十進制10)92 99 91 97 96 95 94 98 93 9A
12 19 11 17 16 15 14 18 13 1A
72 79 71 77 76 75 74 78 73 7A
62 69 61 67 66 65 64 68 63 6A
52 59 51 57 56 55 54 58 53 5A
42 49 41 47 46 45 44 48 43 4A
82 89 81 87 86 85 84 88 83 8A
32 39 31 37 36 35 34 38 33 3A
922
(00)2 (01)9 (02)1 (03)7 (04)6 (05)5 (06)4 (07)8 (08)3 (09)A
(10)92 (11)99 (12)91 (13)97 (14)96 (15)95 (16)94 (17)98 (18)93 (19)9A
(20)12 (21)19 (22)11 (23)17 (24)16 (25)15 (26)14 (27)18 (28)13 (29)1A
(30)72 (31)79 (32)71 (33)77 (34)76 (35)75 (36)74 (37)78 (38)73 (39)7A
(40)62 (41)69 (42)61 (43)67 (44)66 (45)65 (46)64 (47)68 (48)63 (49)6A
(50)52 59 51 57 56 55 54 58 53 5A
(60)42 49 41 47 46 45 44 48 43 4A
(70)82 89 81 87 86 85 84 88 83 8A
(80)32 39 31 37 36 35 34 38 33 3A
(90)922
- 現有 9 進制數 9 個,符號分別是:2,9,1,7,6,5,4, 8,3,逢 9 進 1,那么: 123 + 234 = ____(9926):
十進制: 0 1 2 3 4 5 6 7 8
自定義: 2 9 1 7 6 5 4 8 3
92 99 91 97 96 95 94 98 93
12 19 11 17 16 15 14 18 13
72 79 71 77 76 75 74 78 73
62 69 61 67 66 65 64 68 63
52 59 51 57 56 55 54 58 53
42 49 41 47 46 45 44 48 43
82 89 81 87 86 85 84 88 83
32 39 31 37 36 35 34 38 33
922
(00)2 (01)9 (02)1 (03)7 (04)6 (05)5 (06)4 (07)8 (08)3
(10)92 (11)99 (12)91 (13)97 (14)96 (15)95 (16)94 (17)98 (18)93
(20)12 19 11 17 16 15 14 18 13
(30)72 79 71 77 76 75 74 78 73
(40)62 69 61 67 66 65 64 68 63
(50)52 59 51 57 56 55 54 58 53
(60)42 49 41 47 46 45 44 48 43
(70)82 89 81 87 86 85 84 88 83
(80)32 39 31 37 36 35 34 38 33
(90)922
四、CPU 與暫存器
- 內部部件之間是由總線連接,如下圖所示:

- CPU 除了有控制器、運算器,還有暫存器,其中暫存器的作用就是進行資料的臨時存盤,
- CPU 的運算速度是非常快的,為了性能,CPU 在內部開辟了一小塊臨時存盤區域,并在進行運算時先將資料從記憶體中復制到這一小塊臨時區域中,運算就在這一小塊臨時存盤區進行,稱這一小塊臨時存盤區域為暫存器,
- 針對 arm64 的 CPU 來說,如果暫存器以 x 開頭,則表明是一個 64 位的暫存器,如果暫存器以 w 開頭,則表明是一個 32 位的暫存器,在系統中沒有提供 16 位和 32 位的暫存器供訪問和使用,其中 32 位的暫存器是 64 位暫存器的低 32 位部分,并不是獨立存在的,
- 對于程式員來說,CPU 中最主要的部件是暫存器,可以通過改變暫存器的內容來實作對 CPU 的控制,不同的 CPU,暫存器的個數和結構是不相同的,
① 浮點和向量暫存器
- 因為浮點數的存盤以及其運算的特殊性,CPU 中專門提供浮點暫存器來處理浮點數,
- 浮點暫存器:
-
- 64 位:D0 - D31;
-
- 32 位:S0 - S31,
- 現在 CPU 支持向量運算(向量運算在圖形處理相關的領域用的非常多),為了支持向量計算,系統也提供了眾多的向量暫存器,向量暫存器 128 位:V0 - V31,
② 通用暫存器
- 通用暫存器也稱為資料地址暫存器,通常用來做資料計算的臨時存盤、累加、計數、地址保存等功能,定義這些暫存器的作用主要是用于在 CPU 指令中保存運算元,在 CPU 中當做一些常規變數來使用,
- arm64 擁有 32 個 64 位的通用暫存器 X0-X30,以及 XZR(零暫存器),這些通用暫存器有時也有特定用途:
-
- 那么 w0-w28 這些 32 位的,因為 64 位 CPU 可以兼容 32 位,所以可以只使用 64 位暫存器的低 32 位;
-
- w0 就是 x0 的低 32 位,
- 通常,CPU 會先將記憶體中的資料存盤到通用暫存器中,然后再對暫存器中的資料進行運算,
- 假設記憶體中有塊紅色記憶體空間的值是 3,現在想把它的值加 1,并將結果存盤到藍色記憶體空間:
-
- CPU 首先會將紅色記憶體空間的值放到 X0 暫存器中:mov X0,紅色記憶體空間;
-
- 然后讓 X0 暫存器與 1 相加:add X0,1;
-
- 最后將值賦值給記憶體空間:mov 藍色記憶體空間,X0

③ PC 暫存器(program counter)
- 為指令指標暫存器,它指示了 CPU 當前要讀取指令的地址;在記憶體/磁盤上,指令和資料沒有任何區別,都是二進制資訊;CPU 在作業時,將有的資訊看作指令,有的看作資料,為同樣的資訊賦予了不同的意義,
- 例如 1110 0000 0000 0011 0000 1000 1010 1010,可以當做資料 0xE003008AA,也可以當做指令 mov x0, x8,
- CPU 根據什么將記憶體中的資訊看作指令呢?
-
- CPU 將 pc 指向的記憶體單元的內容看作指令;
-
- 如果記憶體中的某段內容曾經被 CPU 執行過,那么它所在的記憶體單元必然被 pc 指向過,
④ 暫存器案例分析
- 繼續使用上文中資料的寬度的例子繼續分析,暫存器種類如下所示:
-
- Exception State Registers:例外暫存器
-
- Floating Point Registers:浮點暫存器
-
- general Purpose Registers:通用暫存器

- 匯編演示:

-
- 列印 pc 暫存器,現在是 fdec4:

-
- 按住 control+Step into,繼續列印:


-
- 暫存器除了讀還可以寫:register write pc 0x104c31ecc,register read pc,此時是讀不出來的,因為斷點斷住,如果 step into,此時斷點斷在哪里?最終通過驗證發現,會斷在 cc 的下一行:


-
- 此時 pc 指向 cc,會將 cc 中的指令拿出來執行,執行完畢后走到 cc 的下一條指令,而此時 d0 的指令是還沒有執行的,
④ 高速快取
- iPhoneX 上搭載的 arm 處理器 A11,它的一級快取的容量是 64kb,二級快取的容量是 8M,
- CPU 每執行一條指令前都需要從記憶體中將質量讀取到 CPU 記憶體并執行,而暫存器的運行速度相比記憶體讀寫要快很多,
- 為了性能,CPU 還集成了一個高速快取區域,當程式運行時,先將要執行的指令代碼以及資料復制到高速快取中(由作業系統完成),然后 CPU 直接從高速快取依次讀取指令來執行,
⑤ bl 指令
- CPU 從何處執行指令,是由 pc 中的內容決定的,可以通過改變pc的內容來控制 CPU 執行目標指令;
- arm64 提供了一個 mov 指令(傳送指令),可以用來改變大部分暫存器的值,例如 mov x0,#10、mov x1,#20;
- mov 指令并不能用于設定 pc 的值,arm64 沒有提供這樣的功能;
- arm64 提供了另外的指令來修改 pc 的值,這些指令統一稱為轉移指令,其中最簡單的是 bl 指令,
- 現有兩段代碼,假設程式先執行 A,請寫出指令執行順序和最終暫存器 x0 的值是多少?
_A:
mov x0,#0xa0
mov x1,#0x00
add x1, x0, #0x14
mov x0,x1
bl _B
mov x0,#0x0
ret
_B:
add x0, x0, #0x10
ret
// 結果
流程:
mov x0,#0xa0 -- x0:0xa0
mov x1,#0x00 -- x1:0x00
add x1, x0, #0x14 -- x1:0xa0+0x14=0xb4
mov x0,x1 -- x0:0xb4
add x0, x0, #0x10 -- x0:0xb4+0x10=0xc4
ret -- 回到bl跳轉的下一行
mov x0,#0x0 -- x0:0x00
x0的值:0x00
- 現在來驗證這段匯編代碼的執行,新建一個空檔案,命名為 asm.s,這可以看成是一個匯編代碼檔案,會編譯成原始碼:

- 在 VC 中定義函式的宣告:
// 函式宣告
int A();
int test() {
int cTemp = 0x1FFFFFFFF;
return cTemp;
}
- (void)viewDidLoad {
[super viewDidLoad];
A();
}
- 在 A() 執行處加斷點,并執行程式,開啟匯編除錯;

- 按住 control+step into,進入 A 的詳情匯編:

- 然后開始 lldb 除錯,進入匯編代碼 A:



- 進入匯編代碼 B:

- 執行到 A 的 ret:

- 執行到這里出現了死回圈,這是為什么呢?這是因為回傳地址暫存器的關系,關系到了函式的呼叫堆疊,
五、總結
- 匯編概述:
-
- 使用助記符代替機器指令的一種編程語言;
-
- 匯編和機器指令是一一對應的關系,拿到二進制就可以進行反匯編;
-
- 由于匯編和 CPU 的指令集是對應的,所以匯編不具備移植性,
- 總線:是一堆導線的集合;
-
- 地址總線:地址總線的寬度決定了尋址能力;
-
- 資料總線:資料總線的寬度決定了 CPU 資料的吞吐量,
- 進制:
-
- 任意進制,都是對應個數的符號組成,符號可以自己定義;
-
- 2/8/16 進制是相對完美的進制,他們之間的關系:
-
-
- 3 個 2 進制位使用一個 8 進制位標識;
-
-
-
- 4 個 2 進制位使用一個 16 進制位標識;
-
-
-
- 兩個 16 進制位可以標識一個位元組;
-
-
- 數量單位:1024 = 1K,1024K = 1M,1024M = 1G;
-
- 容量單位:
-
-
- 1024B = 1KB,1024KB = 1MB,1024MB = 1GB;
-
-
-
- B:byte(位元組)1B = 8bit;
-
-
-
- bit(位元):一個二進制位;
-
-
-
- 資料寬度:計算機中的資料是有寬度的,超過就會溢位,
-
- 暫存器:CPU 為了性能,在內部開辟了一小塊臨時存盤區域;
-
- 浮點向量暫存器:用于浮點數/向量的存盤及運算;
-
- 例外狀態暫存器;
-
- 通用暫存器:除了存放資料有時也有特殊的用途;
-
-
- ARM64 擁有 32 個 64 位的通用暫存器 X0-X30 以及 XZR(零暫存器);
-
-
-
- 為了兼容 32 位,所以 ARM64 位擁有 W0-W28 以及 WZR 30 個 32 位暫存器;
-
-
-
- 32 位暫存器并不是獨立存在的,例如 W0 是 X0 的低 32 位;
-
-
- PC 暫存器:指令指標暫存器;
-
-
- PC 暫存器里面的值保存的就是 CPU 接下來需要執行的指令地址;
-
-
-
- 改變 PC 的值可以改變程式的執行流程;
-
-
-
- mov 指令不能更改 PC 暫存器的值,需要通過 bl 跳轉指令來改變 PC 暫存器的值,
-
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/298689.html
標籤:其他
上一篇:ios 打包分發全流程
