匯編總結(jié)
匯編的發(fā)展史
機(jī)械語言
- 由0和1組成的機(jī)器指令(如:0101 0001 1101 0110)
匯編語言(Assembly Language)
- 使用符號代替機(jī)器語言,也稱符號語言(如:mov,ax,bx)
高級語言
- C\C++\Java\OC\Swift, 更加接近人類的自然語言(如:int a = b;)
我們的代碼在終端設(shè)備上是這樣的過程:
- 匯編語言和機(jī)器語言一一對應(yīng),每一條機(jī)器指令都有與之對應(yīng)的匯編指令
- 匯編語言可以通過編譯得到機(jī)器語言,機(jī)器語言可以通過反匯編得到匯編語言
- 高級語言可以通過編譯得到匯編語言\機(jī)器語言,但是匯編語言\機(jī)器語言幾乎不可能還原成高級語言
匯編語言的特點
- 可以直接訪問、控制各種硬件設(shè)備,比如存儲器、CPU等,能最大限度地發(fā)揮硬件的功能
- 能改不受編譯器的限制,對生成的二進(jìn)制代碼進(jìn)行完全的控制
- 目標(biāo)代碼簡短,占用內(nèi)存少,執(zhí)行速度快
- 匯編指令是機(jī)器指令的助記符,同機(jī)器指令一一對應(yīng)。每一種CPU都有自己的機(jī)器指令集、匯編指令集,所以匯編語言不具備可移植性
- 知識點過多,開發(fā)者需要對CPU等硬件結(jié)構(gòu)有所了解,不易編寫、調(diào)試、維護(hù)
- 不區(qū)分大小寫,比如mov 和MOV 是一樣的
用途
- 軟件安全
- 病毒分析
- 逆向、加殼、脫殼、破解、外掛、免殺、加密解密、漏洞、黑客
- 理解整個計算機(jī)系統(tǒng)的最佳起點和最有效途徑
- 弄清代碼的本質(zhì)
- 函數(shù)的本質(zhì)究竟是什么
- sizeof(其實是一條指令)
- ++a + ++a + ++a 底層如何執(zhí)行的?
- 編譯器到底幫我們做了什么?
- debug 模式和 release 模式有什么關(guān)鍵的地方被我們忽略了
匯編種類
- 目前討論的比較多的有
- 8086 匯編(8086 處理器16bit 的CPU)
- Win 32匯編
- Win 64匯編
- ARM 匯編(嵌入式、Mac、iOS)
- 我們iPhone 里用到的都是ARM 匯編,但是不同的設(shè)備也是有差異的,因CPU的架構(gòu)不同。
架構(gòu) 設(shè)備 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 iPhone6s , iphone6s plus,iPhone6, iPhone6 plus,iPhone5S ,iPad Air, iPad mini2
- 匯編的原理是相通的
幾個必要常識
- 要學(xué)好匯編,首先要了解CPU等硬件的結(jié)構(gòu)
App程序執(zhí)行過程
- 硬件相關(guān)最為關(guān)鍵的是CPU、內(nèi)存
- 在匯編中大部分指令都是和CPU和內(nèi)存相關(guān)的
總線
- 每一個CPU芯片都是多管腳的,這些管腳和總線相連,CPU通過總線跟外部器件進(jìn)行交互
- 總線:一根根導(dǎo)線的集合
- 總線分類
- 地址總線
- 數(shù)據(jù)總線
- 控制總線
例子
- 地址總線
- 它的寬度決定了CPU的尋址能力
- 8086 的地址總線為20,所以尋址能力為1M(2^20)
- 數(shù)據(jù)總線
- 它的寬度決定了CPU的單次數(shù)據(jù)傳達(dá)量,也就是數(shù)據(jù)傳送速度
- 8086 的數(shù)據(jù)總線寬度為16,所以單次最大傳遞2個字節(jié)的數(shù)據(jù)
- 控制總線
- 它的寬度決定了CPU對其它器件的控制能力、能有多少中控制
寄存器
內(nèi)部部件之間由總線連接
- 對于程序員來說,CPU中最主要的部件就是寄存器,可以通過改變寄存器內(nèi)容來實現(xiàn)對CPU的控制
- 不同的CPU,寄存器個數(shù)、結(jié)構(gòu)是不相同的(8086是16位結(jié)構(gòu)的CPU)
- 8086 有14個寄存器
- 都是16位寄存器
可以存放2個字節(jié)
通用寄存器
- AX、BX、CX、DX 這4個寄存器通常用來存放一般性的數(shù)據(jù),稱為通用寄存器(有時有特定的用途)
- 都是16位
- 為了保證兼容性可分位2個獨立的8位來寄存器來使用
- H代表高位寄存器
- L代表地位寄存器
- 通常CPU會先將內(nèi)存中的數(shù)據(jù)存儲到寄存器中,然后在對寄存器中數(shù)據(jù)進(jìn)行運算
例:假設(shè)內(nèi)存中有一塊紅色區(qū)域為3,現(xiàn)在想把它的值加1,并將結(jié)果存到藍(lán)色內(nèi)的內(nèi)存空間 mov ax,紅色內(nèi)存空間 add ax,1 mov 藍(lán)色內(nèi)存空間,ax
字和字節(jié)
- 在匯編的數(shù)據(jù)存儲中,有2個比較常用的單位
- 字節(jié):byte,一個字節(jié)由8bit 組成,可以存儲8位寄存器中
- 字:word, 一個字由2個字節(jié)組成,這2個字節(jié)分別稱為字的高字節(jié)和低字節(jié)
- 比如數(shù)據(jù)2000(4e20h, 01001110001000000B),高字節(jié)是78,低字節(jié)是32
- 一個字存在1個16位寄存器中,這個字的高字節(jié)、低字節(jié)分別存儲在這個寄存器的高8位寄存器、低8位寄存器中
8086的尋址方式
- CPU訪問內(nèi)存單元時,要給出內(nèi)存單元的地址,所有的內(nèi)存單元都有唯一的地址,叫做物理地址
- 8086有20位地址總線,可以傳送20位的地址,1M的尋址能力
- 但它又是16位結(jié)構(gòu)的CPU,它內(nèi)部能夠一次性處理、傳輸、暫時存儲的地址為16位。如果將地址從內(nèi)部簡單地發(fā)出,那么它只能送出16位的地址,表現(xiàn)出來的尋址能力只有64KB
8086采用一種在內(nèi)部用2個16位地址合成的方法來生成1個20位的物理地址
結(jié)論:CPU可以用不同的段地址和偏移地址形成同一個物理地址
比如:CPU要訪問21F60H 單元,則它給出的段地址SA 和偏移地址EA 滿足SA * 16 + EA = 21F60H 即可。
內(nèi)存分段管理
- 8086是用“基礎(chǔ)地址(段地址×16) + 偏移地址 = 物理地址”的方式給出物理地址
為了開發(fā)方便,我們可以采取分段的方法來管理內(nèi)存,比如:
- 地址10000H~100FFH的內(nèi)存單元組成一個段,該段的起始地址(基礎(chǔ)地址)為10000H,段地址為1000H,大小為100H
- 地址10000H1007FH、10080H100FFH的內(nèi)存單元組成2個段,它們的起始地址(基礎(chǔ)地址)為:10000H和10080H,段地址為1000H和1008H,大小都為80H
- 在編程時可以根據(jù)需要,將若干連續(xù)地址的內(nèi)存單元看做一個段,用短地址*16定位段地址的起始地址(基礎(chǔ)地址),用偏移地址定位段中的單元
段寄存器
- 8086在訪問內(nèi)存時要由相關(guān)部件提供內(nèi)存單元的段地址和偏移地址,送入地址加法器合成物理地址
- 是什么部件提供段地址?段地址在8086的段寄存器中存放
- 8086有4個段寄存器:CS、DS、SS、ES,當(dāng)CPU需要訪問內(nèi)存時由這4個段寄存器提供內(nèi)存單元的段地址
- CS(Code Segment):代碼段寄存器
- DS(Data Segment):數(shù)據(jù)段寄存器
- SS(Stack Segment):堆棧段寄存器
- ES(Extra Segment):附加段寄存器
CS和IP
- CS 為代碼段寄存器,IP為指令指針寄存器,他們表示了CPU當(dāng)前讀取指令的地址
任意時刻,8086CPU都會將CS:IP指向的指令作為下一條需要取出執(zhí)行的指令
指令和數(shù)據(jù)
- 在內(nèi)存或者磁盤上,指令和數(shù)據(jù)沒有任何區(qū)別,都是二進(jìn)制信息
CPU在工作的時候把有的信息看做指令,有的信息看做數(shù)據(jù),為同樣的信息賦予了不同的意義
- CPU 根據(jù)什么將內(nèi)存中的信息看做指令?
- CPU將CS:IP 指向的內(nèi)存單元的內(nèi)容看做指令
- 如果內(nèi)存中的某段內(nèi)容曾被CPU執(zhí)行過,那么它所在的內(nèi)存單元比如被CS:IP指向過
jmp指令
- CPU從何處執(zhí)行指令是由CS、IP中的內(nèi)容決定的,我們可以通過改變CS、IP的內(nèi)存來控制CPU的執(zhí)行目標(biāo)指令
- 8086 提供了一個mov指令可以用來修改大部分寄存器的值
- mov ax,10、mov bx,20、
- 但是mov指令不能用于設(shè)置CS、IP的值
通過轉(zhuǎn)移指令來修改CS、IP的值最簡單的就是jmp 指令
DS 和 [arrdeess]
- CPU要讀寫一個內(nèi)存單元時,必須要先給出這個內(nèi)存單元的地址,內(nèi)存單元地址在8086中由段地址和偏移地址組成
- 8086 中有一個DS寄存器,通常用來存放訪問數(shù)據(jù)的段地址
mov bx,1000H mov ds,bx mov al,[0]
- 上面3條指令的作用將10000H(1000:0)中的內(nèi)存數(shù)據(jù)賦值到al寄存器中
- mov al,[address]的意思將DS:address中的內(nèi)存數(shù)據(jù)賦值到al寄存器中
- 由于al是8位寄存器,所以是將一個字節(jié)的數(shù)據(jù)賦值給al寄存器
- 8086不支持將數(shù)據(jù)直接送入段寄存器中,mov ds,1000H是錯誤的
寫幾條指令,將al中的數(shù)據(jù)送入內(nèi)存單元1000H中
mov bx,1000H mov ds,bx mov [0],al
字型數(shù)據(jù)的傳遞(2個字節(jié))
寫出下面指令執(zhí)行后寄存器ax,bx,cx中的值
mov ax,1000H ; ax = 1000H mov ds,ax ; ds = ax mov ax,[0] ; ax = 1123H mov bx,[2] ; bx = 6622H mov cx,[1] ; cx = 2211H add bx,[1] ; bx = 6622H + 2211H = 8833H add cx,[2] ; cx = 8833H
大小端
- 大端模式,是指數(shù)據(jù)的高字節(jié)保存在內(nèi)存的低地址中,而數(shù)據(jù)的低字節(jié)保存在內(nèi)存的高地址中(高低\低高)(Big Endian)
- 小端模式,是指數(shù)據(jù)的高字節(jié)保存在內(nèi)存的高地址中,而數(shù)據(jù)的低字節(jié)保存在內(nèi)存的低地址中(高高\低低) (Little Endian)
匯編指令
- 匯編指令:mov、add、sub...
- 偽指令:assume、Segment、ends、end...等
- 注釋以分號開頭
; 代碼段 assume cs:code code segment mov ax,1122h mov bx,3344h add ax,bx ; 退出指令 mov ah,4ch int 21h code ends end
- segment和ends的作用是定義一個段,segment代表一個段的開始,ends代表一個段的結(jié)束,使用格式為
- 一個有意義的匯編程序中,至少要有一個段作為代碼段存放代碼
- assume
- 將用作代碼段的code段和CPU中的cs寄存器關(guān)聯(lián)起來
- end
- 編譯器遇到end時,就結(jié)束對源程序的編譯
- 下面2句代碼的作用是退出程序
mov ah,4ch int 21h
或者
mov ax,4c00h int 21
棧
棧:是一種具有特殊處理方式的存儲空間(先進(jìn)后出)
- 8086會將CS作為代碼段的段地址,將CS:IP指向的指令作為下一條需要取出執(zhí)行的指令
- 8086會將DS作為數(shù)據(jù)段的段地址,mov ax,[address]就是取出DS:address的內(nèi)存數(shù)據(jù)放到ax寄存器中
- 8086會將SS作為棧段的段地址,任意時刻,SS:SP指向棧頂元素
- 8086提供了PUSH(入棧)和POP(出棧)指令來操作棧段的數(shù)據(jù)
- 比如push ax是將ax的數(shù)據(jù)入棧,pop ax是將棧頂?shù)臄?shù)據(jù)送入ax
push ax
push ax 的執(zhí)行由2個步驟完成
- SP = SP - 2,SS:SP 指向當(dāng)前棧頂前面的單元,以當(dāng)前棧頂前面的單元為新的棧頂;
將ax中的內(nèi)容送入 SS:SP 指向的內(nèi)存單元處,SS:SP此時指向新棧頂
pop ax
pop ax的執(zhí)行過程和push ax剛好相反,由以下兩步完成
- 將SS:SP指向的內(nèi)存單元處的數(shù)據(jù)送入ax中;
- SP=SP+2,SS:SP 指向當(dāng)前棧頂下面的單元,以當(dāng)前棧頂下面的單元為新的棧頂
簡書連接
Loop 指令
- loop 指令和cx 寄存器配合使用,用于操作類似于高級語言的for、while
- 使用格式
mov cx,循環(huán)次數(shù) 標(biāo)號: 循環(huán)執(zhí)行的程序代碼 loop 標(biāo)號
loop 指令流程
步驟1:先將cx 寄存器的值-1,cx = cx - 1
步驟2:判斷cx 的值
- 如果不為零執(zhí)行標(biāo)號代碼,又執(zhí)行步驟1
- 如果為零執(zhí)行l(wèi)oop 后面的代碼
補(bǔ)充:
獲取數(shù)據(jù),除了通過ds段來獲取,還可以利用其它段地址來獲取
mov ax,ds:[0]
8086 偽指令
- db(define byte)自定義字節(jié)
- dw(define word)自定義字
Call 和 ret 指令
Call指令
- call 標(biāo)號
- 將下一條指令的偏移地址入棧!
- 跳轉(zhuǎn)到定位的地址執(zhí)行指令!
ret 指令
- ret 指令就是將棧頂?shù)闹祊op 給IP