x86彙編程序基礎(AT&T語法)

 

 

 

ins.luhannews.cnhtml

1、簡單的彙編程序linux

 如下面這段簡單的彙編代碼爲例shell

.section .data
.section .text
.globl _start
_start:
movl $1, %eax
movl $4, %ebx
int $0x80

(注意是globl不是global;movl(MOVL)不是mov1(MOV一))編程

 

將這段程序保存爲demo.s,而後用匯編器as把彙編程序中的助記符翻譯成機器指令(彙編指令與機器指令是對應的)生成目標文件demo.o。而後用連接器ld把目標文件demo.o連接成可執行文件demo(雖然只有一個目標文件可是也須要通過連接才能成爲可執行文件由於連接器要修改目標文件中的一些信息)。這個程序只作了一件事就是退出,退出狀態爲4。shell中能夠echo $?獲得上一條命令的退出狀態。數組

 

【解釋】:彙編程序中以"."開頭的名稱不是指令的助記符,不會被翻譯成機器指令,而是給彙編器一些特殊的指示,稱爲彙編指示或僞操做。安全

.section .data
.section .text

.section指示把代碼劃分紅若干個段(section),程序被操做系統加載時,每一個段被加載到不一樣的地址,具備不一樣的讀寫執行權限。less

.data段保存程序的數據是可讀寫的,C程序的全局變量也屬於.data段。上邊的程序沒定義數據因此.data是空的。函數

.text段保存代碼,是隻讀和可執行的,後面那些指令都屬於這個.text段。oop

.globl  _start

_start是一個符號(Symbol),符號在彙編程序中表明一個地址,能夠用在指令中,彙編程序通過彙編器的處理後全部的符號都被替換成它所表明的地址值。在C中咱們能夠經過變量名訪問一個變量,其實就是讀寫某個地址的內存單元,咱們經過函數名調用一個函數其實就是調轉到該函數的第一條指令所在的地址,因此變量名和函數名都是符號,本質上是表明內存地址的。spa

.globl指示告訴彙編器_start這個符號要被連接器用到,因此要在目標文件的符號表中給它特殊標記。_start就像C程序的main函數同樣特殊是整個程序的入口,連接器在連接時會查找目標文件中的_start符號表明的地址,把它設置爲整個程序的入口地址,因此每一個彙編程序都要提供一個_start符號而且用.globl聲明。若是一個符號沒有用.globl指示聲明這個符號就不會被連接器用到。

_start:

_start在這裏就像C語言的語句標號同樣。彙編器在處理彙編程序時會計算每一個數據對象和每條指令的地址,當彙編器看到這樣一個標號時,就把它下面一條指令的地址做爲_start這個符號所表明的地址。而_start這個符號又比較特殊事整個程序的入口地址,因此下一條指令movl $1, %eax就成了程序中第一條被執行的指令。

movl $1, %eax

這是一條數據傳送指令,CPU內部產生一個數字1, 而後傳送到eax寄存器中。mov後邊的l表示long,說明是32位的傳送指令。CPU內部產生的數稱爲當即數,在彙編程序中當即數前面加"$"寄存器前面加"%",以便跟符號名區分開。

movl $4, %ebx

與上條指令相似,生成一個當即數4,傳送到ebx寄存器中。

int $0x80

前兩條指令都是爲這條指令作準備的,執行這條指令時:

  1. int指令稱爲軟中斷指令,能夠用這條指令故意產生一個異常。異常的處理與中斷相似,CPU從用戶模式切換到特權模式,而後跳轉到內核代碼中執行異常處理程序。

  2. int指令中的當即數0x80是一個參數,在異常處理程序中根據這個參數決定如何處理,在linux內核中,int $0x80這種異常稱系統調用(System Call)。內核提供了許多系統服務供用戶程序使用,但這些系統服務不能像庫函數(好比printf)那樣調用,由於在執行用戶程序時CPU處於用戶模式不能直接調用內核函數,因此須要經過系統調用切換CPU模式,經過異常處理程序進入內核,用戶程序只能經過寄存器傳幾個參數,以後就要按內核設計好的代碼路線走,而不能由用戶程序爲所欲爲想調那個內核函數,這樣保證了系統服務被安全的調用,在調用結束後CPU再切換回用戶模式,繼續執行int指令後面的指令,在用戶程序看來就像函數的調用和返回同樣。

  3. eax和ebx寄存器的值是傳遞給系統調用的兩個參數,eax的值是系統調用號,1表示_exit系統調用,ebx的值則是傳給_exit系統調用的參數,也就是退出狀態。_exit這個系統調用會終止掉當前進程,而不會返回它繼續執行。不一樣的系統調用須要的參數個數也不一樣,有的會須要ebx、ecx、edx三個寄存器的值作參數,大多數系統調用完成以後是會返回用戶程序繼續執行的,_exit系統調用特殊。

 

