Repository: fwsGonzo/barebones Branch: master Commit: abb810ef9437 Files: 48 Total size: 78.8 KB Directory structure: gitextract_wcbe4rrg/ ├── .gitignore ├── .gitmodules ├── README.md ├── barebones.cmake ├── build_iso.sh ├── ext/ │ └── CMakeLists.txt ├── grub/ │ └── grub.cfg ├── install_ubuntu_deps.sh ├── machines/ │ ├── 32bit/ │ │ ├── CMakeLists.txt │ │ └── main.cpp │ ├── default/ │ │ ├── CMakeLists.txt │ │ └── main.cpp │ ├── eastl/ │ │ ├── CMakeLists.txt │ │ └── main.cpp │ └── shared/ │ ├── CMakeLists.txt │ ├── README.md │ ├── library/ │ │ ├── CMakeLists.txt │ │ ├── interface.hpp │ │ └── library.cpp │ └── main.cpp ├── run.sh ├── src/ │ ├── CMakeLists.txt │ ├── crt/ │ │ ├── c_abi.c │ │ ├── c_stubs.c │ │ ├── cxxabi.cpp │ │ ├── heap.c │ │ ├── malloc.c │ │ ├── print.c │ │ ├── ubsan.c │ │ └── udiv.c │ ├── hw/ │ │ ├── serial.h │ │ └── serial1.c │ ├── kernel/ │ │ ├── dylib.hpp │ │ ├── elf.cpp │ │ ├── elf.hpp │ │ ├── init_libc.c │ │ ├── kernel_start.c │ │ ├── panic.cpp │ │ ├── start.asm │ │ ├── start32.c │ │ ├── start64.asm │ │ ├── tls.cpp │ │ └── tls.hpp │ ├── kprint.h │ ├── linker.ld │ ├── main.h │ └── multiboot.h └── tools/ └── Makefile ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ *.o *.d chainloader temp_disk grub.iso build/ ================================================ FILE: .gitmodules ================================================ [submodule "ext/EASTL"] path = ext/EASTL url = https://github.com/electronicarts/EASTL.git [submodule "ext/tinyprintf"] path = ext/tinyprintf url = git@github.com:fwsGonzo/tinyprintf.git ================================================ FILE: README.md ================================================ # Barebones multiboot kernel - Intended for beginner OS development - Run the dependency installation script (or install the equivalent packages) - Build and boot a tiny test kernel in Qemu with `./run.sh default`. - There is a 32-bit test kernel as well: `./run.sh 32bit` - You can run any of the machines in the `machines` folder this way. - If building with the NATIVE option (or anything that requires AVX), remember to run with KVM enabled (./run.sh --kvm) so that Qemu will present the modern instruction set features to the guest operating system. - VGA textmode using ./run.sh --vga (NOTE: you must implement VGA support yourself! You will (if you're lucky) see a black screen.) - Use ./build_iso.sh to run on real hardware or a hypervisor like VirtualBox, but keep serial port logging in mind! ## Features - 600kb stack and working GDT (with room for IST task) - Multiboot parameters passed to kernel start - assert(), kprintf() and snprintf() - Very basic heap implementation (malloc/free, new/delete) - C and C++ global constructors, C++ RTTI and exceptions - Stack protector support - EASTL C++ support, which has many useful containers - Produces tiny machine images - Machine image is 6944 bytes with minimal build on GCC 9.1 - Machine image is 6376 bytes with minimal LTO build on Clang 8 - Remember to disable stack protector to shave a few extra bytes off! - LTO and ThinLTO support if you are using clang - Undefined-sanitizer support to catch some problems during runtime - Option to unmap zero page (for the very common null-pointer access bugs) - You have to enable this yourself, after CPU exception handling ## Running Use `./run.sh ` to build and run your kernel stored in machines folder. - By default run.sh will build and run the `default` machine. The project is located in `machines/default/` and most of the output comes from `main.cpp`. - Use argument --kvm if you want to use hardware-accelerated virtualization, or you have built with -march=native in which you must use that. Enable virtualization in your BIOS settings if you haven't. - Use argument --vga to get a graphical window, which starts out in VGA textmode accessible at 0xb8000. - Use argument --sanitize to enable the undefined-sanitizer, catching problems like misaligned accesses and overflows. ## Changing build options Go into the build folder in your machines folder and type `ccmake ..`, which opens a GUI with some adjustable settings. After changing settings press C to configure the changes, and then E to exit the GUI. Changes will be rebuilt automatically using run.sh, or you could simply use `make` like normally in the build folder itself. ## Goal The goal is to provide a barebones kernel project that implements the most basic C/C++ voodoo to let people start reading/writing to registers and devices immediately. The goal is also to provide people with a choice of using C or C++, or a mix of both. ## Future work - Add TLS api and support for most thread local variables - Add support for LTO with GCC (no ideas on how to do this yet) - Optional 512b bootloader to avoid GRUB (although not recommended) ## Validate output ./run.sh output should match: ``` ------------------ * Multiboot EAX: 0x2badb002 * Multiboot EBX: 0x9500 * SSE instructions ... work! * Global constructors ... work! Hello OSdev world! my_kernel (This is a test kernel!) Press Ctrl+A -> X to close Returned from kernel_start! Halting... ``` ## Common issues - File format not recognized - You changed linker or ELF architecture without rebuilding all the files - Nothing happens when I start the kernel - If you are building with -march=native, you need to enable KVM and run the kernel natively, because the machine code is full of extended instructions (like AVX) - I can't use normal C/C++ library functions - Make sure you are using something that exists in EASTL, or you will have to implement it yourself - I can write to the zero page - Yes you can. Welcome to (identity-mapped) kernel space. Set up your own pagetables! - Do I really need a cross-compiler to work on this project? - No, that is a misconception that the OSdev community holds dear to its heart, but its not necessary. The Linux-native compilers will use FS/GS for TLS, and will require you to fake the table that (among other things) contain the stack sentinel value. Otherwise, you can reasonably expect normal library calls to be optimized (printf -> write). - When booting the GRUB image nothing happens, it looks to be crashing etc. - Make sure you didn't compile with GCC and then link with LLD, and especially don't mix LTO into this. - Minimal builds can be risky, especially -Oz on clang. - There could be a genuine issue, so let me know. - I'm having problems compiling or linking when using exceptions! - Make sure that you use your own exceptions, and don't rely on anything that belongs to the C++ standard library, which is not present - I keep getting errors about not finding `bits/c++config.h` when building for 32-bit! - You will need to install the C++ multilib package for your compiler, such as `g++-8-multilib`. ## Undefined sanitizer - Works on GCC and Clang - Easy to support, but has some caveats - It's miles better if you can resolve symbol addresses to names - Has not been extensively verified ## Link-Time Optimization - Works on Clang out of the box, just make sure you use the correct LLD that comes with your compiler - Both ThinLTO and full LTO works! - Enable LTO option with ccmake and set the LINKER_EXE option to point to a lld executable that is compatible with your Clang version. For example `ld.lld-8`. - Does not work on GCC at all due to how complicated it is to call the LTO-helpers manually ## Thread-Local Storage - The basic foundation has been laid down - Linker script needs to be amended to store .tbss and .tdata sections - Functionality for creating new thread storage needs to be created ## EASTL, RTTI and exceptions - To make use of EASTL you will need to enable the EASTL CMake option - Tons of useful containers that work right out of the box - To use RTTI and exceptions in C++ you will need to enable the RTTI_EXCEPTIONS option - This option bloats the binary a great deal, due to the C++ ABI bundles, as well as the libgcc dependency ## Qemu defaults Qemu provides you with some default devices to start with - IDE for disk devices, but you can use PCI to get information - e1000-compatible network card - If you add -vga std you will get VGA graphics area at 0xb8000 - In graphics mode: PS/2 keyboard and mouse As an example, use this function to clear the VGA textmode screen: ``` uint16_t* vga = (uint16_t*) 0xb8000; for (int i = 0; i < 25*80; i++) { vga[i] = ' ' | (7 << 8); } ``` What it's actually doing is filling the text buffer with spaces. Spaces that have a gray color. You just can't see them, because spaces are spaces. ================================================ FILE: barebones.cmake ================================================ option(NATIVE "Compile natively for this CPU" OFF) option(MINIMAL "Compile small executable" OFF) option(EASTL "Compile with EASTL C++ library" OFF) option(LTO_ENABLE "Enable LTO (Clang-only)" OFF) option(STACK_PROTECTOR "Enable stack protector (SSP)" ON) option(UBSAN "Enable the undefined sanitizer" OFF) option(STRIPPED "Strip the executable" OFF) option(DEBUG "Build and preserve debugging information" OFF) option(RTTI_EXCEPTIONS "Enable C++ RTTI and exceptions" OFF) option(BUILD_32 "Build a 32-bit kernel" OFF) set(CPP_VERSION "c++17" CACHE STRING "C++ version compiler argument") set(C_VERSION "gnu11" CACHE STRING "C version compiler argument") set(LINKER_EXE "ld" CACHE STRING "Linker to use") if (BUILD_32) set(ELF_FORMAT "i386") set(CMAKE_ASM_NASM_OBJECT_FORMAT "elf32") set(OBJCOPY_TARGET "elf32-x86-32") set(TARGET_TRIPLE "i686-pc-linux") set(CAPABS "-m32") else() set(ELF_FORMAT "x86_64") set(CMAKE_ASM_NASM_OBJECT_FORMAT "elf64") set(OBJCOPY_TARGET "elf64-x86-64") set(TARGET_TRIPLE "x86_64-pc-linux") set(CAPABS "-m64 -fno-omit-frame-pointer -fPIE") endif() enable_language(ASM_NASM) set(CAPABS "${CAPABS} -Wall -Wextra -g -ffreestanding") # Optimization flags set(OPTIMIZE "-mfpmath=sse -msse3") if (NATIVE) set(OPTIMIZE "${OPTIMIZE} -Ofast -march=native") elseif (MINIMAL) if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") set(OPTIMIZE "${OPTIMIZE} -Oz") else() set(OPTIMIZE "${OPTIMIZE} -Os -ffunction-sections -fdata-sections") endif() endif() set(CMAKE_CXX_FLAGS "-MMD ${CAPABS} ${OPTIMIZE} -std=${CPP_VERSION}") set(CMAKE_C_FLAGS "-MMD ${CAPABS} ${OPTIMIZE} -std=${C_VERSION}") if (LTO_ENABLE) if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -flto=thin") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -flto=thin") else() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -flto") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -flto") endif() # BUG: workaround for LTO bug set(KERNEL_LIBRARY --whole-archive kernel --no-whole-archive -gc-sections) else() set(KERNEL_LIBRARY kernel) endif() if (NOT RTTI_EXCEPTIONS) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-exceptions -fno-rtti") endif() if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -target ${TARGET_TRIPLE}") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -target ${TARGET_TRIPLE}") endif() # Sanitizer options if (UBSAN) set(UBSAN_PARAMS "-fsanitize=undefined -fno-sanitize=vptr -DUBSAN_ENABLED") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${UBSAN_PARAMS}") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${UBSAN_PARAMS}") if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-sanitize=function") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fno-sanitize=function") endif() endif() if (STACK_PROTECTOR) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fstack-protector-strong") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fstack-protector-strong") endif() # linker stuff set(CMAKE_LINKER ${LINKER_EXE}) set(CMAKE_SHARED_LIBRARY_LINK_CXX_FLAGS) # this removed -rdynamic from linker output set(CMAKE_CXX_LINK_EXECUTABLE " -o ") set(BBPATH ${CMAKE_CURRENT_LIST_DIR}) # linker arguments string(RANDOM LENGTH 16 ALPHABET 0123456789abcdef SSP_VALUE) set(LDSCRIPT "${BBPATH}/src/linker.ld") set(LDFLAGS "-static -nostdlib -N -melf_${ELF_FORMAT} --script=${LDSCRIPT} --defsym __SSP__=0x${SSP_VALUE}") if (NOT DEBUG AND STRIPPED) set(LDFLAGS "${LDFLAGS} -s") elseif (NOT DEBUG) set(LDFLAGS "${LDFLAGS} -S") endif() if (MINIMAL) set(LDFLAGS "${LDFLAGS} --gc-sections") endif() # Compiler, C and C++ libraries include(ExternalProject) ExternalProject_Add(exceptions PREFIX exceptions URL https://github.com/fwsGonzo/barebones/releases/download/exceptions/exceptions.zip URL_HASH SHA1=8851485a7134eb8743069439235c1a2a9728ea58 CONFIGURE_COMMAND "" BUILD_COMMAND "" UPDATE_COMMAND "" INSTALL_COMMAND "" ) # gcc-9 --print-file-name libgcc.a if (BUILD_32) execute_process(COMMAND ${CMAKE_C_COMPILER} -m32 --print-file-name libgcc_eh.a OUTPUT_VARIABLE LIBGCC_ARCHIVE OUTPUT_STRIP_TRAILING_WHITESPACE) else() execute_process(COMMAND ${CMAKE_C_COMPILER} -m64 --print-file-name libgcc_eh.a OUTPUT_VARIABLE LIBGCC_ARCHIVE OUTPUT_STRIP_TRAILING_WHITESPACE) endif() add_library(libgcc STATIC IMPORTED) set_target_properties(libgcc PROPERTIES LINKER_LANGUAGE CXX) if (EXISTS ${LIBGCC_ARCHIVE}) set_target_properties(libgcc PROPERTIES IMPORTED_LOCATION ${LIBGCC_ARCHIVE}) else() # Use this if you don't have a compatible libgcc_eh.a in your system: set_target_properties(libgcc PROPERTIES IMPORTED_LOCATION exceptions/src/exceptions/libgcc.a) endif() add_dependencies(libgcc exceptions) if (RTTI_EXCEPTIONS) add_library(cxxabi STATIC IMPORTED) set_target_properties(cxxabi PROPERTIES LINKER_LANGUAGE CXX) set_target_properties(cxxabi PROPERTIES IMPORTED_LOCATION exceptions/src/exceptions/libc++abi.a) add_dependencies(cxxabi exceptions) # Add --eh-frame-hdr for exception tables set(LDFLAGS "${LDFLAGS} --eh-frame-hdr") set(CXX_ABI_LIBS cxxabi) endif() # Machine image creation function(add_machine_image NAME BINARY_NAME BINARY_DESC) add_executable(${NAME} ${ARGN}) set_target_properties(${NAME} PROPERTIES OUTPUT_NAME ${BINARY_NAME}) target_include_directories(${NAME} PRIVATE src) target_compile_definitions(${NAME} PRIVATE KERNEL_BINARY="${BINARY_NAME}") target_compile_definitions(${NAME} PRIVATE KERNEL_DESC="${BINARY_DESC}") add_subdirectory(${BBPATH}/ext ext) if (EASTL) target_link_libraries(${NAME} eastl) endif() add_subdirectory(${BBPATH}/src src) target_link_libraries(${NAME} # BUG: unfortunately, there is an LLD bug that prevents ASM objects # from linking with outside files that are in archives, unless they are # --whole-archived, which sort of defeats the purpose of LTO ${KERNEL_LIBRARY} tinyprintf ${CXX_ABI_LIBS} libgcc kernel ) set_target_properties(${NAME} PROPERTIES LINK_FLAGS "${LDFLAGS}") # write out the binary name to a known file to simplify some scripts file(WRITE ${CMAKE_BINARY_DIR}/binary.txt ${BINARY_NAME}) endfunction() function(create_payload LIB_NAME BLOB_FILE BLOB_NAME) # call objcopy with curated filename, and then # create a static library LIB_NAME with payload set(OBJECT_FILE ${BLOB_NAME}_generated.o) add_custom_command( OUTPUT ${OBJECT_FILE} # temporarily name blob file as BLOB_NAME so that we can better control # the symbols generated on the inside of the kernel COMMAND cp ${BLOB_FILE} ${BLOB_NAME} COMMAND ${CMAKE_OBJCOPY} -I binary -O ${OBJCOPY_TARGET} -B i386 ${BLOB_NAME} ${OBJECT_FILE} COMMAND rm -f ${BLOB_NAME} DEPENDS ${BLOB_FILE} WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} ) add_library(${LIB_NAME} STATIC ${OBJECT_FILE}) set_target_properties(${LIB_NAME} PROPERTIES LINKER_LANGUAGE C) endfunction() function (add_machine_payload TARGET LIB_NAME BLOB_FILE BLOB_NAME) create_payload(${LIB_NAME} ${BLOB_FILE} ${BLOB_NAME}) target_link_libraries(${TARGET} --whole-archive mylib_payload --no-whole-archive) endfunction() ================================================ FILE: build_iso.sh ================================================ #!/bin/bash set -e MACHINE=machines/${1-default} BUILD_DIR=$MACHINE/build mkdir -p $BUILD_DIR pushd $BUILD_DIR cmake .. make -j4 $OPTION popd KERNEL=$BUILD_DIR/`cat $BUILD_DIR/binary.txt` LOCAL_DISK=temp_disk # create grub.iso mkdir -p $LOCAL_DISK/boot/grub cp $KERNEL $LOCAL_DISK/boot/mykernel cp grub/grub.cfg $LOCAL_DISK/boot/grub echo "=>" grub-mkrescue -o grub.iso $LOCAL_DISK echo "grub.iso constructed" ================================================ FILE: ext/CMakeLists.txt ================================================ # EASTL C++ library if (EASTL) add_library(eastl STATIC EASTL/source/allocator_eastl.cpp EASTL/source/assert.cpp EASTL/source/fixed_pool.cpp EASTL/source/hashtable.cpp EASTL/source/intrusive_list.cpp EASTL/source/numeric_limits.cpp EASTL/source/red_black_tree.cpp EASTL/source/string.cpp ) target_include_directories(eastl PUBLIC EASTL/include EASTL/test/packages/EABase/include/Common ) endif() # Tiny-printf library add_library(tinyprintf STATIC tinyprintf/tinyprintf.c ) target_include_directories(tinyprintf PUBLIC tinyprintf ) target_compile_definitions(tinyprintf PUBLIC TINYPRINTF_OVERRIDE_LIBC=0) ================================================ FILE: grub/grub.cfg ================================================ set timeout=0 set default=0 # Set the default menu entry menuentry "My kernel" { multiboot /boot/mykernel boot } ================================================ FILE: install_ubuntu_deps.sh ================================================ #!/bin/bash git submodule update --init --recursive sudo apt install build-essential g++-multilib nasm qemu xorriso cmake cmake-curses-gui ================================================ FILE: machines/32bit/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.1) project (kernel C CXX) option(BUILD_32 "" ON) include(../../barebones.cmake) add_machine_image( # name, binary and description mykernel "my_kernel" "This is a 32-bit test kernel!" # list of source files main.cpp ) target_compile_definitions(mykernel PRIVATE MYTEST="4") ================================================ FILE: machines/32bit/main.cpp ================================================ #include #include #include #include static bool test_sse(); static int test_value = 0; void kernel_main(const uint32_t eax, const uint32_t ebx) { kprint("------------------\n"); kprintf("* Multiboot EAX: 0x%x\n", eax); kprintf("* Multiboot EBX: 0x%x\n", ebx); // some helpful self-checks kprint("* SSE instructions ... "); kprint(test_sse() ? "work!\n" : "did NOT work!\n"); kprint("* Global constructors ... "); kprint(test_value ? "work!\n" : "did NOT work!\n"); #ifdef UBSAN_ENABLED // when undefined sanitizer is enabled, we can manually trigger it like this uint64_t test = 0; char* misaligned = (char*) &test + 2; *(uint32_t*) misaligned = 0x12345678; kprintf("kernel_main is at %p\n", kernel_main); kprintf("misaligned is %p\n", (void*) test); #endif kprint( "\n" "Hello OSdev world!\n" KERNEL_BINARY " (" KERNEL_DESC ")\n" "\n" "Press Ctrl+A -> X to close\n" ); } __attribute__((constructor)) static void my_cpp_constructor() { test_value = 1; } bool test_sse() { typedef union { __m128i i128; int32_t i32[4]; } imm; volatile imm xmm1; xmm1.i128 = _mm_set_epi32(1, 2, 3, 4); return ( xmm1.i32[0] == 4 && xmm1.i32[1] == 3 && xmm1.i32[2] == 2 && xmm1.i32[3] == 1); } ================================================ FILE: machines/default/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.1) project (kernel C CXX) include(../../barebones.cmake) add_machine_image( # name, binary and description mykernel "my_kernel" "This is a test kernel!" # list of source files main.cpp ) target_compile_definitions(mykernel PRIVATE MYTEST="4") ================================================ FILE: machines/default/main.cpp ================================================ #include #include #include #include static bool test_sse(); static int test_value = 0; void kernel_main(const uint32_t eax, const uint32_t ebx) { kprint("------------------\n"); kprintf("* Multiboot EAX: 0x%x\n", eax); kprintf("* Multiboot EBX: 0x%x\n", ebx); // some helpful self-checks kprint("* SSE instructions ... "); kprint(test_sse() ? "work!\n" : "did NOT work!\n"); kprint("* Global constructors ... "); kprint(test_value ? "work!\n" : "did NOT work!\n"); #ifdef UBSAN_ENABLED // when undefined sanitizer is enabled, we can manually trigger it like this uint64_t test = 0; char* misaligned = (char*) &test + 2; *(uint32_t*) misaligned = 0x12345678; kprintf("kernel_main is at %p\n", kernel_main); kprintf("misaligned is %p\n", (void*) test); #endif kprint( "\n" "Hello OSdev world!\n" KERNEL_BINARY " (" KERNEL_DESC ")\n" "\n" "Press Ctrl+A -> X to close\n" ); } __attribute__((constructor)) static void my_cpp_constructor() { test_value = 1; } bool test_sse() { typedef union { __m128i i128; int32_t i32[4]; } imm; volatile imm xmm1; xmm1.i128 = _mm_set_epi32(1, 2, 3, 4); return ( xmm1.i32[0] == 4 && xmm1.i32[1] == 3 && xmm1.i32[2] == 2 && xmm1.i32[3] == 1); } ================================================ FILE: machines/eastl/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.1) project (kernel C CXX) option(EASTL "" ON) option(RTTI_EXCEPTIONS "" ON) include(../../barebones.cmake) add_machine_image( # name, binary and description mykernel "eastl_kernel" "This is a test for EASTL!" # list of source files main.cpp ) ================================================ FILE: machines/eastl/main.cpp ================================================ #include #include #include #include #include class IdioticException : public std::exception { const char* oh_god; public: IdioticException(const char* reason) : oh_god(reason) {} const char* what() const noexcept override { return oh_god; } }; using callback_t = int (*) (eastl::vector&); static void test_rtti(); void kernel_main(uint32_t /*eax*/, uint32_t /*ebx*/) { kprintf(KERNEL_DESC "\n"); const callback_t callback = [] (auto& vec) -> int { kprintf("EASTL vector size: %zu (last element is %d)\n", vec.size(), vec.back()); throw IdioticException{"Test!"}; }; eastl::vector test; int caught = 0; for (int i = 0; i < 16; i++) { test.push_back(i); try { assert(callback(test) == 0); } catch (const std::exception& e) { kprintf("Exceptions caught: %d\n", ++caught); } } assert(caught == 16); test_rtti(); throw IdioticException("This is on purpose"); } class A { public: virtual void f() { kprintf("A::f() called\n"); } }; class B : public A { public: void f() { kprintf("B::f() called\n"); } }; static void test_rtti() { A a; B b; a.f(); // A::f() b.f(); // B::f() A *pA = &a; B *pB = &b; pA->f(); // A::f() pB->f(); // B::f() pA = &b; // pB = &a; // not allowed pB = dynamic_cast(&a); // allowed but it returns NULL assert(pB == nullptr); } ================================================ FILE: machines/shared/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.1) project (kernel C CXX) include(../../barebones.cmake) add_machine_image( # name, binary and description mykernel "my_kernel" "This is a test kernel!" # list of source files main.cpp ) #target_compile_definitions(mykernel PRIVATE MYTEST="4") add_subdirectory(library) add_machine_payload(mykernel mylib_payload ${CMAKE_BINARY_DIR}/library/libmylib.so mylib) add_dependencies(mylib_payload mylib) ================================================ FILE: machines/shared/README.md ================================================ ## Machine with shared library loader This tiny machine allows you to call into a function in a shared library, and just enough to make it usable. No constructors or destructors. ## Questions - Why is the init function using the C calling convention? - It's to make it easier to look up the functions name, as it won't be mangled. - Can you look up functions from the shared library? - No, and that is ideally something done at loading time where functions are resolved automatically, which is something called dynamic linking. ================================================ FILE: machines/shared/library/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.1) project(mylib VERSION 1.0 DESCRIPTION "mylib description") add_library(mylib SHARED library.cpp ) # unfortunately we have to do this to shake some of the arguments from the # top level project set(CMAKE_CXX_FLAGS "-Wall -Wextra -g -O2 -ffreestanding -fno-omit-frame-pointer") target_compile_options(mylib PRIVATE "-nostdlib") target_compile_options(mylib PRIVATE "-Os") set_target_properties(mylib PROPERTIES VERSION ${PROJECT_VERSION}) set_target_properties(mylib PROPERTIES SOVERSION 1) set_target_properties(mylib PROPERTIES PUBLIC_HEADER library.hpp) target_include_directories(mylib PRIVATE .) ================================================ FILE: machines/shared/library/interface.hpp ================================================ #pragma once #include struct jumptable { int (*kprintf)(const char* string, ...); void* (*malloc) (size_t); void (*free) (void*); }; typedef int (*init_function_t) (jumptable*); ================================================ FILE: machines/shared/library/library.cpp ================================================ #include "interface.hpp" static int test_value = 666; struct Test { int a; }; Test test; extern "C" __attribute__((noinline)) void test_function(jumptable* table) { table->kprintf("Hello from test()! jumptable = %p\n", table); } extern "C" int init(jumptable* t) { jumptable* table = t; table->kprintf("Hello world from a shared library! table = %p\n", table); table->kprintf("test_value == %d\n", test_value); char* data = (char*) table->malloc(4096); table->kprintf("malloc() returned %p\n", data); for (int i = 0; i < 4096; i++) { data[i] = i & 0xFF; } table->free(data); test.a = 55; test_function(t); return 0; } ================================================ FILE: machines/shared/main.cpp ================================================ #include #include #include #include #include extern char _binary_mylib_start; extern char _binary_mylib_end; #include #include "library/interface.hpp" // a made-up interface void kernel_main(const uint32_t, const uint32_t) { // load address is the start of the mylib blob const auto* hdr = Dylib::load(&_binary_mylib_start); assert(hdr != nullptr); // translate init function name into symbol and create callable function auto init = Dylib::resolve_function(hdr, "init"); assert(init != nullptr); // setup call table static jumptable table; table.kprintf = kprintf; table.malloc = malloc; table.free = free; // call into library function const int result = init(&table); kprintf("init call result: %d\n", result); } ================================================ FILE: run.sh ================================================ #!/bin/bash set -e GRAPHICS=-nographic for i in "$@" do case $i in --kvm) KVM="--enable-kvm -cpu host" shift # past argument with no value ;; --vga) GRAPHICS="-vga std" shift # past argument with no value ;; --sanitize) OPTION="sanitize" shift # past argument with no value ;; --clean) rm -rf $BUILD_DIR/ exit 0 ;; *) # unknown option echo "--kvm, --vga, --sanitize, --clean" ;; esac done MACHINE=machines/${1-default} BUILD_DIR=$MACHINE/build pushd $MACHINE mkdir -p build pushd build cmake .. make -j4 $OPTION BINARY=$BUILD_DIR/`cat binary.txt` popd popd # NOTE: if building with -march=native, make sure to enable KVM, # as emulated qemu only supports up to SSE3 instructions CLASS=`od -An -t x1 -j 4 -N 1 $BINARY` if [ $CLASS == "02" ]; then echo "Starting 64-bit kernel: $BINARY" qemu-system-x86_64 $KVM -kernel tools/chainloader -initrd $BINARY $GRAPHICS else echo "Starting 32-bit kernel: $BINARY" qemu-system-x86_64 $KVM -kernel $BINARY $GRAPHICS fi ================================================ FILE: src/CMakeLists.txt ================================================ set(KERNEL_SOURCES # .c files kernel/kernel_start.c kernel/init_libc.c hw/serial1.c crt/c_abi.c crt/c_stubs.c crt/heap.c crt/malloc.c crt/print.c crt/ubsan.c # .cpp files crt/cxxabi.cpp kernel/elf.cpp kernel/tls.cpp kernel/panic.cpp # .asm files (for NASM) kernel/start.asm ) if (BUILD_32) list(APPEND KERNEL_SOURCES kernel/start32.c crt/udiv.c ) else() list(APPEND KERNEL_SOURCES kernel/start64.asm ) endif() add_library(kernel STATIC ${KERNEL_SOURCES}) target_include_directories(kernel PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(kernel tinyprintf) if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") set_source_files_properties( kernel/panic.cpp PROPERTIES COMPILE_FLAGS -Wno-frame-address) endif() if (RTTI_EXCEPTIONS) target_compile_definitions(kernel PRIVATE -DEH_ENABLED) endif() ================================================ FILE: src/crt/c_abi.c ================================================ #include #include #include #include #include __attribute__((noreturn)) void panic(const char*); void* _impure_ptr = NULL; void __stack_chk_fail_local() { panic("Stack protector: Canary modified"); __builtin_unreachable(); } __attribute__((used)) void __stack_chk_fail() { panic("Stack protector: Canary modified"); __builtin_unreachable(); } void __assert_fail(const char *assertion, const char *file, unsigned int line, const char *function) { char buffer[4096]; snprintf(buffer, sizeof(buffer), "Assertion failed: %s in %s:%u, function %s\n", assertion, file, line, function); panic(buffer); __builtin_unreachable(); } void __assert_func( const char *file, int line, const char *func, const char *failedexpr) { kprintf( "assertion \"%s\" failed: file \"%s\", line %d%s%s\n", failedexpr, file, line, func ? ", function: " : "", func ? func : ""); extern void abort() __attribute__((noreturn)); abort(); } void _exit(int status) { char buffer[64]; snprintf(buffer, sizeof(buffer), "Exit called with status %d\n", status); panic(buffer); __builtin_unreachable(); } __attribute__((used)) void* memset(char* dest, int ch, size_t size) { for (size_t i = 0; i < size; i++) dest[i] = ch; return dest; } void* memcpy(char* dest, const char* src, size_t size) { for (size_t i = 0; i < size; i++) dest[i] = src[i]; return dest; } void* memmove(char* dest, const char* src, size_t size) { if (dest <= src) { for (size_t i = 0; i < size; i++) dest[i] = src[i]; } else { for (int i = size-1; i >= 0; i--) dest[i] = src[i]; } return dest; } int memcmp(const void* ptr1, const void* ptr2, size_t n) { const uint8_t* iter1 = (const uint8_t*) ptr1; const uint8_t* iter2 = (const uint8_t*) ptr2; while (n > 0 && *iter1 == *iter2) { iter1++; iter2++; n--; } return n == 0 ? 0 : (*iter1 - *iter2); } // naive version (needed for EASTL) float ceilf(float n) { long int i = (int) n; return (n == (float) i) ? n : i + 1; } char* strcpy(char* dst, const char* src) { while ((*dst++ = *src++)); *dst = 0; return dst; } size_t strlen(const char* str) { const char* iter; for (iter = str; *iter; ++iter); return iter - str; } int strcmp(const char* str1, const char* str2) { while (*str1 != 0 && *str2 != 0 && *str1 == *str2) { str1++; str2++; } return *str1 - *str2; } char* strcat(char* dest, const char* src) { strcpy(dest + strlen(dest), src); return dest; } void* __memcpy_chk(void* dest, const void* src, size_t len, size_t destlen) { assert (len <= destlen); return memcpy(dest, src, len); } void* __memset_chk(void* dest, int c, size_t len, size_t destlen) { assert (len <= destlen); return memset(dest, c, len); } char* __strcat_chk(char* dest, const char* src, size_t destlen) { size_t len = strlen(dest) + strlen(src) + 1; assert (len <= destlen); return strcat(dest, src); } int isdigit(int c) { return (('0' <= c) && (c <= '9')) ? 1 : 0; } int isalpha(int c) { return ((c > 64 && c < 91) || (c > 96 && c <= 122)) ? 1 : 0; } int isxdigit(int c) { return (isdigit(c) || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f')); } int isupper(int c) { return (c >= 'A' && c <= 'Z'); } ================================================ FILE: src/crt/c_stubs.c ================================================ #define _GNU_SOURCE #include #include #include #include char *getenv(const char *name) { //kprintf("getenv called for %s\n", name); (void) name; return NULL; } int setenv(const char *name, const char *value, int overwrite) { (void) name; (void) value; (void) overwrite; return -1; } int dl_iterate_phdr( int (*callback) (struct dl_phdr_info *info, size_t size, void *data), void *data) { return 0; } ================================================ FILE: src/crt/cxxabi.cpp ================================================ #include #include #include #include extern "C" void* malloc(size_t); extern "C" void free(void*); //#define DEBUG_HEAP #ifdef DEBUG_HEAP #define HPRINT(fmt, ...) kprintf(fmt, __VA_ARGS__) #else #define HPRINT(fmt, ...) /* fmt */ #endif void* operator new(size_t size) { void* res = malloc(size); HPRINT("operator new: size %u => %p\n", size, res); return res; } void* operator new[](size_t size) { void* res = malloc(size); HPRINT("operator new[]: size %u => %p\n", size, res); return res; } void operator delete(void* ptr) { HPRINT("operator delete: %p\n", ptr); free(ptr); } void operator delete[](void* ptr) { HPRINT("operator delete[]: %p\n", ptr); free(ptr); } // C++14 sized deallocation void operator delete(void* ptr, std::size_t) { free(ptr); } void operator delete [](void* ptr, std::size_t) { free(ptr); } extern "C" void __cxa_pure_virtual() { kprintf("Unimplemented pure virtual function called\n"); } /// EASTL glue /// void* operator new[] ( size_t size, /* size */ const char*, /* pName */ int, /* flags */ unsigned, /* debugFlags */ const char*, /* file */ int) /* line */ { void* res = malloc(size); HPRINT("eastl new: size: %u = %p\n", size, res); return res; } void* operator new[] ( size_t size, /* size */ size_t align, /* alignment */ size_t, /* alignmentOffset */ const char*, /* pName */ int, /* flags */ unsigned, /* debugFlags */ const char*, /* file */ int) /* line */ { void* res = malloc(size); HPRINT("eastl aligned new: size %u align %u => %p\n", size, align, res); (void) align; return res; } ================================================ FILE: src/crt/heap.c ================================================ #include #include #include #include static uintptr_t heap_start; static uintptr_t heap_end; // heap_max really should be set to max physical // by reading the value from multiboot meminfo table static uintptr_t heap_max = 0xC0000000; void __init_heap(void* free_begin) { heap_start = (uintptr_t) free_begin; if (heap_start & 0xF) heap_start += 0x10 - (heap_start & 0xF); heap_end = heap_start; assert(((heap_start & 0xF) == 0) && "Heap should be aligned"); } // data segment size void* sbrk(intptr_t increment) { uintptr_t old = heap_end; if (heap_end + increment <= heap_max) { heap_end += increment; return (void*) old; } return (void*) -1; } int posix_memalign(void **memptr, size_t alignment, size_t size) { uintptr_t addr = (uintptr_t) malloc(size + alignment); const intptr_t offset = addr & (alignment-1); if (offset) addr += (alignment-1) - offset; *memptr = (void*) addr; return 0; } ================================================ FILE: src/crt/malloc.c ================================================ // // As written by Snaipe @ https://github.com/Snaipe/malloc // #include #include static inline size_t word_align(size_t size) { return size + ((sizeof(size_t) - 1) & ~(sizeof(size_t) - 1)); } struct chunk { struct chunk *next, *prev; size_t size; int free; void *data; }; typedef struct chunk *Chunk; static void *malloc_base() { static Chunk b = NULL; if (!b) { b = sbrk(word_align(sizeof(struct chunk))); if (b == (void*) -1) { _exit(127); } b->next = NULL; b->prev = NULL; b->size = 0; b->free = 0; b->data = NULL; } return b; } Chunk malloc_chunk_find(size_t s, Chunk *heap) { Chunk c = malloc_base(); for (; c && (!c->free || c->size < s); *heap = c, c = c->next); return c; } void malloc_merge_next(Chunk c) { c->size = c->size + c->next->size + sizeof(struct chunk); c->next = c->next->next; if (c->next) { c->next->prev = c; } } void malloc_split_next(Chunk c, size_t size) { Chunk newc = (Chunk)((char*) c + size); newc->prev = c; newc->next = c->next; newc->size = c->size - size; newc->free = 1; newc->data = newc + 1; if (c->next) { c->next->prev = newc; } c->next = newc; c->size = size - sizeof(struct chunk); } void *malloc(size_t size) { if (!size) return NULL; size_t length = word_align(size + sizeof(struct chunk)); Chunk prev = NULL; Chunk c = malloc_chunk_find(size, &prev); if (!c) { Chunk newc = sbrk(length); if (newc == (void*) -1) { return NULL; } newc->next = NULL; newc->prev = prev; newc->size = length - sizeof(struct chunk); newc->data = newc + 1; prev->next = newc; c = newc; } else if (length + sizeof(size_t) < c->size) { malloc_split_next(c, length); } c->free = 0; return c->data; } void free(void *ptr) { if (!ptr || ptr < malloc_base() || ptr > sbrk(0)) return; Chunk c = (Chunk) ptr - 1; if (c->data != ptr) return; c->free = 1; if (c->next && c->next->free) { malloc_merge_next(c); } if (c->prev->free) { malloc_merge_next(c = c->prev); } if (!c->next) { c->prev->next = NULL; sbrk(- c->size - sizeof(struct chunk)); } } void *calloc(size_t nmemb, size_t size) { size_t length = nmemb * size; void *ptr = malloc(length); if (ptr) { char *dst = ptr; for (size_t i = 0; i < length; *dst = 0, ++dst, ++i); } return ptr; } void *realloc(void *ptr, size_t size) { void *newptr = malloc(size); if (newptr && ptr && ptr >= malloc_base() && ptr <= sbrk(0)) { Chunk c = (Chunk) ptr - 1; if (c->data == ptr) { size_t length = c->size > size ? size : c->size; char *dst = newptr, *src = ptr; for (size_t i = 0; i < length; *dst = *src, ++src, ++dst, ++i); free(ptr); } } return newptr; } ================================================ FILE: src/crt/print.c ================================================ #include #include #include #include FILE* stderr = NULL; FILE* stdout = NULL; char* hex32_to_str(char buffer[], unsigned int val) { char const lut[] = "0123456789ABCDEF"; for (int i = 0; i < 4; i++) { buffer[i*2+0] = lut[(val >> (28-i*8)) & 0xF]; buffer[i*2+1] = lut[(val >> (24-i*8)) & 0xF]; } buffer[8] = 0; return buffer; } char* int32_to_str(char buffer[], int val) { char* b = buffer; // negation if (val < 0) { *b++ = '-'; val *= -1; } // move to end of repr. int tmp = val; do { ++b; tmp /= 10; } while (tmp); *b = 0; // move back and insert as we go do { *--b = '0' + (val % 10); val /= 10; } while (val); return buffer; } int kprintf(const char* fmt, ...) { char buffer[4096]; va_list va; va_start(va, fmt); int ret = tfp_vsnprintf(buffer, sizeof(buffer), fmt, va); va_end(va); kprint(buffer); return ret; } #undef snprintf __attribute__((format (printf, 3, 4))) int snprintf(char *s, size_t maxlen, const char *format, ...) { va_list arg; va_start (arg, format); int bytes = tfp_vsnprintf(s, maxlen, format, arg); va_end (arg); return bytes; } #undef printf #undef fprintf #undef vfprintf int vfprintf(FILE* fp, const char *format, va_list ap) { (void) fp; char buffer[4096]; int len = tfp_vsnprintf(buffer, sizeof(buffer), format, ap); __serial_print(buffer, len); return len; } __attribute__((format (printf, 2, 3))) int fprintf(FILE* stream, const char* fmt, ...) { va_list arg; va_start (arg, fmt); int bytes = vfprintf(stream, fmt, arg); va_end (arg); return bytes; } __attribute__((format(printf, 2, 3))) int __printf_chk (int flag, const char *format, ...) { (void) flag; va_list ap; va_start (ap, format); int bytes = vfprintf (stdout, format, ap); va_end (ap); return bytes; } int __fprintf_chk(FILE* fp, int flag, const char* format, ...) { (void) flag; va_list arg; va_start (arg, format); int bytes = vfprintf(fp, format, arg); va_end (arg); return bytes; } int __vfprintf_chk(FILE* fp, int flag, const char *format, va_list ap) { (void) flag; int bytes = vfprintf (fp, format, ap); return bytes; } int __vsprintf_chk(char* s, int flag, size_t slen, const char* format, va_list args) { (void) flag; int res = tfp_vsnprintf(s, slen, format, args); assert ((size_t) res < slen); return res; } int __vsnprintf_chk (char *s, size_t maxlen, int flags, size_t slen, const char *format, va_list args) { assert (slen < maxlen); (void) flags; return tfp_vsnprintf(s, slen, format, args); } __attribute__((format(printf, 4, 5))) int __sprintf_chk(char* s, int flags, size_t slen, const char *format, ...) { va_list arg; va_start (arg, format); int bytes = __vsprintf_chk(s, flags, slen, format, arg); va_end (arg); return bytes; } int __snprintf_chk (char *s, size_t maxlen, int flags, size_t slen, const char *format, ...) { va_list arg; int done; va_start (arg, format); done = __vsnprintf_chk (s, maxlen, flags, slen, format, arg); va_end (arg); return done; } ================================================ FILE: src/crt/ubsan.c ================================================ #include #include #include extern void panic(const char*) __attribute__((noreturn)); extern void print_backtrace(); struct source_location { const char *file_name; struct { uint32_t line; uint32_t column; }; }; struct type_descriptor { uint16_t type_kind; uint16_t type_info; char type_name[1]; }; const char* type_kind_string[] = { "load of", "store to", "reference binding to", "member access within", "member call on", "constructor call on", "downcast of", "downcast of" }; struct out_of_bounds { struct source_location src; struct type_descriptor* array_type; struct type_descriptor* index_type; }; struct overflow { struct source_location src; }; struct mismatch { struct source_location src; struct type_descriptor* type; unsigned char log_align; unsigned char type_kind; }; struct function_type_mismatch { const struct source_location src; const struct type_descriptor* type; }; struct nonnull_return { struct source_location src; struct source_location attr; }; struct unreachable { struct source_location src; }; static void print_src_location(const struct source_location* src) { kprintf("ubsan: %s at line %u col %u\n", src->file_name, src->line, src->column); } static void undefined_throw(const char* error) { kprintf("ubsan: %s", error); print_backtrace(); kprintf("\n"); } /// Undefined-behavior sanitizer void __ubsan_handle_out_of_bounds(struct out_of_bounds* data) { print_src_location(&data->src); undefined_throw("Out-of-bounds access"); } void __ubsan_handle_missing_return() { undefined_throw("Missing return"); } void __ubsan_handle_nonnull_return(struct nonnull_return* data) { print_src_location(&data->src); undefined_throw("Non-null return"); } void __ubsan_handle_add_overflow() { undefined_throw("Overflow on addition"); } void __ubsan_handle_sub_overflow() { undefined_throw("Overflow on subtraction"); } void __ubsan_handle_mul_overflow() { undefined_throw("Overflow on multiplication"); } void __ubsan_handle_negate_overflow() { undefined_throw("Overflow on negation"); } void __ubsan_handle_pointer_overflow() { undefined_throw("Pointer overflow"); } void __ubsan_handle_divrem_overflow(struct overflow* data, unsigned long lhs, unsigned long rhs) { print_src_location(&data->src); kprintf("ubsan: LHS %lu / RHS %lu\n", lhs, rhs); undefined_throw("Division remainder overflow"); } void __ubsan_handle_float_cast_overflow() { undefined_throw("Float-cast overflow"); } void __ubsan_handle_shift_out_of_bounds() { undefined_throw("Shift out-of-bounds"); } void __ubsan_handle_type_mismatch_v1(struct mismatch* data, uintptr_t ptr) { print_src_location(&data->src); const char* reason = "Type mismatch"; const long alignment = 1 << data->log_align; if (alignment && (ptr & (alignment-1)) != 0) { reason = "Misaligned access"; } else if (ptr == 0) { reason = "Null-pointer access"; } char buffer[2048]; // TODO: resolve symbol name snprintf(buffer, sizeof(buffer), "%s on ptr %p (aligned %lu)\n" "ubsan: type name %s\n", reason, (void*) ptr, alignment, data->type->type_name); undefined_throw(buffer); } void __ubsan_handle_function_type_mismatch( struct function_type_mismatch* data, unsigned long ptr) { print_src_location(&data->src); char buffer[2048]; snprintf(buffer, sizeof(buffer), "Function type mismatch on ptr %p\n" "ubsan: type name %s\n" "ubsan: function %p", (void*) ptr, data->type->type_name, (void*) ptr); // TODO: resolve symbol name undefined_throw(buffer); } void __ubsan_handle_nonnull_arg() { undefined_throw("Non-null argument violated"); } void __ubsan_handle_invalid_builtin() { undefined_throw("Invalid built-in function"); } void __ubsan_handle_load_invalid_value() { undefined_throw("Load of invalid value"); } __attribute__((noreturn)) void __ubsan_handle_builtin_unreachable(struct unreachable* data) { print_src_location(&data->src); panic("Unreachable code reached"); } ================================================ FILE: src/crt/udiv.c ================================================ #include #include typedef unsigned su_int; typedef long long di_int; typedef unsigned long long du_int; typedef union { du_int all; struct { su_int low; su_int high; } s; } udwords; du_int __udivmoddi4(du_int a, du_int b, du_int* rem) { const unsigned n_uword_bits = sizeof(su_int) * CHAR_BIT; const unsigned n_udword_bits = sizeof(du_int) * CHAR_BIT; udwords n; n.all = a; udwords d; d.all = b; udwords q; udwords r; unsigned sr; /* special cases, X is unknown, K != 0 */ if (n.s.high == 0) { if (d.s.high == 0) { /* 0 X * --- * 0 X */ if (rem) *rem = n.s.low % d.s.low; return n.s.low / d.s.low; } /* 0 X * --- * K X */ if (rem) *rem = n.s.low; return 0; } /* n.s.high != 0 */ if (d.s.low == 0) { if (d.s.high == 0) { /* K X * --- * 0 0 */ if (rem) *rem = n.s.high % d.s.low; return n.s.high / d.s.low; } /* d.s.high != 0 */ if (n.s.low == 0) { /* K 0 * --- * K 0 */ if (rem) { r.s.high = n.s.high % d.s.high; r.s.low = 0; *rem = r.all; } return n.s.high / d.s.high; } /* K K * --- * K 0 */ if ((d.s.high & (d.s.high - 1)) == 0) /* if d is a power of 2 */ { if (rem) { r.s.low = n.s.low; r.s.high = n.s.high & (d.s.high - 1); *rem = r.all; } return n.s.high >> __builtin_ctz(d.s.high); } /* K K * --- * K 0 */ sr = __builtin_clz(d.s.high) - __builtin_clz(n.s.high); /* 0 <= sr <= n_uword_bits - 2 or sr large */ if (sr > n_uword_bits - 2) { if (rem) *rem = n.all; return 0; } ++sr; /* 1 <= sr <= n_uword_bits - 1 */ /* q.all = n.all << (n_udword_bits - sr); */ q.s.low = 0; q.s.high = n.s.low << (n_uword_bits - sr); /* r.all = n.all >> sr; */ r.s.high = n.s.high >> sr; r.s.low = (n.s.high << (n_uword_bits - sr)) | (n.s.low >> sr); } else /* d.s.low != 0 */ { if (d.s.high == 0) { /* K X * --- * 0 K */ if ((d.s.low & (d.s.low - 1)) == 0) /* if d is a power of 2 */ { if (rem) *rem = n.s.low & (d.s.low - 1); if (d.s.low == 1) return n.all; sr = __builtin_ctz(d.s.low); q.s.high = n.s.high >> sr; q.s.low = (n.s.high << (n_uword_bits - sr)) | (n.s.low >> sr); return q.all; } /* K X * --- * 0 K */ sr = 1 + n_uword_bits + __builtin_clz(d.s.low) - __builtin_clz(n.s.high); /* 2 <= sr <= n_udword_bits - 1 * q.all = n.all << (n_udword_bits - sr); * r.all = n.all >> sr; */ if (sr == n_uword_bits) { q.s.low = 0; q.s.high = n.s.low; r.s.high = 0; r.s.low = n.s.high; } else if (sr < n_uword_bits) // 2 <= sr <= n_uword_bits - 1 { q.s.low = 0; q.s.high = n.s.low << (n_uword_bits - sr); r.s.high = n.s.high >> sr; r.s.low = (n.s.high << (n_uword_bits - sr)) | (n.s.low >> sr); } else // n_uword_bits + 1 <= sr <= n_udword_bits - 1 { q.s.low = n.s.low << (n_udword_bits - sr); q.s.high = (n.s.high << (n_udword_bits - sr)) | (n.s.low >> (sr - n_uword_bits)); r.s.high = 0; r.s.low = n.s.high >> (sr - n_uword_bits); } } else { /* K X * --- * K K */ sr = __builtin_clz(d.s.high) - __builtin_clz(n.s.high); /* 0 <= sr <= n_uword_bits - 1 or sr large */ if (sr > n_uword_bits - 1) { if (rem) *rem = n.all; return 0; } ++sr; /* 1 <= sr <= n_uword_bits */ /* q.all = n.all << (n_udword_bits - sr); */ q.s.low = 0; if (sr == n_uword_bits) { q.s.high = n.s.low; r.s.high = 0; r.s.low = n.s.high; } else { q.s.high = n.s.low << (n_uword_bits - sr); r.s.high = n.s.high >> sr; r.s.low = (n.s.high << (n_uword_bits - sr)) | (n.s.low >> sr); } } } /* Not a special case * q and r are initialized with: * q.all = n.all << (n_udword_bits - sr); * r.all = n.all >> sr; * 1 <= sr <= n_udword_bits - 1 */ su_int carry = 0; for (; sr > 0; --sr) { /* r:q = ((r:q) << 1) | carry */ r.s.high = (r.s.high << 1) | (r.s.low >> (n_uword_bits - 1)); r.s.low = (r.s.low << 1) | (q.s.high >> (n_uword_bits - 1)); q.s.high = (q.s.high << 1) | (q.s.low >> (n_uword_bits - 1)); q.s.low = (q.s.low << 1) | carry; /* carry = 0; * if (r.all >= d.all) * { * r.all -= d.all; * carry = 1; * } */ const di_int s = (di_int)(d.all - r.all - 1) >> (n_udword_bits - 1); carry = s & 1; r.all -= d.all & s; } q.all = (q.all << 1) | carry; if (rem) *rem = r.all; return q.all; } du_int __udivdi3(du_int a, du_int b) { return __udivmoddi4(a, b, 0); } du_int __umoddi3(du_int a, du_int b) { du_int r; __udivmoddi4(a, b, &r); return r; } ================================================ FILE: src/hw/serial.h ================================================ #pragma once extern void __serial_print1(const char*); extern void __serial_print(const char*, int); extern void __serial_putchr(void*, char); ================================================ FILE: src/hw/serial1.c ================================================ #include static const uint16_t port = 0x3F8; // Serial port 1 static char initialized = 0; static inline uint8_t inb(int port) { int ret; asm ("xorl %eax,%eax"); asm ("inb %%dx,%%al":"=a" (ret):"d"(port)); return ret; } static inline void outb(int port, uint8_t data) { asm ("outb %%al,%%dx"::"a" (data), "d"(port)); } static inline void init_serial_if_needed() { if (initialized) return; initialized = 1; // properly initialize serial port outb(port + 1, 0x00); // Disable all interrupts outb(port + 3, 0x80); // Enable DLAB (set baud rate divisor) outb(port + 0, 0x03); // Set divisor to 3 (lo byte) 38400 baud outb(port + 1, 0x00); // (hi byte) outb(port + 3, 0x03); // 8 bits, no parity, one stop bit outb(port + 2, 0xC7); // Enable FIFO, clear them, with 14-byte threshold } void __serial_print1(const char* cstr) { init_serial_if_needed(); while (*cstr) { while (!(inb(port + 5) & 0x20)) /* */; outb(port, *cstr++); } } void __serial_print(const char* str, int len) { init_serial_if_needed(); for (int i = 0; i < len; i++) { while (!(inb(port + 5) & 0x20)) /* */; outb(port, str[i]); } } // buffered serial output static char buffer[256]; static unsigned cnt = 0; void fflush(void* fileno) { (void) fileno; __serial_print(buffer, cnt); cnt = 0; } void __serial_putchr(const void* file, char c) { (void) file; buffer[cnt++] = c; if (c == '\n' || cnt == sizeof(buffer)) { fflush(0); } } ================================================ FILE: src/kernel/dylib.hpp ================================================ #pragma once #include struct Dylib { static Elf64_Ehdr* load(const void* address); template static T resolve_function(const Elf64_Ehdr* hdr, const char* name); }; inline Elf64_Ehdr* Dylib::load(const void* address) { auto* hdr = (Elf64_Ehdr*) address; // validate shared library ELF header assert(Elf::validate(hdr)); // resolve symbolic references from PLT and GOT Elf::perform_relocations(hdr); return hdr; } template inline T Dylib::resolve_function(const Elf64_Ehdr* hdr, const char* name) { const auto* sym = Elf::resolve_name(hdr, name); if (sym == nullptr) return T(); return (T) &((char*) hdr)[sym->st_value]; } ================================================ FILE: src/kernel/elf.cpp ================================================ #include "elf.hpp" #include #include #include #include static constexpr bool DEBUG_ELF = false; bool Elf::validate(Elf64_Ehdr* hdr) { return (hdr->e_ident[EI_MAG0] == ELFMAG0) && (hdr->e_ident[EI_MAG1] == ELFMAG1) && (hdr->e_ident[EI_MAG2] == ELFMAG2) && (hdr->e_ident[EI_MAG3] == ELFMAG3) // we will need 64-bit addresses and such && (hdr->e_ident[EI_CLASS] == ELFCLASS64); } template inline T* elf_offset(const Elf64_Ehdr* hdr, intptr_t ofs) { return (T*) &((char*) hdr)[ofs]; } static const Elf64_Sym* elf_sym_index(const Elf64_Ehdr* hdr, const Elf64_Shdr* shdr, uint32_t symidx) { assert(symidx < shdr->sh_size / sizeof(Elf64_Sym)); auto* symtab = elf_offset(hdr, shdr->sh_offset); return &symtab[symidx]; } const Elf64_Shdr* Elf::section_by_name(const Elf64_Ehdr* hdr, const char* name) { const auto* shdr = elf_offset (hdr, hdr->e_shoff); const auto& shstrtab = shdr[hdr->e_shnum-1]; const char* strings = elf_offset(hdr, shstrtab.sh_offset); for (auto i = 0; i < hdr->e_shnum; i++) { const char* shname = &strings[shdr[i].sh_name]; if (strcmp(shname, name) == 0) { return &shdr[i]; } } return nullptr; } const Elf64_Sym* Elf::resolve_name(const Elf64_Ehdr* hdr, const char* name) { const auto* sym_hdr = Elf::section_by_name(hdr, ".symtab"); assert(sym_hdr != nullptr); const auto* str_hdr = Elf::section_by_name(hdr, ".strtab"); assert(str_hdr != nullptr); const Elf64_Sym* symtab = elf_sym_index(hdr, sym_hdr, 0); const size_t symtab_ents = sym_hdr->sh_size / sizeof(Elf64_Sym); const char* strtab = elf_offset(hdr, str_hdr->sh_offset); for (size_t i = 0; i < symtab_ents; i++) { const char* symname = &strtab[symtab[i].st_name]; //kprintf("Testing %s vs %s\n", symname, name); if (strcmp(symname, name) == 0) { return &symtab[i]; } } return nullptr; } static void elf_print_sym(const Elf64_Sym* sym) { kprintf("-> Sym is at %p with size %llu, type %u name %u\n", (void*) sym->st_value, sym->st_size, ELF64_ST_TYPE(sym->st_info), sym->st_name); } static void elf_relocate_section(Elf64_Ehdr* hdr, const char* section_name) { const auto* rela = Elf::section_by_name(hdr, section_name); const auto* dyn_hdr = Elf::section_by_name(hdr, ".dynsym"); const size_t rela_ents = rela->sh_size / sizeof(Elf64_Rela); auto* rela_addr = elf_offset(hdr, rela->sh_offset); for (size_t i = 0; i < rela_ents; i++) { const uint32_t symidx = ELF64_R_SYM(rela_addr[i].r_info); auto* sym = elf_sym_index(hdr, dyn_hdr, symidx); const uint8_t type = ELF64_ST_TYPE(sym->st_info); if (type == STT_FUNC || type == STT_OBJECT) { uintptr_t entry = (uintptr_t) hdr + rela_addr[i].r_offset; uintptr_t final = (uintptr_t) hdr + sym->st_value; if constexpr (DEBUG_ELF) { kprintf("Relocating rela %zu with sym idx %u where %p -> %p\n", i, symidx, (void*) entry, (void*) final); elf_print_sym(sym); } *(char**) entry = (char*) final; } } } void Elf::perform_relocations(Elf64_Ehdr* hdr) { elf_relocate_section(hdr, ".rela.plt"); elf_relocate_section(hdr, ".rela.dyn"); } ================================================ FILE: src/kernel/elf.hpp ================================================ #pragma once #include struct Elf { static bool validate(Elf64_Ehdr* hdr); static const Elf64_Shdr* section_by_name(const Elf64_Ehdr*, const char* name); // get the symbol associated with @name static const Elf64_Sym* resolve_name(const Elf64_Ehdr*, const char* name); // dynamic loader static void perform_relocations(Elf64_Ehdr* hdr); }; ================================================ FILE: src/kernel/init_libc.c ================================================ #include #include #include #include #include extern size_t strlen(const char* str); extern char _end; static void* multiboot_free_begin(intptr_t mb_addr) { const multiboot_info_t* info = (multiboot_info_t*) mb_addr; uintptr_t end = (uintptr_t) &_end; if (info->flags & MULTIBOOT_INFO_CMDLINE) { const char* cmdline = (char*) (intptr_t) info->cmdline; const uintptr_t pot_end = info->cmdline + strlen(cmdline); if (pot_end > end) end = pot_end; } const multiboot_module_t* mod = (multiboot_module_t*) (intptr_t) info->mods_addr; const multiboot_module_t* mods_end = mod + info->mods_count; for (; mod < mods_end; mod++) { if (mod->mod_end > end) end = mod->mod_end; } return (void*) end; } void __init_stdlib(uint32_t mb_magic, uint32_t mb_addr) { assert(mb_magic == 0x2badb002); // 1. enable printf facilities init_printf(NULL, __serial_putchr); // 2. find end of multiboot areas void* free_begin = multiboot_free_begin(mb_addr); assert(free_begin >= (void*) &_end); // 3. initialize heap (malloc, etc.) extern void __init_heap(void*); __init_heap(free_begin); #ifdef EH_ENABLED /// 4. initialize exceptions before we run constructors extern char __eh_frame_start[]; extern void __register_frame(void*); __register_frame(&__eh_frame_start); #endif // 5. call global C/C++ constructors extern void(*__init_array_start [])(); extern void(*__init_array_end [])(); int count = __init_array_end - __init_array_start; for (int i = 0; i < count; i++) { __init_array_start[i](); } } ================================================ FILE: src/kernel/kernel_start.c ================================================ #include #include #include static void __init_paging() { static uintptr_t pdir0[512] __attribute__((aligned(4096))); // unmap zero page assert(((uintptr_t) pdir0 & 0xfff) == 0); pdir0[0] = 0x0; // unpresent zero page for (uintptr_t i = 1; i < 512; i++) { pdir0[i] = (i * 0x1000) | 0x3; // RW + P } // install into PML2 entry 0 uintptr_t* pml2 = (uintptr_t*) 0x3000; pml2[0] = ((uintptr_t) pdir0) | 0x3; // RW + P; __asm__ ("invlpg 0x0"); } void kernel_start(uint32_t eax, uint32_t ebx) { kprintf("kernel_start(eax: %x, ebx: %x)\n", eax, ebx); extern void __init_stdlib(uint32_t, uint32_t); __init_stdlib(eax, ebx); // we have to do this after initializing .bss // NOTE: don't enable this until you catch CPU exceptions! //__init_paging(); extern void kernel_main(uint32_t, uint32_t); kernel_main(eax, ebx); } ================================================ FILE: src/kernel/panic.cpp ================================================ #include #include // less risky when the stack is blown out static char buffer[4096]; #define frp(N, ra) \ (__builtin_frame_address(N) != nullptr) && \ (ra = __builtin_return_address(N)) != nullptr && ra != (void*)-1 static void print_trace(const int N, const void* ra) { snprintf(buffer, sizeof(buffer), "[%d] %p\n", N, ra); kprint(buffer); } extern "C" void print_backtrace() { kprintf("\nBacktrace:\n"); void* ra; if (frp(0, ra)) { print_trace(0, ra); if (frp(1, ra)) { print_trace(1, ra); if (frp(2, ra)) { print_trace(2, ra); } } } } extern "C" __attribute__((noreturn)) void panic(const char* reason) { kprintf("\n\n!!! PANIC !!!\n%s\n", reason); print_backtrace(); // the end kprintf("\nKernel halting...\n"); while (1) asm("cli; hlt"); __builtin_unreachable(); } extern "C" void abort() { panic("Abort called"); } extern "C" void abort_message(const char* fmt, ...) { va_list arg; va_start (arg, fmt); int bytes = tfp_vsnprintf(buffer, sizeof(buffer), fmt, arg); (void) bytes; va_end (arg); panic(buffer); } ================================================ FILE: src/kernel/start.asm ================================================ ;; stack base address at EBDA border ;; NOTE: Multiboot can use 9d400 to 9ffff %define STACK_LOCATION 0x9D400 ;; multiboot magic %define MB_MAGIC 0x1BADB002 %define MB_FLAGS 0x3 ;; ALIGN + MEMINFO extern _MULTIBOOT_START_ extern _LOAD_START_ extern _LOAD_END_ extern _end extern begin_enter_longmode extern kernel_start ALIGN 4 section .multiboot dd MB_MAGIC dd MB_FLAGS dd -(MB_MAGIC + MB_FLAGS) dd _MULTIBOOT_START_ dd _LOAD_START_ dd _LOAD_END_ dd _end dd _start section .data multiboot_data_magic: dq 0 multiboot_data_address: dq 0 global multiboot_data_magic global multiboot_data_address [BITS 32] section .text global _start ;; make _start a global symbol _start: cli ;; load simple GDT lgdt [gdtr] ;; activate new GDT jmp 0x8:rock_bottom ;; code seg rock_bottom: mov cx, 0x10 ;; data seg mov ss, cx mov ds, cx mov es, cx mov fs, cx mov cx, 0x18 ;; GS seg mov gs, cx ;; 32-bit stack ptr mov esp, STACK_LOCATION mov ebp, esp ;; store multiboot params mov DWORD [multiboot_data_magic], eax mov DWORD [multiboot_data_address], ebx ;;ASM_PRINT(strings.phase1) call enable_cpu_feat ;; zero .bss (used to be in the C portion, but easier to do here) extern _BSS_START_ extern _BSS_END_ mov edi, _BSS_START_ mov ecx, _BSS_END_ sub ecx, _BSS_START_ mov eax, 0 rep stosb ;; Enable stack protector: ;; GS is located at 0x1000 ;; Linux uses GS:0x14 to access stack protector value ;; Copy RDTSC.EAX to this location as preliminary value rdtsc mov DWORD [0x1014], eax ;; for 32-bit kernels just call kernel_start here call begin_enter_longmode ;; stop cli hlt enable_cpu_feat: ;; enable SSE (pretty much always exists) mov eax, cr0 and ax, 0xFFFB ;clear coprocessor emulation CR0.EM or ax, 0x2 ;set coprocessor monitoring CR0.MP mov cr0, eax mov eax, cr4 or ax, 3 << 9 ;set CR4.OSFXSR and CR4.OSXMMEXCPT at the same time or ax, 0x20 ;enable native FPU exception handling mov cr4, eax ;; read out CPU features mov eax, 1 xor ecx, ecx cpuid mov edx, ecx ;; check for XSAVE support (bit 26) and ecx, 0x04000000 jz xsave_not_supported ;; enable XSAVE mov eax, cr4 or eax, 0x40000 mov cr4, eax ;; check for AVX support (bit 28) and edx, 0x10000000 jz xsave_not_supported ;; enable AVX xor ecx, ecx xgetbv or eax, 0x7 xsetbv xsave_not_supported: ret ALIGN 32 gdtr: dw gdt32_end - gdt32 - 1 dd gdt32 ALIGN 32 gdt32: ;; Entry 0x0: Null descriptor dq 0x0 ;; Entry 0x8: Code segment dw 0xffff ;Limit dw 0x0000 ;Base 15:00 db 0x00 ;Base 23:16 dw 0xcf9a ;Flags / Limit / Type [F,L,F,Type] db 0x00 ;Base 32:24 ;; Entry 0x10: Data segment dw 0xffff ;Limit dw 0x0000 ;Base 15:00 db 0x00 ;Base 23:16 dw 0xcf92 ;Flags / Limit / Type [F,L,F,Type] db 0x00 ;Base 32:24 ;; Entry 0x18: GS Data segment dw 0x0100 ;Limit dw 0x1000 ;Base 15:00 db 0x00 ;Base 23:16 dw 0x4092 ;Flags / Limit / Type [F,L,F,Type] db 0x00 ;Base 32:24 gdt32_end: ================================================ FILE: src/kernel/start32.c ================================================ #include extern uint32_t multiboot_data_magic; extern uint32_t multiboot_data_address; extern void kernel_start(uint32_t eax, uint32_t ebx); void begin_enter_longmode() { kernel_start(multiboot_data_magic, multiboot_data_address); } ================================================ FILE: src/kernel/start64.asm ================================================ [BITS 32] global begin_enter_longmode:function extern __serial_print1 extern kernel_start %define STACK_LOCATION 0x9D000 %define P4_TAB 0x1000 ;; one page %define P3_TAB 0x2000 ;; one page %define P2_TAB 0x3000 ;; many pages %define NUM_P3_ENTRIES 4 %define NUM_P2_ENTRIES (NUM_P3_ENTRIES * 512) %define IA32_EFER 0xC0000080 %define IA32_STAR 0xC0000081 %define IA32_LSTAR 0xc0000082 %define IA32_FMASK 0xc0000084 %define IA32_FS_BASE 0xc0000100 %define IA32_GS_BASE 0xc0000101 %define IA32_KERNEL_GS_BASE 0xc0000102 ;; CR0 paging enable bit %define PAGING_ENABLE 0x80000000 ;; CR0 Supervisor write-protect enable %define SUPER_WP_ENABLE 0x10000 ;; EFER Longmode bit %define LONGMODE_ENABLE 0x100 ;; EFER Execute Disable bit %define NX_ENABLE 0x800 ;; EFER Syscall enable bit %define SYSCALL_ENABLE 0x1 extern multiboot_data_magic extern multiboot_data_address SECTION .text begin_enter_longmode: ;; disable old paging mov eax, cr0 and eax, 0x7fffffff ;; clear PG (bit 31) mov cr0, eax ;; address for Page Map Level 4 mov edi, P4_TAB mov cr3, edi ;; clear out P4 and P3 mov ecx, 0x2000 / 0x4 xor eax, eax ; Nullify the A-register. rep stosd ;; create page map entry mov edi, P4_TAB mov DWORD [edi], P3_TAB | 0x3 ;; present+write ;; create 1GB mappings mov ecx, NUM_P3_ENTRIES mov edi, P3_TAB mov eax, P2_TAB | 0x3 ;; present + write mov ebx, 0x0 .p3_loop: mov DWORD [edi], eax ;; Low word mov DWORD [edi+4], ebx ;; High word add eax, 1 << 12 ;; page increments adc ebx, 0 ;; increment high word when CF set add edi, 8 loop .p3_loop ;; create 2MB mappings mov ecx, NUM_P2_ENTRIES mov edi, P2_TAB mov eax, 0x0 | 0x3 | 1 << 7 ;; present + write + huge mov ebx, 0x0 .p2_loop: mov DWORD [edi], eax ;; Low word mov DWORD [edi+4], ebx ;; High word add eax, 1 << 21 ;; 2MB increments adc ebx, 0 ;; increment high word when CF set add edi, 8 loop .p2_loop ;; enable PAE mov eax, cr4 or eax, 1 << 5 mov cr4, eax ;; enable long mode mov ecx, IA32_EFER rdmsr or eax, (LONGMODE_ENABLE | NX_ENABLE | SYSCALL_ENABLE) wrmsr ;; enable paging mov eax, cr0 ; Set the A-register to control register 0. or eax, (PAGING_ENABLE | SUPER_WP_ENABLE) mov cr0, eax ; Set control register 0 to the A-register. ;; load 64-bit GDT lgdt [__gdt64_base_pointer] jmp GDT64.Code:long_mode [BITS 64] long_mode: cli ;; segment regs mov cx, GDT64.Data mov ds, cx mov es, cx mov fs, cx mov gs, cx mov ss, cx ;; set up new stack for 64-bit push rsp mov rsp, STACK_LOCATION push 0 push 0 mov rbp, rsp mov ecx, IA32_STAR mov edx, 0x8 mov eax, 0x0 wrmsr ;; Enable stack protector: ;; On amd64 FS should point to tls-table for cpu0 ;; Linux uses FS:0x28 to access stack protector value extern tls mov ecx, IA32_FS_BASE mov edx, 0x0 mov eax, tls wrmsr ;; Set "random" stack protector value extern __SSP__ rdtsc mov rcx, __SSP__ xor rax, rcx ;; Install in TLS table mov QWORD [tls+0x28], rax ;; geronimo! mov edi, DWORD[multiboot_data_magic] mov esi, DWORD[multiboot_data_address] call kernel_start ;; warning that we returned from kernel_start mov rdi, strings.panic call __serial_print1 cli hlt SECTION .data strings: .panic: db `Returned from kernel_start! Halting...\n`,0x0 GDT64: .Null: equ $ - GDT64 ; The null descriptor. dq 0 .Code: equ $ - GDT64 ; The code descriptor. dw 0 ; Limit (low). dw 0 ; Base (low). db 0 ; Base (middle) db 10011010b ; Access (exec/read). db 00100000b ; Granularity. db 0 ; Base (high). .Data: equ $ - GDT64 ; The data descriptor. dw 0 ; Limit (low). dw 0 ; Base (low). db 0 ; Base (middle) db 10010010b ; Access (read/write). db 00000000b ; Granularity. db 0 ; Base (high). .Task: equ $ - GDT64 ; TSS descriptor. dq 0 dq 0 dw 0x0 ;; alignment padding __gdt64_base_pointer: dw $ - GDT64 - 1 ; Limit. dq GDT64 ; Base. ================================================ FILE: src/kernel/tls.cpp ================================================ #include #include "tls.hpp" #ifdef __x86_64__ static_assert(offsetof(tls_table, guard) == 0x28, "TLS is at 0x28 on amd64"); #elif __i386__ static_assert(offsetof(tls_table, guard) == 0x14, "TLS is at 0x18 on i386"); #endif // we have to store this in .data otherwise .bss initialization // will overwrite this in stdlib init struct tls_table tls; ================================================ FILE: src/kernel/tls.hpp ================================================ #pragma once #include struct tls_table { // thread self-pointer void* tls_data; // 0x0 // per-cpu cpuid int cpuid; uintptr_t pad[3]; uintptr_t guard; // _SENTINEL_VALUE_ }; extern tls_table tls; ================================================ FILE: src/kprint.h ================================================ #pragma once #ifdef __cplusplus extern "C" { #endif #include // better, more familiar way to print! extern int kprintf(const char* fmt, ...) __attribute__((format(printf, 1, 2))); // print text directly to serial port static inline void kprint(const char* text) { __serial_print1(text); } #include #ifdef __cplusplus } #endif ================================================ FILE: src/linker.ld ================================================ /** * Inspired from the IncludeOS unikernel (https://github.com/hioa-cs/IncludeOS) * * This is the smallest possible linker script that supports * normal C and C++ operation, including global constructors, * exceptions and linker GC sections option. **/ ENTRY(_start) SECTIONS { PROVIDE(_ELF_START_ = . + 0x100000); PROVIDE(_LOAD_START_ = _ELF_START_); . = _ELF_START_; .multiboot (_ELF_START_ ): { PROVIDE(_MULTIBOOT_START_ = .); KEEP(*(.multiboot)) } .text ALIGN(0x10) : { _TEXT_START_ = .; *(.text*) *(.gnu.linkonce.t*) _TEXT_END_ = .; } .rodata : { _RODATA_START_ = .; *(.rodata*) *(.gnu.linkonce.r*) _RODATA_END_ = .; } .init_array : { PROVIDE_HIDDEN (__init_array_start = .); KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*))) KEEP (*(.init_array .ctors)) PROVIDE_HIDDEN (__init_array_end = .); } /*.fini_array : { PROVIDE_HIDDEN (__fini_array_start = .); KEEP (*(SORT_BY_INIT_PRIORITY(.fini_array.*) SORT_BY_INIT_PRIORITY(.dtors.*))) KEEP (*(.fini_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .dtors)) PROVIDE_HIDDEN (__fini_array_end = .); }*/ /* For stack unwinding (exception handling) */ .eh_frame_hdr ALIGN(0x8): { KEEP(*(.eh_frame_hdr*)) } .eh_frame ALIGN(0x8): { PROVIDE (__eh_frame_start = .); KEEP(*(.eh_frame)) LONG (0); } .gcc_except_table : { *(.gcc_except_table) } .data : { _DATA_START_ = .; *(.data*) *(.gnu.linkonce.d*) _DATA_END_ = .; } PROVIDE(_LOAD_END_ = .); .bss : { _BSS_START_ = .; *(.bss .bss.* .gnu.linkonce.b.*) *(COMMON) _BSS_END_ = .; } . = ALIGN(0x10); _end = .; } ================================================ FILE: src/main.h ================================================ #pragma once #include #ifdef __cplusplus extern "C" { #endif extern void kernel_main(uint32_t eax, uint32_t ebx); #ifdef __cplusplus } #endif ================================================ FILE: src/multiboot.h ================================================ /* Copyright (C) 1999,2003,2007,2008,2009,2010 Free Software Foundation, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL ANY * DEVELOPER OR DISTRIBUTOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR * IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef MULTIBOOT_HEADER #define MULTIBOOT_HEADER 1 /* How many bytes from the start of the file we search for the header. */ #define MULTIBOOT_SEARCH 8192 #define MULTIBOOT_HEADER_ALIGN 4 /* The magic field should contain this. */ #define MULTIBOOT_HEADER_MAGIC 0x1BADB002 /* This should be in %eax. */ #define MULTIBOOT_BOOTLOADER_MAGIC 0x2BADB002 /* Alignment of multiboot modules. */ #define MULTIBOOT_MOD_ALIGN 0x00001000 /* Alignment of the multiboot info structure. */ #define MULTIBOOT_INFO_ALIGN 0x00000004 /* Flags set in the 'flags' member of the multiboot header. */ /* Align all boot modules on i386 page (4KB) boundaries. */ #define MULTIBOOT_PAGE_ALIGN 0x00000001 /* Must pass memory information to OS. */ #define MULTIBOOT_MEMORY_INFO 0x00000002 /* Must pass video information to OS. */ #define MULTIBOOT_VIDEO_MODE 0x00000004 /* This flag indicates the use of the address fields in the header. */ #define MULTIBOOT_AOUT_KLUDGE 0x00010000 /* Flags to be set in the 'flags' member of the multiboot info structure. */ /* is there basic lower/upper memory information? */ #define MULTIBOOT_INFO_MEMORY 0x00000001 /* is there a boot device set? */ #define MULTIBOOT_INFO_BOOTDEV 0x00000002 /* is the command-line defined? */ #define MULTIBOOT_INFO_CMDLINE 0x00000004 /* are there modules to do something with? */ #define MULTIBOOT_INFO_MODS 0x00000008 /* These next two are mutually exclusive */ /* is there a symbol table loaded? */ #define MULTIBOOT_INFO_AOUT_SYMS 0x00000010 /* is there an ELF section header table? */ #define MULTIBOOT_INFO_ELF_SHDR 0X00000020 /* is there a full memory map? */ #define MULTIBOOT_INFO_MEM_MAP 0x00000040 /* Is there drive info? */ #define MULTIBOOT_INFO_DRIVE_INFO 0x00000080 /* Is there a config table? */ #define MULTIBOOT_INFO_CONFIG_TABLE 0x00000100 /* Is there a boot loader name? */ #define MULTIBOOT_INFO_BOOT_LOADER_NAME 0x00000200 /* Is there a APM table? */ #define MULTIBOOT_INFO_APM_TABLE 0x00000400 /* Is there video information? */ #define MULTIBOOT_INFO_VBE_INFO 0x00000800 #define MULTIBOOT_INFO_FRAMEBUFFER_INFO 0x00001000 #ifndef ASM_FILE typedef unsigned char multiboot_uint8_t; typedef unsigned short multiboot_uint16_t; typedef unsigned int multiboot_uint32_t; typedef unsigned long long multiboot_uint64_t; struct multiboot_header { /* Must be MULTIBOOT_MAGIC - see above. */ multiboot_uint32_t magic; /* Feature flags. */ multiboot_uint32_t flags; /* The above fields plus this one must equal 0 mod 2^32. */ multiboot_uint32_t checksum; /* These are only valid if MULTIBOOT_AOUT_KLUDGE is set. */ multiboot_uint32_t header_addr; multiboot_uint32_t load_addr; multiboot_uint32_t load_end_addr; multiboot_uint32_t bss_end_addr; multiboot_uint32_t entry_addr; /* These are only valid if MULTIBOOT_VIDEO_MODE is set. */ multiboot_uint32_t mode_type; multiboot_uint32_t width; multiboot_uint32_t height; multiboot_uint32_t depth; }; /* The symbol table for a.out. */ struct multiboot_aout_symbol_table { multiboot_uint32_t tabsize; multiboot_uint32_t strsize; multiboot_uint32_t addr; multiboot_uint32_t reserved; }; typedef struct multiboot_aout_symbol_table multiboot_aout_symbol_table_t; /* The section header table for ELF. */ struct multiboot_elf_section_header_table { multiboot_uint32_t num; multiboot_uint32_t size; multiboot_uint32_t addr; multiboot_uint32_t shndx; }; typedef struct multiboot_elf_section_header_table multiboot_elf_section_header_table_t; struct multiboot_info { /* Multiboot info version number */ multiboot_uint32_t flags; /* Available memory from BIOS */ multiboot_uint32_t mem_lower; multiboot_uint32_t mem_upper; /* "root" partition */ multiboot_uint32_t boot_device; /* Kernel command line */ multiboot_uint32_t cmdline; /* Boot-Module list */ multiboot_uint32_t mods_count; multiboot_uint32_t mods_addr; union { multiboot_aout_symbol_table_t aout_sym; multiboot_elf_section_header_table_t elf_sec; } u; /* Memory Mapping buffer */ multiboot_uint32_t mmap_length; multiboot_uint32_t mmap_addr; /* Drive Info buffer */ multiboot_uint32_t drives_length; multiboot_uint32_t drives_addr; /* ROM configuration table */ multiboot_uint32_t config_table; /* Boot Loader Name */ multiboot_uint32_t boot_loader_name; /* APM table */ multiboot_uint32_t apm_table; /* Video */ multiboot_uint32_t vbe_control_info; multiboot_uint32_t vbe_mode_info; multiboot_uint16_t vbe_mode; multiboot_uint16_t vbe_interface_seg; multiboot_uint16_t vbe_interface_off; multiboot_uint16_t vbe_interface_len; multiboot_uint64_t framebuffer_addr; multiboot_uint32_t framebuffer_pitch; multiboot_uint32_t framebuffer_width; multiboot_uint32_t framebuffer_height; multiboot_uint8_t framebuffer_bpp; #define MULTIBOOT_FRAMEBUFFER_TYPE_INDEXED 0 #define MULTIBOOT_FRAMEBUFFER_TYPE_RGB 1 #define MULTIBOOT_FRAMEBUFFER_TYPE_EGA_TEXT 2 multiboot_uint8_t framebuffer_type; union { struct { multiboot_uint32_t framebuffer_palette_addr; multiboot_uint16_t framebuffer_palette_num_colors; }; struct { multiboot_uint8_t framebuffer_red_field_position; multiboot_uint8_t framebuffer_red_mask_size; multiboot_uint8_t framebuffer_green_field_position; multiboot_uint8_t framebuffer_green_mask_size; multiboot_uint8_t framebuffer_blue_field_position; multiboot_uint8_t framebuffer_blue_mask_size; }; }; }; typedef struct multiboot_info multiboot_info_t; struct multiboot_color { multiboot_uint8_t red; multiboot_uint8_t green; multiboot_uint8_t blue; }; struct multiboot_mmap_entry { multiboot_uint32_t size; multiboot_uint64_t addr; multiboot_uint64_t len; #define MULTIBOOT_MEMORY_AVAILABLE 1 #define MULTIBOOT_MEMORY_RESERVED 2 #define MULTIBOOT_MEMORY_ACPI_RECLAIMABLE 3 #define MULTIBOOT_MEMORY_NVS 4 #define MULTIBOOT_MEMORY_BADRAM 5 multiboot_uint32_t type; } __attribute__((packed)); typedef struct multiboot_mmap_entry multiboot_memory_map_t; struct multiboot_mod_list { /* the memory used goes from bytes 'mod_start' to 'mod_end-1' inclusive */ multiboot_uint32_t mod_start; multiboot_uint32_t mod_end; /* Module command line */ multiboot_uint32_t cmdline; /* padding to take it to 16 bytes (must be zero) */ multiboot_uint32_t pad; }; typedef struct multiboot_mod_list multiboot_module_t; /* APM BIOS info. */ struct multiboot_apm_info { multiboot_uint16_t version; multiboot_uint16_t cseg; multiboot_uint32_t offset; multiboot_uint16_t cseg_16; multiboot_uint16_t dseg; multiboot_uint16_t flags; multiboot_uint16_t cseg_len; multiboot_uint16_t cseg_16_len; multiboot_uint16_t dseg_len; }; #endif /* ! ASM_FILE */ #endif /* ! MULTIBOOT_HEADER */ ================================================ FILE: tools/Makefile ================================================ # # This is the old Makefile for the project, before moving to CMake # if CMake is not for you, then this may still be useful for you to build # a single kernel from the source tree. Just keep in mind you need to add # your own main file, and that the source tree is outdated. Good luck. # # kernel binary OUT = mykernel # .c files (add your own!) C_FILES = src/kernel/kernel_start.c \ src/hw/serial1.c \ src/crt/c_abi.c src/crt/heap.c src/crt/malloc.c \ src/crt/ubsan.c \ src/prnt/print.c src/prnt/mini-printf.c # .cpp files CPP_FILES=src/main.cpp src/crt/cxxabi.cpp \ src/kernel/tls.cpp src/kernel/panic.cpp # .asm files for NASM ASM_FILES=src/kernel/start.asm src/kernel/start64.asm # includes INCLUDE=-Isrc # EASTL C++ library INCLUDE +=-Iext/EASTL/include -Iext/EASTL/test/packages/EABase/include/Common CPP_FILES += ext/EASTL/source/allocator_eastl.cpp ext/EASTL/source/assert.cpp \ ext/EASTL/source/fixed_pool.cpp ext/EASTL/source/hashtable.cpp \ ext/EASTL/source/intrusive_list.cpp ext/EASTL/source/numeric_limits.cpp \ ext/EASTL/source/red_black_tree.cpp ext/EASTL/source/string.cpp GDEFS = LIBS = OPTIMIZE = -Ofast -mfpmath=sse -msse3 #-march=native ## to enable LLVM / ThinLTO use these ## #LD=ld.lld # path to your LLD binary #LTO_DEFS=-flto=full # full or thin OPTIONS=-m64 $(INCLUDE) $(GDEFS) $(OPTIMIZE) $(LTO_DEFS) WARNS=-Wall -Wextra -pedantic COMMON=-ffreestanding -nostdlib -MMD -fstack-protector-strong -fno-omit-frame-pointer $(OPTIONS) $(WARNS) # inject some random numbers into a symbol so we can get some free random bits SSP=$(shell hexdump -n 8 -e '4/4 "%08X" 1 "\n"' /dev/random) LDFLAGS=-static -nostdlib -N -melf_x86_64 --strip-all --script=src/linker.ld --defsym __SSP__=0x$(SSP) CFLAGS=-std=gnu11 $(COMMON) CXXFLAGS=-std=c++14 -fno-exceptions -fno-rtti $(COMMON) CFLAGS += $(SANITIZE) CXXFLAGS += $(SANITIZE) #-fno-sanitize=function COBJ=$(C_FILES:.c=.o) CXXOBJ=$(CPP_FILES:.cpp=.o) ASMOBJ=$(ASM_FILES:.asm=.o) DEPS=$(CXXOBJ:.o=.d) $(COBJ:.o=.d) .PHONY: clean all executable %.o: %.asm nasm -f elf64 -o $@ $< %.o: %.c $(CC) $(CFLAGS) -c $< -o $@ %.o: %.cpp $(CXX) $(CXXFLAGS) -c $< -o $@ all: $(COBJ) $(CXXOBJ) $(ASMOBJ) $(LD) $(LDFLAGS) $(COBJ) $(CXXOBJ) $(ASMOBJ) $(LIBS) -o $(OUT) sanitize: SANITIZE="-fsanitize=undefined -fno-sanitize=vptr" $(MAKE) all chainloader: $(COBJ) $(CXXOBJ) $(ASMOBJ) $(LD) $(LDFLAGS) $(COBJ) $(CXXOBJ) $(ASMOBJ) $(LIBS) -o chainloader executable: $(info $(OUT)) @true clean: $(RM) $(OUT) $(COBJ) $(CXXOBJ) $(ASMOBJ) $(DEPS) -include $(DEPS)