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

String print function outputs garbage (2nd stage bootloader)

Post by cakehonolulu »

Hello all!
After spending some time reading bootloader theory, I decided to try and make my own loader so that I could use it as a bootstrapper for my kernel.
I've managed to load up a second stage loader and jump to it, but whenever I try to print a string on my screen, it just outputs garbage.
However, the routine I use on my first stage loader works just fine. Is there anything I'm missing?
I'd like to get a review (If possible) of my bootloader as it is right now, maybe you guys find out things I'm probably missing!
Many thanks for all your time!
Sorry for my english!
https://github.com/cakehonolulu/Ferrum
User avatar
Combuster
Member
Member
Posts: 9301
Joined: Wed Oct 18, 2006 3:45 am
Libera.chat IRC: [com]buster
Location: On the balcony, where I can actually keep 1½m distance
Contact:

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

Post by Combuster »

There's one kind of line in your first linker script that's missing from the second.

Most important to notice here is that code flow is based on relative addresses, and will generally work even if the code is loaded at a different address than linked. Whenever you address a variable - such as a string - it will generate an absolute address, which means that the bootloader and linker need to agree on where things will appear in the output. Right now, you haven't told the linker anything.
"Certainly avoid yourself. He is a newbie and might not realize it. You'll hate his code deeply a few years down the road." - Sortie
[ My OS ] [ VDisk/SFS ]
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 »

Combuster wrote:There's one kind of line in your first linker script that's missing from the second.

Most important to notice here is that code flow is based on relative addresses, and will generally work even if the code is loaded at a different address than linked. Whenever you address a variable - such as a string - it will generate an absolute address, which means that the bootloader and linker need to agree on where things will appear in the output. Right now, you haven't told the linker anything.
Hello!
I've now specified a starting address in my second linker script (Matching the one I use on my boot0 code) and now the string print routine doesn't print anything. Can this be a stack-related problem?
Thanks for your time!
User avatar
Combuster
Member
Member
Posts: 9301
Joined: Wed Oct 18, 2006 3:45 am
Libera.chat IRC: [com]buster
Location: On the balcony, where I can actually keep 1½m distance
Contact:

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

Post by Combuster »

Matching the one I use on my boot0 code
I have to disagree

Code: Select all

	mov $0x1000, %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.
	int $0x13						# Call BIOS interrupt 13.
The bios call loads to the address given as ES:BX (i.e. 16 * ES + BX). Look again carefully what the values are at the end :wink:.
"Certainly avoid yourself. He is a newbie and might not realize it. You'll hate his code deeply a few years down the road." - Sortie
[ My OS ] [ VDisk/SFS ]
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 »

Your boot0 code discards the value passed by the BIOS in DL. This will make your code fail under certain situations, including when booting from USB mass storage on certain computers (there are many other issues you may encounter there!) and when chain-loading from another bootloader on a different disk.

Your boot0 code checks for INT 0x13 extensions, but doesn't use them. You don't need to check anything to use basic functions like INT 0x13 AH=0x02.

The comments in your boot0 code suggest it's supposed to load boot1 to 0x1000:0x0000, but the code is doing something else. (And your recent update to the boot1 linker script suggests you don't understand how segment base addresses work, which is another problem.)

Your boot0 code uses CLI and HLT to stop the CPU, but doesn't include an infinite loop. If you're worried about booting on an old 8088 that doesn't correctly disable interrupts during a MOV to SS, you should also be worried about old PC-compatibles (or not-quite-PC-compatibles) where NMI may be used for funky things like DRAM refresh.

Your boot1 code doesn't check to see if the CPU is at least a 386 before running 386-specific code. There's not much point in working around the 8088 MOV to SS bug in boot0 when boot1 assumes the CPU is always at least a 386.

Your boot1 code loads the wrong value into the GDTR, since you're not taking the segment base into account.

You never tell your assembler which CPU to target, so it might generate opcodes that aren't compatible with the 8088, particularly for things like conditional jumps.

Your project description says this is intended to be a MBR, but floppy disks don't have a MBR.

