use std::net::{TcpListener, TcpStream};
use gdbstub::common::Signal;
use gdbstub::conn::ConnectionExt;
use gdbstub::stub::{state_machine, GdbStub, SingleThreadStopReason};
use gdbstub::arch::lldb::{Encoding, Format, Generic, Register};
use gdbstub::arch::RegId;
use gdbstub::target;
use gdbstub::target::{Target, TargetError, TargetResult};
use core::convert::TryInto;
use bpf_arch::reg::id::BpfRegId;
use bpf_arch::reg::BpfRegs;
use bpf_arch::Bpf;
use gdbstub::target::ext::base::singlethread::{SingleThreadBase, SingleThreadResume};
use gdbstub::target::ext::lldb_register_info_override::{Callback, CallbackToken};
use gdbstub::target::ext::section_offsets::Offsets;
use crate::{
ebpf,
error::EbpfError,
interpreter::{DebugState, Interpreter},
memory_region::AccessType,
verifier::Verifier,
vm::{ContextObject, ProgramResult},
};
type DynResult<T> = Result<T, Box<dyn std::error::Error>>;
fn wait_for_tcp(port: u16) -> DynResult<TcpStream> {
let sockaddr = format!("127.0.0.1:{}", port);
eprintln!("Waiting for a Debugger connection on {:?}...", sockaddr);
let sock = TcpListener::bind(sockaddr)?;
let (stream, addr) = sock.accept()?;
eprintln!("Debugger connected from {}", addr);
Ok(stream)
}
pub fn execute<V: Verifier, C: ContextObject>(interpreter: &mut Interpreter<V, C>, port: u16) {
let connection: Box<dyn ConnectionExt<Error = std::io::Error>> =
Box::new(wait_for_tcp(port).expect("Cannot connect to Debugger"));
let mut dbg = GdbStub::new(connection)
.run_state_machine(interpreter)
.expect("Cannot start debugging state machine");
loop {
dbg = match dbg {
state_machine::GdbStubStateMachine::Idle(mut dbg_inner) => {
let byte = dbg_inner.borrow_conn().read().unwrap();
dbg_inner.incoming_data(interpreter, byte).unwrap()
}
state_machine::GdbStubStateMachine::Disconnected(_dbg_inner) => {
eprintln!("Client disconnected");
break;
}
state_machine::GdbStubStateMachine::CtrlCInterrupt(dbg_inner) => dbg_inner
.interrupt_handled(
interpreter,
Some(SingleThreadStopReason::Signal(Signal::SIGINT)),
)
.unwrap(),
state_machine::GdbStubStateMachine::Running(mut dbg_inner) => {
let conn = dbg_inner.borrow_conn();
match interpreter.debug_state {
DebugState::Step => {
let mut stop_reason = if interpreter.step() {
SingleThreadStopReason::DoneStep
} else if let ProgramResult::Ok(result) = &interpreter.vm.env.program_result
{
SingleThreadStopReason::Exited(*result as u8)
} else {
SingleThreadStopReason::Terminated(Signal::SIGSTOP)
};
if interpreter.breakpoints.contains(&interpreter.get_dbg_pc()) {
stop_reason = SingleThreadStopReason::SwBreak(());
}
dbg_inner.report_stop(interpreter, stop_reason).unwrap()
}
DebugState::Continue => loop {
if conn.peek().unwrap().is_some() {
let byte = dbg_inner.borrow_conn().read().unwrap();
break dbg_inner.incoming_data(interpreter, byte).unwrap();
}
if interpreter.step() {
if interpreter.breakpoints.contains(&interpreter.get_dbg_pc()) {
break dbg_inner
.report_stop(interpreter, SingleThreadStopReason::SwBreak(()))
.unwrap();
}
} else if let ProgramResult::Ok(result) = &interpreter.vm.env.program_result
{
break dbg_inner
.report_stop(
interpreter,
SingleThreadStopReason::Exited(*result as u8),
)
.unwrap();
} else {
break dbg_inner
.report_stop(
interpreter,
SingleThreadStopReason::Terminated(Signal::SIGSTOP),
)
.unwrap();
}
},
}
}
};
}
}
impl<'a, 'b, V: Verifier, C: ContextObject> Target for Interpreter<'a, 'b, V, C> {
type Arch = Bpf;
type Error = &'static str;
#[inline(always)]
fn base_ops(&mut self) -> target::ext::base::BaseOps<'_, Self::Arch, Self::Error> {
target::ext::base::BaseOps::SingleThread(self)
}
#[inline(always)]
fn support_breakpoints(
&mut self,
) -> Option<target::ext::breakpoints::BreakpointsOps<'_, Self>> {
Some(self)
}
#[inline(always)]
fn support_section_offsets(
&mut self,
) -> Option<target::ext::section_offsets::SectionOffsetsOps<'_, Self>> {
Some(self)
}
#[inline(always)]
fn support_lldb_register_info_override(
&mut self,
) -> Option<target::ext::lldb_register_info_override::LldbRegisterInfoOverrideOps<'_, Self>>
{
Some(self)
}
}
fn get_host_ptr<V: Verifier, C: ContextObject>(
interpreter: &mut Interpreter<V, C>,
mut vm_addr: u64,
pc: usize,
) -> Result<*mut u8, EbpfError> {
if vm_addr < ebpf::MM_PROGRAM_START {
vm_addr += ebpf::MM_PROGRAM_START;
}
match interpreter.vm.env.memory_mapping.map(
AccessType::Load,
vm_addr,
std::mem::size_of::<u8>() as u64,
pc + ebpf::ELF_INSN_DUMP_OFFSET,
) {
ProgramResult::Ok(host_addr) => Ok(host_addr as *mut u8),
ProgramResult::Err(err) => Err(err),
}
}
impl<'a, 'b, V: Verifier, C: ContextObject> SingleThreadBase for Interpreter<'a, 'b, V, C> {
fn read_registers(&mut self, regs: &mut BpfRegs) -> TargetResult<(), Self> {
for i in 0..10 {
regs.r[i] = self.reg[i];
}
regs.sp = self.reg[ebpf::FRAME_PTR_REG];
regs.pc = self.get_dbg_pc();
Ok(())
}
fn write_registers(&mut self, regs: &BpfRegs) -> TargetResult<(), Self> {
for i in 0..10 {
self.reg[i] = regs.r[i];
}
self.reg[ebpf::FRAME_PTR_REG] = regs.sp;
self.pc = regs.pc as usize;
Ok(())
}
#[inline(always)]
fn support_single_register_access(
&mut self,
) -> Option<target::ext::base::single_register_access::SingleRegisterAccessOps<'_, (), Self>>
{
Some(self)
}
fn read_addrs(&mut self, start_addr: u64, data: &mut [u8]) -> TargetResult<(), Self> {
for (vm_addr, val) in (start_addr..).zip(data.iter_mut()) {
let host_ptr = match get_host_ptr(self, vm_addr, self.pc) {
Ok(host_ptr) => host_ptr,
_ => continue,
};
*val = unsafe { *host_ptr as u8 };
}
Ok(())
}
fn write_addrs(&mut self, start_addr: u64, data: &[u8]) -> TargetResult<(), Self> {
for (_addr, _val) in (start_addr..).zip(data.iter().copied()) {
eprintln!("Memory write not supported");
}
Ok(())
}
#[inline(always)]
fn support_resume(
&mut self,
) -> Option<target::ext::base::singlethread::SingleThreadResumeOps<'_, Self>> {
Some(self)
}
}
impl<'a, 'b, V: Verifier, C: ContextObject>
target::ext::base::single_register_access::SingleRegisterAccess<()>
for Interpreter<'a, 'b, V, C>
{
fn read_register(
&mut self,
_tid: (),
reg_id: BpfRegId,
buf: &mut [u8],
) -> TargetResult<usize, Self> {
match reg_id {
BpfRegId::Gpr(i) => {
let r = self.reg[i as usize];
buf.copy_from_slice(&r.to_le_bytes());
}
BpfRegId::Sp => buf.copy_from_slice(&self.reg[ebpf::FRAME_PTR_REG].to_le_bytes()),
BpfRegId::Pc => buf.copy_from_slice(&self.get_dbg_pc().to_le_bytes()),
BpfRegId::InstructionCountRemaining => buf.copy_from_slice(
&self
.vm
.env
.context_object_pointer
.get_remaining()
.to_le_bytes(),
),
}
Ok(buf.len())
}
fn write_register(&mut self, _tid: (), reg_id: BpfRegId, val: &[u8]) -> TargetResult<(), Self> {
let r = u64::from_le_bytes(
val.try_into()
.map_err(|_| TargetError::Fatal("invalid data"))?,
);
match reg_id {
BpfRegId::Gpr(i) => self.reg[i as usize] = r,
BpfRegId::Sp => self.reg[ebpf::FRAME_PTR_REG] = r,
BpfRegId::Pc => self.pc = r as usize,
BpfRegId::InstructionCountRemaining => (),
}
Ok(())
}
}
impl<'a, 'b, V: Verifier, C: ContextObject> SingleThreadResume for Interpreter<'a, 'b, V, C> {
fn resume(&mut self, signal: Option<Signal>) -> Result<(), Self::Error> {
if signal.is_some() {
return Err("no support for continuing with signal");
}
self.debug_state = DebugState::Continue;
Ok(())
}
#[inline(always)]
fn support_single_step(
&mut self,
) -> Option<target::ext::base::singlethread::SingleThreadSingleStepOps<'_, Self>> {
Some(self)
}
}
impl<'a, 'b, V: Verifier, C: ContextObject> target::ext::base::singlethread::SingleThreadSingleStep
for Interpreter<'a, 'b, V, C>
{
fn step(&mut self, signal: Option<Signal>) -> Result<(), Self::Error> {
if signal.is_some() {
return Err("no support for stepping with signal");
}
self.debug_state = DebugState::Step;
Ok(())
}
}
impl<'a, 'b, V: Verifier, C: ContextObject> target::ext::section_offsets::SectionOffsets
for Interpreter<'a, 'b, V, C>
{
fn get_section_offsets(&mut self) -> Result<Offsets<u64>, Self::Error> {
Ok(Offsets::Sections {
text: 0,
data: 0,
bss: None,
})
}
}
impl<'a, 'b, V: Verifier, C: ContextObject> target::ext::breakpoints::Breakpoints
for Interpreter<'a, 'b, V, C>
{
#[inline(always)]
fn support_sw_breakpoint(
&mut self,
) -> Option<target::ext::breakpoints::SwBreakpointOps<'_, Self>> {
Some(self)
}
}
impl<'a, 'b, V: Verifier, C: ContextObject> target::ext::breakpoints::SwBreakpoint
for Interpreter<'a, 'b, V, C>
{
fn add_sw_breakpoint(
&mut self,
addr: u64,
_kind: bpf_arch::BpfBreakpointKind,
) -> TargetResult<bool, Self> {
self.breakpoints.push(addr);
Ok(true)
}
fn remove_sw_breakpoint(
&mut self,
addr: u64,
_kind: bpf_arch::BpfBreakpointKind,
) -> TargetResult<bool, Self> {
match self.breakpoints.iter().position(|x| *x == addr) {
None => return Ok(false),
Some(pos) => self.breakpoints.remove(pos),
};
Ok(true)
}
}
impl<'a, 'b, V: Verifier, C: ContextObject>
target::ext::lldb_register_info_override::LldbRegisterInfoOverride
for Interpreter<'a, 'b, V, C>
{
fn lldb_register_info<'c>(
&mut self,
reg_id: usize,
reg_info: Callback<'c>,
) -> Result<CallbackToken<'c>, Self::Error> {
match BpfRegId::from_raw_id(reg_id) {
Some((_, None)) | None => Ok(reg_info.done()),
Some((r, Some(size))) => {
let name: String = match r {
BpfRegId::Gpr(i) => match i {
0 => "r0",
1 => "r1",
2 => "r2",
3 => "r3",
4 => "r4",
5 => "r5",
6 => "r6",
7 => "r7",
8 => "r8",
9 => "r9",
_ => "unknown",
},
BpfRegId::Sp => "sp",
BpfRegId::Pc => "pc",
BpfRegId::InstructionCountRemaining => "remaining",
}
.into();
let set = String::from("General Purpose Registers");
let generic = match r {
BpfRegId::Sp => Some(Generic::Sp),
BpfRegId::Pc => Some(Generic::Pc),
_ => None,
};
let reg = Register {
name: &name,
alt_name: None,
bitsize: (usize::from(size)) * 8,
offset: reg_id * (usize::from(size)),
encoding: Encoding::Uint,
format: Format::Hex,
set: &set,
gcc: None,
dwarf: Some(reg_id),
generic,
container_regs: None,
invalidate_regs: None,
};
Ok(reg_info.write(reg))
}
}
}
}
mod bpf_arch {
use gdbstub::arch::{Arch, SingleStepGdbBehavior};
#[derive(Debug)]
pub enum BpfBreakpointKind {
BpfBpKindBrkpt,
}
impl gdbstub::arch::BreakpointKind for BpfBreakpointKind {
fn from_usize(kind: usize) -> Option<Self> {
let kind = match kind {
0 => BpfBreakpointKind::BpfBpKindBrkpt,
_ => return None,
};
Some(kind)
}
}
pub enum Bpf {}
#[allow(deprecated)]
impl Arch for Bpf {
type Usize = u64;
type Registers = reg::BpfRegs;
type RegId = reg::id::BpfRegId;
type BreakpointKind = BpfBreakpointKind;
#[inline(always)]
fn single_step_gdb_behavior() -> SingleStepGdbBehavior {
SingleStepGdbBehavior::Required
}
}
pub mod reg {
pub use bpf::BpfRegs;
mod bpf {
use core::convert::TryInto;
use gdbstub::arch::Registers;
#[derive(Debug, Default, Clone, Eq, PartialEq)]
pub struct BpfRegs {
pub r: [u64; 10],
pub sp: u64,
pub pc: u64,
}
impl Registers for BpfRegs {
type ProgramCounter = u64;
fn pc(&self) -> Self::ProgramCounter {
self.pc
}
fn gdb_serialize(&self, mut write_byte: impl FnMut(Option<u8>)) {
macro_rules! write_bytes {
($bytes:expr) => {
for b in $bytes {
write_byte(Some(*b))
}
};
}
for reg in self.r.iter() {
write_bytes!(®.to_le_bytes());
}
write_bytes!(&self.sp.to_le_bytes());
write_bytes!(&self.pc.to_le_bytes());
}
fn gdb_deserialize(&mut self, mut bytes: &[u8]) -> Result<(), ()> {
if bytes.len() < (12 * 8) {
return Err(());
}
let mut next_reg = || {
if bytes.len() < 8 {
Err(())
} else {
let (next, rest) = bytes.split_at(8);
bytes = rest;
Ok(u64::from_le_bytes(next.try_into().unwrap()))
}
};
for reg in self.r.iter_mut() {
*reg = next_reg()?
}
self.sp = next_reg()?;
self.pc = next_reg()?;
if next_reg().is_ok() {
return Err(());
}
Ok(())
}
}
}
pub mod id {
use core::num::NonZeroUsize;
use gdbstub::arch::RegId;
#[derive(Debug, Clone, Copy)]
#[non_exhaustive]
pub enum BpfRegId {
Gpr(u8),
Sp,
Pc,
InstructionCountRemaining,
}
impl RegId for BpfRegId {
fn from_raw_id(id: usize) -> Option<(BpfRegId, Option<NonZeroUsize>)> {
let reg = match id {
0..=9 => {
return Some((BpfRegId::Gpr(id as u8), Some(NonZeroUsize::new(8)?)))
}
10 => BpfRegId::Sp,
11 => BpfRegId::Pc,
12 => BpfRegId::InstructionCountRemaining,
_ => return None,
};
Some((reg, Some(NonZeroUsize::new(8)?)))
}
}
}
}
}