7

According to this tutorial it is sufficient to create a simple operating system with switching to protected mode as simple as the following code without the need for other well known actions such as enabling A20...

Anyway, I am newbie to this domain, I wrote the following code as they mentioned exactly with the modification inspired from this SO.

Code Structure: This simple operating system should load briefly as follows:

  1. Load/read 15 sectors
  2. Enable GDT
  3. Switch to protected mode (And print "Successfully landed in 32-bit Protected Mode").
  4. Load kernel and print "X"

However the emulator is still rebooting. Please find enclosed the entire code.

bootloader.asm

[bits 16]
[org 0x7C00]

KERNEL_OFFSET equ 0x1000

xor ax, ax
mov ds, ax
mov es, ax
mov [BOOT_DRIVE], dl
mov ax, 0x07E0                  ; End of stack
cli
mov ss, ax
mov sp, 0x1200                  ; Size of Stack. By this, we assume that stack starts at 9000h
                            ; of size 1200h and ends at 7E00h to avoid being overwritten.
sti

call    load_kernel
call    switch_to_pm

jmp $

%include "src/functions/disk_load.asm"

load_kernel:
    mov bx, KERNEL_OFFSET   
    mov dh, 15
    mov dl, [BOOT_DRIVE]
    call    disk_load

    ret

; Global variables
BOOT_DRIVE  db 0
SECTORS     db 0
MSG_PROT_MODE   db "Successfully landed in 32-bit Protected Mode" , 0

%include "src/functions/gdt.asm"
%include "src/functions/switch_to_pm.asm"

[ bits 32]
; This is where we arrive after switching to and initialising protected mode.
BEGIN_PM:
    mov ebx , MSG_PROT_MODE
    call    print_string_pm     ; Use our 32 - bit print routine.


    ;call   KERNEL_OFFSET       ; Now jump to the address of our loaded
                    ; kernel code , assume the brace position ,
                    ; and cross your fingers. Here we go !

    jmp $           ; Hang.

%include "src/functions/writing_video_mode.asm"

; Bootsector padding
times 510-($-$$) db 0
dw 0xAA55

; 15 sector padding
times 15*256 dw 0xDADA

disk_load.asm

disk_load:
    mov [SECTORS], dh
    mov ch, 0x00            ;C=0
    mov dh, 0x00            ;H=0
    mov cl, 0x02            ;S=2

next_group:
    mov di, 5                ;Max 5 tries

again: 
    mov ah, 0x02            ;Read sectors
    mov al, [SECTORS]
    int 0x13
    jc   maybe_retry
    sub [SECTORS], al ;Remaining sectors
    jz  ready
    mov cl, 0x01            ;Always sector 1
    xor dh, 1                ;Next head on diskette!
    jnz next_group
    inc ch                      ;Next cylinder
    jmp next_group

maybe_retry:
    mov ah, 0x00            ;Reset diskdrive
    int 0x13
    dec di
    jnz again
    jmp disk_error

ready:
    ret

disk_error:
    mov ah, 0x0e
    mov al, 'Y'
    int 0x10
    jmp $

DISK_ERROR_MSG db "Disk read error!", 0

gdt.asm

gdt_start:

    gdt_null:
        dd 0x0 ; ’ dd ’ means define double word ( i.e. 4 bytes )
        dd 0x0

    gdt_code:
        dw 0xffff
        dw 0x0
        db 0x0
        db 10011010b ; 1 st flags , type flags
        db 11001111b ; 2 nd flags , Limit ( bits 16 -19)
        db 0x0

    gdt_data:
        dw 0xffff
        dw 0x0
        db 0x0
        db 10010010b ; 1 st flags , type flags
        db 11001111b ; 2 nd flags , Limit ( bits 16 -19)
        db 0x0

    gdt_end:

    gdt_descriptor:
        dw gdt_end - gdt_start - 1
        dd gdt_start

CODE_SEG equ gdt_code - gdt_start
DATA_SEG equ gdt_data - gdt_start

switch_to_pm.asm

[ bits 16 ]

switch_to_pm:
    cli
    lgdt [ gdt_descriptor ]
    mov eax , cr0
    or eax , 0x1
    mov cr0 , eax
    jmp CODE_SEG:init_pm

[ bits 32 ]

init_pm:
    mov ax, DATA_SEG
    mov ds, ax
    mov ss, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ebp , 0x90000
    mov esp , ebp
    call BEGIN_PM

And in order to make sure that we landed in the protected mode:

writing_video_mode.asm

[ bits 32]

VIDEO_MEMORY equ 0xb8000
WHITE_ON_BLACK equ 0x0f

print_string_pm:
    push eax
    push ebx
    push edx
    mov edx , VIDEO_MEMORY ; Set edx to the start of vid mem.

    print_string_pm_loop:
        mov al, [ebx]
        mov ah, WHITE_ON_BLACK
        cmp al, 0
        je print_string_pm_done
        mov [edx], ax
        add ebx, 1
        add edx, 2
        jmp print_string_pm_loop

    print_string_pm_done:
        pop edx 
        pop ebx 
        pop eax 
        ret

kernel.c

void main () {
    char * video_memory = (char *) 0xb8000;
    *video_memory = 'X';
}

By the way, I am using this Makefile:

all: bootloader.bin kernel.bin

bootloader.bin: src/bootloader.asm
    nasm src/bootloader.asm -f bin -o output/bootloader.bin

kernel.o: src/kernel/kernel.c
    gcc -ffreestanding -c src/kernel/kernel.c -o output/kernel.o -m32

kernel.bin: kernel.o
    ld -o output/kernel.bin -Ttext 0x1000 --oformat binary output/kernel.o -melf_i386

clean:
    rm -f output/*.* output/*

and in order to move it into the flash memory, I use these commands:

cat output/bootloader.bin output/kernel.bin > os-image
sudo dd if=os-image of=/dev/sdb bs=512 conv=notrunc && sync

In order to run it, I am using qemu with this command:

qemu-system-i386 -hda /dev/sdb

Noting that /dev/sdb is my flash memory drive.

Problem: Indeed, the code is landing into the protected mode (I.e. Printing "Successfully landed in 32-bit Protected Mode") just when disabling/commenting the call KERNEL_OFFSET in the bootloader.asm. However when enabling this line it starts booting and rebooting.

I hope I have provided every needed information. It seems for me that the far jump should not be done this way. Any comment is appreciated.

Community
  • 1
  • 1
  • What code does GCC generate for *kernel.c*? It isn't 64-bit code, is it? Worth taking a look with `objdump -D`. Plus have you took care of the interrupts? (I haven't read the code admittedly) – Margaret Bloom Feb 27 '17 at 11:04
  • Use a debugger (the one built-in to `bochs` for example) to see what's happening. – Jester Feb 27 '17 at 11:14
  • @MargaretBloom, you are right. I am compiling on 64-bit. Actually, I am aware of this point but I didn't mention it in the makefile. I handled your point now in the makefile. – محمد جعفر نعمة Feb 27 '17 at 11:41
  • @Jester, thank you dear, I will check it with bochs and be back. – محمد جعفر نعمة Feb 27 '17 at 11:43
  • @Mohamad-jaafar Note that options go before operands when you invoke `gcc`. So the file name, `src/kernel/kernel.c` is the last thing you put on the command line. – fuz Feb 27 '17 at 14:27
  • 2
    @fuz gcc doesn't care. To quote the [manual](https://linux.die.net/man/1/gcc): _You can mix options and other arguments. For the most part, the order you use doesn't matter._ – Jester Feb 27 '17 at 15:20
  • 1
    Comment out `times 15*256 dw 0xDADA` in bootloader.asm. You create an image that has a 512 byte boot sector. You then pad it with 15 more sectors and then you concatenate both with `cat output/bootloader.bin output/kernel.bin > os-image` . Your kernel will be placed outside of the first 16 sectors. – Michael Petch Feb 27 '17 at 15:21
  • 1
    @Jester : Correct, because GCC isn't POSIX compliant. – Michael Petch Feb 27 '17 at 15:22
  • And besides my last comment about removing the padding,you place your stack pointer in real mode at 0x0000:0x1200. It will grow down from there. Problem is that your stack is going to be clobbered when you read your sectors into 0x0000:0x1000. You might want to set your _SS:SP_ to 0x0000:0x0000 so that the stack will grow down from the top of the first 64k. Anywhere is preferable than putting it on top of where you read your sectors. – Michael Petch Feb 27 '17 at 15:48
  • Great. Thank you @MichaelPetch It is solved. You are right, the problem was with padding. – محمد جعفر نعمة Feb 27 '17 at 20:37
  • 1
    You really need to fix the stack issue as well or it will come back to bite you in the butt. – Michael Petch Feb 27 '17 at 20:38
  • Thank you again dear @MichaelPetch. You could suggest your solution as answer so that I accept it. – محمد جعفر نعمة Feb 27 '17 at 22:33

1 Answers1

1

Just remove

times 15*256 dw 0xDADA

(btw, why DADA?)
then compile your kernel, after that

cat output/bootloader.bin output/kernel.bin > os-image

and somehow make your os image 8192 byte long (16 sectors, bootloader + 15). I'm not Linux/Unix fan (even can't use them), but I think dd command (something like dd if=dev\zero of=temp_file count=(8192 - file actual size), and then cat os-image temp-file > os-image) should do the job. I'm also not sure is this compilation command correct (only not sure). I would remove "-melf_i386" from linker command, but idk, I have only used MinGW on Windows (it's only similar to GCC).

Sorry for my bad English, I hope I helped.

TheNNX
  • 26
  • 2
  • If using DD it is generally just preferred to create the entire disk image first then place the files you need at specific locations. Some ideas can be found in this SO answer: http://stackoverflow.com/a/34108769/3857942 . – Michael Petch Mar 25 '17 at 22:45
  • As for `-melf_i386` that is required if you happen to be developing with a 64-bit compiler and linker. 64-bit tool chains will default to generating 64-bit code and objects. Specifying `-melf_i386` specifically targets objects that are 32-bit. Using 32-bit objects for doing 32 and 16-bit code comes with less hassles. If you are generating 64-bit code then of course you don't want to use `-melf_i386`. This is also the reason why GCC is compiling files with `-m32`. If you generate 32-bit objects you can't link them with other 64-bit objects and you will get a linker error. – Michael Petch Mar 25 '17 at 22:49
  • If you use a GCC that generates 32-bit programs (and not 64-bit) then you don't need to specify `-melf_i386` or `-m32` because those are the default for those compilers. This also holds true for i386, i486, i586, i686 (etc) cross compilers. – Michael Petch Mar 25 '17 at 22:51
  • 1
    If you read the answer I linked to in my first comment I actually have a section on using _DD_ (the one from Chrysocome) for Windows, and where it can be downloaded from. It uses the same command line syntax as the Linux versions. The only place where Chrysocome's DD differs from Linux is if you want to write directly to physical medium (like a USB drive etc). – Michael Petch Mar 26 '17 at 08:19
  • Oh, thanks. You right, I didn't read it. I started reading 2nd comment and forgot about it. But even without that I created .bat to compile it and in my case `dd` looks like `dd bs=1 if=/dev/zero of=temp_file count=%sizeoftempfile%`, where sizeoftempfile is 8192-os-image size (file made of `cat bootloader.bin kernel.bin > os-image`) and then just `cat os-image temp_file > finaloutput` – TheNNX Mar 26 '17 at 09:13