Page 1 of 1

Questions about string after jump to kernel in protected mode

Posted: Wed Jul 31, 2024 3:16 am
by Antonio82
Good Morning,

I am working on a small kernel that transitions from real mode to protected mode. The initial setup is in boot.asm, which then jumps to kernel.asm.

In kernel.asm, I am able to print the character 'H' directly to the video buffer. However, when I attempt to print a string, it does not work as expected. I suspect this issue may be related to segmentation.

Here are the steps I’ve already taken:

Moved the string to the data section—this did not resolve the issue.
Set the Data Segment register (DS) to 0x1000 and 0—neither approach worked.

Other examples I found online show similar methods for printing a single character, which work correctly in my case. I have attached the following files for your review:

Code: Select all

  
    boot.asm
    Makefile (note: it is somewhat messy)


; boot.asm
org 0x7C00  ; Origin, as bootloaders start at this address

[BITS 16]               ; 16-bit real mode                                  

start:
    
    mov [BOOT_DISK], dl             ; Store the boot disk number (from DL register) into BOOT_DISK.

    ; Display a message to indicate this is the first file
        mov ah, 0x0E
        mov al, 'H'
        int 0x10
        mov al, 'i'
        int 0x10
        mov al, '!'
        int 0x10

        call timer

        mov ebx, 0x1000
        ; Set up registers for reading from disk
        mov dh, 2                       ; Set DH register to read 2 sectors.

        mov ah, 0x02                    ; BIOS interrupt function 0x02 - Read sectors.
        mov al, dh                      ; Number of sectors to read (stored in DH).
        mov ch, 0x00                    ; Cylinder number (0).
        mov dh, 0x00                    ; Head number (0).
        mov cl, 0x02                    ; Starting sector (2).
        mov dl, [BOOT_DISK]             ; Disk number (boot disk).
        int 0x13  

        ; Check for error
        jc disk_read_error   ; If carry flag is set, there was an error


        call print_mem

        call timer

        ;; stop interruptions
        cli
       ;; Set the video mode to text mode (80x25 16-color)
        mov ah, 0x0
        mov al, 0x3
        int 0x10  

    ;; Load the descriptor table
        lgdt [gdt_descriptor]

    ;; turn on protected mode
        mov eax, cr0
        or eax, 1
        mov cr0, eax

        jmp CODE_SEG:flush_pipeline


disk_read_error:
    ; Display an error message
    mov ah, 0x0E
    mov al, 'E'
    int 0x10
    mov al, 'r'
    int 0x10
    mov al, 'r'
    int 0x10
    mov al, 'o'
    int 0x10
    mov al, 'r'
    int 0x10
    hlt                  ; Halt the CPU


print_mem:
     mov si, 0 
loop:    
    mov al, 0x1000[si] ; move counter to the begin of the buffer
   
    ; bios printing service
    mov ah, 0x0e
    mov bh, 0x00
    mov bl, 0x07
    int 10h
    
    ;next element
    inc si
    cmp si,100
    jl loop    
    ret

timer:
    ; wait this time that actually
    ; is not very precise
    mov     cx, 0x000F
    mov     dx, 0x4240
    mov     al,0
    mov     ah, 0x86
    int     0x15
    ret


gdt_start:
gdt_null:
    dd 0
    dd 0
gdt_code:
    dw 0xFFFF
    dw 0x0000
    db 0x00
    db 10011010b
    db 11001111b
    db 0x00
gdt_data:
    dw 0xFFFF
    dw 0x0000
    db 0x00
    db 10010010b
    db 11001111b
    db 0x00
gdt_end:

gdt_descriptor:
    dw gdt_end - gdt_start - 1
    dd gdt_start

[bits 32]

flush_pipeline:
    mov ax, DATA_SEG
    mov ds, ax       
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ss, ax


    mov esi, hello_str  ; Load the address of the string
    mov edi, 0xB8000    ; Video memory starts at address 0xB8000
    call print_string
    jmp 0x1000


print_string:
    lodsb               ; Load byte at address DS:SI into AL
    test al, al
    jz .done            ; If null terminator, finish
    mov ah, 0x0F        ; Attribute byte: white on black
    stosw               ; Store word at ES:DI (AL=character, AH=attribute)
    jmp print_string
