error: support core::error::Error types
Description
Add support for reporting errors that conform to core::error::Error. This allows for calling e.source(), which may provide more information. For example, probe-rs uses error sources to create chains of errors, and without this errors are very terse.
Before:
During the execution of GDB an error was encountered:
Target threw a fatal error: A RISC-V specific error occurred.
(No further information is available)
After:
During the execution of GDB an error was encountered:
Target threw a fatal error: A RISC-V specific error occurred.
... caused by: Error occurred during execution of an abstract command: Exception
This is gated by "std", so should not pose any API changes to deeply embedded targets.
API Stability
- [x] This PR does not require a breaking API change
Checklist
- Documentation
- [x] Ensured any public-facing
rustdocformatting looks good (viacargo doc) - [ ] (if appropriate) Added feature to "Debugging Features" in README.md
- [x] Ensured any public-facing
- Validation
- [x] Included output of running
examples/armv4twithRUST_LOG=trace+ any relevant GDB output under the "Validation" section below - [x] Included output of running
./example_no_std/check_size.shbefore/after changes under the "Validation" section below
- [x] Included output of running
- If implementing a new protocol extension IDET
- [ ] Included a basic sample implementation in
examples/armv4t - [ ] IDET can be optimized out (confirmed via
./example_no_std/check_size.sh) - [ ] OR implementation requires introducing non-optional binary bloat (please elaborate under "Description")
- [ ] Included a basic sample implementation in
- If upstreaming an
Archimplementation- [ ] I have tested this code in my project, and to the best of my knowledge, it is working as intended.
Validation
GDB output
(gdb) info mem
Using memory regions provided by the target.
Num Enb Low Addr High Addr Attrs
0 y 0x08000000 0x08010000 flash blocksize 0x4000 nocache
1 y 0x08010000 0x08020000 flash blocksize 0x10000 nocache
2 y 0x08020000 0x08080000 flash blocksize 0x20000 nocache
3 y 0x20000000 0x20020000 rw nocache
(gdb)
armv4t output
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.01s
Running `target/debug/examples/armv4t`
loading section ".text" into memory from [0x55550000..0x55550078]
Setting PC to 0x55550000
Waiting for a GDB connection on "127.0.0.1:9001"...
Debugger connected from 127.0.0.1:51333
TRACE gdbstub::protocol::recv_packet > <-- +
TRACE gdbstub::protocol::recv_packet > <-- $qSupported:multiprocess+;swbreak+;hwbreak+;qRelocInsn+;fork-events+;vfork-events+;exec-events+;vContSupported+;QThreadEvents+;QThreadOptions+;no-resumed+;memory-tagging+;xmlRegisters=i386;error-message+#14
TRACE gdbstub::protocol::response_writer > --> $PacketSize=1000;vContSupported+;multiprocess+;QStartNoAckMode+;ReverseContinue+;ReverseStep+;QDisableRandomization+;QEnvironmentHexEncoded+;QEnvironmentUnset+;QEnvironmentReset+;QStartupWithShell+;QSetWorkingDir+;swbreak+;hwbreak+;QTBuffer:size+;TracepointSource+;QCatchSyscalls+;qXfer:features:read+;qXfer:memory-map:read+;qXfer:exec-file:read+;qXfer:auxv:read+;qXfer:libraries-svr4:read+#39
TRACE gdbstub::protocol::recv_packet > <-- +
TRACE gdbstub::protocol::recv_packet > <-- $vCont?#49
TRACE gdbstub::protocol::response_writer > --> $vCont;c;C;s;S;r#0f
TRACE gdbstub::protocol::recv_packet > <-- +
TRACE gdbstub::protocol::recv_packet > <-- $vMustReplyEmpty#3a
INFO gdbstub::stub::core_impl > Unknown command: Ok("vMustReplyEmpty")
TRACE gdbstub::protocol::response_writer > --> $#00
TRACE gdbstub::protocol::recv_packet > <-- +
TRACE gdbstub::protocol::recv_packet > <-- $QStartNoAckMode#b0
TRACE gdbstub::protocol::response_writer > --> $OK#9a
TRACE gdbstub::protocol::recv_packet > <-- +
TRACE gdbstub::protocol::recv_packet > <-- $!#21
TRACE gdbstub::protocol::response_writer > --> $OK#9a
TRACE gdbstub::protocol::recv_packet > <-- $Hgp0.0#ad
TRACE gdbstub::protocol::response_writer > --> $OK#9a
TRACE gdbstub::protocol::recv_packet > <-- $qXfer:features:read:target.xml:0,ffb#79
TRACE gdbstub::protocol::response_writer > --> $m<?xml version="1.0"?>
<!DOCTYPE target SYSTEM "gdb-target.dtd">
<target version="1.0">
<architecture>armv4t</architecture>
<feature name="org.gnu.gdb.arm.core">
<vector id="padding" type="uint32" count="25"/>
<reg name="r0" bitsize="32" type="uint32"/>
<reg name="r1" bitsize="32" type="uint32"/>
<reg name="r2" bitsize="32" type="uint32"/>
<reg name="r3" bitsize="32" type="uint32"/>
<reg name="r4" bitsize="32" type="uint32"/>
<reg name="r5" bitsize="32" type="uint32"/>
<reg name="r6" bitsize="32" type="uint32"/>
<reg name="r7" bitsize="32" type="uint32"/>
<reg name="r8" bitsize="32" type="uint32"/>
<reg name="r9" bitsize="32" type="uint32"/>
<reg name="r10" bitsize="32" type="uint32"/>
<reg name="r11" bitsize="32" type="uint32"/>
<reg name="r12" bitsize="32" type="uint32"/>
<reg name="sp" bitsize="32" type="data_ptr"/>
<reg name="lr" bitsize="32"/>
<reg name="pc" bitsize="32" type="code_ptr"/>
<!--
For some reason, my version of `gdb-multiarch` doesn't seem to
respect "regnum", and will not parse this custom target.xml unless I
manually include the padding bytes in the target description.
On the bright side, AFAIK, there aren't all that many architectures
that use padding bytes. Heck, the only reason armv4t uses padding is
for historical reasons (see comment below).
Odds are if you're defining your own custom arch, you won't run into
this issue, since you can just lay out all the registers in the
correct order.
-->
<reg name="padding" type="padding" bitsize="32"/>
<!-- The CPSR is register 25, rather than register 16, because
the FPA registers historically were placed between the PC
and the CPSR in the "g" packet. -->
<reg name="cpsr" bitsize="32" regnum="25"/>
</feature>
<xi:include href="extra.xml"/>
</target>#38
TRACE gdbstub::protocol::recv_packet > <-- $qXfer:features:read:target.xml:80d,ffb#15
TRACE gdbstub::protocol::response_writer > --> $l#6c
TRACE gdbstub::protocol::recv_packet > <-- $qXfer:features:read:extra.xml:0,ffb#16
TRACE gdbstub::protocol::response_writer > --> $m<?xml version="1.0"?>
<!DOCTYPE target SYSTEM "gdb-target.dtd">
<feature name="custom-armv4t-extension">
<!--
maps to a simple scratch register within the emulator. the GDB
client can read the register using `p }custom` and set it using
`set }custom=1337`
-->
<reg name="custom" bitsize="32" type="uint32"/>
<!--
pseudo-register that return the current time when read.
notably, i've set up the target to NOT send this register as part of
the regular register list, which means that GDB will fetch/update
this register via the 'p' and 'P' packets respectively
-->
<reg name="time" bitsize="32" type="uint32"/>
<!--
pseudo-register that is always unavailable.
it is supposed to be reported as 'x'-ed bytes in replies to 'p' packets
and shown by the GDB client as "<unavailable>".
-->
<reg name="unavailable" bitsize="32" type="uint32"/>
</feature>#7d
TRACE gdbstub::protocol::recv_packet > <-- $qXfer:features:read:extra.xml:3c5,ffb#b1
TRACE gdbstub::protocol::response_writer > --> $l#6c
TRACE gdbstub::protocol::recv_packet > <-- $qTStatus#49
TRACE gdbstub::protocol::response_writer > --> $T0;tframes:00#4b
TRACE gdbstub::protocol::recv_packet > <-- $qTfV#81
TRACE gdbstub::protocol::response_writer > --> $#00
TRACE gdbstub::protocol::recv_packet > <-- $?#3f
TRACE gdbstub::protocol::response_writer > --> $T05thread:p01.01;#06
TRACE gdbstub::protocol::recv_packet > <-- $qfThreadInfo#bb
TRACE gdbstub::protocol::response_writer > --> $mp01.01#cd
TRACE gdbstub::protocol::recv_packet > <-- $qsThreadInfo#c8
TRACE gdbstub::protocol::response_writer > --> $l#6c
TRACE gdbstub::protocol::recv_packet > <-- $qAttached:1#fa
GDB queried if it was attached to a process with PID 1
TRACE gdbstub::protocol::response_writer > --> $1#31
TRACE gdbstub::protocol::recv_packet > <-- $qXfer:exec-file:read:1:0,ffb#b7
TRACE gdbstub::protocol::response_writer > --> $m/test.elf#c1
TRACE gdbstub::protocol::recv_packet > <-- $qXfer:exec-file:read:1:9,ffb#c0
TRACE gdbstub::protocol::response_writer > --> $l#6c
TRACE gdbstub::protocol::recv_packet > <-- $Hc-1#09
TRACE gdbstub::protocol::response_writer > --> $OK#9a
TRACE gdbstub::protocol::recv_packet > <-- $qOffsets#4b
TRACE gdbstub::protocol::response_writer > --> $Text=00;Data=00;Bss=00#94
TRACE gdbstub::protocol::recv_packet > <-- $g#67
TRACE gdbstub::protocol::response_writer > --> $00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000107856341200005555xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx1000000078563412#0a
TRACE gdbstub::protocol::recv_packet > <-- $qfThreadInfo#bb
TRACE gdbstub::protocol::response_writer > --> $mp01.01#cd
TRACE gdbstub::protocol::recv_packet > <-- $qsThreadInfo#c8
TRACE gdbstub::protocol::response_writer > --> $l#6c
TRACE gdbstub::protocol::recv_packet > <-- $qXfer:memory-map:read::0,ffb#18
TRACE gdbstub::protocol::response_writer > --> $m<?xml version="1.0"?>
<!DOCTYPE memory-map
PUBLIC "+//IDN gnu.org//DTD GDB Memory Map V1.0//EN"
"http://sourceware.org/gdb/gdb-memory-map.dtd">
<memory-map>
<memory type="ram" start="0x20000000" length="0x20000"/>
<memory type="flash" start="0x08000000" length="0x10000">
<property name="blocksize">0x4000</property>
</memory>
<memory type="flash" start="0x08010000" length="0x10000">
<property name="blocksize">0x10000</property>
</memory>
<memory type="flash" start="0x08020000" length="0x60000">
<property name="blocksize">0x20000</property>
</memory>
</memory-map>#da
TRACE gdbstub::protocol::recv_packet > <-- $qXfer:memory-map:read::27c,ffb#b4
TRACE gdbstub::protocol::response_writer > --> $l#6c
TRACE gdbstub::protocol::recv_packet > <-- $qSymbol::#5b
INFO gdbstub::stub::core_impl > Unknown command: Ok("qSymbol::")
TRACE gdbstub::protocol::response_writer > --> $#00
TRACE gdbstub::protocol::recv_packet > <-- $qTStatus#49
TRACE gdbstub::protocol::response_writer > --> $T0;tframes:00#4b
TRACE gdbstub::protocol::recv_packet > <-- $qTfP#7b
TRACE gdbstub::protocol::response_writer > --> $#00
Before/After `./example_no_std/check_size.sh` output
Before
Finished `release` profile [optimized + debuginfo] target(s) in 0.00s
Finished `release` profile [optimized + debuginfo] target(s) in 0.00s
Analyzing target/release/gdbstub-nostd
File .text Size Crate Name
24.1% 125.7% 17.2KiB gdbstub_arch <gdbstub_arch::arm::reg::arm_core::ArmCoreRegs as gdbstub::arch::Registers>::gdb_deserialize
24.0% 125.3% 17.2KiB [Unknown] __mh_execute_header
12.2% 63.7% 8.7KiB [Unknown] _main
1.0% 5.4% 764B gdbstub gdbstub::stub::state_machine::GdbStubStateMachineInner<gdbstub::stub::state_machine::state::Running,T...
0.5% 2.4% 336B gdbstub gdbstub::protocol::commands::breakpoint::BasicBreakpoint::from_slice
0.4% 2.3% 320B gdbstub <gdbstub::protocol::common::thread_id::ThreadId as core::convert::TryFrom<&[u8]>>::try_from
0.4% 2.2% 308B gdbstub gdbstub::protocol::common::hex::decode_hex_buf
0.4% 2.0% 284B gdbstub gdbstub::protocol::response_writer::ResponseWriter<C>::write
0.3% 1.7% 236B gdbstub gdbstub::stub::core_impl::resume::<impl gdbstub::stub::core_impl::GdbStubImpl<T,C>>::write_stop_common
0.3% 1.7% 232B gdbstub gdbstub::protocol::response_writer::ResponseWriter<C>::write_specific_thread_id
0.3% 1.5% 216B core core::iter::traits::iterator::Iterator::nth
0.3% 1.5% 216B core core::iter::traits::iterator::Iterator::nth
0.2% 1.1% 160B gdbstub gdbstub::protocol::common::hex::decode_hex
0.2% 1.1% 160B gdbstub gdbstub::protocol::response_writer::ResponseWriter<C>::inner_write
0.2% 1.1% 156B gdbstub gdbstub::protocol::common::hex::decode_hex
0.2% 1.1% 156B gdbstub gdbstub::protocol::common::hex::decode_hex
0.2% 1.1% 156B gdbstub gdbstub::protocol::response_writer::ResponseWriter<C>::write_num
0.2% 1.0% 144B gdbstub <gdbstub::protocol::common::thread_id::IdKind as core::convert::TryFrom<&[u8]>>::try_from
0.2% 1.0% 144B gdbstub gdbstub::protocol::response_writer::ResponseWriter<C>::write_num
0.2% 1.0% 136B gdbstub gdbstub::protocol::response_writer::ResponseWriter<C>::write_hex
0.2% 0.9% 132B gdbstub gdbstub::protocol::response_writer::ResponseWriter<C>::flush
0.2% 0.8% 116B gdbstub_nostd? <gdbstub_nostd::gdb::DummyTarget as gdbstub::target::ext::base::multithread::MultiThreadBase>::read_a...
0.1% 0.8% 108B core <core::slice::iter::SplitMut<T,P> as core::iter::traits::iterator::Iterator>::next
0.1% 0.8% 108B core <core::iter::adapters::skip::Skip<I> as core::iter::traits::iterator::Iterator>::next
0.1% 0.5% 76B gdbstub_nostd? <gdbstub_nostd::gdb::DummyTarget as gdbstub::target::ext::base::multithread::MultiThreadBase>::write_...
0.1% 0.5% 76B gdbstub_nostd? <gdbstub_nostd::gdb::DummyTarget as gdbstub::target::ext::base::multithread::MultiThreadBase>::read_r...
0.1% 0.5% 76B gdbstub_nostd? <gdbstub_nostd::gdb::DummyTarget as gdbstub::target::ext::base::multithread::MultiThreadBase>::write_...
0.1% 0.4% 52B gdbstub_nostd? <gdbstub_nostd::gdb::DummyTarget as gdbstub::target::ext::base::multithread::MultiThreadResume>::resume
0.1% 0.4% 52B gdbstub_nostd? <gdbstub_nostd::gdb::DummyTarget as gdbstub::target::ext::base::multithread::MultiThreadResume>::set_...
0.1% 0.4% 52B gdbstub_nostd? <gdbstub_nostd::gdb::DummyTarget as gdbstub::target::ext::base::multithread::MultiThreadResume>::clea...
0.1% 0.3% 48B gdbstub_nostd gdbstub_nostd::print_str::print_str
0.0% 0.1% 12B gdbstub_nostd? <gdbstub_nostd::gdb::DummyTarget as gdbstub::target::ext::breakpoints::SwBreakpoint>::add_sw_breakpoint
19.2% 100.0% 13.7KiB .text section size, the file size is 71.6KiB
error: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/size: invalid argument -A
Usage: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/size [-m] [-l] [-x] [--] [[-arch <arch_flag>] ...] [file ...]
After
Finished `release` profile [optimized + debuginfo] target(s) in 0.86s
Finished `release` profile [optimized + debuginfo] target(s) in 0.00s
Analyzing target/release/gdbstub-nostd
File .text Size Crate Name
24.1% 125.7% 17.2KiB gdbstub_arch <gdbstub_arch::arm::reg::arm_core::ArmCoreRegs as gdbstub::arch::Registers>::gdb_deserialize
24.0% 125.3% 17.2KiB [Unknown] __mh_execute_header
12.2% 63.7% 8.7KiB [Unknown] _main
1.0% 5.4% 764B gdbstub gdbstub::stub::state_machine::GdbStubStateMachineInner<gdbstub::stub::state_machine::state::Running,T...
0.5% 2.4% 336B gdbstub gdbstub::protocol::commands::breakpoint::BasicBreakpoint::from_slice
0.4% 2.3% 320B gdbstub <gdbstub::protocol::common::thread_id::ThreadId as core::convert::TryFrom<&[u8]>>::try_from
0.4% 2.2% 308B gdbstub gdbstub::protocol::common::hex::decode_hex_buf
0.4% 2.0% 284B gdbstub gdbstub::protocol::response_writer::ResponseWriter<C>::write
0.3% 1.7% 236B gdbstub gdbstub::stub::core_impl::resume::<impl gdbstub::stub::core_impl::GdbStubImpl<T,C>>::write_stop_common
0.3% 1.7% 232B gdbstub gdbstub::protocol::response_writer::ResponseWriter<C>::write_specific_thread_id
0.3% 1.5% 216B core core::iter::traits::iterator::Iterator::nth
0.3% 1.5% 216B core core::iter::traits::iterator::Iterator::nth
0.2% 1.1% 160B gdbstub gdbstub::protocol::common::hex::decode_hex
0.2% 1.1% 160B gdbstub gdbstub::protocol::response_writer::ResponseWriter<C>::inner_write
0.2% 1.1% 156B gdbstub gdbstub::protocol::common::hex::decode_hex
0.2% 1.1% 156B gdbstub gdbstub::protocol::common::hex::decode_hex
0.2% 1.1% 156B gdbstub gdbstub::protocol::response_writer::ResponseWriter<C>::write_num
0.2% 1.0% 144B gdbstub <gdbstub::protocol::common::thread_id::IdKind as core::convert::TryFrom<&[u8]>>::try_from
0.2% 1.0% 144B gdbstub gdbstub::protocol::response_writer::ResponseWriter<C>::write_num
0.2% 1.0% 136B gdbstub gdbstub::protocol::response_writer::ResponseWriter<C>::write_hex
0.2% 0.9% 132B gdbstub gdbstub::protocol::response_writer::ResponseWriter<C>::flush
0.2% 0.8% 116B gdbstub_nostd? <gdbstub_nostd::gdb::DummyTarget as gdbstub::target::ext::base::multithread::MultiThreadBase>::read_a...
0.1% 0.8% 108B core <core::slice::iter::SplitMut<T,P> as core::iter::traits::iterator::Iterator>::next
0.1% 0.8% 108B core <core::iter::adapters::skip::Skip<I> as core::iter::traits::iterator::Iterator>::next
0.1% 0.5% 76B gdbstub_nostd? <gdbstub_nostd::gdb::DummyTarget as gdbstub::target::ext::base::multithread::MultiThreadBase>::write_...
0.1% 0.5% 76B gdbstub_nostd? <gdbstub_nostd::gdb::DummyTarget as gdbstub::target::ext::base::multithread::MultiThreadBase>::read_r...
0.1% 0.5% 76B gdbstub_nostd? <gdbstub_nostd::gdb::DummyTarget as gdbstub::target::ext::base::multithread::MultiThreadBase>::write_...
0.1% 0.4% 52B gdbstub_nostd? <gdbstub_nostd::gdb::DummyTarget as gdbstub::target::ext::base::multithread::MultiThreadResume>::resume
0.1% 0.4% 52B gdbstub_nostd? <gdbstub_nostd::gdb::DummyTarget as gdbstub::target::ext::base::multithread::MultiThreadResume>::set_...
0.1% 0.4% 52B gdbstub_nostd? <gdbstub_nostd::gdb::DummyTarget as gdbstub::target::ext::base::multithread::MultiThreadResume>::clea...
0.1% 0.3% 48B gdbstub_nostd gdbstub_nostd::print_str::print_str
0.0% 0.1% 12B gdbstub_nostd? <gdbstub_nostd::gdb::DummyTarget as gdbstub::target::ext::breakpoints::SwBreakpoint>::add_sw_breakpoint
19.2% 100.0% 13.7KiB .text section size, the file size is 71.6KiB
error: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/size: invalid argument -A
Usage: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/size [-m] [-l] [-x] [--] [[-arch <arch_flag>] ...] [file ...]
I'm proposing this change because in probe-rs I keep getting errors along the lines of "An error occurred in probe-rs" with no additional context, because they rely heavily on thiserror which makes liberal use of Error::source(). By supporting this, the chain isn't severed when adding it to gdbstub.
I've managed to work around it by changing the error type that gets propagated, but I think it might still be a nice feature.
Cross-linking to #112
I agree that having a way to propagate the error source is useful.
That said... relying on Box<dyn core::error::Error> for the implementation seems a bit unfortunate, given the extra indirection / allocation (and, ofc, the reliance on alloc, when core::error::Error doesn't strictly need alloc).
I wonder if an approach like this would work? Just riffing off the top of my head - haven't proven it out in practice.
// add a new default type param to the type (which should be semver compatible?)
pub enum TargetError<E, ECore = NoCoreError> {
...
Fatal(E),
FatalCore(ECore),
}
// by default, use a ZST to make `FatalCore` be a totally noop variant
enum NoCoreError {}
impl std::core::Error for NoCoreError {}
// tweak the impl bounds here (which _should_ be semver compatible still)
#[cfg(any(feature = "std", feature = "core_error"))]
impl<T, C, ECore> CoreError for GdbStubError<T, C, ECore>
where
C: Debug + Display,
T: Debug + Display,
ECore: CoreError
{
fn source(&self) -> Option<&(dyn CoreError + 'static)> {
let GdbStubError {
kind: InternalError::FatalCore(e),
} = self
else {
return None;
};
e.source()
}
}
In practice, this is a prime example of a scenario where specialization would really come in clutch, since we'd just be able to specialize the existing TargetError<E> type in instances where E: core::error::Error... but I think this might work as well?
Let me know what you think (or if there's some obvious semver hazard / technical blocker to this idea)
Specialization would make so many of these things easier, I agree.
I considered the Box<T> acceptable for now since it only happens once per session, seeing as how it's a fatal error. But your approach seems sound, and is probably a better approach than mine.