This commit is contained in:
nora 2025-03-07 23:27:54 +01:00
commit 7183421b8f
10 changed files with 378 additions and 0 deletions

1
.envrc Normal file
View file

@ -0,0 +1 @@
use nix

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
/target
/test/*
!/test/*.c
!/test/Makefile

97
Cargo.lock generated Normal file
View file

@ -0,0 +1,97 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "aligned-vec"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af15ccceeacb9304119d97925de463bc97ae3555ee8dc8056f67b119f66e5934"
dependencies = [
"equator",
]
[[package]]
name = "equator"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc"
dependencies = [
"equator-macro",
]
[[package]]
name = "equator-macro"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "eyre"
version = "0.6.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec"
dependencies = [
"indenter",
"once_cell",
]
[[package]]
name = "indenter"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
[[package]]
name = "once_cell"
version = "1.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e"
[[package]]
name = "proc-macro2"
version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rustv64i"
version = "0.1.0"
dependencies = [
"aligned-vec",
"eyre",
]
[[package]]
name = "syn"
version = "2.0.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e02e925281e18ffd9d640e234264753c43edc62d64b2d4cf898f1bc5e75f3fc2"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"

8
Cargo.toml Normal file
View file

@ -0,0 +1,8 @@
[package]
name = "rustv64i"
version = "0.1.0"
edition = "2024"
[dependencies]
aligned-vec = "0.6.2"
eyre = "0.6.12"

3
shell.nix Normal file
View file

@ -0,0 +1,3 @@
{ pkgs ? import <nixpkgs> { } }: pkgs.mkShell {
packages = with pkgs; [ llvmPackages_18.clang-unwrapped llvmPackages_18.lld ];
}

145
src/elf.rs Normal file
View file

@ -0,0 +1,145 @@
use eyre::{Result, bail};
pub struct Elf {
pub content: Vec<u8>,
}
#[derive(Debug)]
pub struct Header {
pub e_entry: u32,
pub e_phoff: u32,
pub e_shoff: u32,
pub e_flags: u32,
pub e_ehsize: u16,
pub e_phentsize: u16,
pub e_phnum: u16,
pub e_shentsize: u16,
pub e_shnum: u16,
pub e_shstrndx: u16,
}
#[derive(Debug)]
pub struct Phdr {
pub p_type: u32,
pub p_offset: u32,
pub p_vaddr: u32,
pub p_paddr: u32,
pub p_filesz: u32,
pub p_memsz: u32,
pub p_flags: u32,
pub p_align: u32,
}
impl Elf {
pub fn header(&self) -> Result<Header> {
let (ident, rest) = self.content.split_bytes(16)?;
if ident[..4] != *b"\x7fELF" {
bail!("not an elf file (invalid magic)");
}
// ELFCLASS32
if ident[5] != 1 {
bail!("not a ELF32 file (EI_CLASS={})", ident[5]);
}
let (e_type, rest) = rest.split_u16()?;
// ET_EXEC
if e_type != 2 {
bail!("not a static executable: {e_type}");
}
let (e_machine, rest) = rest.split_u16()?;
// EM_RISCV
if e_machine != 243 {
bail!("not a RISC-V executable");
}
let (_e_version, rest) = rest.split_u32()?;
let (e_entry, rest) = rest.split_u32()?;
let (e_phoff, rest) = rest.split_u32()?;
let (e_shoff, rest) = rest.split_u32()?;
let (e_flags, rest) = rest.split_u32()?;
let (e_ehsize, rest) = rest.split_u16()?;
let (e_phentsize, rest) = rest.split_u16()?;
let (e_phnum, rest) = rest.split_u16()?;
let (e_shentsize, rest) = rest.split_u16()?;
let (e_shnum, rest) = rest.split_u16()?;
let (e_shstrndx, _) = rest.split_u16()?;
Ok(Header {
e_entry,
e_phoff,
e_shoff,
e_flags,
e_ehsize,
e_phentsize,
e_phnum,
e_shentsize,
e_shnum,
e_shstrndx,
})
}
pub fn segments(&self) -> Result<Vec<Phdr>> {
let header = self.header()?;
let (_, phdrs) = self.content.split_bytes(header.e_phoff as usize)?;
let (mut phdrs, _) = phdrs.split_bytes((header.e_phentsize * header.e_phnum) as usize)?;
let mut parsed_phdrs = vec![];
while !phdrs.is_empty() {
let phdr;
(phdr, phdrs) = phdrs.split_bytes(header.e_phentsize as usize)?;
let (p_type, phdr) = phdr.split_u32()?;
let (p_offset, phdr) = phdr.split_u32()?;
let (p_vaddr, phdr) = phdr.split_u32()?;
let (p_paddr, phdr) = phdr.split_u32()?;
let (p_filesz, phdr) = phdr.split_u32()?;
let (p_memsz, phdr) = phdr.split_u32()?;
let (p_flags, phdr) = phdr.split_u32()?;
let (p_align, _) = phdr.split_u32()?;
parsed_phdrs.push(Phdr {
p_type,
p_offset,
p_vaddr,
p_paddr,
p_filesz,
p_memsz,
p_flags,
p_align,
});
}
Ok(parsed_phdrs)
}
}
pub trait SplitAtCheckedErr {
fn split_bytes(&self, split: usize) -> Result<(&[u8], &[u8])>;
fn split_u16(&self) -> Result<(u16, &[u8])>;
fn split_u32(&self) -> Result<(u32, &[u8])>;
fn split_u64(&self) -> Result<(u64, &[u8])>;
}
impl SplitAtCheckedErr for [u8] {
fn split_bytes(&self, mid: usize) -> Result<(&[u8], &[u8])> {
if self.len() < mid {
bail!("invalid file: too short");
}
Ok(self.split_at(mid))
}
fn split_u16(&self) -> Result<(u16, &[u8])> {
let (bytes, rest) = self.split_bytes(2)?;
Ok((u16::from_le_bytes(bytes.try_into().unwrap()), rest))
}
fn split_u32(&self) -> Result<(u32, &[u8])> {
let (bytes, rest) = self.split_bytes(4)?;
Ok((u32::from_le_bytes(bytes.try_into().unwrap()), rest))
}
fn split_u64(&self) -> Result<(u64, &[u8])> {
let (bytes, rest) = self.split_bytes(8)?;
Ok((u64::from_le_bytes(bytes.try_into().unwrap()), rest))
}
}

41
src/emu.rs Normal file
View file

@ -0,0 +1,41 @@
use aligned_vec::{AVec, ConstAlign};
use crate::PAGE_SIZE;
pub struct Memory {
pub mem: AVec<u8, ConstAlign<PAGE_SIZE>>,
}
impl Memory {
fn load_u32(&self, addr: u32) -> Result<u32, ()> {
Ok(u32::from_le_bytes(
self.mem
.get((addr as usize)..)
.ok_or(())?
.get(..4)
.ok_or(())?
.try_into()
.unwrap(),
))
}
}
pub struct Emulator {
pub mem: Memory,
pub xreg: [u32; 32],
pub pc: u32,
}
impl Emulator {
pub fn execute(&mut self) -> Result<(), ()> {
loop {
self.step();
}
}
fn step(&mut self) -> Result<(), ()> {
let instruction = self.mem.load_u32(self.pc)?;
todo!()
}
}

71
src/main.rs Normal file
View file

@ -0,0 +1,71 @@
use aligned_vec::avec;
use eyre::{OptionExt, bail};
mod elf;
mod emu;
const PAGE_SIZE: usize = 4096;
// 2 MiB
const MEMORY_SIZE: usize = 2 << 21;
fn main() -> eyre::Result<()> {
let content = std::fs::read(std::env::args().nth(1).unwrap()).unwrap();
let elf = elf::Elf { content };
let header = elf.header()?;
dbg!(&header);
let segments = elf.segments()?;
let mut mem = emu::Memory {
mem: avec![[PAGE_SIZE]| 0; MEMORY_SIZE],
};
for phdr in segments {
dbg!(&phdr);
match phdr.p_type {
// PT_NULL
0 => {}
// PT_LOAD
1 => {
if phdr.p_filesz > 0 {
let contents = &elf
.content
.get((phdr.p_offset as usize)..)
.ok_or_eyre("invalid offset")?
.get(..(phdr.p_filesz as usize))
.ok_or_eyre("invalid offset")?;
mem.mem
.get_mut((phdr.p_vaddr as usize)..)
.ok_or_eyre("invalid offset")?
.get_mut(..(phdr.p_filesz as usize))
.ok_or_eyre("invalid offset")?
.copy_from_slice(contents);
}
}
// PT_PHDR
6 => {}
// PT_GNU_EH_FRAME
1685382480 => {}
// PT_GNU_STACK
1685382481 => {}
// PT_RISCV_ATTRIBUTES
0x70000003 => {}
_ => bail!("unknown program header type: {}", phdr.p_type),
}
}
let start = header.e_entry;
let mut emu = emu::Emulator {
mem,
xreg: [0; 32],
pc: start,
};
emu.execute().unwrap();
Ok(())
}

5
test/Makefile Normal file
View file

@ -0,0 +1,5 @@
CC = clang -target riscv32-unknown-linux-gnu -fuse-ld=lld -march=rv32i
CC_STATIC = $(CC) -static -nostdlib -nodefaultlibs
test: test.c
$(CC_STATIC) test.c -o test

3
test/test.c Normal file
View file

@ -0,0 +1,3 @@
void _start()
{
}