blob: f2758f0a02e3c9097bcd551f6005cf66f74969aa [file]
// Copyright 2025 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
use std::fs;
use std::path::Path;
use anyhow::{Context, Result, anyhow};
use object::{Endian, File, Object, ObjectSection};
pub struct StackInfo {
pub name: String,
pub stack_addr: u64,
pub stack_size: u64,
}
pub struct ThreadInfo {
pub name: String,
pub id: u64,
pub parent_id: u64,
}
pub struct ProcessInfo {
pub name: String,
pub id: u64,
}
pub struct TraceBufferInfo {
pub name: String,
pub addr: u64,
pub size: u64,
}
pub struct ImageInfo {
pub stacks: Vec<StackInfo>,
pub threads: Vec<ThreadInfo>,
pub processes: Vec<ProcessInfo>,
pub trace_buffers: Vec<TraceBufferInfo>,
pub endian: object::Endianness,
}
impl ImageInfo {
pub fn new(path: &Path) -> Result<Self> {
let bin_data = fs::read(path).context("Failed to read ELF file")?;
let obj_file = object::File::parse(&*bin_data).context("Failed to parse ELF file")?;
let endian = obj_file.endianness();
let stacks = Self::extract_stacks(&obj_file)?;
let threads = Self::extract_threads(&obj_file)?;
let processes = Self::extract_processes(&obj_file)?;
let trace_buffers = Self::extract_trace_buffers(&obj_file)?;
Ok(ImageInfo {
stacks,
threads,
endian,
processes,
trace_buffers,
})
}
fn get_section_data<'a>(obj_file: &File<'a>, section_name: &str) -> Result<&'a [u8]> {
let section = obj_file
.section_by_name(section_name)
.with_context(|| format!("Failed to find section {}", section_name))?;
let data = section.data().context("Failed to read section data")?;
Ok(data)
}
fn extract_stacks(obj_file: &File<'_>) -> Result<Vec<StackInfo>> {
Self::extract_entries(obj_file, ".pw_kernel.annotations.stack", 4, |fields| {
let name = Self::read_string(obj_file, fields[0], fields[1])
.context("Failed to read stack name")?;
Ok(StackInfo {
name,
stack_addr: fields[2],
stack_size: fields[3],
})
})
}
fn extract_threads(obj_file: &File<'_>) -> Result<Vec<ThreadInfo>> {
Self::extract_entries(obj_file, ".pw_kernel.annotations.thread", 4, |fields| {
let name = Self::read_string(obj_file, fields[0], fields[1])
.context("Failed to read thread name")?;
Ok(ThreadInfo {
name,
id: fields[2],
parent_id: fields[3],
})
})
}
fn extract_processes(obj_file: &File<'_>) -> Result<Vec<ProcessInfo>> {
Self::extract_entries(obj_file, ".pw_kernel.annotations.process", 3, |fields| {
let name = Self::read_string(obj_file, fields[0], fields[1])
.context("Failed to read process name")?;
Ok(ProcessInfo {
name,
id: fields[2],
})
})
}
fn extract_trace_buffers(obj_file: &File<'_>) -> Result<Vec<TraceBufferInfo>> {
Self::extract_entries(
obj_file,
".pw_kernel.annotations.trace_buffer",
4,
|fields| {
let name = Self::read_string(obj_file, fields[0], fields[1])
.context("Failed to read trace buffer name")?;
Ok(TraceBufferInfo {
name,
addr: fields[2],
size: fields[3],
})
},
)
}
fn extract_entries<T, F>(
obj_file: &File<'_>,
section_name: &str,
num_fields: usize,
mapper: F,
) -> Result<Vec<T>>
where
F: Fn(&[u64]) -> Result<T>,
{
let data = Self::get_section_data(obj_file, section_name)?;
let is_64 = obj_file.is_64();
let field_size = if is_64 { 8 } else { 4 };
let entry_size = num_fields * field_size;
if data.len() % entry_size != 0 {
return Err(anyhow!(
"{} section size is not a multiple of {}",
section_name,
entry_size
));
}
let endian = obj_file.endianness();
let mut entries = Vec::new();
for entry_data in data.chunks(entry_size) {
let mut fields = Vec::with_capacity(num_fields);
for i in 0..num_fields {
fields.push(Self::extract_usize_field(endian, entry_data, i, is_64)?);
}
entries.push(mapper(&fields)?);
}
Ok(entries)
}
fn extract_usize_field(
endian: object::Endianness,
chunk: &[u8],
index: usize,
is_64: bool,
) -> Result<u64> {
if is_64 {
let start = index * 8;
let bytes = chunk
.get(start..start + 8)
.context("Chunk too small for 64-bit field")?;
Ok(endian.read_u64_bytes(bytes.try_into()?))
} else {
let start = index * 4;
let bytes = chunk
.get(start..start + 4)
.context("Chunk too small for 32-bit field")?;
Ok(u64::from(endian.read_u32_bytes(bytes.try_into()?)))
}
}
fn read_data<'a>(obj_file: &'a object::File, addr: u64, len: u64) -> Result<&'a [u8]> {
// Find the section containing the address
for section in obj_file.sections() {
let section_addr = section.address();
let section_size = section.size();
if addr >= section_addr && addr + len <= section_addr + section_size {
let data = section.data()?;
let offset = usize::try_from(addr - section_addr).context("Offset too large")?;
return Ok(&data[offset..offset + usize::try_from(len).context("Len too large")?]);
}
}
Err(anyhow!("Could not find address {:#x} in any section", addr))
}
fn read_string(obj_file: &object::File, addr: u64, len: u64) -> Result<String> {
let bytes = Self::read_data(obj_file, addr, len)?;
Ok(String::from_utf8_lossy(bytes).into_owned())
}
}