花了三天的时间,学习了王爽的《汇编语言》和清华大学的公开课,还有网上的资料。笔记整理:
汇编语言实际上就是机器码的助记符,不同类型的CPU之间没有可移植性。直接接触底层硬件包括内存地址和寄存器。汇编程序操作的就是数据、内存地址、寄存器和栈。书上和网上介绍的一般是16位8086CPU、X86-32和X86-64的CPU。王爽的《汇编语言》中介绍的是8086的16位CPU,而公开课中讲的却是32位和64位CPU。
CPU通过数据、地址和控制总线与外界相连并进行数据、地址和控制信号的传送。从CPU的角度看内存地址空间是逻辑地址空间,是连续的,也就是说物理空间实际不连续:
在8086PC机中,存储单元的地址用两个元素来描述,即段地址和偏移地址段地址*16 + 偏移地址 = 物理地址一个段的起始地址一定是16的倍数,一个段的长度最大为64KB。这种方式可以寻址的最高地址为0xFFFF:0xFFFF,其地址空间为0x00000~0x10FFEF,因为8086的地址总线是20位,最大只能访问到1MB的物理地址空间,即物理地址空间是0x00000~0xFFFFF。当程序访问0x100000~0x10FFEF这一段地址时,因为其逻辑上是正常的,CPU并不会认为其访问越界而产生异常,但这段地址确实没有实际的物理地址与其对应。此时CPU采取的策略是,对于这部分超出1M地址空间的部分,自动将其从物理0地址处开始映射。也就是说,系统计算实际物理地址时是按照对1M求模运算的方式进行的,这种技术被称之为wrap-around:
每个段的最小长度是16字节,而最大长度只能是64KB。但是同一物理地址可以多种表示,地址空间缺乏保护机制。对于每一个由段寄存器的内容确定的“基地址”,一个进程总是能够访问从此开始64KB的连续地址空间,而无法加以限制。另一方面,可以用来改变段寄存器内容的指令不是“特权指令”,通过改变段寄存器的内容,一个进程可以随心所欲地访问内存中的任何一个单元,而丝毫不受限制。这种模式就是实模式,得到的实在的地址。是CPU工作的一种模式。
在80286的CPU里,首次引入的地址保护的概念。也就是说80286的CPU能够对内存及一些其他外围设备做硬件级的保护设置(实质上就是屏蔽一些地址的访问)。Intel决定在80386的段寄存器(CS,DS,SS,ES)的基础上构筑保护模式,并且继续保留段寄存器为16位,同时又增添了两个段寄存器FS和GS。为了实现保护模式,光是用段寄存器来确定一个基地址是不够的,至少还要有一个地址段的长度,并且还需要一些诸如访问权限之类的其他信息,此时需要“段描述符”的数据结构来抽象进而管理。
在保护模式下改变段寄存器的功能,使其从一个单纯的段基址变成指向一个“段描述符”的指针。因此,当一个访存指令发出一个内存地址时, CPU按照下面过程实现从指令中的32位逻辑地址到32位线性地址,再到物理地址的转换:
1、首先根据指令的性质来确定该使用哪一个段寄存器,例如操作指令中的地址在代码段CS里,而数据指令中的地址在数据段DS里。这一点与实地址模式相同。
2、根据段寄存器里的内容,找到相应的“段描述符”结构。
3、然后,从“段描述符”里得到的才是段基址。
4、将指令中的地址作为偏移量,然后和段描述符结构中规定的段长度进行比较,看齐是否越界。
5、根据指令的性质和段描述符中的访问权限来确定当前指令操作是否越权。
6、最后才将指令中的地址作为偏移量,存储单元的物理地址就是该段地址加上段内偏移量,与段基址相加得到线性地址,或者叫虚拟地址。
7、最后根据线性地址算出实际的物理地址。此时的由虚拟地址计算到实际物理地址即虚拟内存管理,一般以段或者页进行管理。
段描述符保存在端描述符表中分为GDT全局端描述符表和LDT局部端描述符表。其存储在内存中,GDT的起始地址存放在GDTR寄存器中由OS 设置。
CS、IP是8086CPU中两个最关键的寄存器,它们指示了CPU当前要读取指令的地址。CS为代码段寄存器,IP为指令指针寄存器。从CS:IP指向的内存单元读取指令,读取的指令进入指令缓冲器IP = IP + 送读取指令的长度,从而指向下一条指令。
ax、bx、cx、dx 可以用mov指令来改变,mov指令被称为传送指令,但是mov指令不能用于设置cs、ip的值。寄存器ax和al通常称为累加器(Accumulator),用累加器进行的操作可能需要更少时间。累加器可用于乘、除、输入/输出等操作,它们的使用频率很高;寄存器bx称为基地址寄存器(Base Register)。它可作为存储器指针来使用;寄存器cx称为计数寄存器(Count Register)。在循环和字符串操作时,要用它来控制循环次数;在位操作中,当移多位时,要用cl来指明移位的位数;寄存器dx称为数据寄存器(Data Register)。在进行乘、除运算时,它可作为默认的操作数参与运算,也可用于存放I/O的端口地址。32位指令前面加E,64位前面加R。
寄存器ESI、EDI、SI和DI称为变址寄存器(IndexRegister),它们主要用于存放存储单元在段内的偏移量,用它们可实现多种存储器操作数的寻址方式。寄存器EBP、ESP、BP和SP称为指针寄存器(PointerRegister),主要用于存放堆栈内存储单元的偏移量,用它们可实现多种存储器操作数的寻址方式。不可分割成8位寄存器。作为通用寄存器,也可存储算术逻辑运算的操作数和运算结果。
段寄存器是根据内存分段的管理模式而设置的。内存单元的物理地址由段寄存器的值和一个偏移量组合而成的,这样可用两个较少位数的值组合成一个可访问较大物理空间的内存地址。CS——代码段寄存器(Code Segment Register),其值为代码段的段值;DS——数据段寄存器(Data Segment Register),其值为数据段的段值;ES——附加段寄存器(Extra Segment Register),其值为附加数据段的段值;SS——堆栈段寄存器(Stack Segment Register),其值为堆栈段的段值;FS——附加段寄存器(Extra Segment Register),其值为附加数据段的段值;GS——附加段寄存器(Extra Segment Register),其值为附加数据段的段值。16位CPU中只有前面4个段寄存器,32位有6个。段寄存器CS指向存放程序的内存段,IP是用来存放下条待执行的指令在该段的偏移量,把它们合在一起可在该内存段内取到下次要执行的指令。段寄存器SS指向用于堆栈的内存段,SP是用来指向该堆栈的栈顶,把它们合在一起可访问栈顶单元。另外,当偏移量用到了指针寄存器BP,则其缺省的段寄存器也是SS,并且用BP可访问整个堆栈,不仅仅是只访问栈顶。段寄存器DS指向数据段,ES指向附加段,在存取操作数时,二者之一和一个偏移量合并就可得到存储单元的物理地址。该偏移量可以是具体数值、符号地址和指针寄存器的值等之一。
访问存储器方式 | 缺省的段寄存器 | 可选用的段寄存器 | 偏移量 | |
取指令 | CS | IP | ||
堆栈操作 | SS | SP | ||
一般取操作数 | DS | CS、ES、SS | 有效地址 | |
串操作 | 源操作数 |
DS | CS、ES、SS | SI |
目标操作数 |
ES | DI | ||
使用指针寄存器BP | SS | CS、DS、ES | 有效地址 |
指令指针EIP、IP(Instruction Pointer)是存放下次将要执行的指令在代码段的偏移量。在具有预取指令功能的系统中,下次要执行的指令通常已被预取到指令队列中,除非发生转移情况。
程序状态字寄存器PSW,共9个标识位,分为二组:运算结果标志位和状态控制标志位。前者受算术运算和逻辑运算结果的影响,后者受一些控制指令执行的影响。有些指令的执行会改变标志位(如:算术运算指令等),不同的指令会影响不同的标志位,有些指令的执行不改变任何标志位(如:MOV指令等),有些指令的执行会受标志位的影响(如:条件转移指令等),也有指令的执行不受其影响。各个标志位的意义:
1、进位标志CF(Carry Flag)
进位标志CF主要用来反映运算是否产生进位或借位。如果运算结果的最高位产生了一个进位或借位,那么,其值为1,否则其值为0。
使用该标志位的情况有:多字(字节)数的加减运算,无符号数的大小比较运算,移位操作,字(字节)之间移位,专门改变CF值的指令等。
2、奇偶标志PF(Parity Flag)
奇偶标志PF用于反映运算结果中“1”的个数的奇偶性。如果“1”的个数为偶数,则PF的值为1,否则其值为0。
利用PF可进行奇偶校验检查,或产生奇偶校验位。在数据传送过程中,为了提供传送的可靠性,如果采用奇偶校验的方法,就可使用该标志位。
3、辅助进位标志AF(Auxiliary Carry Flag)
在发生下列情况时,辅助进位标志AF的值被置为1,否则其值为0:
(1)、在字操作时,发生低字节向高字节进位或借位时;
(2)、在字节操作时,发生低4位向高4位进位或借位时。
对以上6个运算结果标志位,在一般编程情况下,标志位CF、ZF、SF和OF的使用频率较高,而标志位PF和AF的使用频率较低。
4、零标志ZF(Zero Flag)
零标志ZF用来反映运算结果是否为0。如果运算结果为0,则其值为1,否则其值为0。在判断运算结果是否为0时,可使用此标志位。
5、符号标志SF(Sign Flag)
符号标志SF用来反映运算结果的符号位,它与运算结果的最高位相同。在微机系统中,有符号数采用补码表示法,所以,SF也就反映运算结果的正负号。运算结果为正数时,SF的值为0,否则其值为1。
6、溢出标志OF(Overflow Flag)
溢出标志OF用于反映有符号数加减运算所得结果是否溢出。如果运算结果超过当前运算位数所能表示的范围,则称为溢出,OF的值被置为1,否则,OF的值被清为0。
“溢出”和“进位”是两个不同含义的概念,不要混淆。如果不太清楚的话,请查阅《计算机组成原理》课程中的有关章节。
二、状态控制标志位
状态控制标志位是用来控制CPU操作的,它们要通过专门的指令才能使之发生改变。
1、追踪标志TF(Trap Flag)
当追踪标志TF被置为1时,CPU进入单步执行方式,即每执行一条指令,产生一个单步中断请求。这种方式主要用于程序的调试。
指令系统中没有专门的指令来改变标志位TF的值,但程序员可用其它办法来改变其值。
2、中断允许标志IF(Interrupt-enableFlag)
中断允许标志IF是用来决定CPU是否响应CPU外部的可屏蔽中断发出的中断请求。但不管该标志为何值,CPU都必须响应CPU外部的不可屏蔽中断所发出的中断请求,以及CPU内部产生的中断请求。具体规定如下:
(1)、当IF=1时,CPU可以响应CPU外部的可屏蔽中断发出的中断请求;
(2)、当IF=0时,CPU不响应CPU外部的可屏蔽中断发出的中断请求。
CPU的指令系统中也有专门的指令来改变标志位IF的值。
3、方向标志DF(DirectionFlag)
方向标志DF用来决定在串操作指令执行时有关指针寄存器发生调整的方向。具体规定在第5.2.11节——字符串操作指令——中给出。在微机的指令系统中,还提供了专门的指令来改变标志位DF的值。
三、32位标志寄存器增加的标志位
1、I/O特权标志IOPL(I/O Privilege Level)
I/O特权标志用两位二进制位来表示,也称为I/O特权级字段。该字段指定了要求执行I/O指令的特权级。如果当前的特权级别在数值上小于等于IOPL的值,那么,该I/O指令可执行,否则将发生一个保护异常。
2、嵌套任务标志NT(Nested Task)
嵌套任务标志NT用来控制中断返回指令IRET的执行。具体规定如下:
(1)、当NT=0,用堆栈中保存的值恢复EFLAGS、CS和EIP,执行常规的中断返回操作;
(2)、当NT=1,通过任务转换实现中断返回。
3、重启动标志RF(Restart Flag)
重启动标志RF用来控制是否接受调试故障。规定:RF=0时,表示“接受”调试故障,否则拒绝之。在成功执行完一条指令后,处理机把RF置为0,当接受到一个非调试故障时,处理机就把它置为1。
在指令中,指定操作数或操作数存放位置的方法称为寻址方式。微机系统有七种基本的寻址方式:立即寻址方式、寄存器寻址方式、直接寻址方式、寄存器间接寻址方式、寄存器相对寻址方式、基址加变址寻址方式、相对基址加变址寻址方式等。
操作数作为指令的一部分而直接写在指令中,这种操作数称为立即数,这种寻址方式也就称为立即数寻址方式。立即数不能作为指令中的第二操作数。MOV AX, 1234H
指令所要的操作数已存储在某寄存器中,或把目标操作数存入寄存器。把在指令中指出所使用寄存器(即:寄存器的助忆符)的寻址方式称为寄存器寻址方式。由于使用寄存器寻址方式可以加快速度所以提倡使用寄存器寻址方式。
指令所要的操作数存放在内存中,在指令中直接给出该操作数的有效地址,这种寻址方式为直接寻址方式。MOV BX, [1234H],此时默认基址存在DS中。MOV ES:[1000H], AX此为明显给出基址寄存器。
操作数在存储器中,操作数的有效地址用SI、DI、BX和BP等四个寄存器之一来指定,称这种寻址方式为寄存器间接寻址方式。该寻址方式物理地址的计算方法如下:
若有效地址用SI、DI和BX等之一来指定,则其缺省的段寄存器为DS;若有效地址用BP来指定,则其缺省的段寄存器为SS(即:堆栈段)。
操作数在存储器中,其有效地址是一个基址寄存器(BX、BP)或变址寄存器(SI、DI)的内容和指令中的8位/16位偏移量之和,成为寄存器相对寻址方式。其有效地址的计算公式如右式所示:
若有效地址用SI、DI和BX等之一来指定,则其缺省的段寄存器为DS;若有效地址用BP来指定,则其缺省的段寄存器为SS。
操作数在存储器中,其有效地址是一个基址寄存器(BX、BP)和一个变址寄存器(SI、DI)的内容之和,成为基址加变址寻址方式。如果有效地址中含有BP,则缺省的段寄存器为SS;否则,缺省的段寄存器为DS。
操作数在存储器中,其有效地址是一个基址寄存器(BX、BP)的值、一个变址寄存器(SI、DI)的值和指令中的8位/16位偏移量之和。其有效地址的计算公式如右式所示:
如果有效地址中含有BP,则其缺省的段寄存器为SS;否则,其缺省的段寄存器为DS。指令中给出的8位/16位偏移量用补码表示。在计算有效地址时,如果偏移量是8位,则进行符号扩展成16位。当所得的有效地址超过0FFFFH,则取其64K的模。MOV AX, [BX+SI+1000H]。上面所算的所有EA是内存地址的偏移量而非最终地址。
源操作数 |
指令的变形 |
源操作数的寻址方式 |
只有偏移量 |
MOV AX, [100H] |
直接寻址方式 |
只有一个寄存器 |
MOV AX, [BX] 或 MOV AX, [SI] |
寄存器间接寻址方式 |
有一个寄存器和偏移量 |
MOV AX, [BX+100H] 或 MOV AX, [SI+100H] |
寄存器相对寻址方式 |
有二个寄存器 |
MOV AX, [BX+SI] |
基址加变址寻址方式 |
有二个寄存器和偏移量 |
MOV AX, [BX+SI+100H] |
相对基址加变址寻址方式 |
在汇编语言中,标号、内存变量名、子程序名和宏名等都是标识符,它一般最多由31个字母、数字及规定的特殊字符(?、@、_、$)等组成,并且不能用数字开头。通常情况下,汇编语言不区分标识符中字母的大小写。汇编语言中常用的数据类型有字节、字和双字。
定义数据变量语句是在程序中经常使用的伪指令语句,其一般格式:[变量名] 数据定义符 表达式1[, 表达式2, …,表达式n] ;注释。数据定义符一般是DB/DD/DW。DB 1, 3, 5, 7, 9, 11,'o',每一个数字均为16位即一个字节存储,由引号括起来的字符在内存中是存放其ASCII码值。他们在内存中连续存储。MSG1 DB 'I am a student.'定义了一个字符串,存放在内存仍然是一个一个ASCII码。Word1 DW 89H,1909H, -1定义字变量,内存中存储形式为89 00 09 19 FF FF。低对低高对高。
汇编程序中除了硬指令,还有给编译器处理的伪指令。偶对齐伪指令even,告诉汇编程序(Assember)即将汇编源代码程序编译成机器指令的程序,本伪指令下面的内存变量从下一个偶地址单元开始分配。对齐伪指令ALIGN num告诉汇编程序,本伪指令下面的内存变量必须从下一个能被Num整除的地址开始分配,而且num只能是2的倍数。调整偏移量伪指令ORG告诉汇编程序,本伪指令下面的内存变量从该“数值表达式”所指定的地址开始分配。这三个伪指令用在数据定义之间。
重复说明符DUP,即duplicated,一般使用形式为count DUP (表达式, 表达式, …, 表达式),count是重复次数,(表达式,表达式, …, 表达式)是被重复的部分,“表达式”可以是存储单元的初值,也可以是含义另一个DUP的式子。STRING DB 120 DUP('ABCDE'), 0。这类似于数组。
用STRUC和ENDS可以把一系列数据定义语句括起来作为一种新的、用户定义的结构类型。使用形式为:
结构名 STRUC [Alignment][, NONUNIQUE]
数据定义语句序列
结构名 ENDS
其中Alignment为对齐方式。例如:
stri struc
name dd
aage dw
stri ends
与结构体类型对应的还有联合类型,很想C语言。联合UNION使用形式:
[联合类型名] UNION [Alignment] [,NONUNIQUE]
数据定义语句序列
[联合类型名] ENDS
记录类型RECORD类型:记录名 RECORD 字段[, 字段, ……],“字段”代表:字段名:宽度[=初值表达式]。例如COLOR RECORD BLINK:1,BACK:3=0, INTENSE:1=1, FORE:3
段属性操作符(SEG)返回该标识符所在段的段地址。例如数据段、代码段等。例如moc ax,meg score。偏移量属性操作符(OFFSET)返回该标识符离它所在段的段地址有多少字节,例如mov bx,offset name。类型属性操作符(TYPE)是返回该变量所占字节数,例如type name。长度属性操作符(LENGTH)是针对内存变量的操作符,它返回重复操作符DUP中的重复数。如果有嵌套的DUP,则只返回最外层的重复数;如果没有操作符DUP,则返回1。SIZE 变量 = (LENGTH 变量) × (TYPE 变量)。强制属性操作符:数据类型 PTR 地址表达式,例如mov byte ptr [bx], 9H。
关系运算符有LT GT EQ NE LE GE。逻辑运算符有AND OR XOR NOT SHL SHR。各个运算符的优先级:
优先级:高 |
LENGTH、SIZE、WIDTH、MASK、()、[]、.(用于结构字段)、<>(用于记录类型) | |
↓ ↓ |
PTR、SEG、OFFSET、TYPE、THIS、:(用于段超越前缀) | |
*、/、MOD、SHL、SHR | ||
HIGH、LOW | ||
+、- | ||
EQ、NE、LT、LE、GT、GE | ||
NOT | ||
AND | ||
OR、XOR | ||
优先级:低 |
SHORT |
杂项:
数据存储对齐:数据在内存中存放的时候需要对齐,之所以对齐是为了配合按照块取数据的机制,为了方便数据的访问,提高计算机的处理速度,但是对齐会导致内存空间的浪费。例如struct结构体中的数据不会是紧挨着存放的,而是按照各种类型数据的大小,必须在其整数倍位置存放,并不连续,每种OS有自己的规定。对齐是为了提高系统的访问速度,一般基本的对齐原则是按着最大的基本类型的长度进行对齐,较小的元素可以几个组合起来填充一段对齐内存,实现基本的对齐。结构体中的元素也要满足一定的分布条件,就是元素的存储起始地址要满足能够整除该元素类型的长度。所以在设计struct时,应该将大数据放在前面,用小类型数据来放在后面。
高速缓存CACHE的工作原理基于局部性原理。
各CPU特性:
寄存器的分类 | 寄存器 | 主 要 用 途 |
|
通 用 寄 存 器 |
数据 寄存器 |
AX | 乘、除运算,字的输入输出,中间结果的缓存 |
AL | 字节的乘、除运算,字节的输入输出,十进制算术运算 |
||
AH | 字节的乘、除运算,存放中断的功能号 |
||
BX | 存储器指针 |
||
CX | 串操作、循环控制的计数器 |
||
CL | 移位操作的计数器 |
||
DX | 字的乘、除运算,间接的输入输出 |
||
变址 寄存器 |
SI | 存储器指针、串指令中的源操作数指针 |
|
DI | 存储器指针、串指令中的目的操作数指针 |
||
变址 寄存器 |
BP | 存储器指针、存取堆栈的指针 |
|
SP | 堆栈的栈顶指针 |
||
指令指针 | IP/EIP | ||
标志位寄存器 | Flag/EFlag | ||
32位 CPU的 段寄存器 |
16位CPU的 段寄存器 |
ES | 附加段寄存器 |
CS | 代码段寄存器 | ||
SS | 堆栈段寄存器 | ||
DS | 数据段寄存器 | ||
新增加的 段寄存器 |
FS | 附加段寄存器 | |
GS | 附加段寄存器 |
借鉴文章:
http://blog.chinaunix.net/uid-20937170-id-3053573.html
http://www.cnblogs.com/luzhiyuan/p/3587470.html
http://blog.chinaunix.net/uid-23069658-id-3569341.html