引导加载程序未加载第二个扇区
Bootloader not loading second sector
我用汇编写了一个bootloader。
它是这样工作的:
首先,BIOS 正常加载引导加载程序。指示去200h.
在 200h,有一些代码位于 200h 和 21Eh 之间。它只是切换到 VGA 模式,使用 VGA 功能在坐标 1,1 上绘制一个洋红色像素。它会永远循环该代码。
然而,当我加载它时,它只是继续闪烁光标,这是普通 VGA .bin 文件不会做的,并且会显示一个像素。我检查了一个像素,但什么也没看到。我所看到的意味着 VGA 代码不是 运行,并且只是加载了引导加载程序,没有别的。
引导程序代码:
cli
startload:
push 0x00000200
ret
times 510-($-$$) db 0
db 0x55
db 0xAA
你可以看到它只是转到下一个扇区(从 200h 开始)
以及 200h-21Eh 处的代码:
BITS 16
org 200h
data:
xcoord DB '1'
ycoord DB '1'
color DB 'D'
.code:
vga:
mov ax, 13h
int 10h
mov ax, ycoord
mov bx, xcoord
mov cx, 320
mul cx
add ax, bx
mov di, ax
mov dl, color
mov [es:di],dl
jmp vga
(是的,我知道这不是 230h 字节,那是编译输出的大小,即 230h。)
问题是什么?
注意:这里不讨论如何让它进入第二扇区。它在问为什么它不去那里。我还没有找到任何解决方案。
您似乎缺少一些非常重要的代码来实际将第二个扇区读入内存。像这样:
mov ah,0x02 ; read sectors into memory
mov al,0x10 ; number of sectors to read (16)
mov dl,[bootDrive] ; drive number
mov ch,0 ; cylinder number
mov dh,0 ; head number
mov cl,2 ; starting sector number
mov bx,Main ; address to load to
int 0x13 ; call the interrupt routine
这假设您已将 dl
的值保存到标签为 bootDrive
的字节中。显然,您需要更改加载代码的地址。
使用 ORG 0x200
只是将汇编程序配置为处理不可重定位地址引用的生成。
回答你的一个问题。此代码:
startload:
push 0x00000200
ret
几乎相当于 CS:0x200 的绝对跳跃。我们不知道 CS 中的值是什么,但许多 BIOS 将以 CS=0 和 IP=0x7c00 开头(但情况并非总是如此)。你不能真正依赖 CS 是一个特定的值,除非你自己设置它。在大多数情况下,CS 可能为零,这意味着您可能会跳转到物理内存地址 0x00200 (0x0000:0x0200)。这恰好在从物理地址 0x00000 到 0x003FF 运行的实模式中断 table 的中间。跳转到该位置可能会导致某种未定义的行为。
您可以将您的引导加载程序加载到 BOCHS 中,它有一个合理的调试器,可以理解 16 位实模式。您将能够单步执行代码并准确确定 CS 是什么以及它跳转到的位置。
您可能想要完成的任务可以使用以下代码完成。
这是我之前 对另一个问题的简单改动。要了解此代码的作用,请参阅我之前的回答。
简而言之,BIOS 从物理内存地址 0x7C00 开始的磁盘读取单个磁盘扇区(512 字节)。如果要加载其他扇区,则必须编写将它们加载到内存中的代码,然后在加载后跳转到该代码。
在此示例中,第一阶段是引导加载程序,它从磁盘的第 2 扇区(引导扇区之后的扇区)加载单个扇区第二阶段。在此示例中,我将通过 segment:offset 对 0x7e0:0x0000 在物理地址 0x07e00 的第一个扇区之后加载第二个扇区。 (0x07e0<<4)+0x0000 = 0x07e00.
bootload.asm
bits 16
ORG 0x7c00 ; Bootloader starts at physical address 0x07c00
; At start bootloader sets DL to boot drive
; Since we specified an ORG(offset) of 0x7c00 we should make sure that
; Data Segment (DS) is set accordingly. The DS:Offset that would work
; in this case is DS=0 . That would map to segment:offset 0x0000:0x7c00
; which is physical memory address (0x0000<<4)+0x7c00 . We can't rely on
; DS being set to what we expect upon jumping to our code so we set it
; explicitly
xor ax, ax
mov ds, ax ; DS=0
cli ; Turn off interrupts for SS:SP update
; to avoid a problem with buggy 8088 CPUs
mov ss, ax ; SS = 0x0000
mov sp, 0x7c00 ; SP = 0x7c00
; We'll set the stack starting just below
; where the bootloader is at 0x0:0x7c00. The
; stack can be placed anywhere in usable and
; unused RAM.
sti ; Turn interrupts back on
reset: ; Resets floppy drive
xor ax,ax ; AH = 0 = Reset floppy disk
int 0x13
jc reset ; If carry flag was set, try again
mov ax,0x07e0 ; When we read the sector, we are going to read to
; address 0x07e0:0x0000 (phys address 0x07e00)
; right after the bootloader in memory
mov es,ax ; Set ES with 0x07e0
xor bx,bx ; Offset to read sector to
floppy:
mov ah,0x2 ; 2 = Read floppy
mov al,0x1 ; Reading one sector
mov ch,0x0 ; Track(Cylinder) 1
mov cl,0x2 ; Sector 2
mov dh,0x0 ; Head 1
int 0x13
jc floppy ; If carry flag was set, try again
jmp 0x07e0:0x0000 ; Jump to 0x7e0:0x0000 setting CS to 0x07e0
; IP to 0 which is code in second stage
; (0x07e0<<4)+0x0000 = 0x07e00 physical address
times 510 - ($ - $$) db 0 ; Fill the rest of sector with 0
dw 0xAA55 ; This is the boot signature
第二阶段将由 INT 13h/AH=2h 在内存中的引导加载程序之后加载,从物理地址 0x07e00 开始。
stage2.asm
BITS 16
; ORG needs to be set to the offset of the far jump used to
; reach us. Jump was 0x7e0:0x0000 so ORG = Offset = 0x0000.
ORG 0x0000
main:
; Set DS = CS
mov ax, cs
mov ds, ax
; Set to graphics mode 0x13 (320x200x256)
mov ax, 13h
int 10h
; Set ES to the VGA video segment at 0xA000
mov ax, 0xa000
mov es, ax
vga:
; Draw pixel in middle of screen
mov ax, [ycoord]
mov bx, [xcoord]
mov cx, 320
mul cx
add ax, bx
mov di, ax
mov dl, [color]
mov [es:di],dl
; Put processor in an endless loop
cli
.endloop:
hlt
jmp .endloop
; Put Data after the code
xcoord DW 160
ycoord DW 100
color DB 0x0D ; One of the magenta shades in VGA palette
上面的代码是您的 VGA 代码的一个稍微改动的版本,因为您的代码有错误。您的 VGA 代码没有正确地将 ES 段设置为 0xA000,这是 VGA 显存的开始;它没有正确地取消引用变量(你使用了它们的地址而不是值);坐标的大小是 BYTE 而不是 WORD;在具有 ASCII 字符值的变量中定义值。我还在代码之后移动了数据。
我修改了代码,在 320x200 显示器中间绘制一个像素,然后无限循环结束程序。
在 Linux 上(或在 Windows 上与 Chrysocome DD)使用 NASM 到 assemble,你可以用这些命令生成一个 720K 的启动盘:
dd if=/dev/zero of=disk.img bs=1024 count=720
nasm -f bin bootload.asm -o bootload.bin
dd if=bootload.bin of=disk.img conv=notrunc
nasm -f bin stage2.asm -o stage2.bin
dd if=stage2.bin of=disk.img bs=512 seek=1 conv=notrunc
这将构建 bootload.bin
并将其放入磁盘映像的第一个扇区,然后构建 stage2.bin
并将其放入磁盘映像的第 2 个扇区。磁盘映像名为 disk.img
。
你应该可以用 QEMU 来测试它,比如:
qemu-system-i386 -fda disk.img
更多关于 DD 用法的信息可以在我的另一个 Whosebug answers 中找到。巧合的是,这个答案是针对你去年的一个问题。
我用汇编写了一个bootloader。 它是这样工作的:
首先,BIOS 正常加载引导加载程序。指示去200h.
在 200h,有一些代码位于 200h 和 21Eh 之间。它只是切换到 VGA 模式,使用 VGA 功能在坐标 1,1 上绘制一个洋红色像素。它会永远循环该代码。
然而,当我加载它时,它只是继续闪烁光标,这是普通 VGA .bin 文件不会做的,并且会显示一个像素。我检查了一个像素,但什么也没看到。我所看到的意味着 VGA 代码不是 运行,并且只是加载了引导加载程序,没有别的。
引导程序代码:
cli
startload:
push 0x00000200
ret
times 510-($-$$) db 0
db 0x55
db 0xAA
你可以看到它只是转到下一个扇区(从 200h 开始) 以及 200h-21Eh 处的代码:
BITS 16
org 200h
data:
xcoord DB '1'
ycoord DB '1'
color DB 'D'
.code:
vga:
mov ax, 13h
int 10h
mov ax, ycoord
mov bx, xcoord
mov cx, 320
mul cx
add ax, bx
mov di, ax
mov dl, color
mov [es:di],dl
jmp vga
(是的,我知道这不是 230h 字节,那是编译输出的大小,即 230h。)
问题是什么? 注意:这里不讨论如何让它进入第二扇区。它在问为什么它不去那里。我还没有找到任何解决方案。
您似乎缺少一些非常重要的代码来实际将第二个扇区读入内存。像这样:
mov ah,0x02 ; read sectors into memory
mov al,0x10 ; number of sectors to read (16)
mov dl,[bootDrive] ; drive number
mov ch,0 ; cylinder number
mov dh,0 ; head number
mov cl,2 ; starting sector number
mov bx,Main ; address to load to
int 0x13 ; call the interrupt routine
这假设您已将 dl
的值保存到标签为 bootDrive
的字节中。显然,您需要更改加载代码的地址。
使用 ORG 0x200
只是将汇编程序配置为处理不可重定位地址引用的生成。
回答你的一个问题。此代码:
startload:
push 0x00000200
ret
几乎相当于 CS:0x200 的绝对跳跃。我们不知道 CS 中的值是什么,但许多 BIOS 将以 CS=0 和 IP=0x7c00 开头(但情况并非总是如此)。你不能真正依赖 CS 是一个特定的值,除非你自己设置它。在大多数情况下,CS 可能为零,这意味着您可能会跳转到物理内存地址 0x00200 (0x0000:0x0200)。这恰好在从物理地址 0x00000 到 0x003FF 运行的实模式中断 table 的中间。跳转到该位置可能会导致某种未定义的行为。
您可以将您的引导加载程序加载到 BOCHS 中,它有一个合理的调试器,可以理解 16 位实模式。您将能够单步执行代码并准确确定 CS 是什么以及它跳转到的位置。
您可能想要完成的任务可以使用以下代码完成。
这是我之前
简而言之,BIOS 从物理内存地址 0x7C00 开始的磁盘读取单个磁盘扇区(512 字节)。如果要加载其他扇区,则必须编写将它们加载到内存中的代码,然后在加载后跳转到该代码。
在此示例中,第一阶段是引导加载程序,它从磁盘的第 2 扇区(引导扇区之后的扇区)加载单个扇区第二阶段。在此示例中,我将通过 segment:offset 对 0x7e0:0x0000 在物理地址 0x07e00 的第一个扇区之后加载第二个扇区。 (0x07e0<<4)+0x0000 = 0x07e00.
bootload.asm
bits 16
ORG 0x7c00 ; Bootloader starts at physical address 0x07c00
; At start bootloader sets DL to boot drive
; Since we specified an ORG(offset) of 0x7c00 we should make sure that
; Data Segment (DS) is set accordingly. The DS:Offset that would work
; in this case is DS=0 . That would map to segment:offset 0x0000:0x7c00
; which is physical memory address (0x0000<<4)+0x7c00 . We can't rely on
; DS being set to what we expect upon jumping to our code so we set it
; explicitly
xor ax, ax
mov ds, ax ; DS=0
cli ; Turn off interrupts for SS:SP update
; to avoid a problem with buggy 8088 CPUs
mov ss, ax ; SS = 0x0000
mov sp, 0x7c00 ; SP = 0x7c00
; We'll set the stack starting just below
; where the bootloader is at 0x0:0x7c00. The
; stack can be placed anywhere in usable and
; unused RAM.
sti ; Turn interrupts back on
reset: ; Resets floppy drive
xor ax,ax ; AH = 0 = Reset floppy disk
int 0x13
jc reset ; If carry flag was set, try again
mov ax,0x07e0 ; When we read the sector, we are going to read to
; address 0x07e0:0x0000 (phys address 0x07e00)
; right after the bootloader in memory
mov es,ax ; Set ES with 0x07e0
xor bx,bx ; Offset to read sector to
floppy:
mov ah,0x2 ; 2 = Read floppy
mov al,0x1 ; Reading one sector
mov ch,0x0 ; Track(Cylinder) 1
mov cl,0x2 ; Sector 2
mov dh,0x0 ; Head 1
int 0x13
jc floppy ; If carry flag was set, try again
jmp 0x07e0:0x0000 ; Jump to 0x7e0:0x0000 setting CS to 0x07e0
; IP to 0 which is code in second stage
; (0x07e0<<4)+0x0000 = 0x07e00 physical address
times 510 - ($ - $$) db 0 ; Fill the rest of sector with 0
dw 0xAA55 ; This is the boot signature
第二阶段将由 INT 13h/AH=2h 在内存中的引导加载程序之后加载,从物理地址 0x07e00 开始。
stage2.asm
BITS 16
; ORG needs to be set to the offset of the far jump used to
; reach us. Jump was 0x7e0:0x0000 so ORG = Offset = 0x0000.
ORG 0x0000
main:
; Set DS = CS
mov ax, cs
mov ds, ax
; Set to graphics mode 0x13 (320x200x256)
mov ax, 13h
int 10h
; Set ES to the VGA video segment at 0xA000
mov ax, 0xa000
mov es, ax
vga:
; Draw pixel in middle of screen
mov ax, [ycoord]
mov bx, [xcoord]
mov cx, 320
mul cx
add ax, bx
mov di, ax
mov dl, [color]
mov [es:di],dl
; Put processor in an endless loop
cli
.endloop:
hlt
jmp .endloop
; Put Data after the code
xcoord DW 160
ycoord DW 100
color DB 0x0D ; One of the magenta shades in VGA palette
上面的代码是您的 VGA 代码的一个稍微改动的版本,因为您的代码有错误。您的 VGA 代码没有正确地将 ES 段设置为 0xA000,这是 VGA 显存的开始;它没有正确地取消引用变量(你使用了它们的地址而不是值);坐标的大小是 BYTE 而不是 WORD;在具有 ASCII 字符值的变量中定义值。我还在代码之后移动了数据。
我修改了代码,在 320x200 显示器中间绘制一个像素,然后无限循环结束程序。
在 Linux 上(或在 Windows 上与 Chrysocome DD)使用 NASM 到 assemble,你可以用这些命令生成一个 720K 的启动盘:
dd if=/dev/zero of=disk.img bs=1024 count=720
nasm -f bin bootload.asm -o bootload.bin
dd if=bootload.bin of=disk.img conv=notrunc
nasm -f bin stage2.asm -o stage2.bin
dd if=stage2.bin of=disk.img bs=512 seek=1 conv=notrunc
这将构建 bootload.bin
并将其放入磁盘映像的第一个扇区,然后构建 stage2.bin
并将其放入磁盘映像的第 2 个扇区。磁盘映像名为 disk.img
。
你应该可以用 QEMU 来测试它,比如:
qemu-system-i386 -fda disk.img
更多关于 DD 用法的信息可以在我的另一个 Whosebug answers 中找到。巧合的是,这个答案是针对你去年的一个问题。