一.概述
語(yǔ)言分類
1.機(jī)器語(yǔ)言:
- 由0和1組成的機(jī)器指令,如:0101 0001 1101 0110
2.匯編語(yǔ)言
- 使用符號(hào)代替難編寫(xiě)和很難閱讀機(jī)器語(yǔ)言,也成為符號(hào)語(yǔ)言。如:mov,ax,bx,call
3.高級(jí)語(yǔ)言
- C/C++/OC/Java/Swift,更讓人易讀和編寫(xiě)的語(yǔ)言(更接近人類的自然語(yǔ)言)
編程語(yǔ)言編譯時(shí)的區(qū)別:
C++/OC/Swift... ------編譯器編譯------>匯編代碼------->機(jī)器碼
JS/Python... ------腳本引擎執(zhí)行------>中間代碼------->機(jī)器碼
Java... ------編譯器編譯------>字節(jié)碼----JVM--->機(jī)器碼
匯編語(yǔ)言與機(jī)器語(yǔ)言一一對(duì)應(yīng), 每一條機(jī)器指令都有與之對(duì)應(yīng)的匯編指令
匯編語(yǔ)言可以通過(guò)編譯得到機(jī)器語(yǔ)言,機(jī)器語(yǔ)言可以通過(guò)反匯編得到匯編語(yǔ)言
高級(jí)語(yǔ)言可以通過(guò)編譯得到匯編語(yǔ)言/機(jī)器語(yǔ)言,但匯編語(yǔ)言/機(jī)器語(yǔ)言幾乎不可能還原成高級(jí)語(yǔ)言
特點(diǎn)
- 可直接訪問(wèn)、控制各種硬件設(shè)備。比如存儲(chǔ)器、CPU等,能最大限度地發(fā)揮硬件的功能
- 能夠不受編譯器的限制,對(duì)生成的二進(jìn)制代碼進(jìn)行完全的控制
- 目標(biāo)代碼簡(jiǎn)短,占用內(nèi)存少,執(zhí)行速度快
- 匯編指令是機(jī)器指令的助記符,同機(jī)器指令一一對(duì)應(yīng)。每種CPU都有自己的機(jī)器指令集/匯編指令集,所以匯編語(yǔ)言不具備可移植性
- 匯編語(yǔ)言知識(shí)點(diǎn)過(guò)多,開(kāi)發(fā)者需要對(duì)CPU等硬件結(jié)構(gòu)有所了解,不宜于編寫(xiě)、調(diào)試、維護(hù)
- 不區(qū)分大小寫(xiě),比如mov和MOV是一樣的
用途
- 編寫(xiě)驅(qū)動(dòng)程序、操作系統(tǒng)(比如Linux內(nèi)核的某些關(guān)鍵部分)
- 對(duì)性能要求極高的程序或者代碼片段,可與高級(jí)語(yǔ)言混合使用(內(nèi)嵌匯編)
- 軟件安全
- 病毒分析和防治
- 逆向\加殼\脫殼\破解\外掛\免殺\加密解密\漏洞\黑客
- 理解整個(gè)計(jì)算機(jī)系統(tǒng)的最佳起點(diǎn)和最有效途徑
- 為編寫(xiě)高效代碼打下基礎(chǔ)
- 弄清楚代碼的本質(zhì)
- 函數(shù)的本質(zhì)是什么?
sizeof
++a + ++a + ++a 底層如何執(zhí)行的? - 編譯器到底幫我們干了什么?
- DEBUG模式和RELEASE模式有什么關(guān)鍵的地方被我們忽略
種類
目前討論比較多的匯編語(yǔ)言
- 8086匯編(8086處理器是16bit的CPU)
- Win32匯編
- Win64匯編
- ARM匯編(嵌入式、Mac、iOS)
我們iPhone里面的用到的是ARM匯編,但是不同的設(shè)備也有差異。因?yàn)镃PU的架構(gòu)不同,5s以后的手機(jī)都是ARM64匯編
二.ARM64匯編
1.寄存器
通用寄存器
64bit x0-x28
32bit w0-w28
(lldb) register read x0 //讀取x0地址
x0 = 0x0000000000000003
(lldb) register read //讀取所有寄存器地址
(lldb) register write x0 0x0000000000000123 //寫(xiě)入寄存器x0地址
(lldb) register read x0
x0 = 0x0000000000000123
(lldb) register read w0
w0 = 0x00000123
零寄存器
用來(lái)清空內(nèi)存數(shù)據(jù)
- wzr(32bit),存儲(chǔ)#0x0000,
- xzr(64bit) ,存儲(chǔ)#0x00000000,
程序計(jì)數(shù)器
-
pc 記錄當(dāng)前執(zhí)行的指令的內(nèi)存的位置
WX20190920-102457.png -
lr 記錄函數(shù)返回的內(nèi)存地址的位置,也是x30寄存器
可以用bl指令驗(yàn)證,調(diào)用bl 跳轉(zhuǎn)函數(shù)段后,lr保存下一行代碼內(nèi)存地址(也就是函數(shù)返回地址)
WX20190920-105830.png
堆棧指針
- sp
- fp 也就是x29寄存器
2.指令
- ret 函數(shù)返回
- mov a b 把b賦給a
測(cè)試:
1.創(chuàng)建一個(gè)armtest頭文件和匯編的實(shí)現(xiàn).s文件
//在程序啟動(dòng)的時(shí)候調(diào)用函數(shù)test
int main(int argc, char * argv[]) {
@autoreleasepool {
test();
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}

//armtest.h:
#ifndef armtest_h
#define armtest_h
void test();
#endif /* armtest_h */
//armtest.s:
.text //聲明代碼段
.global _test //指定test函數(shù)為全局函數(shù)
_test:
mov x0,#0x9 //16進(jìn)制9賦給x0寄存器
mov x1,x0 //x0賦給x1寄存器
ret
2.斷點(diǎn)測(cè)試

- add a,b,c 把b,c相加賦值給a
- sub a,b,c 把b,c相減賦值給a
一般寄存器x0-x7存放函數(shù)參數(shù),更多的存放在堆棧中,x0存放函數(shù)的返回值
//1.定義函數(shù):
int addM(int a,int b);
int subM(int a,int b);
//2.調(diào)用函數(shù)
NSLog(@"%d",addM(2,5));
NSLog(@"%d",subM(8,3));
//3.函數(shù)的匯編實(shí)現(xiàn)
.text
.global _addM,_subM
_addM:
add x0,x0,x1 //x0,x1表示存放函數(shù)參數(shù),x0f存放函數(shù)返回值
ret
_subM:
sub x0,x0,x1
ret
- cmp a b 比較a,b(a-b的值寄存器)的結(jié)果放到cpsr寄存器中的標(biāo)志位
- b 跳轉(zhuǎn)到指定程序段[可以結(jié)合條件和cmp指令進(jìn)行跳轉(zhuǎn)eq(相等) ne(不等) gt(大于) ge(大于等于) lt(小于) le(小于等于)]
//register read 查看所有寄存器的值
_cmpM:
mov x0,#0x8
mov x1,#0x2
cmp x0,x1 //x0>x1 cpsr =0x80000000 1000...0000
mov x0,#0x1
mov x1,#0x1
cmp x0,x1 //x0=x1 cpsr = 0x20000000 0010...0000
mov x0,#0x1
mov x1,#0x6
cmp x0,x1 //x0<x1 cpsr = 0x60000000 0110...0000
ret
_bM:
mov x0,#0x7
mov x1,#0x7
cmp x0,x1
beq _bres //eq ne gt ge lt le
mov x0,#0x0 //如果結(jié)果相等就跳轉(zhuǎn)到_bres方法,否則繼續(xù)向下執(zhí)行
ret
_bres:
mov x0,#0x1
ret
- bl 帶返回的跳轉(zhuǎn)指令
_blM:
mov x1,#0x1
mov x2,#0x2
//b code 直接執(zhí)行code段代碼,不執(zhí)行mov x3,#0x3
//bl code 直接執(zhí)行code段代碼,然后再接著執(zhí)行mov x3,#0x3,類似函數(shù)調(diào)用
bl code
mov x3,#0x3
ret
code:
add x0,x1,x2
ret
使用b,bl指令可以簡(jiǎn)單處理if-else和函數(shù)調(diào)用。
查看if-else語(yǔ)句的匯編代碼
int a=3;
int b=5;
if(a>b)
{
printf("a>b");
}else
{
NSLog(@"a<=b");
}

cmp w8,w9 (3,5)
b.le
w8小于等于w9 b.le指令跳到NSLog代碼段
否則直接向下執(zhí)行,遇到NSLog代碼段直接b指令跳過(guò)
查看mytest()函數(shù)調(diào)用的匯編代碼
void mytest(void)
{
NSLog(@"test");
}
int main(int argc, char * argv[]) {
@autoreleasepool {
int a=1;
int b=2;
if(a<b)
{
mytest();
}
printf("continue");
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}

bl指令跳轉(zhuǎn)到mytest()代碼塊,然后再執(zhí)行printf代碼塊
從內(nèi)存中讀取數(shù)據(jù)的指令
- ldr w0,[x1,#0x5] 偏移位是正,[x1,#0x5]表示x1+#0x5賦值給w0;[x1,#0x5]!表示x1+#0x5賦值x1,再賦值給w0
- ldur w0,[x1,#-0x3] 偏移位是負(fù)
- ldp w0,w1,[x1,#0x7]使用兩個(gè)寄存器按低到高位存儲(chǔ)數(shù)據(jù)
_ldrM:
ldr w0,[x1] //把x1的地址(前32bit)賦給w0寄存器
ret

隨便打斷點(diǎn)運(yùn)行一段程序都可以看到這些指令的存在

從內(nèi)存中寫(xiě)入數(shù)據(jù)的指令(用法基本同讀取指令)
- str
- stur
- stp
int a=8;
strM();
strM:
_strM:
str w0,[x1] //把w0寄存器的值賦給x1的內(nèi)存地址
ret

隨便打斷點(diǎn)運(yùn)行一段程序都可以看到這些指令的存在

3.堆棧
函數(shù)的分類:
- 葉子函數(shù)
函數(shù)內(nèi)部不調(diào)用其他函數(shù) - 非葉子函數(shù)
函數(shù)內(nèi)部還要調(diào)用其他函數(shù)
葉子函數(shù)的堆棧調(diào)用:
void leafF()
{
int a=3;
int b=9;
}
int main(int argc, char * argv[]) {
@autoreleasepool {
leafF();
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
leafF對(duì)應(yīng)的匯編:
ARMTest`leafF:
-> 0x100ce66b0 <+0>: sub sp, sp, #0x10 ; =0x10 //sp堆棧指針sp=sp-16
0x100ce66b4 <+4>: orr w8, wzr, #0x3 //3賦值給w8
0x100ce66b8 <+8>: str w8, [sp, #0xc] //sp+12寫(xiě)入w8
0x100ce66bc <+12>: mov w8, #0x9 //9賦值給w8
0x100ce66c0 <+16>: str w8, [sp, #0x8] //sp+8寫(xiě)入w8
0x100ce66c4 <+20>: add sp, sp, #0x10 ; =0x10 //sp堆棧指針sp=sp+16
0x100ce66c8 <+24>: ret

說(shuō)明:
上圖就是模擬一段函數(shù)調(diào)用時(shí)的??臻g;
sub sp,sp #0x10 就是把起始的sp位置-16,目的是分配函數(shù)調(diào)用空間。
str w8, [sp, #0xc]和str w8, [sp, #0x8] 就是把sp+12和sp+8的內(nèi)存分配給a,b(4個(gè)字節(jié));
add sp,sp #0x10 就是把sp的位置+16,如果不+16,那么會(huì)導(dǎo)致內(nèi)存被浪費(fèi),這里是局部變量,所以+16之后讓sp回到起點(diǎn),3,9分配的空間被忽略。
非葉子函數(shù)的堆棧調(diào)用:
void leafF()
{
int a=3;
int b=9;
}
void NOleafF()
{
int a=4;
int b=5;
leafF();
}
int main(int argc, char * argv[]) {
@autoreleasepool {
NOleafF();
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
NOleafF對(duì)應(yīng)的匯編:
ARMTest`NOleafF:
0x10466e6a0 <+0>: sub sp, sp, #0x20 ; =0x20 //sp堆棧指針sp=sp-32
0x10466e6a4 <+4>: stp x29, x30, [sp, #0x10] //x29(fp),x30(lr)寄存器順序?qū)懭雜p+16的地址
0x10466e6a8 <+8>: add x29, sp, #0x10 ; =0x10 //fp指針指向當(dāng)前的fp區(qū)域
-> 0x10466e6ac <+12>: orr w8, wzr, #0x4
0x10466e6b0 <+16>: stur w8, [x29, #-0x4] //4寫(xiě)入x29-4
0x10466e6b4 <+20>: mov w8, #0x5
0x10466e6b8 <+24>: str w8, [sp, #0x8] //5寫(xiě)入sp+8
0x10466e6bc <+28>: bl 0x10466e684 ; leafF at main.m:13 //跳轉(zhuǎn)到內(nèi)部函數(shù)(leafF)執(zhí)行段(葉子函數(shù)執(zhí)行)
0x10466e6c0 <+32>: ldp x29, x30, [sp, #0x10] //從sp+16順序讀取出賦值給x29,x30
0x10466e6c4 <+36>: add sp, sp, #0x20 ; =0x20 //sp堆棧指針sp=sp+32
0x10466e6c8 <+40>: ret

說(shuō)明:
stp x29, x30, [sp, #0x10] 函數(shù)開(kāi)始時(shí)用sp-32這段內(nèi)存空間寫(xiě)入該函數(shù)fp(堆棧信息),lr(函數(shù)返回)的地址
非葉子函數(shù)會(huì)跳轉(zhuǎn)到其他函數(shù)體執(zhí)行代碼,很可能修改x29,x30寄存器中的值,所以函數(shù)跳轉(zhuǎn)回來(lái)之后,需要執(zhí)行
ldp x29, x30, [sp, #0x10] ,為了恢復(fù)當(dāng)前函數(shù)x29,x30寄存器為之前存儲(chǔ)fp,lr的值,達(dá)到堆棧平衡。
所以,也可以稱sp寄存器為棧頂指針,fp寄存器為棧底指針。

