引导加载程序的第二阶段使用 Int 0x10/ah=0x0e 打印垃圾 [英] Second stage of bootloader prints garbage using Int 0x10/ah=0x0e

查看:27
本文介绍了引导加载程序的第二阶段使用 Int 0x10/ah=0x0e 打印垃圾的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试学习汇编和编写引导加载程序.以下代码将软盘驱动器的内容加载到内存并跳转到它(从地址 0x1000 开始加载).这段代码应该在屏幕上打印X",但由于某种原因它打印了一个空格.有人可以告诉我有什么问题吗?

I am trying to learn assembly and to write a bootloader. The following code loads the contents of a floppy drive to memory and jumps to it (starts loading at address 0x1000). This code is supposed to print "X" on the screen, but for some reason it prints a space. Can somebody please tell me what is wrong?

[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

推荐答案

我可以推断出您正在使用 NASM(或 NASM 兼容)汇编程序.我不知道您使用什么操作系统来构建引导加载程序,但我假设使用 Linux 或 Windows.其他环境会有些类似.

I can deduce you are using NASM (or NASM compatible) assembler. I don't know what OS you are using to build the bootloader, but I'll assume Linux or Windows. Other environments would be somewhat similar.

您应该将引导加载程序分成两部分以使其更容易.一个是引导加载程序,第二个是您在 0x1000:0x0000 加载的第二阶段.这使我们能够正确定义引导加载程序的原点.引导加载程序预计在物理地址 0x07c00 处加载,第二阶段在 0x10000 ((0x1000<<4+)+0).我们需要汇编程序为我们的数据和代码正确生成地址.

You should split your bootloader into two parts to make this easier. One is the bootloader, and the second being the second stage you load at 0x1000:0x0000. This allows us to properly define the origin point for our bootloader. The bootloader is expected to be loaded at physical address 0x07c00, and the second stage at 0x10000 ((0x1000<<4+)+0). We need the assembler to properly generate addresses for our data and code.

我写了许多 StackOverflow 答案,描述了我对代码所做的一些更改.几个更相关的是:

I have written a number of StackOverflow answers that describe some of the changes I have made to the code. A couple of the more relevant ones are:

  • General Boot Loader Tips which give general guidelines and assumptions you don't want to make in a bootloader
  • Information on the pitfalls of not setting up DS properly and getting garbage when accessing memory variables. This applies somewhat to your second stage

如果您对 segment:offset 对没有正确的理解,我推荐这个 文章.我提出这一点是因为您的问题和代码中似乎存在混淆.您似乎认为物理内存地址 0x1000 与段:偏移量对 0x1000:0x0000 相同.在您的问题中,您说:

If you don't have a proper understanding of segment:offset pairs I recommend this article. I bring this up because there seems to be confusion in your question and in your code. You seem to think that physical memory address 0x1000 is the same as the segment:offset pair 0x1000:0x0000. In your question you say:

以下代码将软盘驱动器的内容加载到内存并跳转到它(从地址 0x1000 开始加载).

The following code loads the contents of a floppy drive to memory and jumps to it (starts loading at address 0x1000).

在您的代码中有这一行和注释:

In your code you have this line and comment:

jmp 0x1000:0000 ;Jump to 0x1000, start of second program

如果您查看该链接,您会发现 segment:offset 通过将段左移 4 位(乘以十进制 16)然后添加偏移量来计算物理地址.该方程通常显示为 (segment<<4)+offset .在您的情况下 0x1000:0x0000 是 0x1000 和 0x0000 偏移量的一段.使用等式获取内存中的物理地址,您将得到 (0x1000<<4)+0x0000 = 0x10000(不是 0x1000)

If you review that link you'll discover that segment:offset computes to a physical address by shifting the segment left 4 bits (multiply by 16 decimal) and then adding the offset. The equation usually appears as (segment<<4)+offset . In your case 0x1000:0x0000 is a segment of 0x1000 and offset of 0x0000 . Using the equation to get the physical address in memory you'd get (0x1000<<4)+0x0000 = 0x10000 (not 0x1000)

从您的代码中无法判断您是如何使用 NASM 进行组装的.我提供了一个如何完成的示例,但重要的部分是拆分引导加载程序.假设我们将您的引导加载程序放在一个名为 bootload.asm 的文件中:

From your code it isn't possible to tell how you are assembling with NASM. I provide an example of how it could be done, but the important part is splitting the bootloader up. Assume we put your bootloader in a file called 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

你应该注意到我删除了这一行:

You should notice that I removed this line:

mov dl,0x0  ;Drive = 0 (Floppy)

这将引导驱动器硬编码到软盘 A:.如果您从 USB、硬盘驱动器或软盘 B 启动:您的代码将不起作用,因为在这些情况下驱动器编号可能不会为零.BIOS 传递用于加载引导加载程序的实际引导驱动器.该值位于寄存器 DL 中.这是您应该用于 BIOS 磁盘功能的值.由于 DL 已经包含启动驱动器,我们就按原样使用它.

This hard codes the boot drive to the Floppy A:. If you boot off of USB, hard drive, or Floppy B: your code won't work because the drive number likely won't be zero in those cases. The BIOS passes the actual boot drive that was used to load your bootloader. That value is in the register DL. This is the value you should be using for BIOS disk functions. Since DL already contains the boot drive, we just use it as-is.

第二阶段可以这样修改.我假设有一个名为 stage2.asm 的文件:

The second stage can be modified in this way. I'll assume a file called 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 处工作.您正在加载映射到物理地址 0x10000 的 0x1000:0x0000 的第二阶段.我们将 ORG 设置为 0x0000,因为 FAR JUMP jmp 0x1000:0000 将设置 CS=0x1000,并且 IP=0x0000.因为 IP 是 0x0000,我们希望 ORG 匹配它,以便近内存引用相对于我们 64k 段的开头.

I've made no attempt to streamline your code. The idea is to show how to add the glue to fix your issues. Both files specify an origin point using the ORG directive. Bootloaders need to be assembled so that they work at memory address 0x07c00 . You are loading the second stage at 0x1000:0x0000 that maps to physical address 0x10000. We set ORG to 0x0000 since the FAR JUMP jmp 0x1000:0000 will set CS=0x1000, and IP=0x0000 . Because IP is 0x0000 we want ORG to match it so that near memory references are relative to the beginning of our 64k segment.

这将允许汇编器为您的变量和代码生成正确的内存引用.因为您没有在代码中正确执行此操作,所以您的第二阶段读取了 var 的错误内存位置,随后显示了错误字符.

This will allow the assembler to generate proper memory references for your variables and code. Because you didn't properly do this in your code, your second stage was reading the wrong memory location for var and subsequently displayed an incorrect character.

拆分两个文件后,您需要使用 NASM 组合它们,然后将它们放入磁盘映像中.与您的问题不同,我将使用 DD 构建一个 720k 软盘映像,然后将引导加载程序放在开头(不截断磁盘),然后将第二阶段从扇区开始.可以这样完成:

Once you have the 2 files split you need to assemble them with NASM and then place them into a disk image. Unlike your question, I will use DD to build a 720k floppy disk image and then place the bootloader at the beginning (without truncating the disk) and then place the second stage starting at the sector right after. That can be accomplished like this:

# 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 运行这样的图像,例如:

You could run such an image using QEMU with something like:

qemu-system-i386 -fda disk.img 

<小时>

如果使用的是 Windows,并且您无权访问 DD,您可以使用此修改对 stage2.asm:


If using Windows, and you don't have access to DD, you may be able to use this modification to 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 磁盘映像:

And then assemble and build the 720K disk image with these commands:

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 字节.

disk.img would be the 720K disk image that should be usable by QEMU or Bochs. The final size of disk.img should be 737,280 bytes.

如果你想把一个值从一个内存地址移动到一个寄存器中,你可以直接进行,不需要中间寄存器.在你的 stage2.asm 你有这个:

If you want to move a value from a memory address to a register, you can do it directly without an intermediate register. In your stage2.asm you have this:

mov bx, var
mov ah, 0x0e
mov al, [bx]

可以写成:

mov ah, 0x0e
mov al, [var]

这将从内存位置 var 移动一个字节并将其直接移动到 AL .大小由 NASM 决定为一个字节,因为目标 AL 是一个 8 位寄存器.

This would move a single byte from the memory location var and move it directly to AL . The size is determined by NASM to be a byte because the destination AL is an 8-bit register.

这篇关于引导加载程序的第二阶段使用 Int 0x10/ah=0x0e 打印垃圾的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