lucet_runtime_internals/module/
dl.rs1use crate::error::Error;
2use crate::module::{AddrDetails, GlobalSpec, HeapSpec, Module, ModuleInternal, TableElement};
3use libc::c_void;
4use libloading::Library;
5use lucet_module::{
6 FunctionHandle, FunctionIndex, FunctionPointer, FunctionSpec, ModuleData, ModuleFeatures,
7 ModuleSignature, PublicKey, SerializedModule, Signature, VersionInfo, LUCET_MODULE_SYM,
8};
9use std::ffi::CStr;
10use std::mem::MaybeUninit;
11use std::path::Path;
12use std::slice;
13use std::slice::from_raw_parts;
14use std::sync::Arc;
15
16use raw_cpuid::CpuId;
17
18fn check_feature_support(module_features: &ModuleFeatures) -> Result<(), Error> {
19 let cpuid = CpuId::new();
20
21 fn missing_feature(feature: &str) -> Error {
22 Error::Unsupported(format!(
23 "Module requires feature host does not support: {}",
24 feature
25 ))
26 }
27
28 let info = cpuid
29 .get_feature_info()
30 .ok_or_else(|| Error::Unsupported("Unable to obtain host CPU feature info!".to_string()))?;
31
32 if module_features.sse3 && !info.has_sse3() {
33 return Err(missing_feature("SSE3"));
34 }
35 if module_features.ssse3 && !info.has_ssse3() {
36 return Err(missing_feature("SSS3"));
37 }
38 if module_features.sse41 && !info.has_sse41() {
39 return Err(missing_feature("SSE4.1"));
40 }
41 if module_features.sse42 && !info.has_sse42() {
42 return Err(missing_feature("SSE4.2"));
43 }
44 if module_features.avx && !info.has_avx() {
45 return Err(missing_feature("AVX"));
46 }
47 if module_features.popcnt && !info.has_popcnt() {
48 return Err(missing_feature("POPCNT"));
49 }
50
51 if module_features.bmi1 || module_features.bmi2 {
52 let info = cpuid.get_extended_feature_info().ok_or_else(|| {
53 Error::Unsupported("Unable to obtain host CPU extended feature info!".to_string())
54 })?;
55
56 if module_features.bmi1 && !info.has_bmi1() {
57 return Err(missing_feature("BMI1"));
58 }
59
60 if module_features.bmi2 && !info.has_bmi2() {
61 return Err(missing_feature("BMI2"));
62 }
63 }
64
65 if module_features.lzcnt {
66 let info = cpuid.get_extended_function_info().ok_or_else(|| {
67 Error::Unsupported("Unable to obtain host CPU extended function info!".to_string())
68 })?;
69
70 if module_features.lzcnt && !info.has_lzcnt() {
71 return Err(missing_feature("LZCNT"));
72 }
73 }
74
75 Ok(())
77}
78
79pub struct DlModule {
81 lib: Library,
82
83 fbase: *const c_void,
85
86 module: lucet_module::Module<'static>,
88}
89
90unsafe impl Send for DlModule {}
92unsafe impl Sync for DlModule {}
93
94impl DlModule {
95 pub fn load<P: AsRef<Path>>(so_path: P) -> Result<Arc<Self>, Error> {
97 Self::load_and_maybe_verify(so_path, None)
98 }
99
100 pub fn load_and_verify<P: AsRef<Path>>(so_path: P, pk: PublicKey) -> Result<Arc<Self>, Error> {
103 Self::load_and_maybe_verify(so_path, Some(pk))
104 }
105
106 fn load_and_maybe_verify<P: AsRef<Path>>(
107 so_path: P,
108 pk: Option<PublicKey>,
109 ) -> Result<Arc<Self>, Error> {
110 let abs_so_path = so_path.as_ref().canonicalize().map_err(Error::DlError)?;
115 let lib = Library::new(abs_so_path.as_os_str()).map_err(Error::DlError)?;
116
117 let serialized_module_ptr = unsafe {
118 lib.get::<*const SerializedModule>(LUCET_MODULE_SYM.as_bytes())
119 .map_err(|e| {
120 lucet_incorrect_module!("error loading required symbol `lucet_module`: {}", e)
121 })?
122 };
123
124 let serialized_module: &SerializedModule =
125 unsafe { serialized_module_ptr.as_ref().unwrap() };
126
127 let module_version = serialized_module.version.clone();
128
129 let runtime_version =
130 VersionInfo::current(include_str!(concat!(env!("OUT_DIR"), "/commit_hash")).as_bytes());
131
132 if !module_version.valid() {
133 return Err(lucet_incorrect_module!("reserved bit is not set. This module is likely too old for this lucet-runtime to load."));
134 } else if !runtime_version.compatible_with(&module_version) {
135 return Err(lucet_incorrect_module!(
136 "version mismatch. module has version {}, while this runtime is version {}",
137 module_version,
138 runtime_version,
139 ));
140 }
141
142 let module_data_slice: &'static [u8] = unsafe {
150 slice::from_raw_parts(
151 serialized_module.module_data_ptr as *const u8,
152 serialized_module.module_data_len as usize,
153 )
154 };
155 let module_data = ModuleData::deserialize(module_data_slice)?;
156
157 check_feature_support(module_data.features())?;
158
159 if let Some(pk) = pk {
162 ModuleSignature::verify(so_path, &pk, &module_data)?;
163 }
164
165 let fbase = if let Some(dli) =
166 dladdr(serialized_module as *const SerializedModule as *const c_void)
167 {
168 dli.dli_fbase
169 } else {
170 std::ptr::null()
171 };
172
173 if serialized_module.tables_len > std::u32::MAX as u64 {
174 lucet_incorrect_module!("table segment too long: {}", serialized_module.tables_len);
175 }
176 let tables: &'static [&'static [TableElement]] = unsafe {
177 from_raw_parts(
178 serialized_module.tables_ptr as *const &[TableElement],
179 serialized_module.tables_len as usize,
180 )
181 };
182
183 let function_manifest = if serialized_module.function_manifest_ptr != 0 {
184 unsafe {
185 from_raw_parts(
186 serialized_module.function_manifest_ptr as *const FunctionSpec,
187 serialized_module.function_manifest_len as usize,
188 )
189 }
190 } else {
191 &[]
192 };
193
194 Ok(Arc::new(DlModule {
195 lib,
196 fbase,
197 module: lucet_module::Module {
198 version: module_version,
199 module_data,
200 tables,
201 function_manifest,
202 },
203 }))
204 }
205}
206
207impl Module for DlModule {}
208
209impl ModuleInternal for DlModule {
210 fn is_instruction_count_instrumented(&self) -> bool {
211 self.module.module_data.features().instruction_count
212 }
213
214 fn heap_spec(&self) -> Option<&HeapSpec> {
215 self.module.module_data.heap_spec()
216 }
217
218 fn globals(&self) -> &[GlobalSpec<'_>] {
219 self.module.module_data.globals_spec()
220 }
221
222 fn get_sparse_page_data(&self, page: usize) -> Option<&[u8]> {
223 if let Some(ref sparse_data) = self.module.module_data.sparse_data() {
224 *sparse_data.get_page(page)
225 } else {
226 None
227 }
228 }
229
230 fn sparse_page_data_len(&self) -> usize {
231 self.module
232 .module_data
233 .sparse_data()
234 .map(|d| d.len())
235 .unwrap_or(0)
236 }
237
238 fn table_elements(&self) -> Result<&[TableElement], Error> {
239 match self.module.tables.get(0) {
240 Some(table) => Ok(table),
241 None => Err(lucet_incorrect_module!("table 0 is not present")),
242 }
243 }
244
245 fn get_export_func(&self, sym: &str) -> Result<FunctionHandle, Error> {
246 self.module
247 .module_data
248 .get_export_func_id(sym)
249 .ok_or_else(|| Error::SymbolNotFound(sym.to_string()))
250 .map(|id| {
251 let ptr = self.function_manifest()[id.as_u32() as usize].ptr();
252 FunctionHandle { ptr, id }
253 })
254 }
255
256 fn get_func_from_idx(&self, table_id: u32, func_id: u32) -> Result<FunctionHandle, Error> {
257 if table_id != 0 {
258 return Err(Error::FuncNotFound(table_id, func_id));
259 }
260 let table = self.table_elements()?;
261 let func = table
262 .get(func_id as usize)
263 .map(|element| element.function_pointer())
264 .ok_or(Error::FuncNotFound(table_id, func_id))?;
265
266 Ok(self.function_handle_from_ptr(func))
267 }
268
269 fn get_start_func(&self) -> Result<Option<FunctionHandle>, Error> {
270 if let Ok(start_func) = unsafe { self.lib.get::<*const extern "C" fn()>(b"guest_start") } {
274 if start_func.is_null() {
275 lucet_incorrect_module!("`guest_start` is defined but null");
276 }
277 Ok(Some(self.function_handle_from_ptr(
278 FunctionPointer::from_usize(unsafe { **start_func } as usize),
279 )))
280 } else {
281 Ok(None)
282 }
283 }
284
285 fn function_manifest(&self) -> &[FunctionSpec] {
286 self.module.function_manifest
287 }
288
289 fn addr_details(&self, addr: *const c_void) -> Result<Option<AddrDetails>, Error> {
290 if let Some(dli) = dladdr(addr) {
291 let file_name = if dli.dli_fname.is_null() {
292 None
293 } else {
294 Some(unsafe { CStr::from_ptr(dli.dli_fname).to_owned().into_string()? })
295 };
296 let sym_name = if dli.dli_sname.is_null() {
297 None
298 } else {
299 Some(unsafe { CStr::from_ptr(dli.dli_sname).to_owned().into_string()? })
300 };
301 Ok(Some(AddrDetails {
302 in_module_code: dli.dli_fbase as *const c_void == self.fbase,
303 file_name,
304 sym_name,
305 }))
306 } else {
307 Ok(None)
308 }
309 }
310
311 fn get_signature(&self, fn_id: FunctionIndex) -> &Signature {
312 self.module.module_data.get_signature(fn_id)
313 }
314}
315
316fn dladdr(addr: *const c_void) -> Option<libc::Dl_info> {
319 let mut info = MaybeUninit::<libc::Dl_info>::uninit();
320 let res = unsafe { libc::dladdr(addr, info.as_mut_ptr()) };
321 if res != 0 {
322 Some(unsafe { info.assume_init() })
323 } else {
324 None
325 }
326}