.done:
    ret

    ;; constants
hello_str db 'protected mode ok', 0    
BOOT_DISK: db 0                 

;;variables
CODE_SEG equ gdt_code  - gdt_start
DATA_SEG equ gdt_data - gdt_start


times 510-($-$$) db 0  ; Pad the rest of the 512-byte sector with zeros
dw 0xAA55              ; Boot sector signature

Code: Select all

;; kernel.asm

[bits 32]

;; not working also !

;;section .data

;;    hello_kernel db 'kernel ok', 0   

section .text



;; it works !
    mov ah, 0x0F
    mov al, hello_kernel  ; Load the address of the string
    mov [0xB8000 + 160], eax   ; Video memory starts at address 0xB8000

 ;;it doesnt works !
   mov esi, hello_kernel  ; Load the address of the string
   mov edi, 0xB8000    ; Video memory starts at address 0xB8000
   call print_string



jmp $

print_string:
    lodsb               ; Load byte at address DS:SI into AL
    test al, al
    jz .done            ; If null terminator, finish
    mov ah, 0x0F        ; Attribute byte: white on black
    stosw               ; Store word at ES:DI (AL=character, AH=attribute)
    jmp print_string
.done:
    ret

hello_kernel db 'kernel ok', 0   

times 510-($-$$) db 0  ; Pad the rest of the 512-byte sector with zeros
 


Code: Select all

  

# Define the targets and their source files
BOOTLOADER = boot
BOOTLOADER_SRC = boot.asm

KERNEL_IMG = kernel
KERNEL_SRC = kernel.asm

ASM = nasm
ASM_FLAGS = -f bin -g

# Default target to build both bootloader and kernel image
all: $(BOOTLOADER) $(KERNEL_IMG)

# Rule to build the bootloader
$(BOOTLOADER): $(BOOTLOADER_SRC)
	$(ASM) $(ASM_FLAGS) $(BOOTLOADER_SRC) -o $(BOOTLOADER).bin

# Rule to build the kernel image
$(KERNEL_IMG): $(KERNEL_SRC)
	$(ASM) $(ASM_FLAGS) $(KERNEL_SRC) -o $(KERNEL_IMG).bin

# Clean rule to remove both binaries
clean:
	rm -f $(BOOTLOADER).bin $(KERNEL_IMG).bin os.img disk.img

image:

	# Create an empty 1.44MB disk image
	dd if=/dev/zero of=disk.img bs=512 count=2880

	# Write the boot binary to the first sector
	dd if=$(BOOTLOADER).bin of=disk.img bs=512 count=1 conv=notrunc

	# Write the kernel binary to the appropriate sector (sector 2)
	dd if=$(KERNEL_IMG).bin of=disk.img bs=512 count=1 seek=1 conv=notrunc


image_old:
	dd if=$(BOOTLOADER).bin of=$(BOOTLOADER).img bs=512 count=1	
	dd if=$(KERNEL_IMG).bin of=$(KERNEL_IMG).img bs=512 count=1	
	cat $(BOOTLOADER).img $(KERNEL_IMG).img > os.img




run:
	make all
	make image
	qemu-system-x86_64 -fda  disk.img -s -S

run_old:
	make all
	make image_old
	qemu-system-x86_64 -fda  os.img

.PHONY: all clean





I am using make run to build and execute the kernel. Despite reviewing various threads and resources, I haven’t been able to find a solution related to the jump from real mode to protected mode.

Thank you very much for your assistance.

Best regards,
[Your Name]


I alread try to read some threads here and also I got this code as example ;

https://github.com/mell-o-tron/OS-Reference/tree/main/7

but the buy is doing in the kernel.cpp exactly as I am doing in the block which works.

Re: Questions about string after jump to kernel in protected mode

