1. 內(nèi)存操作:從內(nèi)存中加載數(shù)據(jù) ldr、ldur、ldp
2. 如何通過程序代碼來理解 ldr?
// ARM64.s
.text
.global _test
_test:
ldr x9, [x0] // 尋得 x0 地址所在的值,賦給 x9
ret
// main.m
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
#import "ARM64.h"
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
int a = 8;
int b = 4;
test();
appDelegateClassName = NSStringFromClass([AppDelegate class]);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
- 操作說明:在
main.m
文件中test();
行打上斷點,然后如下操作
(lldb) p &b // 獲取 b 的地址值
(int *) $4 = 0x000000016f137860
(lldb) s // 進入 test 函數(shù)
(lldb) register write x0 0x000000016f137860 // 將&b 寫入 x0
(lldb) ni
(lldb) register read x9 // 獲取 x9 的內(nèi)容
x9 = 0x0000000800000004
思考,為什么我們原本只是將&b
的值讀了,賦給 x9
,為什么 x9
里面的值還有一個 8
,如果能思考清楚,我想 ldr
的指令就掌握得差不多了。
解答:因為 ldr 的指令含義是尋得 x0
的地址,將從 x0
地址所在的值賦予 x9
,int 通常是占 4 個字節(jié),而 x9
是 8 個字節(jié)的,所以 x9 不僅僅讀取了 b 的內(nèi)容,還多讀取了四個字節(jié)。如何證明呢?
(lldb) x 0x000000016f137860 // x 指令:從指定地址開始打印存儲內(nèi)容
0x16f137860: 04 00 00 00 08 00 00 00 00 00 00 00 00 00 00 00 ................
0x16f137870: c8 78 13 6f 01 00 00 00 01 00 00 00 00 00 00 00 .x.o............
我們發(fā)現(xiàn) &b 后續(xù)的內(nèi)容,就是 a 的內(nèi)容,這就解答了我們的疑惑。那么我們只想去四個字節(jié)呢?把 x9
換成 w9
即可。
3. 內(nèi)存操作:往內(nèi)存中寫入數(shù)據(jù) str、stur、stp
4. 零寄存器 wzr、xzr
因為我們在使用 str 的是沒法使用立即數(shù) 0
給寄存器賦值,所以 wzr
xzr
就是干這個事情的。是一個比較特殊又常常見到的寄存器。
5. pc 寄存器 lr 寄存器
- pc:Program Counter,用于記錄當(dāng)前匯編存儲執(zhí)行到那一條了,存儲的是對應(yīng)匯編指令所在的地址值:
(lldb) register read pc
pc = 0x0000000100cda958 TestARM64`test
- lr:Link Register(x30),鏈接寄存器。存儲著函數(shù)的返回地址
6. bl 指令的本質(zhì)
- 先將
bl所在匯編指令
的下一個指令
存儲到pc 寄存器
中 - 然后運行 bl 指令,跳到指定函數(shù)內(nèi)部
- 運行完指定函數(shù),執(zhí)行
ret
時候,會跳到pc
所存的匯編指令地址,繼續(xù)執(zhí)行
7. 葉子函數(shù)和非葉子函數(shù)
- 葉子函數(shù):函數(shù)里面沒有再調(diào)用其他函數(shù)的函數(shù)。
- 非葉子函數(shù):函數(shù)里面還有調(diào)用其他函數(shù)的函數(shù)。
8. 葉子函數(shù)的匯編情況分析
編寫如下 CTest.c
文件
#include <stdio.h>
void haha(){
int a = 5;
int b = 7;
}
使用指令xcrun -sdk iphoneos clang -arch arm64 -S CTest.c
生成 ARM64 匯編代碼 CTest.s
,去掉一些無用注釋得到如下匯編代碼:
_haha:
sub sp, sp, #16 //函數(shù)開始:sp 是棧指針寄存器,這句等價于 `sp=sp-16` 開辟了 16 字節(jié)的棧空間
mov w8, #5 //將 5 賦值給 w8
str w8, [sp, #12] //將 w8 的字?jǐn)?shù)據(jù)存儲到 sp-12 這個地址開始的位置,占據(jù)四個字節(jié)
mov w8, #7 //將 7 賦值給 w8
str w8, [sp, #8] //將 w8 的字?jǐn)?shù)據(jù)存儲到 sp-8 這個地址開始的位置,占據(jù)四個字節(jié)
add sp, sp, #16 //函數(shù)馬上結(jié)束: 這句話等價于 `sp=sp+16`,將 sp 復(fù)位,釋放棧空間
ret //函數(shù)結(jié)束
9. 非葉子函數(shù)的匯編情況分析
編寫如下 CTest.c
文件
#include <stdio.h>
#include <stdio.h>
void haha(){
int a = 5;
int b = 7;
}
void hehe(){
int c = 1;
int d = 3;
haha();
}
使用指令xcrun -sdk iphoneos clang -arch arm64 -S CTest.c
生成 ARM64 匯編代碼 CTest.s
,去掉一些無用注釋得到如下匯編代碼:
_haha:
sub sp, sp, #16 //_haha函數(shù)開始:sp 是棧指針寄存器,這句等價于 `sp=sp-16` 開辟了 16 字節(jié)的棧空間
mov w8, #5 //將 5 賦值給 w8
str w8, [sp, #12] //將 w8 的字?jǐn)?shù)據(jù)存儲到 sp-12 這個地址開始的位置,占據(jù)四個字節(jié)
mov w8, #7 //將 7 賦值給 w8
str w8, [sp, #8] //將 w8 的字?jǐn)?shù)據(jù)存儲到 sp-8 這個地址開始的位置,占據(jù)四個字節(jié)
add sp, sp, #16 //這句話等價于 `sp=sp+16`,將 sp 復(fù)位,釋放棧空間 [這一步和本函數(shù)第一步相呼應(yīng)]
ret //_haha函數(shù)結(jié)束
_hehe:
sub sp, sp, #32 //_hehe函數(shù)開始:sp 是棧指針寄存器,這句等價于 `sp=sp-32` 開辟了 32 字節(jié)的棧空間
stp x29, x30, [sp, #16] //將 x29(fp 寄存器) x30(lr 寄存器)的地址存儲到棧空間 sp+16 地址開始的字?jǐn)?shù)據(jù)中
add x29, sp, #16 //將 x29(fp 寄存器) 指向棧空間 sp+16 地址
mov w8, #1 //將 1 賦值給 w8
stur w8, [x29, #-4] //將 w8 賦值給 x29-4 地址開始的字?jǐn)?shù)據(jù)中
mov w8, #3 //將 3 賦值給 w8
str w8, [sp, #8] //將 w8 賦值給 sp+8 地址開始的字?jǐn)?shù)據(jù)中
bl _haha //程序進入 _haha 函數(shù)中
ldp x29, x30, [sp, #16] //將空間 sp+16 地址開始的字?jǐn)?shù)據(jù)按順序賦值給 x29(fp 寄存器) x30(lr 寄存器) [這一步和本函數(shù)第二步相呼應(yīng)]
add sp, sp, #32 //等價于 sp=sp+32 釋放棧空間 [這一步和本函數(shù)第一步相呼應(yīng)]
ret //_hehe函數(shù)結(jié)束
x29(fp 幀指針寄存器) x30(lr 鏈接寄存器) 以及 sp 棧指針寄存器是維護
棧平衡
的主要角色。iOS 系統(tǒng)分配棧空間常常以 16 字節(jié)的倍數(shù)進行分配 16、32、64
什么情況下棧空間會出現(xiàn)不夠用?通常出現(xiàn)在遞歸調(diào)用的時候,不斷嵌套
為什么有時候訪問野指針會報錯?因為訪問到了操作系統(tǒng)不允許你范圍的內(nèi)存,放著不愿意讓用戶知道的信息,所以給你一個
EXEC_BAD_ADDRESS
。