From 1747989c23a8953f3216700c295bd7c863dc4ea1 Mon Sep 17 00:00:00 2001 From: Dan Printzell Date: Mon, 22 Aug 2016 01:05:00 +0200 Subject: [PATCH] [Data/ELF] Added ELF loader and example Userspace ELF file. Signed-off-by: Dan Printzell --- .gitignore | 3 + Initrd/Binary/Init | 2 - Kernel/src/Data/ELF.d | 441 ++++++++++++++++++++++++++++++++++++++++++ Kernel/src/KMain.d | 16 +- Userspace/Init/app.d | 130 +++++++++++++ build | 23 +++ 6 files changed, 611 insertions(+), 4 deletions(-) delete mode 100644 Initrd/Binary/Init create mode 100644 Kernel/src/Data/ELF.d create mode 100644 Userspace/Init/app.d diff --git a/.gitignore b/.gitignore index b220826..f7d8224 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,6 @@ Utils/GenerateSymbols Utils/MakeInitrd .vscode *.psf +*.o +.dub +Initrd/Binary diff --git a/Initrd/Binary/Init b/Initrd/Binary/Init deleted file mode 100644 index 25e0494..0000000 --- a/Initrd/Binary/Init +++ /dev/null @@ -1,2 +0,0 @@ -THIS WILL BE A INIT SYSTEM! -echo "hello" \ No newline at end of file diff --git a/Kernel/src/Data/ELF.d b/Kernel/src/Data/ELF.d new file mode 100644 index 0000000..39340f1 --- /dev/null +++ b/Kernel/src/Data/ELF.d @@ -0,0 +1,441 @@ +module Data.ELF; + +import Data.BitField; +import Data.Address; +import Data.String; +import IO.FS.FileNode; +import IO.Log; +import Data.TextBuffer : scr = GetBootTTY; +import Task.Process; +import Memory.Heap; + +struct ELF64Header { + struct Identification { + char[4] magic; + + enum Class : ubyte { + None, + _32, + _64 + } + + Class class_; + + enum Data : ubyte { + None, + LeastSignificantBit, + MostSignificantBit + } + + Data data; + + enum ELFVersion : ubyte { + None, + Current + } + + ELFVersion elfVersion; + + enum OSABI : ubyte { + None, + PowerNex = 16 + } + + OSABI osABI; + + enum ABIVersion : ubyte { + Current = 0 + } + + ABIVersion abiVersion; + + private char[7] pad; + } + + static assert(Identification.sizeof == 16); + Identification identification; + + enum ObjectType : ushort { + None, + Relocatable, + Executable, + Shared, + Core + } + + ObjectType type; + + enum Machine : ushort { + None, + I386 = 3, + AMD64 = 0x3E + } + + Machine machine; + + uint fileVersion; + VirtAddress entry; + VirtAddress programHeaderOffset; + VirtAddress sectionHeaderOffset; + private uint flags; // not used + ushort elfHeaderSize; + ushort programHeaderEntrySize; + ushort programHeaderCount; + ushort sectionHeaderEntrySize; + ushort sectionHeaderCount; + ushort sectionHeaderStringTableIndex; + + @property bool Valid() { + immutable char[4] ELF64Magic = [0x7F, 'E', 'L', 'F']; + return identification.magic == ELF64Magic && programHeaderEntrySize == ELF64ProgramHeader.sizeof + && ELF64SectionHeader.sizeof == sectionHeaderEntrySize; + } +} + +struct ELF64ProgramHeader { + enum Type : uint { + Null, + Load, + Dynamic, + Interpreter, + Note, + SHLIB, // Not use, Not allowed + ProgramHeader, + ThreadLocalStorage, + + GNUEHFrameHeader = 0x6474E550, + GNUStack = 0x6474E551, + } + + Type type; + + enum Flags : uint { + None, + X = 1 << 0, + W = 1 << 1, + R = 1 << 2 + } + + Flags flags; + VirtAddress offset; + VirtAddress virtAddress; + PhysAddress physAddress; + ulong fileSize; + ulong memorySize; + ulong align_; +} + +struct ELF64SectionHeader { + uint nameIdx; + + enum Type : uint { + Null, + ProgramBits, + SymbolTable, + StringTable, + RelocationEntries, + SymbolHashTable, + DynamicLinking, + Note, + NoBits, + RelocationOffsets, + SHLIB, // Not used, not allowed + DynamicLinkingSymbols, + ConstructorArray = 14, + DestructorArray, + PreConstructorArray, + GNUHashTable = 0x6FFFFFF6, + GNUVersionNeeds = 0x6FFFFFFE, + GNUVersionSymbolTable = 0x6FFFFFFF, + } + + Type type; + + enum Flags : ulong { + Null, + Write = 1 << 0, + Allocate = 1 << 1, + ExecutableInstructions = 1 << 2, + Merge = 1 << 4, + Strings = 1 << 5, + InfoLink = 1 << 6, + LinkOrder = 1 << 7, + Group = 1 << 9, + ThreadLocalData = 1 << 10, + Compressed = 1 << 11, + + Allocate_Write = Allocate | Write, + ExecutableInstructions_Allocate = ExecutableInstructions | Allocate, + Strings_Merge = Strings | Merge, + InfoLink_Allocate = InfoLink | Allocate, + ThreadLocalData_Allocate_Write = ThreadLocalData | Allocate | Write + } + + Flags flags; + VirtAddress address; + VirtAddress offset; + ulong size; + uint link; + uint info; + ulong addressAlign; + ulong entrySize; +} + +struct ELF64Symbol { + uint name; + struct Info { + enum InfoType : ubyte { + NoType, + Object, + Function, + Section, + File, + Common, + TLS + } + + enum InfoBinding : ubyte { + Local, + Global, + Weak + } + + private ubyte data; + @property InfoType Type() { + return cast(InfoType)(data & 0xF); + } + + @property InfoType Type(InfoType type) { + data = data & 0xF0 | type & 0xF; + return type; + } + + @property InfoBinding Binding() { + return cast(InfoBinding)((data & 0xF0 >> 4) & 0x2); + } + + @property InfoBinding Binding(InfoBinding binding) { + data = (binding << 4) & 0xF0 | data & 0xF; + return binding; + } + } + + Info info; + enum Other : ubyte { + Default, + Internal, + Hidden, + Protected + } + + Other other; + ushort sectionIndex; + VirtAddress value; + ulong size; +} + +struct ELF64Relocation { + VirtAddress offset; + struct Info { + uint sym; + uint type; + } + + Info info; +} + +struct ELF64RelocationAddend { + VirtAddress offset; + struct Info { + uint sym; + uint type; + } + + Info info; + int addend; +} + +struct ELF64Dynamic { + enum Tag : long { + Null, + Needed, + PLTRelocationEntries, + PLTGOT, + HashTable, + StringTable, + SymbolTable, + RelocationAddendTable, + RelocationAddendTableSize, + RelocationAddendTableEntrySize, + StringTableSize, + SymbolTableEntrySize, + Init, + Fini, + SOName, + RPath, + Symbolic, + RelocationTable, + RelocationTableSize, + RelocationTableEntrySize, + PLTRel, + Debug, + TextRel, + JumpRel, + BindNow, + RunPath + } + + Tag tag; + VirtAddress valueOrAddress; +} + +class ELF { +public: + this(FileNode file) { + this.file = file; + + if (file.Size <= ELF64Header.sizeof) + return; + + file.Read((cast(ubyte*)&header)[0 .. ELF64Header.sizeof], 0); + valid = header.Valid; + + foreach (idx; 0 .. header.sectionHeaderCount) { + ELF64SectionHeader sectionHdr = GetSectionHeader(idx); + if (sectionHdr.type == ELF64SectionHeader.Type.SymbolTable) + symtabIdx = idx; + else if (sectionHdr.type == ELF64SectionHeader.Type.StringTable) + strtabIdx = idx; + } + } + + void MapAndRun() { + import Memory.Paging; + import Task.Scheduler; + + Scheduler scheduler = GetScheduler; + Process* process = scheduler.CurrentProcess; + Paging paging = process.threadState.paging; + + if (process.heap && !(--process.heap.RefCounter)) + process.heap.destroy; + + paging.RemoveUserspace(true); + + VirtAddress startHeap; + + foreach (idx; 0 .. header.programHeaderCount) { + ELF64ProgramHeader program = GetProgramHeader(idx); + if (program.type != ELF64ProgramHeader.Type.Load) + continue; + + MapMode mode = MapMode.User; + if (!(program.flags & ELF64ProgramHeader.Flags.X)) + mode |= MapMode.NoExecute; + if (program.flags & ELF64ProgramHeader.Flags.W) + mode |= MapMode.Writable; + // Page will always be readable + + VirtAddress start = program.virtAddress & ~0xFFF; + VirtAddress end = program.virtAddress + program.memorySize; + VirtAddress cur = start; + while (cur < end) { + paging.MapFreeMemory(cur, MapMode.DefaultKernel); + cur += 0x1000; + } + + log.Debug("Start: ", start, " End: ", end, " cur: ", cur, " Mode: R", (mode & MapMode.Writable) ? "W" : "", + (mode & MapMode.NoExecute) ? "" : "X", (mode & MapMode.User) ? "-User" : ""); + + memset(start.Ptr, 0, (program.virtAddress - start).Int); + cur = (program.virtAddress + program.fileSize); + memset(cur.Ptr, 0, (end - cur).Int); + file.Read(program.virtAddress.Ptr!ubyte[0 .. program.fileSize], program.offset.Int); + + cur = start; + while (cur < end) { + auto page = paging.GetPage(cur); + page.Mode = mode; + cur += 0x1000; + } + + if (end > startHeap) + startHeap = end; + } + + // Setup stack, setup heap + + asm { + cli; + } + + startHeap = (startHeap.Int + 0xFFF) & ~0xFFF; + + process.heap = new Heap(process.threadState.paging, MapMode.DefaultUser, startHeap, VirtAddress(0xFFFF_FFFF_0000_0000)); + + enum StackSize = 0x1000; + VirtAddress userStack = VirtAddress(process.heap.Alloc(StackSize)) + StackSize; + process.image.userStack = userStack; + + switchToUserMode(header.entry.Int, userStack.Int); + } + + ELF64ProgramHeader GetProgramHeader(size_t idx) { + assert(idx < header.programHeaderCount); + ELF64ProgramHeader programHdr; + file.Read(&programHdr, header.programHeaderOffset + header.programHeaderEntrySize * idx); + return programHdr; + } + + ELF64SectionHeader GetSectionHeader(size_t idx) { + assert(idx < header.sectionHeaderCount); + ELF64SectionHeader sectionHdr; + file.Read(§ionHdr, header.sectionHeaderOffset + header.sectionHeaderEntrySize * idx); + return sectionHdr; + } + + ELF64Symbol GetSymbol(size_t idx) { + assert(symtabIdx != ulong.max); + ELF64SectionHeader symtab = GetSectionHeader(symtabIdx); + ELF64Symbol symbol; + file.Read(&symbol, symtab.offset + ELF64Symbol.sizeof * idx); + return symbol; + } + + /// Note that the output will only be valid until GetSectionName is called again + char[] GetSectionName(uint nameIdx) { + __gshared char[255] buf; + if (!header.sectionHeaderStringTableIndex) + return cast(char[])"UNKNOWN"; + + file.Read(buf, GetSectionHeader(header.sectionHeaderStringTableIndex).offset + nameIdx); + + return buf[0 .. strlen(buf)]; + } + + /// Note that the output will only be valid until GetSymbolName is called again + char[] GetSymbolName(uint idx) { + __gshared char[255] buf; + if (!strtabIdx) + return cast(char[])"UNKNOWN"; + + file.Read(buf, GetSectionHeader(strtabIdx).offset + idx); + + return buf[0 .. strlen(buf)]; + } + + @property bool Valid() { + return valid; + } + + @property ELF64Header Header() { + return header; + } + +private: + FileNode file; + bool valid; + ELF64Header header; + ulong strtabIdx = ulong.max; + ulong symtabIdx = ulong.max; +} diff --git a/Kernel/src/KMain.d b/Kernel/src/KMain.d index 7bb4721..0967664 100644 --- a/Kernel/src/KMain.d +++ b/Kernel/src/KMain.d @@ -28,6 +28,7 @@ import HW.PCI.PCI; import HW.CMOS.CMOS; import System.SyscallHandler; import Data.TextBuffer : scr = GetBootTTY; +import Data.ELF; import Bin.BasicShell; @@ -44,9 +45,20 @@ extern (C) int kmain(uint magic, ulong info) { sti; } - scheduler.AddThread(new BasicShellThread()); + string initFile = "/Binary/Init"; - while (true) { + ELF init = new ELF(cast(FileNode)rootFS.Root.FindNode(initFile)); + if (init.Valid) { + scr.Writeln(initFile, " is valid! Loading..."); + + scr.Writeln(); + auto tmp = scr.Foreground; + scr.Foreground = scr.Background; + scr.Background = tmp; + init.MapAndRun(); + } else { + scr.Writeln("Invalid ELF64 file"); + log.Fatal("Invalid ELF64 file!"); } scr.Foreground = Color(255, 0, 255); diff --git a/Userspace/Init/app.d b/Userspace/Init/app.d new file mode 100644 index 0000000..74a432a --- /dev/null +++ b/Userspace/Init/app.d @@ -0,0 +1,130 @@ +extern (C) void _start() { + asm { + naked; + mov RBP, RSP; + call dmain; + mov RDI, RAX; + jmp exit; + } +} + +ulong ExitValue = 0; +__gshared align(16) ubyte[0x1000] CloneStack = void; + +ulong dmain() { + const(char)* HelloWorld = "Hello World from Userspace and D!"; + const(char)* TryClone = "Trying to clone!"; + const(char)* CloneName = "Cloned process!"; + const(char)* TryFork = "Trying to fork!"; + const(char)* ForkSuccess = "FORK WORKED!"; + + printcstr(HelloWorld); + + printcstr(TryClone); + clone(&cloneEntry, &CloneStack.ptr[0x1000], null, CloneName); + + ulong pid = fork(); + printcstr(ForkSuccess); + + if (!pid) + return 0x31415; + + while (true) + yield(); +} + +void cloneEntry() { + asm { + naked; + mov RBP, RSP; + call cloneFunction; + mov RDI, RAX; + jmp exit; + } +} + +ulong cloneFunction() { + const(char)* CloneSuccess = "CLONE WORKED!\0"; + printcstr(CloneSuccess); + + ExitValue = 0x1337; + + return ExitValue; +} + +ulong exit(ulong exitCode) { + asm { + naked; + mov RAX, 0; + int 0x80; + exit_loop: + hlt; // Should never happen + jmp exit_loop; + } +} + +ulong printcstr(const(char)* str) { + asm { + mov RDI, str; + mov RAX, 16; + int 0x80; + } +} + +ulong clone(void function() func, void* stack, void* userdata, const(char)* name) { + asm { + mov RDI, func; + mov RSI, stack; + mov RDX, userdata; + mov RCX, name; + + mov RAX, 1; + int 0x80; + } +} + +ulong fork() { + asm { + mov RAX, 2; + int 0x80; + } +} + +ulong yield() { + asm { + mov RAX, 3; + int 0x80; + } +} + +// Hack below to make dmd compile the file + +alias immutable(char)[] string; + +extern (C) __gshared void* _Dmodule_ref; + +extern (C) int __dmd_personality_v0(int, int, ulong, void*, void*) { + return 0; +} + +__gshared void* _minfo_beg; +__gshared void* _minfo_end; +__gshared immutable(void)* _deh_beg; +__gshared immutable(void)* _deh_end; + +extern (C) void _d_dso_registry(void* data) { +} + +/*void _d_array_bounds(ModuleInfo* m, uint line) { + _d_arraybounds(m.name, line); + }*/ + +extern (C) void _d_arraybounds(string m, uint line) { + //throw new Error("Range error", m, line); +} + +extern (C) void _d_unittest() { +} + +extern (C) void _d_assert(string file, uint line) { +} diff --git a/build b/build index 3e378ff..a3d914a 100755 --- a/build +++ b/build @@ -38,6 +38,19 @@ "MakeInitrd" : { "command": "Utils/MakeInitrd", "arguments": "$in $out" + }, + + "user-dc": { + "command": "cc/bin/powernex-dmd", + "arguments": "-m64 -debug -c -IKernel/src -defaultlib= -debuglib= -version=bare_metal -debug=allocations -of$out $in" + }, + "user-ac": { + "command": "cc/bin/x86_64-powernex-as", + "arguments": "--64 -o $out $in" + }, + "user-ld": { + "command": "cc/bin/x86_64-powernex-ld", + "arguments": "-o $out $in" } }, "phonies": { @@ -58,6 +71,15 @@ } }, "rules": { + "Userspace/Init/*.o": { + "processor": "user-dc", + "input": "Userspace/Init/*.d" + }, + "Initrd/Binary/Init": { + "processor": "user-ld", + "input": "Userspace/Init/*.o" + }, + "Kernel/obj/DCode.o": { "processor": "dc", "input": "Kernel/src/*.d" @@ -91,6 +113,7 @@ "Disk/boot/PowerNex.krl", "Initrd/Data/PowerNex.map", "Initrd/Data/Font/TTYFont.psf", + "Initrd/Binary/Init", "Disk/boot/PowerNex.dsk", "PowerNex.iso" ]