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.
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 |
daniel5151
left a comment
There was a problem hiding this comment.
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_registerfunctionality as well?- It shouldn't be too difficult, and would keep the API nice and symmetrical
- Hint: When parsing the
Ppacket, you'll want to follow a similar pattern to theGpacket, where the hex string is decoded "in place" using thedecode_hex_bufhelper, with the resulting packet holding a&'a [u8]reference to the decoded buffer.
- If it's not too much trouble, could you update the
armv4texample with an example implementation ofread/write_register?- The corresponding
RegIDstructure should be self-evident (theArmCoreRegsstruct 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-multiarchfrom thetest_bindirectory, 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.
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
| /// | ||
| /// 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.
| /// 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.
| /// **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 }) | ||
| } | ||
| } No newline at end of file |
There was a problem hiding this comment.
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), |
|
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 registerI struggle to get gdb send single read register for this architecture, so I removed |
daniel5151
left a comment
There was a problem hiding this comment.
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.
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".