Skip to content

Commit 8833d8f

Browse files
committed
[common,guest] moved outb functionality to hyperlight_common and added a DebugPrint OutBAction
- the mechanism for causing VM exits and its abstractions over host functions should be exposed in the common library so they can be used externally. - 'PrintOutput' should not be a special host function. Instead, we should provide functionality for debug prints—i.e., the DebugPrint OutBAction. Signed-off-by: danbugs <[email protected]>
1 parent ed6a42f commit 8833d8f

File tree

15 files changed

+200
-411
lines changed

15 files changed

+200
-411
lines changed
+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
use crate::flatbuffer_wrappers::function_types::{ParameterValue, ReturnType, ReturnValue};
2+
use crate::input_output::{InputDataSection, OutputDataSection};
3+
use anyhow::Result;
4+
use crate::flatbuffer_wrappers::function_call::{FunctionCall, FunctionCallType};
5+
use crate::outb::{outb, OutBAction};
6+
use crate::PEB;
7+
8+
use alloc::vec::Vec;
9+
use alloc::string::ToString;
10+
use core::ffi::c_char;
11+
12+
/// Get a return value from a host function call.
13+
/// This usually requires a host function to be called first using `call_host_function`.
14+
pub fn get_host_return_value<T: TryFrom<ReturnValue>>() -> Result<T> {
15+
let input_data_section: InputDataSection =
16+
unsafe { (*PEB).clone() }.get_input_data_region().into();
17+
let return_value = input_data_section
18+
.try_pop_shared_input_data_into::<ReturnValue>()
19+
.expect("Unable to deserialize a return value from host");
20+
21+
T::try_from(return_value).map_err(|_| {
22+
anyhow::anyhow!("Host return value was not a {} as expected", core::any::type_name::<T>())
23+
})
24+
}
25+
26+
/// Calls a host function.
27+
// TODO: Make this generic, return a Result<T, ErrorCode> this should allow callers to call this function and get the result type they expect
28+
// without having to do the conversion themselves
29+
pub fn call_host_function(
30+
function_name: &str,
31+
parameters: Option<Vec<ParameterValue>>,
32+
return_type: ReturnType,
33+
) -> Result<()> {
34+
let host_function_call = FunctionCall::new(
35+
function_name.to_string(),
36+
parameters,
37+
FunctionCallType::Host,
38+
return_type,
39+
);
40+
41+
let host_function_call_buffer: Vec<u8> = host_function_call
42+
.try_into()
43+
.expect("Unable to serialize host function call");
44+
45+
let output_data_section: OutputDataSection =
46+
unsafe { (*PEB).clone() }.get_output_data_region().into();
47+
output_data_section
48+
.push_shared_output_data(host_function_call_buffer)?;
49+
50+
outb(OutBAction::CallFunction as u16, 0);
51+
52+
Ok(())
53+
}
54+
55+
/// Uses `hloutb` to issue multiple `DebugPrint` `OutBAction`s to print a message.
56+
pub fn print(message: &str) {
57+
for byte in message.bytes() {
58+
outb(OutBAction::DebugPrint as u16, byte);
59+
}
60+
}
61+
62+
/// Exposes a C API to allow the guest to print a string, byte by byte
63+
///
64+
/// # Safety
65+
/// This function is not thread safe and assumes `outb` is safe to call directly.
66+
#[no_mangle]
67+
pub unsafe extern "C" fn _putchar(c: c_char) {
68+
outb(OutBAction::DebugPrint as u16, c as u8);
69+
}

src/hyperlight_common/src/input_output.rs

+9-28
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,9 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1313
See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
16-
use alloc::boxed::Box;
17-
use alloc::format;
18-
use alloc::string::ToString;
16+
use anyhow::{bail, Result};
1917
use alloc::vec::Vec;
2018
use core::any::type_name;
21-
use core::error::Error;
2219
use core::slice::from_raw_parts_mut;
2320

