IA32 architecture 学习笔记 (二)

指针数据类型

在非64bits模式下,体系结构定义了两种指针:

near pointer :在一个段中32bits (or 16bits) 的offset。near pointer被用来 

  1. flat memory 模式。(比如在win32保护模式下Ring3,用户看到是flag memory,DS段描述符指定的DS段基地址是0x00000000, limit是整个4G,所以near pointer可以访问整个4G空间,而不需要指定段选择子了)。
  2. 在segmented模式,要被访问的段已经被隐含指定了。(比如:指向段内数据)
far pointer:逻辑地址,由16btis的段选择子和32bits(or 16bits)的offset组成。far pointer 被用来在segmented memory模式下,当被访问的段必须被显式指明的时候。(比如访问其他段的内容)


IA32 architecture 学习笔记 (二)_第1张图片


位域数据类型

位域是一些连续的bits

IA32 architecture 学习笔记 (二)_第2张图片


Stack

Stack是一块连续的内存区域,它包含在由SS寄存器作为段选择子指向的段。在flat memory 模式下,stack可以处于程序线性地址空间的任意位置。一个stack最大可以高达4GBytes--段的最大大小。

Stack上的数据通过PUSH指令和POP指令来加入和移除,当加入数据时,处理器减少ESP寄存器的值,然后把数据卸载新的栈顶;当移除数据时,处理器从栈顶读出数据,然后增加ESP寄存器的值。通过这种方式,当栈增加数据时,它向下生长(向内存低地址);当移除数据时,栈向上生长(向内存高地址)。

一个程序或者OS可以建立很多Stack。例如,在多任务系统中,每个task都有自己的Stack。Stack的最大数量取决于段的最大数量和可用的物理内存的大小。

当系统建立了很多stack,但是在任一时刻只有当前栈(current stack)是可用的。当前栈(current stack)处于被SS寄存器指向的段中。(当前栈的概念很重要,尤其是当在不同特权级之间进行栈切换的时候,这部分以后会提到。)

处理器自动使用SS寄存器来处理所有栈操作,例如,当ESP寄存器被用来作为一个内存地址的时候,ESP自动指向了一个当前栈中的一个地址。同样,CALL、RET、PUSH、POP、Enter、Leave指令所有的操作都在当前栈上。

  • 建立一个栈

为了建立一个栈并把它设置为当前栈,程序或者OS需要:

1)建立一个stack段。

2)通过MOV、POP或者LSS指令,把stack段的段选择子加载到SS寄存器。

3)通过MOV、POP或者LSS指令,把指向栈顶的栈指针(stack pointer)加载到ESP寄存器中。LSS可以在一个操作内完成对SS和ESP的加载(加载段选择子和栈         指针)。

  • 过程的链接信息

处理器通过两个指针来把有调用关系的过程链接起来:

1)栈框指针(Stack-Frame Base Pointer)

针对过程调用(函数调用),stack分成多个frames。每个stack frame都包含参数过程的链接信息局部变量。通过栈框指针(被包含在EBP寄存器中),可以方便访问局部变量和参数。

2)返回地址(Return Instruction Pointer)

即,返回地址,当即CALL指令的一下条指令的地址。

所谓过程连接信息指的是:返回地址+保存在stack上的EBP+指向新frame的EBP,这些在汇编中非常常见,再次不再赘述。

IA32 architecture 学习笔记 (二)_第3张图片


使用CALL和RET指令来调用过程

CALL指令能够控制跳转到当前代码段(code segment)的过程(near call),也能跳转到不同代码段的过程(far call)。近跳转(near call)通常用来访问当前运行的程序或task的局部过程;而远跳转 (far call)则用来访问OS的过程或者不同task的过程。

RET指令和CALL指令的近跳转、远跳转相对应(同样是CALL指令,近跳转和远跳转的机器码是不同的),也提供和near return 和 far return。RET指令还能在return的时候,增加栈指针的值和从stack中弹出参数(也通过增加ESP来实现)。ESP的变化取决于RET的可选参数,例如__stdcall 的时候 ret n,n取决于参数一个多少Bytes,最后,ESP = ESP +4+n。

  • 近跳转和RET操作

当执行一个近跳转,处理器如下操作:

1)当前EIP寄存器的值压栈;

2)把被调用过程的offset加载到EIP寄存器中;

3)开始执行被调用函数。

当执行一个近跳转返回,处理器如下操作:

1)弹出栈上保存的返回地址到EIP寄存器中;

2)如果RET指令有可选参数n,则根据n的大小ESP进行调整:ESP-=n;

3)继续calling过程(调用者过程)的执行;

  • 远跳转和RET操作

当执行一个远跳转,处理器如下处理:

1)把当前CS寄存器的值压栈;

2)把当前EIP寄存器的值压栈;

3)把指向包含called过程(被调用过程)的段的段选择子加载到CS寄存器中;

4)把called过程的offset加载到EIP寄存器中;

5)开始执行called过程;

当执行一个远跳转返回,处理器如下操作:

