程序集 32 位保护模式,标签未指向定义的字符串?
Assembly 32 bit-Protected Mode, label not pointing to defined string?
我正在尝试学习一些 x86 程序集。我已经成功地创建了一个带有引导加载程序的 MBR,加载了另一个扇区,切换到保护模式并执行了远跳转到加载的扇区。
使用环境
我在 64 位 Windows 安装上使用 NASM,并使用 nasm -s -f bin bootloader.asm -o test.img
进行组装。我将 test.img
文件作为软盘加载到 VirtualBox VM 中。
问题
我写了一个子程序(代码部分的子程序定义),它使用 lodsb
从 ESI 加载字符,我之前使用 mov esi, _LOW_KERNEL_MESSAGES_TEST
指向一个标签(其中 _LOW_KERNEL_MESSAGES_TEST
是定义为 db "Hello, World!", 0
) 的标签。但是,这不会打印任何内容。
预期打印第一个字符'H'的mov al, [_LOW_KERNEL_MESSAGES_TEST]
也没有(字符打印子例程将AL
作为字符输入)
通过mov al, 't'
直接将一个字符移动到AL
中,然后调用单字符打印子程序就可以了,我也测试了偏移并成功打印了'Test'这个词,问题仅在于打印的“将标签移动到 esi 然后将其加载到 al”部分
我试过的
- 用我在引导加载程序中找到的各种常量偏移标签
- 打印第一个字符(如
mov al, [_LOW_KERNEL_MESSAGES_TEST]
] 所述
- 打印子程序的注释部分
- 修改bootloader中的远跳转和GDT
- 使用
popad
而不是 popa
(认为 AX
寄存器被修改了?)
密码
应以下评论者的要求,我尝试创建一个最小的可重现示例,其代码可在此处找到:
这可以通过使用进一步组装:
nasm -s -f bin <name of the code file here>.asm -o floppy.img
通过将此 floppy.img 文件绑定到 VirtualBox 6.1 VM 中的软盘控制器
和运行它,一个完全黑的屏幕出现在左上角的白色光标和虚拟机停止,并显示“发生严重错误”消息,错误日志没有多大意义对我来说可能会有帮助,可以在 https://pastebin.com/g1C2xf7V
找到
我的文件:
bootloader.asm:
%define KERNEL_LOAD_POSITION 0x1000
[bits 16]
[org 0x0600]
;***********************************************************;
; ENTRY SECTION ;
;***********************************************************;
__ENTRY: ; ENTRY POINT
cli ; Clear Interrupts
xor ax, ax ; Zero out AX (set to 0)
mov ds, ax ; Set Data Segment to 0 (AX)
mov es, ax ; Set Extra Segment to 0 (AX)
mov ss, ax ; Set Stack Segment to 0 (AX)
mov sp, ax ; Set Stack Pointer to 0 (AX)
__SET__LOWER__ENTRY:
mov cx, 0x0100 ; 256 WORDs in MBR
mov si, 0x7C00 ; Current MBR Address
mov di, 0x0600 ; New MBR Address
rep movsw ; Copy MBR
jmp 0:__LOWER_ENTRY ; Jump to new Address
__LOWER_ENTRY:
sti
mov al, 1
call _BOOTLOADER_READSECTORS
call _BOOTLOADER_ENTER_PROTECTED
;***********************************************************;
; FUNCTION SECTION ;
;***********************************************************;
_BOOTLOADER_READSECTORS:
pusha
mov ah, 02h
mov ch, 0x0
mov cl, 0x02
mov dh, 0x0
mov dl, 0x0
mov bx, KERNEL_LOAD_POSITION
int 13h
jc _BOOTLOADER_HANG
popa
ret
_BOOTLOADER_HANG:
jmp $
ret
_BOOTLOADER_ENTER_PROTECTED:
cli
lgdt [GDTR]
pusha
mov eax, cr0
or al, 1
mov cr0, eax
popa
jmp 0x08:_BOOTLOADER_PERFORM_PROTECTED_FAR_JUMP
[bits 32]
_BOOTLOADER_PERFORM_PROTECTED_FAR_JUMP:
mov ax, 0x10
mov ds, ax
mov ss, ax
mov fs, ax
mov es, ax
mov gs, ax
jmp KERNEL_LOAD_POSITION
;***********************************************************;
; DATA SECTION ;
;***********************************************************;
;***********************************************************;
; GDT SECTION ;
;***********************************************************;
GDT:
GDT_NULL_DESC:
dd 0 ; null descriptor
dd 0
GDT_CODE_DESC:
dw 0xFFFF ; limit low
dw 0 ; base low
db 0 ; base middle
db 10011010b ; access
db 11001111b ; granularity
db 0 ; base high
GDT_DATA_DESC:
dw 0xFFFF ; data descriptor
dw 0 ; limit low
db 0 ; base low
db 10010010b ; access
db 11001111b ; granularity
db 0 ; base high
GDTR:
Limit dw 24 ; length of GDT
Base dd GDT_NULL_DESC ; base of GDT
;***********************************************************;
; PADDING ;
;***********************************************************;
times (440 - ($-$$)) db 0 ; Pad For MBR Partition Table
;***********************************************************;
; NON-CODE SECTION ;
;***********************************************************;
UID: times 4 db 0 ; Unique Disk ID
Reserved: times 2 db 0 ; Reserved, either 0x0000 (normal) or 0x5a5a (readonly)
;***********************************************************;
; PARTITION TABLE SECTION ;
;***********************************************************;
PT1: ; First Partition Entry ( KERNEL HERE )
db 0x80 ; Bootable ( Active ) ( 8 bits )
db 0x00 ; Starting Head ( Start at head 0 ) ( 8 bits )
db 0b00000010 ; Starting Sector ( Start at Sector 2 ) ( 6 bits ) NOTE: 2 LSB are for the following cylinder field
db 0b00000000 ; Starting Cylinder ( Start at Cylinder 0 ) ( 10 bits )
db 0x21 ; System ID ( Filysystem ) ( 21, Reserved )
db 0x00 ; Ending Head ( End at head 0 ) ( 8 bits )
db 0b00000100 ; Ending Sector ( End at Sector 4 ) ( 6 bits ) NOTE: 2 LSB are for the following cylinder field
db 0b00000000 ; Ending Cylinder ( End at Cylinder 0 ) ( 10 bits )
db 0x00 ; | Relative sector ( 32 bits )
db 0x00 ; |
db 0x00 ; |
db 0x01 ; |
db 0x00 ; | Total sectors in partition ( 32 bits )
db 0x00 ; |
db 0x00 ; |
db 0x02 ; |
PT2: times 16 db 0 ; Second Partition Entry
PT3: times 16 db 0 ; Third Partition Entry
PT4: times 16 db 0 ; Fourth Partition Entry
dw 0xAA55 ; Magic Word
%define INCLUDE_OFFSET 512
%include "low_kernel.asm"
low_kernel.asm:
[bits 32]
jmp __KERNEL__ENTRY ; JUMP TO KERNEL ENTRY POINT
;***********************************************************;
; MACRO/DEFINE SECTION ;
;***********************************************************;
%define _LOW_KERNEL_VIDEO_MEMORY_ADDRESS 0xb8000
%define SECTOR_OFFSET INCLUDE_OFFSET
%macro PAD_SECTOR 0
times (512 - ($-$$) + SECTOR_OFFSET) db 0 ; Pad out sector
%define SECTOR_OFFSET SECTOR_OFFSET+512
%endmacro
;***********************************************************;
; KERNEL SECTION ;
;***********************************************************;
__KERNEL__ENTRY:
mov esi, _LOW_KERNEL_MESSAGES_TEST
mov ah, 0x0F
call _LOW_KERNEL_PRINT
call _LOW_KERNEL_HANGKERNEL
;; LOW KERNEL SUBROUTINE
; Desc: Prints one character to the designated offset
;
; _IN_ AL ( CHARACTER )
; _IN_ AH ( COLOR )
; _IN_ ECX ( VIDEO MEMORY OFFSET )
_LOW_KERNEL_CHPRINT:
pushad
mov ebx, _LOW_KERNEL_VIDEO_MEMORY_ADDRESS
add ebx, ecx
mov [ebx], eax
popad
ret
;; LOW KERNEL SUBROUTINE
; Desc: Prints strings from ESI
;
; _IN_ AH ( STRING COLOR )
; _IN_ ESI ( STRING ADDRESS )
; _IN_ ECX ( STARTING VIDEO MEMORY OFFSET )
_LOW_KERNEL_PRINT:
pushad
.internal_loop:
lodsb
cmp al, 0
je .internal_end
call _LOW_KERNEL_CHPRINT
add ecx, 0x02
jmp .internal_loop
.internal_end:
mov ah, 0x0F
mov al, 'T'
call _LOW_KERNEL_CHPRINT
popad
ret
_LOW_KERNEL_HANGKERNEL:
jmp $
ret
;***********************************************************;
; KERNEL DATA SECTION ;
;***********************************************************;
_LOW_KERNEL_MESSAGES_TEST:
db "Hello, World!", 0
PAD_SECTOR
这些是通过『nasm -s -f bin bootloader.asm -o test.img
组装的,其.img结果也作为软盘绑定到VM中,通过运行它我得到了一个没有光标的完全黑屏
GDT和far jump是我在网上找的乱七八糟的东西,所以我怀疑是我对它们的了解不足导致了我的问题
请注意,这是我在 Whosebug 上提出的第一个问题,我不了解如何设置问题的格式以使其可读且每个人都能理解。非常欢迎您在评论中 post 提出一些批评,这样我以后可以提出更好的问题。
您将内核(LBA-numbered 扇区 1 及以下)加载到 KERNEL_LOAD_POSITION
,即 1000h (4 KiB)。但是,您的内核紧跟在程序集中的引导扇区加载程序之后,因此它的标签被评估为好像内核将被放置在 600h + 512 = 800h (2 KiB)。 call
指令不受此错误影响,因为它们是相对的,即 position-independent。加载位置可以设置为800h
另一种解决方法是使用 NASM 的 multi-section bin 格式。这就是它的样子:
org 600h
section LOADER start=600h
; loader here
times 510 - ($ - $$) db 0 ; pad to where sector's signature goes
dw 0AA55h ; sector signature
section KERNEL follows=LOADER vstart=KERNEL_LOAD_POSITION
; kernel here
即使加载位置设置为 1000h,这也应该可以工作。
我正在尝试学习一些 x86 程序集。我已经成功地创建了一个带有引导加载程序的 MBR,加载了另一个扇区,切换到保护模式并执行了远跳转到加载的扇区。
使用环境
我在 64 位 Windows 安装上使用 NASM,并使用 nasm -s -f bin bootloader.asm -o test.img
进行组装。我将 test.img
文件作为软盘加载到 VirtualBox VM 中。
问题
我写了一个子程序(代码部分的子程序定义),它使用 lodsb
从 ESI 加载字符,我之前使用 mov esi, _LOW_KERNEL_MESSAGES_TEST
指向一个标签(其中 _LOW_KERNEL_MESSAGES_TEST
是定义为 db "Hello, World!", 0
) 的标签。但是,这不会打印任何内容。
预期打印第一个字符'H'的mov al, [_LOW_KERNEL_MESSAGES_TEST]
也没有(字符打印子例程将AL
作为字符输入)
通过mov al, 't'
直接将一个字符移动到AL
中,然后调用单字符打印子程序就可以了,我也测试了偏移并成功打印了'Test'这个词,问题仅在于打印的“将标签移动到 esi 然后将其加载到 al”部分
我试过的
- 用我在引导加载程序中找到的各种常量偏移标签
- 打印第一个字符(如
mov al, [_LOW_KERNEL_MESSAGES_TEST]
] 所述
- 打印子程序的注释部分
- 修改bootloader中的远跳转和GDT
- 使用
popad
而不是popa
(认为AX
寄存器被修改了?)
密码
应以下评论者的要求,我尝试创建一个最小的可重现示例,其代码可在此处找到:
这可以通过使用进一步组装:
nasm -s -f bin <name of the code file here>.asm -o floppy.img
通过将此 floppy.img 文件绑定到 VirtualBox 6.1 VM 中的软盘控制器
和运行它,一个完全黑的屏幕出现在左上角的白色光标和虚拟机停止,并显示“发生严重错误”消息,错误日志没有多大意义对我来说可能会有帮助,可以在 https://pastebin.com/g1C2xf7V
找到我的文件:
bootloader.asm:
%define KERNEL_LOAD_POSITION 0x1000
[bits 16]
[org 0x0600]
;***********************************************************;
; ENTRY SECTION ;
;***********************************************************;
__ENTRY: ; ENTRY POINT
cli ; Clear Interrupts
xor ax, ax ; Zero out AX (set to 0)
mov ds, ax ; Set Data Segment to 0 (AX)
mov es, ax ; Set Extra Segment to 0 (AX)
mov ss, ax ; Set Stack Segment to 0 (AX)
mov sp, ax ; Set Stack Pointer to 0 (AX)
__SET__LOWER__ENTRY:
mov cx, 0x0100 ; 256 WORDs in MBR
mov si, 0x7C00 ; Current MBR Address
mov di, 0x0600 ; New MBR Address
rep movsw ; Copy MBR
jmp 0:__LOWER_ENTRY ; Jump to new Address
__LOWER_ENTRY:
sti
mov al, 1
call _BOOTLOADER_READSECTORS
call _BOOTLOADER_ENTER_PROTECTED
;***********************************************************;
; FUNCTION SECTION ;
;***********************************************************;
_BOOTLOADER_READSECTORS:
pusha
mov ah, 02h
mov ch, 0x0
mov cl, 0x02
mov dh, 0x0
mov dl, 0x0
mov bx, KERNEL_LOAD_POSITION
int 13h
jc _BOOTLOADER_HANG
popa
ret
_BOOTLOADER_HANG:
jmp $
ret
_BOOTLOADER_ENTER_PROTECTED:
cli
lgdt [GDTR]
pusha
mov eax, cr0
or al, 1
mov cr0, eax
popa
jmp 0x08:_BOOTLOADER_PERFORM_PROTECTED_FAR_JUMP
[bits 32]
_BOOTLOADER_PERFORM_PROTECTED_FAR_JUMP:
mov ax, 0x10
mov ds, ax
mov ss, ax
mov fs, ax
mov es, ax
mov gs, ax
jmp KERNEL_LOAD_POSITION
;***********************************************************;
; DATA SECTION ;
;***********************************************************;
;***********************************************************;
; GDT SECTION ;
;***********************************************************;
GDT:
GDT_NULL_DESC:
dd 0 ; null descriptor
dd 0
GDT_CODE_DESC:
dw 0xFFFF ; limit low
dw 0 ; base low
db 0 ; base middle
db 10011010b ; access
db 11001111b ; granularity
db 0 ; base high
GDT_DATA_DESC:
dw 0xFFFF ; data descriptor
dw 0 ; limit low
db 0 ; base low
db 10010010b ; access
db 11001111b ; granularity
db 0 ; base high
GDTR:
Limit dw 24 ; length of GDT
Base dd GDT_NULL_DESC ; base of GDT
;***********************************************************;
; PADDING ;
;***********************************************************;
times (440 - ($-$$)) db 0 ; Pad For MBR Partition Table
;***********************************************************;
; NON-CODE SECTION ;
;***********************************************************;
UID: times 4 db 0 ; Unique Disk ID
Reserved: times 2 db 0 ; Reserved, either 0x0000 (normal) or 0x5a5a (readonly)
;***********************************************************;
; PARTITION TABLE SECTION ;
;***********************************************************;
PT1: ; First Partition Entry ( KERNEL HERE )
db 0x80 ; Bootable ( Active ) ( 8 bits )
db 0x00 ; Starting Head ( Start at head 0 ) ( 8 bits )
db 0b00000010 ; Starting Sector ( Start at Sector 2 ) ( 6 bits ) NOTE: 2 LSB are for the following cylinder field
db 0b00000000 ; Starting Cylinder ( Start at Cylinder 0 ) ( 10 bits )
db 0x21 ; System ID ( Filysystem ) ( 21, Reserved )
db 0x00 ; Ending Head ( End at head 0 ) ( 8 bits )
db 0b00000100 ; Ending Sector ( End at Sector 4 ) ( 6 bits ) NOTE: 2 LSB are for the following cylinder field
db 0b00000000 ; Ending Cylinder ( End at Cylinder 0 ) ( 10 bits )
db 0x00 ; | Relative sector ( 32 bits )
db 0x00 ; |
db 0x00 ; |
db 0x01 ; |
db 0x00 ; | Total sectors in partition ( 32 bits )
db 0x00 ; |
db 0x00 ; |
db 0x02 ; |
PT2: times 16 db 0 ; Second Partition Entry
PT3: times 16 db 0 ; Third Partition Entry
PT4: times 16 db 0 ; Fourth Partition Entry
dw 0xAA55 ; Magic Word
%define INCLUDE_OFFSET 512
%include "low_kernel.asm"
low_kernel.asm:
[bits 32]
jmp __KERNEL__ENTRY ; JUMP TO KERNEL ENTRY POINT
;***********************************************************;
; MACRO/DEFINE SECTION ;
;***********************************************************;
%define _LOW_KERNEL_VIDEO_MEMORY_ADDRESS 0xb8000
%define SECTOR_OFFSET INCLUDE_OFFSET
%macro PAD_SECTOR 0
times (512 - ($-$$) + SECTOR_OFFSET) db 0 ; Pad out sector
%define SECTOR_OFFSET SECTOR_OFFSET+512
%endmacro
;***********************************************************;
; KERNEL SECTION ;
;***********************************************************;
__KERNEL__ENTRY:
mov esi, _LOW_KERNEL_MESSAGES_TEST
mov ah, 0x0F
call _LOW_KERNEL_PRINT
call _LOW_KERNEL_HANGKERNEL
;; LOW KERNEL SUBROUTINE
; Desc: Prints one character to the designated offset
;
; _IN_ AL ( CHARACTER )
; _IN_ AH ( COLOR )
; _IN_ ECX ( VIDEO MEMORY OFFSET )
_LOW_KERNEL_CHPRINT:
pushad
mov ebx, _LOW_KERNEL_VIDEO_MEMORY_ADDRESS
add ebx, ecx
mov [ebx], eax
popad
ret
;; LOW KERNEL SUBROUTINE
; Desc: Prints strings from ESI
;
; _IN_ AH ( STRING COLOR )
; _IN_ ESI ( STRING ADDRESS )
; _IN_ ECX ( STARTING VIDEO MEMORY OFFSET )
_LOW_KERNEL_PRINT:
pushad
.internal_loop:
lodsb
cmp al, 0
je .internal_end
call _LOW_KERNEL_CHPRINT
add ecx, 0x02
jmp .internal_loop
.internal_end:
mov ah, 0x0F
mov al, 'T'
call _LOW_KERNEL_CHPRINT
popad
ret
_LOW_KERNEL_HANGKERNEL:
jmp $
ret
;***********************************************************;
; KERNEL DATA SECTION ;
;***********************************************************;
_LOW_KERNEL_MESSAGES_TEST:
db "Hello, World!", 0
PAD_SECTOR
这些是通过『nasm -s -f bin bootloader.asm -o test.img
组装的,其.img结果也作为软盘绑定到VM中,通过运行它我得到了一个没有光标的完全黑屏
GDT和far jump是我在网上找的乱七八糟的东西,所以我怀疑是我对它们的了解不足导致了我的问题
请注意,这是我在 Whosebug 上提出的第一个问题,我不了解如何设置问题的格式以使其可读且每个人都能理解。非常欢迎您在评论中 post 提出一些批评,这样我以后可以提出更好的问题。
您将内核(LBA-numbered 扇区 1 及以下)加载到 KERNEL_LOAD_POSITION
,即 1000h (4 KiB)。但是,您的内核紧跟在程序集中的引导扇区加载程序之后,因此它的标签被评估为好像内核将被放置在 600h + 512 = 800h (2 KiB)。 call
指令不受此错误影响,因为它们是相对的,即 position-independent。加载位置可以设置为800h
另一种解决方法是使用 NASM 的 multi-section bin 格式。这就是它的样子:
org 600h
section LOADER start=600h
; loader here
times 510 - ($ - $$) db 0 ; pad to where sector's signature goes
dw 0AA55h ; sector signature
section KERNEL follows=LOADER vstart=KERNEL_LOAD_POSITION
; kernel here
即使加载位置设置为 1000h,这也应该可以工作。