There are quite a few places where you can reduce the size of your code, in case you're running low on space.
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!
Thanks for your time!
Octocontrabass wrote:Your boot0 code discards the value passed by the BIOS in DL. This will make your code fail under certain situations, including when booting from USB mass storage on certain computers (there are many other issues you may encounter there!) and when chain-loading from another bootloader on a different disk.
Corrected that! (Store dl drive number and then use it as per need)
Octocontrabass wrote:Your boot0 code checks for INT 0x13 extensions, but doesn't use them. You don't need to check anything to use basic functions like INT 0x13 AH=0x02.
Also corrected that one! (Simply removed that bits of code)
Octocontrabass wrote:The comments in your boot0 code suggest it's supposed to load boot1 to 0x1000:0x0000, but the code is doing something else. (And your recent update to the boot1 linker script suggests you don't understand how segment base addresses work, which is another problem.)
What am I missing?
Octocontrabass wrote:Your boot0 code uses CLI and HLT to stop the CPU, but doesn't include an infinite loop. If you're worried about booting on an old 8088 that doesn't correctly disable interrupts during a MOV to SS, you should also be worried about old PC-compatibles (or not-quite-PC-compatibles) where NMI may be used for funky things like DRAM refresh.
Fixed it by adding an infinite loop that halts the CPU.
Octocontrabass wrote:Your boot1 code doesn't check to see if the CPU is at least a 386 before running 386-specific code. There's not much point in working around the 8088 MOV to SS bug in boot0 when boot1 assumes the CPU is always at least a 386.
For now, boot1 is kinda floating on the air, as my intention is to target different i8x86 CPU's and I need to set up a proper configure environment before compilation that way users can personalize the binary they'll get.
That being said, I'll probably look-up how to check if the CPU is at least a 386 (I know GS and FS desc's should not be used as they were added on that CPU "revision") as it doesn't do anything harmful to my base code.
Octocontrabass wrote:Your boot1 code loads the wrong value into the GDTR, since you're not taking the segment base into account.
I'll have to check my GDT part too, it had given me a few problems on the past (Double-faults and such) and I must improve how I handle the temporary GDT thing before entering Protected Mode.
Octocontrabass wrote:You never tell your assembler which CPU to target, so it might generate opcodes that aren't compatible with the 8088, particularly for things like conditional jumps.
I have added -mtune=i8086 flag (For now) on the Makefile. Should I also use -mcpu ? (I've looked the available arch's and I'm not able to find anything lower than i386).
Octocontrabass wrote:Your project description says this is intended to be a MBR, but floppy disks don't have a MBR.
That was unexpected! Well, we learn new things everyday! Fixed that too!
Octocontrabass wrote:There are quite a few places where you can reduce the size of your code, in case you're running low on space.
Yeah, I should optimize my code a fair bit, but right now I'd like to get a good working base and then, I'd start doing optimizations and a few more things here and there.

That being said, many thanks again for your time!
Last edited by cakehonolulu on Mon Jan 07, 2019 11:08 am, edited 1 time in total.
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 »

Combuster wrote:
Matching the one I use on my boot0 code
I have to disagree

Code: Select all

	mov $0x1000, %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.
	int $0x13						# Call BIOS interrupt 13.
The bios call loads to the address given as ES:BX (i.e. 16 * ES + BX). Look again carefully what the values are at the end :wink:.
Is this anything better?
Thanks for your time!

Code: Select all

	mov $0x1000, %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.
	xor %bx, %bx                                   # ES:BX -> 0x1000 : 0x0 ?
	int $0x13						# Call BIOS interrupt 13.
	ljmp $0x1000, $0x0 				# Long jump into our stage 2 bootloader. [0x1000:0x0] -> CS=0x1000; IP=0x0
Last edited by cakehonolulu on Mon Jan 07, 2019 12:32 pm, edited 1 time in total.
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:
Octocontrabass wrote:The comments in your boot0 code suggest it's supposed to load boot1 to 0x1000:0x0000, but the code is doing something else. (And your recent update to the boot1 linker script suggests you don't understand how segment base addresses work, which is another problem.)
What am I missing?
Your assembler and linker only work in terms of offsets relative to the segment base. If you tell the linker your code will be loaded at 0x1000, it expects the offset portion of the address to be 0x1000 and has no expectation for the segment portion. Additionally, in real mode, the segment base is calculated by multiplying the value in the segment register by 16. Setting a segment register to 0x1000 means having a segment base of 0x10000.

You're also missing the part where you set BX to zero.

