From 585abe9a181d04d72c06206f16a0a07e638e371a Mon Sep 17 00:00:00 2001 From: "Justin C. Miller" Date: Thu, 6 Sep 2018 01:31:47 -0700 Subject: [PATCH] Simple ELF program loader Now any initrd file is treated like a program image and passed to the loader to load as a process. Very rudimentary elf loading just allocates pages, copies sections, and sets the ELF's entrypoint as the RIP to iretq to. --- src/drivers/nulldrv/main.s | 1 - src/kernel/interrupts.s | 14 ----- src/kernel/main.cpp | 11 ++-- src/kernel/page_manager.h | 8 +++ src/kernel/scheduler.cpp | 97 +++++++++++++++++++++++---------- src/kernel/scheduler.h | 7 +++ src/kernel/wscript | 6 ++- src/libraries/elf/elf.cpp | 49 +++++++++++++++++ src/libraries/elf/elf.h | 61 +++++++++++++++++++++ src/libraries/elf/headers.h | 98 ++++++++++++++++++++++++++++++++++ src/libraries/elf/wscript | 14 +++++ src/libraries/kutil/memory.cpp | 4 +- src/libraries/kutil/memory.h | 2 +- 13 files changed, 321 insertions(+), 51 deletions(-) create mode 100644 src/libraries/elf/elf.cpp create mode 100644 src/libraries/elf/elf.h create mode 100644 src/libraries/elf/headers.h create mode 100644 src/libraries/elf/wscript diff --git a/src/drivers/nulldrv/main.s b/src/drivers/nulldrv/main.s index cbb0009..d6973f6 100644 --- a/src/drivers/nulldrv/main.s +++ b/src/drivers/nulldrv/main.s @@ -2,7 +2,6 @@ global _start _start: xor rbp, rbp ; Sentinel rbp - pop rsi ; My PID mov rdi, 0 ; DEBUG syscall .loop: diff --git a/src/kernel/interrupts.s b/src/kernel/interrupts.s index b406a9f..cea294c 100644 --- a/src/kernel/interrupts.s +++ b/src/kernel/interrupts.s @@ -209,17 +209,3 @@ ramdisk_process_loader: add rsp, 16 ; because the ISRs add err/num iretq - -global taskA -taskA: - push rbp - mov rbp, rsp - mov rdi, 0 - mov rax, 0xaaaaaaaaaaaaaaaa - -.loop: - syscall - jmp .loop - -global taskAend -taskAend: diff --git a/src/kernel/main.cpp b/src/kernel/main.cpp index 1debf5d..4431eb5 100644 --- a/src/kernel/main.cpp +++ b/src/kernel/main.cpp @@ -41,9 +41,9 @@ init_console() log::init(cons); log::enable(logs::apic, log::level::info); - log::enable(logs::device, log::level::debug); + log::enable(logs::device, log::level::info); log::enable(logs::driver, log::level::debug); - log::enable(logs::memory, log::level::info); + log::enable(logs::memory, log::level::debug); log::enable(logs::fs, log::level::debug); log::enable(logs::task, log::level::debug); } @@ -142,7 +142,12 @@ kernel_main(popcorn_data *header) syscall_enable(); scheduler *sched = new (&scheduler::get()) scheduler(devices->get_lapic()); - sched->start(); + for (auto &f : ird.files()) { + //if (f.executable()) + sched->create_process(f.name(), f.data(), f.size()); + } + + sched->start(); g_console.puts("boogity!\n"); } diff --git a/src/kernel/page_manager.h b/src/kernel/page_manager.h index 0540cb8..11f980e 100644 --- a/src/kernel/page_manager.h +++ b/src/kernel/page_manager.h @@ -34,6 +34,14 @@ public: page_manager(); + /// Helper to get the number of pages needed for a given number of bytes. + /// \arg bytes The number of bytes desired + /// \returns The number of pages needed to contain the desired bytes + static inline size_t page_count(size_t bytes) + { + return (bytes - 1) / page_size + 1; + } + /// Helper to read the PML4 table from CR3. /// \returns A pointer to the current PML4 table. static inline page_table * get_pml4() diff --git a/src/kernel/scheduler.cpp b/src/kernel/scheduler.cpp index 33f78cf..4e42e29 100644 --- a/src/kernel/scheduler.cpp +++ b/src/kernel/scheduler.cpp @@ -7,6 +7,9 @@ #include "page_manager.h" #include "scheduler.h" +#include "elf/elf.h" +#include "kutil/assert.h" + scheduler scheduler::s_instance(nullptr); //static const uint32_t quantum = 2000000; static const uint32_t quantum = 20000000; @@ -16,43 +19,83 @@ const uint64_t rflags_noint = 0x002; const uint64_t rflags_int = 0x202; extern "C" { - void taskA(); - void taskAend(); void ramdisk_process_loader(); - void load_process(void *copy_start, size_t butes, cpu_state state); + void load_process(const void *image_start, size_t butes, cpu_state state); }; scheduler::scheduler(lapic *apic) : m_apic(apic), - m_current(0) + m_current(0), + m_next_pid(0) { m_processes.ensure_capacity(50); + m_processes.append({m_next_pid++, 0, page_manager::get_pml4()}); // The kernel idle task, also the thread we're in now } void -load_process(void *copy_start, size_t bytes, cpu_state state) +load_process(const void *image_start, size_t bytes, cpu_state state) { - log::debug(logs::task, "Loading task! Start: %016lx [%d]", copy_start, bytes); - log::debug(logs::task, "New process state:"); + // We're now in the process space for this process, allocate memory for the + // process code and load it + page_manager *pager = page_manager::get(); + + log::debug(logs::task, "Loading task! ELF: %016lx [%d]", image_start, bytes); + + // TODO: Handle bad images gracefully + elf::elf image(image_start, bytes); + kassert(image.valid(), "Invalid ELF passed to load_process"); + + const unsigned program_count = image.program_count(); + for (unsigned i = 0; i < program_count; ++i) { + const elf::program_header *header = image.program(i); + + if (header->type != elf::segment_type::load) + continue; + + addr_t aligned = header->vaddr & ~(page_manager::page_size - 1); + size_t size = (header->vaddr + header->mem_size) - aligned; + size_t pages = page_manager::page_count(size); + + log::debug(logs::task, " Loadable segment %02d: vaddr %016lx size %016lx", + i, header->vaddr, header->mem_size); + + log::debug(logs::task, " - aligned to: vaddr %016lx pages %d", + aligned, pages); + + void *mapped = pager->map_pages(aligned, pages, true); + kassert(mapped, "Tried to map userspace pages and failed!"); + + kutil::memset(mapped, 0, pages * page_manager::page_size); + } + + const unsigned section_count = image.section_count(); + for (unsigned i = 0; i < section_count; ++i) { + const elf::section_header *header = image.section(i); + + if (header->type != elf::section_type::progbits || + !bitfield_has(header->flags, elf::section_flags::alloc)) + continue; + + log::debug(logs::task, " Loadable section %u: vaddr %016lx size %016lx", + i, header->addr, header->size); + + void *dest = reinterpret_cast(header->addr); + const void *src = kutil::offset_pointer(image_start, header->offset); + kutil::memcpy(dest, src, header->size); + } + + state.rip = image.entrypoint(); + + log::debug(logs::task, "Loaded! New process state:"); log::debug(logs::task, " CS: %d [%d]", state.cs >> 3, state.cs & 0x07); log::debug(logs::task, " SS: %d [%d]", state.ss >> 3, state.ss & 0x07); log::debug(logs::task, " RFLAGS: %08x", state.rflags); log::debug(logs::task, " RIP: %016lx", state.rip); log::debug(logs::task, " uRSP: %016lx", state.user_rsp); - - // We're now in the process space for this process, allocate memory for the - // process code and load it - page_manager *pager = page_manager::get(); - - size_t count = ((bytes - 1) / page_manager::page_size) + 1; - void *loading = pager->map_pages(0x200000, count, true); - - kutil::memcpy(loading, copy_start, bytes); - state.rip = reinterpret_cast(loading); } -static process -create_process(uint16_t pid) +void +scheduler::create_process(const char *name, const void *data, size_t size) { uint16_t kcs = (1 << 3) | 0; // Kernel CS is GDT entry 1, ring 0 uint16_t cs = (5 << 3) | 3; // User CS is GDT entry 5, ring 3 @@ -90,29 +133,25 @@ create_process(uint16_t pid) loader_state->rip = reinterpret_cast(ramdisk_process_loader); loader_state->user_rsp = reinterpret_cast(state); - // TODO: replace with an actual ELF location in memory - loader_state->rax = reinterpret_cast(taskA); - loader_state->rbx = reinterpret_cast(taskAend) - reinterpret_cast(taskA); + loader_state->rax = reinterpret_cast(data); + loader_state->rbx = size; - log::debug(logs::task, "Creating PID %d:", pid); + uint16_t pid = m_next_pid++; + log::debug(logs::task, "Creating process %s:", name); + log::debug(logs::task, " PID %d", pid); log::debug(logs::task, " RSP0 %016lx", state); log::debug(logs::task, " RSP3 %016lx", state->user_rsp); log::debug(logs::task, " PML4 %016lx", pml4); log::debug(logs::task, " Loading %016lx [%d]", loader_state->rax, loader_state->rbx); - return {pid, reinterpret_cast(loader_state), pml4}; + m_processes.append({pid, reinterpret_cast(loader_state), pml4}); } void scheduler::start() { log::info(logs::task, "Starting scheduler."); - m_apic->enable_timer(isr::isrTimer, 128, quantum, false); - - m_processes.append({0, 0, page_manager::get_pml4()}); // The kernel idle task, also the thread we're in now - m_processes.append(create_process(1)); - //m_processes.append(create_process(2, &taskB)); } addr_t diff --git a/src/kernel/scheduler.h b/src/kernel/scheduler.h index 9893fad..1a05e7c 100644 --- a/src/kernel/scheduler.h +++ b/src/kernel/scheduler.h @@ -24,6 +24,12 @@ public: /// \arg apic Pointer to the local APIC object scheduler(lapic *apic); + /// Create a new process from a program image in memory. + /// \arg name Name of the program image + /// \arg data Pointer to the image data + /// \arg size Size of the program image, in bytes + void create_process(const char *name, const void *data, size_t size); + /// Start the scheduler working. This may involve starting /// timer interrupts or other preemption methods. void start(); @@ -41,6 +47,7 @@ private: lapic *m_apic; kutil::vector m_processes; uint16_t m_current; + uint16_t m_next_pid; static scheduler s_instance; }; diff --git a/src/kernel/wscript b/src/kernel/wscript index 097818c..47f4235 100644 --- a/src/kernel/wscript +++ b/src/kernel/wscript @@ -15,7 +15,11 @@ def build(bld): name = 'kernel', includes = '.', target = bld.env.KERNEL_FILENAME, - use = ['kutil', 'initrd'], + use = [ + 'kutil', + 'initrd', + 'elf', + ], linkflags = "-T {}".format(lds), ) diff --git a/src/libraries/elf/elf.cpp b/src/libraries/elf/elf.cpp new file mode 100644 index 0000000..4593da2 --- /dev/null +++ b/src/libraries/elf/elf.cpp @@ -0,0 +1,49 @@ +#include "elf/elf.h" +#include "elf/headers.h" + +static const uint32_t expected_magic = 0x464c457f; // "\x7f" "ELF" + +namespace elf { + +elf::elf(const void *data, size_t size) : + m_data(data), + m_size(size) +{ +} + +bool +elf::valid() const +{ + const file_header *fheader = header(); + + return + fheader->magic == expected_magic && + fheader->word_size == wordsize::bits64 && + fheader->endianness == encoding::lsb && + fheader->os_abi == osabi::sysV && + fheader->file_type == filetype::executable && + fheader->machine_type == machine::x64 && + fheader->ident_version == 1 && + fheader->version == 1; +} + +const program_header * +elf::program(unsigned index) const +{ + const file_header *fheader = header(); + uint64_t off = fheader->ph_offset + (index * fheader->ph_entsize); + const void *pheader = kutil::offset_pointer(m_data, off); + return reinterpret_cast(pheader); +} + +const section_header * +elf::section(unsigned index) const +{ + const file_header *fheader = header(); + uint64_t off = fheader->sh_offset + (index * fheader->sh_entsize); + const void *sheader = kutil::offset_pointer(m_data, off); + return reinterpret_cast(sheader); +} + + +} // namespace elf diff --git a/src/libraries/elf/elf.h b/src/libraries/elf/elf.h new file mode 100644 index 0000000..d527031 --- /dev/null +++ b/src/libraries/elf/elf.h @@ -0,0 +1,61 @@ +#pragma once +#include +#include "elf/headers.h" +#include "kutil/memory.h" + +namespace elf { + +class elf +{ +public: + /// Constructor: Create an elf object out of ELF data in memory + /// \arg data The ELF data to read + /// \arg size Size of the ELF data, in bytes + elf(const void *data, size_t size); + + /// Check the validity of the ELF data + /// \returns true for valid ELF data + bool valid() const; + + /// Get the entrypoint address of the program image + /// \returns A pointer to the entrypoint of the program + inline addr_t entrypoint() const + { + return static_cast(header()->entrypoint); + } + + /// Get the number of program sections in the image + /// \returns The number of program section entries + inline unsigned program_count() const + { + return header()->ph_num; + } + + /// Get a program header + /// \arg index The index number of the program header + /// \returns A pointer to the program header data + const program_header * program(unsigned index) const; + + /// Get the number of data sections in the image + /// \returns The number of section entries + inline unsigned section_count() const + { + return header()->sh_num; + } + + /// Get a section header + /// \arg index The index number of the section header + /// \returns A pointer to the section header data + const section_header * section(unsigned index) const; + +private: + inline const file_header *header() const + { + return reinterpret_cast(m_data); + } + + const void *m_data; + size_t m_size; +}; + +} diff --git a/src/libraries/elf/headers.h b/src/libraries/elf/headers.h new file mode 100644 index 0000000..cd6bbf4 --- /dev/null +++ b/src/libraries/elf/headers.h @@ -0,0 +1,98 @@ +#pragma once +#include +#include "kutil/enum_bitfields.h" + +namespace elf { + +enum class wordsize : uint8_t { invalid, bits32, bits64 }; +enum class encoding : uint8_t { invalid, lsb, msb }; +enum class osabi : uint8_t { sysV, hpux, standalone = 255 }; +enum class machine : uint16_t { none, x64 = 0x3e }; + +enum class filetype : uint16_t +{ + none, + relocatable, + executable, + shared, + core +}; + + +struct file_header +{ + uint32_t magic; + + wordsize word_size; + encoding endianness; + uint8_t ident_version; + osabi os_abi; + + uint64_t reserved; + + filetype file_type; + machine machine_type; + + uint32_t version; + + uint64_t entrypoint; + uint64_t ph_offset; + uint64_t sh_offset; + + uint32_t flags; + + uint16_t eh_size; + + uint16_t ph_entsize; + uint16_t ph_num; + + uint16_t sh_entsize; + uint16_t sh_num; + + uint16_t sh_str_idx; +} __attribute__ ((packed)); + + +enum class segment_type : uint32_t { null, load, dynamic, interpreter, note }; + +struct program_header +{ + segment_type type; + uint32_t flags; + uint64_t offset; + + uint64_t vaddr; + uint64_t paddr; + + uint64_t file_size; + uint64_t mem_size; + + uint64_t align; +} __attribute__ ((packed)); + + +enum class section_type : uint32_t { null, progbits }; +enum class section_flags : uint64_t +{ + write = 0x01, + alloc = 0x02, + exec = 0x04, +}; + +struct section_header +{ + uint32_t name_offset; + section_type type; + section_flags flags; + uint64_t addr; + uint64_t offset; + uint64_t size; + uint32_t link; + uint32_t info; + uint64_t align; + uint64_t entry_size; +} __attribute__ ((packed)); + +} // namespace elf + +IS_BITFIELD(elf::section_flags); diff --git a/src/libraries/elf/wscript b/src/libraries/elf/wscript new file mode 100644 index 0000000..f572913 --- /dev/null +++ b/src/libraries/elf/wscript @@ -0,0 +1,14 @@ + +def configure(ctx): + pass + +def build(bld): + sources = bld.path.ant_glob("**/*.cpp") + + bld.stlib( + source = sources, + name = 'elf', + target = 'elf', + ) + +# vim: ft=python et diff --git a/src/libraries/kutil/memory.cpp b/src/libraries/kutil/memory.cpp index 71766b0..7a48a20 100644 --- a/src/libraries/kutil/memory.cpp +++ b/src/libraries/kutil/memory.cpp @@ -17,9 +17,9 @@ memset(void *s, uint8_t v, size_t n) } void * -memcpy(void *dest, void *src, size_t n) +memcpy(void *dest, const void *src, size_t n) { - uint8_t *s = reinterpret_cast(src); + const uint8_t *s = reinterpret_cast(src); uint8_t *d = reinterpret_cast(dest); for (size_t i = 0; i < n; ++i) d[i] = s[i]; return d; diff --git a/src/libraries/kutil/memory.h b/src/libraries/kutil/memory.h index 72e2237..67d37ea 100644 --- a/src/libraries/kutil/memory.h +++ b/src/libraries/kutil/memory.h @@ -35,7 +35,7 @@ void * memset(void *p, uint8_t v, size_t n); /// \src The memory to copy from /// \n The number of bytes to copy /// \returns A pointer to the destination memory -void * memcpy(void *dest, void *src, size_t n); +void * memcpy(void *dest, const void *src, size_t n); /// Read a value of type T from a location in memory /// \arg p The location in memory to read