1use std::{alloc::Layout, collections::HashSet, ffi::CStr, ptr::null_mut, time::Instant};
2
3use dynlink::{
4 compartment::CompartmentId,
5 context::{Context, LoadIds, NewCompartmentFlags},
6 engines::LoadCtx,
7 library::{AllowedGates, LibraryId, UnloadedLibrary},
8 DynlinkError, SMALL_STRING_SIZE, SMALL_VEC_SIZE,
9};
10use happylock::ThreadKey;
11use monitor_api::SharedCompConfig;
12use smallstr::SmallString;
13use talc::{ErrOnOom, Talc};
14use tinyvec::TinyVec;
15use twizzler_rt_abi::{
16 bindings::binding_info,
17 core::{CtorSet, RuntimeInfo},
18 error::{GenericError, TwzError},
19 object::{MapFlags, ObjID},
20};
21
22use super::{
23 CompConfigObject, CompartmentMgr, RunComp, StackObject, COMP_DESTRUCTED, COMP_EXITED,
24 COMP_IS_BINARY, COMP_READY, COMP_STARTED,
25};
26use crate::mon::{
27 get_monitor,
28 space::{MapHandle, Space},
29 thread::DEFAULT_STACK_SIZE,
30 Monitor,
31};
32
33#[derive(Debug)]
35pub struct RunCompLoader {
36 loaded_extras: dynlink::Vec<LoadInfo, SMALL_VEC_SIZE>,
37 root_comp: LoadInfo,
38}
39
40#[derive(Debug, Clone)]
42struct LoadInfo {
43 #[allow(dead_code)]
45 root_id: LibraryId,
46 #[allow(dead_code)]
48 rt_id: LibraryId,
49 sctx_id: ObjID,
51 name: SmallString<[u8; SMALL_STRING_SIZE]>,
52 comp_id: CompartmentId,
53 ctor_info: dynlink::Vec<CtorSet, SMALL_VEC_SIZE>,
55 entry: Option<extern "C" fn(*const RuntimeInfo) -> !>,
57 main_entry: Option<extern "C" fn(*const RuntimeInfo) -> !>,
59 is_binary: bool,
60}
61
62impl Default for LoadInfo {
63 fn default() -> Self {
64 Self {
65 root_id: LibraryId::default(),
66 rt_id: LibraryId::default(),
67 sctx_id: 0.into(),
68 name: "".into(),
69 comp_id: CompartmentId::default(),
70 ctor_info: dynlink::Vec::new(),
71 entry: None,
72 main_entry: None,
73 is_binary: false,
74 }
75 }
76}
77
78impl LoadInfo {
79 fn new(
80 dynlink: &mut Context,
81 root_id: LibraryId,
82 rt_id: LibraryId,
83 sctx_id: ObjID,
84 is_binary: bool,
85 extras: &[LibraryId],
86 ) -> Result<Self, DynlinkError> {
87 let root_lib = dynlink.get_library(root_id)?;
88 let lib = dynlink.get_library(rt_id)?;
89 let extra_ctors: Vec<_> = extras
90 .iter()
91 .map(|extra| dynlink.build_ctors_list::<1>(*extra, Some(lib.compartment()), None))
92 .try_collect()?;
93 let root_ctors = dynlink.build_ctors_list::<1>(root_id, Some(lib.compartment()), None)?;
94 let mut ctor_info: dynlink::Vec<_, SMALL_VEC_SIZE> =
95 extra_ctors.iter().flatten().copied().collect();
96 ctor_info.extend_from_slice(root_ctors.as_slice());
97
98 let main_entry = root_lib.get_entry_address().ok();
99
100 Ok(Self {
101 root_id,
102 rt_id,
103 comp_id: lib.compartment(),
104 sctx_id,
105 name: dynlink
106 .get_compartment(lib.compartment())?
107 .name
108 .as_str()
109 .into(),
110 ctor_info,
111 entry: Some(lib.get_entry_address()?),
112 main_entry,
113 is_binary,
114 })
115 }
116
117 fn build_runcomp(
118 &self,
119 handle: MapHandle,
120 stack_object: StackObject,
121 is_debugging: bool,
122 controller: Option<ObjID>,
123 mut loader_config: monitor_api::CompartmentLoaderConfig,
124 ) -> Result<RunComp, DynlinkError> {
125 let _start = Instant::now();
126 let mut comp_config = CompConfigObject::new(
127 handle,
128 SharedCompConfig::new(self.sctx_id, null_mut(), loader_config),
129 );
130
131 let flags = if self.is_binary { COMP_IS_BINARY } else { 0 };
132
133 let mut alloc = Talc::new(ErrOnOom);
134 unsafe { alloc.claim(comp_config.alloc_span()).unwrap() };
135
136 if !loader_config.fd_spec.is_null() {
137 let fd_spec_layout = Layout::array::<binding_info>(loader_config.fd_spec_len).unwrap();
138 let fd_spec_ptr =
139 unsafe { alloc.malloc(fd_spec_layout).unwrap().cast::<binding_info>() };
140 let fd_spec_slice = unsafe {
141 core::slice::from_raw_parts_mut(fd_spec_ptr.as_ptr(), loader_config.fd_spec_len)
142 };
143 let src_fd_spec_slice = unsafe {
144 core::slice::from_raw_parts(loader_config.fd_spec, loader_config.fd_spec_len)
145 };
146 fd_spec_slice.copy_from_slice(src_fd_spec_slice);
147 loader_config.fd_spec = fd_spec_ptr.as_ptr();
148 comp_config.write_config(SharedCompConfig::new(
149 self.sctx_id,
150 null_mut(),
151 loader_config,
152 ));
153 }
154 tracing::trace!("build_runcomp in {}ms", _start.elapsed().as_millis());
155
156 Ok(RunComp::new(
157 self.sctx_id,
158 self.sctx_id,
159 self.name.to_string(),
160 self.comp_id,
161 vec![],
162 comp_config,
163 flags,
164 stack_object,
165 self.entry.map(|x| x as usize).unwrap_or_default(),
166 self.main_entry.map(|x| x as usize).unwrap_or_default(),
167 &self.ctor_info,
168 is_debugging,
169 controller,
170 alloc,
171 ))
172 }
173}
174
175impl Drop for RunCompLoader {
176 fn drop(&mut self) {
177 tracing::warn!("drop RunCompLoader: TODO");
178 }
179}
180
181const RUNTIME_NAME: &str = "libtwz_rt.so";
182
183impl RunCompLoader {
184 fn maybe_inject_runtime(
187 dynlink: &mut Context,
188 root_id: LibraryId,
189 comp_id: CompartmentId,
190 load_ctx: &mut LoadCtx,
191 ) -> Result<LibraryId, DynlinkError> {
192 if let Some(id) = dynlink.lookup_library(comp_id, RUNTIME_NAME) {
193 return Ok(id);
194 }
195
196 let rt_unlib = UnloadedLibrary::new(RUNTIME_NAME);
197 let loads = dynlink.load_library_in_compartment(
198 comp_id,
199 rt_unlib,
200 AllowedGates::Private,
201 load_ctx,
202 )?;
203 dynlink.add_manual_dependency(root_id, loads[0].lib);
204 Ok(loads[0].lib)
205 }
206
207 pub fn new(
210 dynlink: &mut Context,
211 comp_name: &str,
212 root_unlib: UnloadedLibrary,
213 extras: &[UnloadedLibrary],
214 extras_sctx: &[UnloadedLibrary],
215 new_comp_flags: NewCompartmentFlags,
216 mondebug: bool,
217 ) -> miette::Result<Self> {
218 let _start_1 = Instant::now();
219 struct UnloadOnDrop(TinyVec<[LoadIds; SMALL_VEC_SIZE]>);
220 impl Drop for UnloadOnDrop {
221 fn drop(&mut self) {
222 tracing::warn!("todo: drop");
223 }
224 }
225 let root_comp_id = dynlink.add_compartment(comp_name, new_comp_flags)?;
226 let allowed_gates = if new_comp_flags.contains(NewCompartmentFlags::EXPORT_GATES) {
227 AllowedGates::Public
228 } else {
229 AllowedGates::Private
230 };
231 let mut load_ctx = LoadCtx::default();
232 let _start_2 = Instant::now();
233
234 let mut extra_sctx_load_ids: Vec<_> = extras_sctx
235 .into_iter()
236 .map(|extra| {
237 let comp_id = dynlink
238 .add_compartment(extra.name.clone(), NewCompartmentFlags::EXPORT_GATES)?;
239 if mondebug {
240 tracing::info!(
241 "loading sctx preload library: {} -> {}",
242 extra.name,
243 comp_id
244 );
245 } else {
246 tracing::debug!(
247 "loading sctx preload library: {} -> {}",
248 extra.name,
249 comp_id
250 );
251 }
252 dynlink.load_library_in_compartment(
253 comp_id,
254 extra.clone(),
255 AllowedGates::Public,
256 &mut load_ctx,
257 )
258 })
259 .try_collect()?;
260
261 let mut extra_load_ids: Vec<_> = extras
262 .into_iter()
263 .map(|extra| {
264 if mondebug {
265 tracing::info!("loading ld preload library: {}", extra.name);
266 } else {
267 tracing::debug!("loading ld preload library: {}", extra.name);
268 }
269 dynlink.load_library_in_compartment(
270 root_comp_id,
271 extra.clone(),
272 AllowedGates::Private,
273 &mut load_ctx,
274 )
275 })
276 .try_collect()?;
277
278 let mut loads = UnloadOnDrop(dynlink.load_library_in_compartment(
279 root_comp_id,
280 root_unlib.clone(),
281 allowed_gates,
282 &mut load_ctx,
283 )?);
284
285 extra_load_ids.append(&mut extra_sctx_load_ids);
286
287 for extra in &extra_load_ids {
288 for extra in extra {
289 loads.0.push(extra.clone());
290 }
291 }
292
293 let mut cache = HashSet::new();
297 let extra_compartments = loads.0.iter().filter_map(|load| {
298 tracing::debug!(
299 "extra? {} {} {}",
300 load.comp,
301 root_comp_id,
302 cache.contains(&load.comp)
303 );
304 if load.comp != root_comp_id {
305 if cache.contains(&load.comp) {
308 return None;
309 }
310 cache.insert(load.comp);
311
312 let rt_id =
314 match Self::maybe_inject_runtime(dynlink, load.lib, load.comp, &mut load_ctx) {
315 Ok(id) => id,
316 Err(e) => return Some(Err(e)),
317 };
318 Some(LoadInfo::new(
319 dynlink,
320 load.lib,
321 rt_id,
322 *load_ctx.set.get(&load.comp).unwrap(),
323 false,
324 &[],
325 ))
326 } else {
327 None
328 }
329 });
330
331 let extra_compartments = DynlinkError::collect(
332 dynlink::DynlinkErrorKind::CompartmentLoadFail {
333 compartment: comp_name.into(),
334 },
335 extra_compartments,
336 )?;
337 tracing::trace!("extras: {:?}", extra_compartments);
338
339 let root_id = loads.0[0].lib;
340 let rt_id = Self::maybe_inject_runtime(dynlink, root_id, root_comp_id, &mut load_ctx)?;
341 let extra_lids = extra_load_ids
342 .iter()
343 .flatten()
344 .map(|x| x.lib)
345 .collect::<Vec<_>>();
346 let _start_3 = Instant::now();
347 for extra in &extra_lids {
348 dynlink.relocate_all(*extra)?;
349 }
350 dynlink.relocate_all(root_id)?;
351
352 let is_binary = dynlink.get_library(root_id)?.is_binary();
353 let root_comp = LoadInfo::new(
354 dynlink,
355 root_id,
356 rt_id,
357 *load_ctx.set.get(&root_comp_id).unwrap(),
358 is_binary,
359 extra_lids.as_slice(),
360 )?;
361
362 if mondebug {
363 let print_comp = |cmp: &LoadInfo| -> miette::Result<()> {
364 let dcmp = dynlink.get_compartment(cmp.comp_id)?;
365 tracing::info!("Loaded libraries for {}:", &dcmp.name);
366 for lid in dcmp.library_ids() {
367 let lib = dynlink.get_library(lid)?;
368 let mut flags = ["-", "-", "-"];
369 if lib.is_binary() {
370 flags[0] = "B";
371 } else {
372 flags[0] = "l";
373 }
374 if lib.id() == cmp.rt_id {
375 flags[1] = "r";
376 } else if lib.id() == cmp.root_id {
377 flags[1] = "R";
378 }
379 if lib.allows_gates() {
380 flags[2] = "g";
381 }
382 let flags = flags.join("");
383 tracing::info!("{:16x} {} {}", lib.base_addr(), flags, &lib.name);
384 if let Some(isg) = lib.iter_secgates() {
385 for gate in isg {
386 tracing::info!(
387 " GATE {:16x} {}",
388 gate.imp,
389 gate.name().to_string_lossy()
390 )
391 }
392 }
393 }
394 Ok(())
395 };
396 tracing::info!("Load info for {}", comp_name);
397 let _ = print_comp(&root_comp);
398 for cmp in &extra_compartments {
399 let _ = print_comp(cmp);
400 }
401 }
402
403 std::mem::forget(loads);
405
406 tracing::trace!(
407 "prepped in {}ms, loaded in {}ms, relocated in {}ms",
408 (_start_2 - _start_1).as_millis(),
409 (_start_3 - _start_2).as_millis(),
410 _start_3.elapsed().as_millis()
411 );
412
413 Ok(RunCompLoader {
414 loaded_extras: extra_compartments,
415 root_comp,
416 })
417 }
418
419 pub fn build_rcs(
420 self,
421 cmp: &mut CompartmentMgr,
422 dynlink: &mut Context,
423 mondebug: bool,
424 is_debugging: bool,
425 controller: Option<ObjID>,
426 loader_config: monitor_api::CompartmentLoaderConfig,
427 ) -> miette::Result<ObjID> {
428 let make_new_handle = |ty, id| {
429 if mondebug {
430 tracing::info!(
431 "creating runtime {} object {} for compartment {}",
432 ty,
433 id,
434 self.root_comp.name
435 );
436 }
437 Space::safe_create_and_map_runtime_object(
438 &get_monitor().space,
439 id,
440 MapFlags::READ | MapFlags::WRITE,
441 )
442 };
443 let stack = StackObject::new(
444 make_new_handle("stack", self.root_comp.sctx_id)?,
445 DEFAULT_STACK_SIZE,
446 )?;
447
448 let mut root_rc = self.root_comp.build_runcomp(
449 make_new_handle("comp-config", self.root_comp.sctx_id)?,
450 stack,
451 is_debugging,
452 controller,
453 loader_config,
454 )?;
455 tracing::trace!("starting {} as {}", self.root_comp.name, root_rc.instance);
456
457 let mut ids = vec![root_rc.instance];
458 let handles = self
460 .loaded_extras
461 .iter()
462 .map(|extra| {
463 let stack =
464 StackObject::new(make_new_handle("stack", extra.sctx_id)?, DEFAULT_STACK_SIZE)?;
465 Ok::<_, miette::Report>((make_new_handle("comp-config", extra.sctx_id)?, stack))
466 })
467 .try_collect::<Vec<_>>()?;
468 let mut extras = self
470 .loaded_extras
471 .iter()
472 .zip(handles)
473 .map(|extra| {
474 extra
475 .0
476 .build_runcomp(extra.1 .0, extra.1 .1, false, controller, loader_config)
477 })
478 .try_collect::<Vec<_>>()?;
479
480 for rc in extras.drain(..) {
481 ids.push(rc.instance);
482 root_rc.deps.push(rc.instance);
483 cmp.insert(rc);
484 }
485 cmp.insert(root_rc);
486 std::mem::forget(self);
487
488 for id in &ids {
490 let Ok(comp) = cmp.get(*id) else { continue };
491 let mut deps = dynlink
492 .compartment_dependencies(comp.compartment_id)?
493 .iter()
494 .filter_map(|item| cmp.get_dynlinkid(*item).map(|rc| rc.instance).ok())
495 .collect();
496 cmp.get_mut(*id).unwrap().deps.append(&mut deps);
497
498 let Ok(comp) = cmp.get(*id) else { continue };
499 tracing::debug!("set comp {} deps to {:?}", comp.name, comp.deps);
500 }
501 Self::rec_inc_all_use_counts(cmp, ids[0], &HashSet::from_iter(ids.iter().cloned()));
502
503 Ok(ids[0])
504 }
505
506 fn rec_inc_all_use_counts(
507 cmgr: &mut CompartmentMgr,
508 start: ObjID,
509 created: &HashSet<ObjID>,
510 ) -> Option<()> {
511 debug_assert!(created.contains(&start));
512 let rc = cmgr.get(start).ok()?;
513 for dep in rc.deps.clone() {
514 if created.contains(&dep) {
515 Self::rec_inc_all_use_counts(cmgr, dep, created);
516 }
517 if let Ok(rc) = cmgr.get_mut(dep) {
518 rc.inc_use_count();
519 }
520 }
521
522 Some(())
523 }
524}
525
526impl Monitor {
527 pub(crate) fn start_compartment(
528 &self,
529 instance: ObjID,
530 args: &[&CStr],
531 env: &[&CStr],
532 mondebug: bool,
533 suspend_on_start: bool,
534 ) -> Result<(), TwzError> {
535 let deps = {
536 let cmp = self.comp_mgr.read(ThreadKey::get().unwrap());
537 let rc = cmp.get(instance)?;
538
539 if mondebug {
540 tracing::info!(
541 "start compartment {}: {:?} {:?} flags = {:x}",
542 instance,
543 args,
544 env,
545 rc.raw_flags()
546 );
547 }
548
549 tracing::debug!(
550 "starting compartment {} ({}) (binary = {})",
551 rc.name,
552 rc.instance,
553 rc.has_flag(COMP_IS_BINARY)
554 );
555 rc.deps.clone()
556 };
557 for dep in deps {
558 self.start_compartment(dep, &[], env, false, false)?;
559 }
560 let state = self.load_compartment_flags(instance);
562 if state & COMP_EXITED != 0 || state & COMP_DESTRUCTED != 0 {
563 tracing::error!(
564 "tried to start compartment ({:?}, {}) that has already exited (state: {:x})",
565 self.comp_name(instance),
566 instance,
567 state
568 );
569 return Err(GenericError::Internal.into());
570 }
571
572 let _loop_start = Instant::now();
573 loop {
574 let state = self.load_compartment_flags(instance);
576 if state & COMP_READY != 0 {
577 tracing::trace!(
578 "started main detected ready in {}ms",
579 _loop_start.elapsed().as_millis()
580 );
581 return Ok(());
582 }
583 if suspend_on_start {
584 if state & COMP_STARTED != 0 {
586 tracing::trace!(
587 "started main detected started in {}ms",
588 _loop_start.elapsed().as_millis()
589 );
590 return Ok(());
591 }
592 }
593 let info = {
594 let (ref mut tmgr, ref mut cmp, ref mut dynlink, _, _) =
595 *self.locks.lock(ThreadKey::get().unwrap());
596 let rc = cmp.get_mut(instance)?;
597
598 let _start = Instant::now();
599 let r = rc.start_main_thread(
600 state,
601 &mut *tmgr,
602 &mut *dynlink,
603 args,
604 env,
605 suspend_on_start,
606 );
607 tracing::trace!("start_main_thread in {}ms", _start.elapsed().as_millis());
608
609 r
610 };
611 if info.is_none() {
612 return Err(GenericError::Internal.into());
613 }
614 self.wait_for_compartment_state_change(instance, state);
615 }
616 }
617}