admin 管理员组

文章数量: 888526

汇编学习

汇编分类

  汇编语言种类大致可以分为:8086汇编(16bit)、x86汇编(32bit)、x64汇编(64bit)以及嵌入式汇编等。根据书写格式的不同可将汇编分为:Intel汇编AT&T汇编。GCC编译器中默认使用的是AT&T汇编,两种格式的差异如下:

寻址方式的差异如下:

寄存器

  寄存器是cpu中的数据存储区域,cpu会先将内存中的数据存储到寄存器,再对寄存器中的数据进行运算。不同种类的汇编码中,寄存器也是不一样的,以最常用的几个通用寄存器为例:在64 bit汇编下的rax、rbx、rcx、rdx寄存器,在32 bit汇编下为eax、ebx、ecx、edx,在16 bit汇编下为ax、bx、cx、dx。64 bit汇编码是兼容32 bit和16 bit汇编码的,如下图所示:

在16位汇编下,AX寄存器由AH(高位)和AL(低位)组成,在32位汇编下AX寄存器则是EAX寄存器的低16位,同样在64位汇编下,EAX寄存器是RAX寄存器的低32位,示例如下:

movl    $0, %ax
movl    $11112222h, %eax

  以上汇编码执行完成之后,ax寄存器的值被改变了,不再是 0 ,而是 2222h,说明给eax寄存器赋值会改变低16位ax寄存器的值。对于rax和eax寄存器赋值也是如此。

mov和call指令

例如有以下示例代码:

#include <stdio.h>void call_fun()
{int a = 10;int b = 2;int c = a +b;printf("%d\n",c);
}
int main()
{call_fun();return 0;
}

GCC编译器使用O0编译下得到汇编码如下:

call_fun:.LFB0:.cfi_startprocpushq   %rbp.cfi_def_cfa_offset 16.cfi_offset 6, -16movq    %rsp, %rbp.cfi_def_cfa_register 6subq    $16, %rspmovl    $10, -4(%rbp)movl    $2, -8(%rbp)movl    -4(%rbp), %edxmovl    -8(%rbp), %eaxaddl    %edx, %eaxmovl    %eax, -12(%rbp)movl    -12(%rbp), %eaxmovl    %eax, %esimovl    $.LC0, %edimovl    $0, %eaxcall    printfnopleave.cfi_def_cfa 7, 8ret.cfi_endproc.LFE0:.size   call_fun, .-call_fun.globl  main.type   main, @functionmain:.LFB1:.cfi_startprocpushq   %rbp.cfi_def_cfa_offset 16.cfi_offset 6, -16movq    %rsp, %rbp.cfi_def_cfa_register 6movl    $0, %eaxcall    call_funmovl    $0, %eaxpopq    %rbp.cfi_def_cfa 7, 8ret.cfi_endproc.LFE1:.size   main, .-main.ident  "GCC: (GNU) 8.2.0".section    .note.GNU-stack,"",@progbits 

  汇编指令call call_funcall printf都表示函数调用,调用call_fun函数和调用printf函数打印结果。
  汇编指令movl $10, -4(%rbp)中,-4(%rbp)等价于Intel汇编中的[rbp - 4],表示rbp寄存器减去4之后的地址值。整条汇编指令表示:将立即数10放到-4(%rbp)内存地址所在的存储空间。其中movll用于32位的长字值,与Intel汇编中的mov不同之处在于多了一个数据元素的长度。

  在call_fun和main之前都有pushq %rbpmovq %rsp, %rbp两条指令,做函数的序言(prologue)。在函数的结尾处都有popq %rbpret(epilogue)指令,它们组合形成维护函数的调用栈的作用。

  假设有AT&T汇编指令:movl $3, (1122h),其Intel汇编为:mov dword ptr [1122h], 3,表示将3放到1122h地址所在的存储空间,示意图如下:

如图所示,4字节大小空间如何存储数值3,根据小端模式,从低地址开始向高地址存储,而读取数据都是从低位地址开始往高地址读,在组合时从高地址开始组合到低地址,所以看到00000000 00000000 00000000 00000011在内存中的形式如上。

假设有如下示例代码:

int a = 10;
int b = 2;
void call_fun()
{int c = a + b;printf("%d\n",c);
}
int main()
{ call_fun();return 0;
}

GCC编译器使用O0编译下得到汇编码如下:

     .file   "t.c".text.globl  a.data.align 4.type   a, @object.size   a, 4a:.long   10.globl  b.align 4.type   b, @object.size   b, 4b:.long   2.section    .rodata.LC0:.string "%d\n".text.globl  call_fun.type   call_fun, @functioncall_fun:.LFB0:.cfi_startprocpushq   %rbp.cfi_def_cfa_offset 16.cfi_offset 6, -16movq    %rsp, %rbp.cfi_def_cfa_register 6subq    $16, %rspmovl    a(%rip), %edxmovl    b(%rip), %eaxaddl    %edx, %eaxmovl    %eax, -4(%rbp)movl    -4(%rbp), %eaxmovl    %eax, %esimovl    $.LC0, %edimovl    $0, %eaxcall    printfnopleave.cfi_def_cfa 7, 8ret.cfi_endproc.LFE0:.size   call_fun, .-call_fun.globl  main.type   main, @functionmain:.LFB1:.cfi_startprocpushq   %rbp.cfi_def_cfa_offset 16.cfi_offset 6, -16movq    %rsp, %rbp.cfi_def_cfa_register 6movl    $0, %eaxcall    call_funmovl    $0, %eaxpopq    %rbp.cfi_def_cfa 7, 8ret.cfi_endproc.LFE1:.size   main, .-main.ident  "GCC: (GNU) 8.2.0".section    .note.GNU-stack,"",@progbits 

前面两个示例代码区别仅在于a、b两个变量是局部变量还是全局变量,但得到的汇编码有较大差异,如下图所示:

  如图可知,局部变量的汇编码地址进行了减法操作,通过rbp寄存器的偏移地址获得,即局部变量地址值不是固定的,而全局变量地址是固定的。
  movl (1122h),%eax,指令表示将1122h地址空间中的值取出放到eax,而指令lea (1122h),%eax表示直接将1122h这个地址值赋值给eax,类似于movl 1122h,%eaxlea表示装载有效的地址值,它和mov两者是不同的操作。

注意:

  • 汇编语言不区分大小写。
  • 一般R开头的寄存器是64 bit,占8字节,E开头的寄存器是32 bit,占4字节。
  • 小端模式:高字节放高地址,大端模式相反。

References:
  • =25

本文标签: 汇编学习