英文:
Kernel message not displaying in custom OS: Bootloader or Kernel issue?
问题
我正在构建一个操作系统,并遇到一个问题,即引导加载程序成功打印其消息,但内核的消息未显示出来。以下是一个简要的说明:
设置:
汇编器和环境:
- 使用NASM,使用-f bin选项
- 在qemu-system-i386上进行测试
- 环境:Windows Subsystem for Linux(WSL)与Xlaunch(尽管我怀疑这与问题无关)
快速注释
push ax
push bx
mov bl, dl ; 这是为了保存dl
push bx
这段代码片段是引导加载程序的一部分,最初可能看起来有些混乱。
这里的要求是在整个函数中保留dl寄存器的值。你可能会问:
- 为什么不直接将dl压入堆栈?
- 原因是堆栈只适用于16位值。dl是一个8位寄存器,因此不能直接将其推入堆栈。
- 为什么不使用
push dx
?- 这是因为在这个上下文中,
dh
充当返回值。
- 这是因为在这个上下文中,
为了解决这些限制,我采用了以下方法:
- 将bx推入堆栈。
- 将
dl的值移动到bx的低位
(即bl)。 - 再次将bx(
现在包含dl的值
)推入堆栈。 - 稍后,为了恢复这个值,我只需弹出bx,然后
mov dl, bl
。
文件:
bootloader.asm
[org 0x7c00]
[bits 16]
jmp main
_message_read_err: db 'Error in reading floppy!', 0
_message_boot: db 'Booting SSoS...', 0
_sectors_per_track: dw 18
_head_count: dw 2
_kernel_start_LBA: dw 1
_kernel_size: dw 1
_stack_ptr_addr: dw 0x7c00
_es_start: dw 0x7c0
; 参数:
; 无
; 返回值:
; 无
main:
mov ax, 0 ; 不能直接写入ds和ss
mov ds, ax
mov ss, ax
mov sp, [_stack_ptr_addr] ; 堆栈指针
mov ax, [_es_start] ; 地址 * 16 = 实际地址 -> 0x7c0 * 16(十进制)= 0x7c00
mov es, ax
mov si, _message_boot
call puts
; 将LBA转换为CHS
;; 参数
mov si, 1
call LBA_to_CHS ; 调用后,CH、CL和DH将具有柱面、扇区和磁头的值
mov bx, 0x200
mov al, [_kernel_size] ; 要读取的扇区数
call read_disk
jmp 0:7e00h
; 参数:
; si - 指向要打印的字符串
; 返回值:
; 无
puts:
push ax
.begin:
mov ah, 0x0E
lodsb
cmp al, 0
je .done
int 10h
jmp .begin
.done:
pop ax
ret
; 参数:
; si - LBA地址
; 返回值:
; ch、dh、cl - CHS地址
;; dx和cx必须进行修改
LBA_to_CHS:
push ax
push bx
mov bl, dl ; 这是为了保存dl
push bx
mov ax, [_head_count]
mul word [_sectors_per_track]
mov bx, ax
mov ax, si
mul word bx
mov ch, al ; 将ax的低位作为柱面地址
mov ax, si
div word [_sectors_per_track] ; 结果在dx:ax中
div word [_head_count] ; 结果还在dx:ax中(dx是余数)
mov dh, dl ; 由于dx由dh(高位)和dl(低位)组成,我们希望将低位移到dh中
push dx
mov ax, si
div word [_sectors_per_track] ; 余数在dx中
inc dx
mov cl, dl
pop dx
pop bx
mov dl, bl
pop bx
pop ax
ret
; 参数:
; bx - 要加载数据的地址
; ch、cl、dh - CHS值
; 返回值:
; 无
read_disk:
push ax
push si
mov si, 4
.retry:
dec si
cmp si, 0
je .read_error
mov ah, 02h
mov al, [_kernel_size]
int 13h
jc .retry
pop si
pop ax
ret
.read_error:
mov si, _message_read_err
call puts
cli
hlt
times 510-($-$$) db 0
dw 0aa55h
kernel.asm
[org 0x7E00]
[bits 16]
jmp main
_message: db 'Hello from kernel!', 0
main:
mov si, _message
call puts
cli
hlt
puts:
mov ah, 0x0E
.begin:
lodsb
cmp al, 0
je .done
int 10h
jmp .begin
.done:
ret
Makefile
BUILD_DIR = build
SRC_DIR = src
IMG_NAME = dev
dev: image
qemu-system-i386 -fda $(BUILD_DIR)/$(IMG_NAME).img
image: $(BUILD_DIR)/bootloader.bin $(BUILD_DIR)/kernel.bin
@dd if=$(BUILD_DIR)/bootloader.bin of=$(BUILD_DIR)/$(IMG_NAME).img bs=512
@dd if=$(BUILD_DIR)/kernel.bin of=$(BUILD_DIR)/$(IMG_NAME).img bs=512 seek=1
@truncate -s 1440k $(BUILD_DIR)/$(IMG_NAME).img
@hexdump -C $(BUILD_DIR)/$(IMG_NAME).img
@objdump -b binary -m
<details>
<summary>英文:</summary>
I am building an operating system and am experiencing a problem where the bootloader successfully prints its message, but the kernel's message isn't displayed. Here's a breakdown:
# Setup:
Assembler and Environment:
- NASM with -f bin option
- Tested on qemu-system-i386
- Environment: Windows Subsystem for Linux (WSL) with Xlaunch (though I suspect this isn't relevant)
# Quick Note
push ax
push bx
mov bl, dl ; This is to preserve dl
push bx
This code snippet is part of a bootloader, and it might seem confusing initially.
The requirement here is to preserve the dl register throughout the function. You might ask:
1. Why not just `push dl` onto the stack?
- The reason is the stack only works with 16-bit values. dl is an 8-bit register, so pushing it directly isn't possible.
2. Why not `push dx`?
- This is because `dh` serves as a return value in this context.
To work around these constraints, I took this approach:
- Pushed bx onto the stack.
- Moved the value of `dl into the lower bits of bx` (i.e., bl).
- Pushed bx (`now holding the value of dl`) onto the stack again.
- Later, to restore the value, I simply popped bx and then `mov dl, bl`.
# Files:
*bootloader.asm*
[org 0x7c00]
[bits 16]
jmp main
_message_read_err: db 'Error in reading floppy!', 0
_message_boot: db 'Booting SSoS...', 0
_sectors_per_track: dw 18
_head_count: dw 2
_kernel_start_LBA: dw 1
_kernel_size: dw 1
_stack_ptr_addr: dw 0x7c00
_es_start: dw 0x7c0
; Arguments:
; None
; Returns:
; None
main:
mov ax, 0 ; can't write to ds and ss directly
mov ds, ax
mov ss, ax
mov sp, [_stack_ptr_addr] ; stack pointer
mov ax, [_es_start] ; address * 16 = real address -> 0x7c0 * 16 (dec) = 0x7c00
mov es, ax
mov si, _message_boot
call puts
; Converting LBA to CHS
;; Parameters
mov si, 1
call LBA_to_CHS ; after this call, CH, CL, and DH will have cylinder, sector, and head values
mov bx, 0x200
mov al, [_kernel_size] ; number of sectors to read
call read_disk
jmp 0:7e00h
; Arguments:
; si - Points to the string to be printed
; Returns:
; None
puts:
push ax
.begin:
mov ah, 0x0E
lodsb
cmp al, 0
je .done
int 10h
jmp .begin
.done:
pop ax
ret
; Arguments:
; si - LBA adress
; Returns:
; ch, dh, cl - CHS adress
;; dx and cx will have to be modified
LBA_to_CHS:
push ax
push bx
mov bl, dl ; This is to preserve dl
push bx
mov ax, [_head_count]
mul word [_sectors_per_track]
mov bx, ax
mov ax, si
mul word bx
mov ch, al ; Put lower bits of ax as the cylinder address
mov ax, si
div word [_sectors_per_track] ; result in dx:ax
div word [_head_count] ; result also in dx:ax (dx is remainder)
mov dh, dl ; since dx is composed of dh (higher bits) and dl (lower bits) we want to move the lower bits into dh
push dx
mov ax, si
div word [_sectors_per_track] ; remainder in dx
inc dx
mov cl, dl
pop dx
pop bx
mov dl, bl
pop bx
pop ax
ret
; Arguments:
; bx - Address to load the data
; ch, cl, dh - CHS values
; Returns:
; None
read_disk:
push ax
push si
mov si, 4
.retry:
dec si
cmp si, 0
je .read_error
mov ah, 02h
mov al, [_kernel_size]
int 13h
jc .retry
pop si
pop ax
ret
.read_error:
mov si, _message_read_err
call puts
cli
hlt
times 510-($-$$) db 0
dw 0aa55h
*kernel.asm*
[org 0x7E00]
[bits 16]
jmp main
_message: db 'Hello from kernel!', 0
main:
mov si, _message
call puts
cli
hlt
puts:
mov ah, 0x0E
.begin:
lodsb
cmp al, 0
je .done
int 10h
jmp .begin
.done:
ret
*Makefile*
BUILD_DIR = build
SRC_DIR = src
IMG_NAME = dev
dev: image
qemu-system-i386 -fda $(BUILD_DIR)/$(IMG_NAME).img
image: $(BUILD_DIR)/bootloader.bin $(BUILD_DIR)/kernel.bin
@dd if=$(BUILD_DIR)/bootloader.bin of=$(BUILD_DIR)/$(IMG_NAME).img bs=512
@dd if=$(BUILD_DIR)/kernel.bin of=$(BUILD_DIR)/$(IMG_NAME).img bs=512 seek=1
@truncate -s 1440k $(BUILD_DIR)/$(IMG_NAME).img
@hexdump -C $(BUILD_DIR)/$(IMG_NAME).img
@objdump -b binary -m i8086 -M intel -D build/dev.img
clean:
rm -f $(BUILD_DIR)/bootloader.bin $(BUILD_DIR)/kernel.bin
$(BUILD_DIR)/%.bin: $(SRC_DIR)/%.asm
nasm $^ -f bin -o $@
# Specifics on Constants in bootloader.asm:
- _stack_ptr_addr: Set to 0x7b00 because it's 256 bytes under the bootloader. I believe this is a safe location since the stack grows downwards.
- _es_start: Set to 0x7c0 so the actual physical address (in real mode) equals 0x7c00 — the bootloader's load address. I offset by 0x200 when jumping to the kernel, which matches the kernel's load address.
[![Disassembly of the image](https://i.stack.imgur.com/xaNai.png)](https://i.stack.imgur.com/xaNai.png) for clarity shows that the first instruction appears at byte 0x200.
# The issue:
Upon booting, the message "Booting SSoS..." is displayed, but the kernel's "Hello from kernel!" message isn't.
# Observations & Assumptions:
Someone also had the same problem [here](https://stackoverflow.com/questions/26567810/trouble-with-custom-oss-bootloader-not-jumping-to-kernel). I tried removing the `mov dl, 0` in the read_disk function but it still didin't work.
I suspect the issue might be in the read_disk function within the bootloader.
Even though no error message displays, the qemu-system-i386 command seems unusual.
Constants like _sectors_per_track and drive number (register dl in read_disk function) seem accurate. However, even when I attempted different disk-reading methods, the problem was still occurring.
# Seeking Help:
I know this is a complex and low-level topic which requires a deep understanding of operating systems. That's why I thank everyone who will try to help me. Thanks a lot!
I tried different methods of reading the floppy disk (like reading directly with CHS adressing) however none of them worked. I also double-checked my constants and looked on the disassebly of the image but everything seems to be right. I looked into others code but mine seems (almost except it's not that perfect) identical.
</details>
# 答案1
**得分**: 1
下面是您的代码的翻译部分:
1. 我在这里和那里添加了一些 `xor dx, dx`。
2. `mul word bx` 应该改成 `div word bx`。
3. 在 kernel.asm 中,将第一行更改回旧值 `0x7e00`。
4. `push` 和 `pop` 指令没有特殊作用。您可以将它们删除。
5. 我不确定我是否正确理解了 LBA 到 CHS 的转换。应该考虑什么?ax 或 dx 中的值?
Bootloader.asm
```assembly
[org 0x7c00]
[bits 16]
jmp main
_message_read_err: db 'Error in reading floppy!', 0
_message_boot: db 'Booting SSoS...', 0
_sectors_per_track: dw 18
_head_count: dw 2
_kernel_start_LBA: dw 1
_kernel_size: dw 1
_stack_ptr_addr: dw 0x7c00 ;prev value 0x7b00
_es_start: dw 0x7c0
; 参数:
; 无
; 返回:
; 无
main:
mov ax, 0 ; 不能直接写入 ds 和 ss
mov ds, ax
mov ss, ax
mov sp, [_stack_ptr_addr] ; 栈指针
mov ax, [_es_start] ; 地址 * 16 = 真实地址 - 0x7c0 * 16(十进制) = 0x7c00
mov es, ax
mov si, _message_boot
call puts
; 将 LBA 转换为 CHS
;; 参数
;(* 1 *)
;; 在调用 LBA_to_CHS 之前
;; ax = 0x07c0
;; bx = 未知值,可以是 16 位范围内的任何值(0x0000 - 0xFFFF)
;; cx = 同样的 cx,未初始化
;; dx = 同样的情况
;; 未知的值可能会导致意外的结果;)
;; 在 LBA_to_CHS 内部,您 push ax / push bx / mov bl, dl / push bx
;; 在从 LBA_to_CHS 返回之前,您弹出这些值
;; 所以在过程之后:
;; ax = 弹出的值 0x07c0,然后 al = _kernel_size = 1,ah = 0x07 在 read_disk 内部更改为 0x02
;; bx = 未知值,但后来设置为 0x0200
;; cx = ch - 柱面,cl - 扇区
;; dx = dh - 磁头,dl - 弹出的值,您确定这是驱动器号(0 = A :) 吗?
mov si, 1 ;LBA = si = 1
call LBA_to_CHS ; 调用后,CH、CL 和 DH 将具有柱面、扇区和磁头值
mov bx, 0x200
mov al, [_kernel_size] ; 要读取的扇区数
call read_disk
jmp 0:7e00h
; 参数:
; si - 指向要打印的字符串
; 返回:
; 无
puts:
push ax
.begin:
mov ah, 0x0E
lodsb
cmp al, 0
je .done
int 10h
jmp .begin
.done:
pop ax
ret
; 参数:
; si - LBA 地址
; 返回:
; ch, dh, cl - CHS 地址
;; dx 和 cx 将需要修改
LBA_to_CHS:
push ax ; push ax 和 push bx 不会破坏任何必要的数据
push bx
mov bl, dl ; dl = 垃圾值
push bx ; 删除这个
; C = LBA ÷ (HPC × SPT)
mov ax, [_head_count] ; ax = 2
mul word [_sectors_per_track] ; ax = 2 * 18 = 36
mov bx, ax ; bx = 36
mov ax, si ; ax = 1
xor dx,dx ; dx = 0
;(* 2 *)
; 在这里,您进行了 mul word bx,所以我将其更改为 div word bx
; 我们除以 LBA mod(磁头数×扇区数),而不是相乘
;mul word bx
;应该是 div word bx
div word bx ; ax = 0 dx = 1
mov ch, al ; 将 ax 的低位作为柱面地址
;(* 3 *)
; 在 div 指令之前,我使用 xor dx, dx,因为在 16 位除法中,被除数在 dx:ax 中
; 所以我们必须清除 dx,否则我们的结果可能是错误的值
; H = (LBA ÷ SPT) mod HPC
xor dx, dx ; dx = 0
mov ax, si ; ax = 1
div word [_sectors_per_track] ; 18;结果在 dx:ax 中,ax = 0 dx = 1
xor dx, dx
div word [_head_count] ; 2 ;结果也在 dx:ax 中(dx 是余数) ax = 0 dx = 0
mov dh, dl ; 由于 dx 由 dh(高位)和 dl(低位)组成,我们要将低位移入 dh
push dx ; 保存 dx
; S = (LBA mod SPT) + 1
xor dx, dx ; dx = 0
mov ax, si ; ax = 1
div word [_sectors_per_track] ; 18;余数在 dx 中,ax = 0 dx = 1
inc dx ; dx = 2
mov cl, dl
pop dx
pop bx ; 删除
mov dl, bl ; dl = 垃圾
pop bx ; 删除
pop ax ; 删除
ret
; 参数:
;
<details>
<summary>英文:</summary>
Update:
Below is Your code:
1. I added few `xor dx,dx` here and there
2. `mul word bx` should be `div word bx`
3. Changed in kernel.asm first line to old value `0x7e00`.
4. `push` and `pop` instructions do nothing special. You can remove them.
5. I don't know if I understand conversion LBA -> CHS correctly.
What take into account? Values from ax or dx?
Source:<br>
https://datacadamia.com/io/drive/lba <br>
https://en.wikipedia.org/wiki/Logical_block_addressing
But code seems to work fine!
Result is:
[![enter image description here][1]][1]
Bootloader.asm
[org 0x7c00]
[bits 16]
jmp main
_message_read_err: db 'Error in reading floppy!', 0
_message_boot: db 'Booting SSoS...', 0
_sectors_per_track: dw 18
_head_count: dw 2
_kernel_start_LBA: dw 1
_kernel_size: dw 1
_stack_ptr_addr: dw 0x7c00 ;prev value 0x7b00
_es_start: dw 0x7c0
; Arguments:
; None
; Returns:
; None
main:
mov ax, 0 ; can't write to ds and ss directly
mov ds, ax
mov ss, ax
mov sp, [_stack_ptr_addr] ; stack pointer
mov ax, [_es_start] ; address * 16 = real address -> 0x7c0 * 16 (dec) = 0x7c00
mov es, ax
mov si, _message_boot
call puts
; Converting LBA to CHS
;; Parameters
;(* 1 *)
;; before call to LBA_to_CHS
;; ax = 0x07c0
;; bx = unknown value, could be anything from 16-bit range (0x0000 - 0xFFFF)
;; cx = same cx, not initialized
;; dx = same here
;; unknown values could cause unexpected results ;)
;; Inside LBA_to_CHS You push ax / push bx / mov bl, dl / push bx
;; and before return from LBA_to_CHS You pop those values
;; So after procedure:
;; ax = popped value 0x07c0, then al = _kernel_size = 1, ah = 0x07 changed inside read_disk to 0x02
;; bx = unknown value but set later to 0x0200
;; cx = ch - cylinder, cl - sector
;; dx = dh - head, dl - popped value, are you sure this is drive number (0 = A:)
mov si, 1 ;LBA = si = 1
call LBA_to_CHS ; after this call, CH, CL, and DH will have cylinder, sector, and head values
mov bx, 0x200
mov al, [_kernel_size] ; number of sectors to read
call read_disk
jmp 0:7e00h
; Arguments:
; si - Points to the string to be printed
; Returns:
; None
puts:
push ax
.begin:
mov ah, 0x0E
lodsb
cmp al, 0
je .done
int 10h
jmp .begin
.done:
pop ax
ret
; Arguments:
; si - LBA adress
; Returns:
; ch, dh, cl - CHS adress
;; dx and cx will have to be modified
LBA_to_CHS:
push ax ; push ax and push bx won't destroy any necessary data
push bx
mov bl, dl ; dl = trash values
push bx ; delete this too
; C = LBA ÷ (HPC × SPT)
mov ax, [_head_count] ; ax = 2
mul word [_sectors_per_track] ; ax = 2 * 18 = 36
mov bx, ax ; bx = 36
mov ax, si ; ax = 1
xor dx,dx ; dx = 0
;(* 2 )
; here You have mul word bx so I changed this to div word bx
; we divide LBA mod (Heads * Sectors) not mul
;mul word bx
;should be div word bx
div word bx ; ax = 0 dx = 1
mov ch, al ; Put lower bits of ax as the cylinder address
;( 3 *)
; I used xor dx, dx before div instructions because in 16-bit division dividend is in dx:ax
; so we have to clear dx or our result maybe wrong value
; H = (LBA ÷ SPT) mod HPC
xor dx, dx ; dx = 0
mov ax, si ; ax = 1
div word [_sectors_per_track] ; 18 ; result in dx:ax, ax = 0 dx = 1
xor dx, dx
div word [_head_count] ; 2 ; result also in dx:ax (dx is remainder) ax = 0 dx = 0
mov dh, dl ; since dx is composed of dh (higher bits) and dl (lower bits) we want to move the lower bits into dh
push dx ; save dx
; S = (LBA mod SPT) + 1
xor dx, dx ; dx = 0
mov ax, si ; ax = 1
div word [_sectors_per_track] ; 18 ; remainder in dx, ax = 0 dx = 1
inc dx ; dx = 2
mov cl, dl
pop dx
pop bx ; delete
mov dl, bl ; dl = trash
pop bx ; delete
pop ax ; delete
ret
; Arguments:
; bx - Address to load the data
; ch, cl, dh - CHS values
; Returns:
; None
read_disk:
push ax
push si
mov si, 4
.retry:
dec si
cmp si, 0
je .read_error
mov ah, 02h
mov al, [_kernel_size]
int 13h
jc .retry
pop si
pop ax
ret
.read_error:
mov si, _message_read_err
call puts
cli
hlt
times 510-($-$$) db 0
dw 0aa55h
Kernel.asm
[org 0x7e00]
[bits 16]
jmp main
_message: db 'Hello from kernel!', 0
main:
mov si, _message
call puts
cli
hlt
puts:
mov ah, 0x0E
.begin:
lodsb
cmp al, 0
je .done
int 10h
jmp .begin
.done:
ret
[1]: https://i.stack.imgur.com/q1p2S.png
[2]: https://i.stack.imgur.com/D9AWu.png
[3]: https://i.stack.imgur.com/4ME1N.png
[4]: https://i.stack.imgur.com/2NQdK.png
</details>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论