Linux汇编之无条件分支

汇编 2015年03月09日 ,

Linux汇编无条件分支

本文中枫竹梦介绍Linux汇编语言(ASM)中的无条件分支语句,并且演示如何在汇编语言程序中使用它们。

无条件分支总体介绍

程序中遇到无条件分支时,指令指针自动转到另一个位置。可以使用的无条件分支有如下3种:

  • 跳转
  • 调用
  • 中断

每种无条件分支在程序中的行为都不同,可以决定在使用哪一种。

跳转

跳转是汇编语言中最基本的分支语句,其与C语言是不提倡使用的goto语句类似。而在汇编语言中使用跳转不认为是不良的设计,而且必须使用它来实现很多功能。

跳转的指令格式如下:

jmp location

其中location是要跳转到的内存地址。这个位置被声明为标签。遇到跳转时,指令指针改变为紧跟在标签后面的指令码的地址。

单一汇编跳转指令被汇编为跳转操作码的3种之一:

  • 短跳转
  • 近跳转
  • 远跳转

具体使用哪种跳转则根据当前指令的内存位置与目的指令内存位置的距离决定。当偏移量小于128字节时使用短跳转;当跳转到另一个段中的指令时,使用远跳转;其他情况用近跳转。汇编中使用单一的指令进行跳转,不用担心跳转的长度问题,但跳转的长度会影响程序的性能。

跳转实例如下:

# jump.s - By furzoom @ Mar 5, 2015
.section .text
.globl _start
_start:
nop
movl $1, %eax
jmp overthere
movl $1, %ebx
int $0x80
overthere:
movl $2, %ebx
int $0x80

程序很简单,EBX寄存器保存返回值,如果程序没有跳转则返回1,如果程序跳转了则返回2。程序返回值可以通过Linux系统的

echo $?

来查看。生成程序并运行:

[mn@furzoom asm]$ as -o jump.o jump.s
[mn@furzoom asm]$ ld -o jump jump.o
[mn@furzoom asm]$ ./jump
[mn@furzoom asm]$ echo $?
2
[mn@furzoom asm]$

正如期望的那样,程序成功跳转,返回值为2。换种方式,使用调试器和objdump程序监视程序中使用的实际内存位置。先通过objdump将程序反汇编,如下:

[mn@furzoom asm]$ objdump -D jump

jump: 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 <overthere>
804805c: bb 01 00 00 00 mov $0x1,%ebx
8048061: cd 80 int $0x80

08048063 <overthere>:
8048063: bb 02 00 00 00 mov $0x2,%ebx
8048068: cd 80 int $0x80
[mn@furzoom asm]$

反汇编器的输出显示每条指令使用的内存位置,二进制指令码及汇编助记码。然后在调试器中运行程序:

[mn@furzoom asm]$ as -gstabs -o jump.o jump.s
[mn@furzoom asm]$ ld -o jump jump.o
[mn@furzoom asm]$ gdb -q jump
Reading symbols from /home/mn/Desktop/Documents/asm/jump...done.
(gdb) break *_start+1
Breakpoint 1 at 0x8048055: file jump.s, line 6.
(gdb) run
Starting program: /home/mn/Desktop/Documents/asm/jump

Breakpoint 1, _start () at jump.s:6
6 movl $1, %eax
(gdb) print /x $eip
$1 = 0x8048055
(gdb) step
7 jmp overthere
(gdb) step
11 movl $2, %ebx
(gdb) print /x $eip
$2 = 0x8048063
(gdb)

正如期望的那样,跳转前后EIP寄存器的值与反汇编的相应指令的内存位置一致。

调用

调用与跳转指令类似,但是要保存发生跳转的位置并且它有在需要的时候返回这个位置的能力。使用它实现函数调用。调用指令有两个部分,第一部分是实际的CALL指令,实现跳转到位置:

call address

address是程序中的标签,它被转换为函数中的第一条指令的内存地址,该指令会将EIP寄存器的值压入堆栈。

第二部分是返回指令,

ret

它使得函数可以返回代码的原始部分。该指令没有操作数。通过查看堆栈,知道应该返回到什么位置。函数的调用与返回时还有一些问题需要考虑,如何保存调用前的一些状态,调用时的参数问题,函数运算结果如何传递给主程序等,都是通过堆栈完成的。

当执行CALL指令时,返回地址被添加到堆栈中。当被调用函数开始时,它必须在某个位置存储ESP寄存器,在RET指令试图返回前,被调用函数可以从这个存储位置恢复ESP寄存器的原始形式。这是由于被调用函数中也可能对堆栈进行操作,所以EBP经常用作堆栈的基指针。函数的模板类似下面的结构:

function_label:
pushl %ebp
movl %esp, %ebp
< 函数代码 >
movl %ebp, %esp
popl %ebp
ret

一个调用的实例如下:

# call.s - By furzoom @ Mar 6, 2015
.section .data
output:
.asciz "Section %d\n"
.section .text
.globl _start
_start:
nop
pushl $1
pushl $output
call printf
addl $8, %esp # clear up stack
call overhere
pushl $3
pushl $output
call printf
addl $8, %esp # clear up stack
pushl $0
call exit
overhere:
pushl %ebp
movl %esp, %ebp
pushl $2
pushl $output
call printf
addl $8, %esp # clear up stack
movl %ebp, %esp
popl %ebp
ret

调用函数时,通过堆栈传递的函数需要在函数返回时,进行清理。运行程序如下:

[mn@furzoom asm]$ as -o call.o call.s
[mn@furzoom asm]$ ld -dynamic-linker /lib/ld-linux.so.2 -lc -o call call.o
[mn@furzoom asm]$ ./call
Section 1
Section 2
Section 3
[mn@furzoom asm]$

如期望的那样,按照顺序输出。

中断

无条件分支的第三种类型是中断。中断是处理器中断当前指令码路径并且切换到不同路径的方式。中断分为两种:

  • 软件中断
  • 硬件中断

硬件中断中硬件层发出的信号,如I/O端口接收到输入信号时。程序可以生成软件中断,将控制权交给另外一个程序。当一个程序调用中断时,该程序暂停执行,被调用的程序接替运行。指令指针被转移到被调用的程序,并且从被调用的程序内继续执行。被调用的程序完成时,它可以把控制权返回给发出调用的程序。

软件中断是操作系统提供的,使应用程序可以使用操作系统的函数,并且在某些情况下甚至可以接触底层的BIOS系统。如DOS的0x21中断,Linux的0x80中断。

调试包含软件中断的应用程序时,查看中断程序中发生了什么是困难的,因为调试信息并没有包含在函数内。

(完)

如无特别说明,本站文章皆为原创,若要转载,务必请注明以下原文信息:
日志标题:《Linux汇编之无条件分支》
日志链接:http://furzoom.com/linux-asm-jmp/
博客名称:枫竹梦

发表评论

插入图片

NOTICE1:请申请gravatar头像,没有头像的评论可能不会被回复!

回到顶部