Now that we can read files from the disk, we want to be able to run them, as is the point of an operating system. I tried build a c library but haven’t gotten it to work quite yet, so for now user programs are compiled with no library, and I supply a few helper functions.

The main programs are written as normal C programs, just without a library. The syscalls are defined in an assembly file, which just loads the arguments and calls the syscall interrupt. The normal c program starts with a main(), as usual, but main() is not the start of a c program, it is actually _start. Usually this would be located in crt0.o, which would set up argc and argv, call main(), handle the return value, and then exit the program. As I am not using a c library I do this all myself too, with the syscalls. I compile and link that all together and end up with an ELF file, which gets bundled in the disk image when the vm is run.

To execute a program, the program is found by starting at the root directory and parsing the path until the inode is found. We then map two different memory sections: One section at 0x8000000, where the program will be ran, and one at 0xC000000, which is 4mb above that. Since physical memory is just one program after another, I just temporarily use the space where the next program would be. Once the entire file is loaded, the elf header and program headers are read. The elf header is right at the beginning of the file, and holds information about the file like any flags, what the target system and abi the file is for, as well as some magic numbers to identify it. It also contains the entry point, where execution should start. The location and size of program headers are included too.

Program headers contain information about segments of the program, what type they are, where they should be loaded, etc. If there is only one program header than this is irrelevant, but in my cause I am using static variables which are located not on the stack but in a different section, which has a new program header. Each program header has its offset in the file, its size, and where in memory it wants to be. For each program header, I copy the appropriate data from the temporary mapping where the entire file is loaded into the location where it wants to be.

Once all the sections are loaded, I enter user mode at the entry point specified in the elf header and let it rip.

Entering user mode is a little weird, see here.