I recommend setting all segment registers to zero and keeping them that way while you're in real mode. It limits you to the lowest 64kB of memory (minus the IVT and BDA), but it allows you to ignore segmentation entirely. You're not missing out by not learning how segmentation works; it's not useful for much outside of a few specific niches like thread-local storage.
cakehonolulu wrote:
Octocontrabass wrote:Your boot1 code loads the wrong value into the GDTR, since you're not taking the segment base into account.
I'll have to check my GDT part too, it had given me a few problems on the past (Double-faults and such) and I must improve how I handle the temporary GDT thing before entering Protected Mode.
If you set all of the segment bases to 0, you won't have to change anything. It's only a problem here because you're trying to use nonzero segment bases.
cakehonolulu wrote:
Octocontrabass wrote:You never tell your assembler which CPU to target, so it might generate opcodes that aren't compatible with the 8088, particularly for things like conditional jumps.
I have added -mtune=i8086 flag (For now) on the Makefile. Should I also use -mcpu ? (I've looked the available arch's and I'm not able to find anything lower than i386).
This page from the binutils documentation may help.


I spotted another issue I didn't notice the first time I looked at your code: there is no error handling at all when boot0 tries to load boot1 into memory. What if the read fails?
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 »

It appears you are confused about segment:offset addressing.If you want to place your kernel at 0x1000:0x0000 (which is physical address 0x10000 (0x1000<<4+0x0000), the disk read needs to set ES to 0x1000 and BX to 0. In your linkboot1.ld you need to set the VMA to 0x0000(not 0x1000). LD doesn't know anything about segment:offset addressing. It just generates code relative an origin point. The origin point is the offset portion of 0x1000:0x0000, thus you need to set it 0x0000. Change . = 0x1000; to . = 0x0000;. In boot1.S you need to set the segments to 0x1000 using something like:

Code: Select all

    mov $0x1000, %ax
    mov %ax, %ds
.You could also set ES to 0x1000 as well, but it doesn't appear ES is used in boot1.s.
Although not a problem in boot0.S you do ljmp $0, $(boot0-init0+0x7c00). That could be simplified to ljmp $0, $boot0.In boot1.S your GDT needs to use the linear address of the GDT. You have tomanually adjust the GDT address to account for the fact that it is loaded using segment 0x1000. You should be using something like .long 0x1000<<4+gdt_start instead of .long gdt_start

Your code doesn't jump into protected mode after setting the protected mode bit. Because of the way that GNU assembler processes labels you will need to actually place the GDT before the FAR JMP so that the assembler realizes that the CODE_SEG declaration is in fact absolute. You can place the GDT and GDTR into the .data section before the .text section. You will also need to use ljmpl instead of jmpl because the address of the 32-bit code is in an offset >=64 kb. You will also need to compute the linear address of the 32-bit entry point by adding 0x1000<<4 to the address. Your boot1.S could look like:

Code: Select all

.code16

.global init1

.set BOOT1_SEG, 0x1000
.set PM_STACK,  0x9c000

.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 BOOT1_SEG << 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"

.section .text

init1:
        mov $BOOT1_SEG, %ax
        mov %ax, %ds

        mov $0x2, %ah
        mov $0x1, %dh
        mov $0x0, %dl
        mov $0x0, %bh
        int $0x10
        call boot1LoadedMessage
        mov $0x2, %ah
        mov $0x2, %dh
        mov $0x0, %dl
        mov $0x0, %bh
        int $0x10
        call boot1EnableA20
        mov $0x2, %ah
        mov $0x3, %dh
        mov $0x0, %dl
        mov $0x0, %bh
        int $0x10
        call boot1LoadGDT
        cli
        mov %cr0, %eax
        or $1, %eax
        mov %eax, %cr0
        ljmpl $CODE_SEG, $(BOOT1_SEG << 4 + start_32)

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, %si
        call boot1print
        ret

boot1A20HasAlreadyBeenEnabled:
        mov $.boot1A20HasAlreadyBeenEnabled, %si
        call boot1print
        ret

boot1LoadedMessage:
        mov $.boot1LoadedMessage, %si
        call boot1print
        ret

boot1LoadedGDTMessage:
        mov $.boot1LoadedGDTMessage, %si
        call boot1print
        ret

boot1print:
        mov $0x0E, %ah
.boot1printchar:
        lodsb
        cmp $0, %al
        je .boot1printdone
        int $0x10
        jmp .boot1printchar
.boot1printdone:
        ret

.code32
start_32:
        mov $DATA_SEG, %eax
        mov %eax, %ds
        mov %eax, %es
        mov %eax, %fs
        mov %eax, %gs
        mov %eax, %ss
        mov $PM_STACK, %esp                 # Set the stack pointer. I place it below the EBDA

.end_loop:
        hlt
        jmp .end_loop
Last edited by MichaelPetch on Mon Jan 07, 2019 1:00 pm, edited 3 times in total.
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 »

cakehonolulu wrote:
Octocontrabass wrote:The comments in your boot0 code suggest it's supposed to load boot1 to 0x1000:0x0000, but the code is doing something else. (And your recent update to the boot1 linker script suggests you don't understand how segment base addresses work, which is another problem.)
What am I missing?
Octocontrabass wrote:Your assembler and linker only work in terms of offsets relative to the segment base. If you tell the linker your code will be loaded at 0x1000, it expects the offset portion of the address to be 0x1000 and has no expectation for the segment portion. Additionally, in real mode, the segment base is calculated by multiplying the value in the segment register by 16. Setting a segment register to 0x1000 means having a segment base of 0x10000.
Many, many thanks for the explanation!
I was confused about ld being able to check if the 'code' it was linking was targeting 16-bit thus, adapting the addresses and all that stuff using the base:offset model from real mode, that's why I thought, adding . = 0x1000; was a good idea!
Thanks for that!
Octocontrabass wrote:You're also missing the part where you set BX to zero.
Fixed!
Octocontrabass wrote:I recommend setting all segment registers to zero and keeping them that way while you're in real mode. It limits you to the lowest 64kB of memory (minus the IVT and BDA), but it allows you to ignore segmentation entirely. You're not missing out by not learning how segmentation works; it's not useful for much outside of a few specific niches like thread-local storage.
I'll do!
cakehonolulu wrote:
Octocontrabass wrote:Your boot1 code loads the wrong value into the GDTR, since you're not taking the segment base into account.
I'll have to check my GDT part too, it had given me a few problems on the past (Double-faults and such) and I must improve how I handle the temporary GDT thing before entering Protected Mode.
Octocontrabass wrote:If you set all of the segment bases to 0, you won't have to change anything. It's only a problem here because you're trying to use nonzero segment bases.
Thanks! I'll take a look into it!
cakehonolulu wrote:
Octocontrabass wrote:You never tell your assembler which CPU to target, so it might generate opcodes that aren't compatible with the 8088, particularly for things like conditional jumps.
I have added -mtune=i8086 flag (For now) on the Makefile. Should I also use -mcpu ? (I've looked the available arch's and I'm not able to find anything lower than i386).
I'll take that into consideration, thanks again!
Octocontrabass wrote:I spotted another issue I didn't notice the first time I looked at your code: there is no error handling at all when boot0 tries to load boot1 into memory. What if the read fails?
I've added a jump if Carry Flag is set after calling INT13 over to a function in order to tell the user something went wrong (As INT13 AH=0x02 changes the Carry Flag if something went wrong as I've read). Is this all I have to do?

Thanks for your time!
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:It appears you are confused about segment:offset addressing.If you want to place your kernel at 0x1000:0x0000 (which is physical address 0x10000 (0x1000<<4+0x0000), the disk read needs to set ES to 0x1000 and BX to 0. In your linkboot1.ld you need to set the VMA to 0x0000(not 0x1000). LD doesn't know anything about segment:offset addressing. It just generates code relative an origin point. The origin point is the offset portion of 0x1000:0x0000, thus you need to set it 0x0000. Change . = 0x1000; to . = 0x0000;. In boot1.S you need to set the segments to 0x1000 using something like:

Code: Select all

    mov $0x1000, %ax
    mov %ax, %ds
.You could also set ES to 0x1000 as well, but it doesn't appear ES is used in boot1.s.
Although not a problem in boot0.S you do ljmp $0, $(boot0-init0+0x7c00). That could be simplified to ljmp $0, $boot0.In boot1.S your GDT needs to use the linear address of the GDT. You have tomanually adjust the GDT address to account for the fact that it is loaded using segment 0x1000. You should be using something like .long 0x1000<<4+gdt_start instead of .long gdt_start

Your code doesn't jump into protected mode after setting the protected mode bit. Because of the way that GNU assembler processes labels you will need to actually place the GDT before the FAR JMP so that the assembler realizes that the CODE_SEG declaration is in fact absolute. You can place the GDT and GDTR into the .data section before the .text section. You will also need to use ljmpl instead of jmpl because the address of the 32-bit code is in an offset >=64 kb. You will also need to compute the linear address of the 32-bit entry point by adding 0x1000<<4 to the address. Your boot1.S could look like:

Code: Select all

.code16

.global init1

.set BOOT1_SEG, 0x1000
.set PM_STACK,  0x9c000

.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 BOOT1_SEG << 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"

.section .text

init1:
        mov $BOOT1_SEG, %ax
        mov %ax, %ds

        mov $0x2, %ah
        mov $0x1, %dh
        mov $0x0, %dl
        mov $0x0, %bh
        int $0x10
        call boot1LoadedMessage
        mov $0x2, %ah
        mov $0x2, %dh
        mov $0x0, %dl
        mov $0x0, %bh
        int $0x10
        call boot1EnableA20
        mov $0x2, %ah
        mov $0x3, %dh
        mov $0x0, %dl
        mov $0x0, %bh
        int $0x10
        call boot1LoadGDT
        cli
        mov %cr0, %eax
        or $1, %eax
        mov %eax, %cr0
        ljmpl $CODE_SEG, $(BOOT1_SEG << 4 + start_32)

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, %si
        call boot1print
        ret

boot1A20HasAlreadyBeenEnabled:
        mov $.boot1A20HasAlreadyBeenEnabled, %si
        call boot1print
        ret

boot1LoadedMessage:
        mov $.boot1LoadedMessage, %si
        call boot1print
        ret

boot1LoadedGDTMessage:
        mov $.boot1LoadedGDTMessage, %si
        call boot1print
        ret

boot1print:
        mov $0x0E, %ah
.boot1printchar:
        lodsb
        cmp $0, %al
        je .boot1printdone
        int $0x10
        jmp .boot1printchar
.boot1printdone:
        ret

.code32
start_32:
        mov $DATA_SEG, %eax
        mov %eax, %ds
        mov %eax, %es
        mov %eax, %fs
        mov %eax, %gs
        mov %eax, %ss
        mov $PM_STACK, %esp                 # Set the stack pointer. I place it below the EBDA

.end_loop:
        hlt
        jmp .end_loop
Thats... Incredible.
I must appreciate the time you've spent helping me!
Thanks to your explanation I think I've managed to understand the quirks and underlying pieces of the Protected Mode switch.
I'm so thankful for what both you and Octocontrabass have explained me!
Many, many thanks for your time!
I have another question, I've been using "mov" instead of "movw" or "movb", is gnu-as able to detect the type of data I'm moving from one place to another by itself or should I better add the "w" or "b" (From movw and movb respectively) ?
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 »

My personal coding preference in GNU assembler is to let the assembler choose the size based on the operands if it can. If it can't determine the size, then of course I use an instruction suffix to remove the ambiguity (ie: moving an immediate to a memory address). Not sure others feelings on the matter, just my opinion.
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:My personal coding preference in GNU assembler is to let the assembler choose the size based on the operands if it can. If it can't determine the size, then of course I use an instruction suffix to remove the ambiguity (ie: moving an immediate to a memory address). Not sure others feelings on the matter, just my opinion.
Thanks, that was helpful too!

I'm running into a problem, now that I am in protected mode, string print routines must target 0xb8000.
I've written a fairly simple function that does that, but...
It doesn't work.
I can't seem to find where it fails, any inputs on this?
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 should point out a caveat though. Octo hinted on a preferred solution. When I jumped into protected mode I provided a limited amount of code that didn't require any use of absolute addresses so it worked. If you attempt to use a label in an absolute expression, the label will be relative to the offset 0x0000. The problem is that in protected mode with a flat 4gb memory model the labels are actually at 0x10000+offset because you loaded the code into segment 0x1000 while in realmode. GNU doesn't know that. For that reason, the easiest thing to do is alter your code to use a segment of 0x0000 for your boot1.S rather than 0x1000. I'd place boot1.S in memory at 0x0000:0x7e00 just above the bootloader. linkedboot1.ld should set the VMA to 0x7e00 instead of 0x0000, and boot0.S would have to load boot1 to es:bx of 0x0000:0x7e00 rather than 0x1000:0x0000.

By using a segment of 0x0000 you don't have to fix up the addresses at all. If you don't make a change like this you have to develop a more complex linker script. In the version of boot1.S I provided you only have to change BOOT1_SEG to 0x0000 (instead of 0x1000).
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:I can't seem to find where it fails, any inputs on this?
It looks like another issue caused by a non-zero segment base in real mode. Since the assembler/linker is unable to handle the change in the segment base from 0x10000 in real mode to 0 in protected mode, the address you're loading into EBX is wrong. You need to add BOOT1CODESEGMENT << 4 to get the correct offset for use in a segment with 0 as the base.

You don't need to do this if the segment base is the same in real mode and protected mode; this is why I suggest setting all of the segment registers to 0 in real mode.
Post Reply