linux平台学x86汇编(八):条件跳转

【版权声明:尊重原创,转载请保留出处: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 $0x80
gotohere:
    movl $20, %ebx
    int $0x80

编译执行,查看程序返回结果:

$ make
as -o jmp.o jmp.s --gstabs
ld -o jmp jmp.o
$ ./jmp
$ echo $?
20

程序简单调用系统调用exit,通过查看程序执行返回码就可以确定跳转发生了。我们也可以在调试程序中单步运行查看运行的每行代码来确定跳转的发生。如下:

(gdb) b *_start
Breakpoint 1 at 0x8048054: file jmp.s, line 5.
(gdb) r
Starting program: /home/allen/as/4_jmp/jmp

Breakpoint 1, _start () at jmp.s:5
5    nop
(gdb) s
6    movl $1, %eax
(gdb) s
7    jmp gotohere
(gdb) s
11    movl $20, %ebx
(gdb) s
12    int $0x80
(gdb) s

Program exited with code 024.
(gdb)

重新编译程序,去掉调试信息,使用objdump程序反汇编可执行程序,可以了解指令码在内存中是如何安排的:

$ as -o jmp.o jmp.s
$ ld -o jmp jmp.o 

$ objdump -D jmp
jmp:     file format elf32-i386
Disassembly 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    $0x80
08048063 <gotohere>:
8048063: bb 14 00 00 00        mov    $0x14,%ebx
 8048068: cd 80                 int    $0x80

现在可以通过程序make编译程序,在调试程序中对照上面反汇编中指令码内存位置查看eip寄存器的值。

Breakpoint 1, _start () at jmp.s:5
5    nop
(gdb) n
6    movl $1, %eax
(gdb) n
7    jmp gotohere
(gdb) print $eip
$1 = (void (*)()) 0x804805a <_start+6>
(gdb) n
11    movl $20, %ebx
(gdb) print $eip
$2 = (void (*)()) 0x8048063 <gotohere>
(gdb)

可以看到,输出eip地址0x8048063就是gotohere标签指向的内存位置。

  • 调用指令

调用指令类似跳转指令,但是它保存发生跳转的位置,在必要时可以返回这个位置。在汇编语言中,实现函数就使用调用指令。类似C语言,汇编语言函数也是分割功能模块,避免多次编写相同代码。调用指令用法:

call addr

addr为操作数引用程序中的标签,其被转换为函数中第一条指令的内存地址。函数返回代码原始部分使用助记符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 .data
msg:
    .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 $0x80

output_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 --gstabs
ld -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寄存器为0

ecxz 如果ECS寄存器为0

e 相等

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 $0x80

greater:
    movl $11, %ebx
    movl $1, %eax
    int $0x80

make,运行结果如下:

$ make
as -o cmp.o cmp.s --gstabs
ld -o cmp cmp.o
$ ./cmp
$ echo $?
11

通过查看程序返回输出结果说明发生了条件跳转,ebx寄存器的值大于大小寄存器中值,所以代码跳转到greater标签处执行,也可以在调试器中单步运行查看代码执行顺序。

时间: 2024-10-11 23:14:01

linux平台学x86汇编(八):条件跳转的相关文章

linux平台学x86汇编(十三 ):字符串的比较与搜索

[版权声明:尊重原创,转载请保留出处:blog.csdn.net/shallnet,文章仅供学习交流,请勿用于商业用途] cmps指令用于比较字符串值,cmps指令有三种格式:cmpsb.cmpsw.cmpsl.隐含的源操作数和目标操作数位置存储在esi和edi寄存器中,每次执行cmps指令时,根据DF标志,esi和edi寄存器按照被比较的数据长度递增或递减.cmps指令从源字符串中减去目标字符串,并且适当地设置EFLAGS寄存器的进位.符号.溢出.零.奇偶校验和富足进位标志.cmps指令执行之

linux平台学x86汇编(九):循环指令

[版权声明:尊重原创,转载请保留出处:blog.csdn.net/shallnet,文章仅供学习交流,请勿用于商业用途] 循环也是改变指令执行顺序的一种方式,循环操作重复的执行,直到满足条件.我们可以使用条件跳转指令来创建循环,但事实上汇编语言中有更简单的循环指令系列. 循环指令使用ECX寄存器作为计数器,随着循环指令的执行自动递减它的值,并且不会影响EFLAGS寄存器的标志位,当ecx寄存器值到达0时,0标志不会被设置.循环指令有如下:loop(循环直到ecx寄存器为0).loope/loop

linux平台学x86汇编(十八):内联汇编

