windows程序员进阶系列:《软件调试》之二:cpu基础

     windows程序员进阶系列:《软件调试》之二:cpu基础

 

                       by ithzhang blog.csdn.net/ithzhang 转载请注明出处!!

 

     本文将会介绍与软件调试密切相关的cpu基础。很多软件开发人员对硬件知识了解都比较少,为了帮助大家更好的入门软件调试,这里简单帮大家回顾下硬件的一些基础知识。涉及cpu的基础,如寄存器啦,指令了啥的。了解必要的硬件知识对理解软件经常会有事半功倍的效果。虽然这些内容与软件调试没有直接的关系,但是对于理解计算机系统的底层原理和进行系统级调试有着重要意义。

 

      随着科学技术的发展cpu的集成度也越来越高,但是cpu承担的任务角色却没有发生太大的变化。仍然是从内存中读取指令,然后进行解码和执行。

 

      指令是cpu唯一能看懂的语言。某一cpu所支持的指令的集合被称为指令集。根据指令集的特征,可以把cpu分为两大阵营:RISCCISC

 

      RISCReduced instruction set computer的缩写,即精简指令计算机。顾名思义,它的指令数量和格式都很少且都是等长度的。这有利于解码和寻址,但目标代码占用空间大。这有利于我们进行软件调试。

     CISCcomplex instruction set compute的缩写,即复杂指令计算机。顾名思义,它的指令数量很多且复杂,寄存器个数少于RISC处理器。

      我们经常接触的x86处理器就属于CISC处理器。

      关于RISCCISC不再详细介绍。两者差别不小,可以百度下相关资料。

 

寻址方式

      指令是由操作码和操作数构成的。寻址方式定义了如何得到操作数。下面主要介绍x86汇编语言常见的寻址方式:

 

     立即寻址:如move eax,10

     寄存器寻址:如move eax,ebx

     直接寻址:move eax,[42554122H]

     寄存器间接寻址:move eax,[ebx]

 

     大家也都或多或少接触过汇编,此处仅仅是一带而过,有个印象。更详细的信息只能去参考其他书籍了。

      IA-32处理器是对Intel设计生产的x86 32位微处理器的统称。IA32Intel architecture 32-bit的缩写。用来泛指IA-32处理器所使用的架构和共同特征。

 

cpu的工作模式

     cpu的操作模式也可以理解为cpu的工作方式。在不同的操作模式下,cpu按照不同的方式来工作。迄今为止,IA32处理器定义了一下五种工作模式:

      一:保护模式,所有IA32native模式。具有强大的虚拟内存和完善的任务保护机制。现在最经常使用的就是保护模式。

      二:实地址模式:即模拟8086处理器工作的模式。工作在此模式下的cpu相当于高速的8086处理器。它提供了一种简单的单任务环境,可以直接访问物理内存和IO空间。操作系统和应用软件处于同一优先级。

      三:虚拟8086模式:保护模式下用来执行8086任务的准模式。通过该模式可以将8086程序当作保护模式的一项任务来执行。

      四:系统管理模式:供系统固件执行电源管理、安全检查或与平台相关的特殊任务。

      五:IA-32e模式:支持Intel6464位工作模式。是IA32支持64位的一种扩展技术。

 

     处理器加电开始运行或复位后处于实地址模式。CR0控制寄存器的PE标志用来控制cpu处于实地址模式还是保护模式。标志寄存器VM用来控制cpu是在虚拟8086模式还是普通的保护模式。EFER寄存器用于启动IA32e模式。

 

寄存器

 

     寄存器是cpu内部的高速存储单元。用来存放运算过程中的操作数、操作结果、程序指针或其他信息。cpu可以直接存取寄存器的值,且速度很快。

      寄存器的数据宽度表示cpu可以直接表示的数据的范围。32位的寄存器可以表示的最大数据为2^32-1。寄存器的宽度和个数是cpu的基本指标。我们常说的32cpu64cpu就是指的寄存器的位数。

      x86cpu定义的用于程序执行的基本寄存器共有16个:包括8个通用寄存器,6个段寄存器,1个标志寄存器和一个程序指针寄存器EIP 。随着cpu功能的增加,IA32CPU逐渐加入了控制寄存器、调试寄存器、用于浮点和向量计算的向量寄存器、性能监视寄存器,以及与cpu型号有关的MSR寄存器。

 

 

通用寄存器

      通用寄存器共有8个,分别为:eax,ebx,ecx,edx,esp,ebp,esiedi 。每个寄存器的最大宽度为32位。

       虽然通用寄存器是通用的,但是有一些约定俗成的用法。如ecxesiedi在循环操作中通常分别用作计数器、源地址和目标地址。ebpesp用于维护堆栈。esp指向栈顶部,ebp指向当前栈帧的起始地址。特别注意,x86寄存器栈是从高地址向低地址生长的。

 

 

标志寄存器

      IA-32cpu有一个标志寄存器,名为eflags。标志寄存器相当于监视面板。每个标志位相当于一个指示灯或按钮。分别用来显示cpu的状态或是切换cpu的工作状态。

      eflag包括三类标志:

       用于报告算术指令的结果状态标志。如 cf,pf,af,zf,sf,of

       控制字符串指令操作方向的控制标志:df.

       供系统软件执行管理操作的系统标志。

 

MSR寄存器

      MSRmodel specific register的缩写。这些寄存器与cpu型号有关。MSR寄存器的默认大小为64位。后面会有介绍。

 

 

控制寄存器

 

      IA32CPU设计了5个控制寄存器CR0-CR4。用来决定CPU的工作模式以及当前任务的关键特征。

      CR0-CR4包含了很多与cpu工作模式关系密切的重要标志位。CR1保留不用。CR2用来记录导致页错误异常的线性地址。CR3又被称为页目录基地址寄存器,包含页目录的基地址和两个用来控制页目录缓存的标志PCDPWT。页目录的基地址的低12位,被假定为0,因此页目录所在内存一定是按照4kb边界对齐的。

 

 

其他寄存器

 

      除了上面介绍的寄存器,IA32cpu还包括:

      cs,ds,ss,es,fs,gs六个16位段寄存器。当CPU工作在实模式下时,其内容为段地址的高16位,将其左移4位便可以得到该段的基地址。在保护模式下,段寄存器下存放的是段选择子(马上会有介绍)。

      EIP32位的程序指针寄存器。指向cpu要执行的下一条指令的地址。

      8128位的向量运算寄存器XMM0-XMM7,用以支持对单精度浮点数进行计算。

      880位的FPUMMX两用寄存器ST0-ST7。当执行MMX指令时低64位,用以MMX数据寄存器。当执行x87浮点指令时,被用户浮点数据寄存器。

      132位的中断描述符表寄存器IDTR。用于记录中断描述表IDT的基地址和边界。

      132位的全局描述表寄存器GDTR。用于记录全局描述表GDT的基地址和边界。

      116位的局部描述符表寄存器LDTR。用以记录局部描述符表LDT的基地址和边界。

     116位的任务寄存器TR,用于存放选取任务状态段TSS描述符的选择子。TSS用于存放任务的状态信息。在多任务状态下,cpu从一个任务切换到另一个任务时,前一个任务的寄存器状态被保存到TSS中。

     164位的时间戳寄存器TSC。每个时钟周期其数值加一,重启时清零。RDTSC指令用以读一读去TSC寄存器。但只有在最高优先级下才可以执行该指令(CR4寄存器的TSD位为0时)。

     内存类型范围寄存器MTRR。定义了内存空间中各个区域的内存类型。cpu据此直到内存区域的特征。

     调试寄存器DR0-DR7。用于支持调试。

     性能监视寄存器,后面会有介绍。

 

保护模式

 

     保护模式是操作系统实现多任务的基础。了解保护模式的底层原理对操作系统和软件调试的学习具有事半功倍的作用。所以大家要打起精神哦!

     保护模式是为多任务而设计的,所谓的保护就是保护多任务环境中各个任务的安全。多任务环境的一个基本问题,就是当多个任务运行时,如何保证一个任务不会受到其他任务的破坏。从cpu角度看,任务就是独立调度和执行的程序单位。从Windows操作系统的角度来看,一个任务就是一个线程。

     保护模式对任务的保护机制可以分为:任务内的保护和任务间的保护。

     任务内的保护是指同一任务空间内不同级别的代码不会相互破坏。

     任务间保护指一个任务不会破坏另一个任务。

     任务间的保护是通过内存映射机制(段映射和页映射)来实现的。任务内的保护是靠特权级别检查实现的。

 

任务间的保护机制

 

     任务间的保护机制是通过内存映射机制实现。在保护模式下,每个任务都被置于一个虚拟内存空间中,Windows决定何时以及如何把这些虚拟内存映射到物理内存中。

      在win32中,每个进程都具有4GB的虚拟内存空间。不同进程可以访问不同的属于自己的虚拟内存空间,且互不干扰。

     不同的虚拟地址空间会被映射到不同的物理地址,这样就很好的防止了一个进程的代码访问另一个进程的数据。

     IA-32cpu提供两种机制来实现内存映射:段映射和页映射。

 

