Enabling `ConsoleOutput` in more places
At the moment, gdbstub only exposes ConsoleOutput in one place: as part of the handle_monitor_cmd interface. That said, this functionality of "print text from the target to the client terminal" isn't some special feature of the monitor command interface, and is also supported as a bog-standard Stop Reply Packet (namely, the O XX... packet).
Moreover, based on a very brief skim through the gdb client source code, it seems that the GDB client might be resilient enough to accept O packets at any time, not just as a response to qRcmd and as a stop reply packet! Note that this behavior is not formalized by the spec, but if it works, then it opens the door to some very cool functionality...
EDIT: of course, there's also the Host I/O packet interface, which enables writing to the host console via a standard write(1, buf, len) interface. Admittedly, this can only be used in the same context as regular O xx stop reply packets, so aside from the benefit of using less bandwidth (as it uses the binary data transfer protocol vs. the 2-char ascii per byte protocol), it's not that much better.
With these bits of info in mind, there are a few ways gdbstub could expand expose ConsoleOutput in more places:
1. Exposing ConsoleOutput as part of the current resume() interfaces.
This should be pretty straightforward, and could be implemented similar to the current monitor command ConsoleOutput (i.e: using a callback).
Note that this will require an API change to add the additional function parameter.
2. Exposing a "global" handle to send O XX... packets.
At the time of writing, I don't have a concrete idea of how to implement this (especially for no_std targets), but I was thinking the API could look something like this:
let connection: TcpStream = wait_for_gdb_connection(9001);
let mut debugger = GdbStub::new(connection);
// get an instance of the global `ConsoleOutput` handle
let console_out_handle = debugger.console_output_handle();
let mut target = MyTarget::new(console_out_handle)?;
target.set_console_out_handle(console_out_handle); // or maybe the target supports late-binding
match debugger.run(&mut target) { ... }
// ..
// later, in the target implementation...
// ..
impl MultiThreadOps for MyTarget {
fn read_registers(
&mut self,
regs: &mut gdbstub_arch::arm::reg::ArmCoreRegs,
tid: Tid,
) -> TargetResult<(), Self> {
outputln!(self.console_out_handle, "reading regs for tid {:?}", tid);
// ...
}
}
IMPORTANT NOTE: because the underlying GDB protocol doesn't support sending console output packets at totally arbitrary points during execution (e.g: in the middle of writing out some other, longer packet), calling output! will require buffering + differing output until such a time when it's reasonable to flush. In other words, the global output! interface will have to be non blocking. This is in contrast to the qRcmd or the resume implementations, which can immediately output the data.
Two final comments:
- I have a strong suspicion that this feature will have to be
std/alloconly, as my gut feeling is that it'll be incredibly tricky to get the lifetimes / locking right without the use ofArc. That said, this is just a gut feeling, and it very well might be the case that once someone takes a crack at an implementation, a more obvious solution will present itself. - If a
no_stdimplementation is possible, it may need to be feature-gated to avoid ballooning the binary size.
On a somewhat related note, I recently opened a issue on the GDB bugzilla regarding support for host console IO in non-stop mode:
https://sourceware.org/bugzilla/show_bug.cgi?id=27910
Obviously, gdbstub doesn't currently support non-stop mode, but if/when it does, it would be nice to avoid gating this console output feature.