2421
pub struct InputDataSection {
@@ -31,16 +28,14 @@ impl InputDataSection {
3128
InputDataSection { ptr, len }
3229
}
3330

34-
pub fn try_pop_shared_input_data_into<T>(&self) -> Result<T, Box<dyn Error>>
31+
pub fn try_pop_shared_input_data_into<T>(&self) -> Result<T>
3532
where
3633
T: for<'a> TryFrom<&'a [u8]>,
3734
{
3835
let input_data_buffer = unsafe { from_raw_parts_mut(self.ptr, self.len) };
3936

4037
if input_data_buffer.is_empty() {
41-
return Err("Got a 0-size buffer in pop_shared_input_data_into"
42-
.to_string()
43-
.into());
38+
bail!("Got a 0-size buffer in pop_shared_input_data_into");
4439
}
4540

4641
// get relative offset to next free address
@@ -51,11 +46,7 @@ impl InputDataSection {
5146
);
5247

5348
if stack_ptr_rel > self.len || stack_ptr_rel < 16 {
54-
return Err(format!(
55-
"Invalid stack pointer: {} in pop_shared_input_data_into",
56-
stack_ptr_rel
57-
)
58-
.into());
49+
bail!("Invalid stack pointer: {} in pop_shared_input_data_into", stack_ptr_rel);
5950
}
6051

6152
// go back 8 bytes and read. This is the offset to the element on top of stack
@@ -71,7 +62,7 @@ impl InputDataSection {
7162
let type_t = match T::try_from(buffer) {
7263
Ok(t) => Ok(t),
7364
Err(_e) => {
74-
return Err(format!("Unable to convert buffer to {}", type_name::<T>()).into());
65+
bail!("Unable to convert buffer to {}", type_name::<T>());
7566
}
7667
};
7768

@@ -95,13 +86,11 @@ impl OutputDataSection {
9586
OutputDataSection { ptr, len }
9687
}
9788

98-
pub fn push_shared_output_data(&self, data: Vec<u8>) -> Result<(), Box<dyn Error>> {
89+
pub fn push_shared_output_data(&self, data: Vec<u8>) -> Result<()> {
9990
let output_data_buffer = unsafe { from_raw_parts_mut(self.ptr, self.len) };
10091

10192
if output_data_buffer.is_empty() {
102-
return Err("Got a 0-size buffer in push_shared_output_data"
103-
.to_string()
104-
.into());
93+
bail!("Got a 0-size buffer in push_shared_output_data");
10594
}
10695

10796
// get offset to next free address on the stack
@@ -119,22 +108,14 @@ impl OutputDataSection {
119108
// It can be equal to the size, but never greater
120109
// It can never be less than 8. An empty buffer's stack pointer is 8
121110
if stack_ptr_rel > self.len || stack_ptr_rel < 8 {
122-
return Err(format!(
123-
"Invalid stack pointer: {} in push_shared_output_data",
124-
stack_ptr_rel
125-
)
126-
.into());
111+
bail!("Invalid stack pointer: {} in push_shared_output_data", stack_ptr_rel);
127112
}
128113

129114
// check if there is enough space in the buffer
130115
let size_required = data.len() + 8; // the data plus the pointer pointing to the data
131116
let size_available = self.len - stack_ptr_rel;
132117
if size_required > size_available {
133-
return Err(format!(
134-
"Not enough space in shared output buffer. Required: {}, Available: {}",
135-
size_required, size_available
136-
)
137-
.into());
118+
bail!("Not enough space in shared output buffer. Required: {}, Available: {}", size_required, size_available);
138119
}
139120

140121
// write the actual data

src/hyperlight_common/src/lib.rs

+19
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ limitations under the License.
2323
pub const PAGE_SIZE: usize = 0x1_000; // 4KB
2424

2525
extern crate alloc;
26+
extern crate core;
27+
28+
use crate::hyperlight_peb::{HyperlightPEB, RunMode};
2629

2730
pub mod flatbuffer_wrappers;
2831
/// cbindgen:ignore
@@ -43,6 +46,14 @@ mod flatbuffers;
4346
/// be able to communicate via function calls.
4447
pub mod hyperlight_peb;
4548

49+
/// We keep track of the PEB address in a global variable that references a region of
50+
/// shared memory.
51+
pub static mut PEB: *mut HyperlightPEB = core::ptr::null_mut();
52+
53+
/// Hyperlight supports running in both hypervisor mode and in process mode. We keep track of that
54+
/// state in this global variable.
55+
pub static mut RUNNING_MODE: RunMode = RunMode::None;
56+
4657
/// Hyperlight operates with a host-guest execution model.
4758
///
4859
/// The host is who creates the hypervisor partition, and the guest is whatever runs
@@ -53,3 +64,11 @@ pub mod hyperlight_peb;
5364
/// push data to the output section. On the other hand, the host can push data to the
5465
/// input section and pop data from the output section.
5566
pub mod input_output;
67+
68+
/// `outb` is the mechanism that Hyperlight uses to cause a VM exit to execute host functions and
69+
/// similar functionality.
70+
pub mod outb;
71+
72+
/// Hyperlight provides abstractions for performing a VM exits with the intent of calling
73+
/// functionality in the host.
74+
pub mod host_calling;

src/hyperlight_common/src/outb.rs

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
use core::arch;
2+
use anyhow::{bail, Result};
3+
use crate::hyperlight_peb::RunMode;
4+
use crate::RUNNING_MODE;
5+
6+
/// Supported actions when issuing an OUTB actions by Hyperlight.
7+
/// - Log: for logging,
8+
/// - CallFunction: makes a call to a host function,
9+
/// - Abort: aborts the execution of the guest,
10+
/// - DebugPrint: prints a message to the host console.
11+
pub enum OutBAction {
12+
Log = 99,
13+
CallFunction = 101,
14+
Abort = 102,
15+
DebugPrint = 103,
16+
}
17+
18+
impl TryFrom<u16> for OutBAction {
19+
type Error = anyhow::Error;
20+
fn try_from(val: u16) -> Result<Self> {
21+
match val {
22+
99 => Ok(OutBAction::Log),
23+
101 => Ok(OutBAction::CallFunction),
24+
102 => Ok(OutBAction::Abort),
25+
103 => Ok(OutBAction::DebugPrint),
26+
_ => bail!("Invalid OutB value: {}", val),
27+
}
28+
}
29+
}
30+
31+
/// Issues an OUTB instruction to the specified port with the given value.
32+
fn hloutb(port: u16, val: u8) {
33+
unsafe { arch::asm!("out dx, al", in("dx") port, in("al") val, options(preserves_flags, nomem, nostack)); }
34+
}
35+
36+
pub fn outb(port: u16, value: u8) {
37+
unsafe {
38+
match RUNNING_MODE {
39+
RunMode::Hypervisor => {
40+
hloutb(port, value);
41+
}
42+
RunMode::InProcessLinux | RunMode::InProcessWindows => {
43+
// TODO(danbugs:297): bring back
44+
// if let Some(outb_func) = OUTB_PTR_WITH_CONTEXT {
45+
// if let Some(peb_ptr) = PEB {
46+
// outb_func((*peb_ptr).pOutbContext, port, value);
47+
// }
48+
// } else if let Some(outb_func) = OUTB_PTR {
49+
// outb_func(port, value);
50+
// } else {
51+
// panic!("Tried to call outb without hypervisor and without outb function ptrs");
52+
// }
53+
}
54+
_ => {
55+
panic!("Tried to call outb in invalid runmode");
56+
}
57+
}
58+
59+
// TODO(danbugs:297): bring back
60+
// check_for_host_error();
61+
}
62+
}

src/hyperlight_guest/src/chkstk.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,10 @@ use core::arch::global_asm;
1818
use core::mem::size_of;
1919

2020
use hyperlight_common::hyperlight_peb::RunMode;
21+
use hyperlight_common::RUNNING_MODE;
2122

2223
use crate::guest_error::{set_invalid_runmode_error, set_stack_allocate_error};
23-
use crate::{MIN_STACK_ADDRESS, RUNNING_MODE};
24+
use crate::MIN_STACK_ADDRESS;
2425

2526
extern "win64" {
2627
fn __chkstk();

src/hyperlight_guest/src/entrypoint.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,14 @@ use core::ptr::copy_nonoverlapping;
2020
use hyperlight_common::hyperlight_peb::{HyperlightPEB, RunMode};
2121
use log::LevelFilter;
2222
use spin::Once;
23-
23+
use hyperlight_common::outb::{outb, OutBAction};
24+
use hyperlight_common::{PEB, RUNNING_MODE};
2425
use crate::gdt::load_gdt;
2526
use crate::guest_error::reset_error;
2627
use crate::guest_function_call::dispatch_function;
2728
use crate::guest_logger::init_logger;
28-
use crate::host_function_call::{outb, OutBAction};
2929
use crate::idtr::load_idt;
30-
use crate::{__security_cookie, HEAP_ALLOCATOR, MIN_STACK_ADDRESS, PEB, RUNNING_MODE};
30+
use crate::{__security_cookie, HEAP_ALLOCATOR, MIN_STACK_ADDRESS};
3131

3232
#[inline(never)]
3333
pub fn halt() {

src/hyperlight_guest/src/guest_error.rs

+2-3
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,9 @@ use core::ffi::{c_char, CStr};
2020

2121
use hyperlight_common::flatbuffer_wrappers::guest_error::{ErrorCode, GuestError};
2222
use log::error;
23-
23+
use hyperlight_common::outb::{outb, OutBAction};
24+
use hyperlight_common::PEB;
2425
use crate::entrypoint::halt;
25-
use crate::host_function_call::{outb, OutBAction};
26-
use crate::PEB;
2726

2827
pub(crate) fn write_error(error_code: ErrorCode, message: Option<&str>) {
2928
let peb = unsafe { (*PEB).clone() };

0 commit comments

Comments
 (0)