x86彙編的兩種語法:intel語法和AT&T語法
x86彙編一直存在兩種不一樣的語法,在intel的官方文檔中使
用intel語法,Windows也使用intel語法,而UNIX平臺的彙編器一
直使用AT&T語法,因此本書使用AT&T語法。 mov %edx,%eax 這條
指令若是用intel語法來寫,就是 mov eax,edx ,寄存器名不加 % 號,
而且源操做數和目標操做數的位置互換。本書不詳細討論這兩種
語法之間的區別,讀者能夠參考[AssemblyHOWTO]。
介紹x86彙編的書不少,UNIX平臺的書都採用AT&T語法,例
如[GroudUp],其它書通常採用intel語法,例如[x86Assembly]。

 

2、x86的寄存器

  x86的通用寄存器eaxebxecxedxediesi。這些寄存器在大多數指令中是能夠任意使用的。但有些指令限制只能用其中某些寄存器作某種用途,例如除法指令idivl規定被除數在eax寄存器中,edx寄存器必須是0,而除數能夠是任何寄存器中。計算結果的商數保存在eax寄存器中(覆蓋被除數),餘數保存在edx寄存器。

  x86的特殊寄存器ebpespeipeflags。eip是程序計數器。eflags保存計算過程當中產生的標誌位,包括進位、溢出、零、負數四個標誌位,在x86的文檔中這幾個標誌位分別稱爲CF、OF、ZF、SF。ebp和esp用於維護函數調用的棧幀。

  esp爲棧指針,用於指向棧的棧頂(下一個壓入棧的活動記錄的頂部),而ebp爲幀指針,指向當前活動記錄的底部。每一個函數的每次調用,都有它本身獨立的一個棧幀,這個棧幀中維持着所須要的各類信息。寄存器ebp指向當前的棧幀的底部(高地址),寄存器esp指向當前的棧幀的頂部(低地址)。

  注意:ebp指向當前位於系統棧最上邊一個棧幀的底部,而不是系統棧的底部。嚴格說來,「棧幀底部」和「棧底」是不一樣的概念;esp所指的棧幀頂部和系統棧的頂部是同一個位置。

 

3、第二個彙編程序

求一組數最大值的彙編程序:

.section .data
data_items:
.long 3,67,34,222,45,75,54,34,44,33,22,11,66,0
.section .text
.globl _start
_start:
movl $0, %edi
movl data_items(,%edi,4), %eax
movl %eax, %ebx
start_loop:
cmpl $0, %eax
je loop_exit
incl %edi
movl data_items(, %edi,4), %eax
cmpl %ebx, %eax
jle start_loop
movl %eax, %ebx
jmp start_loop
loop_exit:
mov $1, %eax
int $0x80

彙編連接執行,而後echo $?會看到輸出222。

 

這個程序在一組數中找到一個最大的數,並把它做爲程序的退出狀態。這段數在.data段給出:

data_items:
.long 3,67,34,222,45,75,54,34,44,33,22,11,66,0

 .long指示聲明一組數,每一個數32位,至關於C數組。數組開頭有個標號data_items,彙編器會把數組的首地址做爲data_items符號所表明的地址,data_items相似於C中的數組名。data_items這個標號沒有.globl聲明是由於它只在這個彙編程序內部使用,連接器不須要知道這個名字的存在。除了.long以外經常使用的聲明:

  • .byte,也是聲明一組數,每一個數8位
  • .ascii,例: .ascii "Hello World",聲明瞭11個數,取值爲相應字符的ASCII碼。和C語言不一樣的是這樣聲明的字符串末尾是沒有'\0'字符的。