[版权声明:尊重原创,转载请保留出处:blog.csdn.net/shallnet,文章仅供学习交流,请勿用于商业用途] 使用汇编语言笔编程最常见的方式是在高级语言(C和C++)程序内编写汇编函数,这种吧汇编语言直接写到C和C++语言程序内的技术称为内联汇编. GNU的C编译器使用asm关键字指出使用汇编语言编写的源代码段落.asm段的基本格式如下: asm("as code"): 括号中的汇编指令必须在括号,指令超过一条的话必须使用新的行分隔汇编语言代码每一行,因为编译器逐字地取得a

linux平台学x86汇编(三):相关开发工具

[版权声明:尊重原创,转载请保留出处:blog.csdn.net/shallnet,文章仅供学习交流,请勿用于商业用途] 类似于其它高级语言,编写汇编语言,必须有一个开发环境,那么也就需要适当的工具了.搭建汇编语言至少应该有下面这些工具:汇编器.链接器.调试器.下下面看看在汇编语言开发环境中如何使用它们. 汇编器 汇编器用于把汇编语言源代码转换为处理器指令码.选择的汇编器必须能够生成所在系统的处理器系列指令码.汇编语言源代码程序有3个部分:操作码助记符.数据段.命令.但是每种汇编器对于每个部分使

linux平台学x86汇编(十九):C语言中调用汇编函数

[版权声明:尊重原创,转载请保留出处:blog.csdn.net/shallnet,文章仅供学习交流,请勿用于商业用途] 除了内联汇编以外,还有一种途径可以把汇编代码整合到C/C++语言中,C/C++语言可以直接调用汇编函数,把输入值传递给函数,然后从函数获得输出值. 如果希望汇编语言函数和C/C++程序一起工作,就必须显示地遵守C样式的函数格式,也就是说所有输入变量都必须从堆栈读取,并且大多数输入值都返回到EAX嫁寄存器中.在汇编函数代码中,C样式函数对于可以修改哪些寄存器和函数必须保留哪些寄

linux平台学x86汇编(五):使用gdb调试汇编程序

[版权声明:尊重原创,转载请保留出处:blog.csdn.net/shallnet,文章仅供学习交流,请勿用于商业用途] 正如C语言一样,编写所有语言程序一样会出现一些一些错误,发生错误时,我们可以使用调试器一步一步运行程序以监视数据是如何被处理的.本节使用GNU调试器检查上一节hello程序,监视处理过程中寄存器和内存的值的变化.要调试汇编语言程序,在编译时,需要使用-gstabs参数重新汇编源代码,使用了该参数编译出来的可执行文件要比之前稍大一些,因为添加了附加信息.上一节程序不使用-gst

linux平台学x86汇编(六):数据的传送

[版权声明:尊重原创,转载请保留出处:blog.csdn.net/shallnet,文章仅供学习交流,请勿用于商业用途] 前面讲了定义数据元素,既然定义了数据元素,那么就需要知道如何处理这些数据元素.数据元素位于内存中,并且处理器很多指令要使用寄存器,所以处理数据元素的第一个步骤就是在内存和寄存器之间传送它们.数据传送指令为mov,其为汇编语言中最常用的指令之一. mov指令的基本格式如下: movx source, dest 其中source和dest的值可以是内存地址.存储在内存中的数值.指

linux平台学x86汇编(二十):汇编库的使用

[版权声明:尊重原创,转载请保留出处:blog.csdn.net/shallnet,文章仅供学习交流,请勿用于商业用途] 汇编语言和C一样,可以通过使用库来简化阻止大量函数的目标文件的问题.GNU C编译器可以不在命令行中独立地包含每个独立地函数目标文件,它允许吧所有目标文件组合在单一存档文件中.在编译C程序时,要做的工作就是包含单一的目标库文件,在编译时,编译器可以从库文件中挑出所需的正确目标文件.在库文件中,经常按照应用程序类型或者函数类型把函数分组在一起,单一应用程序项目可以使用多个库文件

linux平台学x86汇编(四):从“hello world!”开始

[版权声明:尊重原创,转载请保留出处:blog.csdn.net/shallnet,文章仅供学习交流,请勿用于商业用途] 汇编语言程序由定义好的段构成,每个段有各自的目的.三个最常用的的段如下:数据段.bss段.文本段.文本段是可执行程序内声明指令码的地方,所有汇编程序都必须有文本段,数据段和bss段是可选的,但是在程序中经常使用.数据段声明带有初始值的变量,bss段声明使用0值初始化的数据元素,这些元素常用作汇编程序的缓冲区.下图为汇编语言程序的布局. GNU汇编器使用.section命令语句