Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tracepoint extension support #160

Open
wants to merge 29 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
cb6b95a
tracepoint extension support
Dec 9, 2024
a470c6c
wip get_owned
Dec 10, 2024
f5b5d3c
add armv4t tracepoint support
Dec 11, 2024
9b5f8b7
run cargo fmt
Dec 11, 2024
882736b
remove incorrect spaces in packets
Dec 30, 2024
ad92d99
better type safety for experiment status
Dec 30, 2024
4cccd75
use write_num helper instead of manual byte conversion
Dec 30, 2024
0e6943e
remove extra cloned
Dec 31, 2024
dbeb9cc
split experiment status in two
Jan 1, 2025
943b7f4
remove qtnotes
Jan 6, 2025
4e7dcca
split qTBuffer/QTBuffer, fix trace explanations
Jan 6, 2025
c5e2ed6
address nits
Jan 6, 2025
8b358de
strip suffix for QTDP
Jan 6, 2025
804487e
strip suffix on QTDP parse raw actions
Jan 6, 2025
3871bc5
refactor tracepoint enumeration to use a callback
Jan 6, 2025
7e367ed
remove arch_int helper
Jan 6, 2025
a65be61
move implementations, have actions method return an error for parsing…
Jan 6, 2025
7631df4
fix actions documentation
Jan 6, 2025
2b632ab
fix TracepointAction documentation
Jan 6, 2025
0ea0245
break out tracepoint status information
Jan 7, 2025
1da19ab
fix tracepoint status documentation
Jan 7, 2025
ad297de
rustfmt
Jan 7, 2025
11d5fed
standardize QTinit
Jan 7, 2025
9ce35a3
standardize qTStatus
Jan 7, 2025
7ebd28f
remove extra derives
Jan 7, 2025
340b43f
remove pub from from_tdp
Jan 7, 2025
ce4acb4
explain tracepoint extensions, drop InstallInTrace
Jan 8, 2025
992d54a
remove get_owned and just use clone derive
Jan 8, 2025
de201eb
restrict visibility for internal actions list, removed managed slice …
Jan 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ Of course, most use-cases will want to support additional debugging features as
- Read auxiliary vector (`info auxv`)
- Extra thread info (`info threads`)
- Extra library information (`info sharedlibraries`)
- Tracepoints
- Configure tracepoints and actions to perform when hit
- Select and interrogate collected trace frames

_Note:_ GDB features are implemented on an as-needed basis by `gdbstub`'s contributors. If there's a missing GDB feature that you'd like `gdbstub` to implement, please file an issue and/or open a PR!

Expand Down
43 changes: 43 additions & 0 deletions examples/armv4t/emu.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::gdb::tracepoints::TraceFrame;
use crate::mem_sniffer::AccessKind;
use crate::mem_sniffer::MemSniffer;
use crate::DynResult;
Expand All @@ -7,6 +8,9 @@ use armv4t_emu::ExampleMem;
use armv4t_emu::Memory;
use armv4t_emu::Mode;
use gdbstub::common::Pid;
use gdbstub::target::ext::tracepoints::Tracepoint;
use gdbstub::target::ext::tracepoints::TracepointItem;
use std::collections::HashMap;

const HLE_RETURN_ADDR: u32 = 0x12345678;

