CPU在運行的時候實際上是讀取指令并一條一條的執(zhí)行诺核,而這些指令是二進制的衔肢,也就是機器碼,但是由于二進制的語言對人類的可讀性不好脖含,因此便出現(xiàn)了匯編語言罪塔,一般而言,匯編語言可以看作是機器碼的文本格式养葵,它們間可以相互轉換征堪,還原成機器碼后便可以被CPU執(zhí)行。其特點有:
- 可直接訪問关拒、控制各種硬件設備佃蚜。比如存儲器、CPU等着绊,能最大限度地發(fā)揮硬件的功能
- 能夠不受編譯器的限制谐算,對生成的二進制代碼進行完全的控制
- 目標代碼簡短,占用內存少归露,執(zhí)行速度快
- 匯編指令是機器指令的助記符洲脂,同機器指令一一對應。每種CPU都有自己的機器指令集/匯編指令集剧包,所以* 匯編語言不具備可移植性
- 匯編語言知識點過多恐锦,開發(fā)者需要對CPU等硬件結構有所了解往果,不宜于編寫、調試一铅、維護
- 不區(qū)分大小寫陕贮,比如mov和MOV是一樣的
對于一個使用高級語言的程序員來說,匯編語言不需要自己編寫馅闽,但是至少需要看得懂飘蚯,知道其中的原理,才能更好的排查問題福也。
總的來說局骤,匯編代碼是被CPU一條一條地取出執(zhí)行的,CPU會通過這些指令來對寄存器和內存等進行操作暴凑。因此學好匯編峦甩,需要對寄存器和內存有一定的了解
寄存器
由于CPU的運算速度通常都比內存讀寫速度要快,而CPU要運算的時候都要從內存中讀取數(shù)據(jù)现喳。為了節(jié)省時間凯傲,一般CPU都自帶緩存,除此之外嗦篱,還自帶了寄存器冰单,用來存儲頻繁使用的數(shù)據(jù),此外CPU還會使用寄存器與內存交換數(shù)據(jù)灸促。
不同CPU里面的寄存器名字和數(shù)量都不一致诫欠,寄存器的用法基本都是想通的,但是大致上可以分成以下種:
通用寄存器
主要是AX浴栽,BX,CX,DX等等
指令寄存器
也就是IP,用于記錄現(xiàn)在程序執(zhí)行到哪
指針寄存器
主要是棧指針寄存器SP和椈牡穑基指針寄存器BP,每當有數(shù)據(jù)入棧/出棧的時候典鸡,都會導致SP改變
段寄存器
標志段的開始被廓,主要有CS,DS,SS,ES等等
當然還有許多別的種類的寄存器沒有提及,比如索引寄存器(SI,DI)萝玷,而且嫁乘,上述的通用寄存器部分又有各自的用途,比如AX用于累加和中斷球碉,CX用于計數(shù)蜓斧、循環(huán)等等。實際上汁尺,如果僅是看代碼的話法精,大部分寄存器在通常情況下都可以看作通用寄存器多律。因為它們存放的數(shù)值被匯編代碼的指令控制的痴突。我們只需要知道的特殊的寄存器(比如IP,SP)即可搂蜓。
內存分段
在程序運行的時候,系統(tǒng)會給程序分配一定的內存空間辽装,這些內存空間會分成好幾段:
代碼段
存放匯編指令帮碰,其段寄存器為CS,此外還有指令寄存器IP,CPU就是從這個段讀取指令
數(shù)據(jù)段
存放全局變量拾积,可以根據(jù)這些變量有沒有被初始化進一步劃分殉挽,其段寄存器為DS
堆棧段
用來存放程序運行期間產生的變量,又分成堆和棧拓巧,其中棧用于存放函數(shù)中的局部變量斯碌,而堆用來存放動態(tài)分配的變量,堆棧段的寄存器為SS,另外有個SP的寄存器永遠指向棧頂。堆和棧的具體區(qū)別如下圖肛度。
擴展段
保存程序其它相關的信息傻唾,其段寄存器為ES
常用匯編指令
每一個處理器匯編指令都不太一樣,但是其基本功能都是相通的承耿,這里僅以8086處理器的指令集為例:
mov
傳送指令冠骄,其用法為mov a,b
加袋,指的是將b的值賦值給a
add
加法凛辣,其用法為add a,b
,將b的值加上a的值賦值給a职烧,即a = a + b
sub
減法扁誓,其用法為sub a, b
,與上面類似,相當于a = a - b
cmp
比較阳堕,其用法為cmp a, b
,比較a和b的大小跋理,其比較的結果存儲在標志寄存器中。
jmp
無條件轉移指令恬总,通過修改IP和CS寄存器前普,使程序跳到目標地址運行
jcc
條件轉移指令,jcc包含一系列的指令壹堰,通過判斷標志寄存器的狀態(tài)決定是否跳轉
call
調用函數(shù)拭卿,程序會調到函數(shù)入口執(zhí)行
ret
函數(shù)返回
高級語言程序結構對應的匯編語言
下面看一下程序的順序結構、選擇結構贱纠、循環(huán)結構下的匯編語言是怎么樣的峻厚,本文所使用的語言為C++,處理器為x86_64谆焊,其指令集跟上面的不太一樣惠桃,而且,其用法跟8086是相反的。
順序結構
在main函數(shù)里面寫上如下代碼:
int a = 5;
int b = 1;
a++;
b += a;
其對應的匯編語言是這樣的
;a = 5
0x100000f94 <+20>: movl $0x5, -0x14(%rbp) ;將5賦值給偏移量為-0x14的內存區(qū)域辜王,也就是說-0x14代表a
;b = 1
0x100000f9b <+27>: movl $0x1, -0x18(%rbp) ;將1賦值給偏移量為-0x18的內存區(qū)域劈狐,也就是說-0x18代表b
;a++
0x100000fa2 <+34>: movl -0x14(%rbp), %edi ;將a賦值給edi寄存器
0x100000fa5 <+37>: addl $0x1, %edi ; edi寄存器加1
0x100000fa8 <+40>: movl %edi, -0x14(%rbp) ;將edi寄存器賦值給a,這個時候完成了a++的操作
;b += a
0x100000fab <+43>: movl -0x14(%rbp), %edi ;將a賦值給edi寄存器
0x100000fae <+46>: addl -0x18(%rbp), %edi ;edi加上b的值
0x100000fb1 <+49>: movl %edi, -0x18(%rbp) ;將edi寄存器賦值給a,這個時候完成了b += a的操作
選擇結構
同樣,在main函數(shù)中寫一個簡單的選擇結構
int a = 5;
if (a>3) {
a++;
}else {
a--;
}
對應的匯編代碼如下:
0x100000f82 <+18>: movl $0x5, -0x14(%rbp) ;a = 5
0x100000f89 <+25>: cmpl $0x3, -0x14(%rbp) ;a 和 3比較
0x100000f8d <+29>: jle 0x100000fa1 ;如果小于等于呐馆,就跳到0x100000fa1執(zhí)行
;下面是a++的實現(xiàn)
0x100000f93 <+35>: movl -0x14(%rbp), %eax
0x100000f96 <+38>: addl $0x1, %eax
0x100000f99 <+41>: movl %eax, -0x14(%rbp)
0x100000f9c <+44>: jmp 0x100000faa ; 實現(xiàn)后程序跳轉到0x100000faa執(zhí)行
;下面是a--
0x100000fa1 <+49>: movl -0x14(%rbp), %eax
0x100000fa4 <+52>: addl $-0x1, %eax
0x100000fa7 <+55>: movl %eax, -0x14(%rbp)
0x100000faa .....
顯然肥缔,匯編語言是使用cmp、jmp和jcc指令實現(xiàn)選擇結構的汹来。
循環(huán)機構
在main函數(shù)中寫一個簡單的循環(huán):
int i = 0;
while(1){
i++;
if(i>5){
break;
}
}
這里使用了while做循環(huán)续膳,實際上跟使用for循環(huán)的匯編代碼是差不多的。
0x100000f82 <+18>: movl $0x0, -0x14(%rbp) ;i = 0
0x100000f89 <+25>: movl -0x14(%rbp), %eax ;這里開始循環(huán)
0x100000f8c <+28>: addl $0x1, %eax
0x100000f8f <+31>: movl %eax, -0x14(%rbp)
0x100000f92 <+34>: cmpl $0x5, -0x14(%rbp)
0x100000f96 <+38>: jle 0x100000fa1
0x100000f9c <+44>: jmp 0x100000fa6 ;如果i>5,則跳出循環(huán)
0x100000fa1 <+49>: jmp 0x100000f89 ;跳回0x100000f89收班,繼續(xù)循環(huán)
0x100000fa6 ....
可以看出坟岔,循環(huán)結構也是通過cmp、jmp和jcc指令實現(xiàn)的摔桦。