Linux汇编之高级分支

汇编 2015年03月12日 ,

Linux汇编模仿高级条件分支

如果使用过C、C++、java或者其他高级语言,可能使用很多条件语句,而这些条件语句与汇编语言中的的条件语句相差较大。本文中枫竹梦介绍在Linux汇编语言(ASM)中如何写出高级语言的条件语句,采用反汇编C语言程序的方式进行学习。

if语句

高级语言中常用的条件语句是if语句,下面的程序演示C语言程序中的if语句的常见用法:

/* ifthen.c - By furzoom @ Mar 9, 2015 */
#include <stdio.h>
int main()
{
  int a = 100;
  int b = 25;
  if (a > b)
  {
    printf("The higher value is %d\n", a);
  }
  else
  {
    printf("The higher value is %d\n", b);
  }
  return 0;
}

使用GNU编译器的-S参数生成汇编代码,接着查看汇编语言程序:

[mn@furzoom asm]$ gcc -S ifthen.c
[mn@furzoom asm]$ cat ifthen.s
        .file   "ifthen.c"
        .section        .rodata
.LC0:
        .string "The higher value is %d\n"
        .text
.globl main
        .type   main, @function
main:
        leal    4(%esp), %ecx
        andl    $-16, %esp
        pushl   -4(%ecx)
        pushl   %ebp
        movl    %esp, %ebp
        pushl   %ecx
        subl    $36, %esp
        movl    $100, -12(%ebp)
        movl    $25, -8(%ebp)
        movl    -12(%ebp), %eax
        cmpl    -8(%ebp), %eax
        jle     .L2
        movl    -12(%ebp), %eax
        movl    %eax, 4(%esp)
        movl    $.LC0, (%esp)
        call    printf
        jmp     .L4
.L2:
        movl    -8(%ebp), %eax
        movl    %eax, 4(%esp)
        movl    $.LC0, (%esp)
        call    printf
.L4:
        movl    $0, %eax
        addl    $36, %esp
        popl    %ecx
        popl    %ebp
        leal    -4(%ecx), %esp
        ret
        .size   main, .-main
        .ident  "GCC: (GNU) 4.1.2 20080704 (Red Hat 4.1.2-55)"
        .section        .note.GNU-stack,"",@progbits
[mn@furzoom asm]$

如此简单的C函数生成了这么多的代码,因此,在写C语言程序时,要保证代码的简单。下面来分析汇编代码:

leal 4(%esp), %ecx
andl $-16, %esp
pushl -4(%ecx)
pushl %ebp
movl %esp, %ebp
pushl %ecx
subl $36, %esp

这部分代码主要的作用是使main函数的堆栈对齐到16字节的倍数。接着存储EBP寄存器,以便将其作为指向程序中的本地堆栈的指针,然后把本地变量压入堆栈。

接着的代码如下:

movl $100, -12(%ebp)
movl $25, -8(%ebp)

先将变量a值传送给堆栈寄存器中,接着将变量b值传送给堆栈寄存器中。

movl -12(%ebp), %eax
cmpl -8(%ebp), %eax
jle .L2

将变量a的值从堆栈传送到EAX寄存器,然后将其与堆栈中b的值进行比较。而汇编代码中并没有比较a>b,而是查看相反的a< =b。接着.L2是if语句中的else部分:

.L2:
movl -8(%ebp), %eax
movl %eax, 4(%esp)
movl $.LC0, (%esp)
call printf

这是输出变量b的答案的代码,将变量b的值存放到堆栈中,再将输出的文本位置存入堆栈,最后调用printf函数进行输出。然后是if语句的then部分:

movl -12(%ebp), %eax
movl %eax, 4(%esp)
movl $.LC0, (%esp)
call printf
jmp .L4

这是输出变量a的答案的代码,将变量a的值存放到堆栈中,再将输出的文本位置存入堆栈,最后调用printf函数进行输出。最后跳转到.L4部分:

movl $0, %eax
addl $36, %esp
popl %ecx
popl %ebp
leal -4(%ecx), %esp
ret

恢复堆栈,并返回。

最后是程序的一些其他标识:

.size main, .-main
.ident "GCC: (GNU) 4.1.2 20080704 (Red Hat 4.1.2-55)"
.section .note.GNU-stack,"",@progbits

