String print function outputs garbage (2nd stage bootloader)
- cakehonolulu
- Member
- Posts: 37
- Joined: Thu Jun 16, 2016 9:35 am
- Libera.chat IRC: cakehonolulu
String print function outputs garbage (2nd stage bootloader)
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
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
- Combuster
- 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
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.
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.
- cakehonolulu
- Member
- Posts: 37
- Joined: Thu Jun 16, 2016 9:35 am
- Libera.chat IRC: cakehonolulu
Re: String print function outputs garbage (2nd stage bootloa
Hello!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.
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!
- Combuster
- 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
I have to disagreeMatching the one I use on my boot0 code
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.
-
- Member
- Posts: 5575
- Joined: Mon Mar 25, 2013 7:01 pm
Re: String print function outputs garbage (2nd stage bootloa
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.
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.
- cakehonolulu
- Member
- Posts: 37
- Joined: Thu Jun 16, 2016 9:35 am
- Libera.chat IRC: cakehonolulu
Re: String print function outputs garbage (2nd stage bootloa
Hello!
Thanks for your time!
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.
That being said, many thanks again for your time!
Thanks for your time!
Corrected that! (Store dl drive number and then use it as per need)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.
Also corrected that one! (Simply removed that bits of code)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.
What am I missing?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.)
Fixed it by adding an infinite loop that halts the CPU.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.
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.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.
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.
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:Your boot1 code loads the wrong value into the GDTR, since you're not taking the segment base into account.
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: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.
That was unexpected! Well, we learn new things everyday! Fixed that too!Octocontrabass wrote:Your project description says this is intended to be a MBR, but floppy disks don't have a MBR.
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.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.
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.
- cakehonolulu
- Member
- Posts: 37
- Joined: Thu Jun 16, 2016 9:35 am
- Libera.chat IRC: cakehonolulu
Re: String print function outputs garbage (2nd stage bootloa
Is this anything better?Combuster wrote:I have to disagreeMatching the one I use on my boot0 codeThe 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 .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.
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.
-
- Member
- Posts: 5575
- Joined: Mon Mar 25, 2013 7:01 pm
Re: String print function outputs garbage (2nd stage bootloa
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.cakehonolulu wrote:What am I missing?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.)
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.
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: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:Your boot1 code loads the wrong value into the GDTR, since you're not taking the segment base into account.
This page from the binutils documentation may help.cakehonolulu wrote: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: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 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?
-
- Member
- Posts: 797
- Joined: Fri Aug 26, 2016 1:41 pm
- Libera.chat IRC: mpetch
Re: String print function outputs garbage (2nd stage bootloa
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:.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
mov $0x1000, %ax
mov %ax, %ds
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.
- cakehonolulu
- Member
- Posts: 37
- Joined: Thu Jun 16, 2016 9:35 am
- Libera.chat IRC: cakehonolulu
Re: String print function outputs garbage (2nd stage bootloa
cakehonolulu wrote:What am I missing?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.)
Many, many thanks for the explanation!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.
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!
Fixed!Octocontrabass wrote:You're also missing the part where you set BX to zero.
I'll do!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.
cakehonolulu wrote: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:Your boot1 code loads the wrong value into the GDTR, since you're not taking the segment base into account.
Thanks! I'll take a look into it!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.
cakehonolulu wrote: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: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'll take that into consideration, thanks again!Octocontrabass wrote:This page from the binutils documentation may help.
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?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?
Thanks for your time!
- cakehonolulu
- Member
- Posts: 37
- Joined: Thu Jun 16, 2016 9:35 am
- Libera.chat IRC: cakehonolulu
Re: String print function outputs garbage (2nd stage bootloa
Thats... Incredible.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:.You could also set ES to 0x1000 as well, but it doesn't appear ES is used in boot1.s.Code: Select all
mov $0x1000, %ax mov %ax, %ds
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
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) ?
-
- Member
- Posts: 797
- Joined: Fri Aug 26, 2016 1:41 pm
- Libera.chat IRC: mpetch
Re: String print function outputs garbage (2nd stage bootloa
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.
- cakehonolulu
- Member
- Posts: 37
- Joined: Thu Jun 16, 2016 9:35 am
- Libera.chat IRC: cakehonolulu
Re: String print function outputs garbage (2nd stage bootloa
Thanks, that was helpful too!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.
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?
-
- Member
- Posts: 797
- Joined: Fri Aug 26, 2016 1:41 pm
- Libera.chat IRC: mpetch
Re: String print function outputs garbage (2nd stage bootloa
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).
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).
-
- Member
- Posts: 5575
- Joined: Mon Mar 25, 2013 7:01 pm
Re: String print function outputs garbage (2nd stage bootloa
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.cakehonolulu wrote:I can't seem to find where it fails, any inputs on this?
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.