data_items數組的最後一個數是0,咱們在一個循環中依次比較每一個數,碰到0的時候就終止循環。在這個循環中:

  • edi寄存器保存數組中的當前位置,每次比較完一個數就把edi的值加1,指向數組中的下一個數。
  • ebx寄存器保存到目前爲止找打的最大值,若是發現有更大的數就更新ebx的值。
  • eax寄存器保存當前要比較的數,每次更新edi以後,就把下一個數讀到eax中。
_start:
movl $0, %edi

初始化edi,指向數組的第0個元素。

 

movl data_items(,%edi,4), %eax

這條指令把數組的第0個元素傳送到eax寄存器中。data_items是數組的首地址,edi的值是數組的下標,4表示數組的每一個元素佔4字節,那麼數組中第edi個元素的地址應該是data_items+edi*4。從這個地址讀數據,寫成指令就是上面那樣。

 

movl %eax, %ebx

ebx的初始值也是數組的第0個元素。

 

下面進入一個循環,在循環的開頭用標號start_loop表示,循環的末尾以後用標號loop_exit表示。

start_loop:
cmpl $0, %eax
je loop_exit

比較eax的值是否是0,若是是0就說明到了數組末尾了,就要跳出循環。cmpl指令將兩個操做數相減,但計算結果並不保存,只是根據計算結果改變eflags寄存器中的標誌位。若是兩個操做數相等,則計算結果爲0,eflags中的ZF位置1。je是一個條件跳轉指令,它檢查eflags中的ZF位,ZF位爲1則發生跳轉,ZF位爲0則不跳轉繼續執行下一條指令。(條件跳轉指令和比較指令是配合使用的)je的e就表示equal

 

incl %edi
movl data_items(,%edi,4), %eax

將edi的值加1,把數組中的下一個數組傳送到eax寄存器中。

 

cmpl %ebx, %eax
jle start_loop

把當前數組元素eax和目前爲止找到的最大值ebx作比較,若是前者小於等於後者,則最大值沒有變,跳轉到循環開頭比較下一個數,不然繼續執行下一條指令。jle也是一個條件跳轉指令,le表示less than or equal

 

movl %eax, %ebx
jmp start_loop

更新了最大值ebx而後跳轉到循環開頭繼續比較下一個數。jmp是一個無條件跳轉指令,什麼條件也不判斷直接跳轉。loop_exit標號後面的指令用_exit系統調用來退出程序。

 

4、尋址方式

訪問內存時在指令中能夠用多種方式表示內存地址。內存尋址在指令中能夠表示成以下的通用格式:

ADDRESS_OR_OFFSET(%BASE_OR_OFFSET,%INDEX,MULTIPLIER)

它所表示的地址能夠這樣計算出來:

FINAL ADDRESS = ADDRESS_OR_OFFSET + BASE_OR_OFFSET + MULTIPLIER * INDEX

其中ADDRESS_OR_OFFSET和MULTIPLIER必須是常數,BASE_OR_OFFSET和INDEX必須是寄存器。在有些尋址方式中會省略這4項中的某些項,至關於這些項是0。

  • 直接尋址:只使用ADDRESS_OR_OFFSET尋址,例如movl ADDRESS, %eax把ADDRESS地址處的32位數傳送到eax寄存器。
  • 變址尋址:movl data_items(,%edi,4), %eax就屬於這種方式,用於訪問數組很方便
  • 間接尋址:只使用BASE_OR_OFFSET尋址,例如movl (%eax), %ebx,把eax寄存器的值看做地址,把這個地址處的32位數傳送到ebx寄存器。
  • 基址尋址:只使用ADDRESS_OR_OFFSET和BASE_OR_OFFSET尋址,例如movl 4(%eax), %ebx,用於訪問結構體成員比較方便,例如一個結構體的基地址保存在eax寄存器中,其中一個成員在結構體內偏移量是4字節,要把這個成員讀上來就能夠用這條指令。
  • 當即數尋址:就是指令中有一個操做數是當即數,例:movl $3, %eax。
  • 寄存器尋址:就是指令中有一個操做數是寄存器。在彙編程序中寄存器用助記符來表示,在機器指令中則要用幾個Bit表示寄存器的編號,這幾個Bit與能夠看作寄存器的地址,可是和內存地址不在一個地址空間。

 

關於彙編程序的Hello World能夠參看個人另外一篇文章:http://www.cnblogs.com/orlion/p/5316519.html

相關文章
相關標籤/搜索