Commit 24842f33 authored by Nifou's avatar Nifou

WIP: Dynamically linked binaries

*  Handle the field `interpreter` of ELF files
*  Load the `interpreter`s binary
*  Add some syscalls
*  Add some FS structures
parent 3fe175da
Pipeline #175138537 passed with stages
in 9 minutes
......@@ -20,6 +20,7 @@ pages:
- touch root/bin/hello
- touch root/bin/hello2
- touch root/bin/test-std
- touch root/bin/test-std-dynamic
- cd kernel && cargo doc
- cd .. && mkdir public
- cp -r kernel/target/doc/* public
......
......@@ -119,7 +119,7 @@ set_up_page_tables:
mov [(p2_table - 0xc0000000) + ecx * 8], eax ; map ecx-th entry
inc ecx ; increase counter
cmp ecx, 6 ; the kernel size is 0x800000 (see memory/mod.rs:KERNEL_SIZE), so 0x800000 / 0x200000 + 1 = 6
cmp ecx, 9 ; the kernel size is 0x800000 (see memory/mod.rs:KERNEL_SIZE), so 0x1000000 / 0x200000 = 8. We need also to map the kernel heap, so 8 + 1 = 9
jne .map_p2_table ; else map the next entry
ret
......
......@@ -15,21 +15,19 @@
* along with this program. If not, see https://www.gnu.org/licenses.
*/
//! A loader which loads ELF static binaries
use alloc::{collections::btree_map::BTreeMap, prelude::v1::String};
use alloc::{collections::btree_map::BTreeMap, prelude::v1::{String, Vec}};
use core::ptr::copy_nonoverlapping;
use x86_64::structures::paging::page_table::PageTableFlags as Flags;
use goblin::elf::{
program_header::{PT_LOAD, PT_PHDR},
Elf,
};
use x86_64::structures::paging::page_table::PageTableFlags as Flags;
use crate::memory::{self, PAGE_SIZE};
use crate::tasking::{
info::{AuxVecItem, ProcessInfo},
process::{DATA_SEGMENT_SIZE, SIGNAL_STACK_SIZE, STACK_SIZE, ProcessBuilder},
memory::MemoryArea,
process::{ProcessBuilder, STACK_SIZE, DATA_SEGMENT_SIZE, SIGNAL_STACK_SIZE},
};
#[derive(PartialEq, Debug, Clone, Copy)]
......@@ -93,9 +91,8 @@ impl<'a> ElfLoader<'a> {
Ok(())
}
fn info(&self) -> ProcessInfo {
let args = vec![self.path.clone()];
let envs = vec!["PATH=/bin".into()];
/// Create a new ProcessInfo
pub fn info(&self, args: Vec<String>, envs: Vec<String>) -> ProcessInfo {
let mut auxv = BTreeMap::new();
if let Some(phdr) = self
......@@ -135,8 +132,8 @@ impl<'a> ElfLoader<'a> {
}
/// Load the binary in memory
pub fn load(&mut self) {
self.process = Some(ProcessBuilder::new(self.binary.entry as usize, self.info()));
pub fn load(&mut self, args: Vec<String>, envs: Vec<String>) {
self.process = Some(ProcessBuilder::new(self.binary.entry as usize, self.info(args, envs)));
for ph in self.binary.program_headers.clone() {
if ph.p_type == PT_LOAD {
......@@ -206,7 +203,7 @@ impl<'a> ElfLoader<'a> {
let end_stack = start_stack + STACK_SIZE as u64 + PAGE_SIZE as u64;
let start_signal_stack = end_stack;
let _end_signal_stack = start_signal_stack + SIGNAL_STACK_SIZE as u64;
let end_signal_stack = start_signal_stack + SIGNAL_STACK_SIZE as u64;
// Create the stack and the data segment after the creation of the executable image
self.process.as_mut().unwrap().create_data_segment(start_data_segment);
......@@ -219,6 +216,10 @@ impl<'a> ElfLoader<'a> {
memory.data_segment = Some((start_data_segment, end_data_segment));
memory.stack = Some((start_stack, end_stack));
if let Some(interpreter) = self.binary.interpreter {
self.load_interpreter(String::from(interpreter), end_signal_stack);
}
println!("Binary {}:\n - | Executable image | Start: {:#x}, End: {:#x}\n - | Data segment | Start: {:#x}, End: {:#x}\n - | Stack | Start: {:#x}, End: {:#x}",
self.path,
start_image, end_image,
......@@ -226,6 +227,82 @@ impl<'a> ElfLoader<'a> {
start_stack, end_stack);
}
/// Load the interpreter program
fn load_interpreter(&mut self, path: String, load_addr: u64) {
let data = crate::initfs::INITFS
.get_binary_file(path.clone(), 0)
.expect("file not found");
match Elf::parse(data) {
Ok(binary) => {
for ph in binary.program_headers.clone() {
if ph.p_type == PT_LOAD {
let start = load_addr + ph.p_vaddr as u64;
// Map the binary in the kernel's address space
let mappings =
memory::map(start, start + ph.p_memsz, Flags::PRESENT | Flags::WRITABLE);
// Copy the binary in memory
unsafe {
copy_nonoverlapping(
(data.as_ptr() as u64 + ph.p_offset) as *const u8,
start as *mut u8,
ph.p_filesz as usize,
);
}
// Zero the memory after the file
if ph.p_memsz > ph.p_filesz {
for i in ph.p_filesz..ph.p_memsz {
unsafe {
let ptr = (load_addr + ph.p_vaddr) as *mut u8;
*ptr.offset(i as isize) = 0;
}
}
}
// Use the flags of the segment
let mut flags = Flags::NO_EXECUTE;
if ph.is_read() {
flags.insert(Flags::PRESENT);
}
if ph.is_write() {
flags.insert(Flags::WRITABLE);
}
if ph.is_executable() {
flags.remove(Flags::NO_EXECUTE);
}
let mut area = MemoryArea::new(
mappings.first().unwrap().1.start_address().as_u64(),
mappings.last().unwrap().1.start_address().as_u64() + PAGE_SIZE as u64,
flags,
);
area.mappings(mappings.clone());
// Add the mappings in the process' page table
self.process.as_mut().unwrap().memory().map(area);
// Unmap the binary in the kernel's address space
memory::unmap(mappings);
}
}
// Change the entry point
self.process.as_mut().unwrap().thread.get_kstack().rip = (load_addr + binary.entry) as usize;
println!("Interpreter {} loaded successfully!", path);
},
Err(e) => {
println!("ELF Error: {}", e);
}
}
}
/// Get the created process
pub fn get_process(&self) -> ProcessBuilder {
if let Some(process) = self.process.clone() {
......
......@@ -16,7 +16,7 @@
*/
//! ELF manipulation functions: check, load, run
use alloc::prelude::v1::String;
use alloc::prelude::v1::{Vec, String};
use goblin::elf::*;
pub mod loader;
......@@ -25,9 +25,9 @@ use self::loader::ElfLoader;
use crate::initfs;
/// Check, load and run a static linked binary
pub fn load(path: String) {
pub fn load(path: String, args: Vec<String>, envs: Vec<String>) {
let data = initfs::INITFS
.get_binary_file(path.clone())
.get_binary_file(path.clone(), 0)
.expect("file not found");
match Elf::parse(data) {
......@@ -39,7 +39,7 @@ pub fn load(path: String) {
e
);
} else {
loader.load();
loader.load(args, envs);
loader.add();
}
}
......
/*
* Copyright (C) 2020 Nicolas Fouquet
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see https://www.gnu.org/licenses.
*/
use alloc::prelude::v1::String;
use bitflags::bitflags;
bitflags! {
/// Permissions of a file
pub struct Permissions: u32 {
/// Read only
const O_RDONLY = 0;
/// Write only
const O_WRONLY = 1;
/// Read/Write
const O_RDWR = 2;
// TODO: Remove me
/// Execute
const O_EXEC = 3;
}
}
impl From<u32> for Permissions {
fn from(val: u32) -> Self {
use core::mem::transmute;
unsafe { transmute::<u32, Self>(val) }
}
}
bitflags! {
/// Tests used by the `access` syscall
pub struct TestPermissions: u32 {
/// Test if the file has read permission
const R_OK = 4;
/// Test if the file has write permission
const W_OK = 2;
/// Test if the file has executable permission
const X_OK = 1;
/// Test if the file exists
const F_OK = 0;
}
}
impl From<u32> for TestPermissions {
fn from(val: u32) -> Self {
use core::mem::transmute;
unsafe { transmute::<u32, Self>(val) }
}
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
/// A handle to a file
pub struct FileHandle {
/// The path of the file
pub path: String,
/// The offset into the file
pub offset: usize,
// TODO: Permissions
// TODO: Inode
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone)]
/// A file ID
///
/// This ID is used by user programs to open, read, write and close files
pub struct Fd(pub usize);
impl From<usize> for Fd {
fn from(val: usize) -> Self {
use core::mem::transmute;
unsafe { transmute::<usize, Self>(val) }
}
}
impl From<Fd> for usize {
fn from(val: Fd) -> Self {
use core::mem::transmute;
unsafe { transmute::<Fd, Self>(val) }
}
}
......@@ -19,6 +19,8 @@
use alloc::{collections::BTreeMap, prelude::v1::String};
use lazy_static::lazy_static;
use crate::fs::Permissions;
#[derive(PartialEq, Debug, Clone, Copy)]
/// Errors which can happen during the reading of the files
pub enum FileError {
......@@ -37,7 +39,7 @@ lazy_static! {
#[derive(Debug)]
/// Structure to handle volatile files
pub struct InitFS {
files: BTreeMap<String, &'static [u8]>,
files: BTreeMap<String, (&'static [u8], Permissions, usize)>,
}
impl InitFS {
......@@ -48,40 +50,89 @@ impl InitFS {
}
/// Get a file with a name among the files and return an array
pub fn get_binary_file(&self, name: String) -> Result<&'static [u8], FileError> {
pub fn get_binary_file(&self, name: String, offset: usize) -> Result<&'static [u8], FileError> {
return if let Some(content) = self.files.get(&name) {
Ok(&content.0[offset..])
} else {
Err(FileError::NotFound)
};
}
/// Get the permissions of a file
pub fn perms(&self, name: String) -> Result<Permissions, FileError> {
return if let Some(content) = self.files.get(&name) {
Ok(content)
Ok(content.1)
} else {
Err(FileError::NotFound)
};
}
pub fn size(&self, name: String) -> Result<usize, FileError> {
return if let Some(content) = self.files.get(&name) {
Ok(content.2)
} else {
Err(FileError::NotFound)
};
}
/// Get a file with a name among the files and return a String
pub fn get_str_file(&self, name: String) -> Result<String, FileError> {
let binary = self.get_binary_file(name)?;
///
/// Testing purposes only
pub fn get_str_file(&self, name: String, offset: usize) -> Result<String, FileError> {
let binary = self.get_binary_file(name, offset)?;
Ok(String::from_utf8_lossy(binary).into_owned())
}
/// Get all default files
fn all_files() -> BTreeMap<String, &'static [u8]> {
fn all_files() -> BTreeMap<String, (&'static [u8], Permissions, usize)> {
let mut files = BTreeMap::new();
// Insert all needed files
// /bin
files.insert(
String::from("/bin/hello"),
include_bytes!("../../root/bin/hello") as &[u8],
(include_bytes!("../../root/bin/hello") as &[u8], Permissions::O_RDONLY | Permissions::O_EXEC, 0x3e6a8),
);
files.insert(
String::from("/bin/hello2"),
include_bytes!("../../root/bin/hello2") as &[u8],
(include_bytes!("../../root/bin/hello2") as &[u8], Permissions::O_RDONLY | Permissions::O_EXEC, 0x3e588),
);
files.insert(
String::from("/bin/test-std"),
include_bytes!("../../root/bin/test-std") as &[u8],
(include_bytes!("../../root/bin/test-std") as &[u8], Permissions::O_RDONLY | Permissions::O_EXEC, 0x6c423),
);
files.insert(
String::from("/bin/test-std-dynamic"),
(include_bytes!("../../root/bin/test-std-dynamic") as &[u8], Permissions::O_RDONLY | Permissions::O_EXEC, 0x554b2),
);
// /lib
files.insert(
String::from("/lib64/ld-linux-x86-64.so.2"),
(include_bytes!("../../root/lib/ld-linux-x86-64.so.2") as &[u8], Permissions::O_RDONLY | Permissions::O_EXEC, 0x2954b),
);
files.insert(
String::from("/lib/libc.so.6"),
(include_bytes!("../../root/lib/libc.so.6") as &[u8], Permissions::O_RDONLY, 0x1c195d),
);
files.insert(
String::from("/lib/libdl.so.2"),
(include_bytes!("../../root/lib/libdl.so.2") as &[u8], Permissions::O_RDONLY, 0x2623),
);
files.insert(
String::from("/lib/libgcc_s.so.1"),
(include_bytes!("../../root/lib/libgcc_s.so.1") as &[u8], Permissions::O_RDONLY, 0x16ad9),
);
files.insert(
String::from("/lib/libpthread.so.0"),
(include_bytes!("../../root/lib/libpthread.so.0") as &[u8], Permissions::O_RDONLY, 0x1e7dd),
);
// /
files.insert(
String::from("/README.md"),
include_bytes!("../../README.md") as &[u8],
(include_bytes!("../../README.md") as &[u8], Permissions::O_RDONLY, 0x822),
);
files
......
......@@ -44,6 +44,7 @@ pub mod memory;
pub mod sse;
pub mod syscall;
pub mod tasking;
pub mod fs;
use alloc::{alloc::Layout, prelude::v1::String};
use core::panic::PanicInfo;
......@@ -128,9 +129,18 @@ pub extern "C" fn kmain(info_addr: usize) {
println!("Initialize components...");
init();
elf::load(String::from("/bin/hello"));
elf::load(String::from("/bin/hello2"));
elf::load(String::from("/bin/test-std"));
let args = vec!["/bin/hello".into()];
let envs = vec!["PATH=/bin".into()];
elf::load(String::from("/bin/hello"), args, envs.clone());
let args = vec!["/bin/hello2".into()];
elf::load(String::from("/bin/hello2"), args, envs.clone());
let args = vec!["/bin/test-std".into()];
elf::load(String::from("/bin/test-std"), args, envs.clone());
let args = vec!["/bin/test-std-dynamic".into()];
elf::load(String::from("/bin/test-std-dynamic"), args, envs);
println!("ObsidianOS is ready!");
x86_64::instructions::interrupts::enable();
......
......@@ -52,7 +52,7 @@ pub const KERNEL_START_VIRT: u64 = 0xc0000000;
pub const KERNEL_START_PHYS: u64 = 0;
/// The size of the kernel
pub const KERNEL_SIZE: u64 = 0x900000;
pub const KERNEL_SIZE: u64 = 0x1000000;
/// The start of the multiboot information structure (Virtual)
pub const MULTIBOOT_START_VIRT: u64 = KERNEL_START_VIRT + KERNEL_SIZE;
......
......@@ -24,11 +24,17 @@ pub enum Error {
/// Out of memory
ENOMEM = 12,
/// Permission denied
EACCES = 13,
/// Invalid value
EINVAL = 22,
/// Function not implemented
ENOSYS = 38,
/// Bad file descriptor
EBADFD = 77,
}
/// A return type for all syscalls
......
......@@ -18,26 +18,111 @@
use core::slice::from_raw_parts;
use core::str::from_utf8;
use crate::initfs::{
FileError,
INITFS,
};
use crate::fs::{Permissions, TestPermissions, FileHandle, Fd};
use crate::tasking::scheduler::{SCHEDULER, PROCESSES};
use super::{
error::{Error, Result},
Syscall,
Syscall, get_string,
};
const STDIN: usize = 0;
const STDOUT: usize = 1;
const STDERR: usize = 2;
const AT_FDCWD: usize = (-100 as isize) as usize;
const AT_SYMLINK_NOFOLLOW: usize = 0x100;
const AT_REMOVEDIR: usize = 0x200;
const AT_SYMLINK_FOLLOW: usize = 0x400;
const AT_NO_AUTOMOUNT: usize = 0x800;
const AT_EMPTY_PATH: usize = 0x1000;
#[derive(Debug)]
#[allow(missing_docs)] // TODO: Remove me
pub struct StatInfo {
pub st_dev: u64,
pub st_ino: u64,
pub st_nlink: u64,
pub st_mode: u32,
pub st_uid: u32,
pub st_gid: u32,
__pad0: i32,
pub st_rdev: u64,
pub st_size: i64,
pub st_blksize: i64,
pub st_blocks: i64,
pub st_atime: i64,
pub st_atime_nsec: i64,
pub st_mtime: i64,
pub st_mtime_nsec: i64,
pub st_ctime: i64,
pub st_ctime_nsec: i64,
__unused: [i64; 3],
}
#[derive(Debug)]
struct PipePair {
fd: [u32; 2],
}
impl Syscall {
/// Open a file
pub fn open(&self, path: usize, flags: usize, mode: usize) -> Result { self.openat(AT_FDCWD, path, flags, mode) }
/// Open a file
///
/// If the pathname given in pathname is relative, then it is interpreted relative to the
/// directory referred to by the file descriptor `dirfd`.
pub fn openat(&self, dirfd: usize, path: usize, flags: usize, mode: usize) -> Result {
// TODO: `dirfd`, `flags` and `mode` are ignored for now
let path = unsafe { get_string(path).unwrap() };
print!("openat({}, {:?}, {}, {}) = ", dirfd, path, flags, mode);
if let Ok(_) = INITFS.perms(path.clone()) {
let mut processes = PROCESSES.write();
let current = SCHEDULER.get().current().unwrap().process;
let process = processes.get_mut(&current).unwrap();
let handle = FileHandle {
path,
offset: 0,
};
let id = usize::from(process.add_file(handle));
println!("{}", id);
Ok(id)
} else {
println!("ENOENT");
Err(Error::ENOENT)
}
}
/// Read a file descriptor
pub fn read(&self, fd: usize, buf: usize, count: usize) -> Result {
/* TODO: Read syscall */
println!("read({}, {:#x}, {})", fd, buf, count);
Ok(0)
let mut processes = PROCESSES.write();
let current = SCHEDULER.get().current().unwrap().process;
let process = processes.get_mut(&current).unwrap();
match process.get_file_mut(Fd(fd)) {
Some(file) => {
let dest = buf as *mut u8;
match INITFS.get_binary_file(file.path.clone(), file.offset) {
Ok(content) => {
let src = content.as_ptr();
unsafe { src.copy_to(dest, count); }
file.offset += count;
Ok(count)
},
Err(_) => Err(Error::EINVAL),
}
},
None => Err(Error::EBADFD),
}
}
/// Write to the corresponding file descriptor
......@@ -58,10 +143,40 @@ impl Syscall {
}
}
/// Write to the corresponding file descriptor
pub fn writev(&mut self, fd: usize, iov: usize, iovcnt: usize) -> Result {
#[derive(Debug, Clone, Copy)]