Skip to main content

hyperlight_host/mem/
ptr.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2025 The Hyperlight Authors.
3
4use std::ops::Add;
5
6use tracing::{Span, instrument};
7
8use super::ptr_addr_space::{AddressSpace, GuestAddressSpace};
9use super::ptr_offset::Offset;
10use crate::Result;
11use crate::error::HyperlightError::{self, CheckedAddOverflow, RawPointerLessThanBaseAddress};
12
13/// A representation of a raw pointer inside a given address space.
14///
15/// Use this type to distinguish between an offset and a raw pointer
16#[derive(Debug, Clone, Eq, PartialEq)]
17pub struct RawPtr(u64);
18
19impl From<u64> for RawPtr {
20    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
21    fn from(val: u64) -> Self {
22        Self(val)
23    }
24}
25
26impl Add<Offset> for RawPtr {
27    type Output = RawPtr;
28    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
29    fn add(self, rhs: Offset) -> RawPtr {
30        let val = self.0 + u64::from(rhs);
31        RawPtr(val)
32    }
33}
34
35impl TryFrom<usize> for RawPtr {
36    type Error = HyperlightError;
37    #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
38    fn try_from(val: usize) -> Result<Self> {
39        let val_u64 = u64::try_from(val)?;
40        Ok(Self::from(val_u64))
41    }
42}
43
44impl TryFrom<RawPtr> for usize {
45    type Error = HyperlightError;
46    #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
47    fn try_from(val: RawPtr) -> Result<usize> {
48        Ok(usize::try_from(val.0)?)
49    }
50}
51
52impl From<RawPtr> for u64 {
53    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
54    fn from(val: RawPtr) -> u64 {
55        val.0
56    }
57}
58
59impl From<&RawPtr> for u64 {
60    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
61    fn from(val: &RawPtr) -> u64 {
62        val.0
63    }
64}
65
66/// Convenience type for representing a pointer into the guest address space
67pub(crate) type GuestPtr = Ptr<GuestAddressSpace>;
68
69impl TryFrom<RawPtr> for GuestPtr {
70    type Error = HyperlightError;
71    /// Create a new `GuestPtr` from the given `guest_raw_ptr`, which must
72    /// be a pointer in the guest's address space.
73    #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
74    fn try_from(raw: RawPtr) -> Result<Self> {
75        GuestPtr::from_raw_ptr(GuestAddressSpace::new()?, raw)
76    }
77}
78
79impl TryFrom<Offset> for GuestPtr {
80    type Error = HyperlightError;
81    #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
82    fn try_from(val: Offset) -> Result<Self> {
83        let addr_space = GuestAddressSpace::new()?;
84        Ok(Ptr::from_offset(addr_space, val))
85    }
86}
87
88impl TryFrom<i64> for GuestPtr {
89    type Error = HyperlightError;
90    #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
91    fn try_from(val: i64) -> Result<Self> {
92        let offset = Offset::try_from(val)?;
93        GuestPtr::try_from(offset)
94    }
95}
96
97impl TryFrom<GuestPtr> for i64 {
98    type Error = HyperlightError;
99    #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
100    fn try_from(val: GuestPtr) -> Result<Self> {
101        let offset = val.offset();
102        i64::try_from(offset)
103    }
104}
105
106/// A pointer into a specific `AddressSpace` `T`.
107#[derive(Debug, Copy, Clone)]
108pub(crate) struct Ptr<T: AddressSpace> {
109    addr_space: T,
110    offset: Offset,
111}
112
113impl<T: AddressSpace> std::cmp::PartialEq for Ptr<T> {
114    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
115    fn eq(&self, other: &Self) -> bool {
116        other.addr_space == self.addr_space && other.offset == self.offset
117    }
118}
119
120impl<T: AddressSpace> std::cmp::Eq for Ptr<T> {}
121#[instrument(skip_all, parent = Span::current(), level= "Trace")]
122fn cmp_helper<T: AddressSpace>(left: &Ptr<T>, right: &Ptr<T>) -> std::cmp::Ordering {
123    // We know both left and right have the same address space, thus
124    // they have the same base, so we can get away with just comparing
125    // the offsets and assume we're in the same address space, practically
126    // speaking.
127    left.offset.cmp(&right.offset)
128}
129
130#[allow(clippy::non_canonical_partial_ord_impl)]
131impl<T: AddressSpace> std::cmp::PartialOrd for Ptr<T> {
132    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
133    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
134        Some(cmp_helper(self, other))
135    }
136}
137
138impl<T: AddressSpace> std::cmp::Ord for Ptr<T> {
139    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
140    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
141        cmp_helper(self, other)
142    }
143}
144
145impl<T: AddressSpace> Ptr<T> {
146    /// Create a new pointer in the given `AddressSpace` `addr_space`
147    /// from the given pointer `raw_ptr`. Returns `Ok` if subtracting
148    /// the base address from `raw_ptr` succeeds (i.e. does not overflow)
149    /// and a `Ptr<T>` can be successfully created
150    #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
151    fn from_raw_ptr(addr_space: T, raw_ptr: RawPtr) -> Result<Ptr<T>> {
152        let offset = raw_ptr
153            .0
154            .checked_sub(addr_space.base())
155            .ok_or_else(|| RawPointerLessThanBaseAddress(raw_ptr, addr_space.base()))?;
156        Ok(Self {
157            addr_space,
158            offset: Offset::from(offset),
159        })
160    }
161
162    /// Create a new `Ptr` into the given `addr_space` from the given
163    /// `offset`.
164    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
165    fn from_offset(addr_space: T, offset: Offset) -> Ptr<T> {
166        Self { addr_space, offset }
167    }
168
169    /// Get the base address for this pointer
170    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
171    fn base(&self) -> u64 {
172        self.addr_space.base()
173    }
174
175    /// Get the offset into the pointer's address space
176    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
177    pub(super) fn offset(&self) -> Offset {
178        self.offset
179    }
180
181    /// Get the absolute value for the pointer represented by `self`.
182    ///
183    /// This function should rarely be used. Prefer to use offsets
184    /// instead.
185    #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
186    pub(crate) fn absolute(&self) -> Result<u64> {
187        let offset_u64: u64 = self.offset.into();
188        self.base()
189            .checked_add(offset_u64)
190            .ok_or_else(|| CheckedAddOverflow(self.base(), offset_u64))
191    }
192}
193
194impl<T: AddressSpace> Add<Offset> for Ptr<T> {
195    type Output = Ptr<T>;
196    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
197    fn add(self, rhs: Offset) -> Self::Output {
198        Self {
199            addr_space: self.addr_space,
200            offset: self.offset + rhs,
201        }
202    }
203}
204
205impl<T: AddressSpace> TryFrom<Ptr<T>> for usize {
206    type Error = HyperlightError;
207    #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
208    fn try_from(val: Ptr<T>) -> Result<usize> {
209        let abs = val.absolute()?;
210        Ok(usize::try_from(abs)?)
211    }
212}
213
214#[cfg(test)]
215mod tests {
216    use super::{GuestPtr, RawPtr};
217    use crate::mem::layout::SandboxMemoryLayout;
218    const OFFSET: u64 = 1;
219
220    #[test]
221    fn ptr_basic_ops() {
222        {
223            let raw_guest_ptr = RawPtr(OFFSET + SandboxMemoryLayout::BASE_ADDRESS as u64);
224            let guest_ptr = GuestPtr::try_from(raw_guest_ptr).unwrap();
225            assert_eq!(
226                OFFSET + SandboxMemoryLayout::BASE_ADDRESS as u64,
227                guest_ptr.absolute().unwrap()
228            );
229        }
230    }
231}