可以总结if语句的汇编语言代码结构如下:

if:
< codition to evaluate >
jxx else ; 条件失败则跳转到else
< then代码段 >
jmp end ; 结束
else:
< else代码段 >
end:

在实际的程序中,要计算的条件会复杂的多。计算if语句的条件变得和if语句本身一样至关重要了。如C语言的if语句:

if ( (eax < ebx) || (eax == ecx) then

生成如下结构的代码:

if:
cmpl %eax, %ebx
jle else
cmpl %eax, %ecx
jne else
then:
< then代码段 >
jmp end
else:
< else代码段 >
end:

这个if语句条件需要两个单独的CMP指令。因为逻辑操作符是OR,所以如果任何一条CMP指令计算结果为真,程序就跳转到else标签。如果逻辑操作符为AND,就需要使用一个中间标签,以便确保两个CMP指令计算都为真。

for循环

接下来要研究的是高级语言中的for语句。以下C语言的for循环:

/* for.c - By forzoom @ Mar 10, 2015 */
#include <stdio.h>
int main()
{
  int i = 0;
  int j;
  for (i = 0; i < 1000; i++)
  {
    j = i * 5;
    printf("The answer is %d\n", j);
  }
  return 0;
}

生成汇编代码如下:

[mn@furzoom asm]$ gcc -S for.c
[mn@furzoom asm]$ cat for.s
        .file   "for.c"
        .section        .rodata
.LC0:
        .string "The answer is %d\n"
        .text
.globl main
        .type   main, @function
main:
        leal    4(%esp), %ecx
        andl    $-16, %esp
        pushl   -4(%ecx)
        pushl   %ebp
        movl    %esp, %ebp
        pushl   %ecx
        subl    $36, %esp
        movl    $0, -12(%ebp)
        movl    $0, -12(%ebp)
        jmp     .L2
.L3:
        movl    -12(%ebp), %edx
        movl    %edx, %eax
        sall    $2, %eax
        addl    %edx, %eax
        movl    %eax, -8(%ebp)
        movl    -8(%ebp), %eax
        movl    %eax, 4(%esp)
        movl    $.LC0, (%esp)
        call    printf
        addl    $1, -12(%ebp)
.L2:
        cmpl    $999, -12(%ebp)
        jle     .L3
        movl    $0, %eax
        addl    $36, %esp
        popl    %ecx
        popl    %ebp
        leal    -4(%ecx), %esp
        ret
        .size   main, .-main
        .ident  "GCC: (GNU) 4.1.2 20080704 (Red Hat 4.1.2-55)"
        .section        .note.GNU-stack,"",@progbits
[mn@furzoom asm]$

与if语句的代码类似,首先是main函数的堆栈初始化,对齐堆栈到16的倍数地址。接着对ESPEBP寄存器执行保存操作。接着无条件跳转到.L2标签,开始for循环。

.L2
cmpl $999, -12(%ebp)
jle .L3

循环条件为循环变量是否小于1000,在汇编中变为小于或者等于999。如果为真就跳转到.L3标签处,为for循环的循环体代码;如果条件不成立,继续执行,为结束代码。

for循环的代码如下:

.L3:
movl -12(%ebp), %edx
movl %edx, %eax
sall $2, %eax
addl %edx, %eax
movl %eax, -8(%ebp)
movl -8(%ebp), %eax
movl %eax, 4(%esp)
movl $.LC0, (%esp)
call printf
addl $1, -12(%ebp)

将第一个变量位置传送给EDX寄存器,然后传送给EAX寄存器,这个变量就是C代码中的i。接着为两个数学操作,SALL指令执行两次EAX寄存器的移位,相当于乘以4。然后再将EDXEAX寄存数相加。两条指令相当于其值乘以5。然后将其赋值第二个变量,C代码中的j变量。然后将其值和输出文本压入堆栈,调用printf函数。最后将第一个变量,C代码中的循环变量i加1。

通过这个例子,可以了解汇编语言中实现for循环的框架大致为:

jmp for
forcode:
< 循环体 >
< 改变计数器 >
for:
< 比较计算器条件 >
jxx forcode

for语句的循环就是这样。

(完)

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

【上一篇】
【下一篇】

发表评论

插入图片

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

回到顶部