一 if 和 if else
1. if
int g = 30;
void func(int a,int b){
if (a >b) {
g = a;
}
}
int main(int argc, char * argv[]) {
func(10, 20);
return 0;
}
上面func的匯編代碼如下:
067A0 _func ; CODE XREF: _main+28↓p
var_8 = -8
var_4 = -4
SUB SP, SP, #0x10 //拉伸椞趾校空間
STR W0, [SP,#0x10+var_4] //把w0寫入內(nèi)存 int var_4=w0
STR W1, [SP,#0x10+var_8] //把w1寫入內(nèi)存 int var_8 =w1
LDR W0, [SP,#0x10+var_4] //把w0從內(nèi)存var_4中讀出來 int w0= var_4
LDR W1, [SP,#0x10+var_8] //把w1從內(nèi)存var_8中讀出來 int w1= var_8
CMP W0, W1 //比較w0和w1
B.LE loc_1000067CC //如果w0<=w1那么跳轉(zhuǎn)到loc_1000067CC處執(zhí)行
ADRP X8, #_g@PAGE
ADD X8, X8, #_g@PAGEOFF //以上兩句獲取全局變量g的儲存地址
LDR W9, [SP,#0x10+var_4] //int w9=var_4
STR W9, [X8] // 把w9讀到x8的內(nèi)存地址中去拇舀,即 *x8=w9
loc_1000067CC
ADD SP, SP, #0x10 //恢復棧 棧平衡
RET //返回的時候沒有任何操作以及沒有對x0進行操作,所以沒有返回值
main函數(shù)的匯編如下:
_main
__text:00000001000068F8
__text:00000001000068F8 var_10 = -0x10
__text:00000001000068F8 var_8 = -8
__text:00000001000068F8 var_4 = -4
__text:00000001000068F8 var_s0 = 0
__text:00000001000068F8
__text:00000001000068F8 SUB SP, SP, #0x20
__text:00000001000068FC STP X29, X30, [SP,#0x10+var_s0]
__text:0000000100006900 ADD X29, SP, #0x10
__text:0000000100006904 MOV W8, #0xA
__text:0000000100006908 MOV W9, #0x14
__text:000000010000690C STUR WZR, [X29,#var_4]
__text:0000000100006910 STR W0, [SP,#0x10+var_8]
__text:0000000100006914 STR X1, [SP,#0x10+var_10]
__text:0000000100006918 MOV X0, X8
__text:000000010000691C MOV X1, X9
__text:0000000100006920 BL _func
__text:0000000100006924 MOV W8, #0
__text:0000000100006928 MOV X0, X8
__text:000000010000692C LDP X29, X30, [SP,#0x10+var_s0]
__text:0000000100006930 ADD SP, SP, #0x20
__text:0000000100006934 RET
__text:0000000100006934 ; End of function _main
- 因為在main中調(diào)用func之前
MOV X0, X8 和 MOV X1, X9
x0-x7保存參數(shù)芹枷,w是x的低32位,所以func有兩個參數(shù)阴幌。 - 在main中再沧,因為
MOV W8, #0xA
和MOV W9, #0x14
所以func的兩個參數(shù)分別是#0xA和#0x14換算成10進制為10和20提鸟,以上得到void func(int a, int b)
main中調(diào)用func(10,20)
- 在func的匯編中
SP, SP, #0x10
這是拉伸棧空間宠进,拉伸#0x10即16個字節(jié)。拉伸多少椕牯幔空間和局部變量參數(shù)以及是否保護x29,x30有關(guān)材蹬。 - 根據(jù)func中的注釋的分析,得到:
void func(int a, int b)
{
int var_4=w0;
int var_8 =w1;
int w0= var_4;
int w1= var_8;
if (w0 <= w1}
{
return;
}
g = *x8;
int w9=var_4;
*x8 = w9;
} 由此獲得 void func(int a,intb){ if (a>b){g = a} }
2. if else
int func(){
int a=10;
int b=20;
if (a >b) {
g = a;
}
else{
g=b;
}
return g;
}
匯編如下:
var_8 = -8
var_4 = -4
SUB SP, SP, #0x10 //拉伸16字節(jié)椓吡停空間
MOV W8, #0x14 //int w8 = 0x14即int w8=20;
MOV W9, #0xA //int w9 = 10;
STR W9, [SP,#0x10+var_4]
STR W8, [SP,#0x10+var_8]
LDR W8, [SP,#0x10+var_4]
LDR W9, [SP,#0x10+var_8] //以上為w8,w9進行內(nèi)存保護
CMP W8, W9 // if(w8 <= w9)執(zhí)行l(wèi)oc_1000068E0處代碼
B.LE loc_1000068E0
ADRP X8, #_g@PAGE
ADD X8, X8, #_g@PAGEOFF //獲取全局變量g
LDR W9, [SP,#0x10+var_4]
STR W9, [X8] //x8地址的內(nèi)容為var_4的內(nèi)容
B loc_1000068F0 //跳轉(zhuǎn)到loc_1000068F0處執(zhí)行
---------------------------------------------------------------------------
loc_1000068E0
ADRP X8, #_g@PAGE
ADD X8, X8, #_g@PAGEOFF //獲取全局變量g儲存到x8的地址中
LDR W9, [SP,#0x10+var_8]
STR W9, [X8] //x8的內(nèi)容為var_8的內(nèi)容
loc_1000068F0
ADRP X8, #_g@PAGE
ADD X8, X8, #_g@PAGEOFF //獲取全局變量g儲存到x8的地址中
LDR W0, [X8] //x8的內(nèi)容為x0的內(nèi)容
ADD SP, SP, #0x10 //棧平衡
RET
- 在func中堤器,沒有對x0-x7進行保護,main中x0-x7也沒有入棧末贾,那么func沒有參數(shù)
- func中闸溃,RET之前對w0進行了操作,所以有返回值拱撵。函數(shù)返回值一般為x0(w0是x0的低32位.返回值為x8,x8=g,那么返回值為g
- 還可以看出g進行了賦值操作辉川,賦值給var_4和var_8,那么g的類型為var_4和var_8的類型拴测。
- 還可得出var_4和var_8就是局部變量 W8和W9员串,w8=20;w9 = 10;
以上還原成高級代碼為:
int func()
{
int w8 = 20;
int w9=10;
if(w8 > w9)
{
g=w8;
}
else{
g = w9;
}
*x8 = g;
int w0=*x8;
return w0;
}
總結(jié):CMP 和B.LE(小于等于)再加上B組成if else,只有cmp和B組成if等判斷語句
二 常見的cmp(Compare)比較指令
CMP 把一個寄存器的內(nèi)容和另一個寄存器的內(nèi)容或立即數(shù)進行比較昼扛。但不存儲結(jié)果寸齐,只是正確的更改標志。
一般CMP做完判斷后會進行跳轉(zhuǎn)抄谐,后面通常會跟上B指令渺鹦!
- BL 標號:跳轉(zhuǎn)到標號處執(zhí)行
- BL 標號:比較結(jié)果是小于等于,執(zhí)行標號蛹含,否則不跳轉(zhuǎn)
- B.GT 標號:比較結(jié)果是大于(greater than)毅厚,執(zhí)行標號,否則不跳轉(zhuǎn)
- B.GE 標號:比較結(jié)果是大于等于(greater than or equal to)浦箱,執(zhí)行標號吸耿,否則不跳轉(zhuǎn)
- B.EQ 標號:比較結(jié)果是等于祠锣,執(zhí)行標號,否則不跳轉(zhuǎn)
- B.HI 標號:比較結(jié)果是無符號大于咽安,執(zhí)行標號伴网,否則不跳轉(zhuǎn)
三 循環(huán)
1. do while
void funa(int a)
{
do{
a++;
}while (a<=g);
/*
var_4 = -4
SUB SP, SP, #0x10
STR W0, [SP,#0x10+var_4] //參數(shù)w0入棧 int var_4 = w0;
loc_1000068D4
LDR W8, [SP,#0x10+var_4] //int w8= var_4;
ADD W8, W8, #1 //int w8=w8+1;
STR W8, [SP,#0x10+var_4] //int var_4 = w8;
ADRP X8, #_g@PAGE
ADD X8, X8, #_g@PAGEOFF // *x8 = g
LDR W9, [SP,#0x10+var_4] //int w9= var_4;
LDR W10, [X8] //w10=*x8;
CMP W9, W10
B.LE loc_1000068D4 // if(w9 <=w10)返回去執(zhí)行l(wèi)oc_1000068D4
ADD SP, SP, #0x10 //棧平衡
RET
*/
}
- main的匯編中由
MOV W8, #1 ;MOV X0, X8 ; BL _funa
可以看出funa的參數(shù)為int x0=1。 - 先執(zhí)行l(wèi)oc_1000068D4妆棒,再判斷CMP和 B.LE澡腾,符合條件就再回去執(zhí)行l(wèi)oc_1000068D4,由此可以得出為do while循環(huán)
2. while
void funb(int b){
while (b<g) {
b++;
}
/*
var_4 = -4
SUB SP, SP, #0x10
STR W0, [SP,#0x10+var_4]
loc_1000068D0
ADRP X8, #_g@PAGE
ADD X8, X8, #_g@PAGEOFF
LDR W9, [SP,#0x10+var_4]
LDR W10, [X8]
CMP W9, W10
B.GE loc_1000068F8
LDR W8, [SP,#0x10+var_4]
ADD W8, W8, #1
STR W8, [SP,#0x10+var_4]
B loc_1000068D0
---------------------------------------------------------------------------
loc_1000068F8
ADD SP, SP, #0x10
RET
*/
}
- 大于等于的時候執(zhí)行
loc_1000068F8
恢復棧平衡糕珊,然后RET动分,當不符合條件小于的時候執(zhí)行loc_1000068D0
,然后循環(huán)红选±焦可以判斷出是while(<){...}
3. for循環(huán)
void func(int a){
int sum = 0;
for (int i=0;i<a; i++) {
sum +=i;
}
}
int main(int argc, char * argv[]) {
func(3);
return 0;
}
func的匯編代碼是:
var_C = -0xC
var_8 = -8
var_4 = -4
SUB SP, SP, #0x10
STR W0, [SP,#0x10+var_4]
STR WZR, [SP,#0x10+var_8]
STR WZR, [SP,#0x10+var_C]
loc_1000068C8
LDR W8, [SP,#0x10+var_C]
LDR W9, [SP,#0x10+var_4]
CMP W8, W9
B.GE loc_1000068F8
LDR W8, [SP,#0x10+var_C]
LDR W9, [SP,#0x10+var_8]
ADD W8, W9, W8
STR W8, [SP,#0x10+var_8]
LDR W8, [SP,#0x10+var_C]
ADD W8, W8, #1
STR W8, [SP,#0x10+var_C]
B loc_1000068C8
---------------------------------------------------------------------------
loc_1000068F8
ADD SP, SP, #0x10
RET
- for循環(huán)和while的匯編代碼基本是一樣的,for和while的效率是一樣的
四 Swicth
1. case選擇條件連續(xù)且分支小于等于3時
代碼如下:
void funA(int a){
switch (a) {
case 1:
printf("1");
break;
case 2:
printf("2");
break;
case 3:
printf("3");
break;
default:
printf("default");
break;
}
}
int main(int argc, char * argv[]) {
funA(2);
return 0;
}
xcode動態(tài)匯編分析如下:
- 首先參數(shù)-1喇肋,最小的分支玛瘸,看等不等于,等于就打印苟蹈,然后結(jié)束函數(shù)糊渊;如果不等于就繼續(xù)參數(shù)-2,減去第二個分支慧脱,看等不等于渺绒,以此類推,直到函數(shù)結(jié)束菱鸥。
- 相當于if-else直到函數(shù)結(jié)束
2. case選擇條件連續(xù)且分支大于3時
代碼如下:
void funA(int a){
switch (a) {
case 1:
printf("1");
break;
case 2:
printf("2");
break;
case 3:
printf("3");
break;
case 4:
printf("4");
break;
default:
printf("default");
break;
}
}
- 先減去最大值宗兼,判斷是否是default
-
再差表,偏移量氮采,在表中查到相應地址殷绍,如下:
- 獲取x8地址
0x100a2a844 <+44>: adrp x8, 0
x8=x8(0x100a2a844)去掉低12位 ->x8=0x100a2a000,然后0左移3位,x8=0x100a2a00;0x100a2a848 <+48>: add x8, x8, #0x8c8 ; =0x8c8
x8=0x100a2a000+0x8c8 =0x100a2a8c8 -
ldur x9, [x29, #-0x10]
x9=x8,參數(shù)為2鹊漠,那么x9=1 -
ldrsw x10, [x8, x9, lsl #2]
因為x9, lsl #2
x9左移2位得到偏移量=0x100即4個字節(jié)主到,那么x10=[x8+4] -
memory read 0x100a2a8c8
得到0x100a2a8c8: 94 ff ff ff a8 ff ff ff bc ff ff ff d0 ff ff ff
那么x10=0xffffa8,從右往左讀躯概,一個字節(jié)一個字節(jié)的讀登钥,可以把斷點達到ldrsw x10, [x8, x9, lsl #2]
這一行,然后register read x10
驗證娶靡,驗證得到:x10 = 0xffffffffffffffa8
- 因為x10為負數(shù)牧牢,負數(shù)算法:取反加一(0xff-0xa8+1)得到所以
add x8, x10, x8
x8=x8-0x58= 0x100a2a8c8-0x58= 0x100a2a870 -
0x100f6e858 <+64>: br x8
會跳轉(zhuǎn)到0x100f6e870 <+88>: adrp x0, 1
繼續(xù)執(zhí)行,可以xcode斷點到0x100f6e858 <+64>: br x8
這一行,然后單步走一個ni
進行驗證塔鳍;
switch總結(jié):
1伯铣、假設(shè)switch語句的分支比較少的時候(例如3,少于4的時候沒有意義)沒有必要使用此結(jié)構(gòu)轮纫,相當于if腔寡。
2、各個分支常量的差值較大的時候蜡感,編譯器會在效率還是內(nèi)存進行取舍蹬蚁,這個時候編譯器還是會編譯成類似于if恃泪,else的結(jié)構(gòu)郑兴。
3、在分支比較多的時候:在編譯的時候會生成一個表(跳轉(zhuǎn)表每個地址四個字節(jié))贝乎。
五 查看全局或者常量
int sum(int a, int b){
printf("string");
return a + 3*g;
}
- 常量
printf("string");
找到string
0x100aee7d0 <+20>: adrp x0, 1
0x100aee7d4 <+24>: add x0, x0, #0xf18 ; =0xf18
0x100aee7d8 <+28>: bl 0x100aeebe8 ; symbol stub for: printf
1情连、x0=0x100aee7d0去掉低12位 0x=0x100aee000;1左移3位得到0x1000,0x=0x100aef000
2览效、0x=0x+0xf18即0x=0x100aeff18
3却舀、用p (char*)0x100aeff18
查看得到(char *) $1 = 0x0000000100aeff18 "string"
- 全局變量也一樣
六 編譯器優(yōu)化
- 選擇優(yōu)化策略:build setting -> optimization level ->debug ->fastest smallest然后編譯
int sum(int a, int b){
printf("string");
return a +b;
}
int main(int argc, char * argv[]) {
int c = sum(1, 2);
return 0;
}
0x10050ab44 <+0>: stp x29, x30, [sp, #-0x10]!
0x10050ab48 <+4>: mov x29, sp
0x10050ab4c <+8>: adr x0, #0x13cc ; "string"
0x10050ab50 <+12>: nop
0x10050ab54 <+16>: bl 0x10050abd0 ; symbol stub for: printf
-> 0x10050ab58 <+20>: mov w0, #0x0
0x10050ab5c <+24>: ldp x29, x30, [sp], #0x10
0x10050ab60 <+28>: ret
- 由于int c的值沒有用到,編譯器直接去掉了垃圾代碼
int main(int argc, char * argv[]) {
int c = sum(1, 2);
NSLog(@"%d",c);
return 0;
}
就算用到了int c锤灿,編譯器會直接把結(jié)果賦給c:
0x1001e2b10 <+0>: sub sp, sp, #0x20 ; =0x20
0x1001e2b14 <+4>: stp x29, x30, [sp, #0x10]
0x1001e2b18 <+8>: add x29, sp, #0x10 ; =0x10
0x1001e2b1c <+12>: adr x0, #0x13fc ; "string"
0x1001e2b20 <+16>: nop
0x1001e2b24 <+20>: bl 0x1001e2bc4 ; symbol stub for: printf
-> 0x1001e2b28 <+24>: orr w8, wzr, #0x3
0x1001e2b2c <+28>: str x8, [sp]
0x1001e2b30 <+32>: adr x0, #0x1508 ; @"%d"
0x1001e2b34 <+36>: nop
0x1001e2b38 <+40>: bl 0x1001e2ba0 ; symbol stub for: NSLog
0x1001e2b3c <+44>: mov w0, #0x0
0x1001e2b40 <+48>: ldp x29, x30, [sp, #0x10]
0x1001e2b44 <+52>: add sp, sp, #0x20 ; =0x20
0x1001e2b48 <+56>: ret