簡單的匯編程序分析


匯編程序1

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

將這段程序保存為hello.s,然后用匯編器as把匯編程序中的助記符翻譯成機器指令(匯編指令與機器指令是對應的)生成目標文件hello.o。然后用鏈接器ld把目標文件hello.o鏈接成可執行文件hello(雖然只有一個目標文件但是也需要經過鏈接才能成為可執行文件因為鏈接器要修改目標文件中的一些信息)。這個程序只做了一件事就是退出,退出狀態為4。shell中可以echo $?得到上一條命令的退出狀態。

as hello.s -o hello.o
ld hello.o -o hello
./hello
echo $?

匯編程序中以"."開頭的名稱不是指令的助記符,不會被翻譯成機器指令,而是給匯編器一些特殊的指示,稱為匯編指示或偽操作。

.section .data
.section .text

.section指示把代碼划分成若干個段(section),程序被操作系統加載時,每個段被加載到不同的地址,具有不同的讀寫執行權限。

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

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

.globl  _start

_start是一個符號(Symbol),符號在匯編程序中代表一個地址,可以用在指令中,匯編程序經過匯編器的處理后所有的符號都被替換成它所代表的地址值。在C中我們可以通過變量名訪問一個變量,其實就是讀寫某個地址的內存單元,我們通過函數名調用一個函數其實就是調轉到該函數的第一條指令所在的地址,所以變量名和函數名都是符號,本質上是代表內存地址的。

.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系統調用特殊。

匯編程序2

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

.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

這個程序在一組數中找到一個最大的數,並把它作為程序的退出狀態。這段數在.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系統調用來退出程序。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM