When I have to setup the EIP register, the program doesn't jump to the correct position. I'm expecting that jmp *%ecx
jumps at the right spot in memory setting EIP to around 0xC0100000 (label: StartInHigherHalf
) using LEA. I don't think that kmain
is necessary because the problem is before it is called. I'm going to post it anyway.
I've tried to debug it with the -d cpu
flag on QEMU, and before the jump (blocked with HLT) says that ECX is not loaded with the LEA function. Is it possible for LEA instruction not to execute? Why can that happen? How can I fix it?
Boot.S:
.set ALIGN, 1<<0
.set MEMINFO, 1<<1
.set FLAGS, ALIGN | MEMINFO
.set MAGIC, 0x1BADB002
.set CHECKSUM, -(MAGIC + FLAGS)
.set KERNEL_VIRTUAL_BASE, 0xC0000000
.set KERNEL_PAGE_NUMBER, (KERNEL_VIRTUAL_BASE >> 22)
.section .multiboot
.align 4
.long MAGIC
.long FLAGS
.long CHECKSUM
.section .data
.align 0x1000
BootPageDirectory:
.quad 0x00000083
.rept KERNEL_PAGE_NUMBER - 1
.quad 0
.endr
.quad 0x00000083
.rept 0x400 - KERNEL_PAGE_NUMBER - 1
.quad 0
.endr
.set STACKSIZE, 0x4000
.global __start__
.set __start__, (setup)
setup:
mov $(BootPageDirectory - KERNEL_VIRTUAL_BASE), %ecx
mov %ecx, %cr3
mov %cr4, %ecx
or $0x00000010, %ecx
mov %ecx, %cr4
mov %cr0, %ecx
or $0x80000000, %ecx
mov %ecx, %cr0
lea StartInHigherHalf, %ecx
jmp *%ecx
StartInHigherHalf:
movl $0, (BootPageDirectory)
invlpg (0)
mov $(stack + STACKSIZE), %esp
push %eax
push %ebx
call _init
call kmain
cli
1: hlt
jmp 1
.section .bss
.align 32
.lcomm stack, STACKSIZE
.global gdtFlush
.extern gp
gdtFlush:
lgdt (gp)
mov $0x10, %eax
mov %eax, %ds
mov %eax, %es
mov %eax, %gs
mov %eax, %fs
mov %eax, %ss
ljmp $0x08, $setcs
setcs:
ret
linker.ld:
ENTRY(__start__)
OUTPUT_FORMAT(elf32-i386)
SECTIONS {
. = 0xC0100000;
.text ALIGN(0x1000) : AT(ADDR(.text) - 0xC0000000) {
*(.multiboot)
*(.text)
}
.rodata ALIGN(0x1000) : AT(ADDR(.rodata) - 0xC0000000) {
*(.rodata*)
}
.data ALIGN(0x1000) : AT(ADDR(.data) - 0xC0000000) {
*(.data)
}
.bss ALIGN(0x1000) : AT(ADDR(.bss) - 0xC0000000) {
_sbss = .;
*(COMMON)
*(.bss)
_ebss = .;
}
}
kmain.c:
#include <kernel/tty.h>
#include <kernel/log.h>
#include <kernel/stack-protector.h>
#include <kernel/gdt.h>
__attribute__ ((noreturn));
void kmain(void) {
gdtInstall();
initializeTerminal();
char c;
char *buffer = &c;
char *start = buffer;
char str[] = "Hello, kernel World!";
atomicallyLog(1, 1, str, buffer);
kprintf(start, 1);
}
If we assume for a moment that your code actually reaches lea StartInHigherHalf, %ecx
, and don't think it actually executed this instruction - The most obvious issue would be paging related. The LEA instruction happens to be the first instruction to execute after the paging bit is set. If paging is wrong then likely the next instruction will fault.
Reviewing your page directory, I noticed that you build it using .quad
(8 byte type) instead of .int
(4-byte type). Each entry in the page directory is 4 bytes, not 8. This likely is the main cause of your problems. You can avoid the .rept
macro by just using the .fill
directive:
.fill repeat , size , value
result, size and value are absolute expressions. This emits repeat copies of size bytes. Repeat may be zero or more. Size may be zero or more, but if it is more than 8, then it is deemed to have the value 8, compatible with other people's assemblers. The contents of each repeat bytes is taken from an 8-byte number. The highest order 4 bytes are zero. The lowest order 4 bytes are value rendered in the byte-order of an integer on the computer as is assembling for. Each size bytes in a repetition is taken from the lowest order size bytes of this number. Again, this bizarre behavior is compatible with other people's assemblers.
size and value are optional. If the second comma and value are absent, value is assumed zero. If the first comma and following tokens are absent, size is assumed to be 1.
Your Boot Page Directory could be written as:
.align 0x1000
BootPageDirectory:
.int 0x00000083
.fill KERNEL_PAGE_NUMBER - 1, 4, 0
.int 0x00000083
.fill 0x400 - KERNEL_PAGE_NUMBER - 1, 4, 0
.rept
works as well:
.align 0x1000
BootPageDirectory:
.int 0x00000083
.rept KERNEL_PAGE_NUMBER - 1
.int 0
.endr
.int 0x00000083
.rept 0x400 - KERNEL_PAGE_NUMBER - 1
.int 0
.endr
Your code seems to be a variant of the OSDev Higher Half x86 Bare Bones kernel. The main difference appears to be that you converted from NASM to GNU Assembler. I've written about a major deficiency of this code on the OSDev Forum. The way it is structured, all the addresses are generated assuming everything is higher half, rather than splitting the lower half that is at 0x100000 from what is in the higher half at 0xC0100000. This design leads to using Mulitboot in a way that isn't really defined by the Multiboot specification.
You can resolve this by using the linker script to separate the two and put appropriate sections in boot.S
. A linker script (linker.ld
) could look like:
ENTRY(setup)
OUTPUT_FORMAT(elf32-i386)
KERNEL_VIRTUAL_BASE = 0xC0000000;
SECTIONS {
/* The multiboot data and code will exist in low memory
starting at 0x100000 */
. = 0x00100000;
.lowerhalf ALIGN(0x1000) : {
*(.lowerhalf.data)
*(.lowerhalf.text)
}
/* The kernel will live at 3GB + 1MB in the virtual
address space, which will be mapped to 1MB in the
physical address space. */
. += KERNEL_VIRTUAL_BASE;
.text ALIGN(0x1000) : AT(ADDR(.text) - KERNEL_VIRTUAL_BASE) {
*(.text)
}
.data ALIGN (0x1000) : AT(ADDR(.data) - KERNEL_VIRTUAL_BASE) {
*(.data)
*(.rodata*)
}
.bss ALIGN (0x1000) : AT(ADDR(.bss) - KERNEL_VIRTUAL_BASE) {
_sbss = .;
*(COMMON)
*(.bss)
_ebss = .;
}
/DISCARD/ : {
*(.eh_frame);
*(.comment*);
}
}
Your boot.S
file could be written like:
.set ALIGN, 1<<0
.set MEMINFO, 1<<1
.set FLAGS, ALIGN | MEMINFO
.set MAGIC, 0x1BADB002
.set CHECKSUM, -(MAGIC + FLAGS)
.set KERNEL_VIRTUAL_BASE, 0xC0000000
.set KERNEL_PAGE_NUMBER, (KERNEL_VIRTUAL_BASE >> 22)
.section .lowerhalf.data,"aw",@progbits
.align 4
.long MAGIC
.long FLAGS
.long CHECKSUM
.align 0x1000
BootPageDirectory:
.int 0x00000083
.fill KERNEL_PAGE_NUMBER - 1, 4, 0
.int 0x00000083
.fill 0x400 - KERNEL_PAGE_NUMBER - 1, 4, 0
.set STACKSIZE, 0x4000
.section .lowerhalf.text,"axw",@progbits
.global setup
setup:
mov $BootPageDirectory, %ecx
mov %ecx, %cr3
mov %cr4, %ecx
or $0x00000010, %ecx
mov %ecx, %cr4
mov %cr0, %ecx
or $0x80000000, %ecx
mov %ecx, %cr0
jmp StartInHigherHalf
.section .text
StartInHigherHalf:
movl $0, (BootPageDirectory)
invlpg (0)
mov $(stack + STACKSIZE), %esp
push %eax
push %ebx
#call _init
call kmain
cli
1: hlt
jmp 1
/*
.global gdtFlush
.extern gp
gdtFlush:
lgdt (gp)
mov $0x10, %eax
mov %eax, %ds
mov %eax, %es
mov %eax, %gs
mov %eax, %fs
mov %eax, %ss
ljmp $0x08, $setcs
setcs:
ret
*/
.section .bss
.align 32
.lcomm stack, STACKSIZE
A do nothing kernel (kernel.c
) could look like:
volatile unsigned short int *const video_mem = (unsigned short int *)0xc00b8000;
void kmain(void) {
/* print MDP in upper left of display using white on magenta */
video_mem[0] = (0x57 << 8) | 'M';
video_mem[1] = (0x57 << 8) | 'D';
video_mem[2] = (0x57 << 8) | 'P';
while (1) __asm__ ("hlt");
}
You can generate an ELF executable with:
i686-elf-gcc -c -g -m32 boot.S -o boot.o
i686-elf-gcc -c -g -m32 -O3 kernel.c -o kernel.o -ffreestanding -std=gnu99 \
-mno-red-zone -fno-exceptions -Wall -Wextra
i686-elf-gcc -nostdlib -Wl,--build-id=none -T linker.ld boot.o kernel.o -lgcc -o kernel.elf
Comments:
kernel.elf
can be loaded by GRUB (in an ISO file) or directly with QEMU's -kernel
option.StartInHigherHalf
directly. You can still use the LEA / JMP method but it doesn't gain anything.gdtFlush
in the .bss
section. It needs to be moved into the .text
section. I have done so in the code, but I have also commented it out to get this do nothing kernel working with paging. You will need to add it back in to work with your codebase. call _init
to get this do nothing kernel working. You will need to remove the comment to integrate it into your codebase.User contributions licensed under CC BY-SA 3.0