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
Add 'read/write register' commands #22
Conversation
Nice, thanks for splitting up the PRs! Like I mentioned in #21 (comment), the proposed API is leaking part of the underlying GDB protocol (namely, the Unfortunately, I can't think of a super simple way to easily hide this detail without a bit of plumbing and trait-rework 😬 Here's what I'm thinking: extend the e.g: for RISC-V, the enum RiscvCoreRegsId {
R(usize), // shame there's no way to encode a u5 at the type level, ah well
PC,
} That way, the signature of fn read_register(
&mut self,
dst: &mut [u8],
reg: arch::riscv::reg::RiscvCoreRegsId,
) The end result is an API which enables end-users to uniquely identify a register without having to search through the GDB source code to find the register numbers for their target. I've got some other ideas on how it be possible to replace the If all this sounds daunting, I don't blame you. If you want to take a crack at implementing some of these ideas (or proposing your own alternative!), I'd love to work with you to come up with something awesome 😄. If that's not really something you'd be interested in at this time, then I'll go ahead and open an issue (something like "Add read/write single registers API"), and try to implement this feature at some point before releasing the next breaking release (which I've been hacking on intermittently for the past couple weeks). |
Pretty awesome explanation, thanks ! I'm definitely interested to work on something along those lines. Actually its a nice opportunity to play with some advanced Rust :) Hopefully I will have some time to work on it in the next days. |
Hey, good to hear! If you ever want to throw some ideas around, feel free to shoot me an email / DM me on Discord (daniel5151#4520). Additionally, I maaaaaaay have nerd-sniped myself while working on a way to remove the Turns out that while it's possible to pull-off, it ends up being quite ugly. Here's a (very detailed) Rust Playground link with my experimentation: A lot of the code could be massively simplified if/when GATs are ever stabilized, but who knows when those are gonna land... I guess for the initial implementation, passing a raw byte-buffer would work. Alternatively, it might be better to pass a |
8fd47b8
to
8009fa2
Compare
By the way, I wanted to draw your attention to the It's a massive API overhaul which uses a pretty nifty type-system + compiler optimization trick to emulate optional trait methods at compile time (along with a bunch of other useful properties). It should make the Thankfully, the majority of the changes in I bring this up since there's a good chance you'll get a nasty looking merge conflict once the So, uh, sorry about that! |
8009fa2
to
2a2b7a7
Compare
Thanks for notifying ! I read you code snippet, pretty interesting (it blew my mind the first 5 min :p). If I understood correctly, you try to tie the register identifier with their storage, so that we can have different storage types for different identifiers? I pushed my (probably naive) solution to the PR. Here are the important points:
A few remarks:
Does the approach seems good to you? I will take a look at PR #24 and try to adapt. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I read you code snippet, pretty interesting (it blew my mind the first 5 min :p). If I understood correctly, you try to tie the register identifier with their storage, so that we can have different storage types for different identifiers?
Heh, glad you liked it! Pretty sneaky, eh?
And yeah, your analysis was spot on. Shame the ergonomics just aren't there yet...
I threw a couple of general comments in the review. Though it seems that some of them might not be relevant, depending on if you choose to go with the approach I've proposed below...
The register type for a given register ID is not exposed at all (we just pass
&mut [u8]
)
Yeah, that's fine. As I showed above, making it bulletproof at the type level seems really tricky 😉
I added a GdbSerializable trait...
I think a big issue is that the current gdb_deserialize
method isn't particularly well suited to be implemented on a RegId
enum. It forces RegId
to have a Default
type, as there needs to be some destination to write data into...
I did think of another idea though: instead of having RegId
implement GdbSerializable
, why not do something a simple as:
trait RegId {
/// Map raw GDB register numbers to a (RegId, register size).
/// Returns None if the register isn't available.
fn from_raw_id(id: usize) -> Option<(Self, usize)>;
}
// for unsupported / unimplemented systems
impl RegId for () {
fn from_raw_id(id: usize) -> Option<(Self, usize)> { None }
}
// and in the `GdbStub` implementation:
Command::p(cmd) => {
let mut dst = [0u8; 16];
let reg = <<T::Arch as Arch>::Registers as Registers>::RegId::from_raw_id(cmd.reg_id);
let (reg_id, size) = match reg {
Some(v) => v,
None => return Ok(None), // GDB docs say to return nothing for unsupported query
}
let dst = &mut dst[0..size];
// enforce size within the GDB stub, instead of relying on the target
target.read_register(dst, reg_id).maybe_missing_impl()?;
res.write_hex_buf(dst)?;
}
I think this approach has a lot of merit, as it avoids introducing any redundant traits or weird type-bounds. Thoughts?
2a2b7a7
to
9a30c83
Compare
Thank you so much, your feedbacks are very helpful !! I still plan to add a few things in the RISC-V |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice, that's looking pretty slick!
I'm glad we could come up with something that's nice and clean!
I've left a couple of comments inline / below, but this looks just about ready to ship 🚢
I still plan to add a few things in the RISC-V RegId within a few days (mostly adding missing registers). Maybe we can wait this is done before merging the PR?
Yep, just let me know when you're ready to merge 👍
- Could you also implement the
write_register
functionality as well?- It shouldn't be too difficult, and would keep the API nice and symmetrical
- Hint: When parsing the
P
packet, you'll want to follow a similar pattern to theG
packet, where the hex string is decoded "in place" using thedecode_hex_buf
helper, with the resulting packet holding a&'a [u8]
reference to the decoded buffer.
- If it's not too much trouble, could you update the
armv4t
example with an example implementation ofread/write_register
?- The corresponding
RegID
structure should be self-evident (theArmCoreRegs
struct is very straightforward) - Running the example should be fairly straightforward (see examples/armv4t/README.md), and i've even included a pre-built test binary (
test.elf
) with debug symbols. It uses a relative path for the source map, so as long as you rungdb-multiarch
from thetest_bin
directory, it should Just Work - The code is very simple, so even if you're not familiar with the details of the ARM architecture, it shouldn't be too difficult to get working :)
- The corresponding
a5e4925
to
e33d4b6
Compare
Hey! Good news its almost ready to ship 😄 I applied your suggestions and also added the write register command. I'm still not fully satisfied with "what should I return as error?". The doc do not say (as with 'p') that nothing means "unsupported". However, looking at the source code (https://github.com/bsdjhb/gdb/blob/master/gdb/remote.c#L8361), GDB seems to handle the unsupported case the same way as 'p' does... . BTW the error code if any is returned to 'P' is just ignored, anything that starts with E should be accepted... Yeah, I can add the RegId quite easily for Armvt4 I think (speaking ARM all days @work :p). It is a great idea as it would allow to have tests for that new part of the API. What about doing that in a dedicated issue/PR? |
In some architecture, Gdb will actually query single registers, for ex. on RISC-V. This commit introduces support for this command. To support this, several significant modifications are introduced: - Add a `RegId` type in `Register` trait. - the trait Target gained a new 'read_register' function, which is made optional to avoid breaking existing implementations.
The RiscvRegId is now public in the arch::riscv::reg module.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice! The parsing / handler / API surface of the write_registers
command LGTM!
I've got a few more comments and nits, but they're mostly just minor doc-related fixes.
I'm still not fully satisfied with "what should I return as error?"
Lets just play it safe and go off what the protocol docs say (even if the GDB client implementation doesn't seem to care). i.e: add a res.write_str("E01")?
inside the None
arm of the P
packet handler.
Yeah, I can add the RegId quite easily for Armvt4... What about doing that in a dedicated issue/PR?
Could we just lump it in with this PR? After all, we're already lumping in some RISC-V definitions, so why not add some ARM ones as well 😛 Plus, it would be good to have a working example of how to use the new API alongside the implementation itself.
src/arch/traits.rs
Outdated
@@ -31,8 +31,12 @@ impl RegId for () { | |||
pub trait Registers: Default { | |||
/// Register identifier for addressing single registers. | |||
/// | |||
/// If your target does not implement that feature, you can use `RegId = ()` | |||
/// as a default, which implements the `RegId` trait. | |||
/// Architectures that do not implement single register read can safely use |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// Architectures that do not implement single register read can safely use | |
/// Architectures that do not support single register accesses can safely use |
src/arch/traits.rs
Outdated
/// Architectures that do not implement single register read can safely use | ||
/// `RegId = ()` as a default. | ||
/// | ||
/// **Note**: the use `RegId = ()` in most architectures is temporary. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// **Note**: the use `RegId = ()` in most architectures is temporary. | |
/// **Note**: the use of `RegId = ()` in most architectures is temporary. |
src/protocol/commands/_p_upcase.rs
Outdated
let val = decode_hex_buf(body.next()?).ok()?; | ||
Some(P { reg_id, val }) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: could you add a newline to the end of this file?
src/gdbstub_impl/mod.rs
Outdated
let reg = <<T::Arch as Arch>::Registers as Registers>::RegId::from_raw_id(p.reg_id); | ||
let (reg_id, _) = match reg { | ||
Some(v) => v, | ||
None => return Ok(None), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(see main review comment)
By the way, I've decided to hold-off on merging #24 until after this PR gets in, so don't worry about dealing with any merge conflicts, I'll just resolve them on my end 😄 |
9cea90c
to
b9a0918
Compare
Oh, cool! |
This is more in line with GDB specifications of the 'P' packet.
1d3ec13
to
b37f622
Compare
Regarding the > target remote :9001
> set $r2=0xDEADBEEF
> info register I struggle to get gdb send single read register for this architecture, so I removed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hooray! 🎉
I've done one last full-sweep through the PR, found the last few tiiiiny style nits, but after those are fixed, I'm all ready to smash that merge button!
Thanks again for all the work you've put into the PR. It's a great feature, and I hope you had fun messing about with Rust's associated types and generics 😄
P.S: before merging my massive API overhaul PR, I'll publish one more version of gdbstub 0.2
with this feature, so that you won't have to upgrade to the new API if you don't want to 😉
note: the 'P' handler now also reports an error if write_register is not implemented.
Hey Daniel, I hope this last update will be fine :) Many thanks for you very helpful reviews, I learned so much in the process! I think I will continue to contribute small things in Having that in 0.2 would be cool and allow my project to directly depend on the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wonderful! Time to 🚢 this bad boy!
Looking forward to any other contributions you might want to make!
And small heads up, I've made a minor API change in the new version on crates.io, where the resume
method takes a Actions
iterator instead of a raw &mut dyn impl Iterator<..>
. This is entirely surface level, and doesn't change any functionality. Thankfully, semver technically allows versions <1.x.y to make breaking API changes at any time 😋
Oh, and shameless plug, but I would recommend updating to 0.3
once I publish it, since aside from API improvements, it comes with a couple of internal protocol fixes + optimizations. If you're curious what the API looks like, check out the examples
directory on the development branch 😄
Cheers!
I've pushed version |
A proposal (probably to be reworked) API extension to support RSP "read register command".