Skip to content
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 support for memory maps #54

Merged
merged 4 commits into from
May 27, 2021

Conversation

Tiwalun
Copy link
Contributor

@Tiwalun Tiwalun commented May 14, 2021

For embedded targets, we need to be able to configure the memory map, as described in the GDB Docs.
Otherwise, GDB will try to use software breakpoints for code running from flash, which doesn't work.

This PR adds an extension trait based on the target_description_xml_override extension to support memory maps.

@daniel5151 daniel5151 self-requested a review May 14, 2021 15:30
Copy link
Owner

@daniel5151 daniel5151 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you so much for the PR!

The core functionality looks good, though there are some organizational changes that'll need to happen before this can get merged.

One thing that this PR is missing is an example implementation for the in-tree armv4t example*. The armv4t example includes a "grab bag" of all protocol extensions currently implemented in gdbstub. It doesn't have to be a particularly "robust" implementation, but it should be good enough such that the GDB Client will try to interact with the feature, and someone running the example can confirm that the feature was in-fact used (e.g: by checking the protocol logs using RUST_LOG=trace, or through a eprintln! statement in the handler).

Moreover, having a working example in armv4t makes it super easy to validate whether the feature is working as intended, without having to fully integrate it into a larger project.

* I really ought to update the PR template with a checklist, since I think I have to mention this on each PR haha.

src/target/ext/memory_map.rs Outdated Show resolved Hide resolved
src/protocol/commands.rs Outdated Show resolved Hide resolved
@@ -143,6 +147,30 @@ impl<T: Target, C: Connection> GdbStubImpl<T, C> {
}
HandlerStatus::Handled
}
Base::qXferMemoryMapRead(cmd) => {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You'll have to move this hander out into a separate ext mod.

(meta): once this PR is merged, I'll pop-open a tracking task for myself to de-dupe the qXfer logic between the two packets. some copy-paste is fine for now, but I know that there'll be more of these packets coming down the line, and there'll need to be some way to keep the code DRY.

@daniel5151
Copy link
Owner

daniel5151 commented May 14, 2021

On a more meta note, I'm curious if it might make sense to at switch the signature of the memory_map_xml method to something like:

fn memory_map_xml(&self) -> MemoryMap<'_>;

where MemoryMap is defined as:

struct MemoryMapRegionKind {
    Ram,
    Rom,
    Flash { blocksize: usize }
}

struct MemoryMapRegion {
    start: usize,
    length: usize,
    kind: MemoryMapRegionKind
}

enum MemoryMap<'a> {
    Raw(&'a str),
    Structured(&'a [MemoryMapRegion])
}

In this second case, the target wouldn't actually care about the actual underlying XML, and could defer those details over to gdbstub.

I don't know much about how this feature will be used, and if most targets come with a pre-existing memory map XML, then this isn't likely to be super useful. That said, if you're expecting that most targets will end up having to generate their own XML on the fly, then it might make sense to lift XML construction into gdbstub itself.

Let me know what you think. This doesn't need to go in as part of this PR.

@Tiwalun
Copy link
Contributor Author

Tiwalun commented May 14, 2021

Generating the XML in gdbstub sounds like a good idea, especially for dynamic targets. In probe-rs we currently do it ourselves here.

Even for targets with a fixed memory map it's probably more convenient if you don't have to study the GDB documentation but can just provide a struct with the necessary information.

@daniel5151
Copy link
Owner

daniel5151 commented May 14, 2021

Thanks for the quick turnaround!

Would you be interested in implementing the XML generation as part of this PR, or should we punt it to a follow up PR instead?

Also, for my own peace of mind, could you run the armv4t example with RUST_LOG=trace and show me a readout of what's being sent back and forth? e.g:

armv4t output
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:51408
 TRACE gdbstub::gdbstub_impl > <-- +
 TRACE gdbstub::gdbstub_impl > <-- $qSupported:multiprocess+;swbreak+;hwbreak+;qRelocInsn+;fork-events+;vfork-events+;exec-events+;vContSupported+;QThreadEvents+;no-resumed+;xmlRegisters=i386#6a
 TRACE gdbstub::protocol::response_writer > --> $PacketSize=1000;vContSupported+;multiprocess+;QStartNoAckMode+;ReverseContinue+;ReverseStep+;QDisableRandomization+;QEnvironmentHexEncoded+;QEnvironmentUnset+;QEnvironmentReset+;QStartupWithShell+;QSetWorkingDir+;swbreak+;hwbreak+;qXfer:features:read+#64
 TRACE gdbstub::gdbstub_impl              > <-- +
 TRACE gdbstub::gdbstub_impl              > <-- $vMustReplyEmpty#3a
 INFO  gdbstub::gdbstub_impl              > Unknown command: vMustReplyEmpty
 TRACE gdbstub::protocol::response_writer > --> $#00
 TRACE gdbstub::gdbstub_impl              > <-- +
 TRACE gdbstub::gdbstub_impl              > <-- $QStartNoAckMode#b0
 TRACE gdbstub::protocol::response_writer > --> $OK#9a
 TRACE gdbstub::gdbstub_impl              > <-- +
 TRACE gdbstub::gdbstub_impl              > <-- $!#21
 TRACE gdbstub::protocol::response_writer > --> $OK#9a
 TRACE gdbstub::gdbstub_impl              > <-- $Hgp0.0#ad
 TRACE gdbstub::protocol::response_writer > --> $OK#9a
 TRACE gdbstub::gdbstub_impl              > <-- $qXfer:features:read:target.xml:0,ffb#79
 TRACE gdbstub::protocol::response_writer > --> $l<target version="1.0"><!-- custom override string --><architecture>armv4t</architecture></target>#bb
 TRACE gdbstub::gdbstub_impl              > <-- $qTStatus#49
 INFO  gdbstub::gdbstub_impl              > Unknown command: qTStatus
 TRACE gdbstub::protocol::response_writer > --> $#00
 TRACE gdbstub::gdbstub_impl              > <-- $?#3f
 TRACE gdbstub::protocol::response_writer > --> $S05#b8
 TRACE gdbstub::gdbstub_impl              > <-- $qfThreadInfo#bb
 TRACE gdbstub::protocol::response_writer > --> $mp01.01#cd
 TRACE gdbstub::gdbstub_impl              > <-- $qsThreadInfo#c8
 TRACE gdbstub::protocol::response_writer > --> $l#6c
 TRACE gdbstub::gdbstub_impl              > <-- $qAttached:1#fa
GDB queried if it was attached to a process with PID 1
 TRACE gdbstub::protocol::response_writer > --> $1#31
 TRACE gdbstub::gdbstub_impl              > <-- $Hc-1#09
 TRACE gdbstub::protocol::response_writer > --> $OK#9a
 TRACE gdbstub::gdbstub_impl              > <-- $qC#b4
 INFO  gdbstub::gdbstub_impl              > Unknown command: qC
 TRACE gdbstub::protocol::response_writer > --> $#00
 TRACE gdbstub::gdbstub_impl              > <-- $qOffsets#4b
 TRACE gdbstub::protocol::response_writer > --> $Text=00;Data=00;Bss=00#94
 TRACE gdbstub::gdbstub_impl              > <-- $g#67
 TRACE gdbstub::protocol::response_writer > --> $00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000107856341200005555xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx10000000#66
 TRACE gdbstub::gdbstub_impl              > <-- $qfThreadInfo#bb
 TRACE gdbstub::protocol::response_writer > --> $mp01.01#cd
 TRACE gdbstub::gdbstub_impl              > <-- $qsThreadInfo#c8
 TRACE gdbstub::protocol::response_writer > --> $l#6c
 TRACE gdbstub::gdbstub_impl              > <-- $m55550000,4#61
 TRACE gdbstub::protocol::response_writer > --> $04b02de5#26
 TRACE gdbstub::gdbstub_impl              > <-- $m5554fffc,4#35
 TRACE gdbstub::protocol::response_writer > --> $00000000#7e
 TRACE gdbstub::gdbstub_impl              > <-- $m55550000,4#61
 TRACE gdbstub::protocol::response_writer > --> $04b02de5#26
 TRACE gdbstub::gdbstub_impl              > <-- $m5554fffc,4#35
 TRACE gdbstub::protocol::response_writer > --> $00000000#7e
 TRACE gdbstub::gdbstub_impl              > <-- $m55550000,2#5f
 TRACE gdbstub::protocol::response_writer > --> $04b0#f6
 TRACE gdbstub::gdbstub_impl              > <-- $m5554fffe,2#35
 TRACE gdbstub::protocol::response_writer > --> $0000#7a
 TRACE gdbstub::gdbstub_impl              > <-- $m5554fffc,2#33
 TRACE gdbstub::protocol::response_writer > --> $0000#7a
 TRACE gdbstub::gdbstub_impl              > <-- $m55550000,2#5f
 TRACE gdbstub::protocol::response_writer > --> $04b0#f6
 TRACE gdbstub::gdbstub_impl              > <-- $m5554fffe,2#35
 TRACE gdbstub::protocol::response_writer > --> $0000#7a
 TRACE gdbstub::gdbstub_impl              > <-- $m5554fffc,2#33
 TRACE gdbstub::protocol::response_writer > --> $0000#7a
 TRACE gdbstub::gdbstub_impl              > <-- $m55550000,4#61
 TRACE gdbstub::protocol::response_writer > --> $04b02de5#26
 TRACE gdbstub::gdbstub_impl              > <-- $m5554fffc,4#35
 TRACE gdbstub::protocol::response_writer > --> $00000000#7e
 TRACE gdbstub::gdbstub_impl              > <-- $m55550000,4#61
 TRACE gdbstub::protocol::response_writer > --> $04b02de5#26
 TRACE gdbstub::gdbstub_impl              > <-- $m5554fffc,4#35
 TRACE gdbstub::protocol::response_writer > --> $00000000#7e
 TRACE gdbstub::gdbstub_impl              > <-- $m55550000,4#61
 TRACE gdbstub::protocol::response_writer > --> $04b02de5#26
 TRACE gdbstub::gdbstub_impl              > <-- $m55550000,4#61
 TRACE gdbstub::protocol::response_writer > --> $04b02de5#26
 TRACE gdbstub::gdbstub_impl              > <-- $m55550000,4#61
 TRACE gdbstub::protocol::response_writer > --> $04b02de5#26
 TRACE gdbstub::gdbstub_impl              > <-- $qSymbol::#5b
 INFO  gdbstub::gdbstub_impl              > Unknown command: qSymbol::
 TRACE gdbstub::protocol::response_writer > --> $#00

@Tiwalun
Copy link
Contributor Author

Tiwalun commented May 14, 2021

This is from GDB, showing that the memory map was received:

(gdb) info mem
Using memory regions provided by the target.
Num Enb Low Addr   High Addr  Attrs 
0   y  	0x00000000 0x100000000 rw nocache 
armv4t output
    Finished dev [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:37142
 TRACE gdbstub::gdbstub_impl > <-- +
 TRACE gdbstub::gdbstub_impl > <-- $qSupported:multiprocess+;swbreak+;hwbreak+;qRelocInsn+;fork-events+;vfork-events+;exec-events+;vContSupported+;QThreadEvents+;no-resumed+;xmlRegisters=i386#6a
 TRACE gdbstub::protocol::response_writer > --> $PacketSize=1000;vContSupported+;multiprocess+;QStartNoAckMode+;ReverseContinue+;ReverseStep+;QDisableRandomization+;QEnvironmentHexEncoded+;QEnvironmentUnset+;QEnvironmentReset+;QStartupWithShell+;QSetWorkingDir+;swbreak+;hwbreak+;qXfer:features:read+;qXfer:memory-map:read+#e4
 TRACE gdbstub::gdbstub_impl              > <-- +
 TRACE gdbstub::gdbstub_impl              > <-- $vMustReplyEmpty#3a
 INFO  gdbstub::gdbstub_impl              > Unknown command: vMustReplyEmpty
 TRACE gdbstub::protocol::response_writer > --> $#00
 TRACE gdbstub::gdbstub_impl              > <-- +
 TRACE gdbstub::gdbstub_impl              > <-- $QStartNoAckMode#b0
 TRACE gdbstub::protocol::response_writer > --> $OK#9a
 TRACE gdbstub::gdbstub_impl              > <-- +
 TRACE gdbstub::gdbstub_impl              > <-- $Hgp0.0#ad
 TRACE gdbstub::protocol::response_writer > --> $OK#9a
 TRACE gdbstub::gdbstub_impl              > <-- $qXfer:features:read:target.xml:0,ffb#79
 TRACE gdbstub::protocol::response_writer > --> $l<target version="1.0"><!-- custom override string --><architecture>armv4t</architecture></target>#bb
 TRACE gdbstub::gdbstub_impl              > <-- $qTStatus#49
 INFO  gdbstub::gdbstub_impl              > Unknown command: qTStatus
 TRACE gdbstub::protocol::response_writer > --> $#00
 TRACE gdbstub::gdbstub_impl              > <-- $?#3f
 TRACE gdbstub::protocol::response_writer > --> $S05#b8
 TRACE gdbstub::gdbstub_impl              > <-- $qfThreadInfo#bb
 TRACE gdbstub::protocol::response_writer > --> $mp01.01#cd
 TRACE gdbstub::gdbstub_impl              > <-- $qsThreadInfo#c8
 TRACE gdbstub::protocol::response_writer > --> $l#6c
 TRACE gdbstub::gdbstub_impl              > <-- $qAttached:1#fa
GDB queried if it was attached to a process with PID 1
 TRACE gdbstub::protocol::response_writer > --> $1#31
 TRACE gdbstub::gdbstub_impl              > <-- $Hc-1#09
 TRACE gdbstub::protocol::response_writer > --> $OK#9a
 TRACE gdbstub::gdbstub_impl              > <-- $qC#b4
 INFO  gdbstub::gdbstub_impl              > Unknown command: qC
 TRACE gdbstub::protocol::response_writer > --> $#00
 TRACE gdbstub::gdbstub_impl              > <-- $g#67
 TRACE gdbstub::protocol::response_writer > --> $00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000107856341200005555xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx10000000#66
 TRACE gdbstub::gdbstub_impl              > <-- $qfThreadInfo#bb
 TRACE gdbstub::protocol::response_writer > --> $mp01.01#cd
 TRACE gdbstub::gdbstub_impl              > <-- $qsThreadInfo#c8
 TRACE gdbstub::protocol::response_writer > --> $l#6c
 TRACE gdbstub::gdbstub_impl              > <-- $qXfer:memory-map:read::0,ffb#18
 TRACE gdbstub::protocol::response_writer > --> $l<?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="0x0" length="0x100000000"/>
</memory-map>#75
 TRACE gdbstub::gdbstub_impl              > <-- $m55550000,4#61
 TRACE gdbstub::protocol::response_writer > --> $04b02de5#26
 TRACE gdbstub::gdbstub_impl              > <-- $m5554fffc,4#35
 TRACE gdbstub::protocol::response_writer > --> $00000000#7e
 TRACE gdbstub::gdbstub_impl              > <-- $m55550000,4#61
 TRACE gdbstub::protocol::response_writer > --> $04b02de5#26
 TRACE gdbstub::gdbstub_impl              > <-- $m5554fffc,4#35
 TRACE gdbstub::protocol::response_writer > --> $00000000#7e
 TRACE gdbstub::gdbstub_impl              > <-- $m55550000,2#5f
 TRACE gdbstub::protocol::response_writer > --> $04b0#f6
 TRACE gdbstub::gdbstub_impl              > <-- $m5554fffe,2#35
 TRACE gdbstub::protocol::response_writer > --> $0000#7a
 TRACE gdbstub::gdbstub_impl              > <-- $m5554fffc,2#33
 TRACE gdbstub::protocol::response_writer > --> $0000#7a
 TRACE gdbstub::gdbstub_impl              > <-- $m55550000,2#5f
 TRACE gdbstub::protocol::response_writer > --> $04b0#f6
 TRACE gdbstub::gdbstub_impl              > <-- $m5554fffe,2#35
 TRACE gdbstub::protocol::response_writer > --> $0000#7a
 TRACE gdbstub::gdbstub_impl              > <-- $m5554fffc,2#33
 TRACE gdbstub::protocol::response_writer > --> $0000#7a
 TRACE gdbstub::gdbstub_impl              > <-- $m55550000,4#61
 TRACE gdbstub::protocol::response_writer > --> $04b02de5#26
 TRACE gdbstub::gdbstub_impl              > <-- $m5554fffc,4#35
 TRACE gdbstub::protocol::response_writer > --> $00000000#7e
 TRACE gdbstub::gdbstub_impl              > <-- $m55550000,4#61
 TRACE gdbstub::protocol::response_writer > --> $04b02de5#26
 TRACE gdbstub::gdbstub_impl              > <-- $m5554fffc,4#35
 TRACE gdbstub::protocol::response_writer > --> $00000000#7e
 TRACE gdbstub::gdbstub_impl              > <-- $m55550000,4#61
 TRACE gdbstub::protocol::response_writer > --> $04b02de5#26
 TRACE gdbstub::gdbstub_impl              > <-- $m55550000,4#61
 TRACE gdbstub::protocol::response_writer > --> $04b02de5#26
 TRACE gdbstub::gdbstub_impl              > <-- $m55550000,4#61
 TRACE gdbstub::protocol::response_writer > --> $04b02de5#26
 TRACE gdbstub::gdbstub_impl              > <-- $m0,4#fd
 TRACE gdbstub::protocol::response_writer > --> $00000000#7e

I can add the XML generation as well, I will do it in this PR. Might take a day or two however.

@daniel5151
Copy link
Owner

daniel5151 commented May 14, 2021

Awesome 🎉

If you're willing to implement XML generation as part of this PR, I'd love to see it! It'd be nice to get it done as part of the initial implementation, as to avoid any future breaking API changes.

Note that there is a small chance that I'll be cutting the 0.5.0 release this weekend (I've been putting it off for far too long), but since this PR doesn't require breaking changes, it's totally fine if it comes in after 0.5.0, and released as part of 0.5.1, so no rush!

EDIT: while I didn't end up cutting a 0.5 release this weekend, I did find the time to do a long overdue documentation overhaul, which means that I'm pretty much ready to release 0.5 whenever! It'd be cool to have this feature land as part of 0.5.0, so I'll hold off on a couple days 😄

@daniel5151 daniel5151 force-pushed the dev/0.5 branch 2 times, most recently from 50454e7 to 6f21623 Compare May 14, 2021 19:14
@daniel5151
Copy link
Owner

Just wanted to give you a heads up that I'll be releasing 0.5 this weekend.

Let me know if you think you'd be able to finish this PR up before late Saturday, and if so, I'll squeeze this feature in as part of 0.5.0.

@Tiwalun
Copy link
Contributor Author

Tiwalun commented May 22, 2021

I don't think I will manage to get this done today, unfortunately.

Implementing the XML generation in a way that is compatible with no_std is trickier than expected.

@daniel5151
Copy link
Owner

daniel5151 commented May 22, 2021

All good 👍

If you need any help at all, or just want to riff on different API designs, feel free to show me what you've got so far! I'm well aware of just how much implementing these sorts of things in no_std can be.

That said, I maaaay have nerd sniped myself into thinking about what you could have meant by "trickier than expected", so I ended up writing a whole bunch of my thoughts down...


The API I sketched out in #54 (comment) has one pretty annoying limitation: the lifetime of the returned MemoryMap<'_> object needs to live as long as &self. In practical terms, this means that an implementation of memory_map cannot simply return a dynamically-constructed MemoryMap<'_> object directly from its stack, and must instead "stash it" somewhere within itself / as static data.

To illustrate this point with code, consider a target implementation that has a dynamically determined memory map:

impl MemoryMapXml for MyTarget {
    fn memory_map_xml(&self) -> MemoryMap<'_> {
        let map = vec![
            MemoryMapRegion { start: 0, length: 0x1000, kind: MemoryMapRegionKind::Ram },
            MemoryMapRegion { start: 0x1000, length: 0x2000, kind: MemoryMapRegionKind::Rom },
            ..
        ];
        MemoryMap::Structured(&map) // <-- fails to compile, returning reference to stack-allocated array
    }
}

// instead, the data needs to be stashed somewhere within self...

struct MyTarget {
    map: Vec<MemoryMapRegion>,
    // ...
}

impl MemoryMapXml for MyTarget {
    fn memory_map_xml(&self) -> MemoryMap<'_> {
        let map = vec![
            MemoryMapRegion { start: 0, length: 0x1000, kind: MemoryMapRegionKind::Ram },
            MemoryMapRegion { start: 0x1000, length: 0x2000, kind: MemoryMapRegionKind::Rom },
            ..
        ];
        self.map = map;
        MemoryMap::Structured(&self.map) // <-- compiles fine, as data is stored on `self`
    }
}