任务内的保护机制

 

     任务内的保护主要是保护操作系统。操作系统的核心代码会被映射到每个进程的高2GB虚拟地址空间中。因此操作系统核心代码和数据对每个进程都是可见的。任务内保护就是保护操作系统核心代码不被进程所访问。核心思想是权限控制。即为代码和数据根据其重要性指定特权级别。高特权的代码可以执行和访问低特权的代码和数据。反之,则不行。

     高特权级通常被赋给重要的数据和可信的代码,如操作系统内核。低特权的代码通常被赋给应用程序。

     应用程序只能通过API来访问操作系统提供的服务。这为系统代码和用户代码提供了一种沟通机制。

 

特权级

 

     IA32cpu定义4个特权级别。分别用0123来表示。0级最高,3级最低。但是Windows仅使用0级和3级。0级被赋给操作系统,3级被赋给应用程序。因为在0级运行的通常都是内核级代码,因此便把在0级运行成为内核模式运行,把在特权级3级运行说成是用户模式运行。

 

     cpu通过一下三种方式来实现特权控制:

     一:描述符特权级别DPL(Descriptor priviledge level)。位于段描述符或门描述符中。用于表示一个段或门的特权级别。

     二:当前特权级别(Current priviledge level)CPL。位于CSSS寄存器的位0和位1中(两位表示)。用以表示当前正在执行的程序或任务的特权级别。

     三:请求者特权级别。Requestor privilege levelRPL。用于系统调用的情况。位于段选择子的0位和1位。用来代表请求操作系统服务的应用程序的特权级别。当cpu判断是否可以访问一个段时,既要检查CPL也要检查RPL 。这样可以防止高特权的代码代替应用程 序访问应用程序本来没有权限访问的段。

 

         一般来说,CPL代表当前代码段的权限。如果它想要去访问一个段或门,首先要看看对方的权限如何,也就是检查对方的DPL。如果满足当前的权限比要访问的权限高,则有可能允许去访问,有些情况我们还要检查RPL,因为我们通过选择子:偏移量的方式去访问一个段,这算是一个访问请求动作,因此称为请求访问权限RPL(Requst Privilege Level)。当请求权限也满足条件,那么访问就被允许了。

 

        注意:在遇到一致代码段时,情况有些特殊。所谓一致代码段是指:可以被等级相同或者更低特权级的代码访问。当处理器访问一个与当前代码段CPL特权级不同的一致代码段时,CPL不会改变。

 

    以访问数据为例,当cpu要访问处于数据段的操作数时,cpu必须把指向该数据段的段选择子加载到数据段寄存器(DS,ES,FS,GS)或栈段寄存器(SS)中。在加载之前,cpu会进行特权检查。具体来说就是比较当前代码的CPLRPL和目标段的DPL。进当CPLRPL的值小于或等于DPL时,也就是说CPLRPL对应的级别大于或等于DPL时,加载才会成功。否则便抛出保护性异常。这样就保证了一段代码只能访问特权与它同级或比它低的代码。

           

特权指令

      仅能在特权级才能执行的指令被称为特权级指令。

 

段机制

     内存是计算机系统中非常重要的资源。程序必须被加载到内存中才能被cpu执行。由于在一个多任务的系统中,同时有多个任务使用内存,因此操作系统提供一套隔离机制来保证不同任务使用不同的内存,互不干扰。

      IA-32cpu采用两种内存管理机制:段机制和页机制。

      cpu的段机制提供了提供了一种手段,可以将系统空间划分为一个个较小的受保护区域。每个区域被称为一个段。每个段都有起始地址、边界和访问权限。实现段结构的重要数据结构是段描述符。

 

段描述符

     在保护模式下,每个段都有一个段描述符。这是其他代码访问该段的基本条件。每个段描述符有8个字节,用来表示一个段的位置、长度、访问控制和状态等。

     段描述符中最基本的内容是这个段的基地址和长度。基地址为4个字节。它可以是4GB地址空间的任意地址。

     段边界用20位表示。其单位由粒度位G决定,当G=0时,段边界单位是字节。当G=1,段边界单位为4KB。每个段的最大长度为2^20*4KB=4GB

下图表示段描述各个域表示意义。

 

