辅助函数-->字符串打印,软盘读取
问题:主引导程序中如何进行字符串打印?
BIOS中的字符串打印:
指定打印参数(AX=0X1301,BX=0x0007)
指定字符串的内存地址(ES:BP=串地址)段地址和段内偏移地址来指定目标字符串的内存地址
指定字符串的长度(CX=串长度)
中断调用(int 0x10)
字符串打印示例:
//指定字符串地址:
mov ax, msg(字符串在段内偏移地址)
mov bp, ax
mov ax, ds 段地址
mov es, ax
//指定字符串长度:
mov cx, 6
//指定打印参数
mov ax, 0x1301
mov bx,0x0007
int 0x10
汇编小贴士:
汇编中可以定义函数(函数名使用标签定义):
call function 函数调用
函数体的最后一条指令为ret. 返回指令
如果代码中定义了函数,那么需要定义栈空间:
用于保存关键寄存器的值,栈顶地址通过sp寄存器保存
汇编小贴士:
汇编中的“常量定义”(equ)
用法:Const equ 0x7c00; #define Const 0x7c00
与dx(db,dw,dd)的区别:dx定义占用相应的内存空间。equ定义不会占用任何空间。
定义打印函数:
asm 主引导程序源代码
bin 主引导程序编译得到的二进制代码
IMG 虚拟软盘文件
取代一系列操作makefile:
.PHONY : all clean rebuild
SRC := boot.asm
OUT := boot.bin
IMG := data.img
RM := rm -fr
all : $(OUT) $(IMG)
dd if=$(OUT) of=$(IMG) bs=512 count=1 conv=notrunc
@echo "Success!"
$(IMG) :
bximage $@ -q -fd -size=1.44
$(OUT) : $(SRC)
nasm $^ -o $@
clean :
$(RM) $(IMG) $(OUT)
rebuild :
@$(MAKE) clean
@$(MAKE) all
主引导程序:
short :短跳转
jmp占一个字节,start占一个字节,另一个用空指令nop填充
为什么前边有三个字节:因为定义的文件头不是可执行的代码,不是啊,所以必须跳过这段头信息,跳到可执行代码处执行。
问题:主引导程序中如何读取指定扇区处的数据?
直接操作硬件:直接操作软驱去读软盘。
软盘的构造:
一个软盘有2个盘面,,每个盘面对应一个磁头。
每一个盘面被划分为若干个圆圈,成为柱面(磁道)。
每一个柱面被划分为若干个扇区,每个扇区512字节。
3.5寸软盘的数据特性:
每个盘面一共80个柱面(编号为0-79)
每个柱面有18个扇区(编号1-18)
存储大小:2*80*18*512=1474560 Bytes=1440kB
软盘数据的读取:
软盘数据以扇区(512字节)为单位进行读取
指定数据所在位置的磁头号,柱面号,扇区号
计算公式:
逻辑扇区号/柱面扇区数(18)=商Q 磁头号:Q&1 柱面号:Q>>1 余R->扇区号:R+1
怎么读,怎么具体拿到数据:
BIOS中的软盘数据读取(int 0x13):
软盘数据读取流程:
指定逻辑扇区号(AX)指定读取扇区数(CX)-->指定内存位置(ES:BX)-->重置软驱状态-->根据逻辑扇区号计算:柱面号,磁头号,扇区号-->int 0x13-->ret
这样数据就读取到内存中了。
汇编小贴士:
汇编中的16位除法操作(div):
被除数放到AX寄存器,除数放到通用寄存器或内存单元(8位),结果:商位于AL,余数位于AH。
磁盘数据的读取:
如何测试呢?将虚拟软盘文件拖到windows中,
0x4400处有我们的字符,计算在那个扇区,4400的16进制除以512得34扇区(逻辑扇区号)的第0字节处。
如果对汇编不熟悉怎么办?
断点调试:ndisasm -o 0x7c00 boot.bin > boot.txt
ndisasm -o 0x7c00(从这个地址处开始) boot.bin > boot.txt(放到这个文件)
找到函数的入口地址,设置断点,在mov bl, [BPB_SecperTrk]设置断点
bochs
6
break 0x7c78 //函数入口设置断点
info break
c //继续执行
break 0x7c81
info break
c
step
s
s
s
s
s
reg
当得到关键参数时,成功的将逻辑扇区号分解为三元组后,怎么判断分解的对不对呢?打印一下关键寄存器的值,手算一下,对不对比如验证起始扇区号对不对,起始扇区号是由34除以18得到余数之后加1来的。
主引导程序代码:
org 0x7c00
jmp short start
nop
define: //定义栈空间
BaseOfStack equ 0x7c00 //栈空间起始地址
header: //FAT12文件需要的头,第0扇区主引导区,偏移是3,前边是跳转指令
BS_OEMName db "D.T.Soft"
BPB_BytsPerSec dw 512
BPB_SecPerClus db 1
BPB_RsvdSecCnt dw 1
BPB_NumFATs db 2
BPB_RootEntCnt dw 224
BPB_TotSec16 dw 2880
BPB_Media db 0xF0
BPB_FATSz16 dw 9
BPB_SecPerTrk dw 18
BPB_NumHeads dw 2
BPB_HiddSec dd 0
BPB_TotSec32 dd 0
BS_DrvNum db 0
BS_Reserved1 db 0
BS_BootSig db 0x29
BS_VolID dd 0
BS_VolLab db "D.T.OS-0.01"
BS_FileSysType db "FAT12 "
start:
mov ax, cs
mov ss, ax
mov ds, ax
mov es, ax //寄存器初始化
mov sp, BaseOfStack //指定sp栈顶指针寄存器具体值,sp指向栈的起始地址0x7c00,
//程序从低地址向高地址运行,栈起始地址为0x7c00,当函数调用时,就会出现压栈操作,那么栈的增长方向是从高地址到低地址增长,所以他是从高到低,而程序执行是从低到高,所以不会影响内存中的可执行代码,所以可以是0x7c00
mov ax, 34 //我们要读取的逻辑扇区号为34mov cx, 1 //需要连续读取一个扇区
mov bx, Buf //读取到Buf这, 指定一下目标内存的地址
call ReadSector //调用readsector函数
mov bp, Buf //用print函数打印一下看看是不是我们期望的字符串
mov cx, 29 //打印29字节
call Print
last:
hlt
jmp last
; es:bp --> string address
; cx --> string length
Print:
mov ax, 0x1301 //指定打印参数
mov bx, 0x0007
int 0x10
ret
; no parameter
ResetFloppy: //重置软驱
push ax //备份ah 进栈
push dx
mov ah, 0x00 //规定
mov dl, [BS_DrvNum]
int 0x13
pop dx //出栈,与进栈顺序相反
pop ax
ret
; ax --> logic sector number 逻辑扇区号
; cx --> number of sector 多少个扇区
; es:bx --> target address 读取到内存位置
ReadSector: //读取软驱函数
push bx //备份
push cx
push dx
push ax
call ResetFloppy
push bx
push cx //将cx的值暂时放到栈上
mov bl, [BPB_SecPerTrk] //18每个柱面扇区数,这个地方改变了bx寄存器的值,意味着目标内存地址改变了,所以需要之前两行的代码
div bl //除法
mov cl, ah //余数
add cl, 1 //余数加1,之后cl为扇区号
mov ch, al //商
shr ch, 1 //商右移一位 ,得到柱面号
mov dh, al
and dh, 1 //按位与,得到磁头号
mov dl, [BS_DrvNum] //驱动器号
pop ax //将cx中的值拿出来放到ax寄存器中,al里边就有了原来cx寄存器的值,也就是读取的扇区数量
pop bx
mov ah, 0x02 //读操作,规定0x02
read:
int 0x13 //执行0x13号中断的时候有可能读取失败,失败就在读取,一直读取
jc read //错误标识位有没有被设置,设置了就跳转重读
pop ax
pop dx
pop cx
pop bx
ret
MsgStr db "Hello, DTOS!"
MsgLen equ ($-MsgStr) //当前地址减去msgstr地址,两个地址的差就是上边字符串的长度
Buf:
times 510-($-$$) db 0x00
db 0x55, 0xaa
小结:本课编写了函数打印字符串,编写了函数打印软盘中的数据。
当汇编代码中定义了函数,那么也需要定义栈空间。其实就是指定栈空间sp寄存器的值
读取数据前,逻辑扇区号需要转化为磁盘的物理位置。
物理软盘上的数据位置由磁头号,柱面号,扇区号唯一确定。
软盘数据以扇区(512字节)为单位进行读取。
7、512字节中
突破限制的预备工作
辅助函数: (字符串打印,软盘读取)内存比较, 根目录区查找
如果要突破512字节限制:
将根目录区加载进入内存(readsect)-->在根目录区中查找目标文件-->存在:通过FAT表项将文件内存加载进入内存-->执行跳转 不存在-->打印错误信息(print函数)-->end
问题:如何在根目录区查找目标文件?
通过根目录项的前11个字节进行判断:文件名
将文件名字在程序中指定,然后将指定的名字和每一个文件项的前11个字节进行比对,如果一样 ,就查找成功了。
内存比较:
指定源起始地址(DS:SI)
指定目标起始地址(ES: DI)
判断在期望长度(CX)内每一个字节是否都相等。
汇编小贴士:
汇编中的比较与跳转:
比较:cmp cx,0 ; 比较cx的值是否为0
跳转: jz equal; 如果比较的结果为真,则跳转到equal标签处
汇编贴士:
;ds:si -->sourse
;es:di-->destination
;cx-->length
;
; return:
; (cx == 0) ? equal : noequal
MemCmp:
push si //备份
push di
push ax
compare:
cmp cx, 0
jz equal //相等跳转equal
mov al, [si] //将si指向的字节拿出来放到al中
cmp al, byte [di] //al中的值与di中指向的一个字节值比较,byte取一个字节
jz goon //相等跳转
jmp noequal //跳转到不相等处
goon:
inc si //si++
inc di //di++
dec cx //cx--
jmp compare
equal:
noequal:
pop ax
pop di
pop si
ret
//cx寄存器为什么没有备份?
因为我们把cx寄存器作为返回值来使用,就没必要保存它的状态了。
r
打印寄存器值
第二个任务:
查找根目录区是否存在目标文件:
流程:
加载根目录区bx-->指定目标字符串si-->指定查找次数dx-->dx>0 ? yes mov di,bx mov cx,LEN(no noexist)-->Memcmp-->cx>0(no exist)-yes->add bx,32 dex dx
加载根目录区:
mov ax,19 ;从第19逻辑扇区开始
mov cx,14; 连续读取14个扇区
mov bx,Buf ;读取至Buf中
call ReadSector
汇编小贴士:
访问栈空间中的栈顶数据:
不能使用sp直接访问栈顶数据。通过其它通用寄存器间接访问栈顶数据。
push cx
mov bp,sp
;...
mov cx,[bp]; ERROR-->mov cx, [sp]
;....程序:
; es:bx --> root entry offset address根目录起始地址
; ds:si --> target string
; cx --> target length
;
; return:
; (dx != 0) ? exist : noexist
; exist --> bx is the target entry
FindEntry:
push di
push bp
push cx //老用比较函数,所以先把cx寄存器进栈
mov dx, [BPB_RootEntCnt] //头文件中根目录区最多有BPB_RootEntCnt项
mov bp, sp //保存sp的值到bp中,因为反复获取cx寄存器的值,我们要反复用到目标字符串的长度
find:
cmp dx, 0
jz noexist
mov di, bx //bx指向根目录区每一项的入口地址,调用函数之前保存根目录区第0项
mov cx, [bp]
call MemCmp
cmp cx, 0
jz exist
add bx, 32
dec dx
jmp find
exist:
noexist:
pop cx
pop bp
pop di
ret
//把dx定义成了返回值,所以他的状态不需要改变
org 0x7c00jmp short start
nop
define:
BaseOfStack equ 0x7c00
RootEntryOffset equ 19
RootEntryLength equ 14
header:
BS_OEMName db "D.T.Soft"
BPB_BytsPerSec dw 512
BPB_SecPerClus db 1
BPB_RsvdSecCnt dw 1
BPB_NumFATs db 2
BPB_RootEntCnt dw 224
BPB_TotSec16 dw 2880
BPB_Media db 0xF0
BPB_FATSz16 dw 9
BPB_SecPerTrk dw 18
BPB_NumHeads dw 2
BPB_HiddSec dd 0
BPB_TotSec32 dd 0
BS_DrvNum db 0
BS_Reserved1 db 0
BS_BootSig db 0x29
BS_VolID dd 0
BS_VolLab db "D.T.OS-0.01"
BS_FileSysType db "FAT12 "
start:
mov ax, cs
mov ss, ax
mov ds, ax
mov es, ax
mov sp, BaseOfStack
mov ax, RootEntryOffset
mov cx, RootEntryLength
mov bx, Buf //读软盘内容到内存
call ReadSector
mov si, Target
mov cx, TarLen
mov dx, 0
call FindEntry
cmp dx, 0
jz output //查找不到
jmp last
output:
mov bp, MsgStr
mov cx, MsgLen
call Print
last:
hlt
jmp last
; es:bx --> root entry offset address
; ds:si --> target string
; cx --> target length
;
; return:
; (dx != 0) ? exist : noexist
; exist --> bx is the target entr
FindEntry:
push di
push bp
push cx
mov dx, [BPB_RootEntCnt]
mov bp, sp
find:
cmp dx, 0
jz noexist
mov di, bx
mov cx, [bp]
call MemCmp
cmp cx, 0
jz exist
add bx, 32
dec dx
jmp find
exist:
noexist:
pop cx
pop bp
pop di
ret
; ds:si --> source
; es:di --> destination
; cx --> length
;
; return:
; (cx == 0) ? equal : noequal
MemCmp:
push si
push di
push ax
compare:
cmp cx, 0
jz equal
mov al, [si]
cmp al, byte [di]
jz goon
jmp noequal
goon:
inc si
inc di
dec cx
jmp compare
equal:
noequal:
pop ax
pop di
pop si
ret
; es:bp --> string address
; cx --> string length
Print:
mov ax, 0x1301
mov bx, 0x0007
int 0x10
ret
; no parameter
ResetFloppy:
push ax
push dx
mov ah, 0x00
mov dl, [BS_DrvNum]
int 0x13
pop dx
pop ax
ret
; ax --> logic sector number
; cx --> number of sector
; es:bx --> target address
ReadSector:
push bx
push cx
push dx
push ax
call ResetFloppy
push bx
push cx
mov bl, [BPB_SecPerTrk]
div bl
mov cl, ah
add cl, 1
mov ch, al
shr ch, 1
mov dh, al
and dh, 1
mov dl, [BS_DrvNum]
pop ax
pop bx
mov ah, 0x02
read:
int 0x13
jc read
pop ax
pop dx
pop cx
pop bx
ret
MsgStr db "No LOADER ..."
MsgLen equ ($-MsgStr)
Target db "LOADER "
TarLen equ ($-Target)
Buf:
times 510-($-$$) db 0x00
db 0x55, 0xaa
小结:
可通过查找根目录区判断是否存在目标文件:
加载根目录区至内存中(ReadSector)
遍历根目录区中的每一项(FindEntry)
通过每一项的前11个字节进行判断(MemCmp),如果是findEntry返回,dx寄存器的值不为0,bx寄存器就是目标项的入口地址,找不到dx寄存器的值为0.
当目标不存在时,打印错误信息(print)。
8、突破下
通过FAT表项将文件内容加载进入内存。
最后的冲刺:
备份目标文件的目录信息(MemCpy)。前面将目标文件的目录信息起始地址查找到了,对我们有用的32个字节?因为根目录区中每一项占32个字节,将根目录区全部加载到内存中然而存在了浪费,所以对我们有用的32个字节拷贝到内存的另一个地方。这样就能有效利用内存了。
加载Fat表,并完成Fat表项的查找与读取(FatVec)。
第一步:内存拷贝,备份目标文件的目录信息(内存拷贝)
实现的关键要点:
坐标si<di, 原内存和目标内存有重叠,原内存地址较小,目标内存起始地址较大,这时必须从后向前进行拷贝,将原内存的最后一个字节拷贝到目标内存的最后一个字节,然后将原内存的倒数第二个字节拷贝到目标内存的倒数第二个字节,从后向前。右边的情况:si>di,这时从前向后拷贝。
重叠是需要考虑的重点情况。
汇编的小贴士:
cmp si, di
ja btoe ; if(si>di)
; ....
; ....
btoe;
; ....
; ....
ja表示 >,jna <=, jb <, jnb >= (n 反面)
实战:创建内存拷贝函数:
Fat表项的读取:
Fat表中的每个表项占用1.5个字节,即:使用3个字节表示两个表项
第一表项,第二表项是前三个字节
FatVec[j]的“动态组装”:
j=0,2,4,6,8,...
i=j/2*3 -->(i , j均为整形数)
FatVec[j]=((Fat[i+1]& 0x0F)<<8) | Fat[i]; (偶数)
FatVec[i+1]=(Fat[i+2]<<4) | (Fat[i+1]>>4)&(0x0f);(奇数)
汇编小贴士:
汇编中的16位乘法操作(mul)
被乘数放到AL寄存器。
乘数放到通用寄存器或内存单元(8位)
相乘的结果放到AX寄存器中。
试验:fat表项读取函数:
起始簇号为4,并且一个扇区保存文件内容、
ndisasm -o 0x7c00 boot.bin > boot.txt 反编译一下
小结:内存拷贝时需要考虑进行拷贝的方向。
当si>di时,从前向后拷贝。
当si<di时,从后向前拷贝。
Fat表加载到内存中之后,需要“动态组装”表项。
Fat表中使用3个字节表示2个表项,
起始字节=表项下标/2*3-->(运算结果取整)
<bochs:1> break 0x7c8d
<bochs:2> break 0x7c90
<bochs:3> c
<bochs:4> r
eax: 0x00000001 1
ecx: 0x00000004 4
edx: 0x00000000 0
ebx: 0x00007e00 32256
esp: 0x00007c00 31744
ebp: 0x00000000 0
esi: 0x000e7e58 949848
edi: 0x00007dd8 32216
eip: 0x00007c8d
<bochs:5> c
<bochs:6> r
eax: 0x00000002 2
ecx: 0x00007e06 32262
edx: 0x00000fff 4095
ebx: 0x00007e00 32256
esp: 0x00007c00 31744
ebp: 0x00007e06 32262
esi: 0x000e7e58 949848
edi: 0x00007dd8 32216
eip: 0x00007c90
; es:di --> destination
; cx --> length
MemCpy:
push si
push di
push cx
push ax
cmp si, di
ja btoe
add si, cx
add di, cx
dec si
dec di
jmp etob
btoe:
cmp cx, 0
jz done
mov al, [si]
mov byte [di], al
inc si
inc di
dec cx
jmp btoe
etob:
cmp cx, 0
jz done
mov al, [si]
mov byte [di], al
dec si
dec di
dec cx
jmp etob
done:
pop ax
pop cx
pop di
pop si
ret
9、主引导程序控制权的转移
BootLoader内存布局
低----->高
0x7c00之前是栈空间,0x9000之后是Loader程序,之间是主引导程序。最终会将控制权从主引导程序(boot程序)跳转到0x9000处向后执行,也就是将控制权从boot程序转交给loader程序。fat表在0x7e00到0x9000之间,4kB。
通过fat边加载文件内容:
bx寄存器指向fat表的起始地址,之后EntryItem(目标文件的文件目录项)+0x1A,bp指向了DIR_FstClus目标文件所在的第一簇,因为fat12里边1簇就是一个扇区,所以DIR_FstClus指向了目标文件的第一个扇区,si指向了0x9000,(目标地址),然后循环,读扇区内容,指定逻辑扇区号dx+31(对应33+j-2),备份dx,bx值,pop cx,将dx中的值放到cx中,然后调用FatVec获取下一个扇区。si向后偏移512字节,准备读取下一个扇区。
试验步骤:
1.在虚拟软盘中创建体积较大的文本文件(Loader)
2.将Loader的内容加载到BaseOfLoader(0x9000)地址处
3、打印Loader中的文本(判断加载是否完全)
cx必须对应的是表项下标、
dx是读取到的值,与0xFF7比较,如果小于,接着循环。
代码量超过了512字节,调整
读取文件长度,在0x1c的地方 mov cx, [EntryItem+ 0x1C]
到虚拟软盘查看:将虚拟软盘挂载到linux中去:
sudo mount -o loop data.img /mnt/hgfs/
打开loader这个文件:
sudo gedit /mnt/hgfs/loader
将这个虚拟软盘从当前linux中移除掉:
sudo umount /mnt/hgfs/
将代码复制到d.t.中,大小是4kb,一个扇区放不下,结果打印一下,是代码,这样就把文件内容加载到内存中了。
移除,重新运行查看。make bochs 6 c
mov dx, [EntryItem + 0x1A]
mov si, BaseOfLoader
loading:
mov ax,dx
add ax,31
mov cx,1
push dx
push bx
mov bx,si
call ReadSector
pop bx
pop cx
; pop dx
; mov cx, dx
call FatVec
cmp dx,0xFF7
jnb BaseOfLoader
add si, 512
jmp loading
接下来创建一个loader程序,
第一个Loader程序:
起始地址0x9000(org 0x9000)
通过int 0x10在屏幕上打印字符串
汇编小贴士:标志寄存器
如果ZF为1,就执行跳转。
汇编小贴士:
jxx代表了一个指令族,功能是根据标志位进行跳转。
jo:当OF为1则跳转
jc:当CF为1则跳转
jns:当SF不为1则跳转
jz:当ZF为1则跳转
je:比较结果为相等则跳转(即:jz)
试验:
org 0x9000 当前程序放到这个地址处进行执行
begin:
mov si, msg si指向要打印的代码
print:
mov al, [si]
add si, 1
cmp al, 0x00
je end 相等结束
mov ah, 0x0E
mov bx, 0x0F
int 0x10
jmp print
//mov ax, 0x1301 打印不是这个吗?
//mov bx, 0x0007
//int 0x10
end:hlt
jmp end
msg:
db 0x0a,0x0a
db "hello ,d.s.os"
db 0x0a,0x0a
db 0x00
保存之后编译, nasm loader.asm -o loader
为了说明问题:反编译
ndisasm -o 0x9000 loader > loader.txt 从地址0x9000处向后反编译
反编译结果:
00009000 BE1B90 mov si,0x901b
00009003 8A04 mov al,[si]
00009005 81C60100 add si,0x1
00009009 3C00 cmp al,0x0对应 cmp al, 0x00
0000900B 740A jz 0x9017 ====je编译器将je作为jz
0000900D B40E mov ah,0xe
然后将loader拷贝到软盘中,然后 从boot跳转到loader来进行执行,,
将当前虚拟软盘挂载到linux中:
sudo mount -o loop data.img /mnt/hgfs/
然后将loader拷贝过去:
sudo cp loader /mnt/hgfs/loader
之后将虚拟软盘卸载下来:
sudo umount /mnt/hgfs
接下来运行:bochs 6 c
然后将data虚拟软盘文件拷贝到E盘,然后在虚拟机运行
改写makefile:
加上虚拟软盘在linux中的挂载路径IMG_PATH := /mnt/hgfs
编译:nasm $^ -o $@
编译完boot程序后,将boot程序烧写到虚拟软盘中。
dd if=@ of=$(IMG) bs=512 count=1 conv=notrunc
接下来定义规则编写loader程序,
编译完之后将结果拷贝到虚拟软盘中,所以说先进行挂载:
sudo mount -o loop $(IMG) $(IMG_PATH)
挂载好之后进行拷贝:
sudo cp $@ $(IMG_PATH)/$@
拷贝完之后卸载:sudo umount $(IMG_PATH)
clean也要变化:
$(RM) $(IMG) $(BOOT_OUT) $(LOADER_OUT)
.PHONY : all clean rebuild
BOOT_SRC := boot.asm
BOOT_OUT := boot
LOADER_SRC :=loader.asm
LOADER_OUT :=loader
IMG := data.img
IMG_PATH := /mnt/hgfs
RM := rm -fr
all : $(IMG) $(BOOT_OUT) $(LOADER_OUT)
@echo "build Success==>w.s.os"
$(IMG) :
bximage $@ -q -fd -size=1.44
$(BOOT_OUT) : $(BOOT_SRC)
nasm $^ -o $@
dd if=$@ of=$(IMG) bs=512 count=1 conv=notrunc
$(LOADER_OUT) : $(LOADER_SRC)
nasm $^ -o $@
sudo mount -o loop $(IMG) $(IMG_PATH)
sudo cp $@ $(IMG_PATH)/$@
sudo umount $(IMG_PATH)
clean :
$(RM) $(IMG) $(BOOT_OUT) $(LOADER_OUT)
rebuild :
@$(MAKE) clean
@$(MAKE) all
运行结果:只有当Target db "LOADER "是这个的时候在会打印w.s.os(或者是6个空格),当内容改变成别的时候就不会打印这个,会打印no leader(控制权没有转移)
小结:
Boot需要进行重构保证在512字节内完成功能。
在汇编程序中尽量确保函数调用前后通用寄存器的状态不变、
Boot成功加载Loader后将控制权转移。
Loader程序没有代码体积上的限制。