diff --git a/Makefile.config b/Makefile.config index fea0f5c2..afd5bd5a 100644 --- a/Makefile.config +++ b/Makefile.config @@ -4,8 +4,8 @@ KERNEL_CCFLAGS=-Wall -c -ffreestanding -fno-pie -g -std=gnu99 # which is likely to work on native linux-x86. # CC=gcc -m32 -LD=ld -melf_i386 -#LD=ld -arch i386 +LD=ld +KERNEL_LD=ld -melf_i386 AR=ar OBJCOPY=objcopy ISOGEN=genisoimage @@ -15,6 +15,7 @@ ISOGEN=genisoimage # add cross/bin to your path, and uncomment these lines: #CC=i686-elf-gcc #LD=i686-elf-ld +#KERNEL_LD=i686-elf-ld #AR=i686-elf-ar #OBJCOPY=i686-elf-objcopy diff --git a/kernel/Makefile b/kernel/Makefile index c73677a3..870be7f2 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -1,6 +1,6 @@ include ../Makefile.config -KERNEL_OBJECTS=kernelcore.o main.o console.o page.o keyboard.o mouse.o event_queue.o clock.o interrupt.o kmalloc.o pic.o ata.o cdromfs.o string.o bitmap.o graphics.o font.o syscall_handler.o process.o mutex.o list.o pagetable.o rtc.o kshell.o fs.o hash_set.o diskfs.o serial.o elf.o device.o kobject.o pipe.o bcache.o printf.o is_valid.o window.o +KERNEL_OBJECTS=kernelcore.o main.o console.o page.o keyboard.o mouse.o event_queue.o clock.o interrupt.o kmalloc.o pic.o ata.o cdromfs.o string.o bitmap.o graphics.o font.o syscall_handler.o process.o mutex.o list.o pagetable.o rtc.o kshell.o fs.o hash_set.o diskfs.o serial.o loader.o device.o kobject.o pipe.o bcache.o printf.o is_valid.o window.o basekernel.img: bootblock kernel cat bootblock kernel /dev/zero | head -c 1474560 > basekernel.img @@ -12,10 +12,10 @@ bootblock: bootblock.elf ${OBJCOPY} -O binary $< $@ kernel.elf: ${KERNEL_OBJECTS} - ${LD} ${KERNEL_LDFLAGS} -Ttext 0x10000 ${KERNEL_OBJECTS} -o $@ + ${KERNEL_LD} ${KERNEL_LDFLAGS} -Ttext 0x10000 ${KERNEL_OBJECTS} -o $@ bootblock.elf: bootblock.o - ${LD} ${KERNEL_LDFLAGS} -Ttext 0 $< -o $@ + ${KERNEL_LD} ${KERNEL_LDFLAGS} -Ttext 0 $< -o $@ %.o: %.c ${CC} ${KERNEL_CCFLAGS} -I ../include $< -o $@ diff --git a/kernel/elf.c b/kernel/elf.c deleted file mode 100644 index c848efb4..00000000 --- a/kernel/elf.c +++ /dev/null @@ -1,182 +0,0 @@ -/* -Copyright (C) 2015-2019 The University of Notre Dame -This software is distributed under the GNU General Public License. -See the file LICENSE for details. -*/ - -#include "elf.h" -#include "fs.h" -#include "string.h" -#include "console.h" -#include "process.h" -#include "kernel/syscall.h" -#include "memorylayout.h" - -struct elf_header { - char ident[16]; - uint16_t type; - uint16_t machine; - uint32_t version; - uint32_t entry; - uint32_t program_offset; - uint32_t section_offset; - uint32_t flags; - uint16_t header_size; - uint16_t phentsize; - uint16_t phnum; - uint16_t shentsize; - uint16_t shnum; - uint16_t shstrndx; -}; - -#define ELF_HEADER_TYPE_NONE 0 -#define ELF_HEADER_TYPE_OBJECT 1 -#define ELF_HEADER_TYPE_EXECUTABLE 2 -#define ELF_HEADER_TYPE_DYNAMIC 3 -#define ELF_HEADER_TYPE_CORE 4 - -#define ELF_HEADER_MACHINE_I386 3 -#define ELF_HEADER_MACHINE_ARM 40 -#define ELF_HEADER_MACHINE_X86_64 62 - -#define ELF_HEADER_VERSION 1 - -struct elf_program { - uint32_t type; - uint32_t offset; - uint32_t vaddr; - uint32_t paddr; - uint32_t file_size; - uint32_t memory_size; - uint32_t flags; - uint32_t align; -}; - -#define ELF_PROGRAM_TYPE_LOADABLE 1 - -struct elf_section { - uint32_t name; - uint32_t type; - uint32_t flags; - uint32_t address; - uint32_t offset; - uint32_t size; - uint32_t link; - uint32_t info; - uint32_t alignment; - uint32_t entry_size; -}; - -#define ELF_SECTION_TYPE_NULL 0 -#define ELF_SECTION_TYPE_PROGRAM 1 -#define ELF_SECTION_TYPE_SYMBOL_TABLE 2 -#define ELF_SECTION_TYPE_STRING_TABLE 3 -#define ELF_SECTION_TYPE_RELA 4 -#define ELF_SECTION_TYPE_HASH 5 -#define ELF_SECTION_TYPE_DYNAMIC 6 -#define ELF_SECTION_TYPE_NOTE 7 -#define ELF_SECTION_TYPE_BSS 8 - -#define ELF_SECTION_FLAGS_WRITE 1 -#define ELF_SECTION_FLAGS_MEMORY 2 -#define ELF_SECTION_FLAGS_EXEC 8 -#define ELF_SECTION_FLAGS_MERGE 16 -#define ELF_SECTION_FLAGS_STRINGS 32 -#define ELF_SECTION_FLAGS_INFO_LINK 64 -#define ELF_SECTION_FLAGS_LINK_ORDER 128 -#define ELF_SECTION_FLAGS_NONSTANDARD 256 -#define ELF_SECTION_FLAGS_GROUP 512 -#define ELF_SECTION_FLAGS_TLS 1024 - - -/* Ensure that the current process has address space up to this value. */ - -static int elf_ensure_address_space( struct process *p, uint32_t addr ) -{ - /* Size of user data area, ignoring start addr */ - uint32_t limit = addr - PROCESS_ENTRY_POINT; - - /* Round up to next page size. */ - uint32_t overflow = limit % PAGE_SIZE; - limit += (PAGE_SIZE-overflow); - - /* Extend virtual memory if needed. */ - if(limit > p->vm_data_size) { - return process_data_size_set(p,limit); - } else { - return 0; - } - - /* Return zero on success. */ -} - -int elf_load(struct process *p, struct fs_dirent *d, addr_t * entry) -{ - struct elf_header header; - struct elf_program program; - struct elf_section section; - int i; - uint32_t actual; - - actual = fs_dirent_read(d, (char *) &header, sizeof(header), 0); - if(actual != sizeof(header)) - goto noload; - - if(strncmp(header.ident, "\177ELF", 4) || header.machine != ELF_HEADER_MACHINE_I386 || header.version != ELF_HEADER_VERSION) - goto noexec; - - actual = fs_dirent_read(d, (char *) &program, sizeof(program), header.program_offset); - if(actual != sizeof(program)) - goto noload; - - //printf("elf: text %x bytes from offset %x at address %x length %x\n",program.file_size,program.offset,program.vaddr,program.memory_size); - - if(program.type != ELF_PROGRAM_TYPE_LOADABLE || program.vaddr < PROCESS_ENTRY_POINT || program.memory_size > 0x8000000 || program.memory_size != program.file_size) - goto noexec; - - process_data_size_set(p, program.memory_size); - - actual = fs_dirent_read(d, (char *) program.vaddr, program.memory_size, program.offset); - if(actual != program.memory_size) - goto mustdie; - - for(i = 0; i < header.shnum; i++) { - actual = fs_dirent_read(d, (char *) §ion, sizeof(section), header.section_offset + i * header.shentsize); - if(actual != sizeof(section)) - goto mustdie; - - if(section.type == ELF_SECTION_TYPE_BSS) { - /* For BSS, just clear that address space to zero. */ - actual = elf_ensure_address_space(p,section.address+section.size); - if(actual!=0) goto nomem; - memset((void *) section.address, section.size, 0); - } else if(section.type == ELF_SECTION_TYPE_PROGRAM && section.address!=0) { - /* For other loadable section types (usually data), load from file. */ - actual = elf_ensure_address_space(p,section.address+section.size); - if(actual!=0) goto nomem; - actual = fs_dirent_read(d,(char*)section.address,section.size,section.offset); - if(actual != section.size) goto mustdie; - } else { - /* skip all other section types */ - } - } - - *entry = header.entry; - return 0; - - noload: - printf("elf: failed to load correctly!\n"); - return KERROR_NOT_FOUND; - - noexec: - printf("elf: not a valid i386 ELF executable\n"); - return KERROR_NOT_EXECUTABLE; - - nomem: - printf("elf: failed to allocate memory\n"); - return KERROR_OUT_OF_MEMORY; - - mustdie: - printf("elf: did not load correctly\n"); - return KERROR_EXECUTION_FAILED; -} diff --git a/kernel/elf.h b/kernel/elf.h index 8fd47b5d..3365c060 100644 --- a/kernel/elf.h +++ b/kernel/elf.h @@ -1,5 +1,5 @@ /* -Copyright (C) 2015-2019 The University of Notre Dame +Copyright (C) 2015-2025 The University of Notre Dame This software is distributed under the GNU General Public License. See the file LICENSE for details. */ @@ -8,16 +8,95 @@ See the file LICENSE for details. #define ELF_H #include "kernel/types.h" -#include "process.h" -#include "fs.h" -/* -elf_load opens the given filename, and if it contains a valid -ELF executable, allocates space in the current process' pagetable, -loads the text, data, and bss into memory, and updates the -entry point value in the current process structure. -*/ +struct elf_header { + char ident[16]; + uint16_t type; + uint16_t machine; + uint32_t version; + uint32_t entry; + uint32_t program_offset; + uint32_t section_offset; + uint32_t flags; + uint16_t header_size; + uint16_t phentsize; + uint16_t phnum; + uint16_t shentsize; + uint16_t shnum; + uint16_t shstrndx; +}; + +#define ELF_HEADER_TYPE_NONE 0 +#define ELF_HEADER_TYPE_OBJECT 1 +#define ELF_HEADER_TYPE_EXECUTABLE 2 +#define ELF_HEADER_TYPE_DYNAMIC 3 +#define ELF_HEADER_TYPE_CORE 4 + +#define ELF_HEADER_MACHINE_I386 3 +#define ELF_HEADER_MACHINE_ARM 40 +#define ELF_HEADER_MACHINE_X86_64 62 + +#define ELF_HEADER_VERSION 1 + +struct elf_program { + uint32_t type; + uint32_t offset; + uint32_t vaddr; + uint32_t paddr; + uint32_t file_size; + uint32_t memory_size; + uint32_t flags; + uint32_t align; +}; + +#define ELF_PROGRAM_TYPE_NULL 0 +#define ELF_PROGRAM_TYPE_LOADABLE 1 +#define ELF_PROGRAM_TYPE_DYNAMIC 2 +#define ELF_PROGRAM_TYPE_INTERPRETER 3 +#define ELF_PROGRAM_TYPE_NOTE 4 +#define ELF_PROGRAM_TYPE_SHARED_LIBRARY 5 +#define ELF_PROGRAM_TYPE_PROGRAM_HEADER 6 +#define ELF_PROGRAM_TYPE_THREAD_LOCAL 7 +#define ELF_PROGRAM_TYPE_GNU_EH_FRAME 0x6474e550 +#define ELF_PROGRAM_TYPE_GNU_STACK 0x6474e551 +#define ELF_PROGRAM_TYPE_GNU_RELRO 0x6474e552 + +#define ELF_PROGRAM_FLAGS_EXEC 1 +#define ELF_PROGRAM_FLAGS_WRITE 2 +#define ELF_PROGRAM_FLAGS_READ 4 + +struct elf_section { + uint32_t name; + uint32_t type; + uint32_t flags; + uint32_t address; + uint32_t offset; + uint32_t size; + uint32_t link; + uint32_t info; + uint32_t alignment; + uint32_t entry_size; +}; + +#define ELF_SECTION_TYPE_NULL 0 +#define ELF_SECTION_TYPE_PROGRAM 1 +#define ELF_SECTION_TYPE_SYMBOL_TABLE 2 +#define ELF_SECTION_TYPE_STRING_TABLE 3 +#define ELF_SECTION_TYPE_RELA 4 +#define ELF_SECTION_TYPE_HASH 5 +#define ELF_SECTION_TYPE_DYNAMIC 6 +#define ELF_SECTION_TYPE_NOTE 7 +#define ELF_SECTION_TYPE_BSS 8 -int elf_load(struct process *p, struct fs_dirent *d, addr_t * entry); +#define ELF_SECTION_FLAGS_WRITE 1 +#define ELF_SECTION_FLAGS_MEMORY 2 +#define ELF_SECTION_FLAGS_EXEC 8 +#define ELF_SECTION_FLAGS_MERGE 16 +#define ELF_SECTION_FLAGS_STRINGS 32 +#define ELF_SECTION_FLAGS_INFO_LINK 64 +#define ELF_SECTION_FLAGS_LINK_ORDER 128 +#define ELF_SECTION_FLAGS_NONSTANDARD 256 +#define ELF_SECTION_FLAGS_GROUP 512 +#define ELF_SECTION_FLAGS_TLS 1024 #endif diff --git a/kernel/loader.c b/kernel/loader.c new file mode 100644 index 00000000..53e21134 --- /dev/null +++ b/kernel/loader.c @@ -0,0 +1,113 @@ +/* +Copyright (C) 2015-2025 The University of Notre Dame +This software is distributed under the GNU General Public License. +See the file LICENSE for details. +*/ + +#include "loader.h" +#include "elf.h" +#include "fs.h" +#include "string.h" +#include "console.h" +#include "process.h" +#include "kernel/syscall.h" +#include "memorylayout.h" + +/* Ensure that the current process has address space up to this value. */ + +static int loader_ensure_address_space( struct process *p, uint32_t addr ) +{ + /* Size of user data area, ignoring start addr */ + uint32_t limit = addr - PROCESS_ENTRY_POINT; + + /* Round up to next page size. */ + uint32_t overflow = limit % PAGE_SIZE; + limit += (PAGE_SIZE-overflow); + + /* Extend virtual memory if needed. */ + if(limit > p->vm_data_size) { + return process_data_size_set(p,limit); + } else { + return 0; + } + + /* Return zero on success. */ +} + +/* Load an ELF executable into user space. */ + +int loader_load_process(struct process *p, struct fs_dirent *d, addr_t * entry) +{ + struct elf_header header; + struct elf_program program; + int i; + uint32_t actual; + + /* Load the overall ELF header from the beginning of the file. */ + actual = fs_dirent_read(d, (char *) &header, sizeof(header), 0); + if(actual != sizeof(header)) { + printf("elf: unable to load elf header.\n"); + return KERROR_NOT_EXECUTABLE; + } + + /* Bail out if the header doesnt not have the expected values. */ + if(strncmp(header.ident, "\177ELF", 4) || header.machine != ELF_HEADER_MACHINE_I386 || header.version != ELF_HEADER_VERSION) { + printf("elf: not a valid i386 executable file.\n"); + return KERROR_NOT_EXECUTABLE; + } + + /* An elf file contains a sequence of "program headers" that correspond to loadable segments. */ + for(i = 0; i < header.phnum; i++) { + + /* Load in the segment header itself. */ + actual = fs_dirent_read(d, (char *) &program, sizeof(program), header.program_offset + i * header.phentsize); + if(actual != sizeof(program)) { + printf("elf: unable to load segment header %d.\n",i); + return KERROR_NOT_EXECUTABLE; + } + + /* Safe to skip segments that are not loadable or zero size: */ + if(program.type != ELF_PROGRAM_TYPE_LOADABLE || program.memory_size==0 ) { + continue; + } + + /* Each segment must be within the expected userspace range. */ + if(program.vaddr < PROCESS_ENTRY_POINT || program.memory_size > 0x8000000) { + printf("elf: segment %d is invalid: vaddr %x size %x lies outside of user address space.\n",i,program.vaddr,program.memory_size); + return KERROR_NOT_EXECUTABLE; + } + + /* Check for unexpected segment configuration. */ + if(program.file_size > program.memory_size) { + printf("elf: segment %d has unexpected file size %x smaller than memory size %x.\n",program.file_size,program.memory_size); + return KERROR_NOT_EXECUTABLE; + } + + + /* Expand the user address space if needed for this segment. */ + if(loader_ensure_address_space(p,program.vaddr + program.memory_size)!=0) { + printf("elf: unable to allocate memory for segment %d vaddr %x size %x\n",i,program.vaddr,program.memory_size); + return KERROR_OUT_OF_MEMORY; + } + + /* If some (or all) of this segment is on disk, load it in. */ + if(program.file_size>0) { + actual = fs_dirent_read(d, (char *) program.vaddr, program.file_size, program.offset); + if(actual != program.file_size) { + printf("elf: unable to load segment %d from disk.\n"); + return KERROR_NOT_EXECUTABLE; + } + } + + /* If the remainder (or all) of this segment is BSS, initialize it. */ + if(program.memory_size>program.file_size) { + memset( (char*) (program.vaddr+program.file_size), program.memory_size-program.file_size, 0 ); + } + + /* XXX Set page table bits here. */ + } + + /* Capture the program entry point for the caller to use. */ + *entry = header.entry; + return 0; +} diff --git a/kernel/loader.h b/kernel/loader.h new file mode 100644 index 00000000..45d5cd08 --- /dev/null +++ b/kernel/loader.h @@ -0,0 +1,23 @@ +/* +Copyright (C) 2015-2025 The University of Notre Dame +This software is distributed under the GNU General Public License. +See the file LICENSE for details. +*/ + +#ifndef LOADER_H +#define LOADER_H + +#include "process.h" +#include "fs.h" + +/* +loader_load_process reads the given file object, +and if it contains a valid ELF executable, loads +the segments of the executable into the process +address space, returning the executable entry point +in the argument "entry". +*/ + +int loader_load_process(struct process *p, struct fs_dirent *d, addr_t * entry); + +#endif diff --git a/kernel/syscall_handler.c b/kernel/syscall_handler.c index f1bda5e6..ee6793eb 100644 --- a/kernel/syscall_handler.c +++ b/kernel/syscall_handler.c @@ -21,7 +21,7 @@ See the file LICENSE for details. #include "pagetable.h" #include "clock.h" #include "rtc.h" -#include "elf.h" +#include "loader.h" #include "kmalloc.h" #include "page.h" #include "ata.h" @@ -119,7 +119,7 @@ int sys_process_run( int fd, int argc, const char **argv) /* Attempt to load the program image. */ addr_t entry; - int r = elf_load(p, k->data.file, &entry); + int r = loader_load_process(p, k->data.file, &entry); if(r >= 0) { /* If load succeeded, reset stack and pass arguments */ process_stack_reset(p, PAGE_SIZE); @@ -168,7 +168,7 @@ int sys_process_wrun( int fd, int argc, const char **argv, int *fds, int fd_len) /* Attempt to load the program image. */ addr_t entry; - int r = elf_load(p, k->data.file, &entry); + int r = loader_load_process(p, k->data.file, &entry); if(r >= 0) { /* If load succeeded, reset stack and pass arguments */ process_stack_reset(p, PAGE_SIZE); @@ -207,7 +207,7 @@ int sys_process_exec( int fd, int argc, const char **argv) char **copy_argv = argv_copy(argc, argv); /* Attempt to load the program image into this process. */ - int r = elf_load(current, k->data.file, &entry); + int r = loader_load_process(current, k->data.file, &entry); /* On failure, return only if our address space is not corrupted. */ if(r < 0) { diff --git a/library/syscall.S b/library/syscall.S index b4676eec..35511446 100644 --- a/library/syscall.S +++ b/library/syscall.S @@ -27,3 +27,8 @@ syscall: addl $4,%esp leave ret + +# This is a GNU peculiarity which indicates that +# the stack does not need to be executable in this module. +.section .note.GNU-stack,"",@progbits + diff --git a/user/Makefile b/user/Makefile index a2a55e20..573d9920 100644 --- a/user/Makefile +++ b/user/Makefile @@ -9,7 +9,7 @@ all: $(USER_PROGRAMS) ${CC} ${KERNEL_CCFLAGS} -I ../include $< -o $@ %.exe: %.o ../library/user-start.o ../library/baselib.a - ${LD} ${KERNEL_LDFLAGS} -Ttext 0x80000000 ../library/user-start.o $< ../library/baselib.a -o $@ + ${LD} -z max-page-size=4096 -T basekernel.user.ldscript $< ../library/baselib.a -o $@ -Map $@.map clean: rm -rf *.exe *.o diff --git a/user/basekernel.user.ldscript b/user/basekernel.user.ldscript new file mode 100644 index 00000000..5eaf35ef --- /dev/null +++ b/user/basekernel.user.ldscript @@ -0,0 +1,56 @@ +/* Linker script for userspace programs in basekernel */ + +OUTPUT_FORMAT("elf32-i386") +OUTPUT_ARCH(i386) + +/* The first module in every program is the startup code of the standard library. */ +STARTUP(../library/user-start.o) + +/* The entry point for execution is the symbol _start found in user-start.o */ +ENTRY(_start) + +SECTIONS { + +/* User programs begin at 0x80000000, the top 2MB of memory. */ +. = 0x80000000; + +/* The text section contains executable code (rx) */ +.text : { *(.text) } + +/* The rodata section contains constants, strings, etc. */ +.rodata : ALIGN(CONSTANT(MAXPAGESIZE)) { *(.rodata) } + +/* The data section contains initialized values. */ +.data : ALIGN(CONSTANT(MAXPAGESIZE)) { *(.data) } + +/* The bss section describes uninitialized data. */ +/* This takes no space in the executable, but is allocated at runtime. */ +.bss : ALIGN(CONSTANT(MAXPAGESIZE)) { *(.bss) } + +/* Any other miscellanous sections produced by the compiler follow here. */ +} + +/* +Note there are some subtle issues about alignment here. + +The GNU linker on Linux defines COMMONPAGESIZE to be typical 4KB pages +and MAXPAGESIZE to be less-common 2MB superpages. These are controlled +by -z common-page-size and -z max-page-size on the command line, and +can be used (but not set) in this file. + +The linker attempts to page-align items in the executable +file on disk, so as to facilitate demand-paging at runtime. +It uses MAXPAGESIZE to do this by default, and so we if don't take +certain steps, the text section will be pushed to 2MB into the file, +and all of a sudden, we have huge executable files. +So, we set -z max-page-size=4096 on the command line. + +Next, we want sections with different permissions to be +4KB page-aligned, so that the kernel can made code (RX), +rodata (R), and data (RW) by apply the proper page protections. +So, the start of the rodata and data sections are aligned +to the start of the next page. Each must be separately +aligned, so that if one is eliminated, those following +are not combined with the text segment. +*/ +