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            tracing::error!("invalid directives: {:?}", directive);
48            return Err(DynlinkError::new(DynlinkErrorKind::LoadDirectiveFail {
49                dir: *directive,
50            }));
51        }
52
53        // NOTE: Data that needs to be initialized to zero is not handled
54        // (filesz < memsz). The reason things work now is because
55        // the frame allocator in the kernel hands out zeroed pages by default.
56        // If this behaviour changes, we will need to explicitly handle it here.
57        if directive.filesz != directive.memsz {
58            if directive.filesz < directive.memsz {
59                // tracing::warn!(
60                //     "{} bytes after source implicitly zeroed",
61                //     directive.memsz - directive.filesz
62                // );
63            } else {
64                todo!()
65            }
66        }
67
68        // the offset from the base of the object with the ELF executable data
69        let src_start = NULLPAGE_SIZE + directive.offset;
70        // the destination offset is the virtual address we want this data
71        // to be mapped into. since the different sections are seperated
72        // by object boundaries, we keep the object-relative offset
73        // we trust the destination offset to be after the NULL_PAGE
74        let dest_start = directive.vaddr as usize % MAX_SIZE;
75        // the size of the data that must be copied from the ELF
76        let len = directive.filesz;
77
78        if !directive.load_flags.contains(LoadFlags::TARGETS_DATA) {
79            // Ensure we can direct-map the object for the text directives.
80            //
81            // The logic for direct mapping between x86_64 and aarch64 is different
82            // because the linker/compiler sets the page size to be 64K on aarch64.
83            // So we only check if we can direct map for x86_64. The source and
84            // destination offsets (for aarch64) would match up if a 64K page size
85            // for the NULLPAGE was used or we modified the destination address to
86            // be after the NULLPAGE. Loading still works on aarch64, but copies data.
87            #[cfg(target_arch = "x86_64")]
88            if src_start != dest_start {
89                tracing::error!(
90                    "invalid align: {:?}, {:x} {:x}",
91                    directive,
92                    src_start,
93                    dest_start
94                );
95                // TODO: check len too.
96                return Err(DynlinkError::new(DynlinkErrorKind::LoadDirectiveFail {
97                    dir: *directive,
98                }));
99            }
100        }
101
102        Ok(ObjectSource::new_copy(src.id(), src_start as u64, dest_start as u64, len).into())
103    };
104
105    let ld = ld.to_vec();
106    let (data_cmds, text_cmds): (Vec<_>, Vec<_>) = ld.into_iter().partition_map(|directive| {
107        if directive.load_flags.contains(LoadFlags::TARGETS_DATA) {
108            Either::Left(build_copy_cmd(&directive))
109        } else {
110            Either::Right(build_copy_cmd(&directive))
111        }
112    });
113
114    let data_cmds = DynlinkError::collect(DynlinkErrorKind::NewBackingFail, data_cmds)?;
115    let text_cmds = DynlinkError::collect(DynlinkErrorKind::NewBackingFail, text_cmds)?;
116
117    let data_id = sys_object_create(
118        create_spec,
119        &data_cmds,
120        &[CreateTieSpec::new(instance, CreateTieFlags::empty()).into()],
121    )
122    .map_err(|_| DynlinkErrorKind::NewBackingFail)?;
123
124    let text_id = sys_object_create(
125        create_spec,
126        &text_cmds,
127        &[CreateTieSpec::new(instance, CreateTieFlags::empty()).into()],
128    )
129    .map_err(|_| DynlinkErrorKind::NewBackingFail)?;
130    //let text_id = src.id;
131
132    tracing::trace!(
133        "mapped segments in instance {} to {}, {}",
134        instance,
135        text_id,
136        data_id
137    );
138
139    let (text, data) = map(text_id, data_id)?;
140    Ok(vec![text, data])
141}