Finally.. I could touch this project again! I am so happy and excited to develop again!!!!
We need some "housekeepings" to do. I have to reorganize/revise the (entire) makefile system(because I don't like this current system)! I will be incorporating some of the makefile systems I developed in the FOSD project, to make my life a bit easier.
Originally, the kernel loader had their individual makefile for building and folder. Now I want to integrate the building process of the loaders. I made a simple header-like makefile that automatically compiles the C/C++ sources and even links the object into ELF file.
# build_loader_common.mk
##### Required arguments :
# LOADER_CC : gcc(or g++) compiler for the loader
# LOADER_LD : the ld compiler for the loader
# LOADER_AS : the assembler "
# LOADER_CCOPTIONS : gcc(or g++) compiler options
# LOADER_ASOPTIONS : assembler options
# LOADER_LDOPTIONS : ld compiler options
# LOADER_LINKERSCRIPT : path to the linker script
# LOADER_LD_OUT : output file name for the ld compiler
# C_EXTENSION : C/C++ source file's extension
# AS_EXTENSION : assembly source file's extension
# SOURCESFOLDER : the path to the source folder
# INCLUDEPATHS : includepaths for the C/C++ sources
# LINK_OBJECTS[yes/no] : select whether the compiled C/C++ objects will be linked
# If this options is set to "yes," compiled objects are linked using the ld options.
...
Basically, what you need to make a loader is just to include this build_loader_common.mk file and fill out those required variables prior to including it. Because this build_loader_common.mk includes all the necessary operations for compiling C/C++ sources, we can reduce the overall redundancy in building the loader!
LOADER_CC = x86_64-elf-gcc
LOADER_LD = x86_64-elf-ld
LOADER_AS = nasm
IMGMAKER = ./imgmaker
OBJCOPY = x86_64-elf-objcopy
LOADER_CCOPTIONS = -m32 -Os -masm=intel -fno-builtin -Wno-implicit-function-declaration -Wno-int-to-pointer-cast -W -Wall -fpack-struct=1 -I$(HEADERSFOLDER) -fno-stack-protector
LOADER_LDOPTIONS = -m elf_i386
LOADER_ASOPTIONS = -f elf32
LOADER_LINKERSCRIPT = linker.ld
LOADER_LD_OUT = $(BINARYFOLDER)/Loader.elf
...
remove_target:
rm -rf $(TARGET)
build_bootloader:
$(LOADER_AS) bootloader.asm -f bin -o $(BINARYFOLDER)/bootloader.bin
build_final_image:
$(OBJCOPY) -O binary $(LOADER_LD_OUT) $(BINARYFOLDER)/$(KERNEL_LOADER)
$(IMGMAKER) $(TARGET) $(BINARYFOLDER)/bootloader.bin $(BINARYFOLDER)/$(KERNEL_LOADER) $(ROOTDIR)/$(KERNEL_IMG_LOCATION)/$(KERNEL_IMG)
...
include $(ROOTDIR)/build_loader_common.mk
Other than the change in makefile system, I also changed the directory structure for loader from having individual loader in individual directory to having loaders with same architecture together in one folder.
Similar to the loader, I created a common makefile for building the kernel sources. You can just declare some required variables and include the makefile, making a complete makefile with "clean" and "all" sections automatically created.
The kernel is compiled in a bit different way than the loader, however. There are separate directories that all contains the kernel sources, which are later all to be linked. Those directories has a bit different structures, mainly differed by their purposes. For core kernel sources, the source files are separated into source and headers. For integrated drivers, unlike core kernel, they are separated by each drivers, as shown in this image :
So, although these two has lots of things in common, they bit differ by the directory structures. To integrate these differences, I made a variable that indicate (to the common build makefile script) whether this is a core kernel source or a driver source.
# For kernel core...
COMPILE_TYPE = kernel
SEARCH_LOCATION = $(ARCH)/$(COMMON_SRCFOLDER)
INCLUDEPATHS = -I $(ROOTDIR)/$(ARCHFOLDER)/$(ARCH)/ -I $(ROOTDIR)/$(ARCHFOLDER)/$(ARCH)/$(COMMON_HEADERFOLDER) -I $(ROOTDIR)/$(KERNELFOLDER)/$(COMMON_HEADERFOLDER)
include $(ROOTDIR)/build_kernel_common.mk
#################################################
# For drivers...
COMPILE_TYPE = driver
INCLUDEPATHS = -I $(ROOTDIR)/$(ARCHFOLDER)/$(ARCH)/ -I $(ROOTDIR)/$(KERNELFOLDER)/$(COMMON_HEADERFOLDER) -I $(ROOTDIR)/$(ARCHFOLDER)/$(ARCH)/$(COMMON_HEADERFOLDER) -I $(dir $<)
include $(ROOTDIR)/build_kernel_common.mk
The build_kernel_common.mk, based on the given information, automatically searches the sources, compiles them and store them in the "bin" folder. The stored objects are later linked all into one elf file.
I also made it so that the linking process differs by architectures. Each architecture folder must contain "build_kernel_img.mk" file, which must link the given objects into the kernel image. It's just a simple way to make a building process more flexible.
I finally fixed the chronic problem that lingered since the beginning of my OS development.. which is the problem of determining the "entry point."
When the kernel binary image is linked, we need to make sure that the entry point of the kernel is located at the very front of the image. I accomplished this (very stupidly) by manually changing the order in which the objects are compiled, making sure that the object file that contains the entry point is linked first. This is obviously very crude fix for this bug -- if I just make a function before the main entry function, the operating system would completely crash because it jumped to other weird function when loading.
I FINALLY fixed this problem by making a new section and locating it on the very start of the file. If we make a new section, locate it on the start of the kernel, and place the entry function on the section, we can guarentee that the entry function will be located on that exact location. Finally, a reliable fix that will not involve manually placing the object files in linking process...
(And by the way, I thought that the "ENTRY" keyword in linker script would place the function on the front, but in reality, the keyword was just for determining the entry point in the ELF file. )
ENTRY(kernel_main) /* actually useless */
INCLUDE arch_configurations.ld
SECTIONS {
. = _KERNEL_LOAD_LOCATION;
.entry _KERNEL_LOAD_LOCATION : { /* entry section, placed foremost in the kernel */
*(.entry) *(.entry.*)
}
.text ALIGN(4096) : {
*(.text) *(.text.*)
}
.data ALIGN(4096) : {
__data_start__ = .;
*(.data)
__data_end__ = .;
}
.bss ALIGN(4096) : {
__bss_start__ = .;
*(.bss) *(.rodata) *(.bss.*) *(COMMON)
__bss_end__ = .;
}
}
Now, we just have to locate the kernel_main function in the entry section, which we can do with __attribute__ keyword...
extern "C" __attribute__ ((section(".entry"))) void kernel_main(unsigned long kernel_argument_struct_addr) {
...
Finally, I can no longer worry about this chronic problem of entry point...
I also added a script that converts the hpp file into ld file, allowing the linker script to access the global configuration values(such as kernel load address.) I originally developed this system in FOSD project, loved very much, so incorporated it into my kernel!
define convert_hpp_to_ld
echo \#include \"$(notdir $(1))\" > $(dir $(1))dummy.ld
$(KERNEL_CC) -E -P -xc -DLINKER_SCRIPT $(dir $(1))dummy.ld > $(subst .hpp,.ld,$(1))
rm $(dir $(1))dummy.ld
endef
Basically, it creates a dummy ld script that just includes the target header file(that will be converted) and let the gcc convert this hybrid between C++ header and linker script into complete linker script. Simple yet powerful!