After reading 45 registers in one breath, the core technology of CPU is revealed

Preface

Some time ago, I wrote more than ten articles about the low-level CPU technical stories. Many readers sent me private messages and asked me to write about the CPU registers.

There are too many registers and too complicated to write a story. After a long time, I finally finished writing. This article will talk about the complicated registers in the x86/x64 architecture CPU in detail .

Long text warning, fast speed, please fasten your seat belt ~ take off~

Since the birth of the world's first general-purpose electronic computer ENIAC under the leadership of von Neumann in 1946 , computer technology has been developed for more than 70 years.

From the behemoth dedicated to mathematical computing at the beginning, to the later era of mainframe servers, from the booming development of personal microcomputer technology, to the Internet wave sweeping the world, to the mobile Internet and cloud computing nowadays, computers have changed in various forms and are everywhere. .

In the past seventy years, countless programming languages ​​have appeared, and countless applications have been developed through these programming languages.

ee1b11953ccfc4e64fd9136c7cb45c61.png

But no matter what kind of application, what kind of programming language, the final program logic is delivered to the CPU for execution and implementation (of course, there are some impreciseness here, in addition to the CPU, there are coprocessors, GPUs, etc.). Therefore, understanding and learning the principles of CPU is of great benefit to the consolidation of basic computer knowledge.

In the long course of more than 70 years, many CPUs of architecture have also emerged.

  • MIPS
  • PowerPC
  • x86/x64
  • IA64
  • ARM
  • ······

This article aims at the x86-x64 architecture, which is the most widely used in the market, and explains the underlying working principle of the CPU in series by learning to understand the functions of its internal 100 registers.

Through this article, you will learn:

  • CPU instruction execution principle
  • Memory addressing technology
  • Principles of Software Debugging Technology
  • Interrupt and exception handling
  • System call
  • CPU multitasking technology

What is a register?

Registers are some small storage areas used to store data inside the CPU. They are used to temporarily store data involved in calculations and calculation results, as well as some information needed for CPU operation.

The x86 architecture CPU takes the complex instruction set (CISC)  route, providing a wealth of instructions to achieve powerful functions, and at the same time, it also provides a large number of registers to assist in the implementation of functions. This article will cover the following registers:

  • General register
  • Flag register
  • Instruction register
  • Segment register
  • Control register
  • Debug register
  • Descriptor register
  • Task register
  • MSR register

General register

The first to bear the brunt is the general-purpose registers. These registers are the most commonly used and most basic registers for program execution code. During program execution, most of the time is to operate these registers to implement instruction functions.

The so-called general purpose means that these registers have no special purpose for the CPU, and are left to the application program to use at will. Note, this is arbitrary, I put quotation marks, for some registers, the CPU has some hidden rules, you should pay attention when using it.

  • eax : It is usually used to perform addition, and the return value of function call is generally placed here
  • ebx : data access
  • ecx : usually used as a counter, such as a for loop
  • edx : When reading and writing I/O ports, edx is used to store the port number
  • esp : pointer to the top of the stack, pointing to the top of the stack
  • ebp : pointer to the bottom of the stack, pointing to the bottom of the stack, usually used ebp+偏移量to locate the local variables stored in the stack in the function
  • esi : The address used to store the data source during string operations
  • edi : used to store the destination address during string operations. It is often used in conjunction with esi to perform operations such as string copying

In the x64 architecture, the above general-purpose registers have been expanded to 64-bit versions, and the names have also been upgraded. Of course, in order to be compatible with 32-bit mode programs, the above names can still be accessed, which is equivalent to accessing the lower 32 bits of a 64-bit register.

rax rbx rcx rdx rsp rbp rsi rdi

In addition to expanding the existing general-purpose registers, the x64 architecture also introduces 8 new general-purpose registers:

r8-r15

在原来32位时代,函数调用时,那个时候通用寄存器少,参数绝大多数时候是通过线程的栈来进行传递(当然也有使用寄存器传递的,比如著名的C++ this指针使用ecx寄存器传递,不过能用的寄存器毕竟不多)。

