Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
39 changes: 35 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 9 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,20 @@ rust-version = "1.93"
members = ["cli", "ffi", "xtask"]

[workspace.dependencies]
gdbstub = { version = "0.7.8", default-features = false }
gdbstub = { version = "0.7.9", default-features = false }
spin = { version = "0.10.0", default-features = false, features = [
"mutex",
"once",
"spin_mutex",
] }
vex-sdk = "0.28.0"
owo-colors = "4.2.3"

[features]
default = ["alloc"]
alloc = []
freertos = []
pros = ["freertos"]

[profile.release]
opt-level = "z"
Expand All @@ -46,6 +49,9 @@ gdbstub.workspace = true
spin.workspace = true
log = "0.4.29"
vex-sdk.workspace = true
cfg-if = "1.0.4"
bytemuck = { version = "1.25.0", features = ["derive"] }
owo-colors.workspace = true

[target."cfg(target_arch = \"arm\")".dependencies]
zynq7000 = "0.1.1"
Expand All @@ -60,6 +66,8 @@ cfg-if = "1.0.4"

[dev-dependencies]
anyhow = "1.0.100"
clang_log = "2.1.2"
colored = "3.1.1"

[dev-dependencies.vexide]
git = "https://github.com/vexide/vexide.git"
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# v5gdb: VEX V5 debugger

> *"One day we will have all the features of ROBOTC on the V5 brain" - Charles, 2114V*

v5gdb is a debugger backend for the VEX V5 platform that is compatible with GDB. It uses a combination of hardware and software breakpoints to implement features like line-by-line stepping and user-defined breakpoints. Users are also given the ability to read program state such as the arguments passed to the active function or global variables.

## Getting Started
Expand Down
62 changes: 51 additions & 11 deletions cli/src/bin/v5gdb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ use std::{process::exit, time::Duration};
use clap::Parser;
use cobs::CobsDecoderOwned;
use tokio::{
io::{AsyncReadExt, AsyncWriteExt, stderr},
net::{TcpListener, TcpStream},
io::{AsyncReadExt, AsyncWrite, AsyncWriteExt, stderr},
net::TcpListener,
process::Command,
time::sleep,
};
Expand All @@ -19,6 +19,8 @@ struct Args {
#[clap(long)]
tcp: Option<String>,
elf_files_to_debug: Vec<String>,
#[clap(long)]
debug_io: bool,
}