1)弹出返回地址到EIP寄存器中;

2)弹出指向返回地址所在段的段选择子到CS寄存器中;

3)如果RET指令有可选参数n,增加ESP寄存器:ESP+=n;

4)继续calling过程的执行;

IA32 architecture 学习笔记 (二)_第4张图片

  • 参数传递

在过程间的参数传递可以以以下三种方式进行:

1)通过通用寄存器

处理器在过程调用时不会保存通用寄存器的状态。因此,通过把参数复制到通用寄存器,一个calling过程可以给called过程传递最多6个参数(ESP和EBP不能使用)。同样,called过程可以使用这6个来给calling过程传递返回值。6个通用寄存器是:EAX、EBX、ECX、EDX、ESI、EDI。C++中的__fastcall就是使用通用寄存器来传递参数。

2)通过参数列表

参数列表可以传递很多参数,通过把参数放到内存中数据段中的参数列表里,然后把参数列表的指针传递给called过程(通过通用寄存器或者stack)。同样返回值也可以这样处理。sprintf(...)是一个很典型的参数列表传递参数的函数。

3)通过栈

通过stack可以传递很多参数,同样stack也可以用来传递返回值。__stdcall __cdel 是典型的通过栈来传递参数的调用方式。

  • 保存过程状态信息

当过程调用时,处理器不会自动保存通用寄存器、段寄存器和EFLAGS。calling过程需要显式保存这些寄存器的值(如果这些值在调用完called过程后还有用的话),这些值被可以被保存在stack上或者任意数据段中。

PUSHA和POPA指令会利用stack来保存和恢复通用寄存器的内容。

如果called过程显式改变了任何段寄存器的状态,那么当返回到calling过程直线,called过程需要恢复这些段寄存器到以前的状态。

如果calling过程需要保存EFLAGS的状态,可以使用PUSHF/PUSHD和POPF/POPFD来在stack上保存、恢复EFLAGS寄存器。

  • Call其他特权级别

为了增强OS的稳定性,IA32体系结构的保护机制提供了4个特权级别,从0到3,其中0是最高级,3是最低级;


IA32 architecture 学习笔记 (二)_第5张图片

如上图,最高特权级别0用来包含系统中最重要的代码--一般是OS kernel,外层的用来包含次重要的代码段。

低特权级别的段(ring 3上的段)只能通过被严格保护的接口Gate来访问高特权级(ring 2,1,0)上的模块。如果不通过被保护的Gate和没有足够的权限,来么任何对搞特权级上段的访问都会导致GP(general-protection exception)的发生。

应用了多级保护机制(ring 0~3)的OS,对高特权级别上的过程的调用和far call相似:

1)CALL指令中提供的段选择子指向Call gate descriptor,call gate descriptor(调用门描述符)提供如下功能:

- 访问权限信息;

- 指向被调用过程所在代码段的段选择子;

- 被调用过程在其所在代码段中的offset;

2)处理器切换到新的stack来执行被调用过程。每个特权级别有自己的stack,ring 3的stack段选择子和栈指针被保存到SS和ESP寄存器中。当ring3过程调用高特权级上的过程的时候,ring3 的SS和ESP被自动保存。ring 0~2特权级的stack段的段选择子和ESP被保存在一个叫做

       task state segment(TSS 任务状态段)的系统段中。

  • 特权级别之间的CALL和RET操作

当调用一个高特权级上的过程时,处理器如下处理:

1)进行访问权限检查(privilege check);

2) 自动把当前SS、ESP和CS、EIP寄存器的内容临时保存起来;

3)从TSS(任务状态段)中新stack的段选择子和栈顶指针到SS和ESP寄存器中,切换到新的stack;

4)把2)中临时保存的calling过程的SS和ESP压到新的stack中,保存起来;

5)从calling过程的stack中复制参数到新的stack中,调用门描述符(call gate descriptor)中的一个值决定有多少参数要复制到新stack中;

6)把2)中临时保存的calling过程的CS和EIP压到新的stack中,保存起来;

7)把call gate中的指向新的代码段的段选择子和新的指令指针加载到CS和EIP寄存器中;

8)在新的(更高)特权级上,开始执行被调用过程。

当一个特权级上的过程执行返回操作时,处理器如下处理:

1)进行privilege check;

2)把CS和EIP恢复成调用前的值;

3)如果RET指令有可选参数n(意味着参数一共多少bytes),那么根据可选参数n来增加栈顶指针的值。可选参数意味着call gate descriptor 指定了一个或者多个参数,这些参数被从一个stack上复制到了现在的stack上,那么这两个stack上的参数必须都要释放(通过调整ESP)。不同特权级的过

      程调用要涉及到不同stack上的参数复制;

4)恢复SS和ESP到调用前的值,这回导致stack被切换回calling过程的stack;

5)根据3)如果RET指令有可选参数n,那么要调整ESP(calling 过程的ESP);

6)继续caling过程的运行;



IA32 architecture 学习笔记 (二)_第6张图片



你可能感兴趣的:(体系结构)