进入x64时代,寄存器资源富裕了,参数传递绝大多数都是用寄存器来传了。寄存器传参的好处是速度快,减少了对内存的读写次数。

当然,具体使用栈还是用寄存器传参数,这个不是编程语言决定的,而是编译器在编译生成CPU指令时决定的,如果编译器非要在x64架构CPU上使用线程栈来传参那也不是不行,这个对高级语言是无感知的。

标志寄存器

标志寄存器,里面有众多标记位,记录了CPU执行指令过程中的一系列状态,这些标志大都由CPU自动设置和修改:

  • CF 进位标志
  • PF 奇偶标志
  • ZF 零标志
  • SF 符号标志
  • OF 补码溢出标志
  • TF 跟踪标志
  • IF 中断标志
  • ······

91896d9b31ca5d7c3e76a295ce4c0ee0.png

在x64架构下,原来的eflags寄存器升级为64位的rflags,不过其高32位并没有新增什么功能,保留为将来使用。

指令寄存器

eip: 指令寄存器可以说是CPU中最最重要的寄存器了,它指向了下一条要执行的指令所存放的地址,CPU的工作其实就是不断取出它指向的指令,然后执行这条指令,同时指令寄存器继续指向下面一条指令,如此不断重复,这就是CPU工作的基本日常。

而在漏洞***中,***想尽办法费尽心机都想要修改指令寄存器的地址,从而能够执行恶意代码。

同样的,在x64架构下,32位的eip升级为64位的rip寄存器。

段寄存器

段寄存器与CPU的内存寻址技术紧密相关。

早在16位的8086CPU时代,内存资源宝贵,CPU使用分段式内存寻址技术:

8c7aaffe569e67ca7ed0d1e823b2ddec.png

16位的寄存器能寻址的范围是64KB,通过引入段的概念,将内存空间划分为不同的区域:分段,通过段基址+段内偏移段方式来寻址。

这样一来,段的基地址保存在哪里呢?8086CPU专门设置了几个段寄存器用来保存段的基地址,这就是段寄存器段的由来。

段寄存器也是16位的。

段寄存器有下面6个,前面4个是早期16位模式就引入了,到了32位时代,又新增了fs和gs两个段寄存器。

  • cs: 代码段
  • ds: 数据段
  • ss: 栈段
  • es: 扩展段
  • fs: 数据段
  • gs: 数据段

段寄存器里面存储的内容与CPU当前工作的内存寻址模式紧密相关。

当CPU处于16位实地址模式下时,段寄存器存储段的基地址,寻址时,将段寄存器内容左移4位(乘以16)得到段基地址+段内偏移得到最终的地址。

当CPU工作于保护模式下,段寄存器存储的内容不再是段基址了,此时的段寄存器中存放的是段选择子,用来指示当前这个段寄存器“指向”的是哪个分段。

注意我这里的指向打了引号,段寄存器中存储的并不是内存段的直接地址,而是段选择子,它的结构如下:

8fb59781f4c8cc9500cf98fc7569f927.png

16个bit长度的段寄存器内容划分了三个字段:

  • PRL: 特权请求级,就是我们常说的ring0-ring3四个特权级。
  • TI: 0表示用的是全局描述符表GDT,1表示使用的是局部描述符表LDT。
  • Index: 这是一个表格中表项的索引值,这个表格叫内存描述符表,它的每一个表项都描述了一个内存分段。

这里提到了两个表,全局描述符表GDT和局部描述符表LDT,关于这两个表的介绍,下面介绍描述符寄存器时再详述,这里只需要知道,这是CPU支持分段式内存管理需要的表格,放在内存中,表格中的每一项都是一个描述符,记录了一个内存分段的信息。

保护模式下的段寄存器和段描述符到最后的内存分段,通过下图的方式联系在一起:

8740ca2198d72646693224e07354b082.png