An alternative approach that wouldn't require these ownership shenanigans would be to take a page out of functional programming's playbook and use a continuation-passing style API.

Disclaimer: I kinda just banged out this code without testing it. It should work, or at the very least, be a reasonable starting point for a working implementation.

// ZST used to ensure that the continuation has been called. 
// Can only be constructed inside `gdbstub`
#[non_exhaustive]
struct MemoryMapResult {}

impl MemoryMapResult {
    pub(crate) fn new() -> MemoryMapResult { MemoryMapResult {} }
}

trait MemoryMapXml: Target {
    fn get_memory_map_xml(
        &mut self,
        cont: &dyn FnOnce(MemoryMap<'_>) -> TargetResult<MemoryMapResult, Self>,
    ) -> crate::target::TargetResult<MemoryMapResult, Self>;
}

// a rough sketch of how to consume this method within gdbstub

fn handle_memory_map_xml<T: Target, C: Connection>(
    res: &mut ResponseWriter<C>,
    ops: &mut dyn MemoryMapXml<Arch = T::Arch, Error = T::Error>,
) -> Result<HandlerStatus, Error<T::Error, C::Error>> {
    let mut err: Result<_, Error<T::Error, C::Error>> = Ok(());

    let res = ops.get_memory_map_xml(&|memory_map: MemoryMap| {
        // would be replaced with a `try` block if they ever get stabilized
        let e = (|| {
            match memory_map {
                MemoryMap::Raw(s) => { /* transmit raw string */ }
                MemoryMap::Structured(map) => {
                    // transmit header
                    res.write_str("header data...")?;
                    for region in map {
                        // transmit region
                    }
                    // transmit trailing data
                    res.write_str("trailing data...")?;

                    Ok(())
                }
            };
        })();

        if e.is_err() {
            err = e;
        }

        Ok(MemoryMapResult::new())
    });

    err?;

    Ok(HandlerStatus::Handled)
}

// finally, outside of `gdbstub`, in the target implementation...

impl MemoryMapXml for MyTarget {
    fn get_memory_map_xml(
        &self,
        cont: &dyn FnOnce(MemoryMap<'_>) -> TargetResult<MemoryMapResult, Self>,
    ) -> TargetResult<MemoryMapResult, Self> {
        let map = vec![
            MemoryMapRegion {
                start: 0,
                length: 0x1000,
                kind: MemoryMapRegionKind::Ram,
            },
            MemoryMapRegion {
                start: 0x1000,
                length: 0x2000,
                kind: MemoryMapRegionKind::Rom,
            },
            ..,
        ];

        // compiles fine, since the data is passed "down" the stack via a callback.
        // also note that in the monomorphized case, this call will get inlined and/or tail-call-optimized out
        cont(MemoryMap::Structured(&self.map))
    }
}

On a totally orthogonal note, while hacking away at this code, I realize that the other tricky complication is how the return data is transmitted back via the qXfer packet - namely, the whole "chunked" return approach. With the raw string approach, it's super easy, since we can simply recycle the existing code. Unfortunately, the dynamic approach is complicated by the fact that we need to know the size of the generated XML before we start sending it down the wire...

Given the structured and regular nature of the data, it is possible to do a first-pass through the MemoryMapRegion array and calculate the length of the generated strings without transmitting them. i.e: the XML header string has a fixed size, and then each section has a fixed size + a couple variable length number fields, who's hex-encoded-ascii length is easy to calculate depending on their size. If the chunk size is small enough that the client sends multiple qXfer packets, then this pass can also be used to skip over already transmitted data...


Long story short, I now totally understand what you mean when you say that getting it working in no_std is "tricky". It seems totally doable, but it is a non-trivial engineering effort, that's for sure.

As such, here are some ideas of how we can move forwards from here:

