OSDev.org https://forum.osdev.org/ |
|
String print function outputs garbage (2nd stage bootloader) https://forum.osdev.org/viewtopic.php?f=1&t=33420 |
Page 2 of 3 |
Author: | MichaelPetch [ Mon Jan 07, 2019 3:18 pm ] |
Post subject: | Re: String print function outputs garbage (2nd stage bootloa |
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: ENTRY(init1) boot0.S:OUTPUT_FORMAT("binary") OUTPUT_ARCH(i386) SECTIONS { . = 0x7e00; .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } .bss : { *(.bss) } } Code: .code16 # Produce 16-Bit Code (For Real Mode). and boot1.S:.section .text # Text section. .global init0 # Make our function globally available. .global boot0 # Make our function globally available. .section .text init0: # init0 function ljmp $0, $boot0 # [CS : IP] We long jump to CS=0x0 and IP=boot0, # where our bootloader starts. That way, we don't assume # segments which, on some computers can result in strange errors, # as BIOSes might set CS=0x7C00 and IP=0x0000 [ 0x7C00 = 0x0000 ] # and cause undefined behaviours on our code. # Starting our code with a jump instruction, also # makes it compatible with very old Compaq computers # that instead of searching for 0x55AA MBR signature # at the end of the first sector of the bootable media, # looks for a jump instruction on the first bytes of it. boot0: # boot0 function # Note: We'll be avoiding FS and GS registers in order to # make our bootloader as compatible as we can until we # reach 32-bits, where they are used and that, makes them uncompatible # with pre-80386+ processors. # Thoose 2 registers were added to 80386+ CPU's. xor %ax, %ax # Xor'ing ax to ax, results in a 0, as xor'ing two registers with # the same value always results in 0. mov %ax, %ds # Move 0x0 to the data segment register. # We null the data segment, because if we don't, it can overlap # with the code segment. mov %ax, %es # Null extra segment too. mov $0x7C00, %bx # Move 0x7C00 to bx. cli # Disable interrupts to circumvent a bug present on # early 8088 CPU's. mov %ax, %ss # Move ax (Which now is 0) to the stack segment register. # registers that contain the same, will always result in 0. mov %bx, %sp # Set the stack pointer register with 0x7C00. # Stack Dissasembly: # Top stack adress: -> 0x7C00 mov %sp, %bp # Move sp (Which now is 0x7C00) to the base pointer register. sti # Re-enable interrupts cld # CLD sets EFLAG's register DF flag to 0, this way, string # operations increment the segment registers (Such as SI & DI). # Invoking a CLD instruction, also makes us aware of the DF's flag # contents, in order to make 0 assumptions on how the BIOS leaves this value. mov $0x3, %ax # Use VGA Text Mode int $0x10 # Call BIOS interrupt 13. call .boot0Loaded # Call a function that displays a message on-screen. mov %dl, boot0bootdrive # Store BIOS dl value containing the boot drive number. boot0ResetDrive: # Function to reset floppy drive in order to ensure it's working correctly. mov $0x00, %ah # Set ah to 0 (AH=0x00 -> Reset Disk Function). mov boot0bootdrive, %dl # Move boot0bootdrive value back into dl. int $0x13 # Call BIOS interrupt 13. jc boot0ResetDrive # If Carry Flag is set (CF=1) an error has ocurred, run the function again. # On this part we'll load our stage 2 bootloader onto 0x1000 and jump to it. push %dx mov boot0bootdrive, %dl # Move boot0bootdrive value back into dl in case INT13 messes with it. mov $0x02, %ah # Set ah to 2 (AH=0x02 -> Disk Read Function). mov $0x14, %al # Set al to 14 (AL=0x14) -> Sectors to be readed. mov $0x00, %ch # Set ch to 0 (CH=0x00) -> Track 0 of the drive. mov $0x02, %cl # Set cl to 2 (CL=0x02) -> Sector of the drive from which we start reading from. mov $0x00, %dh # Set dh to 0 (DH=0x00) -> Head 0 of the drive. xor %bx, %bx # Set our bx register with the adress we'll jump to. mov %bx, %es # Move bx onto our extra segment register to prepare the jump. mov $0x7e00, %bx # Set bx to 0. int $0x13 # Call BIOS interrupt 13. jc boot0diskerror ljmp $0x0000, $0x7e00 # Long jump into our stage 2 bootloader. [0x0000:0x7e00] -> CS=0x0000; IP=0x7e00 cli .hlt: hlt jmp .hlt boot0print: pusha .boot0printchar: mov (%bx), %al cmp $0x0, %al je .boot0printdone mov $0x0E, %ah int $0x10 add $0x1, %bx jmp .boot0printchar .boot0printdone: popa ret boot0printnl: pusha mov $0x0E, %ah mov $0x0A, %al int $0x10 mov $0x0D, %al int $0x10 popa ret .boot0Loaded: mov $boot0LoadedMessage, %bx call boot0print call boot0printnl ret boot0diskerror: mov $boot0DiskErrorMessage, %bx call boot0print call boot0printnl mov %ah, %dh # TODO: Print in hex what kind of error we've got. jmp .hlt boot0bootdrive: .byte 0 boot0LoadedMessage: .asciz "Entered 1st Stage" boot0DiskErrorMessage: .asciz "Disk Read Error!" .fill 510-(.-init0), 1, 0 # Preprocessor directive from GNU as that fills 'x' # count of bytes, with a 'y' size that has 'z' value. # args: count, size, value .word 0xAA55 # BIOS 2-byte magic number that enables the proper # booting of this bootloader Code: # 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 |
Author: | cakehonolulu [ Mon Jan 07, 2019 3:25 pm ] |
Post subject: | Re: String print function outputs garbage (2nd stage bootloa |
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: ENTRY(init1) boot0.S:OUTPUT_FORMAT("binary") OUTPUT_ARCH(i386) SECTIONS { . = 0x7e00; .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } .bss : { *(.bss) } } Code: .code16 # Produce 16-Bit Code (For Real Mode). and boot1.S:.section .text # Text section. .global init0 # Make our function globally available. .global boot0 # Make our function globally available. .section .text init0: # init0 function ljmp $0, $boot0 # [CS : IP] We long jump to CS=0x0 and IP=boot0, # where our bootloader starts. That way, we don't assume # segments which, on some computers can result in strange errors, # as BIOSes might set CS=0x7C00 and IP=0x0000 [ 0x7C00 = 0x0000 ] # and cause undefined behaviours on our code. # Starting our code with a jump instruction, also # makes it compatible with very old Compaq computers # that instead of searching for 0x55AA MBR signature # at the end of the first sector of the bootable media, # looks for a jump instruction on the first bytes of it. boot0: # boot0 function # Note: We'll be avoiding FS and GS registers in order to # make our bootloader as compatible as we can until we # reach 32-bits, where they are used and that, makes them uncompatible # with pre-80386+ processors. # Thoose 2 registers were added to 80386+ CPU's. xor %ax, %ax # Xor'ing ax to ax, results in a 0, as xor'ing two registers with # the same value always results in 0. mov %ax, %ds # Move 0x0 to the data segment register. # We null the data segment, because if we don't, it can overlap # with the code segment. mov %ax, %es # Null extra segment too. mov $0x7C00, %bx # Move 0x7C00 to bx. cli # Disable interrupts to circumvent a bug present on # early 8088 CPU's. mov %ax, %ss # Move ax (Which now is 0) to the stack segment register. # registers that contain the same, will always result in 0. mov %bx, %sp # Set the stack pointer register with 0x7C00. # Stack Dissasembly: # Top stack adress: -> 0x7C00 mov %sp, %bp # Move sp (Which now is 0x7C00) to the base pointer register. sti # Re-enable interrupts cld # CLD sets EFLAG's register DF flag to 0, this way, string # operations increment the segment registers (Such as SI & DI). # Invoking a CLD instruction, also makes us aware of the DF's flag # contents, in order to make 0 assumptions on how the BIOS leaves this value. mov $0x3, %ax # Use VGA Text Mode int $0x10 # Call BIOS interrupt 13. call .boot0Loaded # Call a function that displays a message on-screen. mov %dl, boot0bootdrive # Store BIOS dl value containing the boot drive number. boot0ResetDrive: # Function to reset floppy drive in order to ensure it's working correctly. mov $0x00, %ah # Set ah to 0 (AH=0x00 -> Reset Disk Function). mov boot0bootdrive, %dl # Move boot0bootdrive value back into dl. int $0x13 # Call BIOS interrupt 13. jc boot0ResetDrive # If Carry Flag is set (CF=1) an error has ocurred, run the function again. # On this part we'll load our stage 2 bootloader onto 0x1000 and jump to it. push %dx mov boot0bootdrive, %dl # Move boot0bootdrive value back into dl in case INT13 messes with it. mov $0x02, %ah # Set ah to 2 (AH=0x02 -> Disk Read Function). mov $0x14, %al # Set al to 14 (AL=0x14) -> Sectors to be readed. mov $0x00, %ch # Set ch to 0 (CH=0x00) -> Track 0 of the drive. mov $0x02, %cl # Set cl to 2 (CL=0x02) -> Sector of the drive from which we start reading from. mov $0x00, %dh # Set dh to 0 (DH=0x00) -> Head 0 of the drive. xor %bx, %bx # Set our bx register with the adress we'll jump to. mov %bx, %es # Move bx onto our extra segment register to prepare the jump. mov $0x7e00, %bx # Set bx to 0. int $0x13 # Call BIOS interrupt 13. jc boot0diskerror ljmp $0x0000, $0x7e00 # Long jump into our stage 2 bootloader. [0x0000:0x7e00] -> CS=0x0000; IP=0x7e00 cli .hlt: hlt jmp .hlt boot0print: pusha .boot0printchar: mov (%bx), %al cmp $0x0, %al je .boot0printdone mov $0x0E, %ah int $0x10 add $0x1, %bx jmp .boot0printchar .boot0printdone: popa ret boot0printnl: pusha mov $0x0E, %ah mov $0x0A, %al int $0x10 mov $0x0D, %al int $0x10 popa ret .boot0Loaded: mov $boot0LoadedMessage, %bx call boot0print call boot0printnl ret boot0diskerror: mov $boot0DiskErrorMessage, %bx call boot0print call boot0printnl mov %ah, %dh # TODO: Print in hex what kind of error we've got. jmp .hlt boot0bootdrive: .byte 0 boot0LoadedMessage: .asciz "Entered 1st Stage" boot0DiskErrorMessage: .asciz "Disk Read Error!" .fill 510-(.-init0), 1, 0 # Preprocessor directive from GNU as that fills 'x' # count of bytes, with a 'y' size that has 'z' value. # args: count, size, value .word 0xAA55 # BIOS 2-byte magic number that enables the proper # booting of this bootloader Code: # 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: .long BOOT1CODESEGMENT << 4 + gdt_start And I've done: Code: .long gdt_start Is it wrong? |
Author: | MichaelPetch [ Mon Jan 07, 2019 3:40 pm ] |
Post subject: | Re: String print function outputs garbage (2nd stage bootloa |
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. |
Author: | cakehonolulu [ Mon Jan 07, 2019 3:49 pm ] |
Post subject: | Re: String print function outputs garbage (2nd stage bootloa |
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? |
Author: | MichaelPetch [ Mon Jan 07, 2019 4:05 pm ] |
Post subject: | Re: String print function outputs garbage (2nd stage bootloa |
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: 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 |
Author: | cakehonolulu [ Sat Jan 12, 2019 5:30 am ] |
Post subject: | Re: String print function outputs garbage (2nd stage bootloa |
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: .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? |
Author: | nullplan [ Sat Jan 12, 2019 5:44 am ] |
Post subject: | Re: String print function outputs garbage (2nd stage bootloa |
Code: 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 isCode: movb 0x450, %al Which means "load into al the byte value at address 0x450".
|
Author: | cakehonolulu [ Sat Jan 12, 2019 2:53 pm ] |
Post subject: | Re: String print function outputs garbage (2nd stage bootloa |
nullplan wrote: Code: 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 isCode: movb 0x450, %al Which means "load into al the byte value at address 0x450".I've done this: Code: .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! |
Author: | MichaelPetch [ Sat Jan 12, 2019 4:08 pm ] |
Post subject: | Re: String print function outputs garbage (2nd stage bootloa |
Regarding: Quote: 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: ; Function: set_cursor 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).
; 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 |
Author: | cakehonolulu [ Sat Jan 12, 2019 5:16 pm ] |
Post subject: | Re: String print function outputs garbage (2nd stage bootloa |
MichaelPetch wrote: Regarding: Quote: 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: ; Function: set_cursor 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).; 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 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! |
Author: | cakehonolulu [ Sun Jan 13, 2019 3:17 pm ] |
Post subject: | Re: String print function outputs garbage (2nd stage bootloa |
MichaelPetch wrote: Regarding: Quote: 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: ; Function: set_cursor 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).; 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 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) |
Author: | cakehonolulu [ Sun May 10, 2020 9:54 am ] |
Post subject: | Re: String print function outputs garbage (2nd stage bootloa |
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. |
Author: | Octocontrabass [ Sun May 10, 2020 11:08 am ] |
Post subject: | Re: String print function outputs garbage (2nd stage bootloa |
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? |
Author: | cakehonolulu [ Sun May 10, 2020 11:19 am ] |
Post subject: | Re: String print function outputs garbage (2nd stage bootloa |
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. |
Author: | Octocontrabass [ Sun May 10, 2020 12:04 pm ] |
Post subject: | Re: String print function outputs garbage (2nd stage bootloa |
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. |
Page 2 of 3 | All times are UTC - 6 hours |
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group http://www.phpbb.com/ |