Posted: Thu Aug 01, 2024 12:45 am
by Octocontrabass
Antonio82 wrote: Wed Jul 31, 2024 3:16 amIn kernel.asm, I am able to print the character 'H' directly to the video buffer. However, when I attempt to print a string, it does not work as expected. I suspect this issue may be related to segmentation.
The issue is how you link your kernel. You're using NASM's internal flat binary linker, so you need to tell NASM where in memory your kernel will be loaded. For your bootloader, you're using an org statement, which works because you only have one section. Your kernel has multiple sections, so you need to either add qualifiers to your sections to tell NASM how you're loading them or tell NASM to output a different format like ELF and use a separate linker to create the flat binary.
Antonio82 wrote: Wed Jul 31, 2024 3:16 am[Your Name]
Hello, ChatGPT.
Antonio82 wrote: Wed Jul 31, 2024 3:16 amI alread try to read some threads here and also I got this code as example ;
That example tells NASM to output ELF and uses a separate linker (i386-elf-ld) to create the flat binary.

Re: Questions about string after jump to kernel in protected mode

Posted: Thu Aug 01, 2024 3:50 am
by Antonio82
Hello,
Many thanks ! and i am sorry about the GPT :D sometimes I an lazy to write and correct grammar ! thank you so much
i come back here with the answer to make the post solved

Re: Questions about string after jump to kernel in protected mode

Posted: Thu Aug 01, 2024 6:40 am
by Antonio82
Hello, my make file now is like this :

Code: Select all

# Define the targets and their source files
BOOTLOADER = boot
BOOTLOADER_SRC = boot.asm

KERNEL_IMG = kernel
KERNEL_SRC = kernel.asm

ASM = nasm
ASM_FLAGS = -f bin -g

# Default target to build both bootloader and kernel image
all: $(BOOTLOADER) $(KERNEL_IMG)

# Rule to build the bootloader
$(BOOTLOADER): $(BOOTLOADER_SRC)
	$(ASM) $(ASM_FLAGS) $(BOOTLOADER_SRC) -o $(BOOTLOADER).bin

# Rule to build the kernel image
$(KERNEL_IMG): $(KERNEL_SRC)
	$(ASM) -f elf -o $(KERNEL_IMG).o $(KERNEL_SRC) 
	ld -m elf_i386 -T linker.ld --oformat binary -o $(KERNEL_IMG).bin $(KERNEL_IMG).o 

# Clean rule to remove both binaries
clean:
	rm -f *.bin *.o *.img *.elf 

# make all images
image:

	# Create an empty 1.44MB disk image
	dd if=/dev/zero of=disk.img bs=512 count=2880

	# Write the boot binary to the first sector
	dd if=$(BOOTLOADER).bin of=disk.img bs=512 count=1 conv=notrunc

	# Write the kernel binary to the appropriate sector (sector 2)
	dd if=$(KERNEL_IMG).bin of=disk.img bs=512 count=1 seek=1 conv=notrunc

# make everything and run using qemu
run:
	make all
	make image
	qemu-system-x86_64 -fda  disk.img 

# clean everything
.PHONY: all clean

As you can see now i am linking ( I installed gcc multilib since the i386-elf-ld is quite messy )

the kernel itself is being build like this :

Code: Select all

$(KERNEL_IMG): $(KERNEL_SRC)
	$(ASM) -f elf -o $(KERNEL_IMG).o $(KERNEL_SRC) 
	ld -m elf_i386 -T linker.ld --oformat binary -o $(KERNEL_IMG).bin $(KERNEL_IMG).o 

I also did a link script like this

Code: Select all

ENTRY(start)
SECTIONS
{
  . = 0x1000;
  .text : {
    *(.text)
  }
  .rodata : {
    *(.rodata)
  }
  .data : {
    *(.data)
  }
  .bss : {
    *(.bss)
  }
}

now the following code is working

Code: Select all



[bits 32]

section .data


section .text

global start

start:
 ;;it doesnt works !
   mov esi, hello_kernel  ; Load the address of the string
   mov edi, 0xB8000+80    ; Video memory starts at address 0xB8000
   call print_string



jmp $

print_string:
    lodsb               ; Load byte at address DS:SI into AL
    test al, al
    jz .done            ; If null terminator, finish
    mov ah, 0x0F        ; Attribute byte: white on black
    stosw               ; Store word at ES:DI (AL=character, AH=attribute)
    jmp print_string
.done:
    ret

hello_kernel db 'starting kernel', 0   

times 510-($-$$) db 0  ; Pad the rest of the 512-byte sector with zeros