通用寄存器、段寄存器、标志寄存器、指令寄存器,这四组寄存器共同构成了一个基本的指令执行环境,一个线程的上下文也基本上就是这些寄存器,在执行线程切换的时候,就是修改它们的内容。

b1ec3c1e4a6dae1369b0e24deb3958d1.png

控制寄存器

控制寄存器是CPU中一组相当重要的寄存器,我们知道eflags寄存器记录了当前运行线程的一系列关键信息。

那CPU运行过程中自身的一些关键信息保存在哪里呢?答案是控制寄存器!

012092fe2fe2736f47274adf5a88143b.png

32位CPU总共有cr0-cr4共5个控制寄存器,64位增加了cr8。他们各自有不同的功能,但都存储了CPU工作时的重要信息:

  • cr0: 存储了CPU控制标记和工作状态
  • cr1: 保留未使用
  • cr2: 页错误出现时保存导致出错的地址
  • cr3: 存储了当前进程的虚拟地址空间的重要信息——页目录地址
  • cr4: 也存储了CPU工作相关以及当前人任务的一些信息
  • cr8: 64位新增扩展使用

其中,CR0尤其重要,它包含了太多重要的CPU信息,值得单独关注一下:

5ef1d149d2d12d27e386db25e3419322.png

一些重要的标记位含义如下:

PG: 是否启用内存分页

AM: 是否启用内存对齐自动检查

WP: 是否开启内存写保护,若开启,对只读页面尝试写入时将触发异常,这一机制常常被用来实现写时复制功能

PE: 是否开启保护模式

除了CR0,另一个值得关注的寄存器是CR3,它保存了当前进程所使用的虚拟地址空间的页目录地址,可以说是整个虚拟地址翻译中的顶级指挥棒,在进程空间切换的时候,CR3也将同步切换。

调试寄存器

在x86/x64CPU内部,还有一组用于支持软件调试的寄存器。

调试,对于我们程序员是家常便饭,必备技能。但你想过你的程序能够被调试背后的原理吗?

程序能够被调试,关键在于能够被中断执行和恢复执行,被中断的地方就是我们设置的断点。那程序是如何能在遇到断点的时候停下来呢?

806d4106c2be4fa4bd5bae74553c87b8.png

对于一些解释执行(PHP、Python、JavaScript)或虚拟机执行(Java)的高级语言,这很容易办到,因为它们的执行都在解释器/虚拟机的掌控之中。

而对于像C、C++这样的“底层”编程语言,程序代码是直接编译成CPU的机器指令来执行的,这就需要CPU来提供对于调试的支持了。

对于通常的断点,也就是程序执行到某个位置下就停下来,这种断点实现的方式,在x86/x64上,是利用了一条软中断指令:int 3来进行实现的。

注意,这里的int不是指高级语言里面的整数,而是表示interrupt中断的意思,是一条汇编指令,int 3则表示中断向量号为3的中断。

在我们使用调试器下断点时,调试器将会把对应位置的原来的指令替换为一个int 3指令,机器码为0xCC。这个动作对我们是透明的,我们在调试器中看到的依然是原来的指令,但实际上内存中已经不是原来的指令了。

顺便提一句,两个0xCC是汉字【烫】的编码,在一些编译器里,会给线程的栈中填充大量的0xCC,如果程序出错的时候,我们经常会看到很多烫烫烫出现,就是这个原因。

766cc370ac3ed24ab7cf965c0e09cc86.png

言归正传,CPU在执行这条int 3指令时,将自动触发中断处理流程(虽然这实际上不是一个真正的中断),CPU将取出IDTR寄存器指向的中断描述符表IDT的第3项,执行里面的中断处理函数。

而这个中断描述符表,早在操作系统启动之初,就已经提前安排好了,所以执行这条指令后,操作系统的中断处理函数将介入,来处理这一事件。

后面的过程就多了,简单来说,操作系统会把触发这一事件的进程冻结起来,随后将这一事件发送到调试器,调试器拿到之后就知道目标进程触发断点了。这个时候,咱们程序员就能通过调试器的UI交互界面或者命令行调试接口来调试目标进程,查看堆栈、查看内存、变量都随你。