  1. You try and implement some of the ideas I listed above, and we get a robust, impressive, and honestly somewhat magical ✨ API available to end users.
  2. We keep things simple and stick with the current &str approach, putting all this effort into the backlog, and possibly introducing a breaking change down the line to integrate it (which is fine)
  3. Stick with the current str only API, but add a MemoryMapXmlBuilder helper type alongside the implementation, which can be used on std targets to construct a valid Memory Map XML String using a type-safe builder interface.

Obviously, the first option would be ideal, since from and end-user perspective it would be totally seamless. That said, it is a non-trivial engineering effort, which you may not want to sink more time into (totally understandable).

The third option seems like a nice middle-ground between simply exposing the Memory Map XML protocol extension, and actually making it easier + less error-prone to use to end users. Yes, it won't be no_std compatible, but given that >90% of gdbstub consumers are using std anyways, it'll still be incredibly useful.

@daniel5151 daniel5151 changed the base branch from dev/0.5 to master May 22, 2021 23:47
@daniel5151
Copy link
Owner

I just released gdbstub 0.5, so I took the liberty of losslessly switching the target branch from dev/0.5 to master

@daniel5151
Copy link
Owner

@Tiwalun, let me know what you want to do with this PR.

Like I mentioned earlier, I'm cool with merging it in as-is. It is provably working, and is a great feature to support. We can punt any follow-up work (such as adding an XML "helper" class and/or an alternative no_std-capable XML generation API) to future PRs.

@Tiwalun
Copy link
Contributor Author

Tiwalun commented May 27, 2021

In that case, I would suggest merging the PR as-is. I don't think I will have time to work on this in the next days, so it's probably better to merge this, and then it can be improved later.

@daniel5151 daniel5151 self-requested a review May 27, 2021 17:02
@daniel5151 daniel5151 merged commit 456952e into daniel5151:master May 27, 2021
@daniel5151
Copy link
Owner

daniel5151 commented May 27, 2021

Alright, it's merged 🎉

Thanks again for your contribution!

Let me know if you'd like me to cut a 0.5.1 release that includes this feature. I'll be away from the 28th to the 31st (it's a long weekend here in the states), so if it's urgent, I can cut a release later today, and if not, I'll go ahead and release it sometime next week.

I'll also pop open a tracking issue that refers back to #54 (comment), in case someone wants to improve the API in the future.

Cheers!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants