X86汇编速成
平时用的电脑都是X86的,但是现在大家都在搞RISC-V,计组也都开始以RISC-V作为示例,所以专门回头来补一下X86的汇编,方便平时使用。
寄存器register
X86_64中一共有16个64位的通用寄存器,分别为:
- RAX, RBX, RCX,RDX, RBP, RSI,RDI, RSP, R8–R15
- RAX用来存储函数返回值,
- RSP用来作为堆栈指针寄存器,RSP增大入站,减小出栈。
- RBP,栈帧指针,标识当前栈帧的起始位置。
- 其余的随便用
Callee save表示当出现函数调用的时候,这些通用寄存器内的值由被调用者保存,即在进入被调用函数后由被调用函数存储到它的栈里面,并在返回前还原回去,与之对应的,Caller save则表示由调用者存储,在进入调用函数前就要自己提前push到自己的栈里面
32位的X86中只有8个通用寄存器,没有R8-R15
栈stack
指令
指令有两种形式,一种是AT&T的,一种是Intel的,我们用Intel风格的
opcode arg1,agr2
这是一段代码:
int add(int a, int b){ return a+b; } int main(){ int a = 1; int b = 2; int c = add(a,b); return c-a-b; }
下面是使用gcc编译得到的汇编代码
.file "test_asm.c" .text .globl add .type add, @function add: .LFB0: .cfi_startproc endbr64 pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 movl %edi, -4(%rbp) movl %esi, -8(%rbp) movl -4(%rbp), %edx movl -8(%rbp), %eax addl %edx, %eax popq %rbp .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE0: .size add, .-add .globl main .type main, @function main: .LFB1: .cfi_startproc endbr64 pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 subq $16, %rsp movl $1, -12(%rbp) movl $2, -8(%rbp) movl -8(%rbp), %edx movl -12(%rbp), %eax movl %edx, %esi movl %eax, %edi call add movl %eax, -4(%rbp) movl -4(%rbp), %eax subl -12(%rbp), %eax subl -8(%rbp), %eax leave .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE1: .size main, .-main .ident "GCC: (Ubuntu 9.4.0-1ubuntu1~20.04.2) 9.4.0" .section .note.GNU-stack,"",@progbits .section .note.gnu.property,"a" .align 8 .long 1f - 0f .long 4f - 1f .long 5 0: .string "GNU" 1: .align 8 .long 0xc0000002 .long 3f - 2f 2: .long 0x3 3: .align 8 4:
下面是在X86_64下使用objdump得到的反汇编指令代码
test_asm.o: file format elf64-x86-64 Disassembly of section .text: 0000000000000000 : 0: f3 0f 1e fa endbr64 ; 引入指令序列断点,并启用64位模式 4: 55 push %rbp ; 保存调用者的栈帧指针到栈中,如上文提到的Callee save 5: 48 89 e5 mov %rsp,%rbp ; 设置当前栈帧指针为栈顶指针 8: 89 7d fc mov %edi,-0x4(%rbp) ; 将第一个参数存储到相对于栈帧指针偏移为-4的位置 b: 89 75 f8 mov %esi,-0x8(%rbp) ; 将第二个参数存储到相对于栈帧指针偏移为-8的位置 e: 8b 55 fc mov -0x4(%rbp),%edx ; 将第一个参数加载到寄存器edx中 11: 8b 45 f8 mov -0x8(%rbp),%eax ; 将第二个参数加载到寄存器eax中 14: 01 d0 add %edx,%eax ; 执行加法操作,将edx和eax的值相加,结果存储在eax中 16: 5d pop %rbp ; 恢复调用者的栈帧指针 17: c3 retq ; 返回至调用者 0000000000000018 : 18: f3 0f 1e fa endbr64 ; 引入指令序列断点,并启用64位模式 1c: 55 push %rbp ; 保存调用者的栈帧指针到栈中 1d: 48 89 e5 mov %rsp,%rbp ; 设置当前栈帧指针为栈顶指针 20: 48 83 ec 10 sub $0x10,%rsp ; 为局部变量分配16字节的栈空间 24: c7 45 f4 01 00 00 00 movl $0x1,-0xc(%rbp) ; 将值1存储到相对于栈帧指针偏移为-12的位置 2b: c7 45 f8 02 00 00 00 movl $0x2,-0x8(%rbp) ; 将值2存储到相对于栈帧指针偏移为-8的位置 32: 8b 55 f8 mov -0x8(%rbp),%edx ; 将第二个参数加载到寄存器edx中 35: 8b 45 f4 mov -0xc(%rbp),%eax ; 将第一个参数加载到寄存器eax中 38: 89 d6 mov %edx,%esi ; 将edx的值复制给esi寄存器,用作add函数的第二个参数 3a: 89 c7 mov %eax,%edi ; 将eax的值复制给edi寄存器,用作add函数的第一个参数 3c: e8 00 00 00 00 callq 41 ; 调用add函数 41: 89 45 fc mov %eax,-0x4(%rbp) ; 将add函数返回值存储到相对于栈帧指针偏移为-4的位置 44: 8b 45 fc mov -0x4(%rbp),%eax ; 将add函数返回值加载到寄存器eax中 47: 2b 45 f4 sub -0xc(%rbp),%eax ; 执行减法操作,将eax的值减去第一个参数的值 4a: 2b 45 f8 sub -0x8(%rbp),%eax ; 执行减法操作,将eax的值减去第二个参数的值 4d: c9 leaveq ; 恢复栈帧并将栈顶指针设置为栈帧指针 4e: c3 retq ; 返回至调用者
也确实是像大家说的,X86的手册太太太长了,X86为了向32位兼容,搞出来的很多机制令人头大
文章版权声明:除非注明,否则均为主机测评原创文章,转载或复制请以超链接形式并注明出处。