however if i insert a .data section and put the variable there it wont work either ( like following )

Code: Select all


 section .data
       hello_kernel db 'starting kernel', 0   
section .text

I already try to specify

Code: Select all


  .data : {
    . = 0x1000;
    *(.data)
  }

but after I parsed again the GDT struct and the base address is 0, however making like following also don't fix

Code: Select all

  .data : {
    .=0	
    *(.data)
  

 
I need to do something to load also the data segment or something like that ?

The example in :

https://github.com/mell-o-tron/OS-Reference/tree/main/7

Also only specify where the text start but not the data section at

Code: Select all

	i386-elf-ld -o "Binaries/full_kernel.bin" -Ttext 0x1000 "Binaries/kernel_entry.o" "Binaries/kernel.o" --oformat binary
is it because it is a constant and I am trying to put in .data ?


just to state -elf32 is not working either in the kernel compilation

Thank you so much, I really appreciate your help !

Antonio

Re: Questions about string after jump to kernel in protected mode

Posted: Thu Aug 01, 2024 10:23 am
by Octocontrabass
Antonio82 wrote: Thu Aug 01, 2024 6:40 amI need to do something to load also the data segment or something like that ?
How big is your kernel binary? You're only copying 512 bytes into your disk image, and you're only loading 1024 bytes from the disk.

Have you tried disassembling your kernel binary or stepping through it with a debugger? You can check the addresses to see if there was a problem during linking.
Antonio82 wrote: Thu Aug 01, 2024 6:40 amis it because it is a constant and I am trying to put in .data ?
If you know it's a constant, it's better to use .rodata, but .data should work too.

Re: Questions about string after jump to kernel in protected mode

Posted: Fri Aug 02, 2024 3:53 am
by Antonio82
Hello,
Many thanks ! all works ! it was the process of how I create the images, you were right ! thank you so much !

Following I will put the complete code here to people have the complete stuff working in case they check this question

1 - boot.asm

Code: Select all

org 0x7C00  ; Origin, as bootloaders start at this address

[BITS 16]  ; 16-bit real mode

start:

    mov [BOOT_DISK], dl  ; Store the boot disk number (from DL register) into 
                         ; BOOT_DISK

    ; Display a message to indicate this is the first file
    mov bp, welcome_str
    call print_mem
    call timer

    mov ebx, 0x1000  ; Set EBX register to 0x1000
    mov dh, 2  ; Set DH register to read 2 sectors

    ; BIOS interrupt function 0x02 - Read sectors
    mov ah, 0x02  ; Function 0x02: read sectors
    mov al, dh  ; Number of sectors to read (stored in DH)
    mov ch, 0x00  ; Cylinder number (0)
    mov dh, 0x00  ; Head number (0)
    mov cl, 0x02  ; Starting sector (2)
    mov dl, [BOOT_DISK]  ; Disk number (boot disk)
    int 0x13  ; Interrupt 0x13 - BIOS disk services

    ; Check for error
    jc disk_read_error  ; If carry flag is set, there was an error

    ; Stop interruptions
    cli  ; Clear interrupt flag
    ; Set the video mode to text mode (80x25 16-color)
    mov ah, 0x0
    mov al, 0x3
    int 0x10

    ; Load the descriptor table
    lgdt [gdt_descriptor]

    ; Turn on protected mode
    mov eax, cr0
    or eax, 1
    mov cr0, eax

    jmp CODE_SEG:flush_pipeline

disk_read_error:
    ; Display an error message
    mov bp, disk_error_str
    call print_mem
    hlt  ; Halt the CPU

print_mem:
    mov si, 0 
loop:
    ; Check null pointer
    mov al, bp[si]
    cmp al, 0
    je final

    ; BIOS printing service
    mov ah, 0x0e
    mov bh, 0x00
    mov bl, 0x07
    int 10h
    
    ; Next element
    inc si
    jmp loop
final:
    ret

timer:
    ; Wait this time that actually is not very precise
    mov cx, 0x000F
    mov dx, 0x4240
    mov al, 0
    mov ah, 0x86
    int 0x15
    ret

gdt_start:
gdt_null:
    dd 0  ; Null descriptor: all zeros
    dd 0
gdt_code:
    dw 0xFFFF  ; Limit (15:0)
    dw 0x0000  ; Base (15:0)
    db 0x00    ; Base (23:16)
    db 10011010b  ; Access byte: present, ring 0, executable, readable
    db 11001111b  ; Flags: 4K granularity, 32-bit segment, Limit (19:16)
    db 0x00    ; Base (31:24)
gdt_data:
    dw 0xFFFF  ; Limit (15:0)
    dw 0x0000  ; Base (15:0)
    db 0x00    ; Base (23:16)
    db 10010010b  ; Access byte: present, ring 0, writable
    db 11001111b  ; Flags: 4K granularity, 32-bit segment, Limit (19:16)
    db 0x00    ; Base (31:24)
gdt_end:

gdt_descriptor:
    dw gdt_end - gdt_start - 1  ; Size of GDT - 1
    dd gdt_start  ; Address of GDT

[bits 32]

flush_pipeline:
    mov ax, DATA_SEG
    mov ds, ax       
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ss, ax

    mov esi, hello_str  ; Load the address of the string
    mov edi, 0xB8000  ; Video memory starts at address 0xB8000
    call print_string
    jmp 0x1000

print_string:
    lodsb  ; Load byte at address DS:SI into AL
    test al, al
    jz .done  ; If null terminator, finish
    mov ah, 0x0F  ; Attribute byte: white on black
    stosw  ; Store word at ES:DI (AL=character, AH=attribute)
    jmp print_string
.done:
    ret

; Constants
welcome_str db 'welcome!', 0
disk_error_str db 'DISC ERROR!!!', 0
hello_str db 'protected mode ok', 0

BOOT_DISK: db 0

; Variables
CODE_SEG equ gdt_code - gdt_start
DATA_SEG equ gdt_data - gdt_start

times 510 - ($ - $$) db 0  ; Pad the rest of the 512-byte sector with zeros
dw 0xAA55  ; Boot sector signature


2 - kernel.asm

Code: Select all


[bits 32]

section .data

    hello_kernel db 'starting kernel', 0   

section .text

global start

start:
   mov esi, hello_kernel  ; Load the address of the string
   mov edi, 0xB8000+80    ; Video memory starts at address 0xB8000
   call print_string



jmp $

print_string:
    lodsb               ; Load byte at address DS:SI into AL
    test al, al
    jz .done            ; If null terminator, finish
    mov ah, 0x0F        ; Attribute byte: white on black
    stosw               ; Store word at ES:DI (AL=character, AH=attribute)
    jmp print_string
.done:
    ret    


times 510-($-$$) db 0  ; Pad the rest of the 512-byte sector with zeros



3 - Makefile

Code: Select all


# Define the targets and their source files
BOOTLOADER = boot
BOOTLOADER_SRC = boot.asm

KERNEL_IMG = kernel
KERNEL_SRC = kernel.asm

ASM = nasm
ASM_FLAGS = -f bin -g

# Default target to build both bootloader and kernel image
all: $(BOOTLOADER) $(KERNEL_IMG)

# Rule to build the bootloader
$(BOOTLOADER): $(BOOTLOADER_SRC)
	$(ASM) $(ASM_FLAGS) $(BOOTLOADER_SRC) -o $(BOOTLOADER).bin

# Rule to build the kernel image
$(KERNEL_IMG): $(KERNEL_SRC)
	$(ASM) -f elf32 -o $(KERNEL_IMG).o $(KERNEL_SRC) 
	ld -m elf_i386 -T linker.ld --oformat binary -o $(KERNEL_IMG).bin $(KERNEL_IMG).o 

# Clean rule to remove both binaries
clean:
	rm -f *.bin *.o *.img *.elf 

# make all images
image:
	cat boot.bin kernel.bin  > os.bin

# make everything and run using qemu
run:
	qemu-system-x86_64 os.bin

# clean everything
.PHONY: all clean


4 - linker script

Code: Select all


ENTRY(start)
SECTIONS
{
  . = 0x1000;
  .text : {
    *(.text)
  }
  .rodata : {
    *(.rodata)
  }
  .data : {
    *(.data)
  }
  .bss : {
    *(.bss)
  }
}