; Example program that uses an absolute reference to a label
; that won't work unless a proper ORG is used. Removing the ORG
; or using the wrong value will cause the code to not work as
; expected
org 0x101000
bits 32
start:
mov eax, [okmsg] ; Using an absolute reference to a label
mov dword [0xb8000], eax ; Write value to display
hlt
okmsg: dd 0x2f4b2f4f
STAGE2_ABS_ADDR equ 0x08000
STAGE2_RUN_SEG equ 0x0000
STAGE2_RUN_OFS equ STAGE2_ABS_ADDR
; Run stage2 with segment of 0x0000 and offset of 0x8000
STAGE2_LOAD_SEG equ STAGE2_ABS_ADDR>>4
; Segment to start reading Stage2 into
; right after bootloader
STAGE2_LBA_START equ 1 ; Logical Block Address(LBA) Stage2 starts on
; LBA 1 = sector after boot sector
STAGE2_LBA_END equ STAGE2_LBA_START + NUM_STAGE2_SECTORS
; Logical Block Address(LBA) Stage2 ends at
DISK_RETRIES equ 3 ; Number of times to retry on disk error
bits 16
ORG 0x7c00
; Include a BPB (1.44MB floppy with FAT12) to be more compatible with USB floppy media
%ifdef WITH_BPB
%include "bpb.inc"
%endif
boot_continue:
xor ax, ax ; DS=SS=0 for stage2 loading
mov ds, ax
mov ss, ax ; Stack at 0x0000:0x7c00
mov sp, 0x7c00
cld ; Set string instructions to use forward movement
; Read Stage2 1 sector at a time until stage2 is completely loaded
load_stage2:
mov [bootDevice], dl ; Save boot drive
mov di, STAGE2_LOAD_SEG ; DI = Current segment to read into
mov si, STAGE2_LBA_START ; SI = LBA that stage2 starts at
jmp .chk_for_last_lba ; Check to see if we are last sector in stage2
.read_sector_loop:
mov bp, DISK_RETRIES ; Set disk retry count
call lba_to_chs ; Convert current LBA to CHS
mov es, di ; Set ES to current segment number to read into
xor bx, bx ; Offset zero in segment
.retry:
mov ax, 0x0201 ; Call function 0x02 of int 13h (read sectors)
; AL = 1 = Sectors to read
int 0x13 ; BIOS Disk interrupt call
jc .disk_error ; If CF set then disk error
.success:
add di, 512>>4 ; Advance to next 512 byte segment (0x20*16=512)
inc si ; Next LBA
.chk_for_last_lba:
cmp si, STAGE2_LBA_END ; Have we reached the last stage2 sector?
jl .read_sector_loop ; If we haven't then read next sector
.stage2_loaded:
mov si, noa20_err ; Default error message to A20 enable error
call a20_enable ; Enable A20 line
jz error_print ; If the A20 line isn't enabled, print error and stop
lgdt [gdtr] ; Load GDT for 32-bit protected mode
cli ; Disable interrupts since we don't have an IDT setup
mov eax, cr0 ; Read CR0 register
or eax, 1 ; Enable protected mode flage (bit 0)
mov cr0, eax ; Set CR0 register&enter quasi 16-bit protected mode
jmp CODE32_SEL:start32pm ; FAR JMP to use a 32-bit code selector
; This enters 32-bit protected mode @ start32pm
.disk_error:
xor ah, ah ; Int13h/AH=0 is drive reset
int 0x13
dec bp ; Decrease retry count
jge .retry ; If retry count not exceeded then try again
disk_error_end:
; Unrecoverable error; print drive error; enter infinite loop
mov si, diskErrorMsg ; Display disk error message
error_print:
call print_string
cli
error_loop:
hlt
jmp error_loop
; Function: print_string
; Display a string to the console on display page 0
;
; Inputs: SI = Offset of address to print
; Clobbers: AX, BX, SI
print_string:
mov ah, 0x0e ; BIOS tty Print
xor bx, bx ; Set display page to 0 (BL)
jmp .getch
.repeat:
int 0x10 ; print character
.getch:
lodsb ; Get character from string
test al,al ; Have we reached end of string?
jnz .repeat ; if not process next character
.end:
ret
; Function: lba_to_chs
; Description: Translate Logical block address to CHS (Cylinder, Head, Sector).
;
; Resources: http://www.ctyme.com/intr/rb-0607.htm
; https://en.wikipedia.org/wiki/Logical_block_addressing#CHS_conversion
; https://stackoverflow.com/q/45434899/3857942
; Sector = (LBA mod SPT) + 1
; Head = (LBA / SPT) mod HEADS
; Cylinder = (LBA / SPT) / HEADS
;
; Inputs: SI = LBA
; Outputs: DL = Boot Drive Number
; DH = Head
; CH = Cylinder (lower 8 bits of 10-bit cylinder)
; CL = Sector/Cylinder
; Upper 2 bits of 10-bit Cylinders in upper 2 bits of CL
; Sector in lower 6 bits of CL
;
; Notes: Output registers match expectation of Int 13h/AH=2 inputs
;
lba_to_chs:
push ax ; Preserve AX
mov ax, si ; Copy LBA to AX
xor dx, dx ; Upper 16-bit of 32-bit value set to 0 for DIV
div word [sectorsPerTrack] ; 32-bit by 16-bit DIV : LBA / SPT
mov cl, dl ; CL = S = LBA mod SPT
inc cl ; CL = S = (LBA mod SPT) + 1
xor dx, dx ; Upper 16-bit of 32-bit value set to 0 for DIV
div word [numHeads] ; 32-bit by 16-bit DIV : (LBA / SPT) / HEADS
mov dh, dl ; DH = H = (LBA / SPT) mod HEADS
mov dl, [bootDevice] ; boot device, not necessary to set but convenient
mov ch, al ; CH = C(lower 8 bits) = (LBA / SPT) / HEADS
shl ah, 6 ; Store upper 2 bits of 10-bit Cylinder into
or cl, ah ; upper 2 bits of Sector (CL)
pop ax ; Restore scratch registers
ret
; Function: wait_8042_cmd
; Wait until the Input Buffer Full bit in the keyboard controller's
; status register becomes 0. After calls to this function it is
; safe to send a command on Port 0x64
;
; Inputs: None
; Clobbers: AX
; Returns: None
KBC_STATUS_IBF_BIT EQU 1
wait_8042_cmd:
in al, 0x64 ; Read keyboard controller status register
test al, 1 << KBC_STATUS_IBF_BIT
; Is bit 1 (Input Buffer Full) set?
jnz wait_8042_cmd ; If it is then controller is busy and we
; can't send command byte, try again
ret ; Otherwise buffer is clear and ready to send a command
; Function: wait_8042_data
; Wait until the Output Buffer Empty (OBE) bit in the keyboard controller's
; status register becomes 0. After a call to this function there is
; data available to be read on port 0x60.
;
; Inputs: None
; Clobbers: AX
; Returns: None
KBC_STATUS_OBE_BIT EQU 0
wait_8042_data:
in al, 0x64 ; Read keyboard controller status register
test al, 1 << KBC_STATUS_OBE_BIT
; Is bit 0 (Output Buffer Empty) set?
jz wait_8042_data ; If not then no data waiting to be read, try again
ret ; Otherwise data is ready to be read
; Function: a20_kbd_enable
; Enable the A20 line via the keyboard controller
;
; Inputs: None
; Clobbers: AX, CX
; Returns: None
a20_kbd_enable:
pushf
cli ; Disable interrupts
call wait_8042_cmd ; When controller ready for command
mov al, 0xad ; Send command 0xad (disable keyboard).
out 0x64, al
call wait_8042_cmd ; When controller ready for command
mov al, 0xd0 ; Send command 0xd0 (read output port)
out 0x64, al
call wait_8042_data ; Wait until controller has data
in al, 0x60 ; Read data from keyboard
mov cx, ax ; CX = copy of byte read
call wait_8042_cmd ; Wait until controller is ready for a command
mov al, 0xd1
out 0x64, al ; Send command 0xd1 (write output port)
call wait_8042_cmd ; Wait until controller is ready for a command
mov ax, cx
or al, 1 << 1 ; Write value back with bit 1 set
out 0x60, al
call wait_8042_cmd ; Wait until controller is ready for a command
mov al, 0xae
out 0x64, al ; Write command 0xae (enable keyboard)
call wait_8042_cmd ; Wait until controller is ready for command
popf ; Restore flags including interrupt flag
ret
; Function: a20_fast_enable
; Enable the A20 line via System Control Port A
;
; Inputs: None
; Clobbers: AX
; Returns: None
a20_fast_enable:
in al, 0x92 ; Read System Control Port A
test al, 1 << 1
jnz .finished ; If bit 1 is set then A20 already enabled
or al, 1 << 1 ; Set bit 1
and al, ~(1 << 0) ; Clear bit 0 to avoid issuing a reset
out 0x92, al ; Send Enabled A20 and disabled Reset to control port
.finished:
ret
; Function: a20_bios_enable
; Enable the A20 line via the BIOS function Int 15h/AH=2401
;
; Inputs: None
; Clobbers: AX
; Returns: None
a20_bios_enable:
mov ax, 0x2401 ; Int 15h/AH=2401 enables A20 on BIOS with this feature
int 0x15
ret
; Function: a20_check
; Determine if the A20 line is enabled or disabled
;
; Inputs: None
; Clobbers: AX, CX, ES
; Returns: ZF=1 if A20 enabled, ZF=0 if disabled
a20_check:
pushf ; Save flags so Interrupt Flag (IF) can be restored
push ds ; Save volatile registers
push si
push di
cli ; Disable interrupts
xor ax, ax
mov ds, ax
mov si, 0x600 ; 0x0000:0x0600 (0x00600) address we will test
mov ax, 0xffff
mov es, ax
mov di, 0x610 ; 0xffff:0x0610 (0x00600) address we will test
; The physical address pointed to depends on whether
; memory wraps or not. If it wraps then A20 is disabled
mov cl, [si] ; Save byte at 0x0000:0x0600
mov ch, [es:di] ; Save byte at 0xffff:0x0610
mov byte [si], 0xaa ; Write 0xaa to 0x0000:0x0600
mov byte [es:di], 0x55 ; Write 0x55 to 0xffff:0x0610
xor ax, ax ; Set return value 0
cmp byte [si], 0x55 ; If 0x0000:0x0600 is 0x55 and not 0xaa
je .disabled ; then memory wrapped because A20 is disabled
dec ax ; A20 Disable, set AX to -1
.disabled:
; Cleanup by restoring original bytes in memory. This must be in reverse
; order from the order they were originally saved
mov [es:di], ch ; Restore data saved data to 0xffff:0x0610
mov [si], cl ; Restore data saved data to 0x0000:0x0600
pop di ; Restore non-volatile registers
pop si
pop ds
popf ; Restore Flags (including IF)
test al, al ; Return ZF=1 if A20 enabled, ZF=0 if disabled
ret
; Function: a20_enable
; Enable the A20 line
;
; Inputs: None
; Clobbers: AX, BX, CX, DX
; Returns: ZF=0 if A20 not enabled, ZF=1 if A20 enabled
a20_enable:
call a20_check ; Is A20 already enabled?
jnz .a20_on ; If so then we're done ZF=1
call a20_bios_enable ; Try enabling A20 via BIOS
call a20_check ; Is A20 now enabled?
jnz .a20_on ; If so then we're done ZF=1
call a20_kbd_enable ; Try enabling A20 via keyboard controller
call a20_check ; Is A20 now enabled?
jnz .a20_on ; If so then we're done ZF=1
call a20_fast_enable ; Try enabling A20 via fast method
call a20_check ; Is A20 now enabled?
jnz .a20_on ; If so then we're done ZF=1
.a20_err:
xor ax, ax ; If A20 disabled then return with ZF=0
.a20_on:
ret
bits 32
start32pm:
mov ax, DATA32_SEL ; Set up the 32-bit data selectors
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
; Zero extend SP to ESP. SP is already at 0x7c00
; DL still contains the boot drive number
movzx esp, sp
; Execute stage2 code
jmp STAGE2_RUN_OFS
; 32-bit GDT for protected mode
; Macro to build a GDT descriptor entry
%define MAKE_GDT_DESC(base, limit, access, flags) \
(((base & 0x00FFFFFF) << 16) | \
((base & 0xFF000000) << 32) | \
(limit & 0x0000FFFF) | \
((limit & 0x000F0000) << 32) | \
((access & 0xFF) << 40) | \
((flags & 0x0F) << 52))
; GDT structure
gdt_start:
dq MAKE_GDT_DESC(0, 0, 0, 0); null descriptor
gdt32_code:
dq MAKE_GDT_DESC(0, 0x000fffff, 10011010b, 1100b)
; 32-bit code, 4kb gran, limit 0xffffffff bytes, base=0
gdt32_data:
dq MAKE_GDT_DESC(0, 0x000fffff, 10010010b, 1100b)
; 32-bit data, 4kb gran, limit 0xffffffff bytes, base=0
gdt_end:
CODE32_SEL equ gdt32_code - gdt_start
DATA32_SEL equ gdt32_data - gdt_start
; GDT record
align 4
dw 0 ; Padding align dd GDT in gdtr on 4 byte boundary
gdtr:
dw gdt_end - gdt_start - 1
; limit (Size of GDT - 1)
dd gdt_start ; base of GDT
; If not using a BPB (via bpb.inc) provide default Heads and SPT values
%ifndef WITH_BPB
numHeads: dw 2 ; 1.44MB Floppy has 2 heads & 18 sector per track
sectorsPerTrack: dw 18
%endif
bootDevice: db 0x00
diskErrorMsg: db "Unrecoverable disk error!", 0
noa20_err: db "A20 line couldn't be enabled", 10, 13, 0
; Pad boot sector to 510 bytes and add 2 byte boot signature for 512 total bytes
TIMES 510-($-$$) db 0
dw 0xaa55
; Beginning of stage2. This is at 0x8000 and will allow your stage2 to be 32.5KiB
; before running into problems. DL will be set to the drive number originally
; passed to us by the BIOS.
NUM_STAGE2_SECTORS equ (stage2_end-stage2_start+511) / 512
; Number of 512 byte sectors stage2 uses.
stage2_start:
; Insert stage2 binary here. It is done this way since we
; can determine the size(and number of sectors) to load since
; Size = stage2_end-stage2_start
incbin "kernel.bin"
; End of stage2. Make sure this label is LAST in this file!
stage2_end:
; Fill out this file to produce a 1.44MB floppy image
TIMES 1024*1440-($-$$) db 0x00
; Example program that uses an absolute reference to a label
; that won't work unless a proper ORG is used. Removing the ORG
; or using the wrong value will cause the code to not work as
; expected
org 0x8000
bits 32
start:
mov eax, [okmsg] ; Using an absolute reference to a label
mov dword [0xb8000], eax ; Write value to display
hlt
okmsg: dd 0x2f4b2f4f
1条答案
按热度按时间fdbelqdn1#
我不知道为什么你不想在你的代码中使用ELF可执行文件,特别是如果你在写内核的话。
没有办法让QEMU直接运行任意的二进制文件。同样,您拥有的代码需要处于32位保护模式才能正常运行(因为它使用
bits 32
)。您给出的唯一要求是它在QEMU中运行,并且其中包含指令和/或数据的二进制文件不能被修改。Multiboot规范允许您使用带有适当Multiboot头的简单ELF文件。在这里使用多引导是最好的选择,因为它已经设置了32位保护模式;启用A20线路;并将数据和代码加载到存储器中。这是一个很大的工作,你不必自己编码。
您可以做的是在NASM中创建一个Multiboot Package 器,其中包含一个简单的Multiboot头,并包含包含代码的二进制文件。下面的示例将创建一个兼容Multiboot的ELF可执行文件,它在内存中的头文件为0x 100000,内核文件为0x 101000。此代码使用NASM的
incbin
指令将一个二进制文件直接包含到包含Multiboot头和入口点的汇编文件中。mboot.asm
字符串
kernel.asm
型
首先,你必须将
kernel.asm
构建为一个名为kernel.bin
的二进制文件,其中:型
然后你必须使用以下命令组装multiboot wrapper:
型
最后,将其链接到名为
kernel.elf
的ELF可执行文件:型
这个兼容Multiboot的ELF可执行文件可以在QEMU中使用
-kernel
选项运行,如下所示:型
运行时的输出应类似于:
x1c 0d1x的数据
补充说明
随着内核二进制文件的增长,您需要通过标签引用绝对内存位置,您需要告诉NASM加载内核二进制文件的虚拟内存地址(VMA)为0x 101000。这可以使用
ORG
指令来完成,如下所示:型
这段代码不需要ORG指令,因为它不需要代码中任何地方的标签的绝对地址,但这可能会随着代码的扩展而改变。如果您没有正确指定ORG(原点),将无法工作的代码示例如下:
型
正如在回答的开头所述,我并不真的推荐这种方法,但我提供了一个解决方案,允许您运行您提交的代码,这些代码被放置在一个您说不能更改的二进制文件中。
加载并运行保护模式内核的软盘 Bootstrap
如果你不想使用Multiboot,而想创建一个磁盘映像,这个相当简化的 Bootstrap :
这段代码是基于我的一些其他答案的代码。一个是bootloader,它以16位真实的模式执行代码。我修改了它的代码,我用在一个问题,enables A20和进入保护模式。
** Boot .asm**
型
首先根据您在
kernel.asm
中的代码构建kernel.bin
:型
创建一个1.44MiB软盘(
disk.img
),其中包含kernel.bin
中的代码和数据以及将内核读入内存的卷 Boot 记录(VBR):型
它可以从软盘映像在QEMU中运行,使用:
型
这个版本的代码最终可能需要在内核中使用
ORG 0x8000
,而不是我前面介绍的Multiboot版本,在您继续开发内核时,它可能需要ORG 0x101000
。如果您没有正确指定ORG(原点),将无法工作的代码示例如下:型