当前位置:首页 > 野生技术 > 正文内容

寄存器详解

七星6个月前 (04-30)野生技术514

What is 寄存器?

寄存器的官方叫法有很多,Wiki 上面的叫法是 Processing Register, 也可以称为 CPU Register
寄存器是 CPU 内部的构造,它主要用于信息的存储。
除此之外,CPU 内部还有运算器,负责处理数据;
控制器控制其他组件;
外部总线连接 CPU 和各种部件,进行数据传输;
内部总线负责 CPU 内部各种组件的数据处理
那么, what is 寄存器?通俗讲:
  寄存器就是你的口袋,身上只有那么几个,只装最常用或者马上要用的东西。
  内存就是你的背包。有时候拿点什么放到口袋里,有时候从口袋里拿出点东西放在背包里。
  辅存就是你家里的抽屉。可以放很多东西,但存取不方便。
  断电了就相当于你人没了,在家里复活,家里抽屉里东西还在,但包里口袋里的装备都爆没了。
总的来讲, 寄存器只是临时存放数据的容器,数量有限, 用完就得还.
在认识寄存器之前, 先瞅瞅CPU的结构. 说到CPU不得不提冯诺依曼,下面放一张CPU的内部结构:

CPU 从逻辑上可以分为 3 个模块,分别是控制单元、运算单元和存储单元,这三部分由 CPU 内部总线连接起来。

这里补一个知识点. 内存就是内存条的空间, 内存给自己的每一块地方都定义了地址编号, 这个编号就叫内存地址.

几乎所有的冯·诺伊曼型计算机的 CPU,其工作都可以分为5个阶段:

「取指令、指令译码、执行指令、访存取数、结果写回」

分别看一下这五个阶段:

取指令阶段是将内存中的指令读取到 CPU 中寄存器的过程,程序寄存器用于存储下一条指令所在的地址

指令译码阶段,在取指令完成后,立马进入指令译码阶段,在指令译码阶段,指令译码器按照预定的指令格式,对取回的指令进行拆分和解释,识别区分出不同的指令类别以及各种获取操作数的方法

执行指令阶段,译码完成后,就需要执行这一条指令了,此阶段的任务是完成指令所规定的各种操作,具体实现指令的功能

访问取数阶段,根据指令的需要,有可能需要从内存中提取数据,此阶段的任务是:根据指令地址码,得到操作数在主存中的地址,并从主存中读取该操作数用于运算

结果写回阶段,作为最后一个阶段,结果写回(Write Back,WB)阶段把执行指令阶段的运行结果数据写回到 CPU 的内部寄存器中,以便被后续的指令快速地存取

在现在的计算机中, 寄存器的速度是非常快的

8086 处理器有 14 个寄存器,每个寄存器都有一个特有的名称,即

 「AX,BX,CX,DX,SP,BP,SI,DI,IP,FLAG,CS,DS,SS,ES」

这 14 个寄存器有可能进行具体的划分,按照功能可以分为三种

  通用寄存器

  控制寄存器

  段寄存器

下面我们分别介绍一下这几种寄存器.



通用寄存器

通用寄存器主要有四种 ,即「AX、BX、CX、DX」同样的,这四个寄存器也是 16 位的,能存放两个字节/

在32位CPU中, 这四个寄存器的名字又叫「EAX、EBX、ECX、EDX」,这是32位寄存器,能存放四个字节.

在64位CPU中, 又叫「RAX、RBX、RCX、RDX」,能放八个字节.

本节只讲原理,所以用16位举例.

AX、BX、CX、DX 这四个寄存器一般用来存放数据,也被称为数据寄存器

这四个寄存器又能依据不同的作用向下细分:

AX(Accumulator Register) :累加寄存器, 它主要用于输入/输出和大规模的指令运算,最常用的就是用于存储函数return值.

BX(Base Register):基址寄存器,用来存储基础访问地址. 

CX(Count Register):计数寄存器,CX 寄存器在迭代的操作中会循环计数,比如for循环中的那个i临时变量 ==

DX(data Register):数据寄存器,它也用于输入/输出操作。它还与 AX 寄存器以及 DX 一起使用,用于涉及大数值的乘法和除法运算

下面通过几个简单的汇编指令康康指令怎么操作寄存器:

mov ax,20   //将20放到ax里面
mov bx,ax   //将ax的值放到bx里
通过上面的两个操作, ax的值就和bx相等了, 两个寄存器都存储了20, 可以康康寄存器存20的真实亚子:

