Add ET_DYN support for the ELF loader, which required adding

relocations, add some more functions to libxunil, use include inside of
tests instead of manually defining types and add basic libc header
files. Move libxunil to the user folder, and add helloworld and
doomgeneric as apps
This commit is contained in:
csd4ni3l
2026-04-01 21:25:34 +02:00
parent 47a480009f
commit 6ac13a876f
23 changed files with 437 additions and 37 deletions

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "user/apps/doomgeneric"]
path = user/apps/doomgeneric
url = git@github.com:xunilGroup/doomgeneric-xunil.git

Binary file not shown.

View File

@@ -101,6 +101,39 @@ pub const AT_UID: u64 = 11;
pub const AT_GID: u64 = 13; pub const AT_GID: u64 = 13;
pub const AT_NULL: u64 = 0; pub const AT_NULL: u64 = 0;
// Dynamic structure types
pub const DT_NULL: i64 = 0;
pub const DT_NEEDED: i64 = 1;
pub const DT_PLTRELSZ: i64 = 2;
pub const DT_PLTGOT: i64 = 3;
pub const DT_HASH: i64 = 4;
pub const DT_STRTAB: i64 = 5;
pub const DT_SYMTAB: i64 = 6;
pub const DT_RELA: i64 = 7;
pub const DT_RELASZ: i64 = 8;
pub const DT_RELAENT: i64 = 9;
pub const DT_STRSZ: i64 = 10;
pub const DT_SYMENT: i64 = 11;
pub const DT_INIT: i64 = 12;
pub const DT_FINI: i64 = 13;
pub const DT_SONAME: i64 = 14;
pub const DT_RPATH: i64 = 15;
pub const DT_SYMBOLIC: i64 = 16;
pub const DT_REL: i64 = 17;
pub const DT_RELSZ: i64 = 18;
pub const DT_RELENT: i64 = 19;
pub const DT_PLTREL: i64 = 20;
pub const DT_DEBUG: i64 = 21;
pub const DT_TEXTREL: i64 = 22;
pub const DT_JMPREL: i64 = 23;
pub const DT_BIND_NOW: i64 = 24;
pub const DT_RUNPATH: i64 = 29;
pub const DT_FLAGS: i64 = 30;
pub const DT_GNU_HASH: i64 = 0x6ffffef5;
pub const DT_VERSYM: i64 = 0x6ffffff0;
pub const DT_VERNEED: i64 = 0x6ffffffe;
pub const DT_VERNEEDNUM: i64 = 0x6fffffff;
#[repr(C)] #[repr(C)]
pub struct Elf64Ehdr { pub struct Elf64Ehdr {
pub e_ident: [u8; 16], pub e_ident: [u8; 16],
@@ -155,6 +188,13 @@ pub struct Elf64Sym {
pub st_size: u64, pub st_size: u64,
} }
#[repr(C)]
#[derive(Copy, Clone, Debug)]
pub struct Elf64Dyn {
pub d_tag: i64,
pub d_val: u64,
}
#[repr(C)] #[repr(C)]
pub struct Elf64Rela { pub struct Elf64Rela {
pub r_offset: u64, pub r_offset: u64,

View File

@@ -36,8 +36,8 @@ pub fn load_file(
} }
return match elf_header.e_type { return match elf_header.e_type {
ET_EXEC => unsafe { load_program(frame_allocator, mapper, elf_header, elf_bytes) }, ET_EXEC => unsafe { load_program(frame_allocator, mapper, elf_header, elf_bytes, false) },
ET_DYN => return null(), // TODO ET_DYN => unsafe { load_program(frame_allocator, mapper, elf_header, elf_bytes, true) }, // TODO
ET_REL => return null(), ET_REL => return null(),
_ => return null(), _ => return null(),
}; };

View File

@@ -1,3 +1,9 @@
use core::{
ffi::CStr,
ptr::{null, null_mut},
};
use alloc::vec::Vec;
use x86_64::{ use x86_64::{
VirtAddr, VirtAddr,
structures::paging::{ structures::paging::{
@@ -8,7 +14,16 @@ use x86_64::{
use crate::{ use crate::{
arch::x86_64::paging::XunilFrameAllocator, arch::x86_64::paging::XunilFrameAllocator,
driver::{ driver::{
elf::header::{Elf64Ehdr, Elf64Phdr, PF_X, PT_LOAD}, elf::{
header::{
DT_JMPREL, DT_NEEDED, DT_NULL, DT_PLTREL, DT_PLTRELSZ, DT_RELA, DT_RELAENT,
DT_RELASZ, DT_STRSZ, DT_STRTAB, DT_SYMENT, DT_SYMTAB, Elf64Dyn, Elf64Ehdr,
Elf64Phdr, Elf64Rela, Elf64Sym, PF_X, PT_DYNAMIC, PT_LOAD, R_X86_64_64,
R_X86_64_GLOB_DAT, R_X86_64_JUMP_SLOT, R_X86_64_NONE, R_X86_64_RELATIVE, SHN_UNDEF,
STB_WEAK,
},
reloc::elf_do_reloc,
},
syscall::memset, syscall::memset,
}, },
util::{align_down, align_up}, util::{align_down, align_up},
@@ -20,24 +35,229 @@ pub unsafe fn elf_pheader(hdr: *const Elf64Ehdr) -> *const Elf64Phdr {
unsafe { (hdr as *const u8).add((*hdr).e_phoff as usize) as *const Elf64Phdr } unsafe { (hdr as *const u8).add((*hdr).e_phoff as usize) as *const Elf64Phdr }
} }
pub fn get_vaddr(phdr: *const Elf64Phdr, load_bias: u64) -> *mut u8 {
unsafe { ((*phdr).p_vaddr + load_bias) as *mut u8 }
}
pub unsafe fn load_program( pub unsafe fn load_program(
frame_allocator: &mut XunilFrameAllocator, frame_allocator: &mut XunilFrameAllocator,
mapper: &mut OffsetPageTable, mapper: &mut OffsetPageTable,
hdr: *const Elf64Ehdr, hdr: *const Elf64Ehdr,
elf_bytes: &[u8], elf_bytes: &[u8],
pie: bool,
) -> *const u8 { ) -> *const u8 {
let phdr = unsafe { elf_pheader(hdr) }; let phdr = unsafe { elf_pheader(hdr) };
let phnum = unsafe { (*hdr).e_phnum }; let phnum = unsafe { (*hdr).e_phnum };
let mut program_headers: Vec<*const Elf64Phdr> = Vec::new();
let mut pt_dynamic_header: *const Elf64Phdr = core::ptr::null();
for i in 0..phnum { for i in 0..phnum {
let program_header = unsafe { phdr.add(i as usize) }; let program_header = unsafe { phdr.add(i as usize) };
let p_type = unsafe { (*program_header).p_type };
if unsafe { (*program_header).p_type } == PT_LOAD { if p_type == PT_LOAD {
load_segment_to_memory(frame_allocator, mapper, program_header, elf_bytes); program_headers.push(program_header);
} else if p_type == PT_DYNAMIC {
pt_dynamic_header = program_header as *const Elf64Phdr;
} }
} }
return unsafe { (*hdr).e_entry as *const u8 }; if !pie {
for program_header in program_headers {
load_segment_to_memory(frame_allocator, mapper, program_header, elf_bytes, 0);
}
return unsafe { (*hdr).e_entry as *const u8 };
} else {
let base_address = 0x0000_0100_0000; // TODO: add per-process memory
let min_vaddr = align_down(
program_headers
.iter()
.map(|&phdr| get_vaddr(phdr, 0) as u64)
.min()
.unwrap(),
4096,
);
let load_bias = base_address - min_vaddr;
for program_header in program_headers {
load_segment_to_memory(
frame_allocator,
mapper,
program_header,
elf_bytes,
load_bias,
);
}
if pt_dynamic_header.is_null() {
return null();
}
parse_dyn(
hdr,
pt_dynamic_header,
unsafe { elf_bytes.as_ptr().add((*phdr).p_offset as usize) as *const Elf64Dyn },
load_bias,
);
return unsafe { ((*hdr).e_entry + load_bias) as *const u8 };
}
}
fn cstr_from_strtab(
strtab_ptr: *const u8,
strtab_size: u64,
off: u32,
) -> Option<&'static core::ffi::CStr> {
let off = off as u64;
if strtab_ptr.is_null() || off >= strtab_size {
return None;
}
let mut i = off;
while i < strtab_size {
let b = unsafe { *strtab_ptr.add(i as usize) };
if b == 0 {
let start = unsafe { strtab_ptr.add(off as usize) } as *const core::ffi::c_char;
return Some(unsafe { CStr::from_ptr(start as *const i8) });
}
i += 1;
}
None
}
pub fn dyn_get_symaddr(
strtab_ptr: *const u8,
strtab_size: u64,
symtab_ptr: *const Elf64Sym,
idx: u64,
load_bias: u64,
) -> Result<u64, ()> {
let sym = unsafe { &*symtab_ptr.add(idx as usize) };
if sym.st_shndx != SHN_UNDEF {
return Ok(load_bias + sym.st_value);
}
let name = cstr_from_strtab(strtab_ptr, strtab_size, idx as u32);
let bind = sym.st_info >> 4;
if bind == STB_WEAK { Ok(0) } else { Err(()) }
}
fn apply_relocations(
hdr: *const Elf64Ehdr,
rela_ptr: *mut Elf64Rela,
rela_table_size: u64,
symtab_ptr: *const Elf64Sym,
strtab_ptr: *const u8,
strtab_size: u64,
load_bias: u64,
) {
for i in 0..rela_table_size as usize / size_of::<Elf64Rela>() {
let rela_ptr = unsafe { rela_ptr.add(i) };
let ptr = unsafe { (load_bias + (*rela_ptr).r_offset) as *mut u64 };
let mut value: u64 = 0;
match unsafe { ((*rela_ptr).r_info & 0xffff_ffff) as u32 } {
x if x == R_X86_64_RELATIVE as u32 => unsafe {
value = (load_bias as i64 + (*rela_ptr).r_addend) as u64;
},
x if x == R_X86_64_64 as u32 => unsafe {
value = (dyn_get_symaddr(
strtab_ptr,
strtab_size,
symtab_ptr,
(*rela_ptr).r_info >> 32,
load_bias,
)
.unwrap() as i64
+ (*rela_ptr).r_addend) as u64;
},
x if [R_X86_64_GLOB_DAT, R_X86_64_JUMP_SLOT].contains(&x) => unsafe {
value = dyn_get_symaddr(
strtab_ptr,
strtab_size,
symtab_ptr,
(*rela_ptr).r_info >> 32,
load_bias,
)
.unwrap() as u64;
},
x if x == R_X86_64_NONE as u32 => {
continue; // explicitly do nothing
}
_ => {
continue;
}
}
unsafe { *ptr = value };
}
}
fn parse_dyn(
hdr: *const Elf64Ehdr,
phdr: *const Elf64Phdr,
dyn_hdr_original: *const Elf64Dyn,
load_bias: u64,
) {
let mut i: usize = 0;
let file_size: u64 = unsafe { (*phdr).p_filesz };
let mut rela_ptr: *mut Elf64Rela = null_mut();
let mut rela_table_size: u64 = 0;
let mut symtab_ptr: *const Elf64Sym = null();
let mut strtab_ptr: *const u8 = null();
let mut strtab_size: u64 = 0;
let max = file_size as usize / size_of::<Elf64Dyn>();
while unsafe { (*dyn_hdr_original.add(i)).d_tag != DT_NULL } && i < max {
let dyn_hdr = unsafe { *(dyn_hdr_original.add(i)) };
match dyn_hdr.d_tag {
DT_RELA => rela_ptr = (dyn_hdr.d_val + load_bias) as *mut Elf64Rela,
DT_RELASZ => {
rela_table_size = dyn_hdr.d_val;
}
DT_JMPREL => {
// TODO: plt relocations
}
DT_PLTREL => {
// TODO: plt relocations
}
DT_PLTRELSZ => {
// TODO: plt relocations
}
DT_NEEDED => {
// TODO: do dynamic loading
}
DT_SYMTAB => symtab_ptr = (dyn_hdr.d_val + load_bias) as *const Elf64Sym,
DT_STRTAB => strtab_ptr = (dyn_hdr.d_val + load_bias) as *const u8,
DT_STRSZ => {
strtab_size = dyn_hdr.d_val;
}
_ => (),
}
i += 1;
}
if rela_ptr.is_null() || symtab_ptr.is_null() || strtab_ptr.is_null() {
return;
}
apply_relocations(
hdr,
rela_ptr,
rela_table_size,
symtab_ptr,
strtab_ptr,
strtab_size,
load_bias,
);
} }
pub fn load_segment_to_memory( pub fn load_segment_to_memory(
@@ -45,12 +265,13 @@ pub fn load_segment_to_memory(
mapper: &mut OffsetPageTable, mapper: &mut OffsetPageTable,
phdr: *const Elf64Phdr, phdr: *const Elf64Phdr,
elf_bytes: &[u8], elf_bytes: &[u8],
load_bias: u64,
) { ) {
let mem_size: u64 = unsafe { (*phdr).p_memsz }; let mem_size: u64 = unsafe { (*phdr).p_memsz };
let p_offset: u64 = unsafe { (*phdr).p_offset }; let p_offset: u64 = unsafe { (*phdr).p_offset };
let file_size: u64 = unsafe { (*phdr).p_filesz }; let file_size: u64 = unsafe { (*phdr).p_filesz };
let vaddr: *mut u8 = unsafe { (*phdr).p_vaddr as *mut u8 }; let vaddr: *mut u8 = get_vaddr(phdr, load_bias);
if p_offset > elf_bytes.len() as u64 if p_offset > elf_bytes.len() as u64
|| file_size > elf_bytes.len() as u64 || file_size > elf_bytes.len() as u64

View File

@@ -11,25 +11,29 @@ pub unsafe fn elf_section(hdr: *const Elf64Ehdr, idx: usize) -> *const Elf64Shdr
unsafe { elf_sheader(hdr).add(idx) } unsafe { elf_sheader(hdr).add(idx) }
} }
// pub unsafe fn elf_str_table(hdr: *const Elf64Ehdr) -> *const u8 { pub unsafe fn elf_str_table(hdr: *const Elf64Ehdr) -> *const u8 {
// if unsafe { (*hdr).e_shstrndx == SHN_UNDEF } { if unsafe { (*hdr).e_shstrndx == SHN_UNDEF } {
// return core::ptr::null(); return core::ptr::null();
// } }
// let shdr = unsafe { elf_section(hdr, (*hdr).e_shstrndx as usize) }; let shdr = unsafe { elf_section(hdr, (*hdr).e_shstrndx as usize) };
// unsafe { (hdr as *const u8).add((*shdr).sh_offset as usize) } unsafe { (hdr as *const u8).add((*shdr).sh_offset as usize) }
// } }
// pub unsafe fn elf_lookup_string(hdr: *const Elf64Ehdr, offset: usize) -> *const u8 { fn elf_lookup_string<'a>(hdr: *const Elf64Ehdr, offset: usize) -> &'a str {
// let str_tab: *const u8 = unsafe { elf_str_table(hdr) }; let strtab: *const u8 = unsafe { elf_str_table(hdr) };
let mut len = 0;
// if str_tab.is_null() { let start = unsafe { strtab.add(offset) };
// return core::ptr::null();
// }
// return unsafe { str_tab.add(offset) }; unsafe {
// } while *start.add(len) != 0 {
len += 1;
}
core::str::from_raw_parts(start, len)
}
}
pub fn elf_lookup_symbol(name: &CStr) -> *const u8 { pub fn elf_lookup_symbol(name: &CStr) -> *const u8 {
return core::ptr::null(); return core::ptr::null();

View File

@@ -39,6 +39,10 @@ pub unsafe fn free(ptr: *mut u8, size: usize, align: usize) {
} }
} }
pub unsafe fn memset(ptr: *mut u8, val: u8, count: usize) {
unsafe { core::ptr::write_bytes(ptr, val, count) };
}
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub unsafe extern "C" fn syscall_dispatch( pub unsafe extern "C" fn syscall_dispatch(
num: usize, num: usize,
@@ -65,9 +69,5 @@ pub unsafe extern "C" fn syscall_dispatch(
} }
} }
pub unsafe fn memset(ptr: *mut u8, val: u8, count: usize) {
unsafe { core::ptr::write_bytes(ptr, val, count) };
}
pub type Fd = i32; pub type Fd = i32;
pub type Off = i64; pub type Off = i64;

View File

@@ -1,7 +1,8 @@
#![no_std] #![no_std]
#![no_main] #![no_main]
#![feature(abi_x86_interrupt)] #![feature(abi_x86_interrupt)]
#![feature(str_from_raw_parts)]
#![allow(warnings)]
extern crate alloc; extern crate alloc;
use core::fmt::Write; use core::fmt::Write;

View File

@@ -1,4 +0,0 @@
gcc -nostdlib -nostdinc -static -no-pie \
-o $1 $1.c \
-L../target/release -l:libxunil.a
mv $1 ../../assets/$1.elf

View File

@@ -1,7 +0,0 @@
void write(int fd, const char* buf, unsigned long count);
void exit(int code);
void _start() {
write(1, "Hello from C!\n", 14);
exit(0);
}

View File

@@ -0,0 +1,4 @@
gcc -nostdlib -nostdinc -static -static-pie \
-o $1 $1.c \
-L../../libxunil/target/release -l:libxunil.a
mv $1 ../../../assets/$1.elf

View File

@@ -0,0 +1,6 @@
#include "../src/stdio.h"
void _start() {
write(0, "Hello, World!", 13);
exit(0);
}

View File

@@ -0,0 +1,2 @@
#include <stddef.h>
#include <stdint.h>

View File

@@ -0,0 +1,32 @@
#pragma once
#include <stddef.h>
#include <stdarg.h>
typedef struct FILE FILE;
extern FILE *stdin;
extern FILE *stdout;
extern FILE *stderr;
#define EOF (-1)
FILE *fopen(const char *path, const char *mode);
int fclose(FILE *fp);
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *fp);
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *fp);
int fseek(FILE *fp, long offset, int whence);
long ftell(FILE *fp);
int fflush(FILE *fp);
char *fgets(char *s, int size, FILE *fp);
int fputs(const char *s, FILE *fp);
int feof(FILE *fp);
int ferror(FILE *fp);
int printf(const char *fmt, ...);
int fprintf(FILE *fp, const char *fmt, ...);
int sprintf(char *buf, const char *fmt, ...);
int snprintf(char *buf, size_t size, const char *fmt, ...);
int vsnprintf(char *buf, size_t size, const char *fmt, va_list ap);
int vfprintf(FILE *fp, const char *fmt, va_list ap);
void write(int fd, const char* buf, unsigned long count);
void exit(int code);

View File

@@ -0,0 +1,20 @@
#pragma once
#include <stddef.h>
void *malloc(size_t size);
void *calloc(size_t nmemb, size_t size);
void *realloc(void *ptr, size_t size);
void free(void *ptr);
void exit(int status);
void abort(void);
int atoi(const char *s);
long atol(const char *s);
double atof(const char *s);
long strtol(const char *s, char **endptr, int base);
double strtod(const char *s, char **endptr);
char *getenv(const char *name);
void qsort(void *base, size_t nmemb, size_t size, int (*cmp)(const void *, const void *));
int abs(int x);

View File

@@ -0,0 +1,22 @@
#pragma once
#include <stddef.h>
void *memset(void *s, int c, size_t n);
void *memcpy(void *dst, const void *src, size_t n);
void *memmove(void *dst, const void *src, size_t n);
int memcmp(const void *s1, const void *s2, size_t n);
void *memchr(const void *s, int c, size_t n);
size_t strlen(const char *s);
char *strcpy(char *dst, const char *src);
char *strncpy(char *dst, const char *src, size_t n);
char *strcat(char *dst, const char *src);
char *strncat(char *dst, const char *src, size_t n);
int strcmp(const char *s1, const char *s2);
int strncmp(const char *s1, const char *s2, size_t n);
char *strchr(const char *s, int c);
char *strrchr(const char *s, int c);
char *strstr(const char *haystack, const char *needle);
char *strtok(char *str, const char *delim);
char *strdup(const char *s);
char *strerror(int errnum);

View File

@@ -0,0 +1,8 @@
#pragma once
#include <stddef.h>
int strcasecmp(const char *s1, const char *s2);
int strncasecmp(const char *s1, const char *s2, size_t n);
void bzero(void *s, size_t n);
void bcopy(const void *src, void *dest, size_t n);
int bcmp(const void *s1, const void *s2, size_t n);

View File

@@ -35,6 +35,32 @@ extern "C" fn puts(s: *const u8) -> isize {
0 0
} }
#[unsafe(no_mangle)]
extern "C" fn abs(n: i32) -> i32 {
n.abs()
}
#[unsafe(no_mangle)]
extern "C" fn atoi(mut c: *const u8) -> i32 {
let mut value: i32 = 0;
let mut sign: i32 = 1;
unsafe {
if (*c) == b'+' || (*c) == b'-' {
if *c == b'-' {
sign = -1;
}
c = c.add(1);
}
while (*c).is_ascii_digit() {
value *= 10;
value += ((*c) - b'0') as i32;
c = c.add(1);
}
}
value * sign
}
#[panic_handler] #[panic_handler]
fn panic(_: &core::panic::PanicInfo) -> ! { fn panic(_: &core::panic::PanicInfo) -> ! {
loop {} loop {}

View File

@@ -0,0 +1,22 @@
abs
atoi
calloc
__ctype_toupper_loc
fprintf
free
malloc
memcpy
memset
printf
realloc
__stack_chk_fail
stderr
strcasecmp
strcmp
strdup
strlen
strncasecmp
strncmp
strncpy
strrchr
toupper