Re: String print function outputs garbage (2nd stage bootloa
Posted: Mon Jan 07, 2019 3:18 pm
I wrote my last post (just above Octo) before seeing you ran into a problem in protected mode. The reason is for the reason I gave. If you use a segment of 0x0000 instead of 0x1000 you can avoid a pile of butt hurt and issue (since the realmode offset and the linear addresses are the same). Based on the comments in my last post you could load boot1 to 0x0000:0x7e00 (or you could choose 0x0000:0x1000). The linker linkboot1.ld would look like:boot0.S:and boot1.S:
Code: Select all
ENTRY(init1)
OUTPUT_FORMAT("binary")
OUTPUT_ARCH(i386)
SECTIONS {
. = 0x7e00;
.text :
{
*(.text)
}
.rodata :
{
*(.rodata)
}
.data :
{
*(.data)
}
.bss :
{
*(.bss)
}
}
Code: Select all
.code16 # Produce 16-Bit Code (For Real Mode).
.section .text # Text section.
.global init0 # Make our function globally available.
.global boot0 # Make our function globally available.
.section .text
init0: # init0 function
ljmp $0, $boot0 # [CS : IP] We long jump to CS=0x0 and IP=boot0,
# where our bootloader starts. That way, we don't assume
# segments which, on some computers can result in strange errors,
# as BIOSes might set CS=0x7C00 and IP=0x0000 [ 0x7C00 = 0x0000 ]
# and cause undefined behaviours on our code.
# Starting our code with a jump instruction, also
# makes it compatible with very old Compaq computers
# that instead of searching for 0x55AA MBR signature
# at the end of the first sector of the bootable media,
# looks for a jump instruction on the first bytes of it.
boot0: # boot0 function
# Note: We'll be avoiding FS and GS registers in order to
# make our bootloader as compatible as we can until we
# reach 32-bits, where they are used and that, makes them uncompatible
# with pre-80386+ processors.
# Thoose 2 registers were added to 80386+ CPU's.
xor %ax, %ax # Xor'ing ax to ax, results in a 0, as xor'ing two registers with
# the same value always results in 0.
mov %ax, %ds # Move 0x0 to the data segment register.
# We null the data segment, because if we don't, it can overlap
# with the code segment.
mov %ax, %es # Null extra segment too.
mov $0x7C00, %bx # Move 0x7C00 to bx.
cli # Disable interrupts to circumvent a bug present on
# early 8088 CPU's.
mov %ax, %ss # Move ax (Which now is 0) to the stack segment register.
# registers that contain the same, will always result in 0.
mov %bx, %sp # Set the stack pointer register with 0x7C00.
# Stack Dissasembly:
# Top stack adress: -> 0x7C00
mov %sp, %bp # Move sp (Which now is 0x7C00) to the base pointer register.
sti # Re-enable interrupts
cld # CLD sets EFLAG's register DF flag to 0, this way, string
# operations increment the segment registers (Such as SI & DI).
# Invoking a CLD instruction, also makes us aware of the DF's flag
# contents, in order to make 0 assumptions on how the BIOS leaves this value.
mov $0x3, %ax # Use VGA Text Mode
int $0x10 # Call BIOS interrupt 13.
call .boot0Loaded # Call a function that displays a message on-screen.
mov %dl, boot0bootdrive # Store BIOS dl value containing the boot drive number.
boot0ResetDrive: # Function to reset floppy drive in order to ensure it's working correctly.
mov $0x00, %ah # Set ah to 0 (AH=0x00 -> Reset Disk Function).
mov boot0bootdrive, %dl # Move boot0bootdrive value back into dl.
int $0x13 # Call BIOS interrupt 13.
jc boot0ResetDrive # If Carry Flag is set (CF=1) an error has ocurred, run the function again.
# On this part we'll load our stage 2 bootloader onto 0x1000 and jump to it.
push %dx
mov boot0bootdrive, %dl # Move boot0bootdrive value back into dl in case INT13 messes with it.
mov $0x02, %ah # Set ah to 2 (AH=0x02 -> Disk Read Function).
mov $0x14, %al # Set al to 14 (AL=0x14) -> Sectors to be readed.
mov $0x00, %ch # Set ch to 0 (CH=0x00) -> Track 0 of the drive.
mov $0x02, %cl # Set cl to 2 (CL=0x02) -> Sector of the drive from which we start reading from.
mov $0x00, %dh # Set dh to 0 (DH=0x00) -> Head 0 of the drive.
xor %bx, %bx # Set our bx register with the adress we'll jump to.
mov %bx, %es # Move bx onto our extra segment register to prepare the jump.
mov $0x7e00, %bx # Set bx to 0.
int $0x13 # Call BIOS interrupt 13.
jc boot0diskerror
ljmp $0x0000, $0x7e00 # Long jump into our stage 2 bootloader. [0x0000:0x7e00] -> CS=0x0000; IP=0x7e00
cli
.hlt:
hlt
jmp .hlt
boot0print:
pusha
.boot0printchar:
mov (%bx), %al
cmp $0x0, %al
je .boot0printdone
mov $0x0E, %ah
int $0x10
add $0x1, %bx
jmp .boot0printchar
.boot0printdone:
popa
ret
boot0printnl:
pusha
mov $0x0E, %ah
mov $0x0A, %al
int $0x10
mov $0x0D, %al
int $0x10
popa
ret
.boot0Loaded:
mov $boot0LoadedMessage, %bx
call boot0print
call boot0printnl
ret
boot0diskerror:
mov $boot0DiskErrorMessage, %bx
call boot0print
call boot0printnl
mov %ah, %dh # TODO: Print in hex what kind of error we've got.
jmp .hlt
boot0bootdrive: .byte 0
boot0LoadedMessage: .asciz "Entered 1st Stage"
boot0DiskErrorMessage: .asciz "Disk Read Error!"
.fill 510-(.-init0), 1, 0 # Preprocessor directive from GNU as that fills 'x'
# count of bytes, with a 'y' size that has 'z' value.
# args: count, size, value
.word 0xAA55 # BIOS 2-byte magic number that enables the proper
# booting of this bootloader
Code: Select all
# Thanks to Octocontrabass, Combuster and MichaelPetch for the help @ forum.osdev.org
.code16
.global init1
.set BOOT1CODESEGMENT, 0x0000
.set BOOT1PMSTACK, 0x9c000
.set BOOT1PMVMEM, 0xb8000
.set BOOT1PMVMEMC, 0x0f
.section .data
gdt_start:
gdt_null:
.long 0
.long 0
gdt_code:
.word 0xffff
.word 0x0
.byte 0x0
.byte 0b10011010
.byte 0b11001111
.byte 0x0
gdt_data:
.word 0xffff
.word 0x0
.byte 0x0
.byte 0b10010010
.byte 0b11001111
.byte 0x0
gdt_end:
.set CODE_SEG, gdt_code - gdt_start
.set DATA_SEG, gdt_data - gdt_start
gdt:
.word (gdt_end - gdt_start - 1)
.long BOOT1CODESEGMENT << 4 + gdt_start
.boot1A20HasAlreadyBeenEnabled: .asciz "A20 Line has already been enabled"
.boot1A20HasBeenEnabled: .asciz "A20 Line Enabled"
.boot1LoadedMessage: .asciz "Entered 2nd Stage"
.boot1LoadedGDTMessage: .asciz "GDT Loaded"
.boot1LoadedPMMessage: .asciz "Entered Protected Mode"
.section .text
init1:
mov $BOOT1CODESEGMENT, %ax
mov %ax, %ds
call boot1LoadedMessage
call boot1EnableA20
call boot1LoadGDT
cli
mov %cr0, %eax
or $1, %eax
mov %eax, %cr0
cli
ljmpl $CODE_SEG, $(BOOT1CODESEGMENT << 4 + boot1Start32)
.hlt:
hlt
jmp .hlt
boot1EnableA20:
call boot1CheckA20LineState # Check if A20 Line is enabled.
jnz boot1A20HasAlreadyBeenEnabled # Jump if condition is met.
movw $0x2401, %ax # Enable A20 Line using the BIOS Method.
stc # Set carry flag.
int $0x15 # Call BIOS interrupt 15 (Enable A20 Line).
jc 1f # BIOS Method failed.
testb %ah, %ah # Compares both registers.
jne 1f # Jumps if they're not equal.
call boot1CheckA20LineState # Check if A20 Line is enabled.
jnz boot1A20HasBeenEnabled # Jump if condition is met.
1: # Enable A20 Line using Intel's 8042 Controller Method.
call .boot1_8042_wait # Wait for Intel's 8042 controller to be ready.
movb $0xd1, %al # Prepare the 8042 port write.
outb %al, $0x64 # Write to the 8042 port.
call .boot1_8042_wait # Wait for Intel's 8042 controller to be ready.
movb $0xdf, %al # Prepare the 8042 port write.
outb %al, $0x60 # Enable A20 Line.
call .boot1_8042_wait # Wait for Intel's 8042 controller to be ready.
call boot1CheckA20LineState # Check if A20 Line is enabled.
jnz boot1A20HasBeenEnabled # Jump if condition is met.
# Enable A20 Line using the 'Fast' Method.
inb $0x92, %al # Try the computer's Fast A20 Gate.
testb $0x02, %al # Compare both values.
jnz 1f # Don't enable A20 if it's already set.
orb $0x02, %al # Check wether the A20 Gate Enable Bit...
andb $0xfe, %al # ...is set or not.
outb %al, $0x92 # Enable the A20 Line using the Fast Gate.
1:
call boot1CheckA20LineState # Check if A20 Line is enabled.
jnz boot1A20HasBeenEnabled # Jump if condition is met.
jmp 1b # Check until A20 Line is set.
.boot1_8042_wait: # Function that waits for Intel's 8042 controller to be ready.
inb $0x64, %al # Read 8042's status.
testb $0x02, %al # Test if bit 1 is zero.
jnz .boot1_8042_wait # Jump if condition is met.
ret # Return to parent function.
boot1CheckA20LineState:
pushw %ds
pushw %es
xorw %ax, %ax
movw %ax, %ds
movw $0x200, %si
decw %ax
movw %ax, %es
movw $0x210, %di
movw %ax, %cx
movw (%si), %ax
pushw %ax
1:
incw %ax
movw %ax, %es:(%di)
cmpw %ax, (%si)
loope 1b
popw (%si)
popw %es
popw %ds
ret
boot1LoadGDT:
cli
pusha
lgdt gdt
sti
popa
call boot1LoadedGDTMessage
ret
boot1A20HasBeenEnabled:
mov $.boot1A20HasBeenEnabled, %bx
call boot1print
call boot1printnl
ret
boot1A20HasAlreadyBeenEnabled:
mov $.boot1A20HasAlreadyBeenEnabled, %bx
call boot1print
call boot1printnl
ret
boot1LoadedMessage:
mov $.boot1LoadedMessage, %bx
call boot1print
call boot1printnl
ret
boot1LoadedGDTMessage:
mov $.boot1LoadedGDTMessage, %bx
call boot1print
call boot1printnl
ret
boot1print:
pusha
.boot1printchar:
mov (%bx), %al
cmp $0x0, %al
je .boot1printdone
mov $0x0E, %ah
int $0x10
add $0x1, %bx
jmp .boot1printchar
.boot1printdone:
popa
ret
boot1printnl:
pusha
mov $0x0E, %ah
mov $0x0A, %al
int $0x10
mov $0x0D, %al
int $0x10
popa
ret
.code32
boot1Start32:
mov $DATA_SEG, %eax
mov %eax, %ds
mov %eax, %es
mov %eax, %fs
mov %eax, %gs
mov %eax, %ss
mov $BOOT1PMSTACK, %esp # Set the stack pointer. I place it below the EBDA
mov $.boot1LoadedPMMessage, %ebx
call boot1pmprintstring
.end_loop:
hlt
jmp .end_loop
boot1pmprintstring:
pusha
mov $BOOT1PMVMEM, %edx
.boot1pmprintstringloop:
mov (%ebx), %al
mov $BOOT1PMVMEMC, %ah
cmp $0x0, %al
je .boot1pmprintstringdone
mov %ax, (%edx)
add $0x1, %ebx
add $0x2, %edx
jmp .boot1pmprintstringloop
.boot1pmprintstringdone:
popa
ret