20的二进制是10100, 不足16位用0填补高位.

可以发现, 这样的话, 如果存一个较小的数字, 就会造成浪费,寄存器本身就那么几个,所以把每个寄存器又分成了两个部分,即:

    AX 寄存器分为两个独立的 8 位的 AH 和 AL 寄存器

    BX 寄存器分为两个独立的 8 位的 BH 和 BL 寄存器

    CX 寄存器分为两个独立的 8 位的 CH 和 CL 寄存器

    DX 寄存器分为两个独立的 8 位的 DH 和 DL 寄存器

即:

but, 除了上面这几个寄存器以外,其他寄存器均不可以分为两个独立的8位寄存器

那就能对AX寄存器的低八位和高八位分别操作了:

mov ah,80   // 将 80 存到AH里(AX高八位)
add al,10     // 将AL的值加上10, 再存到AL里
AX 相比于其他通用寄存器来说,AX他可以使用特殊的MUL(乘)和DIV(除)指令

再看看BX, BX 被称为数据寄存器,即表明他能够暂存一般数据

和上面说的一样, 可以将BX当做两个独立的8位寄存器使用,即BH和BL

BX除了具有暂存数据的功能外, 还用于寻址, 也就是存物理的内存地址

但他不直接存内存地址, 需要配合段寄存器才能确定真正的地址.关于段寄存器稍后道来.

CX 也是数据寄存器,能够暂存一般性数据

同样可以将 CX 当做两个独立的8位寄存器使用,即有 CH 和 CL

除此之外,CX 也是有其专门的用途的,CX 中的 C 被翻译为 Counting 也就是计数器的功能。

当在汇编指令中使用循环 LOOP 指令时,先将循环的次数存到CX中,每次执行循环 LOOP 时候,CPU会做两件事

    一件事是CX自减 1

    还有一件就是判断 CX 中的值,

        如果 CX 中的值为 0,则会跳出循环.

        如果 CX 中的值不为 0,则会继续执行循环中所指定的指令.

DX就和AX差不多了.



接着就开始讲讲段寄存器

CPU 包含四个段寄存器,用作程序指令,数据或栈的基础位置。

段寄存器主要包含

CS(Code Segment) :代码寄存器,程序代码的基础位置

DS(Data Segment):数据寄存器,变量的基本位置

SS(Stack Segment):栈寄存器,栈的基础位置

ES(Extra Segment):其他寄存器,内存中变量的其他基本位置。

在说段之前, 先讲讲几个基本概念

> 物理地址

之前讲过, CPU 访问内存时,需要知道访问内存的具体地址,内存单元是内存的基本单位,每一个内存单元在内存中都有唯一的地址,这个地址就是物理地址。

而 CPU 和内存之间的交互有三条总线,即数据总线、控制总线和地址总线。

虽然这已经是计算机的常识问题, 但是但是CPU怎么找到内存的具体地址呢?

关于这个问题, 还要先讨论下CPU和内存怎么通信.

我们经常听到32位和64位, 但是什么是32位和64位?

可能最常听到的回答就是, CPU处理32/64位的长度的数据, 嗯, 这是不太准确的说法.

官方话:

  1> CPU 内部的运算器一次最多能处理16/32/64位的数据

      运算器其实就是 ALU,运算控制单元,它是 CPU 内部的三大核心器件之一,主要负责数据的运算。

  2> 寄存器的最大宽度为16/32/64位

      寄存器的最大宽度值就是通用寄存器能处理的二进制数的最大位数

  3> 寄存器和运算器之间的通路为16/32/64位

      也就是寄存器和运算器之间的总线,一次能传输 16 位的数据

好了, 理解什么是计算机的位数了, 就来看看怎么通信, 先看看这个图, 这里主要讲16位:

来理一理这个过程:

CPU 相关组件提供两个地址:段地址和偏移地址,这两个地址都是 16 位的,

他们经由地址加法器变为 20 位的物理地址,

这个地址就是输入输出控制电路传递给内存的物理地址,由此完成物理地址的转换

来看看地址加法器的工作过程:

地址加法器采用 物理地址 = 段地址 * 16 + 偏移地址 的方法用段地址和偏移地址计算出物理地址,

流程图:

其实段地址 * 16 ,就是地址左移 4 位

在上面的叙述中,物理地址 = 段地址 * 16 + 偏移地址,

