我正在编写小型操作系统 - 用于练习。我开始使用bootloader。
我想创建一个以16位实模式运行的小命令系统(现在)
我创建了重启驱动器的bootloader,然后在bootloader之后加载扇区
问题是因为jmp
函数在实际上没有发生任何事情。
我试图在0x7E00加载下一个扇区(我不完全确定如何使用es:bx指向地址,这样可能会出现问题,我相信它的地址:偏移),就在之后引导程序。
这是代码:
;
; SECTOR 0x0
;
;dl is number of harddrive where is bootloader
org 0x7C00
bits 16
;reset hard drive
xor ah,ah
int 0x13
;read sectors
clc
mov bx,0x7E00
mov es,bx
xor bx,bx
mov ah,0x02 ;function
mov al,0x1 ;sectors to read
mov ch,0x0 ;tracks
mov cl,0x1 ;sector
mov dh,0x0 ;head
int 0x13
;if not readed jmp to error
jc error
;jump to 0x7E00 - executed only if loaded
jmp 0x7E00
error:
mov si,MSGError
.loop:
lodsb
or al,al
jz .end
mov ah,0x0E
int 0x10
jmp .loop
.end:
hlt
MSGError db "Error while booting", 0x0
times 0x1FE - ($ - $$) db 0x0
db 0x55
db 0xAA
;
; SECTOR 0x1
;
jmp printtest
;definitions
MSGLoaded db "Execution successful", 0x0
;
; Print function
; si - message to pring (NEED TO BE FINISHED WITH 0x0)
printtest:
mov si,MSGLoaded
.loop:
lodsb
or al,al
jz .end
mov ah,0x0E
int 0x10
jmp .loop
.end:
hlt
times 0x400 - ($-$$) db 0x0
我一直在使用VirtualBox测试此代码但实际上没有发生任何事情,读取错误没有显示,以及应该打印的消息。
答案 0 :(得分:44)
此代码的主要问题是:
第一个是在这段代码中:
mov bx,0x7E00
mov es,bx
xor bx,bx
问题是要将扇区从磁盘加载到0x0000:0x7E00
( ES:BX )。此代码将 ES:BX 设置为0x7E00:0x0000
,其解析为物理地址0x7E000
((0x7E00 <&lt;&lt; 4)+ 0x0000)。我认为目的是将0x07E0
加载到 ES 中,这将产生0x7E00
((0x07E0 <&lt;&lt; 4)+ 0x0000)的物理地址。您可以了解有关16:16内存寻址计算的更多信息here。将段乘以16与将其向左移位4位相同。
代码中的第二个问题是:
mov ah,0x02 ;function
mov al,0x1 ;sectors to read
mov ch,0x0 ;tracks
mov cl,0x2 ;sector number
mov dh,0x0 ;head
int 0x13
磁盘上第二个512块扇区的编号是2,而不是1.因此,要修复上述代码,您需要相应地设置 CL :
mov cl,0x2 ;sector number
应该解决的各种仿真器,虚拟机和真实物理硬件上运行代码的其他问题包括:
lodsb
,movsb
等使用的方向标记。如果方向标志设置不正确 SI / DI 寄存器可能会以错误的方向调整。使用STD
/ CLD
将其设置为您希望的方向(CLD =向前/ STD =向后)。在这种情况下,代码假定向前移动,因此应使用CLD
。有关详细信息,请参阅instruction set reference 要解析第一个和第二个项目,可以在引导加载程序的开头附近使用此代码:
xor ax,ax ; We want a segment of 0 for DS for this question
mov ds,ax ; Set AX to appropriate segment value for your situation
mov es,ax ; In this case we'll default to ES=DS
mov bx,0x8000 ; Stack segment can be any usable memory
cli ; Disable interrupts to circumvent bug on early 8088 CPUs
mov ss,bx ; This places it with the top of the stack @ 0x80000.
mov sp,ax ; Set SP=0 so the bottom of stack will be @ 0x8FFFF
sti ; Re-enable interrupts
cld ; Set the direction flag to be positive direction
有几点需要注意。当您更改 SS 寄存器的值(在这种情况下通过MOV
)时,处理器应该关闭该指令的中断并保持关闭,直到之后> em>以下说明。通常,如果您更新 SS ,然后立即更新 SP ,则无需担心禁用中断。在早期的8088处理器中存在一个错误,这并不值得尊重,因此如果您针对最广泛的环境,明确禁用和重新启用它们是一个安全的选择。如果您不打算在有问题的8088上工作,那么可以在上面的代码中删除CLI
/ STI
指令。我在80年代中期在家用电脑上做的工作第一手了解这个错误。
要注意的第二件事是我如何设置堆栈。对于刚接触8088/8086 16位汇编的人来说,可以通过多种方式设置堆栈。在这种情况下,我将堆栈的顶部(内存中的最低部分)设置为0x8000
( SS )。然后我将堆栈指针( SP )设置为0
。当您在16位实模式下push something on the stack时,处理器首先将堆栈指针递减2,然后在该位置放置一个16位 WORD 。因此,对堆栈的第一次推送将是0x0000-2 = 0xFFFE(-2)。然后,您的 SS:SP 看起来像0x8000:0xFFFE
。在这种情况下,堆栈从0x8000:0x0000
运行到0x8000:0xFFFF
。
当处理在8086上运行的堆栈(不适用于80286,80386+处理器)时,最好将堆栈指针( SP )设置为偶数。在原始8086上,如果将 SP 设置为奇数,则每次访问堆栈空间时都会产生4 clock cycle penalty。由于8088具有8位数据总线,因此不存在此惩罚,但在8086上加载16位字需要4个时钟周期,而在8088上需要8个时钟周期(两个8个)位存储器读取。)
最后,如果您要显式设置 CS:IP ,以便在 JMP 完成时正确设置 CS (至你的内核)然后建议做FAR JMP(参见影响段寄存器的操作 / FAR Jump )。在NASM语法中,JMP
将如下所示:
jmp 0x07E0:0x0000
有些(即MASM / MASM32)汇编程序没有直接支持对 FAR Jmp 进行编码,所以可以通过以下方式手动完成:
db 0x0ea ; Far Jump instruction
dw 0x0000 ; Offset
dw 0x07E0 ; Segment
如果使用GNU汇编程序,它将如下所示:
ljmpw $0x07E0,$0x0000