Skip to content

Commit

Permalink
runtime/type-registry: add new type-registry library for the type c…
Browse files Browse the repository at this point in the history
…onfusion APIs

This is written in Rust like `memory-map` is, so I made the `CMakeLists.txt` very similar.
Since the library may be called concurrently, it uses a hash map which is non-trivial in C,
and we already have Rust libraries like `memory-map`, Rust seemed like a good choice here.
  • Loading branch information
kkysen committed Feb 19, 2025
1 parent 26c4211 commit 8037e4a
Show file tree
Hide file tree
Showing 6 changed files with 225 additions and 0 deletions.
1 change: 1 addition & 0 deletions runtime/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ project(runtime)
add_subdirectory(libia2)
add_subdirectory(partition-alloc)
add_subdirectory(tracer)
add_subdirectory(type-registry)
1 change: 1 addition & 0 deletions runtime/tracer/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ else()
set(CARGO_TARGET_FLAG "")
set(CARGO_ARCH_SUFFIX "")
endif()

add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${CARGO_ARCH_SUFFIX}/release/libmemory_map.so
COMMAND CARGO_TARGET_DIR=${CMAKE_CURRENT_BINARY_DIR} cargo build ${CARGO_TARGET_FLAG} --manifest-path ${CMAKE_CURRENT_SOURCE_DIR}/memory-map/Cargo.toml --release
Expand Down
21 changes: 21 additions & 0 deletions runtime/type-registry/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
cmake_minimum_required(VERSION 3.12)
project(type-registry)

if (LIBIA2_AARCH64)
set(CARGO_TARGET_FLAG "--target=aarch64-unknown-linux-gnu" "--config" "target.aarch64-unknown-linux-gnu.linker=\\\"aarch64-linux-gnu-gcc\\\"")
set(CARGO_ARCH_SUFFIX "aarch64-unknown-linux-gnu")
else()
set(CARGO_TARGET_FLAG "")
set(CARGO_ARCH_SUFFIX "")
endif()

add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${CARGO_ARCH_SUFFIX}/release/libtype_registry.so
COMMAND CARGO_TARGET_DIR=${CMAKE_CURRENT_BINARY_DIR} cargo build ${CARGO_TARGET_FLAG} --manifest-path ${CMAKE_CURRENT_SOURCE_DIR}/Cargo.toml --release
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/Cargo.toml ${CMAKE_CURRENT_SOURCE_DIR}/src/lib.rs
)

add_library(type-registry STATIC IMPORTED)
set_property(TARGET type-registry PROPERTY IMPORTED_LOCATION ${CMAKE_CURRENT_BINARY_DIR}/${CARGO_ARCH_SUFFIX}/release/libtype_registry.so)
add_custom_target(type-registry-tgt DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/${CARGO_ARCH_SUFFIX}/release/libtype_registry.so)
add_dependencies(type-registry type-registry-tgt)
7 changes: 7 additions & 0 deletions runtime/type-registry/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 20 additions & 0 deletions runtime/type-registry/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[package]
name = "type-registry"
version = "0.1.0"
edition = "2021"
rust-version = "1.84"

[lib]
crate-type = ["cdylib"]

[dependencies]

# We always need `panic = "abort"`,
# as we don't want panics to cross the C/Rust boundary,
# and we want such panics for these security checks to always be fatal.

[profile.dev]
panic = "abort"

[profile.release]
panic = "abort"
175 changes: 175 additions & 0 deletions runtime/type-registry/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
use std::collections::HashMap;
use std::fmt;
use std::fmt::Debug;
use std::fmt::Display;
use std::fmt::Formatter;
use std::ptr;
use std::sync::LazyLock;
use std::sync::RwLock;

pub type Ptr = *const ();

/// A pointer's address without provenance, used purely as an address for comparisons.
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct PtrAddr(usize);

impl Display for PtrAddr {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{:?}", ptr::without_provenance::<()>(self.0))
}
}

impl Debug for PtrAddr {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", self)
}
}

/// A unique ID for a type.
///
/// This can be anything, as long as it's unique for a type in a program.
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
#[repr(transparent)]
pub struct TypeId(usize);

impl Display for TypeId {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "Type{}", self.0)
}
}

impl Debug for TypeId {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", self)
}
}

