Skip to content

Dynamic Arch selection #53

@daniel5151

Description

@daniel5151

Extracted from a back-and-forth on #31 (comment)

Will likely tie into #12


With the current Arch architecture, it's actually pretty easy to "punch through" the type-safe abstractions by implementing an Arch that looks something like this:

// not actually tested, but something like this ought to work

pub enum PassthroughArch {}

impl Arch for PassthroughArch {
    type Usize = u64; // something reasonably big
    type Registers = PassthroughRegs;
    type RegId = PassthroughRegId;
    ...
}

pub struct PassthroughRegs {
    len: usize,
    buffer: [u8; MAX_SIZE], // a vec could also work
}

// push logic up a level into `Target`
impl Registers for PassthroughRegs {
    fn gdb_serialize(&self, mut write_byte: impl FnMut(Option<u8>)) {
        for byte in &self.buffer[..self.len] { write_byte(Some(byte)) }
    }

    fn gdb_deserialize(&mut self, bytes: &[u8]) -> Result<(), ()> {
        self.buffer[..bytes.len()].copy_from_slice(bytes)
    }
}

pub struct PassthroughRegId(usize);
impl RegId for PassthroughRegId { ... } // elided for brevity, should be straightforward

impl Target {
    type Arch = PassthroughArch;

    // .... later on, in the appropriate IDET ... //

    fn read_registers(
        &mut self, 
        regs: &mut PassthroughRegs<YourTargetSpecificMetadata>
    ) -> TargetResult<(), Self> {
        // write data directly into `regs.buffer` + `regs.len`
        if self.cpu.in_foo_mode() { /* one way */  } else { /* other way */ }
        // can re-use existing `impl Registers` types from `gdbstub_arch`, 
        // calling `gdb_serialize` and `gdb_deserialize` directly...
        if using_arm {
            let mut regs = gdbstub_arch::arm::regs::ArmCoreRegs::default();
            regs.pc = self.pc;
            // ...
            let mut i = 0;
            regs.gdb_serialize(|b| { regs.buffer[i] = b.unwrap_or(0); i++; })
        }
    }

    // similarly for `write_registers`
}

This can then be combined with the recently added TargetDescriptionXmlOverride IDET to effectively create a runtime configurable Arch + Target (see #43)

While this will work in gdbstub right now, this approach does have a few downsides:

  • Bypasses one of gdbstub's most useful abstractions, forcing end users to care about the unfortunate details of how GDB de/serializes register data (e.g: sending data in the order defined by target.xml, input/output buffer management, etc...)
  • It is not zero-copy, as the PassthroughRegs buffer will be be immediately copied into the "true" output buffer on every invocation. Mind you, neither is using a type Registers intermediary, but hey, linear copies are super fast, so it's probably fine.
  • The PassthroughRegs type is static, and as such, will have to include a buffer that's large enough to handle the "wost case" largest inbound/outbound payload. This can be mitigated by using variable-length type as the backing storage (such as a Vec) when running in an std environment.
  • In the current version of gdbstub (0.4.5 at the time of writing), each call to {read,write}_registers results in a fresh instance of type Registers being allocated on the stack. The original intent behind this decision was to avoid having a permanent "static" Registers struct take up memory inside struct GdbStub, but honestly, it's not that important. Thankfully, this is a internal issue with a fairly easy fix, and I'm considering switching where the type is stored (in struct GdbStub vs. the stack) in the next minor/patch version of gdbstub.

There are a couple things that can be done to mitigate this issue:

  1. Provide a single "blessed" implementation of PassthroughArch, so that folks don't have to write all this boilerplate themselves
    • This is easy, and can be done as a first-time-contributor's PR, as it doesn't require digging into gdbstub's guts.
  2. Set aside some time to reconsider the underlying Arch API, and modify it to enable more elegant runtime configuration, while also retaining as many ergonomic features / abstraction layers as the current static-oriented API.
    • This is significantly harder, and is something that I would probably want to tackle myself, as it may have sweeping changes across the codebase

Metadata

Metadata

Assignees

No one assigned

    Labels

    API-breakingBreaking API changeAPI-ergonomicsNothing's "broken", but the API could be improveddesign-requiredGetting this right will require some thoughtnew-apiAdd a new feature to the API (possibly non-breaking)

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions