1use std::{
4 fmt::{Debug, Display},
5 sync::OnceLock,
6 time::Instant,
7};
8
9use elf::{
10 abi::{DT_FLAGS_1, PT_DYNAMIC, PT_PHDR, PT_TLS, STB_WEAK},
11 dynamic::Dyn,
12 endian::NativeEndian,
13 segment::{Elf64_Phdr, ProgramHeader},
14 ParseError,
15};
16use petgraph::stable_graph::NodeIndex;
17use secgate::RawSecGateInfo;
18use smallstr::SmallString;
19use twizzler_abi::object::ObjID;
20use twizzler_rt_abi::{
21 core::{CtorSet, RuntimeInfo},
22 debug::LoadedImageId,
23};
24
25use crate::{
26 compartment::CompartmentId, engines::Backing, symbol::RelocatedSymbol, tls::TlsModId,
27 DynlinkError, DynlinkErrorKind,
28};
29
30#[derive(PartialEq, PartialOrd, Ord, Eq, Debug, Clone, Copy)]
31pub(crate) enum RelocState {
32 Unrelocated,
34 PartialRelocation,
36 Relocated,
38}
39
40#[derive(PartialEq, PartialOrd, Ord, Eq, Debug, Clone, Copy)]
41pub enum AllowedGates {
42 Private,
44 Public,
46 PublicInclSelf,
48}
49
50#[repr(C)]
51#[derive(Debug, Clone, PartialEq, PartialOrd, Ord, Eq, Hash, Default)]
53pub struct UnloadedLibrary {
54 pub name: String,
55 pub id: Option<ObjID>,
56}
57
58impl UnloadedLibrary {
59 pub fn new(name: impl AsRef<str>) -> Self {
61 Self {
62 name: name.as_ref().to_string(),
63 id: None,
64 }
65 }
66
67 pub fn new_object(name: impl AsRef<str>, id: ObjID) -> Self {
68 Self {
69 name: name.as_ref().to_string(),
70 id: Some(id),
71 }
72 }
73}
74
75#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Ord, Eq, Hash, Default)]
77#[repr(transparent)]
78pub struct LibraryId(pub(crate) NodeIndex);
79
80impl From<LoadedImageId> for LibraryId {
81 fn from(value: LoadedImageId) -> Self {
82 LibraryId(NodeIndex::new(value as usize))
83 }
84}
85
86impl Into<LoadedImageId> for LibraryId {
87 fn into(self) -> LoadedImageId {
88 self.0.index() as u32
89 }
90}
91
92impl Display for LibraryId {
93 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
94 write!(f, "{}", &self.0.index())
95 }
96}
97
98#[repr(C)]
99pub struct Library {
101 pub name: String,
103 pub(crate) idx: NodeIndex,
105 pub(crate) comp_id: CompartmentId,
107 comp_name: String,
109 pub full_obj: Backing,
111 pub(crate) reloc_state: RelocState,
113 allowed_gates: AllowedGates,
114
115 pub backings: Vec<Backing>,
116
117 pub tls_id: Option<TlsModId>,
119
120 pub(crate) ctors: CtorSet,
122 pub(crate) secgate_info: SecgateInfo,
123
124 elf: OnceLock<elf::ElfBytes<'static, NativeEndian>>,
126 elf_common: OnceLock<elf::CommonElfData<'static, NativeEndian>>,
127}
128
129#[allow(dead_code)]
130impl Library {
131 pub(crate) fn new(
132 name: String,
133 idx: NodeIndex,
134 comp_id: CompartmentId,
135 comp_name: String,
136 full_obj: Backing,
137 backings: Vec<Backing>,
138 tls_id: Option<TlsModId>,
139 ctors: CtorSet,
140 secgate_info: SecgateInfo,
141 allowed_gates: AllowedGates,
142 ) -> Self {
143 Self {
144 name,
145 idx,
146 full_obj,
147 backings,
148 tls_id,
149 ctors,
150 reloc_state: RelocState::Unrelocated,
151 comp_id,
152 comp_name,
153 secgate_info,
154 allowed_gates,
155 elf: OnceLock::new(),
156 elf_common: OnceLock::new(),
157 }
158 }
159
160 pub fn allows_gates(&self) -> bool {
161 self.allowed_gates != AllowedGates::Private
162 }
163
164 pub fn allows_self_gates(&self) -> bool {
165 self.allowed_gates == AllowedGates::PublicInclSelf
166 }
167
168 pub fn dynamic_ptr(&self) -> Option<*mut Dyn> {
169 let phdr = self
170 .get_elf()
171 .ok()?
172 .segments()?
173 .iter()
174 .find(|s| s.p_type == PT_DYNAMIC)?;
175 Some(self.laddr_mut(phdr.p_vaddr))
176 }
177
178 pub fn is_binary(&self) -> bool {
179 let Some(dynamic) = self
180 .get_elf()
181 .ok()
182 .and_then(|elf| elf.dynamic().ok())
183 .flatten()
184 else {
185 return false;
186 };
187 let Some(flags) = dynamic.iter().find_map(|ent| {
188 if ent.d_tag == DT_FLAGS_1 {
189 Some(ent.d_val())
190 } else {
191 None
192 }
193 }) else {
194 return false;
195 };
196 flags & elf::abi::DF_1_PIE as u64 != 0
197 }
198
199 pub fn id(&self) -> LibraryId {
201 LibraryId(self.idx)
202 }
203
204 pub fn compartment(&self) -> CompartmentId {
206 self.comp_id
207 }
208
209 pub fn get_phdrs_raw(&self) -> Option<(*const Elf64_Phdr, usize)> {
211 Some((
212 self.get_elf().ok()?.segments()?.iter().find_map(|p| {
213 if p.p_type == PT_PHDR {
214 Some(self.laddr(p.p_vaddr))
215 } else {
216 None
217 }
218 })?,
219 self.get_elf().ok()?.segments()?.len(),
220 ))
221 }
222
223 pub fn get_elf(&self) -> Result<&elf::ElfBytes<'static, NativeEndian>, ParseError> {
225 self.elf.get_or_try_init(|| unsafe {
226 elf::ElfBytes::<'static, NativeEndian>::minimal_parse(std::mem::transmute(
227 self.full_obj.slice(),
228 ))
229 })
230 }
231
232 pub fn get_elf_common(&self) -> Result<&elf::CommonElfData<'static, NativeEndian>, ParseError> {
234 self.elf_common
235 .get_or_try_init(|| self.get_elf().and_then(|e| e.find_common_data()))
236 }
237
238 pub fn base_addr(&self) -> usize {
240 self.backings[0].load_addr()
241 }
242
243 pub fn laddr<T>(&self, val: u64) -> *const T {
245 (self.base_addr() + val as usize) as *const T
246 }
247
248 pub fn laddr_mut<T>(&self, val: u64) -> *mut T {
250 (self.base_addr() + val as usize) as *mut T
251 }
252
253 pub fn get_entry_address(
255 &self,
256 ) -> Result<extern "C" fn(*const RuntimeInfo) -> !, DynlinkError> {
257 let entry = self.get_elf()?.ehdr.e_entry;
258 if entry == 0 {
259 return Err(DynlinkErrorKind::NoEntryAddress {
260 name: self.name.as_str().into(),
261 }
262 .into());
263 }
264 let entry: *const u8 = self.laddr(entry);
265 let ptr: extern "C" fn(*const RuntimeInfo) -> ! =
266 unsafe { core::mem::transmute(entry as usize) };
267 Ok(ptr)
268 }
269
270 fn get_tls_phdr(&self) -> Result<Option<ProgramHeader>, DynlinkError> {
272 Ok(self
273 .get_elf()?
274 .segments()
275 .and_then(|phdrs| phdrs.iter().find(|phdr| phdr.p_type == PT_TLS)))
276 }
277
278 pub(crate) fn get_tls_data(&self) -> Result<Option<&[u8]>, DynlinkError> {
279 let phdr = self.get_tls_phdr()?;
280 Ok(phdr.map(|phdr| {
281 let slice = unsafe {
282 core::slice::from_raw_parts(self.laddr(phdr.p_vaddr), phdr.p_memsz as usize)
283 };
284 slice
285 }))
286 }
287
288 fn do_lookup_symbol(
289 &self,
290 name: &str,
291 allow_weak: bool,
292 ) -> Result<RelocatedSymbol<'_>, DynlinkError> {
293 let _start_1 = Instant::now();
294 let common = self.get_elf_common()?;
295 let _start_2 = Instant::now();
296
297 if let Some(h) = &common.gnu_hash {
314 if let Some((_, sym)) = h
315 .find(
316 name.as_ref(),
317 common
318 .dynsyms
319 .as_ref()
320 .ok_or_else(|| DynlinkErrorKind::MissingSection {
321 name: "dynsyms".into(),
322 })?,
323 common.dynsyms_strs.as_ref().ok_or_else(|| {
324 DynlinkErrorKind::MissingSection {
325 name: "dynsyms_strs".into(),
326 }
327 })?,
328 )
329 .ok()
330 .flatten()
331 {
332 if !sym.is_undefined() {
333 if sym.st_bind() != STB_WEAK
335 || allow_weak
336 || (self.is_relocated() && self.is_secgate(name))
337 {
338 tracing::trace!(
339 "found {} in gnu hash in {}us",
340 name,
341 _start_2.elapsed().as_micros()
342 );
343 return Ok(RelocatedSymbol::new(sym, self));
344 } else {
345 tracing::warn!("lookup symbol {} skipping weak binding in {}", name, self);
346 }
347 } else {
348 return Err(DynlinkErrorKind::NameNotFound { name: name.into() }.into());
350 }
351 }
352 }
353
354 if let Some(h) = &common.sysv_hash {
356 if let Some((_, sym)) = h
357 .find(
358 name.as_ref(),
359 common
360 .dynsyms
361 .as_ref()
362 .ok_or_else(|| DynlinkErrorKind::MissingSection {
363 name: "dynsyms".into(),
364 })?,
365 common.dynsyms_strs.as_ref().ok_or_else(|| {
366 DynlinkErrorKind::MissingSection {
367 name: "dynsyms_strs".into(),
368 }
369 })?,
370 )
371 .ok()
372 .flatten()
373 {
374 if !sym.is_undefined() {
375 if sym.st_bind() != STB_WEAK
377 || allow_weak
378 || (self.is_relocated() && self.is_secgate(name))
379 {
380 return Ok(RelocatedSymbol::new(sym, self));
381 } else {
382 tracing::warn!("lookup symbol {} skipping weak binding in {}", name, self);
383 }
384 } else {
385 }
387 }
388 }
389 Err(DynlinkErrorKind::NameNotFound { name: name.into() }.into())
420 }
421
422 pub(crate) fn lookup_symbol(
423 &self,
424 name: &str,
425 allow_weak: bool,
426 allow_prefix: bool,
427 ) -> Result<RelocatedSymbol<'_>, DynlinkError> {
428 let _start = Instant::now();
429 let ret = self.do_lookup_symbol(&name, allow_weak);
430 tracing::trace!(
431 "bare lookup ({} {}) costs {}us",
432 allow_weak,
433 allow_prefix,
434 _start.elapsed().as_micros()
435 );
436 if allow_prefix && ret.is_err() && !name.starts_with("__TWIZZLER_SECURE_GATE_") {
437 let mut prefixedname = SmallString::<[u8; 256]>::from_str("__TWIZZLER_SECURE_GATE_");
438 prefixedname.push_str(name);
439
440 if let Ok(o) = self.do_lookup_symbol(&prefixedname, allow_weak) {
441 return Ok(o);
442 }
443 }
444 ret
445 }
446
447 pub(crate) fn is_local_or_secgate_from(&self, other: &Library, name: &str) -> bool {
448 self.in_same_compartment_as(other) || (self.is_relocated() && self.is_secgate(name))
449 }
450
451 pub(crate) fn in_same_compartment_as(&self, other: &Library) -> bool {
452 other.comp_id == self.comp_id
453 }
454
455 pub fn is_relocated(&self) -> bool {
456 self.reloc_state == RelocState::Relocated
457 }
458
459 fn is_secgate(&self, name: &str) -> bool {
460 self.iter_secgates()
461 .map(|gates| {
462 gates
463 .iter()
464 .any(|gate| gate.name().to_bytes() == name.as_bytes())
465 })
466 .unwrap_or(false)
467 }
468
469 pub fn iter_secgates(&self) -> Option<&[RawSecGateInfo]> {
470 let addr = self.secgate_info.info_addr?;
471
472 Some(unsafe { core::slice::from_raw_parts(addr as *const _, self.secgate_info.num) })
473 }
474}
475
476impl Debug for Library {
477 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
478 f.debug_struct("Library")
479 .field("name", &self.name)
480 .field("comp_name", &self.comp_name)
481 .field("idx", &self.idx)
482 .field("tls_id", &self.tls_id)
483 .finish()
484 }
485}
486
487impl Drop for Library {
488 fn drop(&mut self) {
489 }
491}
492
493impl core::fmt::Display for Library {
494 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
495 write!(f, "{}::{}", &self.comp_name, &self.name)
496 }
497}
498
499impl core::fmt::Display for UnloadedLibrary {
500 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
501 write!(f, "{}(unloaded)", &self.name)
502 }
503}
504
505#[derive(Debug, Clone, Default)]
506pub struct SecgateInfo {
507 pub info_addr: Option<usize>,
508 pub num: usize,
509}