描述符表

 

     在多任务系统中通常存在多个任务同时运行。每个任务又会包括多个段。每个段都需要一个段描述符。为了便于管理,系统用线性表来存放段描述符。IA-32CPU有三种描述符表:全局描述符表GDT(Global descriptor table)、局部描述符表(local descriptor table)和中断描述符表IDT

      GDT是全局的,一个系统只有一个GDT表,供系统所有程序和任务使用。LDT与每个进程相关。每个进程可以有一个LDT,也可以多个任务共享一个LDT

     IDT数量与cpu相关。通常会为每个cpu建立一个IDT

     GDTRIDTR寄存器分别用来用来表示GDTIDT表的位置和边界。在32位模式下,长度为48位,高32位是基地址,低16位是长度。

      LGDTSGDT指令分别用以读取和设置GDTR寄存器。

      LIDTSIDT指令分别用以读取和设置IDTR寄存器。

      操作系统会在启动时建立GDTIDT,并初始化GDTRIDTR

      GDT数组的第一个项保留不用,被称为空描述符。当把指向空描述符的段选择子加载到段寄存器时,不会产生异常。

      LDT会被当作一个特殊的段,它的段描述符会被放在GDT中。

WinDbg可以使用r命令观察GDTRIDTR寄存器。由于它们是48位的应该分两次读取。如r gdtr查看GDTR的起始地址。r gdtl查看长度。注意要在内核模式下哦。

 

段选择子


      LDTR保存当前任务的LDTGDT表的索引。

     其格式是典型的段选择子的格式。

      段选择子的T1位代表要索引的段描述符表。T1=0表示全局描述符表。T1=1表示局部描述符表。

      段描述符的高13位是描述符索引,即要选择的段描述符在T1所表示的段描述符表中的索引。由于它是13位,因此最多索引2^13个描述符。所以GDTLDT表的最大表项都为2^13

      x86cpu最多支持256个中断向量,因此IDT表的最多表项是256

      段选择子的低两位表示的是请求特权级RPL,用于特权检查。

      任务状态段寄存器TR中存放的是 一个段选择子。指向GDT中描述当前任务段TSS的段描述符。任务状态段是保存人物上下文信息的特殊段,其基本长度为104字节。TSS是实现线程切换的重要数据结构。当执行线程切换时,处理器会把当前线程现场--包括CS::EIP在内的寄存器保存在TR和指定的TSS中。然后再把指向下一线程切换的寄存器信息加载到各个寄存器,然开始执行下一线程。

除了LDTRTR,在保护模式下的所有段寄存器(CS,DS,ESFSGS)中存放的也是段选择子。

 

观察段选择寄存器

 

     可以使用调试工具WinDbg来观察寄存器的值。

 

 

      很容易看出这些段寄存器中保存的选择子都是指向全局段描述表中的段描述符。GSFS的请求特权级别RPL0,其余都为3

      可以使用WinDbgdg命令来显示一个段选择子所指向的段描述符的详细信息。dg的参数为段选择子。

      在Windows系统中FS段是存储的当前线程环境块的,即TEB结构。TEB是在内核中创建,映射到进程地址空间的。在WinDbg使用~命令可以列出线程的基本信息,可以看到当前线程的teb地址正是FS所代表段的基地址。

     观察同一个Windows系统的其他进程我们会发现,其他进程的CS,DSES,GS值与原来的线程都是相同的。这说明多个进程是共享GDT表中的段描述符的。

     段机制使保护模式下的所有线程都在系统分配给它的段空间中执行。每个线程的代码和数据地址都是相对于在所在段的段内偏移。处理器根据段选择子在段描述符找到该段的段描述符,然后再根据段描述符定位这个段。每个段具有自己的特权级别以实现对代码和数据的保护。

 

分页机制

 

     分页机制的主要目的是高效的利用内存,按页来组织和管理内存空间,把暂时不使用的页面转移到外部存储器上。

      采用分页机制后,操作系统将线性地址空间划分为固定的大小的页面。每个页面可以被映射到物理内存或是外部存储器上的页交换文件中。

      当程序需要访问某一地址时,cpu首先根据段寄存器的内容将虚拟地址转换为线性地址:首先根据段寄存器的段选择子找到段描述符,然后将段描述符存储的段的基地址加上程序中的偏移地址便得到线性地址线性地址仍然是虚拟地址,还需要经过一定的映射(下面介绍的页机制,如未使用页机制,则此线性地址就是物理地址),映射到对应的物理地址。接下来便可以访问物理地址了。如果cpu发现该线性地址所映射的地址(页表中不存在),并未在物理内存中。就会产生缺页异常。异常处理程序是操作系统的内存管理器。它会根据异常的状态信息,特别是CR2寄存器包含的线性地址,将所需的内存页加载到物理内存中。然后重新执行导致异常的指令。

     控制寄存器三个与分页机制密切相关的标志:

     CR0PG标志:用于启动分页机制。

     CR4PAE(physical address extension) 标志:启动物理地址扩展。最多可以寻址64GB的物理内存。否则为4GB内存。

     CR4PSE(Page size externsion)标志:用于启动大页面支持。 当其为1时,大页面为2MB。为0时,大页面为4MB

     cpu和内存管理器使用页目录和页表和页目录指针来管理从线性地址到物理地址的映射。

 

