Skip to content
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added .DS_Store
Binary file not shown.
10 changes: 9 additions & 1 deletion harm-runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,12 @@ publish = false

[dependencies]
harm = { workspace = true }
memmap2 = "0.9.9"
memmap2 = { version = "0.9.9", optional = true }
thiserror = "2.0.18"

[features]
default = ["memmap2"]
memmap2 = ["dep:memmap2"]

[dev-dependencies]
clear-cache = "0.1.3"
159 changes: 159 additions & 0 deletions harm-runtime/src/builder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
/* Copyright (C) 2026 Ivan Boldyrev
*
* This document is licensed under the BSD 3-clause license.
*/

use std::collections::HashMap;

use harm::reloc::{Addr64, LabelId, Offset64, Rel64, Rel64Error};

#[derive(Debug, thiserror::Error)]
pub enum BuilderError {
#[error("Address overflow: base 0x{0:016x}, offset 0x{1:016x}")]
AddressOverflow(u64, usize),
#[error("Offset overflow: base {0:016x}, offset {1:016x}")]
OffsetOverflow(u64, i64),
#[error("Relocation error: {nested:?} at offset {offset:016x}")]
Relocation { nested: Rel64Error, offset: usize },
#[error("Undefined label: {0:?}")]
UndefinedLabel(LabelId),
}

/// Do static relocations: recalculate labels and applies relocations, producing memory ready for execution.
///
/// Please note that real memory location may be different from base address: it allows to build at some buffer
/// and then move data to real position later.
pub struct Builder<'mem> {
mem: &'mem mut [u8], // real memory
base: Addr64, // virtual base, on ARM system usually matches with `mem` start
}

