越底層越單純!真正的程序員都需要了解的一門非常重要的語言,匯編!
機器語言
- 我們所寫的語言最終安裝在機器上的是什么東西?
- 是機器語言,一堆的0和1.
如0101 001 1101 0110,由0和1組成的機器指令,說白了還是電信號.
這些指令讓我們的cup執行
匯編語言
- 使用符號代替機器語言,也稱符號語言.如 mov ax,bx
- 匯編和機器指令是一一對應的,每一條機器指令都有與之對應的匯編指令
- 匯編語言可以通過編譯得到機器語言,機器語言可以通過反匯編得到匯編語言
- 高級語言可以通過編譯得到匯編語言,但機器語言/匯編語言不能還原成高級語言
高級語言
- 我們平時寫的Oc/swift/C/C++都屬于高級語言,更加接近人類的自然語言
我們的代碼在終端設備上是這樣的過程
匯編語言的特點
- 可以直接訪問、控制各種硬件設備,比如存儲器、CPU等,能最大限度地發揮硬件的功能
- 能夠不受編譯器的限制,對生成的二進制代碼進行完全的控制
- 目標代碼簡短,占用內存少,執行速度快
- 每一種CPU都有自己的機器指令集\匯編指令集,所以匯編語言不具備可移植性
- 知識點過多,開發者需要對CPU等硬件結構有所了解,不易于編寫、調試、維護
- 不區分大小寫,比如mov和MOV是一樣的
- 在匯編中,大部分指令都是和CPU與內存相關的
匯編的用途
- 編寫驅動程序、操作系統(比如Linux內核的某些關鍵部分)
- 對性能要求極高的程序或者代碼片段,可與高級語言混合使用(內聯匯編)
- 軟件安全
- 病毒分析與防治
- 逆向\加殼\脫殼\破解\外掛\免殺\加密解密\漏洞\黑客
哇...破解 哇...外掛 哇...黑客 來自小學生的驚嘆 前段時間我弟弟讓我幫他盜號...! 我很無奈啊有木有...??
- 理解整個計算機系統的最佳起點和最有效途徑
- 為編寫高效代碼打下基礎
- 弄清代碼的本質
- 函數的本質究竟是什么?
- sizeof
- ++a + ++a + ++a 底層如何執行的?
- 編譯器到底幫我們干了什么?
- DEBUG模式和RELEASE模式有什么關鍵的地方被我們忽略
匯編語言的種類
- 討論最多的匯編語言
- 8086匯編(8086處理器是16bit的CPU)
- Win32匯編
- Win64匯編
- ARM匯編(嵌入式、Mac、iOS)
- ......
架構 | 設備 |
---|---|
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) |
armv64 | iPhone6s , iphone6s plus,iPhone6, iPhone6 plus,iPhone5S ,iPad Air, iPad mini2 |
APP/程序的執行過程
總線
- 每一個CPU芯片都有許多管腳,這些管腳和總線相連,CPU通過總線跟外部器件進行交互
- 總線:一根根導線的集合
- 總線的分類
- 地址總線
- 數據總線
- 控制總線
地址總線
- 它的寬度決定了CPU的尋址能力
- 8086的地址總線寬度是20,所以尋址能力是1M( 2^20 )
2 ^ 10 == 1024
2 ^ 20 == 2 ^ 10 * 2 ^ 10 = 1024 * 1024 = 1M
16^5 == 2^4^5 == 2^(4*5) == 2 ^ 20
2^20 == 1M
2^30 == 1G
2^32 == 1G*2^2 == 4G
數據總線
- 它的寬度決定了cup單次傳輸數據的大小,也就是數據傳送速度
- 數據總線直接影響cpu的吞吐量
- 8086數據總線寬度是16,所以單次最大傳送2個字節的數據.
- 1根數據總線代表一個bit位
十根 = 1KB
每一個16進制位代表4個bit,因為兩個16進制位位代表一個字節,一個字節8個bit
一個16進制位 = 4 bit
兩個16進制位 = 1字節
1字節 = 8bit
一個字節 = 8個bit = 2個16進制位
一個字 = 2個字節 (分別為高字節和低字節)
1 Byte == 8 bit
1B == 1Byte == 一個字節
1KB == 1024 Byte
8KB == 1024 * 2^3
1MB == 1024 * 1024 Byte
控制總線
- 它的寬度決定了CPU對其他器件的控制能力、能有多少種控制
練習
- 一個CPU 的尋址能力為8KB,那么它的地址總線的寬度為 (
13
)
尋址能力均是以字節為單元的
1KB = 10根線
1KB = 1024Byte
8=2^3
1024=2^10
8KB = 8*1024=2^3*2^10=2^(3+10)=2^13
- 8080,8088,80286,80386 的地址總線寬度分別為16根,20根,24根,32根.那么他們的尋址能力分別為多少(
64
)KB, (1
)MB
16根 = 2^6 = 64KB
20根 = 2^20 = 1MB
24根 = 2^24 = 2^20 * 2^4 = 16MB
32根 = 2^32 = 1G*2^2 = 4G
- 從內存中讀取1024字節的數據,8086至少要讀(
512
)次,80386至少要讀取(256
)次.
8086數據總線寬度是16,單次最大傳送2個字節的數據
```
#寄存器
* 對程序員來說,CPU中最主要部件是寄存器,可以通過改變寄存器的內容來實現對CPU的控制
* 不同的CPU,寄存器的個數、結構是不相同的(8086是16位結構的CPU)
* 8086有14個寄存器
* 都是16位寄存器,可以放兩個字節

#####通用寄存器
* AX、BX、CX、DX這4個寄存器通常用來存放一般性的數據,稱為通用寄存器(有時也有特定用途)
* 通常,CPU會先將內存中的數據存儲到通用寄存器中,然后再對通用寄存器中的數據進行運算
* 假設內存中有塊紅色內存空間的值是3,現在想把它的值加1,并將結果存儲到藍色內存空間

* 過程
* cpu通過地址總線找到紅色內存空間
* 再通過控制總線告訴內存條我要讀
- 再通過數據總線讀給cpu(cpu通過運算器+1 )
- 在通過cpu通過地址總線找到藍色內存
- 通過控制總線告訴內存我要寫
- 通過數據總線把值傳給藍色內存
* 代碼過程
- mov ax,紅色內存空間
- add ax,1
- mov 藍色內存,ax
- 上一代8086的寄存器都是8位的,為了保證兼容, AX、BX、CX、DX都可分為2個獨立的8位寄存器來使用
- H代表高位寄存器
- L代表低位寄存器


#8086的尋址方式
* CPU訪問內存單元時,要給出內存單元的地址,所有的內存單元都有唯一的地址,叫做物理地址
* 8086有20位地址總線,可以傳送20位的地址,1M的尋址能力
* 但它又是16位結構的CPU,它內部能夠一次性處理、傳輸、暫時存儲的地址為16位。如果將地址從內部簡單地發出,那么它只能送出16位的地址,表現出來的尋址能力只有64KB
**8086采用一種在內部用2個16位地址合成的方法來生成1個20位的物理地址**


#內存分段管理
- 8086是用“基礎地址(段地址×16) + 偏移地址 = 物理地址”的方式給出物理地址
- 在編程時可以根據需要,將若干連續地址的內存單元看做一個段,用段地址×16定為段的起始地址(基礎地址),用偏移地址定位段中的內存單元
#段寄存器
* 8086有4個段寄存器:CS、DS、SS、ES,當CPU需要訪問內存時由這4個段寄存器提供內存單元的段地址
- CS (Code Segment):代碼段寄存器
- DS (Data Segment):數據段寄存器
- SS (Stack Segment):堆棧段寄存器
- ES (Extra Segment):附加段寄存器
#CS和IP
- CS為代碼段寄存器,IP為指令指針寄存器,它們指示了CPU當前要讀取指令的地址
- 任意時刻,8086CPU都會將CS:IP指向的指令作為下一條需要取出執行的指令 (指向下一條指令的地址)
#指令和數據
* 在內存或者磁盤上,指令和數據沒有任何區別,都是二進制信息
- CPU在工作的時候把有的信息看做指令,有的信息看做數據,為同樣的信息賦予了不同的意義
- CPU根據什么將內存中的信息看做指令?
- CPU將CS:IP指向的內存單元的內容看做指令
- 如果內存中的某段內容曾被CPU執行過,那么它所在的內存單元必然被CS:IP指向過
#jmp指令<修改cs、ip的值>
- CPU從何處執行指令是由CS、IP中的內容決定的,我們可以通過改變CS、IP的內容來控制CPU執行目標指令
- 8086提供了一個mov指令(傳送指令),可以用來修改大部分寄存器的值,比如
- mov ax,10、mov bx,20、mov cx,30、mov dx,40
- 但是,mov指令不能用于設置CS、IP的值,8086沒有提供這樣的功能
- 8086提供了另外的指令來修改CS、IP的值,這些指令統稱為轉移指令,最簡單的是jmp指令
#DS和[address]
- CPU要讀寫一個內存單元時,必須要先給出這個內存單元的地址,在8086中,內存地址由段地址和偏移地址組成
- 8086中有一個DS段寄存器,通常用來存放要訪問數據的段地址
```
mov bx,1000H
mov ds,bx
mov al,[0]
```
- 上面3條指令的作用將10000H(1000:0)中的內存數據賦值到al寄存器中
- mov al,[address]的意思將DS:address中的內存數據賦值到al寄存器中
- 由于al是8位寄存器,所以是將一個字節的數據賦值給al寄存器
- 8086不支持將數據直接送入段寄存器中,mov ds,1000H是錯誤的
---
** 寫幾條指令,將al中的數據送入內存單元1000H中 **
```
mov bx,1000H
mov ds,bx
mov [0],al
```
>注意:“mov 內存單元, 內存單元”是不允許的,比如mov[0], [1]
#大小端
- 大端模式,是指數據的高字節保存在內存的低地址中,而數據的低字節保存在內存的高地址中(高低\低高)(Big Endian)
- 小端模式,是指數據的高字節保存在內存的高地址中,而數據的低字節保存在內存的低地址中(高高\低低) (Little Endian)
>注意:ARM既可以工作在大端模式,也可以工作在小端模式
#棧

- 棧:是一種具有特殊的訪問方式的存儲空間(后進先出, Last In Out Firt,LIFO)
- 8086會將SS作為棧段的段地址,任意時刻,SS:SP指向棧頂元素(偏移地址)
- 8086提供了PUSH(入棧)和POP(出棧)指令來操作棧段的數據
- 比如push ax是將ax的數據入棧,pop ax是將棧頂的數據送入ax

>棧是沒有底的,底是我們自己想象的,棧頂是有的,就是sp指針
內存里是絕對有數據的,哪怕是00它也是數據,push相當于改變內存里的數據,我們平時所說的新開辟一塊內存空間,里面沒有數據并不是里面就是0000,而是對于我們來說,這里面我可以改它而不去讀它,讀的話就讀了一個野指針,讀的這塊野指針已經被釋放了,所謂的野指針報錯,就是我們的地址被釋放了,里面并沒有存任何的數據,這時候去拿野指針里的面的東西,我不認識!這叫野.??
- 棧大小可以叫內存的大小嗎?
- 8086 棧大小只有64K
- 內存區域里越往高地址走,我們系統自帶的一些內容不讓我們修改(系統不讓我們修改的內容在高地址)
- pop的時候sp是加(也就是往高地址走)
>pop和push只是偏移我們的sp指針
pop僅僅是讀數據,并沒有改,把sp偏移的數據讀出來
push是改數據,把sp偏移的內存改數據
pop越讀越大
push越改越小
###棧越界
- 當棧滿的時候在使用push指令入棧,或??盏臅r候再使用pop指令出棧,都將發生棧頂超界的問題
- push超界比pop越界要危險的多,因為pop只是讀,而push是改,一旦push越界他有可能把別人的數據改掉(我這個app本來好好的你過來把我的數據改了,那這個app就完蛋了)
>棧以外的地址很有可能存放著具有其他用途的數據,代碼等.這些數據代碼可能在我們的程序中,也有可能在別的程序中(但是由于我們入棧時不小心修改這些代碼,數據,會引發一連串的錯誤)
#棧段
- 對于8086來說,在編程時,可以根據需要,將一組內存單元定義為一個段
- 我們可以將一組長度為N(N<=64KB)、地址連續、起始地址為16倍數的內存單元,當做??臻g來使用,稱為棧段。比如用10010H~1001FH這段內存空間當做棧來使用,我們就可以認為10010H~1001FH是一個棧段,它的段地址為1001H,長度為16字節
- 如何使用push、pop等棧操作指令訪問我們定義的棧段
- 用SS存放棧段的段地址,用SP存放棧頂的偏移地址
#Loop指令
- loop指令和cx寄存器配合使用,用于循環操作類似高級語言的for,while
- 使用格式
```
mov cx,循環次數
標號:
循環執行的程序代碼
loop 標號
```
- loop指令執行流程
步驟1 先將cx寄存器的值 - 1, cx = cx - 1
步驟2 判斷cx 的值
- 如果不為零執行標號的代碼,又執行 步驟 1
- 如果為零執行loop后面的代碼
>補充:
獲取數據,除了通過ds段來獲取.還可以利用其它段地址來獲取
mov ax,ds:[0]
mov ax,cs:[0]
mov ax,ss:[0]
mov ax,es:[0]
#8086偽指令
- db(define byte) 自定義字節
- dw(define word)自定義字
#Call和ret指令
- call指令 (相當于執行一個函數)
- call標號
- 將下一條指令的偏移地址入棧
- 跳轉到定位的地址執行指令!
- ret指令
- ret指令就是將棧頂的值POP給IP,也就是執行下一條指令
- 棧頂得值是什么?
- 下一條指令的偏移地址
###**此文章用作學習筆記**