其实就是 基础地址 + 偏移地址 = 物理地址 这种寻址模式的一种具体实现方案。

基础地址其实就等于段地址 * 16。

好了, 又回到了第一个问题, 啥是段.



段这个概念经常出现在操作系统中,比如在内存管理中,操作系统会把不同的数据分成段来存储,

比如: "代码段、数据段、bss段、rodata段"等:

but in fact, 这些段的划分并不是内存干的

其实,内存没有进行分段,分段完全是由 CPU 搞的

上面说过的通过 基础地址 + 偏移地址 = 物理地址 的方式给出内存单元的物理地址,

使得我们可以分段管理 CPU

好了, 讲这么多, 相信还是没有理解段寄存器是干啥的吧? 嗯, 其实你已经知道了.

上面那个图中的"其他组件"就是段寄存器了.

是不是有种, 豁然开朗的感jio.



段寄存器

CPU 包含四个段寄存器,用作程序指令,数据或栈的基础位置

段寄存器主要包含

CS(Code Segment) :代码寄存器,程序代码的基础位置

DS(Data Segment):数据寄存器,变量的基本位置

SS(Stack Segment):栈寄存器,栈的基础位置

ES(Extra Segment):其他寄存器,内存中变量的其他基本位置

主要还是得看看cs这个寄存器.



CS寄存器

CS要和IP一起寻址的.他俩指出了 CPU 当前需要读取指令的地址

CS 的全称是 Code Segment,即代码寄存器;

而 IP 的全称是 Instruction Pointer ,即指令指针,

现在知道这两个为什么一起出现了吧

在 CPU 内部,由 CS、IP 提供段地址,由加法器负责转换为物理地址,

输入输出控制电路负责输入/输出数据,指令缓冲器负责缓冲指令,

指令执行器负责执行指令。

在内存中有一段连续存储的区域,区域内部存储的是机器码、外面是地址和汇编指令。

上面这幅图的段地址和偏移地址分别是 2000 和 0000,当这两个地址进入地址加法器后,

会由地址加法器负责将这两个地址转换为物理地址

然后地址加法器负责将指令输送到输入输出控制电路中

输入输出控制电路将 20 位的地址总线送到内存中

然后取出对应的数据,也就是 「B8、23、01」,图中的 B8、BB 都是操作数

控制输入/输出电路会将 B8 23 01 送入指令缓存器中。

此时这个指令就已经具备执行条件,

此时 IP 也就是指令指针会自动增加。

我们上面说到 IP 其实就是从 Code Segment 也就是 CS 处偏移的地址,也就是偏移地址。

它会知道下一个需要读取指令的地址,如下图所示

在这之后,指令执行执行取出的 B8 23 01 这条指令。

然后下面再把 2000 和 0003 送到地址加法器中再进行后续指令的读取。

后面的指令读取过程和我们上面探讨的如出一辙,这里就不再赘述惹

通过对上面的描述,我们能总结一下 8086 CPU 的工作过程



8086 CPU 的工作过程

>. 段寄存器提供段地址和偏移地址给地址加法器

>. 由地址加法器计算出物理地址通过输入输出控制电路将物理地址送到内存中

>. 提取物理地址对应的指令,经由控制电路取回并送到指令缓存器中

>. IP 继续指向下一条指令的地址,同时指令执行器执行指令缓冲器中的指令



什么是 Code Segment

Code Segment 即代码段,

它就是我们上面聊到就是 CS 寄存器中存储的基础地址,也就是段地址,

段地址其本质上就是一组内存单元的地址,

例如上面的 「mov ax,0123H 、mov bx, 0003H」

我们可以将长度为 N 的一组代码,存放在一组连续地址、其实地址为 16 的倍数的内存单元中,

我们可以认为,这段内存就是用来存放代码的。



DS 寄存器
CPU 在读写一个内存单元的时候,需要知道这个内存单元的地址。
在 8086 CPU 中,有一个 DS 寄存器,通常用来存放访问数据的段地址。
如果你想要读取一个 10000H 的数据,你可能会需要下面这段代码
mov bx,10000H
mov ds,bx
mov a1,[0]

上面这三条指令就把 10000H 读取到了 a1 中。

在上面汇编代码中,mov 指令有两种传送方式

  1. 一种是把数据直接送入寄存器

  2. 一种是将一个寄存器的内容送入另一个寄存器