impl<'mem> Builder<'mem> {
pub fn new(mem: &'mem mut [u8], base: Addr64) -> Self {
Self { mem, base }
}

pub fn build(
self,
named_labels: impl Iterator<Item = (&'mem str, LabelId)>,
labels: impl Iterator<Item = (LabelId, Offset64)>,
relocations: impl Iterator<Item = (usize, Rel64)>,
) -> Result<HashMap<String, u64>, BuilderError> {
// Recalculate labels.
let labels: HashMap<_, _> = labels
.map(|(label_id, offset)| {
let addr = self
.base
.checked_add_signed(offset)
.ok_or(BuilderError::OffsetOverflow(self.base, offset));
addr.map(|addr| (label_id, addr))
})
.collect::<Result<_, BuilderError>>()?;

// Calculate label addresses.
let label_addresses = named_labels
.map(|(name, label_id)| {
let label_addr = labels
.get(&label_id)
.copied()
.ok_or(BuilderError::UndefinedLabel(label_id))?;
Ok((name.to_owned(), label_addr))
})
.collect::<Result<_, BuilderError>>()?;

// Apply relocations to the self.mem.
for (offset, rel) in relocations {
let label_addr = labels
.get(&rel.label.id)
.copied()
.ok_or(BuilderError::UndefinedLabel(rel.label.id))?;
// TODO is it wrapping?
let label_ref_addr = label_addr.wrapping_add_signed(rel.label.addend);

rel.apply(self.base, label_ref_addr, self.mem, offset)
.map_err(|nested| BuilderError::Relocation { nested, offset })?;
}

Ok(label_addresses)
}
}

#[cfg(test)]
mod tests {
use harm::reloc::{LabelId, LabelRef, Rel64Tag};

use super::*;

#[test]
fn test_good_offset() {
let mut mem = vec![0u8; 4];
let builder = Builder::new(&mut mem, 0);
let label_ref = LabelRef {
id: LabelId(0),
addend: 0,
};
let relocations = [(0, Rel64::new(Rel64Tag::NONE, label_ref))];
let res = builder.build(
[].into_iter(),
[(LabelId(0), 4)].into_iter(),
relocations.into_iter(),
);

assert!(res.is_ok(), "{res:?}");
}

#[test]
fn test_bad_offset() {
let mut mem = vec![0u8; 4];
let builder = Builder::new(&mut mem, 0);
let label_ref = LabelRef {
id: LabelId(0),
addend: 0,
};
// N.B. NONE relocation is 0 bytes wide, so 4 doesn't fail. Use 5.
let relocations = [(5, Rel64::new(Rel64Tag::NONE, label_ref))];
let res = builder.build(
[].into_iter(),
[(LabelId(0), 4)].into_iter(),
relocations.into_iter(),
);

assert!(
matches!(
res,
Err(BuilderError::Relocation {
nested: _,
offset: _
})
),
"{res:?}"
);
}

#[test]
fn test_bad_offset_max() {
let mut mem = vec![0u8; 4];
let builder = Builder::new(&mut mem, 0);
let label_ref = LabelRef {
id: LabelId(0),
addend: 0,
};
let relocations = [(usize::MAX, Rel64::new(Rel64Tag::NONE, label_ref))];
let res = builder.build(
[].into_iter(),
[(LabelId(0), 4)].into_iter(),
relocations.into_iter(),
);

assert!(
matches!(
res,
Err(BuilderError::Relocation {
nested: _,
offset: _
})
),
"{res:?}"
);
}
}
103 changes: 101 additions & 2 deletions harm-runtime/src/labels.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,110 @@
* This document is licensed under the BSD 3-clause license.
*/

use harm::reloc::Offset;
use std::collections::HashMap;

use harm::reloc::{LabelId, Offset64};

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum LabelInfo {
Forward,
// TODO segment
Offset(Offset),
Offset(Offset64),
}

#[derive(Debug, Default)]
pub struct LabelRegistry {
named_labels: HashMap<String, LabelId>,
labels: HashMap<LabelId, LabelInfo>,
next_id: usize,
}

impl LabelRegistry {
#[inline]
pub fn new() -> Self {
Self::default()
}

#[inline]
pub fn get_forward_named_label(&mut self, name: &str) -> LabelId {
if let Some(id) = self.named_labels.get(name) {
*id
} else {
let id = self.next_label();
self.named_labels.insert(name.to_string(), id);
self.labels.insert(id, LabelInfo::Forward);
id
}
}

#[inline]
pub fn forward_label(&mut self) -> LabelId {
let id = self.next_label();
self.labels.insert(id, LabelInfo::Forward);
id
}

/// Define the label to have its address to be base address plus `offset`.
pub fn define_label(&mut self, label_id: LabelId, offset: Offset64) {
if let Some(info) = self.labels.get_mut(&label_id) {
match info {
LabelInfo::Forward => {
*info = LabelInfo::Offset(offset);
}
LabelInfo::Offset(_) => {
todo!("Label {label_id:?} is already defined");
}
}
} else {
todo!("Label {label_id:?} is not registered");
}
}

/// Define the label to have its address to be base address plus `offset`.
#[inline]
pub fn define_named_label(&mut self, name: &str, offset: Offset64) -> LabelId {
if let Some(id) = self.named_labels.get(name).copied() {
self.labels.insert(id, LabelInfo::Offset(offset));
id
} else {
let id = self.next_label();
self.named_labels.insert(name.to_string(), id);
self.labels.insert(id, LabelInfo::Offset(offset));
id
}
}

/// Turn the label into a named.
pub fn name_label(&mut self, id: LabelId, name: &str) {
if self.labels.contains_key(&id) {
self.named_labels.insert(name.to_string(), id);
} else {
todo!("Label {id:?} is not registered");
}
}

/// Return current label info.
#[inline]
pub fn label_info(&self, id: LabelId) -> Option<&LabelInfo> {
self.labels.get(&id)
}

pub fn get_named_labels(&self) -> impl Iterator<Item = (&str, LabelId)> {
self.named_labels
.iter()
.map(|(name, id)| (name.as_str(), *id))
}

pub fn get_defined_labels(&self) -> impl Iterator<Item = (LabelId, Offset64)> {
self.labels.iter().filter_map(|(id, info)| match info {
LabelInfo::Offset(offset) => Some((*id, *offset)),
LabelInfo::Forward => None,
})
}

fn next_label(&mut self) -> LabelId {
let id = LabelId(self.next_id);
self.next_id += 1;
id
}
}
5 changes: 4 additions & 1 deletion harm-runtime/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
/* Copyright (C) 2025 Ivan Boldyrev
/* Copyright (C) 2026 Ivan Boldyrev
*
* This document is licensed under the BSD 3-clause license.
*/

pub mod builder;
pub mod labels;
pub mod memory;
pub mod runtime;
Loading