標籤:
【著作權聲明:尊重原創,轉載請保留出處:blog.csdn.net/shallnet,文章僅供學習交流,請勿用於商業用途】
在此之前我們使用的彙編程式碼範例都是從第一條指令開始,直到最後最後一條指令程式退出。但實際上和進階語言類似,彙編代碼也提供指令來改變程式處理資料方式。 正常情況下,程式要執行要執行的下一條指令是在指令指標寄存器中,指令指標確定程式中哪條指令是應該執行的下一條指令。 當指令指標在程式指令中移動時,EIP寄存器會遞增。指令長度可能是多個位元組,所以指向下一條指令不僅僅是每次是指令指標遞增一。指令指標寄存器(EIP)跟蹤要執行程式的下一條指令代碼,應用程式不能修改指令指標本身,不能使用指定的記憶體位址放在EIP中,相反必須使用能夠改變指令指標的指令來改變預存取緩衝的下一條指令,這些指令稱為分支指令。分支指令可以改變EIP寄存器的值,要麼是無條件改變,要麼是按照條件改變。 當程式遇到跳轉、調用、中斷時,指令指標自動跳轉到另一個位置。
跳轉指令使用單一指令碼:jmp location其中location是要跳轉到的記憶體位址。在組合語言中這個位置是程式碼中的標籤, 類似於C語言中的goto語句。遇到該指令時,指令指標改變為該標籤後面的指令碼的記憶體位址。下面樣本示範跳轉指令操作:
# jmp.s.section .text.globl _start_start: nop movl $1, %eax jmp gotohere movl $10, %ebx int $0x80gotohere: movl $20, %ebx int $0x80
編譯執行,查看程式返回結果:
$ makeas -o jmp.o jmp.s --gstabsld -o jmp jmp.o$ ./jmp$ echo $?20
程式簡單調用系統調用exit,通過查看程式執行返回碼就可以確定跳轉寄生了。我們也可以在偵錯工具中單步運行查看啟動並執行每行代碼來確定跳轉的發生。如下:
(gdb) b *_startBreakpoint 1 at 0x8048054: file jmp.s, line 5.(gdb) rStarting program: /home/allen/as/4_jmp/jmpBreakpoint 1, _start () at jmp.s:55 nop(gdb) s6 movl $1, %eax(gdb) s7 jmp gotohere(gdb) s11 movl $20, %ebx(gdb) s12 int $0x80(gdb) sProgram exited with code 024.(gdb)
重新編譯器,去掉調試資訊,使用objdump程式反組譯碼可執行程式,可以瞭解指令碼在記憶體中是如何安排的:
$ as -o jmp.o jmp.s $ ld -o jmp jmp.o $ objdump -D jmpjmp: file format elf32-i386Disassembly of section .text:08048054 <_start>: 8048054: 90 nop 8048055: b8 01 00 00 00 mov $0x1,%eax 804805a: eb 07 jmp 8048063 <gotohere> 804805c: bb 0a 00 00 00 mov $0xa,%ebx 8048061: cd 80 int $0x8008048063 <gotohere>: 8048063: bb 14 00 00 00 mov $0x14,%ebx 8048068: cd 80 int $0x80
現在可以通過程式make編譯器,在偵錯工具中對照上面反組譯碼中指令碼記憶體位置查看eip寄存器的值。
Breakpoint 1, _start () at jmp.s:55 nop(gdb) n6 movl $1, %eax(gdb) n7 jmp gotohere(gdb) print $eip$1 = (void (*)()) 0x804805a <_start+6>(gdb) n11 movl $20, %ebx(gdb) print $eip$2 = (void (*)()) 0x8048063 <gotohere>(gdb)
可以看到,輸出eip地址0x8048063就是gotohere標籤指向的記憶體位置。
調用指令類似跳轉指令,但是它儲存發生跳轉的位置,在必要時可以返回這個位置。在組合語言中,實現函數就使用調用指令。類似C語言,組合語言函數也是分割功能模組,避免多次編寫相同代碼。調用指令用法:call addraddr為運算元引用程式中的標籤,其被轉換為函數中第一條指令的記憶體位址。函數傳回碼原始部分使用助記符ret。執行call指令時,指令把EIP寄存器的值放到堆棧中,然後修改EIP寄存器以指向被調用的函數地址。當被調用的函數完成後,它從堆棧獲得過去的EIP寄存器值,並且把控制權返回給原始程式,因為在函數中可能對堆棧進行操作,所以EBP經常用作堆棧的基指標,因此在函數的開始通常也把ESP寄存器複製到EBP寄存器中。我們可以因此給出一個組合語言函數的模板:
func_lable: push1 %ebp movl %esp, %ebp <function code here> movl %ebp, %esp popl %ebp ret
在儲存EBP寄存器之後就可以使用它作為堆棧的基指標,在函數中進行對堆棧的所有訪問。在函數返回之前,ESP寄存器必須被恢複為指向發出調用的記憶體位置。下面示範一個簡單的call樣本:
#call.s.section .datamsg: .asciz "this is as call test!\n" len=.-msg.section .text.globl _start_start: nop call output_func movl $0, %ebx movl $1, %eax int $0x80output_func: pushl %ebp movl %esp, %ebp #<function code here> movl $len, %edx movl $msg, %ecx movl $1, %ebx movl $4, %eax int $0x80 movl %ebp, %esp popl %ebp ret
程式調用函數output_func輸出一串字串。make並執行結果如下:
$ make as -o call.o call.s --gstabsld -o call call.o$ ./call this is as call test!$
中斷也可以更改當前指令指標。中斷分非強制中斷和硬中斷,當一個程式被中斷時,指標指標被轉移到被調用的程式,並且從被調用的程式內繼續執行,被調用的程式完成時,它可以把控制返回給發出調用的程式。在之前幾節給出的樣本中,已經使用過中斷。簡單的使用0x80值的INT指令把控制轉移給linux系統調用程式,在中斷髮生時,按照eax寄存器的值執行子函數,有關中斷詳細資料在後面會講到。
條件跳轉條件跳轉按照EFLAGS中的值來判斷是否該跳轉。每個條件跳轉指令都檢查特定的標誌位以便確定是否符合跳轉的條件。EFLAGS寄存器中有很多位,和條件跳轉有關的有5位:0位(進位標誌CF)、11位(溢出標誌OF)、2位(同位標誌PF)、7位(符號標誌SF)、第6位(零標誌ZF)。結合這幾個不同的標誌位可以執行多種跳躍群組合。條件跳轉指令格式如下:jxx addr其中xx是1個到3個字元的條件代碼,addr是程式要跳轉到的位置。下面是所有可用的條件跳轉指令。a 大於時跳轉ae 大於等於b 小於be 小於等於c 進位cxz 如果CX寄存器為0ecxz 如果ECS寄存器為0e 相等na 不大於nae 不大於或者等於nb 不小於nbe 不小於或等於nc 無進位ne 不等於g 大於(有符號)ge 大於等於(有符號)l 小於(有符號)le 小於等於(有符號)ng 不大於(有符號)nge 不大於等於(有符號)nl 不小於nle 不小於等於no 不溢出np 不同位ns 無符號nz 非零o 溢出p 同位pe 如果偶校正po 如果奇數同位s 如果帶符號z 如果為零
EFLAGS寄存器可以通過比較指令比較兩個值來設定,比較指令CMP格式如下:cmp operand1, operand2指令將第二個運算元和第一個運算元進行比較,它對兩個運算元執行減法操作(operand2-operand1),然後設定EFALGS寄存器。如下樣本:
#cmp.s.section .text.globl _start_start: nop movl $11, %eax movl $24, %ebx cmp %eax, %ebx jae greater movl $1, %eax int $0x80greater: movl $11, %ebx movl $1, %eax int $0x80
make,運行結果如下:
$ makeas -o cmp.o cmp.s --gstabsld -o cmp cmp.o$ ./cmp $ echo $?11
通過查看程式返回輸出結果說明發生了條件跳轉,ebx寄存器的值大於大小寄存器中值,所以代碼跳轉到greater標籤處執行,也可以在調試器中單步運行查看代碼執行順序。
linux平台學x86彙編(八):條件跳轉