static GLOBAL_TYPE_REGISTRY: LazyLock<TypeRegistry> = LazyLock::new(TypeRegistry::default);

/// See [`TypeRegistry::construct`].
#[no_mangle]
pub extern "C-unwind" fn ia2_type_registry_construct(ptr: Ptr, type_id: TypeId) {
GLOBAL_TYPE_REGISTRY.construct(ptr, type_id);
}

/// See [`TypeRegistry::destruct`].
#[no_mangle]
pub extern "C-unwind" fn ia2_type_registry_destruct(ptr: Ptr, expected_type_id: TypeId) {
GLOBAL_TYPE_REGISTRY.destruct(ptr, expected_type_id);
}

/// See [`TypeRegistry::check`].
#[no_mangle]
pub extern "C-unwind" fn ia2_type_registry_check(ptr: Ptr, expected_type_id: TypeId) {
GLOBAL_TYPE_REGISTRY.check(ptr, expected_type_id);
}

#[derive(Default)]
pub struct TypeRegistry {
map: RwLock<HashMap<PtrAddr, TypeId>>,
}

impl TypeRegistry {
/// Called after an object is constructed to register it and its type.
///
/// Panics if `ptr` is already constructed.
#[track_caller]
pub fn construct(&self, ptr: Ptr, type_id: TypeId) {
let ptr = PtrAddr(ptr.addr());
let guard = &mut *self.map.write().unwrap();
let prev_type_id = guard.insert(ptr, type_id);
assert_eq!(prev_type_id, None);
}

/// Called after an object is destructured to unregister it and its type.
///
/// Panics if `ptr` has a different type.
#[track_caller]
pub fn destruct(&self, ptr: Ptr, expected_type_id: TypeId) {
let ptr = PtrAddr(ptr.addr());
let guard = &mut *self.map.write().unwrap();
let type_id = guard.remove(&ptr);
assert_eq!(type_id, Some(expected_type_id));
}

/// Called to check a pointer has the expected type.
///
/// Panics if `ptr` is not registered or has a different type.
#[track_caller]
pub fn check(&self, ptr: Ptr, expected_type_id: TypeId) {
let ptr = PtrAddr(ptr.addr());
let guard = &*self.map.read().unwrap();
let type_id = guard.get(&ptr).copied();
assert_eq!(type_id, Some(expected_type_id));
}
}

impl Drop for TypeRegistry {
fn drop(&mut self) {
match self.map.get_mut() {
Ok(map) => {
if !map.is_empty() {
eprintln!("warning: leak: {map:?}");
}
}
Err(e) => {
eprintln!("poison: {e}")
}
}
}
}

#[cfg(test)]
mod tests {
use super::*;

fn ptr(addr: usize) -> Ptr {
core::ptr::without_provenance(addr)
}

#[test]
fn normal() {
let registry = TypeRegistry::default();
registry.construct(ptr(0xA), TypeId(1));
registry.check(ptr(0xA), TypeId(1));
registry.construct(ptr(0xB), TypeId(2));
registry.check(ptr(0xA), TypeId(1));
registry.check(ptr(0xB), TypeId(2));
registry.destruct(ptr(0xA), TypeId(1));
registry.check(ptr(0xB), TypeId(2));
registry.destruct(ptr(0xB), TypeId(2));
}

#[test]
#[should_panic]
fn destruct_non_existent_ptr() {
let registry = TypeRegistry::default();
registry.destruct(ptr(0xA), TypeId(1));
}

#[test]
#[should_panic]
fn check_non_existent_ptr() {
let registry = TypeRegistry::default();
registry.check(ptr(0xA), TypeId(1));
}

#[test]
#[should_panic]
fn check_freed_ptr() {
let registry = TypeRegistry::default();
registry.construct(ptr(0xA), TypeId(1));
registry.check(ptr(0xA), TypeId(1));
registry.destruct(ptr(0xA), TypeId(1));
registry.check(ptr(0xA), TypeId(1));
}

#[test]
#[should_panic]
fn construct_existing_ptr() {
let registry = TypeRegistry::default();
registry.construct(ptr(0xA), TypeId(1));
registry.check(ptr(0xA), TypeId(1));
registry.construct(ptr(0xA), TypeId(1));
}
}

0 comments on commit 8037e4a

Please sign in to comment.