如果我们要继续运行,调试器将会把之前修改的int 3指令给恢复回去,然后告知操作系统:我处理完了,把目标进程解冻吧!

上面简单描述了一下普通断点的实现原理。现在思考一个场景:我们发现一个bug,某个全局整数型变量的值老是莫名其妙被修改,但你发现有很多线程,很多函数都有可能会去修改这个变量,你想找出到底谁干的,怎么办?

这个时候上面的普通断点就没办法了,你需要一种新的断点:硬件断点

这时候就该本小节的主人公调试寄存器登场表演了。

7ea1497a6449b0fc341356513d449ce0.png

在x86架构CPU内部,提供了8个调试寄存器DR0~DR7。

DR0~DR3:这是四个用于存储地址的寄存器

DR4~DR5:这两个有点特殊,受前面提到的CR4寄存器中的标志位DE位控制,如果CR4的DE位是1,则DR4、DR5是不可访问的,访问将触发异常。如果CR4的DE位是0,则DR4和DR5将会变成DR6和DR7的别名,相当于做了一个软链接。这样做是为了将DR4、DR5保留,以便将来扩展调试功能时使用。

DR6:这个寄存器中存储了硬件断点触发后的一些状态信息

DR7:调试控制寄存器,这里面记录了对DR0-DR3这四个寄存器中存储地址的中断方式(是对地址的读,还是写,还是执行)、数据长度(1/2/4个字节)以及作用范围等信息

通过调试器的接口设置硬件断点后,CPU在执行代码的过程中,如果满足条件,将自动中断下来。

回答前面提出的问题,想要找出是谁偷偷修改了全局整形变量,只需要通过调试器设置一个硬件写入断点即可。

描述符寄存器

所谓描述符,其实就是一个数据结构,用来记录一些信息,‘描述’一个东西。把很多个描述符排列在一起,组成一个表,就成了描述符表。再使用一个寄存器来指向这个表,这个寄存器就是描述符寄存器

在x86/x64系列CPU中,有三个非常重要的描述符寄存器,它们分别存储了三个地址,指向了三个非常重要的描述符表。

gdtr: 全局描述符表寄存器,前面提到,CPU现在使用的是段+分页结合的内存管理方式,那系统总共有那些分段呢?这就存储在一个叫全局描述符表(GDT)的表格中,并用gdtr寄存器指向这个表。这个表中的每一项都描述了一个内存段的信息。

ldtr: 局部描述符表寄存器,这个寄存器和上面的gdtr一样,同样指向的是一个段描述符表(LDT)。不同的是,GDT是全局唯一,LDT是局部使用的,可以创建多个,随着任务段切换而切换(下文介绍任务寄存器会提到)。

6f75bf6ff4b73135fad1fca2a2d91818.png

GDT和LDT中的表项,就是段描述符,描述了一个内存分段的信息,其结构如下:

7deb05a565aa9e720f5f82ec39cd88a9.png

一个表项占据8个字节(32位CPU),里面存储了一个内存分段的诸多信息:基地址、大小、权限、类型等信息。

除了这两个段描述符寄存器,还有一个非常重要的描述符寄存器:

idtr: 中断描述符表寄存器,指向了中断描述符表IDT,这个表的每一项都是一个中断处理描述符,当CPU执行过程中发生了硬中断、异常、软中断时,将自动从这个表中定位对应的表项,里面记录了发生中断、异常时该去哪里执行处理函数。

67ccae40cfada78111718e9b7246ebfb.png

IDT中的表项称为Gate,中文意思为,因为这是应用程序进入内核的主要入口。虽然表的名字叫中断描述符表,但表中存储的不全是中断描述符,IDT中的表项存在三种类型,对应三种类型的门:

  • 任务门
  • 陷阱门
  • 中断门

66db59a395e290c9947dffc6998ac919.png

