blob: 2ce5af88aa3bd0efcdf1c913e226832ffe228f6b [file]
// Copyright 2026 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::collections::HashMap;
use std::io::BufWriter;
use std::path::Path;
use anyhow::{Context, Result, anyhow};
use byteorder::{BigEndian, LittleEndian};
use object::Endianness;
use pw_gdb_protocol::Client;
use pw_kernel_tracing::{EventPayload, Record};
use pw_perfetto_writer::{PerfettoWriter, ThreadState};
use tokio::net::TcpStream;
use tokio_util::compat::TokioAsyncReadCompatExt;
use crate::image_info::ImageInfo;
// TODO: konkers - Move this to a kernel types crate.
#[derive(Copy, Clone, PartialEq)]
#[repr(u8)]
pub enum NativeThreadState {
New = 0,
Initial = 1,
Ready = 2,
Running = 3,
Terminated = 4,
Joined = 5,
WaitingInterruptible = 6,
WaitingNonInterruptible = 7,
}
pub fn thread_state(raw_thread_state: u8) -> ThreadState {
if raw_thread_state == NativeThreadState::New as u8
|| raw_thread_state == NativeThreadState::Initial as u8
{
ThreadState::TaskStateCreated
} else if raw_thread_state == NativeThreadState::Ready as u8 {
ThreadState::TaskStateRunnable
} else if raw_thread_state == NativeThreadState::Running as u8 {
ThreadState::TaskStateRunning
} else if raw_thread_state == NativeThreadState::Terminated as u8 {
ThreadState::TaskStateDead
} else if raw_thread_state == NativeThreadState::Joined as u8 {
ThreadState::TaskStateDestroyed
} else if raw_thread_state == NativeThreadState::WaitingInterruptible as u8 {
ThreadState::TaskStateInterruptibleSleep
} else if raw_thread_state == NativeThreadState::WaitingNonInterruptible as u8 {
ThreadState::TaskStateUninterruptibleSleep
} else {
ThreadState::TaskStateUnknown
}
}
pub async fn run(path: &Path, gdb_addr: &str) -> Result<()> {
let info = ImageInfo::new(path)?;
if info.trace_buffers.len() != 1 {
return Err(anyhow!(
"Only a single trace buffer is support, found {}",
info.trace_buffers.len()
));
}
// Connect to GDB server
let stream = TcpStream::connect(gdb_addr)
.await
.context(format!("Failed to connect to GDB server at {}", gdb_addr))?;
let compat_stream = stream.compat();
let mut client = Client::new(compat_stream);
let trace_buffer = &info.trace_buffers[0];
let memory = client
.read_memory(trace_buffer.addr, trace_buffer.size)
.await
.context(format!(
"Failed to read trace buffer memory for {}",
trace_buffer.name
))?;
let records: Vec<Record> = memory
.chunks(16)
.filter_map(|data| {
if info.endian == Endianness::Little {
Record::read_from_buffer::<LittleEndian>(data)
} else {
Record::read_from_buffer::<BigEndian>(data)
}
})
.filter(|record| record.time_stamp != 0)
.collect();
let f = std::fs::File::create("trace.pb").unwrap();
let writer = BufWriter::new(f);
let mut perfetto = PerfettoWriter::new(writer);
// pw_kernel does not have classic process and thread ids. Instead it has
// opaque `usize` ids that may be larger than perfetto's i32 pid and tids.
// Perfetto requires pids and tids so fake ones are created.
//
// Pid 0 is reserved for the kernel.
// Tids start at 0x1000 to avoid colliding with pid 0.
let mut process_pids: HashMap<u64, i32> = HashMap::new();
let mut thread_tids = HashMap::new();
// Add synthetic kernel entry as pid 0.
process_pids.insert(0, 0);
perfetto.add_process_track(0, 0, 0, "kernel");
let first_timestamp = u64::from(
records
.iter()
.map(|v| v.time_stamp)
.reduce(core::cmp::min)
.unwrap(),
);
for (i, process) in info.processes.iter().enumerate() {
// Pids start at 1 to avoid colliding with kernel's pid 0.
let pid = i32::try_from(i).unwrap() + 1;
process_pids.insert(process.id, pid);
perfetto.add_process_track(first_timestamp, process.id, pid, &process.name);
}
for (i, thread) in info.threads.iter().enumerate() {
let pid = process_pids.get(&thread.parent_id).unwrap();
// Tids start at 0x1000 to avoid colliding with pids.
let tid = i32::try_from(i).unwrap() + 0x1000;
thread_tids.insert(u32::try_from(thread.id).unwrap(), tid);
perfetto.add_thread_track(
first_timestamp,
thread.id,
thread.parent_id,
*pid,
tid,
&thread.name,
);
}
for record in records.iter() {
let event = record.payload().unwrap();
match event {
EventPayload::ContextSwitch(event) => {
if event.old_thread_id != 0 {
let old_tid = thread_tids.get(&event.old_thread_id).unwrap();
perfetto.add_thread_state_event(
u64::from(record.time_stamp),
*old_tid,
thread_state(event.old_thread_state),
);
}
let new_tid = thread_tids.get(&event.new_thread_id).unwrap();
perfetto.add_thread_state_event(
u64::from(record.time_stamp),
*new_tid,
ThreadState::TaskStateRunning,
);
}
}
}
println!("wrote trace to trace.pb");
Ok(())
}