页目录


    页目录用以存放页目录表项(Page directory entryPDE的线性表。每个目录表项都指向一个页表。每个页目录为4KB 。每个PDE为 4字节。因此每个页目录最多包含1024PDE。当没有启动PAE时,一共有两种PDE格式。分别用于指向4KB的页表和4MB的内存页。cpu的控制寄存器CR3保存页目录地址。

     下图为指向4KB页表的PDE的格式为:

     高20位代表代表该PDE所指向页表的起始物理地址的高二十位。因为页表地址是按4KB边界对齐的。其地址低12位固定为0.

页表

     下图为指向4MB内存页的PDE格式。高10位代表是4MB内存页的起始物理地址的高10位。这是因为4MB的内存页,是按4MB的边界对齐的。

 

 

 页表

 

     页表是用来存放页表项(page table entry)PTE的线性表。每个页表为4KB,每个PTE4字节。因此每个页表项可以存储最多1024PTE

     2MB4MB的大内存页是直接映射到页目录项,不需要使用页表。

     下图为页表项的具体格式,其中高20位代表4KB内存页的起始物理地址的高20位。低12位假定为0,因此4KB的内存页都是按4KB边界对齐的。

 

页目录表指针(page directory table pointer)

 

     页目录指针表包含464位的表项,每个表项指向一个页目录,仅用于启动PAE时。

 

地址翻译

 

     有了页表和页目录的基础知识,前面也介绍了如何通过段来获得线性地址。线性地址的高10位存储的是页目录中目录项指向的页表的索引。12-21(中间10)位为页表表项的索引。低12位为页内偏移。

     为得到的下面来看下cpu如何利用这些数据结构将  32位的虚拟地址翻译为32位的虚拟地址。

     一:通过CR3寄存器定位到页目录的起始地址。正因为如此CR3寄存器又被称为页目录基地址寄存器。

     二:取得虚拟线性地址的高10位作为索引,选取页目录的一个表项PDE

     三:根据PDE中页表的基地址(PDE的高20位),定位到页表。

     四:取线性地址12位到21位作为索引选取页表的一个表项PTE

     五:取出PTE中的内存页的基地址。(高20位,低20位为0)。

     六:取线性地址的低12位作为页中偏移与第五步得到的内存页地址相加得到物理地址。

 

 

     在程序执行时CPU的内存管理单元MMU,负责将线性地址翻译成物理地址。页表和页目录位于内存中,为了减小开销cpu会把最近使用的页表和页目录存储在cpu的专用告诉缓存TLB中(Translation lookside buffer)。有了TLB大多数对页目录和页表的访问请求都可以从TLB读出,这加快了地址翻译的速度。

     前面讲的分段机制是保护模式所必需的,但是分页机制却不是。如果不启动分页机制,段机制形成的线性地址就是物理地址。

 

使用WinDbg观察分页机制来加深理解

     由于做实验的机器不支持本机内核调试,暂时还不知道原因。会在建立好实验环境之后,加上实验数据。

 

WinDbg有关的命令

 

     WinDbg提供一下几个命令来观察页目录表项和页表。

      dg:显示段选择子所指向的段描述符的信息。

      !pte:显示页目录和页表的地址。

      !vtop:将虚拟地址翻译成物理地址。

      !vpdd:显示物理地址、虚拟地址和内存的内容。

      !ptov:显示指定进程中所有物理内存到虚拟内存之间的映射。

      !sysptes:显示系统的 页目录表项。

 

      对于不经常接触硬件的软件工程师来说,以上这些东西很晦涩难懂。但是确实大多数操作系统原理都会介绍到的,虽然不会上面这么详细。多看几遍就很快会理解。

       以上内容参考自《软件调试》张银奎著。如有纰漏,请不吝指正,谢谢!!

                                                                         2013.2.1于山西大同

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值