三种描述符中都存储了处理这个中断/异常/任务时该去哪里处理的地址。三种门用途不一,其中中断门是真正意义上的中断,而像前面提到的调试指令int 3以及老式的系统调用指令int 2e/int 80都属于陷阱门。任务门则用的较少,要了解任务门,先了解下任务寄存器。

任务寄存器

现代操作系统,都是支持多任务并发运行的,x86架构CPU为了顺应时代潮流,在硬件层面上提供了专门的机制用来支持多任务的切换,这体现在两个方面:

  • CPU内部设置了一个专用的寄存器——任务寄存器TR,它指向当前运行的任务。
4eacbeac2786674a3228b21473eb4e8e.png
  • 定义了描述任务的数据结构TSS,里面存储了一个任务的上下文(一系列寄存器的值),下图是一个32位CPU的TSS结构图:482600e7dfeb1b6930b92303154781ee.png

x86CPU的构想是每一个任务对应一个TSS,然后由TR寄存器指向当前的任务,执行任务切换时,修改TR寄存器的指向即可,这是硬件层面的多任务切换机制。

这个构想其实还是很不错的,然而现实却打了脸,包括Linux和Windows在内的主流操作系统都没有使用这个机制来进行线程切换,而是自己使用软件来实现多线程切换。

所以,绝大多数情况下,TR寄存器都是指向固定的,即便线程切换了,TR寄存器仍然不会变化。

注意,我这里说的的是绝大多数情况,而没有说死。虽然操作系统不依靠TSS来实现多任务切换,但这并不意味着CPU提供的TSS操作系统一点也没有使用。还是存在一些特殊情况,如一些异常处理会使用到TSS来执行处理。

下面这张图,展示了控制寄存器、描述符寄存器、任务寄存器构成的全貌:

28dc57d7ce5a0d92b4ebb652df3e4a1c.png

模型特定寄存器

从80486之后的x86架构CPU,内部增加了一组新的寄存器,统称为MSR寄存器,中文直译是模型特定寄存器,意思是这些寄存器不像上面列出的寄存器是固定的,这些寄存器可能随着不同的版本有所变化。这些寄存器主要用来支持一些新的功能。

随着x86CPU不断更新换代,MSR寄存器变的越来越多,但与此同时,有一部分MSR寄存器随着版本迭代,慢慢固化下来,成为了变化中那部分不变的,这部分MSR寄存器,Intel将其称为Architected MSR,这部分MSR寄存器,在命名上,统一加上了IA32的前缀。

这里选取三个代表性的MSR简单介绍一下:

  • IA32_SYSENTER_CS
  • IA32_SYSENTER_ESP
  • IA32_SYSENTER_EIP

这三个MSR寄存器是用来实现快速系统调用

在早期的x86架构CPU上,系统调用依赖于软中断实现,类似于前面调试用到的int 3指令,在Windows上,系统调用用到的是int 2e,在Linux上,用的是int 80

软中断毕竟还是比较慢的,因为执行软中断就需要内存查表,通过IDTR定位到IDT,再取出函数进行执行。

The system call is a frequently triggered action, so this is bound to have an impact on performance. After entering the Pentium era, the above three MSR registers were added to store the segment registers, stack top, and function addresses required by the kernel system to call the entry function after the system call was executed. There is no need for memory lookup tables. The fast system call also provides special CPU instructions sysenter/sysexit to initiate system calls and exit system calls.

On 64-bit, this pair of instructions is upgraded to syscall/sysret .

to sum up

The above is all the registers to be introduced. What needs to be explained is that this is not all the registers of the x86CPU. In addition to these, there are other registers such as XMM, MMX, FPU floating point operations.

This article takes the x86/x64 architecture CPU as the target, and through the description of the internal registers of the CPU, it talks about the CPU execution code mechanism, memory addressing technology, interrupt and exception handling, multitasking management, system calls, debugging principles and other computers. Low-level knowledge.

Article writing is not easy, welcome everyone to forward and support~


Guess you like

Origin blog.51cto.com/14886635/2550023