Expand Down Expand Up @@ -41,6 +45,12 @@ pub struct Emu {
pub(crate) breakpoints: Vec<u32>,
pub(crate) files: Vec<Option<std::fs::File>>,

pub(crate) tracepoints: HashMap<Tracepoint, Vec<TracepointItem<'static, u32>>>,
pub(crate) traceframes: Vec<TraceFrame>,
pub(crate) tracepoint_enumerate_machine: (Vec<TracepointItem<'static, u32>>, usize),
pub(crate) tracing: bool,
pub(crate) selected_frame: Option<usize>,

pub(crate) reported_pid: Pid,
}

Expand Down Expand Up @@ -93,6 +103,12 @@ impl Emu {
breakpoints: Vec::new(),
files: Vec::new(),

tracepoints: HashMap::new(),
traceframes: Vec::new(),
tracepoint_enumerate_machine: (Vec::new(), 0),
tracing: false,
selected_frame: None,

reported_pid: Pid::new(1).unwrap(),
})
}
Expand All @@ -106,6 +122,33 @@ impl Emu {

/// single-step the interpreter
pub fn step(&mut self) -> Option<Event> {
if self.tracing {
let pc = self.cpu.reg_get(self.cpu.mode(), reg::PC);
let frames: Vec<_> = self
.tracepoints
.iter()
.filter(|(_tracepoint, definition)| {
if let Some(TracepointItem::New(new)) = definition.get(0) {
new.enabled && new.addr == pc
} else {
false
}
})
.map(|(tracepoint, _definition)| {
// our `tracepoint_define` restricts our loaded tracepoints to only contain
// register collect actions. instead of only collecting the registers requested
// in the register mask and recording a minimal trace frame, we just collect
// all of them by cloning the cpu itself.
TraceFrame {
number: *tracepoint,
addr: pc,
snapshot: self.cpu.clone(),
}
})
.collect();
self.traceframes.extend(frames);
}

let mut hit_watchpoint = None;

let mut sniffer = MemSniffer::new(&mut self.mem, &self.watchpoints, |access| {
Expand Down
41 changes: 35 additions & 6 deletions examples/armv4t/gdb/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ mod memory_map;
mod monitor_cmd;
mod section_offsets;
mod target_description_xml_override;
pub(crate) mod tracepoints;

/// Turn a `ArmCoreRegId` into an internal register number of `armv4t_emu`.
fn cpu_reg_id(id: ArmCoreRegId) -> Option<u8> {
Expand Down Expand Up @@ -161,29 +162,46 @@ impl Target for Emu {
) -> Option<target::ext::libraries::LibrariesSvr4Ops<'_, Self>> {
Some(self)
}

#[inline(always)]
fn support_tracepoints(
&mut self,
) -> Option<target::ext::tracepoints::TracepointsOps<'_, Self>> {
Some(self)
}
}

impl SingleThreadBase for Emu {
fn read_registers(
&mut self,
regs: &mut custom_arch::ArmCoreRegsCustom,
) -> TargetResult<(), Self> {
let mode = self.cpu.mode();
// if we selected a frame from a tracepoint, return registers from that snapshot
let cpu = self
.selected_frame
.and_then(|selected| self.traceframes.get(selected))
.map(|frame| frame.snapshot)
.unwrap_or_else(|| self.cpu);
let mode = cpu.mode();

for i in 0..13 {
regs.core.r[i] = self.cpu.reg_get(mode, i as u8);
regs.core.r[i] = cpu.reg_get(mode, i as u8);
}
regs.core.sp = self.cpu.reg_get(mode, reg::SP);
regs.core.lr = self.cpu.reg_get(mode, reg::LR);
regs.core.pc = self.cpu.reg_get(mode, reg::PC);
regs.core.cpsr = self.cpu.reg_get(mode, reg::CPSR);
regs.core.sp = cpu.reg_get(mode, reg::SP);
regs.core.lr = cpu.reg_get(mode, reg::LR);
regs.core.pc = cpu.reg_get(mode, reg::PC);
regs.core.cpsr = cpu.reg_get(mode, reg::CPSR);

regs.custom = self.custom_reg;

Ok(())
}

fn write_registers(&mut self, regs: &custom_arch::ArmCoreRegsCustom) -> TargetResult<(), Self> {
if self.selected_frame.is_some() {
// we can't modify registers in a tracepoint frame
return Err(TargetError::NonFatal);
}
let mode = self.cpu.mode();

for i in 0..13 {
Expand All @@ -208,6 +226,12 @@ impl SingleThreadBase for Emu {
}

fn read_addrs(&mut self, start_addr: u32, data: &mut [u8]) -> TargetResult<usize, Self> {
if self.selected_frame.is_some() {
// we only support register collection actions for our tracepoint frames.
// if we have a selected frame, then we don't have any memory we can
// return from the frame snapshot.
return Ok(0);
}
// this is a simple emulator, with RAM covering the entire 32 bit address space
for (addr, val) in (start_addr..).zip(data.iter_mut()) {
*val = self.mem.r8(addr)
Expand All @@ -216,6 +240,11 @@ impl SingleThreadBase for Emu {
}

fn write_addrs(&mut self, start_addr: u32, data: &[u8]) -> TargetResult<(), Self> {
if self.selected_frame.is_some() {
// we can't modify memory in a tracepoint frame
return Err(TargetError::NonFatal);
}

// this is a simple emulator, with RAM covering the entire 32 bit address space
for (addr, val) in (start_addr..).zip(data.iter().copied()) {
self.mem.w8(addr, val)
Expand Down
193 changes: 193 additions & 0 deletions examples/armv4t/gdb/tracepoints.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
use crate::emu::Emu;
use gdbstub::target;
use gdbstub::target::ext::tracepoints::DefineTracepoint;
use gdbstub::target::ext::tracepoints::ExperimentExplanation;
use gdbstub::target::ext::tracepoints::ExperimentStatus;
use gdbstub::target::ext::tracepoints::FrameDescription;
use gdbstub::target::ext::tracepoints::FrameRequest;
use gdbstub::target::ext::tracepoints::NewTracepoint;
use gdbstub::target::ext::tracepoints::TraceBufferConfig;
use gdbstub::target::ext::tracepoints::Tracepoint;
use gdbstub::target::ext::tracepoints::TracepointAction;
use gdbstub::target::ext::tracepoints::TracepointItem;
use gdbstub::target::ext::tracepoints::TracepointStatus;
use gdbstub::target::TargetError;
use gdbstub::target::TargetResult;

use armv4t_emu::Cpu;
#[derive(Debug)]
pub struct TraceFrame {
pub number: Tracepoint,
pub addr: u32,
pub snapshot: Cpu,
}

impl target::ext::tracepoints::Tracepoints for Emu {
fn tracepoints_init(&mut self) -> TargetResult<(), Self> {
self.tracepoints.clear();
self.traceframes.clear();
Ok(())
}

fn tracepoint_create(&mut self, tp: NewTracepoint<u32>) -> TargetResult<(), Self> {
self.tracepoints
.insert(tp.number, vec![TracepointItem::New(tp)]);
Ok(())
}

fn tracepoint_define(&mut self, tp: DefineTracepoint<'_, u32>) -> TargetResult<(), Self> {
let tp_copy = tp.get_owned();
let mut valid = true;
let _more = tp
.actions(|action| {
if let TracepointAction::Registers { mask: _ } = action {
// we only handle register collection actions for the simple
// case
} else {
valid = false;
}
})
.map_err(|_e| TargetError::Fatal("unable to parse actions"))?;
if !valid {
return Err(TargetError::NonFatal);
}
self.tracepoints
.get_mut(&tp_copy.number)
.map(move |existing| {
existing.push(TracepointItem::Define(tp_copy));
()
})
.ok_or_else(move || TargetError::Fatal("define on non-existing tracepoint"))
}

fn tracepoint_status(
&self,
tp: Tracepoint,
_addr: u32,
) -> TargetResult<TracepointStatus, Self> {
// We don't collect "real" trace buffer frames, so just report hit count
// and say the number of bytes is always 0.
// Because we don't implement "while-stepping" actions, we don't need to
// also check that `addr` matches.
Ok(TracepointStatus {
hit_count: self
.traceframes
.iter()
.filter(|frame| frame.number.0 == tp.0)
.count() as u64,
bytes_used: 0,
})
}

fn tracepoint_enumerate_start(
&mut self,
f: &mut dyn FnMut(TracepointItem<'_, u32>),
) -> TargetResult<(), Self> {
let tracepoints: Vec<_> = self
.tracepoints
.iter()
.flat_map(|(_key, value)| value.iter().map(|item| item.get_owned()))
.collect();
self.tracepoint_enumerate_machine = (tracepoints, 0);

self.tracepoint_enumerate_step(f)
}

fn tracepoint_enumerate_step<'a>(
&'a mut self,
f: &mut dyn FnMut(TracepointItem<'_, u32>),
) -> TargetResult<(), Self> {
let (tracepoints, index) = &mut self.tracepoint_enumerate_machine;
if let Some(item) = tracepoints.iter().nth(*index) {
*index += 1;
f(item.get_owned())
}

Ok(())
}

fn trace_buffer_configure(&mut self, _config: TraceBufferConfig) -> TargetResult<(), Self> {
// we don't collect a "real" trace buffer, so just ignore configuration
// attempts.
Ok(())
}

fn trace_buffer_request(
&mut self,
_offset: u64,
_len: usize,
_buf: &mut [u8],
) -> TargetResult<Option<usize>, Self> {
// We don't have a "real" trace buffer, so fail all raw read requests.
Ok(None)
}

fn trace_experiment_status(&self) -> TargetResult<ExperimentStatus<'_>, Self> {
// For a bare-bones example, we don't provide in-depth status explanations.
Ok(if self.tracing {
ExperimentStatus::Running
} else {
ExperimentStatus::NotRunning
})
}

fn trace_experiment_info(
&self,
report: &mut dyn FnMut(ExperimentExplanation<'_>),
) -> TargetResult<(), Self> {
(report)(ExperimentExplanation::Frames(self.traceframes.len()));

Ok(())
}

fn select_frame(
&mut self,
frame: FrameRequest<u32>,
report: &mut dyn FnMut(FrameDescription),
) -> TargetResult<(), Self> {
// For a bare-bones example, we only support `tfind <number>` and `tfind
// tracepoint <tpnum>` style frame selection and not the more
// complicated ones.
let found = match frame {
FrameRequest::Select(n) => self
.traceframes
.iter()
.nth(n as usize)
.map(|frame| (n, frame)),
FrameRequest::Hit(tp) => {
let start = self
.selected_frame
.map(|selected| selected + 1)
.unwrap_or(0);
self.traceframes.get(start..).and_then(|frames| {
frames
.iter()
.enumerate()
.filter(|(_n, frame)| frame.number == tp)
.map(|(n, frame)| ((start + n) as u64, frame))
.next()
})
}
_ => return Err(TargetError::NonFatal),
};
if let Some((n, frame)) = found {
(report)(FrameDescription::FrameNumber(Some(n)));
(report)(FrameDescription::Hit(frame.number));
self.selected_frame = Some(n as usize);
} else {
(report)(FrameDescription::FrameNumber(None));
self.selected_frame = None;
}
Ok(())
}

fn trace_experiment_start(&mut self) -> TargetResult<(), Self> {
self.tracing = true;
Ok(())
}

fn trace_experiment_stop(&mut self) -> TargetResult<(), Self> {
self.tracing = false;
Ok(())
}
}
21 changes: 21 additions & 0 deletions src/protocol/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -336,4 +336,25 @@ commands! {
libraries_svr4 use 'a {
"qXfer:libraries-svr4:read" => _qXfer_libraries_svr4_read::qXferLibrariesSvr4Read<'a>,
}

tracepoints use 'a {
"QTDP" => _QTDP::QTDP<'a>,
"QTinit" => _QTinit::QTinit,
"QTBuffer" => _QTBuffer::QTBuffer,
"QTStart" => _QTStart::QTStart,
"QTStop" => _QTStop::QTStop,
"QTFrame" => _QTFrame::QTFrame<'a>,

"qTBuffer" => _qTBuffer::qTBuffer<'a>,
"qTStatus" => _qTStatus::qTStatus,
"qTP" => _qTP::qTP<'a>,
"qTfP" => _qTfP::qTfP,
"qTsP" => _qTsP::qTsP,

// These are currently stubbed out to no-ops for tracepoints v1: they're
// needed to suppress "not implemented" errors.
// QTDV is unimplemented.
"qTfV" => _qTfV::qTfV,
"qTsV" => _qTsV::qTsV,
}
}
Loading