dynlink/engines/
twizzler.rs

1use itertools::{Either, Itertools};
2use twizzler_abi::{
3    object::{ObjID, Protections, MAX_SIZE, NULLPAGE_SIZE},
4    syscall::{
5        sys_object_create, BackingType, CreateTieFlags, CreateTieSpec, LifetimeType, ObjectCreate,
6        ObjectCreateFlags, ObjectSource,
7    },
8};
9
10use super::{Backing, LoadDirective, LoadFlags};
11use crate::{DynlinkError, DynlinkErrorKind};
12
13pub struct Engine;
14
15fn within_object(slot: usize, addr: usize) -> bool {
16    addr >= slot * MAX_SIZE + NULLPAGE_SIZE && addr < (slot + 1) * MAX_SIZE - NULLPAGE_SIZE * 2
17}
18
19/// Load segments according to Twizzler requirements. Helper function for implementing a
20/// ContextEngine.
21pub fn load_segments(
22    src: &Backing,
23    ld: &[LoadDirective],
24    instance: ObjID,
25    map: impl FnOnce(ObjID, ObjID) -> Result<(Backing, Backing), DynlinkError>,
26) -> Result<Vec<Backing>, DynlinkError> {
27    let create_spec = ObjectCreate::new(
28        BackingType::Normal,
29        LifetimeType::Volatile,
30        None,
31        ObjectCreateFlags::DELETE,
32        Protections::all(),
33    );
34
35    let build_copy_cmd = |directive: &LoadDirective| {
36        if !within_object(
37            if directive.load_flags.contains(LoadFlags::TARGETS_DATA) {
38                1
39            } else {
40                0
41            },
42            directive.vaddr,
43        ) || directive.memsz > MAX_SIZE - NULLPAGE_SIZE * 2
44            || directive.offset > MAX_SIZE - NULLPAGE_SIZE * 2
45            || directive.filesz > directive.memsz
46        {
47            return Err(DynlinkError::new(DynlinkErrorKind::LoadDirectiveFail {
48                dir: *directive,
49            }));
50        }
51
52        // NOTE: Data that needs to be initialized to zero is not handled
53        // (filesz < memsz). The reason things work now is because
54        // the frame allocator in the kernel hands out zeroed pages by default.
55        // If this behaviour changes, we will need to explicitly handle it here.
56        if directive.filesz != directive.memsz {
57            if directive.filesz < directive.memsz {
58                // tracing::warn!(
59                //     "{} bytes after source implicitly zeroed",
60                //     directive.memsz - directive.filesz
61                // );
62            } else {
63                todo!()
64            }
65        }
66
67        // the offset from the base of the object with the ELF executable data
68        let src_start = NULLPAGE_SIZE + directive.offset;
69        // the destination offset is the virtual address we want this data
70        // to be mapped into. since the different sections are seperated
71        // by object boundaries, we keep the object-relative offset
72        // we trust the destination offset to be after the NULL_PAGE
73        let dest_start = directive.vaddr as usize % MAX_SIZE;
74        // the size of the data that must be copied from the ELF
75        let len = directive.filesz;
76
77        if !directive.load_flags.contains(LoadFlags::TARGETS_DATA) {
78            // Ensure we can direct-map the object for the text directives.
79            //
80            // The logic for direct mapping between x86_64 and aarch64 is different
81            // because the linker/compiler sets the page size to be 64K on aarch64.
82            // So we only check if we can direct map for x86_64. The source and
83            // destination offsets (for aarch64) would match up if a 64K page size
84            // for the NULLPAGE was used or we modified the destination address to
85            // be after the NULLPAGE. Loading still works on aarch64, but copies data.
86            #[cfg(target_arch = "x86_64")]
87            if src_start != dest_start {
88                // TODO: check len too.
89                return Err(DynlinkError::new(DynlinkErrorKind::LoadDirectiveFail {
90                    dir: *directive,
91                }));
92            }
93        }
94
95        Ok(ObjectSource::new_copy(
96            src.id(),
97            src_start as u64,
98            dest_start as u64,
99            len,
100        ))
101    };
102
103    let ld = ld.to_vec();
104    let (data_cmds, text_cmds): (Vec<_>, Vec<_>) = ld.into_iter().partition_map(|directive| {
105        if directive.load_flags.contains(LoadFlags::TARGETS_DATA) {
106            Either::Left(build_copy_cmd(&directive))
107        } else {
108            Either::Right(build_copy_cmd(&directive))
109        }
110    });
111
112    let data_cmds = DynlinkError::collect(DynlinkErrorKind::NewBackingFail, data_cmds)?;
113    let text_cmds = DynlinkError::collect(DynlinkErrorKind::NewBackingFail, text_cmds)?;
114
115    let data_id = sys_object_create(
116        create_spec,
117        &data_cmds,
118        &[CreateTieSpec::new(instance, CreateTieFlags::empty())],
119    )
120    .map_err(|_| DynlinkErrorKind::NewBackingFail)?;
121
122    let text_id = sys_object_create(
123        create_spec,
124        &text_cmds,
125        &[CreateTieSpec::new(instance, CreateTieFlags::empty())],
126    )
127    .map_err(|_| DynlinkErrorKind::NewBackingFail)?;
128
129    tracing::trace!(
130        "mapped segments in instance {} to {}, {}",
131        instance,
132        text_id,
133        data_id
134    );
135
136    let (text, data) = map(text_id, data_id)?;
137    Ok(vec![text, data])
138}