但是不仅仅如此,mov 指令还具有下面这几种表达方式

描述

举例
mov 寄存器,数据
比如:mov ax,8
mov 寄存器,寄存器
比如:mov ax,bx
mov 寄存器,内存单元
比如:mov ax,[0]
mov 内存单元,寄存器
比如:mov[0], ax
mov 段寄存器,寄存器
比如:mov ds,ax
接着还有几类寄存器,先瞅瞅索引寄存器


索引寄存器

索引寄存器主要包含段地址的偏移量,索引寄存器主要为

BP(Base Pointer):基础指针, 也叫栈底指针. 它是栈寄存器上的偏移量, 用来定位栈上变量.在函数调用发生时, 需要开辟堆栈, 这个寄存器永远指向栈底.

SP(Stack Pointer): 栈指针,它是栈寄存器上的偏移量. 和BP相反, 他是指向栈顶的.也叫栈顶指针

SI(Source Index): 变址寄存器,用来拷贝源字符串

DI(Destination Index): 目标变址寄存器,用来复制到目标字符串

这里迎来了一个不算新概念的概念.栈, 稍后讲.


状态和控制寄存器

就剩下两种寄存器还没聊了,这两种寄存器是指令指针寄存器和标志寄存器:

IP(Instruction Pointer):指令指针寄存器,它是从 Code Segment 代码寄存器处的偏移来存储执行的下一条指令

FLAG : Flag 寄存器用于存储当前进程的状态,这些状态有

    位置 (Direction):用于数据块的传输方向,是向上传输还是向下传输

    中断标志位 (Interrupt) :1 - 允许;0 - 禁止

    陷入位 (Trap) :确定每条指令执行完成后,CPU 是否应该停止。1 - 开启,0 - 关闭

    进位 (Carry) : 设置最后一个无符号算术运算是否带有进位

    溢出 (Overflow) : 设置最后一个有符号运算是否溢出

    符号 (Sign) : 如果最后一次算术运算为负,则设置 1 =负,0 =正

    零位 (Zero) : 如果最后一次算术运算结果为零,1 = 零

    辅助进位 (Aux Carry) :用于第三位到第四位的进位

    奇偶校验 (Parity) : 用于奇偶校验




好了, 进入栈.
栈, 我相信大部分人已经非常熟悉了,说白了, 栈就是在内存上开辟出来的一块空间.
栈是一种具有特殊的访问方式的存储空间。
它的特殊性就在于,先进入栈的元素,最后才出去,也就是我们常说的先入后出。
咱可以通过一个动画理解栈是怎么存储和读取数据的:

入栈, 就是把数据压到栈里面, 用指令就是: push.
出栈, 就是把数据从栈里面拿出来, 用指令就是pop
举个栗子:
 mov ax, 1
 push ax  //将ax的值存到栈中, 并且SP指针-2
 pop bx  //将栈中前16位拿出来放到bx里, SP+2
因为push和pop都对栈顶进行操作了, 且SP这个指针永远指向栈顶, 且栈是从高地址向低地址生长的, 
所以就有了在push时, SP指针-2以及 pop时,SP+2了.
为啥加2? emm.....
因为咱8086的寄存器能存16位, CPU也就将内存按照16位来分, 所以栈上一小格就是16位, 两个字节. 


栈和 SS 寄存器
对上述压栈出栈的操作, 弄成图片就是:

现在问一个问题,我们上面描述的是 10000H ~ 1000FH 这段空间来作为 push 和 pop 指令的存取单元。

但是,CPU怎么知道这个栈单元就是 10000H ~ 1000FH 呢?也就是说,CPU如何选择指定的栈单元进行存取?

事实上,8086 CPU 有一组关于栈的寄存器 SS 和 SP。

SS 是段寄存器,它存储的是栈的基础位置,也就是栈顶的位置,

而 SP 是栈指针,它存储的是偏移地址。

在任意时刻,SS:SP 都指向栈顶元素。

push 和 pop 指令执行时,CPU 从 SS 和 SP 中得到栈顶的地址。

现在,我们可以完整的描述一下 push 和 pop 过程:


说到栈了, 就不得不提栈顶越界的问题, 其实这个问题也就是缓冲区溢出了.


栈顶越界问题
现在我们知道,8086 CPU 可以使用 SS 和 SP 指示栈顶的地址,
并且提供 PUSH 和 POP 指令实现入栈和出栈,
所以,CPU现在知道了如何能够找到栈顶位置,
但是如何能保证栈顶的位置不会越界呢?栈顶越界会产生什么影响呢?
比如如下是一个栈顶越界的示意图:

