汇编分类
汇编语言种类大致可以分为: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_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp
movl $10, -4(%rbp)
movl $2, -8(%rbp)
movl -4(%rbp), %edx
movl -8(%rbp), %eax
addl %edx, %eax
movl %eax, -12(%rbp)
movl -12(%rbp), %eax
movl %eax, %esi
movl $.LC0, %edi
movl $0, %eax
call printf
nop
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size call_fun, .-call_fun
.globl main
.type main, @function
main:
.LFB1:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $0, %eax
call call_fun
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE1:
.size main, .-main
.ident "GCC: (GNU) 8.2.0"
.section .note.GNU-stack,"",@progbits
汇编指令call call_fun和call printf都表示函数调用,调用call_fun函数和调用printf函数打印结果。
汇编指令movl $10, -4(%rbp)中,-4(%rbp)等价于Intel汇编中的[rbp - 4],表示rbp寄存器减去4之后的地址值。整条汇编指令表示:将立即数10放到-4(%rbp)内存地址所在的存储空间。其中movl中l用于32位的长字值,与Intel汇编中的mov不同之处在于多了一个数据元素的长度。
在call_fun和main之前都有pushq %rbp、movq %rsp, %rbp两条指令,做函数的序言(prologue)。在函数的结尾处都有popq %rbp、ret(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, 4
a:
.long 10
.globl b
.align 4
.type b, @object
.size b, 4
b:
.long 2
.section .rodata
.LC0:
.string "%d\n"
.text
.globl call_fun
.type call_fun, @function
call_fun:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp
movl a(%rip), %edx
movl b(%rip), %eax
addl %edx, %eax
movl %eax, -4(%rbp)
movl -4(%rbp), %eax
movl %eax, %esi
movl $.LC0, %edi
movl $0, %eax
call printf
nop
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size call_fun, .-call_fun
.globl main
.type main, @function
main:
.LFB1:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $0, %eax
call call_fun
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.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,%eax。lea表示装载有效的地址值,它和mov两者是不同的操作。
注意:
- 汇编语言不区分大小写。
- 一般R开头的寄存器是64 bit,占8字节,E开头的寄存器是32 bit,占4字节。
- 小端模式:高字节放高地址,大端模式相反。
References:
- https://www.bilibili.com/video/BV1HJ411z7ou?p=25
7242

被折叠的 条评论
为什么被折叠?



