匯編語(yǔ)言(assembly language)

一.概述

語(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]));
    }
}
![WX20190917-165220.png](https://upload-images.jianshu.io/upload_images/5734145-05ac6f41ad4d0aa4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

//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è)試


WX20190917-165930.png
  • 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");
        }
WX20190919-150249.png

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]));
    }
}

WX20190919-151814.png

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
WX20190919-163728.png

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


WX20190919-165638.png

從內(nèi)存中寫(xiě)入數(shù)據(jù)的指令(用法基本同讀取指令)

  • str
  • stur
  • stp
int a=8;
strM();

strM:

_strM:
str w0,[x1] //把w0寄存器的值賦給x1的內(nèi)存地址
ret
WX20190919-172217.png

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


WX20190919-172650.png
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    
WX20191011-155954.png

說(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    

WX20191012-155930.png

說(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寄存器為棧底指針。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容