String print function outputs garbage (2nd stage bootloader)

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
MichaelPetch
Member
Member
Posts: 797
Joined: Fri Aug 26, 2016 1:41 pm
Libera.chat IRC: mpetch

Re: String print function outputs garbage (2nd stage bootloa

Post by MichaelPetch »

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:

Code: Select all

ENTRY(init1)

OUTPUT_FORMAT("binary")

OUTPUT_ARCH(i386)

SECTIONS {

        . = 0x7e00;

    .text :
    {
        *(.text)
    }

    .rodata :
    {
        *(.rodata)
    }

    .data :
    {
        *(.data)
    }

    .bss :
    {
        *(.bss)
    }
}
boot0.S:

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
and boot1.S:

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
User avatar
cakehonolulu
Member
Member
Posts: 37
Joined: Thu Jun 16, 2016 9:35 am
Libera.chat IRC: cakehonolulu

Re: String print function outputs garbage (2nd stage bootloa

Post by cakehonolulu »

MichaelPetch wrote: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:

Code: Select all

ENTRY(init1)

OUTPUT_FORMAT("binary")

OUTPUT_ARCH(i386)

SECTIONS {

        . = 0x7e00;

    .text :
    {
        *(.text)
    }

    .rodata :
    {
        *(.rodata)
    }

    .data :
    {
        *(.data)
    }

    .bss :
    {
        *(.bss)
    }
}
boot0.S:

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
and boot1.S:

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
I implemented that a few minutes ago!
Many thanks for your help!
But now that I look closer, you still use:

Code: Select all

.long BOOT1CODESEGMENT << 4 + gdt_start
And I've done:

Code: Select all

.long gdt_start
Is it wrong?
MichaelPetch
Member
Member
Posts: 797
Joined: Fri Aug 26, 2016 1:41 pm
Libera.chat IRC: mpetch

Re: String print function outputs garbage (2nd stage bootloa

Post by MichaelPetch »

Both do the same thing when BOOT1CODESEGMENT = 0x0000. .long BOOT1CODESEGMENT << 4 + gdt_start would be 0x0000<<4+gdt_start. Shifting 0x0000 left 4 times still gives 0x0000. 0x0000+gdt_start = gdt_start. It only ever makes a difference if BOOT1CODESEGMENT isn't equal to 0x0000.
User avatar
cakehonolulu
Member
Member
Posts: 37
Joined: Thu Jun 16, 2016 9:35 am
Libera.chat IRC: cakehonolulu

Re: String print function outputs garbage (2nd stage bootloa

Post by cakehonolulu »

MichaelPetch wrote:Both do the same thing when BOOT1CODESEGMENT = 0x0000. .long BOOT1CODESEGMENT << 4 + gdt_start would be 0x0000<<4+gdt_start. Shifting 0x0000 left 4 times still gives 0x0000. 0x0000+gdt_start = gdt_start. It only ever makes a difference if BOOT1CODESEGMENT isn't equal to 0x0000.
Wow!
Many thanks for that explanation!
I'm not that good into bitfields and I get confused very often, but you explained it perfectly!
Thanks again!
Now the printing code does what it should!
But, why doesn't print it at the current location of the cursor?
EDIT:
I know I start writing from 0xb8000 and that prints the output string at the first line of the screen, but is there any kind of workaround?
MichaelPetch
Member
Member
Posts: 797
Joined: Fri Aug 26, 2016 1:41 pm
Libera.chat IRC: mpetch

Re: String print function outputs garbage (2nd stage bootloa

Post by MichaelPetch »

When you write directly to the screen you bypass the BIOS TTY print routines. By writing starting at 0xb8000 you are always writing to the upper left hand corner of the screen. The BIOS does maintain current row and column state in the BIOS Data Area (BDA) after each update. If you want to continue where the BIOS left off you'd have to load the current row and colum stored in the BDA. You can find the BDA described here: http://stanislavs.org/helppc/bios_data_area.html
In particular the current row and column of page 0 are stored in the 16-bit word at 0x0040:0x0050 (physical address 0x0450). The byte at 0x450 is the column, the byte at 0x451 is the current row. You can also retrieve the number of columns for the last(current) video mode the BIOS set from the word at 0x0040:0x004a (physical address 0x044a).

Using this data you can compute the offset of the BIOS cursor in video memory by ((cur_row * num_columns) + cur_column)*2. The multiply by two is because each cell in text video memory is 2 bytes (character and attribute). You can then add that value to 0xb8000 to get the actual video memory address to write to.

Some basic code that does rudimentary TTY output to page 0 that supports down scrolling, backspace, Carriage return. line feed is below. It ignore TAB characters:The code was the second stage of a bootloader I wrote to help someone else.You can ignore the code related to the GDT/A20 line etc

Code: Select all

org 0x7e00

VIDEO_TEXT_ADDR     EQU 0xb8000 ; Hard code beginning of text video memory
ATTR_WHITE_ON_BLACK EQU 0x07    ; White on black attribute

CR                  EQU 0x0d    ; Carriage return
LF                  EQU 0x0a    ; Line feed
BS                  EQU 0x08    ; Back space
TAB                 EQU 0x09    ; Tab

PM_MODE_STACK       EQU 0x9c000 ; Realmode stack below EBDA

BITS 16
start:
    mov si, boot_init_msg       ; Print boot initialization message
    call print_string_rm

    ; Fast method of enabling A20 may not work on all x86 BIOSes
    ; It is good enough for emulators and most modern BIOSes
    ; See: https://wiki.osdev.org/A20_Line
    cli                         ; Disable interrupts
    in al, 0x92
    or al, 2
    out 0x92, al                ; Enable A20 using Fast Method

    mov si, load_gdt_msg        ; Print loading GDT message
    call print_string_rm

    lgdt [gdtr]                 ; Load our GDT

    mov si, enter_pm_msg        ; Print protected mode message
    call print_string_rm

    mov eax, cr0
    or eax, 1
    mov cr0, eax                ; Set protected mode flag
    jmp CODE32_SEL:start32      ; FAR JMP to set CS

bits 32
start32:
    mov ax, DATA32_SEL          ; Setup the segment registers with data selector
    mov ds, ax
    mov es, ax
    mov ss, ax
    mov esp, 0x9c000            ; Set the stack pointer

    mov fs, ax                  ; Not currently using FS and GS
    mov gs, ax

    ; Initialize our TTY functions with screen height and width, and
    ; current cursor location from the BIOS
    call update_screen_state_from_bios

    mov ah, ATTR_WHITE_ON_BLACK ; Attribute to print with
    mov al, ah                  ; Attribute to clear last line when scrolling
    mov esi, in_pm_msg          ; Print message that we are in protected mode
    call print_string_pm

end_loop:
    hlt
    jmp end_loop

; Function: update_screen_info_from_bios
;           set the hardware cursor position based on the
;           current column (cur_col) and current row (cur_row) coordinates
;
; Inputs:   None
; Clobbers: EAX
; Returns:  None

update_screen_state_from_bios:
    xor eax, eax                ; Clear EAX for the instructions below
    mov al, [0x450]             ; Byte at address 0x450 = last BIOS column position
    mov [cur_col], eax          ; Copy to current column

    mov al, [0x451]             ; Byte at address 0x451 = last BIOS row position
    mov [cur_row], eax          ; Copy to current row

    mov al, [0x484]             ; Word at address 0x484 = # of rows-1 (screen height)
    mov [screen_height],eax     ; Copy to screen height

    mov ax, [0x44a]             ; Word at address 0x44a = # of columns (screen width)
    mov [screen_width], eax     ; Copy to screen width

    ret

; Function: set_cursor
;           set the hardware cursor position based on the
;           current column (cur_col) and current row (cur_row) coordinates
; See:      https://wiki.osdev.org/Text_Mode_Cursor#Moving_the_Cursor_2
;
; Inputs:   None
; Clobbers: EAX, ECX, EDX
; Returns:  None

set_cursor:
    mov ecx, [cur_row]          ; EAX = cur_row
    imul ecx, [screen_width]    ; ECX = cur_row * screen_width
    add ecx, [cur_col]          ; ECX = cur_row * screen_width + cur_col

    ; Send low byte of cursor position to video card
    mov edx, 0x3d4
    mov al, 0x0f
    out dx, al                  ; Output 0x0f to 0x3d4
    inc edx
    mov al, cl
    out dx, al                  ; Output lower byte of cursor pos to 0x3d5

    ; Send high byte of cursor position to video card
    dec edx
    mov al, 0x0e
    out dx, al                  ; Output 0x0e to 0x3d4
    inc edx
    mov al, ch
    out dx, al                  ; Output higher byte of cursor pos to 0x3d5

    ret

; Function: print_string_pm
;           Display a string to the console on display page 0 in protected mode.
;           Handles carriage return, line feed, and backspace. Tab characters
;           are not processed. Scrolling and wrapping are supported.
;           Backspacing beyond the first line does nothing.
;
; Inputs:   ESI = Offset of address to print
;           AH  = Attribute of string to print
;           AL  = Attribute to use when filling bottom line during down scrolling
; Clobbers: ECX, EDX
; Returns:  None

print_string_pm:
    push edi
    push esi
    push eax
    push ebx
    push ebp

    ; Assume base of text video memory is ALWAYS 0xb8000
    mov ebx, VIDEO_TEXT_ADDR    ; EBX = beginning of video memory
    mov cl, al                  ; CL = attribute to use for clearing while scrolling
    call .init                  ; Initialize register state for use while printing
    jmp .getch
.repeat:
    cmp al, CR                  ; Is the character a carriage return?
    jne .chk_lf                 ;     If not skip and check for line feed
    lea edi, [ebx + edx * 2]    ; Set current video memory pointer to beginning of line
    mov dword [cur_col], 0      ; Set current column to 0
    xor al, al                  ; AL = 0 = Don't print character
    jmp .chk_bounds             ; Check screen bounds
.chk_lf:
    ; Process line feed
    cmp al, LF                  ; Is the character a line feed?
    jne .chk_bs                 ;     If not check for backspace
    mov ebp, [screen_width]
    lea edi, [edi + ebp * 2]    ; Set current video memory ptr to same pos on next line
    inc dword [cur_row]         ; Set current row to next line
    xor al, al                  ; AL = 0 = Don't print character
    jmp .chk_bounds             ; Check screen bounds

.chk_bs:
    ; Process back space
    cmp al, BS                  ; Is the character a Back space?
    jne .chk_tab                ;     If not check for tab
    cmp edi, ebx
    je .getch                   ; If at beginning of display, ignore and get next char
    dec dword [cur_col]         ; Set current column to previous column
    jmp .chk_bounds             ; Check screen bounds

    ; Process tab - ignore character
.chk_tab:
    cmp al, TAB                 ; Is the character a Tab?
    je .getch                   ;     If it is, skip and get next character

    ; Check row and column boundaries and clip them if necessary
    ; If we exceed the number of rows on display,scroll down by a line
.chk_bounds:
    mov ebp, [screen_width]     ; EAX=screen width
    cmp [cur_col], ebp          ; Have we reached edge of display?
    jl  .chk_col_start          ;     If not - continue by checking for beginning of line
    mov dword [cur_col], 0      ; Reset current column to beginning of line
    inc dword [cur_row]         ; Advance to the next row
    jmp .chk_rows               ; Check number of rows in bounds
.chk_col_start:
    cmp dword [cur_col], 0      ; Check if beginning of line
    jge .chk_rows               ; If not negative (beginning of line) check row bounds
    mov dword [cur_col], 0      ; Set column to 0
.chk_rows:
    mov ebp, [screen_height]    ; EAX=screen width
    cmp [cur_row], ebp          ; Have we reached edge of display?
    jle  .test_char             ;     If not then continue by updating display
    dec dword [cur_row]         ; Back one row since we will be scrolling down a line
    call .scroll_down_one_line  ; Scroll display down by a line
    call .init                  ; Reinitialize register state after scroll

    ; Display character to video memory at current location if not a NUL character
.test_char:
    test al, al                 ; Is the character 0?
    jz .getch                   ;     If it is we are finished, get next character
    cmp al, BS                  ; Is the character a Back space?
    jne .not_bs                 ;     If not back space print char and advance cursor
    mov al, ' '
    sub edi, 2                  ; Go back one cell in video memory
    mov [es:edi], al            ; Print a space to clear previous character
    jmp .getch                  ; Don't advance cursor and get next character
.not_bs:
    stosw                       ; Update current character at current location
    inc dword [cur_col]         ; Advance the current column by 1 position

    ; Get next character from string parameter
.getch:
    lodsb                       ; Get character from string
    test al, al                 ; Have we reached end of string?
    jnz .repeat                 ;     if not process next character

.end:
    call set_cursor             ; Update hardware cursor position

    pop ebp
    pop ebx
    pop eax
    pop esi
    pop edi
    ret

; Function: print_string_pm.scroll_down_one_line
;           Internal function of print_string_pm to scroll the display down
;           by a single line. The top line is lost and the bottom line is
;           filled with spaces.
;
; Inputs:   EBX = Base address of video page
;           AH  = Attribute to use when clearing last line
; Clobbers: None
; Returns:  None, display updated

.scroll_down_one_line:
    pusha
    mov ebp, [screen_height]    ; EBP = (num_rows-1)
    mov eax, [screen_width]     ; EAX = screen_width
    lea esi, [ebx + eax * 2]    ; ESI = pointer to second line on screen
    mov edi, ebx                ; EDI = pointer to first line on screen
    mul ebp                     ; EAX = screen_width * (num_rows-1)
    mov ecx, eax                ; ECX = number of screen cells to copy
    rep movsw
    lea edi, [ebx + eax * 2]    ; Destination offset =
                                ; last row = screen_width * (num_rows-1)
    mov ecx, [screen_width]     ; Update a rows worth of word cells
    mov ah, cl
    mov al, ' '                 ; Use a space character with current background attribute
    rep stosw                   ; to clear the last line.
    popa
    ret


; Function: print_string_pm.init
;           Internal function of print_string_pm to compute the video memory
;           address of the current cursor location and the address to the
;           beginning of the current line
;
; Inputs:   EBX = Base address of video page
; Returns:  EDI = Current video memory offset of cursor
;           EDX = Video memory offset to beginning of line
; Clobbers: None

.init:
    push eax
    mov eax, [cur_row]          ; EAX = cur_row
    mul dword [screen_width]    ; EAX = cur_row * screen_width
    mov edx, eax                ; EDX = copy of offset to beginning of line
    add eax, [cur_col]          ; EAX = cur_row * screen_width + cur_col
    lea edi, [ebx + eax * 2]    ; EDI = memory location of current screen cell
    pop eax
    ret


bits 16

; Function: print_string_rm
;           Display a string to the console on display page 0 in real mode
;
; Inputs:   SI = Offset of address to print
; Clobbers: AX, BX, SI
; Returns:  None

print_string_rm:
    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

align 4
cur_row:      dd 0x00
cur_col:      dd 0x00
screen_width: dd 0x00
screen_height:dd 0x00

boot_init_msg:
    db "Booting sequence initialized...", CR, LF, 0
load_gdt_msg:
    db "Loading GDT...", CR, LF, 0
enter_pm_msg:
    db "Entering 32-bit Protected Mode...", CR, LF, 0
in_pm_msg:
    db "Executing code in protected mode!", CR, LF, 0

align 4
gdt_start:
    dd 0                        ; null descriptor
    dd 0

gdt32_code:
    dw 0FFFFh                   ; limit low
    dw 0                        ; base low
    db 0                        ; base middle
    db 10011010b                ; access
    db 11001111b                ; 32-bit, 4kb granularity, limit 0xffffffff bytes
    db 0                        ; base high

gdt32_data:
    dw 0FFFFh                   ; limit low (Same as code)
    dw 0                        ; base low
    db 0                        ; base middle
    db 10010010b                ; access
    db 11001111b                ; 32-bit, 4kb granularity, limit 0xffffffff bytes
    db 0                        ; base high
end_of_gdt:

gdtr:
    dw end_of_gdt - gdt_start - 1
                                ; limit (Size of GDT - 1)
    dd gdt_start                ; base of GDT

CODE32_SEL equ gdt32_code - gdt_start
DATA32_SEL equ gdt32_data - gdt_start
User avatar
cakehonolulu
Member
Member
Posts: 37
Joined: Thu Jun 16, 2016 9:35 am
Libera.chat IRC: cakehonolulu

Re: String print function outputs garbage (2nd stage bootloa

Post by cakehonolulu »

MichaelPetch wrote:When you write directly to the screen you bypass the BIOS TTY print routines. By writing starting at 0xb8000 you are always writing to the upper left hand corner of the screen. The BIOS does maintain current row and column state in the BIOS Data Area (BDA) after each update. If you want to continue where the BIOS left off you'd have to load the current row and colum stored in the BDA. You can find the BDA described here: http://stanislavs.org/helppc/bios_data_area.html
In particular the current row and column of page 0 are stored in the 16-bit word at 0x0040:0x0050 (physical address 0x0450). The byte at 0x450 is the column, the byte at 0x451 is the current row. You can also retrieve the number of columns for the last(current) video mode the BIOS set from the word at 0x0040:0x004a (physical address 0x044a).

Using this data you can compute the offset of the BIOS cursor in video memory by ((cur_row * num_columns) + cur_column)*2. The multiply by two is because each cell in text video memory is 2 bytes (character and attribute). You can then add that value to 0xb8000 to get the actual video memory address to write to.

Some basic code that does rudimentary TTY output to page 0 that supports down scrolling, backspace, Carriage return. line feed is below. It ignore TAB characters:The code was the second stage of a bootloader I wrote to help someone else.You can ignore the code related to the GDT/A20 line etc
Many thanks for that!
But... I'm having a little bit of a problem translating NASM syntax to gnu-as one.

Code: Select all

.section .data
.align 4
cur_row: .long 0x00
cur_col: .long 0x00
screen_width: .long 0x00
screen_height:.long 0x00
.section .text
.code32
update_screen_state_from_bios: 
    xorl %eax,%eax              # Clear EAX for the instructions below
    movb $0x450,%al             # Byte at address 0x450 = last BIOS column position
    movl %eax,cur_col           # Copy to current column
    movb $0x451,%al             # Byte at address 0x451 = last BIOS row position
    movl %eax,cur_row           # Copy to current row
    movb $0x484,%al             # Word at address 0x484 = # of rows-1 (screen height)
    movl %eax,screen_height     # Copy to screen height
    movw $0x44a,%ax             # Word at address 0x44a = # of columns (screen width)
    movl %eax,screen_width      # Copy to screen width
    ret
But that doesn't correctly work.
Any inputs?
nullplan
Member
Member
Posts: 1792
Joined: Wed Aug 30, 2017 8:24 am

Re: String print function outputs garbage (2nd stage bootloa

Post by nullplan »

Code: Select all

movb $0x450, %al
That means "load into al the literal value 0x450". Which doesn't fit in a byte, so a good assembler would issue an error or a warning. Unfortunately, you are using gas. What you mean is

Code: Select all

movb 0x450, %al
Which means "load into al the byte value at address 0x450".
Carpe diem!
User avatar
cakehonolulu
Member
Member
Posts: 37
Joined: Thu Jun 16, 2016 9:35 am
Libera.chat IRC: cakehonolulu

Re: String print function outputs garbage (2nd stage bootloa

Post by cakehonolulu »

nullplan wrote:

Code: Select all

movb $0x450, %al
That means "load into al the literal value 0x450". Which doesn't fit in a byte, so a good assembler would issue an error or a warning. Unfortunately, you are using gas. What you mean is

Code: Select all

movb 0x450, %al
Which means "load into al the byte value at address 0x450".
I've done this:

Code: Select all

.section .data
.align 4
boot1_currow: .long 0x00
boot1_curcol: .long 0x00
boot1_screenwidth: .long 0x00
boot1_screenheight:.long 0x00

.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
        call boot1getbioscurpos
        mov $.boot1LoadedPMMessage, %ebx
        call boot1pmprintstring

.end_loop:
        hlt
        jmp .end_loop

boot1getbioscurpos:
    xor %eax, %eax
    mov 0x450, %al
    mov %eax, boot1_curcol
    mov 0x451, %al
    mov %eax, boot1_currow
    mov 0x484, %al
    mov %eax, boot1_screenheight
    mov 0x44a, %ax
    mov %eax, boot1_screenwidth
    ret

boot1pmprintstring:
	pusha
	push %ebx
	xor %eax, %eax
	xor %ecx, %ecx
	mov boot1_currow, %eax
	mov boot1_screenwidth, %ecx
	mul %ecx # eax*ecx=eax modded
	xor %ecx, %ecx
	mov boot1_curcol, %ecx
	add %eax, %ecx # eax+ecx=ecx modded
	mov %ecx, %eax
	mov $2, %ecx
	mul %ecx # eax*ecx=eax modded
	mov $BOOT1PMVMEM, %ecx
	add %eax, %ecx # eax+ecx=ecx modded
	mov %ecx, %ebx
	mov %ebx, %edx
	pop %ebx

.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

I'm not sure if it's very optimized (Sorry for my assembler skills) but hey, it works.
I'd also love setting the cursor over to a new position. Is this doable on protected mode using assembly?
Thanks for your time!
MichaelPetch
Member
Member
Posts: 797
Joined: Fri Aug 26, 2016 1:41 pm
Libera.chat IRC: mpetch

Re: String print function outputs garbage (2nd stage bootloa

Post by MichaelPetch »

Regarding:
I'm not sure if it's very optimized (Sorry for my assembler skills) but hey, it works.
I'd also love setting the cursor over to a new position. Is this doable on protected mode using assembly?
Thanks for your time!
The code I wrote has a set_cursor function that moves the hardware cursor for just that purpose:

Code: Select all

; Function: set_cursor
;           set the hardware cursor position based on the
;           current column (cur_col) and current row (cur_row) coordinates
; See:      https://wiki.osdev.org/Text_Mode_Cursor#Moving_the_Cursor_2
;
; Inputs:   None
; Clobbers: EAX, ECX, EDX
; Returns:  None

set_cursor:
    mov ecx, [cur_row]          ; EAX = cur_row
    imul ecx, [screen_width]    ; ECX = cur_row * screen_width
    add ecx, [cur_col]          ; ECX = cur_row * screen_width + cur_col

    ; Send low byte of cursor position to video card
    mov edx, 0x3d4
    mov al, 0x0f
    out dx, al                  ; Output 0x0f to 0x3d4
    inc edx
    mov al, cl
    out dx, al                  ; Output lower byte of cursor pos to 0x3d5

    ; Send high byte of cursor position to video card
    dec edx
    mov al, 0x0e
    out dx, al                  ; Output 0x0e to 0x3d4
    inc edx
    mov al, ch
    out dx, al                  ; Output higher byte of cursor pos to 0x3d5

    ret
You can read more about about this code on OSDev wiki here: https://wiki.osdev.org/Text_Mode_Cursor ... e_Cursor_2 . Yes, this code can run in protected mode (set_cursor). Accessing memory locations in the BDA for current cursor and column, screen height, number of columns etc can all be accessed from protected mode as well (since they are just memory addresses). As long as you aren't calling BIOS routines your code can be run in protected mode. So inputting and outputting data to IO ports is supported (in and out instructions) as well as direct memory access (including memory mapped IO).
User avatar
cakehonolulu
Member
Member
Posts: 37
Joined: Thu Jun 16, 2016 9:35 am
Libera.chat IRC: cakehonolulu

Re: String print function outputs garbage (2nd stage bootloa

Post by cakehonolulu »

MichaelPetch wrote:Regarding:
I'm not sure if it's very optimized (Sorry for my assembler skills) but hey, it works.
I'd also love setting the cursor over to a new position. Is this doable on protected mode using assembly?
Thanks for your time!
The code I wrote has a set_cursor function that moves the hardware cursor for just that purpose:

Code: Select all

; Function: set_cursor
;           set the hardware cursor position based on the
;           current column (cur_col) and current row (cur_row) coordinates
; See:      https://wiki.osdev.org/Text_Mode_Cursor#Moving_the_Cursor_2
;
; Inputs:   None
; Clobbers: EAX, ECX, EDX
; Returns:  None

set_cursor:
    mov ecx, [cur_row]          ; EAX = cur_row
    imul ecx, [screen_width]    ; ECX = cur_row * screen_width
    add ecx, [cur_col]          ; ECX = cur_row * screen_width + cur_col

    ; Send low byte of cursor position to video card
    mov edx, 0x3d4
    mov al, 0x0f
    out dx, al                  ; Output 0x0f to 0x3d4
    inc edx
    mov al, cl
    out dx, al                  ; Output lower byte of cursor pos to 0x3d5

    ; Send high byte of cursor position to video card
    dec edx
    mov al, 0x0e
    out dx, al                  ; Output 0x0e to 0x3d4
    inc edx
    mov al, ch
    out dx, al                  ; Output higher byte of cursor pos to 0x3d5

    ret
You can read more about about this code on OSDev wiki here: https://wiki.osdev.org/Text_Mode_Cursor ... e_Cursor_2 . Yes, this code can run in protected mode (set_cursor). Accessing memory locations in the BDA for current cursor and column, screen height, number of columns etc can all be accessed from protected mode as well (since they are just memory addresses). As long as you aren't calling BIOS routines your code can be run in protected mode. So inputting and outputting data to IO ports is supported (in and out instructions) as well as direct memory access (including memory mapped IO).
Thanks for all your help!
I'll try to implement it by myself, as I love finding the bits that make that happen!
I did a very poor job probably trying to convert your formula ((cur_row * num_columns) + cur_column)*2 into assembly, is it any good?
Thanks again!
User avatar
cakehonolulu
Member
Member
Posts: 37
Joined: Thu Jun 16, 2016 9:35 am
Libera.chat IRC: cakehonolulu

Re: String print function outputs garbage (2nd stage bootloa

Post by cakehonolulu »

MichaelPetch wrote:Regarding:
I'm not sure if it's very optimized (Sorry for my assembler skills) but hey, it works.
I'd also love setting the cursor over to a new position. Is this doable on protected mode using assembly?
Thanks for your time!
The code I wrote has a set_cursor function that moves the hardware cursor for just that purpose:

Code: Select all

; Function: set_cursor
;           set the hardware cursor position based on the
;           current column (cur_col) and current row (cur_row) coordinates
; See:      https://wiki.osdev.org/Text_Mode_Cursor#Moving_the_Cursor_2
;
; Inputs:   None
; Clobbers: EAX, ECX, EDX
; Returns:  None

set_cursor:
    mov ecx, [cur_row]          ; EAX = cur_row
    imul ecx, [screen_width]    ; ECX = cur_row * screen_width
    add ecx, [cur_col]          ; ECX = cur_row * screen_width + cur_col

    ; Send low byte of cursor position to video card
    mov edx, 0x3d4
    mov al, 0x0f
    out dx, al                  ; Output 0x0f to 0x3d4
    inc edx
    mov al, cl
    out dx, al                  ; Output lower byte of cursor pos to 0x3d5

    ; Send high byte of cursor position to video card
    dec edx
    mov al, 0x0e
    out dx, al                  ; Output 0x0e to 0x3d4
    inc edx
    mov al, ch
    out dx, al                  ; Output higher byte of cursor pos to 0x3d5

    ret
You can read more about about this code on OSDev wiki here: https://wiki.osdev.org/Text_Mode_Cursor ... e_Cursor_2 . Yes, this code can run in protected mode (set_cursor). Accessing memory locations in the BDA for current cursor and column, screen height, number of columns etc can all be accessed from protected mode as well (since they are just memory addresses). As long as you aren't calling BIOS routines your code can be run in protected mode. So inputting and outputting data to IO ports is supported (in and out instructions) as well as direct memory access (including memory mapped IO).
I've finally managed to implement that!
I now have a "sane" (Kinda of) 2-stage bootloader that jumps to protected mode.
Is there anything I could do to try it's limits? (Maybe trying to boot some prebuilt kernels that don't rely on the multiboot specification)
User avatar
cakehonolulu
Member
Member
Posts: 37
Joined: Thu Jun 16, 2016 9:35 am
Libera.chat IRC: cakehonolulu

Re: String print function outputs garbage (2nd stage bootloa

Post by cakehonolulu »

Hello! Sorry for bumping my old thread, but I'm having a weird issue:
https://github.com/cakehonolulu/Ferrum
I've jumped to unreal mode to load my kernel over the 1MB mark, used the rep movsb trick but when I jump to it (After I switch to protected mode again) somehow during the execution of the kernel (The C part, not the assembly one) it just stops. I've been debugging all sorts of things I've thought of, but to no avail, I suspect it's something to do with the sector loading (Since execution just stops without errors) but I don't really have many more options to check so, I thought I'd ask here. What could the problem be?
Many thanks for your time!

EDIT:
Jumping and kernel loading and jumping is kinda working on Bochs, on qemu it doesn't even jump to it.
Octocontrabass
Member
Member
Posts: 5575
Joined: Mon Mar 25, 2013 7:01 pm

Re: String print function outputs garbage (2nd stage bootloa

Post by Octocontrabass »

You load 10 kiB. Is that enough?

You've written a lot of code. Assuming you're loading enough sectors from the disk, I don't think I could spot the problem without running it under a debugger myself.

Speaking of debuggers, have you tried setting some breakpoints to see where it goes off the rails? Even if you can't figure out why it gets stuck, figuring out exactly where it gets stuck will help us help you.

What is this function supposed to do?
User avatar
cakehonolulu
Member
Member
Posts: 37
Joined: Thu Jun 16, 2016 9:35 am
Libera.chat IRC: cakehonolulu

Re: String print function outputs garbage (2nd stage bootloa

Post by cakehonolulu »

Octocontrabass wrote:You load 10 kiB. Is that enough?

You've written a lot of code. Assuming you're loading enough sectors from the disk, I don't think I could spot the problem without running it under a debugger myself.

Speaking of debuggers, have you tried setting some breakpoints to see where it goes off the rails? Even if you can't figure out why it gets stuck, figuring out exactly where it gets stuck will help us help you.

What is this function supposed to do?
Yeah, looks like I'll have to jump on debugging, but really, I don't know where to start.
It just stops at printing a string (Which is exactly the same as many others before, no change on what's really printing) so no error codes, no nothing, it could really be anything.

Pushed 1 more commit, that should adjust the number of sectors loaded (For now) onto memory to 9 (As that's the rounded-up number that my script echoes after gathering a bunch of stuff from the object files) so that's probably more than enough for now, also adjusted the redundant code line you mentioned, still, no effect, which surely is strange.
Octocontrabass
Member
Member
Posts: 5575
Joined: Mon Mar 25, 2013 7:01 pm

Re: String print function outputs garbage (2nd stage bootloa

Post by Octocontrabass »

cakehonolulu wrote:Yeah, looks like I'll have to jump on debugging, but really, I don't know where to start.
I'd start by breaking into the debugger after that last string prints to see what code is executing and at what address.
Post Reply