协程的学习四:X86-64寄存器和栈帧浅谈(协程铺垫,未完待续12/09)

一、x86-64来源历史

1)说到x86-64,总不免要说说AMD的牛逼,x86-64是x86系列中集大成者,继承了向后兼容的优良传统,
最早由AMD公司提出,代号AMD64;正是由于能向后兼容,AMD公司打了一场漂亮翻身战。导致Intel不
得不转而生产兼容AMD64的CPU。这是IT行业以弱胜强的经典战役。不过,大家为了名称延续性,更习
惯称这种系统结构为x86-64
2)X86-64开创了编译器的新纪元,在之前的时代里,Intel CPU的晶体管数量一直以摩尔
定律在指数发展,各种新奇功能层出不穷,比如:条件数据传送指令cmovg,SSE指令等。但是GCC只能
保守地假设目标机器的CPU是1985年的i386,额。。。这样编译出来的代码效率可想而知,虽然GCC额
外提供了大量优化选项,但是这对应用程序开发者提出了很高的要求,会者寥寥。X86-64的出现,给
GCC提供了一个绝好的机会,在新的x86-64机器上,放弃保守的假设,进而充分利用x86-64的各种特
性,比如:在过程调用中,通过寄存器来传递参数,而不是传统的堆栈。又如:尽量使用条件传送指
令,而不是控制跳转指令

二、x86-64的两种工作模式

1)模式一

32位OS既可以跑在传统模式中,把CPU当成i386来用

2)模式二

又可以跑在64位的兼容模式中,更加神奇的是,可以在32位的OS上跑64位的应用程序

三、寄存器介绍

  • 前提

先明确一点,本文关注的是通用寄存器(后简称寄存器)。既然是通用的,使用并没有限制;后面介
绍寄存器使用规则或者惯例,只是GCC(G++)遵守的规则。因为我们想对GCC编译的C(C++)程序进行分
析,所以了解这些规则就很有帮助

1)64位相对于32位做的让步和x86-64寄存器的介绍

  • 补充

寄存器集成在CPU上,存取速度比存储器快好几个数量级,寄存器多了,GCC就可以更多的
使用寄存器,替换之前的存储器堆栈使用,从而大大提升性能。

  • 具体让步和介绍
1)X86-64中,所有寄存器都是64位,相对32位的x86来说,标识符发生了变化,比如:从原来的%ebp变成
了%rbp。为了向后兼容性,%ebp依然可以使用,不过指向了%rbp的低32位
2)X86-64寄存器的变化,不仅体现在位数上,更加体现在寄存器数量上。新增加寄存器%r8到%r15。加上
x86的原有8个,一共16个寄存器
3)X86-64有16个64位寄存器,分
别是:%rax,%rbx,%rcx,%rdx,%esi,%edi,%rbp,%rsp,
%r8,%r9,%r10,%r11,%r12,%r13,%r14,%r15

2)具体寄存器的功能

1)%rax 作为函数返回值使用
2)%rsp 栈指针寄存器,指向栈顶
3)%edi,%esi,%rdx,%rcx,%r8,%r9 用作函数参数,依次对应第1参数,第2参数。。
4)%rbx,%rbp,%r12,%r13,%14,%15 用作数据存储,遵循被调用者使用规则,简单说就是随便
用,调用子函数之前要备份它,以防他被修改
5)%r10,%r11 用作数据存储,遵循调用者使用规则,简单说就是使用之前要先保存原值,用作中介值

四、栈帧

1)栈帧结构

  • 定义:

在对应机器语言中,GCC把过程转化成栈帧(frame),简单的说,每个
栈帧对应一个过程。X86-32典型栈帧结构中,由%ebp指向栈帧开始,%esp指向栈顶

在这里插入图片描述

2)函数进入和返回

函数的进入和退出,通过指令call和ret来完成,给一个例子

  • 代码
#include <stdlib.h>
#include <stdio.h>
int foo(int x)
{
    int array[] ={1,3,5};
    return array[x];
}

int main(int argc,char*argv[])
{
    int i = 1;
    int j = foo(i);
    fprintf(stdout,"i=%d,j=%d\n",i,j);
    return 0;
}

gcc -S -o show.s show.c

	.file	"show.c"
	.text
	.globl	foo
	.type	foo, @function
foo:
.LFB5:
	.cfi_startproc
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	subq	$48, %rsp
	movl	%edi, -36(%rbp)
	movq	%fs:40, %rax
	movq	%rax, -8(%rbp)
	xorl	%eax, %eax
	movl	$1, -20(%rbp)
	movl	$3, -16(%rbp)
	movl	$5, -12(%rbp)
	movl	-36(%rbp), %eax
	cltq
	movl	-20(%rbp,%rax,4), %eax
	movq	-8(%rbp), %rdx
	xorq	%fs:40, %rdx
	je	.L3
	call	__stack_chk_fail@PLT
.L3:
	leave
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE5:
	.size	foo, .-foo
	.section	.rodata
.LC0:
	.string	"i=%d,j=%d\n"
	.text
	.globl	main
	.type	main, @function
main:
.LFB6:
	.cfi_startproc
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	subq	$32, %rsp
	movl	%edi, -20(%rbp)
	movq	%rsi, -32(%rbp)
	movl	$1, -8(%rbp)
	movl	-8(%rbp), %eax
	movl	%eax, %edi
	call	foo
	movl	%eax, -4(%rbp)
	movq	stdout(%rip), %rax
	movl	-4(%rbp), %ecx
	movl	-8(%rbp), %edx
	leaq	.LC0(%rip), %rsi
	movq	%rax, %rdi
	movl	$0, %eax
	call	fprintf@PLT
	movl	$0, %eax
	leave
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE6:
	.size	main, .-main
	.ident	"GCC: (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0"
	.section	.note.GNU-stack,"",@progbits
  • 注释
1)main函数第55行指令 call foo做了两件事
	pushq	%rbp  //保存下一条指令(main函数第56行的代码地址)的地址,用于函数返回继续执行
	je .L3  //跳到3级CPU缓存
2)Foo函数跳到L3的ret指令相当于
    pop;l %rbp  //恢复指针寄存器
  • 补充
1)push和pushl的区别:
汇编器可以自己判断自己要操作的是什么长度的操作对象;但是当汇编器不能自己判断操作对象长度时,就需要使用pushl之类的指令来指明操作对象长度;
2)je就是跳转
je 是条件转移 (jump) 指令,转移条件是此前的两数比较结果是相等(equal)
3)三级缓存(L1、L2、L3)是什么?
以近代CPU的视角来说,三级缓存(包括L1一级缓存、L2二级缓存、L3三级缓存)都是集成在CPU内
的缓存,它们的作用都是作为CPU与主内存之间的高速数据缓冲区,L1最靠近CPU核心;L2其次;L3
再次。运行速度方面:L1最快、L2次快、L3最慢;容量大小方面:L1最小、L2较大、L3最大。CPU会
先在最快的L1中寻找需要的数据,找不到再去找次快的L2,还找不到再去找L3,L3都没有那就只能去内
存找了

3)栈帧的建立和撤销

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值