use std::collections::HashMap;
use std::path::PathBuf;
use std::time::Instant;
use tracing::debug;
use lightswitch_object::BuildId;
use lightswitch_object::ElfLoad;
use lightswitch_object::ExecutableId;
use lightswitch_object::Runtime;
pub type Pid = i32;
#[derive(Debug, Clone, PartialEq)]
pub enum ExecutableMappingType {
FileBacked,
Anonymous,
Vdso,
Kernel,
}
#[derive(Debug, PartialEq, Clone)]
pub enum ProcessStatus {
Running,
Exited,
}
#[derive(Debug, Clone)]
pub struct ProcessInfo {
pub comm: String,
pub status: ProcessStatus,
pub mappings: ExecutableMappings,
pub compatibility_mode: bool,
pub last_used: Instant,
}
#[derive(Debug, Clone)]
pub struct ExecutableMapping {
pub executable_id: ExecutableId,
pub kind: ExecutableMappingType,
pub start_addr: u64,
pub end_addr: u64,
pub offset: u64,
pub load_address: u64,
pub soft_delete: bool,
}
#[derive(Debug, Clone)]
pub struct ExecutableMappings(pub Vec<ExecutableMapping>);
impl ExecutableMappings {
pub fn for_address(&self, virtual_address: &u64) -> Option<&ExecutableMapping> {
self.0
.iter()
.find(|&mapping| (mapping.start_addr..mapping.end_addr).contains(virtual_address))
}
}
impl ExecutableMapping {
pub fn mark_as_deleted(
&mut self,
object_files: &mut HashMap<ExecutableId, ObjectFileInfo>,
) -> bool {
if self.soft_delete {
return false;
}
self.soft_delete = true;
if let Some(object_file) = object_files.get_mut(&self.executable_id) {
if object_file.references == 0 {
return false;
}
object_file.references -= 1;
if object_file.references == 0 {
debug!(
"object file with path {} can be deleted",
object_file.path.display()
);
return true;
}
debug_assert!(
object_file.references >= 0,
"Reference count for {} is negative: {}",
object_file.path.display(),
object_file.references,
);
}
false
}
}
pub struct ObjectFileInfo {
pub build_id: Option<BuildId>,
pub path: PathBuf,
pub elf_load_segments: Vec<ElfLoad>,
pub is_dyn: bool,
pub references: i64,
pub native_unwind_info_size: Option<u64>,
pub is_vdso: bool,
pub runtime: Runtime,
pub is_64: bool,
}
impl Clone for ObjectFileInfo {
fn clone(&self) -> Self {
ObjectFileInfo {
build_id: self.build_id.clone(),
path: self.path.clone(),
elf_load_segments: self.elf_load_segments.clone(),
is_dyn: self.is_dyn,
references: self.references,
native_unwind_info_size: self.native_unwind_info_size,
is_vdso: self.is_vdso,
runtime: self.runtime.clone(),
is_64: self.is_64,
}
}
}
impl ObjectFileInfo {
pub fn normalized_address(
&self,
virtual_address: u64,
mapping: &ExecutableMapping,
) -> Option<u64> {
if mapping.kind == ExecutableMappingType::Kernel {
return Some(virtual_address - mapping.offset);
}
let offset = virtual_address - mapping.start_addr + mapping.offset;
for segment in &self.elf_load_segments {
let address_range = segment.p_offset..(segment.p_offset + segment.p_filesz);
if address_range.contains(&offset) {
return Some(offset - segment.p_offset + segment.p_vaddr);
}
}
None
}
}
pub(crate) fn opened_exe_path(pid: Pid, start_address: u64, end_address: u64) -> PathBuf {
PathBuf::from(format!(
"/proc/{pid}/map_files/{:x}-{:x}",
start_address, end_address
))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_address_normalization() {
let mut object_file_info = ObjectFileInfo {
build_id: None,
path: "/".into(),
elf_load_segments: vec![],
is_dyn: false,
references: 0,
native_unwind_info_size: None,
is_vdso: false,
runtime: Runtime::CLike,
is_64: true,
};
let mapping = ExecutableMapping {
executable_id: ExecutableId(0x0),
kind: ExecutableMappingType::FileBacked,
start_addr: 0x100,
end_addr: 0x100 + 100,
offset: 0x0,
load_address: 0x0,
soft_delete: false,
};
assert!(
object_file_info
.normalized_address(0x110, &mapping)
.is_none()
);
object_file_info.elf_load_segments = vec![ElfLoad {
p_offset: 0x1,
p_vaddr: 0x0,
p_filesz: 0x20,
}];
assert_eq!(
object_file_info.normalized_address(0x110, &mapping),
Some(0xF)
);
object_file_info.elf_load_segments = vec![ElfLoad {
p_offset: 0x0,
p_vaddr: 0x0,
p_filesz: 0x5,
}];
assert!(
object_file_info
.normalized_address(0x110, &mapping)
.is_none()
);
}
}