我正在尝试学习汇编并编写引导程序.以下代码将软盘驱动器的内容加载到内存并跳转到它(开始在地址0x1000加载).此代码应该在屏幕上打印"X",但由于某种原因,它会打印一个空格.有人可以告诉我有什么问题吗?
[bits 16] jmp reset reset: ;Resets floppy drive xor ax,ax ;0 = Reset floppy disk mov dl,0 ;Drive 0 is floppy int 0x13 jc reset ;If carry flag was set, try again mov ax,0x1000 ;When we read the sector, we are going to read address 0x1000 mov es,ax ;Set ES with 0x1000 floppy: mov ah,0x2 ;2 = Read floppy mov al,0x11 ;Reading one sector mov ch,0x0 ;Track 1 mov cl,0x2 ;Sector 2, track 1 mov dh,0x0 ;Head 1 mov dl,0x0 ;Drive = 0 (Floppy) int 0x13 jc floppy ;If carry flag was set, try again jmp 0x1000:0000 ;Jump to 0x1000, start of second program times 510 - ($ - $$) db 0 ;Fill the rest of sector with 0 dw 0xAA55 ;This is the boot signiture ;--- ;--[segment 2]-- mov bx, var mov ah, 0x0e mov al, [bx] int 0x10 jmp $ var: db 'X' times 737280 - ($ - $$) db 0
Michael Petc.. 7
我可以推断您使用的是NASM(或NASM兼容)汇编程序.我不知道你用什么操作系统来构建引导加载程序,但我会假设Linux或Windows.其他环境有点类似.
您应该将引导加载程序分成两部分以使其更容易.一个是引导加载程序,第二个是加载到0x1000:0x0000的第二个阶段.这允许我们正确定义引导加载程序的原点.预计引导加载程序将加载到物理地址0x07c00,第二级加载到0x10000((0x1000 << 4 +)+ 0).我们需要汇编程序为我们的数据和代码正确生成地址.
我编写了许多StackOverflow答案,描述了我对代码所做的一些更改.一些更相关的是:
常规引导加载程序提示,提供您不希望在引导加载程序中进行的一般准则和假设
有关未正确设置DS并在访问内存变量时获取垃圾的缺陷的信息.这适用于你的第二阶段
如果您对段:偏移对没有正确的理解,我推荐这篇文章.我提出这个问题是因为您的问题和代码中似乎存在混淆.您似乎认为物理内存地址0x1000与段:偏移对0x1000:0x0000相同.在你的问题中你说:
以下代码将软盘驱动器的内容加载到内存并跳转到它(开始在地址0x1000加载).
在您的代码中,您有这一行和评论:
jmp 0x1000:0000 ;Jump to 0x1000, start of second program
如果您查看该链接,您将发现该段:offset通过将段左移4位(乘以16十进制)然后添加偏移来计算物理地址.该等式通常表现为(段<< 4)+偏移.在你的情况下,0x1000:0x0000是一个0x1000的段和0x0000的偏移量.使用公式得到内存中的物理地址(0x1000 << 4)+ 0x0000 = 0x10000(不是0x1000)
从您的代码中无法告诉您如何使用NASM进行组装.我提供了一个如何完成它的例子,但重要的是分割引导加载程序.假设我们将您的bootloader放在一个名为的文件中bootload.asm
:
[bits 16] [ORG 0x7c00] ; Bootloader starts at physical address 0x07c00 ; BIOS sets DL to boot drive before jumping to the bootloader ; 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 ; 0 = Reset floppy disk int 0x13 jc reset ; If carry flag was set, try again mov ax,0x1000 ; When we read the sector, we are going to read address 0x1000 mov es,ax ; Set ES with 0x1000 floppy: xor bx,bx ;Ensure that the buffer offset is 0! mov ah,0x2 ;2 = Read floppy mov al,0x1 ;Reading one sector mov ch,0x0 ;Track 1 mov cl,0x2 ;Sector 2, track 1 mov dh,0x0 ;Head 1 int 0x13 jc floppy ;If carry flag was set, try again jmp 0x1000:0000 ;Jump to 0x1000, start of second program times 510 - ($ - $$) db 0 ;Fill the rest of sector with 0 dw 0xAA55 ;This is the boot signature
你应该注意到我删除了这一行:
mov dl,0x0 ;Drive = 0 (Floppy)
这硬盘将引导驱动器编码到软盘A:.如果您从USB,硬盘驱动器或软盘B启动:您的代码将无法运行,因为在这些情况下驱动器号可能不会为零.BIOS传递用于加载引导加载程序的实际引导驱动器.该值在寄存器DL中.这是您应该用于BIOS磁盘功能的值.由于DL已包含启动驱动器,因此我们只是按原样使用它.
可以用这种方式修改第二阶段.我假设一个名为的文件stage2.asm
:
[BITS 16] [ORG 0x0000] ; This code is intended to be loaded starting at 0x1000:0x0000 ; Which is physical address 0x10000. ORG represents the offset ; from the beginning of our segment. ; Our bootloader jumped to 0x1000:0x0000 which sets CS=0x1000 and IP=0x0000 ; We need to manually set the DS register so it can properly find our variables ; like 'var' mov ax, cs mov ds, ax ; Copy CS to DS (we can't do it directly so we use AX temporarily) mov bx, var mov ah, 0x0e mov al, [bx] xor bh, bh ; BH = 0 = Display on text mode page 0 int 0x10 jmp $ var: db 'X'
我没有尝试简化你的代码.我们的想法是展示如何添加胶水来解决您的问题.两个文件都使用该ORG
指令指定原点.需要组装引导加载程序,以便它们在内存地址0x07c00下工作.您正在加载第二阶段0x1000:0x0000,映射到物理地址0x10000.我们将ORG设置为0x0000,因为FAR JUMP jmp 0x1000:0000
将设置CS = 0x1000,并且IP = 0x0000.因为IP是0x0000,我们希望ORG匹配它,以便近存储器引用相对于我们的64k段的开头.
这将允许汇编程序为您的变量和代码生成适当的内存引用.因为您在代码中没有正确执行此操作,所以您的第二阶段是读取错误的内存位置var
,然后显示不正确的字符.
分割完2个文件后,需要将它们与NASM组合在一起,然后将它们放入磁盘映像中.与您的问题不同,我将使用DD构建720k软盘映像,然后将引导加载程序放在开头(不截断磁盘),然后将第二个阶段从扇区开始.这可以这样完成:
# Assemble both components as binary images with NASM nasm -f bin bootload.asm -o bootload.bin nasm -f bin stage2.asm -o stage2.bin # Create a 720k disk image dd if=/dev/zero of=disk.img bs=1024 count=720 # Place bootload.bin at the beginning of disk.img without truncating dd if=bootload.bin of=disk.img conv=notrunc # Place stage2.bin starting at the second 512byte sector and write # it without truncating the disk image. bs=512 seek=1 will skip the # first 512 byte sector and start writing stage2.bin there. dd if=stage2.bin of=disk.img bs=512 seek=1 conv=notrunc
您可以使用QEMU运行这样的图像:
qemu-system-i386 -fda disk.img
如果使用Windows,并且您无权访问DD,则可以使用此修改来stage2.asm
:
[BITS 16] [ORG 0x0000] ; This code is intended to be loaded starting at 0x1000:0x0000 ; Which is physical address 0x10000. ORG represents the offset ; from the beginning of our segment. ; Our bootloader jumped to 0x1000:0x0000 which sets CS=0x1000 and IP=0x0000 ; We need to manually set the DS register so it can properly find our variables ; like 'var' mov ax, cs mov ds, ax ; Copy CS to DS (we can't do it directly so we use AX temporarily) mov bx, var mov ah, 0x0e mov al, [bx] xor bh, bh ; BH = 0 = Display on text mode page 0 int 0x10 jmp $ var: db 'X' ; Extend the second stage to (720K - 512 bytes) ; bootload.bin will take up first 512 bytes times 737280 - 512 - ($ - $$) db 0
然后使用以下命令组装和构建720K磁盘映像:
nasm -f bin bootload.asm -o bootload.bin nasm -f bin stage2.asm -o stage2.bin copy /b bootload.bin+stage2.bin disk.img
disk.img
将是QEMU或Bochs应该可以使用的720K磁盘映像.最终大小disk.img
应为737,280字节.
如果要将值从存储器地址移动到寄存器,可以直接在没有中间寄存器的情况下进行.在你的stage2.asm
你有这样的:
mov bx, var mov ah, 0x0e mov al, [bx]
它可以写成:
mov ah, 0x0e mov al, [var]
这将从存储器位置var移动单个字节并将其直接移动到AL.由NASM确定的大小是一个字节,因为目标AL是一个8位寄存器.
我可以推断您使用的是NASM(或NASM兼容)汇编程序.我不知道你用什么操作系统来构建引导加载程序,但我会假设Linux或Windows.其他环境有点类似.
您应该将引导加载程序分成两部分以使其更容易.一个是引导加载程序,第二个是加载到0x1000:0x0000的第二个阶段.这允许我们正确定义引导加载程序的原点.预计引导加载程序将加载到物理地址0x07c00,第二级加载到0x10000((0x1000 << 4 +)+ 0).我们需要汇编程序为我们的数据和代码正确生成地址.
我编写了许多StackOverflow答案,描述了我对代码所做的一些更改.一些更相关的是:
常规引导加载程序提示,提供您不希望在引导加载程序中进行的一般准则和假设
有关未正确设置DS并在访问内存变量时获取垃圾的缺陷的信息.这适用于你的第二阶段
如果您对段:偏移对没有正确的理解,我推荐这篇文章.我提出这个问题是因为您的问题和代码中似乎存在混淆.您似乎认为物理内存地址0x1000与段:偏移对0x1000:0x0000相同.在你的问题中你说:
以下代码将软盘驱动器的内容加载到内存并跳转到它(开始在地址0x1000加载).
在您的代码中,您有这一行和评论:
jmp 0x1000:0000 ;Jump to 0x1000, start of second program
如果您查看该链接,您将发现该段:offset通过将段左移4位(乘以16十进制)然后添加偏移来计算物理地址.该等式通常表现为(段<< 4)+偏移.在你的情况下,0x1000:0x0000是一个0x1000的段和0x0000的偏移量.使用公式得到内存中的物理地址(0x1000 << 4)+ 0x0000 = 0x10000(不是0x1000)
从您的代码中无法告诉您如何使用NASM进行组装.我提供了一个如何完成它的例子,但重要的是分割引导加载程序.假设我们将您的bootloader放在一个名为的文件中bootload.asm
:
[bits 16] [ORG 0x7c00] ; Bootloader starts at physical address 0x07c00 ; BIOS sets DL to boot drive before jumping to the bootloader ; 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 ; 0 = Reset floppy disk int 0x13 jc reset ; If carry flag was set, try again mov ax,0x1000 ; When we read the sector, we are going to read address 0x1000 mov es,ax ; Set ES with 0x1000 floppy: xor bx,bx ;Ensure that the buffer offset is 0! mov ah,0x2 ;2 = Read floppy mov al,0x1 ;Reading one sector mov ch,0x0 ;Track 1 mov cl,0x2 ;Sector 2, track 1 mov dh,0x0 ;Head 1 int 0x13 jc floppy ;If carry flag was set, try again jmp 0x1000:0000 ;Jump to 0x1000, start of second program times 510 - ($ - $$) db 0 ;Fill the rest of sector with 0 dw 0xAA55 ;This is the boot signature
你应该注意到我删除了这一行:
mov dl,0x0 ;Drive = 0 (Floppy)
这硬盘将引导驱动器编码到软盘A:.如果您从USB,硬盘驱动器或软盘B启动:您的代码将无法运行,因为在这些情况下驱动器号可能不会为零.BIOS传递用于加载引导加载程序的实际引导驱动器.该值在寄存器DL中.这是您应该用于BIOS磁盘功能的值.由于DL已包含启动驱动器,因此我们只是按原样使用它.
可以用这种方式修改第二阶段.我假设一个名为的文件stage2.asm
:
[BITS 16] [ORG 0x0000] ; This code is intended to be loaded starting at 0x1000:0x0000 ; Which is physical address 0x10000. ORG represents the offset ; from the beginning of our segment. ; Our bootloader jumped to 0x1000:0x0000 which sets CS=0x1000 and IP=0x0000 ; We need to manually set the DS register so it can properly find our variables ; like 'var' mov ax, cs mov ds, ax ; Copy CS to DS (we can't do it directly so we use AX temporarily) mov bx, var mov ah, 0x0e mov al, [bx] xor bh, bh ; BH = 0 = Display on text mode page 0 int 0x10 jmp $ var: db 'X'
我没有尝试简化你的代码.我们的想法是展示如何添加胶水来解决您的问题.两个文件都使用该ORG
指令指定原点.需要组装引导加载程序,以便它们在内存地址0x07c00下工作.您正在加载第二阶段0x1000:0x0000,映射到物理地址0x10000.我们将ORG设置为0x0000,因为FAR JUMP jmp 0x1000:0000
将设置CS = 0x1000,并且IP = 0x0000.因为IP是0x0000,我们希望ORG匹配它,以便近存储器引用相对于我们的64k段的开头.
这将允许汇编程序为您的变量和代码生成适当的内存引用.因为您在代码中没有正确执行此操作,所以您的第二阶段是读取错误的内存位置var
,然后显示不正确的字符.
分割完2个文件后,需要将它们与NASM组合在一起,然后将它们放入磁盘映像中.与您的问题不同,我将使用DD构建720k软盘映像,然后将引导加载程序放在开头(不截断磁盘),然后将第二个阶段从扇区开始.这可以这样完成:
# Assemble both components as binary images with NASM nasm -f bin bootload.asm -o bootload.bin nasm -f bin stage2.asm -o stage2.bin # Create a 720k disk image dd if=/dev/zero of=disk.img bs=1024 count=720 # Place bootload.bin at the beginning of disk.img without truncating dd if=bootload.bin of=disk.img conv=notrunc # Place stage2.bin starting at the second 512byte sector and write # it without truncating the disk image. bs=512 seek=1 will skip the # first 512 byte sector and start writing stage2.bin there. dd if=stage2.bin of=disk.img bs=512 seek=1 conv=notrunc
您可以使用QEMU运行这样的图像:
qemu-system-i386 -fda disk.img
如果使用Windows,并且您无权访问DD,则可以使用此修改来stage2.asm
:
[BITS 16] [ORG 0x0000] ; This code is intended to be loaded starting at 0x1000:0x0000 ; Which is physical address 0x10000. ORG represents the offset ; from the beginning of our segment. ; Our bootloader jumped to 0x1000:0x0000 which sets CS=0x1000 and IP=0x0000 ; We need to manually set the DS register so it can properly find our variables ; like 'var' mov ax, cs mov ds, ax ; Copy CS to DS (we can't do it directly so we use AX temporarily) mov bx, var mov ah, 0x0e mov al, [bx] xor bh, bh ; BH = 0 = Display on text mode page 0 int 0x10 jmp $ var: db 'X' ; Extend the second stage to (720K - 512 bytes) ; bootload.bin will take up first 512 bytes times 737280 - 512 - ($ - $$) db 0
然后使用以下命令组装和构建720K磁盘映像:
nasm -f bin bootload.asm -o bootload.bin nasm -f bin stage2.asm -o stage2.bin copy /b bootload.bin+stage2.bin disk.img
disk.img
将是QEMU或Bochs应该可以使用的720K磁盘映像.最终大小disk.img
应为737,280字节.
如果要将值从存储器地址移动到寄存器,可以直接在没有中间寄存器的情况下进行.在你的stage2.asm
你有这样的:
mov bx, var mov ah, 0x0e mov al, [bx]
它可以写成:
mov ah, 0x0e mov al, [var]
这将从存储器位置var移动单个字节并将其直接移动到AL.由NASM确定的大小是一个字节,因为目标AL是一个8位寄存器.