#[tokio::main]
Expand All @@ -34,8 +36,11 @@ async fn main() -> anyhow::Result<()> {

let addr = args.tcp.as_deref().unwrap_or("127.0.0.1:35537");
let server = TcpListener::bind(addr).await?;
let server_task =
tokio::spawn(async move { serve_device_serial(device, server).await.unwrap() });
let server_task = tokio::spawn(async move {
serve_device_serial(device, server, args.debug_io)
.await
.unwrap()
});

if args.tcp.is_some() {
server_task.await?;
Expand Down Expand Up @@ -74,13 +79,18 @@ async fn main() -> anyhow::Result<()> {
Ok(())
}

async fn serve_device_serial(device: SerialDevice, server: TcpListener) -> anyhow::Result<()> {
async fn serve_device_serial(
device: SerialDevice,
server: TcpListener,
debug_io: bool,
) -> anyhow::Result<()> {
let mut connection = device.connect(Duration::from_secs(5))?;

let mut program_output = [0; 2048];
let mut program_input = [0; 4096];

let mut decoder = CobsDecoderOwned::new(2048);
let mut stderr = stderr();

let (mut conn, _addr) = server.accept().await?;

Expand All @@ -97,12 +107,23 @@ async fn serve_device_serial(device: SerialDevice, server: TcpListener) -> anyho
match decoder.push(incoming_bytes) {
Ok(Some(report)) => {
let packet = &decoder.dest()[..report.frame_size()];
let was_valid = handle_packet(&mut conn, packet).await;
let was_valid = handle_packet(
&mut conn,
&mut stderr,
packet,
debug_io,
).await;

if !was_valid {
// If we receive an invalid packet, fall back to assuming it's
// just raw data and print it out.
stderr().write_all(&incoming_bytes[..report.parsed_size()]).await?;
let body = &incoming_bytes[..report.parsed_size()];

if debug_io {
println!("unknown: {:?}", String::from_utf8_lossy(body));
}

stderr.write_all(body).await?;
}

decoder.reset();
Expand All @@ -111,7 +132,11 @@ async fn serve_device_serial(device: SerialDevice, server: TcpListener) -> anyho
Err(_) => {
// We are only discarding one byte, so print that one.
let invalid_byte = incoming_bytes[0];
stderr().write_all(&[invalid_byte]).await?;
stderr.write_all(&[invalid_byte]).await?;

if debug_io {
println!("invalid: {:?}", String::from_utf8_lossy(&[invalid_byte]));
}

// Skip one byte and try to resynchronize
incoming_bytes = &incoming_bytes[1..];
Expand All @@ -126,6 +151,10 @@ async fn serve_device_serial(device: SerialDevice, server: TcpListener) -> anyho
match read {
Ok(0) => break,
Ok(size) => {
if debug_io {
println!("< {}", String::from_utf8_lossy(&program_input[..size]));
}

connection.write_user(&program_input[..size]).await.unwrap();
}
_ => {}
Expand All @@ -140,7 +169,12 @@ async fn serve_device_serial(device: SerialDevice, server: TcpListener) -> anyho
}

/// Handles a packet from the V5 device and returns whether it was valid.
async fn handle_packet(writer: &mut TcpStream, packet: &[u8]) -> bool {
async fn handle_packet(
debug_out: &mut (impl AsyncWrite + Unpin),
user_out: &mut (impl AsyncWrite + Unpin),
packet: &[u8],
debug_io: bool,
) -> bool {
let Some(channel_byte) = packet.first() else {
// Missing channel
return false;
Expand All @@ -150,10 +184,16 @@ async fn handle_packet(writer: &mut TcpStream, packet: &[u8]) -> bool {

match channel_byte {
b'u' => {
_ = stderr().write_all(body).await;
if debug_io {
print!("{}", String::from_utf8_lossy(body));
}
_ = user_out.write_all(body).await;
}
b'd' => {
_ = writer.write_all(body).await;
if debug_io {
println!("> {}", String::from_utf8_lossy(body));
}
_ = debug_out.write_all(body).await;
}
// Unknown channel
_ => return false,
Expand Down
16 changes: 11 additions & 5 deletions examples/basic.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::time::Duration;

use v5gdb::{debugger::V5Debugger, transport::StdioTransport};
use vexide::prelude::*;

Expand All @@ -19,12 +21,16 @@ fn fib(n: u64) -> u64 {

#[vexide::main]
async fn main(_peripherals: Peripherals) {
v5gdb::install(V5Debugger::new(StdioTransport));

println!("Hello, world");
colored::control::set_override(true);
clang_log::init(log::Level::max(), "v5gdb(basic)");

v5gdb::install(V5Debugger::new(StdioTransport));
v5gdb::breakpoint!();

let n = fib(40);
println!("{n}");
loop {
let num = 40;
let x = fib(num);
println!("{x}");
sleep(Duration::from_secs(1)).await;
}
}
2 changes: 2 additions & 0 deletions ffi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@ v5gdb = { path = "../", default-features = false }
gdbstub.workspace = true
spin.workspace = true
vex-sdk.workspace = true
log = "0.4.29"
owo-colors.workspace = true
19 changes: 3 additions & 16 deletions ffi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
use core::{
arch::global_asm,
ffi::{CStr, c_char, c_void},
fmt::Write,
panic::PanicInfo,
ptr,
};

Expand All @@ -17,8 +15,7 @@ use v5gdb::{
transport::{StdioTransport, TransportError},
};

use crate::panic::ErrorReport;

mod log;
mod panic;

/// A custom transport method for communicating with GDB.
Expand Down Expand Up @@ -126,6 +123,7 @@ impl ConnectionExt for TransportImpl {
/// Install the debugger, communicating with GDB over the V5's USB serial port.
#[unsafe(export_name = "v5gdb_install_stdio")]
pub extern "C" fn install_stdio() {
self::log::init();
static DEBUGGER: Once<V5Debugger<StdioTransport>> = Once::new();
DEBUGGER.call_once(|| V5Debugger::new(StdioTransport));
v5gdb::install_by_ref(DEBUGGER.get().unwrap());
Expand All @@ -134,6 +132,7 @@ pub extern "C" fn install_stdio() {
/// Install the debugger with a custom transport method for communicating with GDB.
#[unsafe(export_name = "v5gdb_install_custom")]
pub extern "C" fn install_custom(transport: TransportImpl) {
self::log::init();
static DEBUGGER: Once<V5Debugger<TransportImpl>> = Once::new();
DEBUGGER.call_once(|| V5Debugger::new(transport));
v5gdb::install_by_ref(DEBUGGER.get().unwrap());
Expand All @@ -145,18 +144,6 @@ pub extern "C" fn breakpoint() {
v5gdb::breakpoint!();
}

#[panic_handler]
fn panic_handler(panic: &PanicInfo) -> ! {
let mut report = ErrorReport::begin();
_ = writeln!(report, "v5gdb {panic}");

loop {
unsafe {
vex_sdk::vexTasksRun();
}
}
}

// In the VEX partner SDK, vexTasksRun is renamed to vexBackgroundProcessing.
// We add a weak alias for vexBackgroundProcessing as vexTasksRun in case we're in that environment.
global_asm!(
Expand Down
Loading