完善MBR
1. 地址、section、vstart
1.1 地址
地址只是数字,描述各种符号在源程序中的位置,它是源代码文件中各符号偏移文件开头的距离。由于指令和变量所占的大小不同,故它们相对于文件开头的偏移量参差不齐。
编译器的工作就是给各个符号编址。编译器根据所在硬件平台的特性,将源代码中的每一个符号(指令和数据)都安装本硬件平台的特性分配空间,在不考虑对齐的情况下,这些符号在空间上都是彼此相邻,连续分布的,它们在程序中距第一个符号的距离便是它们在程序中的地址。
编译器给程序中各符号(变量名或函数名等)分配的地址,就是各符号相对于文件开头的偏移量。
1.2 section
在有的编译器中,同时支持segment和section这两个关键字,它们的功能都是在程序中宣称一个区域。它们属于伪指令,CPU不知道有这些东西,只是为了给程序员在逻辑上将程序划分成几个段。
关键字section并没对程序中的地址产生任何影响,即在默认情况下有没有section都一个样,section中数据的地址依然是相对于整个文件的顺延,仅仅是在逻辑上让开发人员梳理程序之用。
1.3 vstart
section用vstart=修饰后,可以被赋予一个虚拟起始地址virtual start address(这与x86CPU开启分页后的虚拟地址是两码事),它被用来计算在该section内的所有内存引用地址。vstart=xxxx修饰后它并不是告诉编译器程序加载到地址xxxx,加载是加载器的工作,编译器只会规划代码。编译器以相对于文件开头偏移来编址的好处是利于重定位。
mbr用vstart=0x7c00来修饰的原因,是因为mbr要被加载器(BIOS)加载到物理地址0x7c00,mbr中后续的物理地址都是0x7c00+(cs段寄存器此时是0)。所以vstart使用的时机是:我预先知道了我的程序将来被加载到某地址处。
由于程序指定自己将来会被加载器放到某个地址,所以告诉编译器把它编成这个地址,将来CPU用此地址才能找到。vstart只是告诉编译器以新的数字作为后面数据的起始值,它本身没改变数据在文件中的地址。
2. CPU的实模式
实模式是指8086CPU的寻址方式、寄存器大小、指令用法等,是用来反应CPU在该环境下如何工作的概念。
2.1 cpu工作原理
CPU大体上可以划分为3个部分,它们是控制单元、运算单元、存储单元。
控制单元是CPU的控制中心,CPU需要它才知道下一步要做什么。而控制单元大致由指令寄存器IR(Instruction Register)、指令译码器ID(Instruction Decoder)、控制寄存器OC(Operation Controller)组成。程序被加载到内存后,也就是指令这时都在内存中了,指令指针寄存器IP指向内存中下一条待执行指令的地址,控制单元根据IP寄存器的指向,将位于内存中的指令逐个装载到指令寄存器,然后指令译码器将位于指令寄存器的指令按照指令格式来解码。
存储单元是指CPU内部的L1、L2缓存及寄存器,待处理的数据就存在这些存储单元中,这里的数据是说指令中的操作数。 这些缓存都是采用的SRAM(Static RAM)存储器,具有静态存取的功能,SRAM不需要刷新电路即能保存它的的内部数据,因此SRAM性能较强劲。缺点是它的集成度较低,相同容量之下,SRAM的体积比DRAM要大很多。二级缓存都不大,目前来说顶多4MB左右,所以现代CPU用二级缓存的数量取胜,如L1、L2、L3共三级。
寄存器可分为两大类,程序员可以使用的寄存器称为程序可见寄存器,如通用寄存器、段寄存器。程序不可见寄存器是指程序员不可使用,也无法访问到它们,系统运行期间可能要用到的寄存器。
运算单元负责算术运算(加减乘除)和逻辑运算(比较、位移),它从控制单元那里接受命令(信号),它没有自主意识,只是个执行部件。
总结:控制单元要取下一条待运行的指令,该指令的地址在程序计数器PC中,在x86CPU上,程序计数器就是cs:ip。于是读取ip寄存器后,将此地址送上总线,CPU根据此地址便得到了指令,并将其存入到指令寄存器IR中。这时轮到指令译码器上场了,它根据指令格式检查指令寄存器中的指令,先确定操作码是什么,再检查操作数类型,若是在内存中,就将相应的操作数从内存中取回自己的存储单元,若操作数是在寄存器中就直接用了。操作码和操作数齐了,操作控制器给运算单元下命,于是运算单元便真正开始执行指令了。ip寄存器的值被加上当前指令的大小,于是ip又指向了下一条指令。
2.2 实模式下的寄存器
寄存器是一种物理存储元件,只不过它比一般的存储介质要快,能够跟上CPU上的步伐,所以在CPU内部有很多这样的寄存器来给CPU存取数据。
缓存是一项伟大的发明,成功解决了速度不匹配设备直接的数据传输,在一般情况下,IO是整个系统的瓶颈,缓存的出现,有效减少了低速IO设备的访问频率,从而大幅度提升了速度。
SRAM是用寄存器来存储数据的,这就是SRAM快的原因。而寄存器是使用触发器实现的,这也是一种存储电路,工作速度极快,是纳秒级别的,这是和cpu一个级别了。
在实模式下,默认用到的寄存器都是16位的宽的。在32位CPU在实模式下,虽然操作数是16位,但依然可以使用32位寄存器。
CPU以“当前IP寄存器中的值 + 当前执行指令的机器码长度”的和作为新的代码段内偏移地址,将其存入IP寄存器,再到该新地址处读取指令并执行。如果下一条指令需要跨段访问,还要加载新的段基址到CS寄存器。
2.3 实模式下的内存分段
实模式的“实”体现在:程序中用到的地址都是真实的物理地址,“段基址:段内偏移”产生的逻辑地址就是物理地址,也就是程序员看到的完全是真实的地址。
在8086之前的CPU,没有段的概念,首先程序无法重定位,必须加载固定内存位置,程序对地址的依赖性强。Intel早期的工程师为此发明了“段”,即CPU访问内存用“段 + 偏移”的形式,这种策略首次在8086CPU上。为了支持段机制,CPU新增了段寄存器,如cs,ds,es等。
为了让16位的寄存器寻址能够访问20位的地址空间,CPU通过“段基址左移4位 + 段内偏移”形成20位地址,从而突破了16位寄存器作为偏移无法访问1MB的限制。但是,现在可以能访问的最大地址是0xFFFF:0xFFFF,即0x10FFEF。相比0xFFFFF的范围,超出了0xFFF0的空间,也就是64K-16字节,这部分就是传说中的高端内存区(Hige Memory Area, HMA)。由于8086一共就20条地址线,即A0~A19,内存地址0xFFFFF+是要用到A20地址线(实模式到保护模式要打开A20),可是8086没有,只能接收20位长的地址。所以超过了20位而产生进位,就给丢弃了。其作用相当于对1MB取模,形成回卷的效果。
2.4 实模式下CPU的内存寻址方式
可分为三大类:
- 寄存器寻址;
- 立即数寻址;
- 内存寻求:
- 直接寻址;
- 基址寻址;
- 变址寻址;
- 基址变址寻址。
2.5 实模式下ret
ret(return)指令的功能是在栈顶(寄存器ss:sp所指向的地址)弹出2两个字节的内容来替换IP寄存器。不用换基地址,属于近返回。
retf(return far) 是从栈顶取得4字节,分别替换ip寄存器和cs寄存器。
2.6 实模式下的call
在8086CPU中,也就是实模式下,call指令调用函数有四种方式。
- 16位实模式相对近调用
“近”就是指同一段内,不用切换段,不用换基地址,只需给出段内偏移地址。
可以使用near关键字来修饰,near表示在内存或寄存器中取2个字节,这是一种数据类型转换。near可以省略。机器码是e8+llhh(操作数)
“相对”既然是相对量,就有正负之分。操作数是个有符号数。在同一段内的函数(近调用),必须要用相对地址的形式,这是硬件设计的问题。
call相对近调用中的操作数并不是被CPU直接用了,CPU又将其恢复成绝对地址:当前的IP指针 + 操作数 + 机器码大小 = 目标函数绝对地址。
- 16位实模式间接绝对近调用
“间接”是指目标函数的地址没有直接给出,是通过寄存器或内存,总之不以立即数的形式出现。
“绝对”是指目标函数的地址是绝对地址。
机器码是ff16。
在寄存器前面添加数据类型伪指令(far、near、short)对寄存器宽度做了强制转换,会发生警告。
- 16位实模式下直接绝对远调用
“直接”意指不需要经过寄存器或内存,操作数以立即数的形式给出。
“远”就意指需要跨段。
对于直接绝对远调用,far可以不加。操作码是0x9a+2字节偏移+2字节段基地址
- 16位实模式下的间接绝对远调用。
和上一种的区别就是“直接”变成“间接”,也就是说,段基地址和段内偏移都不是立即数,要么在内存,要么在寄存器。
但是段基址和偏移地址都是16位的,既然要用两个,干脆一个都不用。所以这种间接绝对远调用,不支持寄存器寻址,只支持内存寻址。
指令格式是:call far 内存地址,一定要加far关键字,否则和第二种间接绝对近调用一样了。
2.7 实模式下的jmp
- 16位实模式相对短转移
相对短转移的机器码大小是2字节,操作码是0xeb,可知其操作数是1字节。“短”体现在,即跳转范围只能是1字节有符号数所表示的范围,-128~127。
关键字short可以省略,但省略后并不能保证nasm依然把它编译成相对短转移。前面说,操作数的范围是-128~127,如果操作数不在此范围,将会在编译阶段报错(前提是加了short关键字)。
- 16位实模式相对近转移
相对近转移机器码是3字节,操作码是0xe9,相对于短转移范围增大了,操作数依然是地址相对量,范围是-32768~32767。
- 16位实模式间接绝对近转移
同相对近转移相比,间接绝对近转移其目标地址是绝对地址,并且未在指令中直接给出,而是存在寄存器或内存中。其操作码是0xff。
- 16位实模式直接绝对远转移
其操作数是立即数,并且是绝对地址, 有跨段需求。操作码是0xea
- 16位间接绝对远转移
指令格式:jmp far 内存地址。