一开始,SS:SP 寄存器指向了栈顶,

然后向栈空间 push 一定数量的元素后,SS:SP 位于栈空间顶部,

此时再向栈空间内部 push 元素,就会出现栈顶越界问题, 也就是栈溢出

栈溢出是非常危险的,因为我们既然将一块区域空间安排为栈,

那么在栈空间外部也可能存放了其他指令和数据,这些指令和数据有可能是其他程序的,

所以如此操作会让计算机懵逼。

我们希望 CPU 能自己解决问题,毕竟现在生产的 CPU 已经是个成熟的 CPU 了,

应该要学会自己解决问题了

然鹅,这对于 CPU 来说,这可能是它一辈子的夙愿 了,真实情况是,
CPU 并不会保证栈顶越界问题,也就是说 CPU 只会告诉你栈顶在哪,
并不会知道栈空间有多大,所以需要程序员自己手动去保证


常见的防护栈溢出的手段是:
1> Stack Protector, 也称 cannary
      原理: 当启用栈保护后,函数开始执行的时候会先从fs/gs寄存器中取出一个4字节(eax)或者8字节(rax)的cookie存到栈上
          当函数真正返回的时候会验证cookie信息是否相同,
          如果不相同就停止程序运行。
          攻击者在覆盖返回地址的时候往往也会将cookie信息给覆盖掉,
          导致栈保护检查失败而阻止shellcode的执行。
          在Linux中我们将cookie信息称为canary。

2> NX(DEP)(堆栈不可执行)

    原理: NX的基本原理是将数据所在内存页标识为不可执行,

          当程序溢出成功转入shellcode时,程序会尝试在数据页面上执行指令,

          此时CPU就会抛出异常,而不是去执行恶意指令。

          等同于Windows下的DEP。

3> PIE(ASLR)

    原理: 标准的可执行程序需要固定的地址,

          并且只有被装载到这个地址才能正确执行,

          PIE能使程序像共享库一样在主存任何位置装载,

          这需要将程序编译成位置无关,并链接为ELF共享对象。

          一般情况下NX和地址空间分布随机化(ASLR)会同时工作。

          引入PIE的原因就是让程序能装载在随机的地址,从而缓解缓冲区溢出攻击

4> RELRO

    原理: 设置符号重定向表格为只读或在程序启动时就解析并绑定所有动态符号,

         从而减少对GOT(Global Offset Table)攻击。

         RELRO为” Partial RELRO”,说明我们对GOT表具有写权限。


qixingbit.com|七星比特

相关文章

为什么32位系统最大只支持4G内存

为什么32位系统最大只支持4G内存

CPU概述上一节讲了寄存器, 这节讲讲CPU.CPU的发展史其他地方一大把, 这就不讲了,目前PC最常见的CPU是两家巨头, 英特尔(Intel)和AMD.可以说是这两家垄断了CPU市场.而从位数来说...

关闭win自动更新

关闭win自动更新

前言:我是一个码农,好不容易码完代码,准备睡觉,电脑关不掉了,那个心烦啊 于是,就有了下文操作Windows旗子 加上 R,输入:services.msc,  回车,打开...

CentOS7_docker图形化汉化管理工具部署

CentOS7_docker图形化汉化管理工具部署

1. 更改国内yum源, 并update2. 下载docker依赖    yum install -y yum-utils device-mapper-persistent-dat...

mosh和cygwin的安装与使用

mosh和cygwin的安装与使用

前言:使用ssh连接我的Linux服务器时,老是会断开,所以决定用mosh.我本地的电脑是windows10所以需要安装cygwin来支持Linux上的mosh客户端.cygwin的安装下载如下安装包...

CentOS_7更改yum为国内源

[qixing@localhost ~]$ su Password: [root@localhost qixing]# mv /etc/yum.repos.d/CentOS-Base.repo /...

从你输入网址到访问页面,浏览器-服务器做了什么

从你输入网址到访问页面,浏览器-服务器做了什么

 文章转载于igoro.com/archive/what-really-happens-when-you-navigate-to-a-url/ 作为一个学网络类的人,对于"从你输入...

发表评论

访客

看不清,换一张

◎欢迎参与讨论,请在这里发表您的看法和观点。