es、cx、si、di寄存器:
es寄存器,extra segment register,叫做附加段寄存器,通常用作一个额外的段寄存器,反正一般就是来作为目标段寄存器的,说白了就是存地址的。
cx寄存器,count register,计数器寄存器,存数计数的。在loop指令中,cx里面存循环次数,循环一次自动减一,直到为零。
si寄存器,source index register,源变址寄存器,经常用来指向源数据的地址。
di寄存器,destination index register,目的变址寄存器,经常用来指向目的数据的地址。
sub和rep指令:
sub指令,subtract的缩写,顾名思义,sub用于执行两个操作数之间的减法操作,具体是:
sub a b,a-b后,把结果存在a里。
rep指令,repeat的缩写,顾名思义,重复。具体来说,rep不是一个指令,而是一个指令前缀,它用于重复执行其后的字符串操作指令,直到cx的值减为零。
继续bootsect.s_1:
entry start
start:
mov ax,#BOOTSEG
mov ds,ax
mov ax,#INITSEG ;0x9000
mov es,ax
mov cx,#256
sub si,si
sub di,di
rep
movw
entry指令会告诉链接器将程序的入口点设置在指定的标签或地址,这里就是start处。
那么程序会从start标签处开始执行。
start后,将ds设置成了0x07c0,es设置成了0x9000,cx设置成了256。
然后,sub了si和di,也就是将这两个寄存器中的值清零了。
再然后,大的来了。rep了movw,movw表示复制一个字,word嘛,也就是16比特。
那么就来了几个问题:重复多少次,从什么地方的数据复制到什么地方,怎么复制。
自然是重复256次,也就是cx中的值。然后是从ds:si处复制到es:di处,这种地址的表示方式,是汇编里特定的,ds和es是段寄存器,si和di是偏移寄存器,通常是以这样两两结合的形式来表示一个完整的物理地址,也就是内存地址。而ds:si和es:di也是常见的常规搭配,需记忆。
在进行寻址时,ds和es都需要进行左移四位,也就是十六进制的一位,然后再加上偏移地址,也就是偏移寄存器si,di的地址。
那么,就可以来算一算具体是哪个地址到哪个地址了。
ds的值是0x07c0,左移后是0x7c00,加上si,si被清零了,第一个内存地址就是0x7c00,也就是那最开始的512字节数据的起始地址。
es的值是0x9000,左移后就是0x90000,加上di,di被清零了,第二个内存地址就是0x90000。
如果把内存比作一栋楼,那么一层有8个房间,也就是说,一个地址,代表8比特。复制时是一个字一个字地复制,也就是16比特,也就是两层楼,也就是两个地址。
复制256次,一次两字节,一共就是512字节。那么,rep movw这一句干了什么事,就是把0x7c00开始的往后的512字节的数据,复制到了以0x90000这个地址开始的后512字节的空间之中。至于他为什么要这么做,现在还不得而知,但肯定是有用滴。
jmpi指令:
jmpi是一个段间跳转指令,跳转到某个地址执行程序。
bootsect.s_2:
jmpi go,INITSEG ;0x9000
go: mov ax,cs
mov ds,ax
第一句是这么个逻辑,跳转到0x9000:go处执行程序,前面的是偏移地址,后面的是基址。在计算时,0x9000仍要左移四位,然后就成了跳转到0x90000+go这个地址处执行程序。
那么go是多少呢?go是一个标签,在编译的时候会被翻译成一个值,具体要看编译的情况。
如果mov ax,cx这行代码在编译后位于0x08处,那么go就等于0x08,那么就会跳转到0x90008这个地址那里。
补丁:
一:
在电源打开后,cpu会自动复位,复位或加电时,会将指令指针寄存器IP设置为0xFFF0,并将代码段寄存器CS设置为0xF000。cs:ip是一个常规寻址搭配,那么其值就会是:
0xF0000+0xFFF0=0xFFFF0。这是intel 80286或80386处理器特定的,这是一个固定的起始点,这个地址指向BIOS ROM中的一个预设位置,这确保了BIOS和系统硬件的兼容性。在现在的处理器中,这个值会更大一些。
开机时,BIOS首先会初始化和检查计算机硬件,比如内存、处理器、键盘、显示器、硬盘等。在完成基本的系统初始化和配置之后,BIOS就会把启动区的代码复制到内存的0x7c00处,然后就是cpu执行此处的程序。
BIOS ROM,这个只读存储器是主板上的一个硬件组件,是一个专门的芯片,里面存的就是BIOS的代码。而0xFFFF0,就是这段代码的起始地址,所以就需要一开始设置cpu指向这个地址并指向代码,也就是cpu执行BIOS的代码。
在BIOS完成启动区数据的复制后,会设置IP为0x0000,cs为0x07c0,这样cpu就指向了内存的0x7c00处,也就是cpu开始执行位于内存的启动区代码。
二:
对于这段代码:
jmpi go,INITSEG ;0x9000
go: mov ax,cs ;cs=0x9000
mov ds,ax ;ds=0x9000
mov es,ax ;es=0x9000
mov ss,ax ;ss=0x9000
mov sp,#0xFF00 ; arbitrary value >>512
在执行jmpi之前,是完成了一个rep movw的操作,这是bootsect.s的代码,bootsect.s就是启动区的代码,大小应该是没有512字节的,也就是说,此时cpu还在执行0x7c00往后的一点点内存处的代码,也就是rep movw所在的内存空间处。
但在执行了jmpi后,一下子就跳去了0x90000+go处去了,这个地址距离0x7c00还是很远的。而在原本的0x7c00那一块内存处,mov ax,cs也是有存储的,也就是说这一处的代码是有一个地址的,而这个地址,应该就是go所代表的值。所以说cpu去到0x90000+go处,具体是多少,就是要看在编译的时候go是多少,然后加上go的地址值,就是jmpi跳转到的具体地址。反正就是0x90000往后一点点就是了,因为在mov ax,cs前面本身也没多少代码。
cs、ds、ss、sp寄存器:
cs,code segment register,代码段寄存器,存代码段的基址。
ds,data segment register,数据段寄存器,存数据段的基址。
ss,stack segment register,堆栈段寄存器,这是中文翻译的一个很诡异的地方,不知道是哪个神仙翻译的,stack就是栈,但偏要加个堆,堆是堆,栈是栈,这才对嘛。虽然叫它堆栈段寄存器,但实际就是栈段寄存器,没必要加个堆来迷惑人。用来存栈段基址的。
sp,stack pointer register,堆栈指针寄存器,同样,也加了个堆,反正理解为栈指针寄存器就对了,用来存栈当前栈顶地址的。栈顶嘛,就是代表了栈此时有多大咯,ss:sp这样的搭配,就是用来具体代表栈顶地址的,很常规的操作。常见的还有:cs:ip,ds:(ax,bx,cx,dx)等。
go处的代码:
在cpu跳转到0x90000+go处后,执行的就是下面的mov操作,一开始是将cs的值复制给ax,那么cs的值是多少呢?上面的代码中并没有说啊?
cpu执行何处的代码,是由cs:ip这个地址指定的。而在上一句,jmpi go,0x9000处,就说了cpu去0x90000+go这个地址去执行程序,也就是说,cs的值,就是0x9000。ip自然就是go的值了,当然,这里没有用它。
然后就简单了,ax、ds、es、ss的值成了0x9000,这几个东西,都是基址,基址可以干嘛,就可以靠这些寄存器找到0x90000这个地址。之前在0x7c00处的512字节的数据应该是弃用了。
然后,给sp赋值了一个0xFF00,sp是和ss搭配的,ss:sp的值现在就是0x9FF00,这个0xFF00加个0x100,也就是加个256,就是0x10000了,0x10000是,也就是65536,也就是
个地址,每个地址8比特,也就是1字节。那么0xFF00就差不多快代表了64kb的空间了,设置大一点是为了数据的安全,影响了其他数据的存储就乱套了。