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

Support read exec-file #69

Merged
merged 8 commits into from Sep 15, 2021
Merged

Support read exec-file #69

merged 8 commits into from Sep 15, 2021

Conversation

bet4it
Copy link
Contributor

@bet4it bet4it commented Aug 22, 2021

Description

This PR implements the qXfer:exec-file:read command, based off the GDB documentation here.

API Stability

  • This PR does not require a breaking API change

Checklist

  • Implementation
    • cargo build compiles without errors or warnings
    • cargo clippy runs without errors or warnings
    • cargo fmt was run
    • All tests pass
  • Documentation
    • rustdoc + approprate inline code comments
    • Updated CHANGELOG.md
    • (if appropriate) Added feature to "Debugging Features" in README.md
  • If implementing a new protocol extension IDET
    • Included a basic sample implementation in examples/armv4t
    • Included output of running examples/armv4t with RUST_LOG=trace + any relevant GDB output under the "Validation" section below
    • Confirmed that IDET can be optimized away (using ./scripts/test_dead_code_elim.sh and/or ./example_no_std/check_size.sh)
    • OR Implementation requires adding non-optional binary bloat (please elaborate under "Description")
  • If upstreaming an Arch implementation
    • I have tested this code in my project, and to the best of my knowledge, it is working as intended.

Validation

GDB output
Remote debugging using :9001
Reading /test.elf from remote target...
warning: File transfers from remote targets can be slow. Use "set sysroot" to access files locally instead.
Reading /test.elf from remote target...
Reading symbols from target:/test.elf...
main () at test.c:1
1       int main() {
armv4t output
    Finished dev [unoptimized + debuginfo] target(s) in 0.02s
     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:37052
 TRACE gdbstub::protocol::recv_packet > <-- +
 TRACE gdbstub::protocol::recv_packet > <-- $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+;QCatchSyscalls+;qXfer:features:read+;qXfer:memory-map:read+;qXfer:exec-file:read+#ba
 TRACE gdbstub::protocol::recv_packet     > <-- +
 TRACE gdbstub::protocol::recv_packet     > <-- $vMustReplyEmpty#3a
 INFO  gdbstub::gdbstub_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>
    <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"/>
    </feature>
</target>#0f
 TRACE gdbstub::protocol::recv_packet     > <-- $qXfer:features:read:target.xml:aa4,ffb#3f
 TRACE gdbstub::protocol::response_writer > --> $l#6c
 TRACE gdbstub::protocol::recv_packet     > <-- $qTStatus#49
 INFO  gdbstub::gdbstub_impl              > Unknown command: Ok("qTStatus")
 TRACE gdbstub::protocol::response_writer > --> $#00
 TRACE gdbstub::protocol::recv_packet     > <-- $?#3f
 TRACE gdbstub::protocol::response_writer > --> $S05#b8
 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     > <-- $vFile:setfs:0#bf
 TRACE gdbstub::protocol::response_writer > --> $F0#76
 TRACE gdbstub::protocol::recv_packet     > <-- $vFile:open:6a7573742070726f62696e67,0,1c0#ed
 TRACE gdbstub::protocol::response_writer > --> $F-1,02#32
 TRACE gdbstub::protocol::recv_packet     > <-- $vFile:setfs:1#c0
 TRACE gdbstub::protocol::response_writer > --> $F0#76
 TRACE gdbstub::protocol::recv_packet     > <-- $vFile:open:2f746573742e656c66,0,0#1e
 TRACE gdbstub::protocol::response_writer > --> $F00#a6
 TRACE gdbstub::protocol::recv_packet     > <-- $vFile:pread:0,1000,0#ef
UUUUxx#eabstub::protocol::response_writer > --> $F1000;ELF(UU4@4 (
 TRACE gdbstub::protocol::recv_packet     > <-- $vFile:fstat:0#bc
 TRACE gdbstub::protocol::response_writer > --> $F40;p#26
 TRACE gdbstub::protocol::recv_packet     > <-- $vFile:pread:0,1000,10540#b9
 TRACE gdbstub::protocol::response_writer > --> $F0230;UxxUUx&xw2�U@D Od_[0��f0HYop�}
�0�P
        Lf��#cc
 TRACE gdbstub::protocol::recv_packet     > <-- $vFile:pread:0,1000,34#26
 TRACE gdbstub::protocol::response_writer > --> $F1000;UUUUxx#83
 TRACE gdbstub::protocol::recv_packet     > <-- $vFile:pread:0,1000,104b2#e8
 TRACE gdbstub::protocol::response_writer > --> $F02be;.symtab.strtab.shstrtab.text.bss.debug_info.debug_abbrev.debug_aranges.debug_line.debug_str.comment.ARM.attributes.debug_frameUxxUUx&xw2�U@D Od_[0��f0HYop�}
�0�P
        Lf��#d8
 TRACE gdbstub::protocol::recv_packet     > <-- $vFile:pread:0,1000,10078#bf
 TRACE gdbstub::protocol::response_writer > --> $F06f8;s
                                                        .UUx�oUUx�ox    o�ty    o�l4UU0i        o�pint%
                                                                                                      ?:
                                                                                                        ;
                                                                                                         9
                                                                                                          I@▒�B:
                                                                                                                ;
                                                                                                                 9
                                                                                                                  I▒
                                                                                                                    }

                                                                                                                     >
test.c                                                                                                               UUx[�
      UU        gKLgiJ
                      /%ef
                          jtest.cGNU C11 9.2.1 20191025 (release) [ARM/arm-9-branch revision 277599] -mfloat-abi=soft -marm -march=armv4t -g -O0 -std=c11mainGCC: (15:9-2019-q4-0ubuntu1) 9.2.1 20191025 (release) [ARM/arm-9-branch revision 277599]A)aeabi4T▒▒
                                                                                                          ����|
UUxB�B
B�UUxUU
�UU
   xUU▒xUUxUU+xUU7UUx<xUUIxUUWUUtest.c}a__DATA_START__end__DATA_END____BSS_END__main__TEXT_END____BSS_START____TEXT_START__.symtab.strtab.shstrtab.text.bss.debug_info.debug_abbrev.debug_aranges.debug_line.debug_str.comment.ARM.attributes.debug_frameUxxUUx&xw2�U@D Od_[0��f0HYop�}
�0�P
        Lf��#90
 TRACE gdbstub::protocol::recv_packet     > <-- $vFile:pread:0,1000,0#ef
UUUUxx#eabstub::protocol::response_writer > --> $F1000;ELF(UU4@4 (
 TRACE gdbstub::protocol::recv_packet     > <-- $vFile:pread:0,1000,10540#b9
 TRACE gdbstub::protocol::response_writer > --> $F0230;UxxUUx&xw2�U@D Od_[0��f0HYop�}
�0�P
        Lf��#cc
 TRACE gdbstub::protocol::recv_packet     > <-- $vFile:pread:0,1000,34#26
 TRACE gdbstub::protocol::response_writer > --> $F1000;UUUUxx#83
 TRACE gdbstub::protocol::recv_packet     > <-- $vFile:pread:0,1000,104b2#e8
 TRACE gdbstub::protocol::response_writer > --> $F02be;.symtab.strtab.shstrtab.text.bss.debug_info.debug_abbrev.debug_aranges.debug_line.debug_str.comment.ARM.attributes.debug_frameUxxUUx&xw2�U@D Od_[0��f0HYop�}
�0�P
        Lf��#d8
 TRACE gdbstub::protocol::recv_packet     > <-- $vFile:pread:0,1000,10078#bf
 TRACE gdbstub::protocol::response_writer > --> $F06f8;s
                                                        .UUx�oUUx�ox    o�ty    o�l4UU0i        o�pint%
                                                                                                      ?:
                                                                                                        ;
                                                                                                         9
                                                                                                          I@▒�B:
                                                                                                                ;
                                                                                                                 9
                                                                                                                  I▒
                                                                                                                    }

                                                                                                                     >
test.c                                                                                                               UUx[�
      UU        gKLgiJ
                      /%ef
                          jtest.cGNU C11 9.2.1 20191025 (release) [ARM/arm-9-branch revision 277599] -mfloat-abi=soft -marm -march=armv4t -g -O0 -std=c11mainGCC: (15:9-2019-q4-0ubuntu1) 9.2.1 20191025 (release) [ARM/arm-9-branch revision 277599]A)aeabi4T▒▒
                                                                                                          ����|
UUxB�B
B�UUxUU
�UU
   xUU▒xUUxUU+xUU7UUx<xUUIxUUWUUtest.c}a__DATA_START__end__DATA_END____BSS_END__main__TEXT_END____BSS_START____TEXT_START__.symtab.strtab.shstrtab.text.bss.debug_info.debug_abbrev.debug_aranges.debug_line.debug_str.comment.ARM.attributes.debug_frameUxxUUx&xw2�U@D Od_[0��f0HYop�}
�0�P
        Lf��#90
 TRACE gdbstub::protocol::recv_packet     > <-- $vFile:pread:0,1000,0#ef
UUUUxx#eabstub::protocol::response_writer > --> $F1000;ELF(UU4@4 (
 TRACE gdbstub::protocol::recv_packet     > <-- $vFile:pread:0,1000,10540#b9
 TRACE gdbstub::protocol::response_writer > --> $F0230;UxxUUx&xw2�U@D Od_[0��f0HYop�}
�0�P
        Lf��#cc
 TRACE gdbstub::protocol::recv_packet     > <-- $vFile:pread:0,1000,34#26
 TRACE gdbstub::protocol::response_writer > --> $F1000;UUUUxx#83
 TRACE gdbstub::protocol::recv_packet     > <-- $vFile:pread:0,1000,104b2#e8
 TRACE gdbstub::protocol::response_writer > --> $F02be;.symtab.strtab.shstrtab.text.bss.debug_info.debug_abbrev.debug_aranges.debug_line.debug_str.comment.ARM.attributes.debug_frameUxxUUx&xw2�U@D Od_[0��f0HYop�}
�0�P
        Lf��#d8
 TRACE gdbstub::protocol::recv_packet     > <-- $vFile:pread:0,1000,10078#bf
 TRACE gdbstub::protocol::response_writer > --> $F06f8;s
                                                        .UUx�oUUx�ox    o�ty    o�l4UU0i        o�pint%
                                                                                                      ?:
                                                                                                        ;
                                                                                                         9
                                                                                                          I@▒�B:
                                                                                                                ;
                                                                                                                 9
                                                                                                                  I▒
                                                                                                                    }

                                                                                                                     >
test.c                                                                                                               UUx[�
      UU        gKLgiJ
                      /%ef
                          jtest.cGNU C11 9.2.1 20191025 (release) [ARM/arm-9-branch revision 277599] -mfloat-abi=soft -marm -march=armv4t -g -O0 -std=c11mainGCC: (15:9-2019-q4-0ubuntu1) 9.2.1 20191025 (release) [ARM/arm-9-branch revision 277599]A)aeabi4T▒▒
                                                                                                          ����|
UUxB�B
B�UUxUU
�UU
   xUU▒xUUxUU+xUU7UUx<xUUIxUUWUUtest.c}a__DATA_START__end__DATA_END____BSS_END__main__TEXT_END____BSS_START____TEXT_START__.symtab.strtab.shstrtab.text.bss.debug_info.debug_abbrev.debug_aranges.debug_line.debug_str.comment.ARM.attributes.debug_frameUxxUUx&xw2�U@D Od_[0��f0HYop�}
�0�P
        Lf��#90
 TRACE gdbstub::protocol::recv_packet     > <-- $vFile:pread:0,1000,0#ef
UUUUxx#eabstub::protocol::response_writer > --> $F1000;ELF(UU4@4 (
 TRACE gdbstub::protocol::recv_packet     > <-- $vFile:pread:0,1000,10540#b9
 TRACE gdbstub::protocol::response_writer > --> $F0230;UxxUUx&xw2�U@D Od_[0��f0HYop�}
�0�P
        Lf��#cc
 TRACE gdbstub::protocol::recv_packet     > <-- $vFile:pread:0,1000,34#26
 TRACE gdbstub::protocol::response_writer > --> $F1000;UUUUxx#83
 TRACE gdbstub::protocol::recv_packet     > <-- $vFile:pread:0,1000,104b2#e8
 TRACE gdbstub::protocol::response_writer > --> $F02be;.symtab.strtab.shstrtab.text.bss.debug_info.debug_abbrev.debug_aranges.debug_line.debug_str.comment.ARM.attributes.debug_frameUxxUUx&xw2�U@D Od_[0��f0HYop�}
�0�P
        Lf��#d8
 TRACE gdbstub::protocol::recv_packet     > <-- $vFile:pread:0,1000,10078#bf
 TRACE gdbstub::protocol::response_writer > --> $F06f8;s
                                                        .UUx�oUUx�ox    o�ty    o�l4UU0i        o�pint%
                                                                                                      ?:
                                                                                                        ;
                                                                                                         9
                                                                                                          I@▒�B:
                                                                                                                ;
                                                                                                                 9
                                                                                                                  I▒
                                                                                                                    }

                                                                                                                     >
test.c                                                                                                               UUx[�
      UU        gKLgiJ
                      /%ef
                          jtest.cGNU C11 9.2.1 20191025 (release) [ARM/arm-9-branch revision 277599] -mfloat-abi=soft -marm -march=armv4t -g -O0 -std=c11mainGCC: (15:9-2019-q4-0ubuntu1) 9.2.1 20191025 (release) [ARM/arm-9-branch revision 277599]A)aeabi4T▒▒
                                                                                                          ����|
UUxB�B
B�UUxUU
�UU
   xUU▒xUUxUU+xUU7UUx<xUUIxUUWUUtest.c}a__DATA_START__end__DATA_END____BSS_END__main__TEXT_END____BSS_START____TEXT_START__.symtab.strtab.shstrtab.text.bss.debug_info.debug_abbrev.debug_aranges.debug_line.debug_str.comment.ARM.attributes.debug_frameUxxUUx&xw2�U@D Od_[0��f0HYop�}
�0�P
        Lf��#90
 TRACE gdbstub::protocol::recv_packet     > <-- $vFile:pread:0,1000,0#ef
UUUUxx#eabstub::protocol::response_writer > --> $F1000;ELF(UU4@4 (
 TRACE gdbstub::protocol::recv_packet     > <-- $vFile:fstat:0#bc
 TRACE gdbstub::protocol::response_writer > --> $F40;p#26
 TRACE gdbstub::protocol::recv_packet     > <-- $vFile:fstat:0#bc
 TRACE gdbstub::protocol::response_writer > --> $F40;p#26
 TRACE gdbstub::protocol::recv_packet     > <-- $vFile:pread:0,1000,102fc#1b
 TRACE gdbstub::protocol::response_writer > --> $F0474;UUxUU
�UU
   xUU▒xUUxUU+xUU7UUx<xUUIxUUWUUtest.c}a__DATA_START__end__DATA_END____BSS_END__main__TEXT_END____BSS_START____TEXT_START__.symtab.strtab.shstrtab.text.bss.debug_info.debug_abbrev.debug_aranges.debug_line.debug_str.comment.ARM.attributes.debug_frameUxxUUx&xw2�U@D Od_[0��f0HYop�}
�0�P
        Lf��#31
 TRACE gdbstub::protocol::recv_packet     > <-- $vFile:open:2f746573742e656c66,0,0#1e
 TRACE gdbstub::protocol::response_writer > --> $F00#a6
 TRACE gdbstub::protocol::recv_packet     > <-- $vFile:pread:0,1000,0#ef
UUUUxx#eabstub::protocol::response_writer > --> $F1000;ELF(UU4@4 (
 TRACE gdbstub::protocol::recv_packet     > <-- $vFile:fstat:0#bc
 TRACE gdbstub::protocol::response_writer > --> $F40;p#26
 TRACE gdbstub::protocol::recv_packet     > <-- $vFile:pread:0,1000,10540#b9
 TRACE gdbstub::protocol::response_writer > --> $F0230;UxxUUx&xw2�U@D Od_[0��f0HYop�}
�0�P
        Lf��#cc
 TRACE gdbstub::protocol::recv_packet     > <-- $vFile:pread:0,1000,34#26
 TRACE gdbstub::protocol::response_writer > --> $F1000;UUUUxx#83
 TRACE gdbstub::protocol::recv_packet     > <-- $vFile:pread:0,1000,104b2#e8
 TRACE gdbstub::protocol::response_writer > --> $F02be;.symtab.strtab.shstrtab.text.bss.debug_info.debug_abbrev.debug_aranges.debug_line.debug_str.comment.ARM.attributes.debug_frameUxxUUx&xw2�U@D Od_[0��f0HYop�}
�0�P
        Lf��#d8
 TRACE gdbstub::protocol::recv_packet     > <-- $vFile:pread:0,1000,10078#bf
 TRACE gdbstub::protocol::response_writer > --> $F06f8;s
                                                        .UUx�oUUx�ox    o�ty    o�l4UU0i        o�pint%
                                                                                                      ?:
                                                                                                        ;
                                                                                                         9
                                                                                                          I@▒�B:
                                                                                                                ;
                                                                                                                 9
                                                                                                                  I▒
                                                                                                                    }

                                                                                                                     >
test.c                                                                                                               UUx[�
      UU        gKLgiJ
                      /%ef
                          jtest.cGNU C11 9.2.1 20191025 (release) [ARM/arm-9-branch revision 277599] -mfloat-abi=soft -marm -march=armv4t -g -O0 -std=c11mainGCC: (15:9-2019-q4-0ubuntu1) 9.2.1 20191025 (release) [ARM/arm-9-branch revision 277599]A)aeabi4T▒▒
                                                                                                          ����|
UUxB�B
B�UUxUU
�UU
   xUU▒xUUxUU+xUU7UUx<xUUIxUUWUUtest.c}a__DATA_START__end__DATA_END____BSS_END__main__TEXT_END____BSS_START____TEXT_START__.symtab.strtab.shstrtab.text.bss.debug_info.debug_abbrev.debug_aranges.debug_line.debug_str.comment.ARM.attributes.debug_frameUxxUUx&xw2�U@D Od_[0��f0HYop�}
�0�P
        Lf��#90
 TRACE gdbstub::protocol::recv_packet     > <-- $vFile:pread:0,1000,0#ef
UUUUxx#eabstub::protocol::response_writer > --> $F1000;ELF(UU4@4 (
 TRACE gdbstub::protocol::recv_packet     > <-- $vFile:pread:0,1000,10540#b9
 TRACE gdbstub::protocol::response_writer > --> $F0230;UxxUUx&xw2�U@D Od_[0��f0HYop�}
�0�P
        Lf��#cc
 TRACE gdbstub::protocol::recv_packet     > <-- $vFile:pread:0,1000,34#26
 TRACE gdbstub::protocol::response_writer > --> $F1000;UUUUxx#83
 TRACE gdbstub::protocol::recv_packet     > <-- $vFile:pread:0,1000,104b2#e8
 TRACE gdbstub::protocol::response_writer > --> $F02be;.symtab.strtab.shstrtab.text.bss.debug_info.debug_abbrev.debug_aranges.debug_line.debug_str.comment.ARM.attributes.debug_frameUxxUUx&xw2�U@D Od_[0��f0HYop�}
�0�P
        Lf��#d8
 TRACE gdbstub::protocol::recv_packet     > <-- $vFile:pread:0,1000,10078#bf
 TRACE gdbstub::protocol::response_writer > --> $F06f8;s
                                                        .UUx�oUUx�ox    o�ty    o�l4UU0i        o�pint%
                                                                                                      ?:
                                                                                                        ;
                                                                                                         9
                                                                                                          I@▒�B:
                                                                                                                ;
                                                                                                                 9
                                                                                                                  I▒
                                                                                                                    }

                                                                                                                     >
test.c                                                                                                               UUx[�
      UU        gKLgiJ
                      /%ef
                          jtest.cGNU C11 9.2.1 20191025 (release) [ARM/arm-9-branch revision 277599] -mfloat-abi=soft -marm -march=armv4t -g -O0 -std=c11mainGCC: (15:9-2019-q4-0ubuntu1) 9.2.1 20191025 (release) [ARM/arm-9-branch revision 277599]A)aeabi4T▒▒
                                                                                                          ����|
UUxB�B
B�UUxUU
�UU
   xUU▒xUUxUU+xUU7UUx<xUUIxUUWUUtest.c}a__DATA_START__end__DATA_END____BSS_END__main__TEXT_END____BSS_START____TEXT_START__.symtab.strtab.shstrtab.text.bss.debug_info.debug_abbrev.debug_aranges.debug_line.debug_str.comment.ARM.attributes.debug_frameUxxUUx&xw2�U@D Od_[0��f0HYop�}
�0�P
        Lf��#90
 TRACE gdbstub::protocol::recv_packet     > <-- $vFile:pread:0,1000,0#ef
UUUUxx#eabstub::protocol::response_writer > --> $F1000;ELF(UU4@4 (
 TRACE gdbstub::protocol::recv_packet     > <-- $vFile:pread:0,1000,10540#b9
 TRACE gdbstub::protocol::response_writer > --> $F0230;UxxUUx&xw2�U@D Od_[0��f0HYop�}
�0�P
        Lf��#cc
 TRACE gdbstub::protocol::recv_packet     > <-- $vFile:pread:0,1000,34#26
 TRACE gdbstub::protocol::response_writer > --> $F1000;UUUUxx#83
 TRACE gdbstub::protocol::recv_packet     > <-- $vFile:pread:0,1000,104b2#e8
 TRACE gdbstub::protocol::response_writer > --> $F02be;.symtab.strtab.shstrtab.text.bss.debug_info.debug_abbrev.debug_aranges.debug_line.debug_str.comment.ARM.attributes.debug_frameUxxUUx&xw2�U@D Od_[0��f0HYop�}
�0�P
        Lf��#d8
 TRACE gdbstub::protocol::recv_packet     > <-- $vFile:pread:0,1000,10078#bf
 TRACE gdbstub::protocol::response_writer > --> $F06f8;s
                                                        .UUx�oUUx�ox    o�ty    o�l4UU0i        o�pint%
                                                                                                      ?:
                                                                                                        ;
                                                                                                         9
                                                                                                          I@▒�B:
                                                                                                                ;
                                                                                                                 9
                                                                                                                  I▒
                                                                                                                    }

                                                                                                                     >
test.c                                                                                                               UUx[�
      UU        gKLgiJ
                      /%ef
                          jtest.cGNU C11 9.2.1 20191025 (release) [ARM/arm-9-branch revision 277599] -mfloat-abi=soft -marm -march=armv4t -g -O0 -std=c11mainGCC: (15:9-2019-q4-0ubuntu1) 9.2.1 20191025 (release) [ARM/arm-9-branch revision 277599]A)aeabi4T▒▒
                                                                                                          ����|
UUxB�B
B�UUxUU
�UU
   xUU▒xUUxUU+xUU7UUx<xUUIxUUWUUtest.c}a__DATA_START__end__DATA_END____BSS_END__main__TEXT_END____BSS_START____TEXT_START__.symtab.strtab.shstrtab.text.bss.debug_info.debug_abbrev.debug_aranges.debug_line.debug_str.comment.ARM.attributes.debug_frameUxxUUx&xw2�U@D Od_[0��f0HYop�}
�0�P
        Lf��#90
 TRACE gdbstub::protocol::recv_packet     > <-- $vFile:pread:0,1000,0#ef
UUUUxx#eabstub::protocol::response_writer > --> $F1000;ELF(UU4@4 (
 TRACE gdbstub::protocol::recv_packet     > <-- $vFile:pread:0,1000,10540#b9
 TRACE gdbstub::protocol::response_writer > --> $F0230;UxxUUx&xw2�U@D Od_[0��f0HYop�}
�0�P
        Lf��#cc
 TRACE gdbstub::protocol::recv_packet     > <-- $vFile:pread:0,1000,34#26
 TRACE gdbstub::protocol::response_writer > --> $F1000;UUUUxx#83
 TRACE gdbstub::protocol::recv_packet     > <-- $vFile:pread:0,1000,104b2#e8
 TRACE gdbstub::protocol::response_writer > --> $F02be;.symtab.strtab.shstrtab.text.bss.debug_info.debug_abbrev.debug_aranges.debug_line.debug_str.comment.ARM.attributes.debug_frameUxxUUx&xw2�U@D Od_[0��f0HYop�}
�0�P
        Lf��#d8
 TRACE gdbstub::protocol::recv_packet     > <-- $vFile:pread:0,1000,10078#bf
 TRACE gdbstub::protocol::response_writer > --> $F06f8;s
                                                        .UUx�oUUx�ox    o�ty    o�l4UU0i        o�pint%
                                                                                                      ?:
                                                                                                        ;
                                                                                                         9
                                                                                                          I@▒�B:
                                                                                                                ;
                                                                                                                 9
                                                                                                                  I▒
                                                                                                                    }

                                                                                                                     >
test.c                                                                                                               UUx[�
      UU        gKLgiJ
                      /%ef
                          jtest.cGNU C11 9.2.1 20191025 (release) [ARM/arm-9-branch revision 277599] -mfloat-abi=soft -marm -march=armv4t -g -O0 -std=c11mainGCC: (15:9-2019-q4-0ubuntu1) 9.2.1 20191025 (release) [ARM/arm-9-branch revision 277599]A)aeabi4T▒▒
                                                                                                          ����|
UUxB�B
B�UUxUU
�UU
   xUU▒xUUxUU+xUU7UUx<xUUIxUUWUUtest.c}a__DATA_START__end__DATA_END____BSS_END__main__TEXT_END____BSS_START____TEXT_START__.symtab.strtab.shstrtab.text.bss.debug_info.debug_abbrev.debug_aranges.debug_line.debug_str.comment.ARM.attributes.debug_frameUxxUUx&xw2�U@D Od_[0��f0HYop�}
�0�P
        Lf��#90
 TRACE gdbstub::protocol::recv_packet     > <-- $vFile:pread:0,1000,0#ef
UUUUxx#eabstub::protocol::response_writer > --> $F1000;ELF(UU4@4 (
 TRACE gdbstub::protocol::recv_packet     > <-- $vFile:fstat:0#bc
 TRACE gdbstub::protocol::response_writer > --> $F40;p#26
 TRACE gdbstub::protocol::recv_packet     > <-- $vFile:fstat:0#bc
 TRACE gdbstub::protocol::response_writer > --> $F40;p#26
 TRACE gdbstub::protocol::recv_packet     > <-- $vFile:pread:0,1000,102fc#1b
 TRACE gdbstub::protocol::response_writer > --> $F0474;UUxUU
�UU
   xUU▒xUUxUU+xUU7UUx<xUUIxUUWUUtest.c}a__DATA_START__end__DATA_END____BSS_END__main__TEXT_END____BSS_START____TEXT_START__.symtab.strtab.shstrtab.text.bss.debug_info.debug_abbrev.debug_aranges.debug_line.debug_str.comment.ARM.attributes.debug_frameUxxUUx&xw2�U@D Od_[0��f0HYop�}
�0�P
        Lf��#31
 TRACE gdbstub::protocol::recv_packet     > <-- $vFile:pread:0,1000,10078#bf
 TRACE gdbstub::protocol::response_writer > --> $F06f8;s
                                                        .UUx�oUUx�ox    o�ty    o�l4UU0i        o�pint%
                                                                                                      ?:
                                                                                                        ;
                                                                                                         9
                                                                                                          I@▒�B:
                                                                                                                ;
                                                                                                                 9
                                                                                                                  I▒
                                                                                                                    }

                                                                                                                     >
test.c                                                                                                               UUx[�
      UU        gKLgiJ
                      /%ef
                          jtest.cGNU C11 9.2.1 20191025 (release) [ARM/arm-9-branch revision 277599] -mfloat-abi=soft -marm -march=armv4t -g -O0 -std=c11mainGCC: (15:9-2019-q4-0ubuntu1) 9.2.1 20191025 (release) [ARM/arm-9-branch revision 277599]A)aeabi4T▒▒
                                                                                                          ����|
UUxB�B
B�UUxUU
�UU
   xUU▒xUUxUU+xUU7UUx<xUUIxUUWUUtest.c}a__DATA_START__end__DATA_END____BSS_END__main__TEXT_END____BSS_START____TEXT_START__.symtab.strtab.shstrtab.text.bss.debug_info.debug_abbrev.debug_aranges.debug_line.debug_str.comment.ARM.attributes.debug_frameUxxUUx&xw2�U@D Od_[0��f0HYop�}
�0�P
        Lf��#90
 TRACE gdbstub::protocol::recv_packet     > <-- $qSymbol::#5b
 INFO  gdbstub::gdbstub_impl              > Unknown command: Ok("qSymbol::")
 TRACE gdbstub::protocol::response_writer > --> $#00
 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     > <-- $qC#b4
 INFO  gdbstub::gdbstub_impl              > Unknown command: Ok("qC")
 TRACE gdbstub::protocol::response_writer > --> $#00
 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="0x0" length="0x100000000"/>
</memory-map>#76
 TRACE gdbstub::protocol::recv_packet     > <-- $qXfer:memory-map:read::f4,ffb#82
 TRACE gdbstub::protocol::response_writer > --> $l#6c
 TRACE gdbstub::protocol::recv_packet     > <-- $m55550000,4#61
 TRACE gdbstub::protocol::response_writer > --> $04b02de5#26
 TRACE gdbstub::protocol::recv_packet     > <-- $m5554fffc,4#35
 TRACE gdbstub::protocol::response_writer > --> $00000000#7e
 TRACE gdbstub::protocol::recv_packet     > <-- $m55550000,4#61
 TRACE gdbstub::protocol::response_writer > --> $04b02de5#26
 TRACE gdbstub::protocol::recv_packet     > <-- $m5554fffc,4#35
 TRACE gdbstub::protocol::response_writer > --> $00000000#7e
 TRACE gdbstub::protocol::recv_packet     > <-- $m55550000,2#5f
 TRACE gdbstub::protocol::response_writer > --> $04b0#f6
 TRACE gdbstub::protocol::recv_packet     > <-- $m5554fffe,2#35
 TRACE gdbstub::protocol::response_writer > --> $0000#7a
 TRACE gdbstub::protocol::recv_packet     > <-- $m5554fffc,2#33
 TRACE gdbstub::protocol::response_writer > --> $0000#7a
 TRACE gdbstub::protocol::recv_packet     > <-- $m55550000,2#5f
 TRACE gdbstub::protocol::response_writer > --> $04b0#f6
 TRACE gdbstub::protocol::recv_packet     > <-- $m5554fffe,2#35
 TRACE gdbstub::protocol::response_writer > --> $0000#7a
 TRACE gdbstub::protocol::recv_packet     > <-- $m5554fffc,2#33
 TRACE gdbstub::protocol::response_writer > --> $0000#7a
 TRACE gdbstub::protocol::recv_packet     > <-- $m55550000,4#61
 TRACE gdbstub::protocol::response_writer > --> $04b02de5#26
 TRACE gdbstub::protocol::recv_packet     > <-- $m5554fffc,4#35
 TRACE gdbstub::protocol::response_writer > --> $00000000#7e
 TRACE gdbstub::protocol::recv_packet     > <-- $m55550000,4#61
 TRACE gdbstub::protocol::response_writer > --> $04b02de5#26
 TRACE gdbstub::protocol::recv_packet     > <-- $m5554fffc,4#35
 TRACE gdbstub::protocol::response_writer > --> $00000000#7e
 TRACE gdbstub::protocol::recv_packet     > <-- $m55550000,4#61
 TRACE gdbstub::protocol::response_writer > --> $04b02de5#26
 TRACE gdbstub::protocol::recv_packet     > <-- $m55550000,4#61
 TRACE gdbstub::protocol::response_writer > --> $04b02de5#26
 TRACE gdbstub::protocol::recv_packet     > <-- $m55550000,4#61
 TRACE gdbstub::protocol::response_writer > --> $04b02de5#26

@bet4it
Copy link
Contributor Author

bet4it commented Aug 23, 2021

Do we need something like ExecFileOutput? Then we may also need MemoryMapOutput...

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.

Do we need something like ExecFileOutput? Then we may also need MemoryMapOutput...

If you read the PR that added MemoryMap, there is an interesting long-form discussion on why we landed on that particular API.

For this feature, I believe it's sufficient to leave the API as-is, and have the target return the filename as a &[u8].


That said, upon re-reviewing the qXfer docs, I realized that these methods do support returning an error condition via an Exx response. As such, this API should be using the TargetResult infrastructure.

Moreover, we should also change MemoryMap to use TargetResult as well, but that's probably something best left to a follow-up fixup commit / PR.

examples/armv4t/gdb/host_io.rs Show resolved Hide resolved
examples/armv4t/gdb/host_io.rs Outdated Show resolved Hide resolved
src/gdbstub_impl/ext/exec_file.rs Show resolved Hide resolved
src/target/ext/exec_file.rs Outdated Show resolved Hide resolved
@bet4it
Copy link
Contributor Author

bet4it commented Aug 24, 2021

For this feature, I believe it's sufficient to leave the API as-is, and have the target return the filename as a &[u8].

Then we can't construct the result dynamically on no_std platforms?


I think we can also change MemoryMap to use TargetResult in this PR?

@daniel5151
Copy link
Owner

Then we can't construct the result dynamically on no_std platforms?

With the current signature, you can still change the result dynamically no-problem. The lifetime of &self and the returned &[u8] are tied to one another, so as long as the buffer lives as long as the target, you can change it's value however you like. e.g:

// desugars into something akin to
// fn get_exec_file<'a>(&'a self, _pid: Option<Pid>) -> TargetResult<&'a [u8], Self> {
fn get_exec_file(&self, _pid: Option<Pid>) -> TargetResult<&[u8], Self> {
    // assume self.exec_file_buf: [u8; BUF_SIZE]
    let s = if something { b"option/a" } else { b"option/b" };
    self.exec_file_buf[..s.len()].copy_from_slize(s);
    &self.exec_file_buf
}

What you can't do with the current API is stream a stack-allocated buffer back to the client:

fn get_exec_file(&self, _pid: Option<Pid>) -> TargetResult<&[u8], Self> {
    let buf = [0; 32];
    let s = b"exec/path";
    buf[..s].copy_from_slice(s);
    &buf // ERROR, cannot pass back stack allocated buffer 
}

I would consider this to be a reasonable tradeoff in the case of qXfer packets, as there's always the potential of having the client read different sub-slices of the result, and in those cases, I'd wager most implementation would probably end up "caching" any dynamically-constructed string somewhere regardless. If this is going to be the behavior most of the time regardless, we might as well simplify the API, and push the nitty-gritty details of reading subslices of the resulting string into the gdbstub itself...

I think we can also change MemoryMap to use TargetResult in this PR?

I'd prefer to keep PRs focused on a single feature. If you'd like, I'd more than welcome a follow-up fixup PR. If not, I'll just pop open a tracking issue after this PR is merged to remind myself to do this cleanup at some point before releasing 0.6.

@bet4it
Copy link
Contributor Author

bet4it commented Aug 24, 2021

// assume self.exec_file_buf: [u8; BUF_SIZE]

Oh no, this means that if I want to construct the result dynamically, I must have a buf with fixed size defined in the extension? How long of the buf will be enough? We must define such buf in every extension need to construct the result dynamically? And if we can do it this way, why we don't use it in Host I/O pread?

This reminds me a variable: PacketSize, which we sent to client in the first packet to declare the length of buf we have. We use the length of PacketBuf.buf as PacketSize. We know that the buf is always enough to accept the packet, and when I wrote gdbserver of myself, I assume the buf is always enough to be used to prepare the response to be sent.

Is that true? In GDB docs:

‘PacketSize=bytes

The remote stub can accept packets up to at least bytes in length. GDB will send packets up to this size for bulk transfers, and will never send larger packets. This is a limit on the data characters in the packet, including the frame and checksum. There is no trailing NUL byte in a remote protocol packet; if the stub stores packets in a NUL-terminated format, it should allow an extra byte in its buffer for the NUL.

Oh sad, the docs don't guarantee the response won't exceed PacketSize. But at least, the length of response can be controlled by us, and with the experience of implementing Host I/O, we can reply with data shorter than request, we only need to tell the client the length afore. And you may notice the request size sent by GDB client won't exceed PacketSize in real use.

And there is an interesting sentence in the docs: If this stub feature is not supported, GDB guesses based on the size of the ‘g’ packet response.

Okey, let's assume the length of all responses won't excced PacketSize, then we can use PacketBuf.buf as a place to prepare the response. This solves the problem 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 you mentioned in #53. And we can use PacketBuf.buf as exec_file_buf.

And I find we already use the buf in this way:

/// Notably, `PacketBuf` will _always_ maintain a mutable reference back to the
/// _entire_ underlying packet buffer. This makes it possible to re-use any
/// unused buffer space as "scratch" space. One notable example of this use-case
/// is the 'm' packet, which recycles unused packet buffer space as a buffer for
/// the target's `read_memory` method.

// the total packet buffer currently looks like:
//
// +------+--------------------+-------------------+-------+-----------------+
// | "$m" | addr (hex-encoded) | len (hex-encoded) | "#XX" | empty space ... |
// +------+--------------------+-------------------+-------+-----------------+
//
// Unfortunately, while `len` can be hex-decoded right here and now into a
// `usize`, `addr` corresponds to a Target::Arch::Usize, which requires holding
// on to a valid &[u8] reference into the buffer.
//
// While it's not _perfectly_ efficient, simply leaving the decoded addr in
// place and wasting a couple bytes is probably the easiest way to tackle this
// problem:
//
// +------+------------------+------------------------------------------------+
// | "$m" | addr (raw bytes) | usable buffer ... |
// +------+------------------+------------------------------------------------+

(Oh wait, shouldn't len also correspond to Target::Arch::Usize?)

We use only part of buf to prepare the response, because we also need a buf to addr which will be decoded afterwards. And we use part of buf in this way in many places.

Base::m(cmd) => {
let buf = cmd.buf;
let addr = <T::Arch as Arch>::Usize::from_be_bytes(cmd.addr)
.ok_or(Error::TargetMismatch)?;

But after this decode, this part of buf will never be used. So we can use the full buf to prepare the response afterwards? But this may not easy to implement?

It will be easier if we could know the type T in ParseCommand. After ParseCommand, the buf is totally free, we can use it as a temporary buf when implement the commands.

This recalls me another thing, is Target::Arch::Usize necessary? Why not always use it with u64 which is always enough to make life easier? I think there is no problem with correctness. When I implement my project which uses Dynamic Arch selection to handle with both 32bit and 64bit architectures, I just fill Usize with u64.

And there is another situation likes Host I/O pwrite, which we need the buf to store input data. But it's lucky such operation don't need us to reply something complex, or we could copy the input data to a stack-allocated buffer?

@daniel5151
Copy link
Owner

Lots of good thoughts here, many of which I've had myself in the past!

To kick off the discussion, consider the following observation about the GDB RSP:

While incoming data to the target will only have a max length of PacketBuf bytes, outgoing data from the target can be as looooooong as we want, as the protocol assumes that the GDB client has "unlimited" storage on it's end, and doesn't need to worry about overflowing a fixed-size packet buffer. This also means that the PacketSize option is only relevant for the size of incoming data, and has absolutely no bearing on the maximum size out the output data!

In other words: your assumption that "the [PacketBuf] is always enough to be used to prepare the response to be sent" is incorrect. The GDB RSP makes no assumptions on how response data is/isn't buffered on the client.

Instead, almost every packet with a non-trivial, dynamically sized response (vFile, qXfer, etc...) will include a provision whereby the target is allowed to return less data than requested, which typically results in the GDB RSP issuing a subsequent request the read more of the file from the appropriate offset + len.

With this in mind, yes, you're absolutely right: we could offer an API that allows the user to write response data into the unused parts of the qXfer packet, whereby if the packet isn't long enough, the user will have to handle writing only the request subslice of the data into the buffer.

The point I'm trying to make is that while this would technically be more flexible, and allow certain targets to entirely bypass allocating their own buffers, I'd wager that most implementations would end up looking something like this:

fn get_exec_file(&self, pid: Option<Pid>, buf: &mut [u8], offset: usize) -> TargetResult<usize, Self> {
    let mut stack_buf = [0; MAX_FILENAME_LEN]; // packet buffer might not be big enough for file name
    let file_name = self.get_exec_file_impl(&stack_buf, pid); // performs syscall / whatever
    copy_dst_src_with_truncation_logic(buf, file_name, offset) // force user to implement offset logic
}

In more complex cases - such as MemoryMap - that middle-step of actually generating the output data isn't something the implementer would want to repeat multiple times, as it'd be wasting computation, so it seems very likely that they'd just compute it once, and then stash it somewhere like self.memory_map: Option<String> - i.e: allocating their own buffer.

And in fact, this is pretty much what the GDB RSP expects! Consider the following qXfer docs (emphasis mine):

Read uninterpreted bytes from the target’s special data area ...

The idea being that this data is in-fact stored in some kind of dedicated "special data area" from which arbitrary data can be read.

For this reason, I would prefer to keep the qXfer APIs as they currently are, without any of the complex callback / streaming logic other types of commands support. The cost/benefit of API complexity, maintenance, and downstream implementation effort just isn't worth it for the additional flexibility a callback/streaming based API would enable.

This recalls me another thing, is Target::Arch::Usize necessary? Why not always use it with u64 which is always enough to make life easier? I think there is no problem with correctness. When I implement my project which uses Dynamic Arch selection to handle with both 32bit and 64bit architectures, I just fill Usize with u64.

gdbstub's minimal configuration is designed to run on incredibly barebone systems (e.g: an AVR microcontroller) with the smallest possible footprint. By using Target::Arch::Usize instead of a "catch all" like u64, the generated code will be a lot tighter, and run faster + with a smaller memory footprint.

Aside from that, there's also the more "philosophical" aspect of "use the most appropriate type for the situation". When we're reading data from the the target system's host / special area (such as in the case of qXfer), usize is the right thing to use, since we're addressing into the target system's host. When we are reading data from the target itself, such as files / memory, we want to use Target::Arch::Usize, as there's no guarantee that the target host's address width matches the target's address width (e.g: puppeting a 32 bit system within a 64 bit GDB stub).

That said, I think you're right that there are a few places where it might make more sense to use usize vs. Target::Arch::Usize, and vice-versa. I should open a task to do an audit of all current methods, and see if there are any that I should change...

@bet4it
Copy link
Contributor Author

bet4it commented Aug 24, 2021

as the protocol assumes that the GDB client has "unlimited" storage on it's end, and doesn't need to worry about overflowing a fixed-size packet buffer

How do you get this conclusion? But, at least in practice, after we set PacketSize=1000, GDB will request $vFile:pread:0,1000,0#ef, the request length is 0x1000. And if we dump memory with dump binary memory elf.bin 0x55550000 0x55552000, GDB will request $m55550000,800#c5, the request length is 0x800, because the result is encoded in hex. Is that just a coincidence?

@daniel5151
Copy link
Owner

daniel5151 commented Aug 24, 2021

This goes back to our previous discussion in #66 about gdbstub conforming to a specific implementation vs. conforming to the spec. The current "reference" GDB client may very-well clamp response sizes to PacketSize, but this behavior is not guaranteed, nor should it be relied upon.

Moreover, you'll find that many of the "barebones" GDB stubs will implement their output routines by writing data directly to UART registers, without using an intermediate buffer of any kind.

Of course, there's always the chance that I misread the spec, so if you can find the line that disputes my claim, I would be more than happy to reevaluate my approach.

@bet4it
Copy link
Contributor Author

bet4it commented Aug 25, 2021

The evidence that PacketSize also impacts on the maximum size out the output data:

  1. In Host I/O doc:

‘vFile:pread: fd, count, offset’

Read data from the open file corresponding to fd. Up to count bytes will be read from the file, starting at offset relative to the start of the file. The target may read fewer bytes; common reasons include packet size limits and an end-of-file condition. The number of bytes read is returned. Zero should only be returned for a successful read at the end of the file, or if count was zero.

At least, GDB client could not have "unlimited" storage on it's end.

  1. If we change the code to reply more data than request when pread, we will get an error:

https://github.com/bminor/binutils-gdb/blob/f120bef247a0dba322fab7138ebbc1a2431dadbe/gdbsupport/rsp-low.cc#L240

And pread stop work.

Yes, I know that's just another implementation rather than spec, but at least that means outgoing data from the target can be as looooooong as we want is not true.

  1. There are two interesting commands:
(gdb) show remote memory-read-packet-size 
The memory-read-packet-size is 0 (default). Packets are limited to 4096 bytes.
(gdb) show remote memory-write-packet-size 
The memory-write-packet-size is 0 (default). Packets are limited to 4096 bytes.

These evidence may not be so persuasive. But at least, if we just use PacketBuf.buf to prepare the response, there are two situations:

  1. The client will never request size larger that PacketSize (in most situations): All things work well, and we don't need extra buffer.
  2. The client request size larger that PacketSize: We can just reply data within the restriction of PacketSize. All things still work well.

And could you tell me, how to get an arbitrary length buffer on no_std platform?


I'd wager that most implementations would end up looking something like this:

fn get_exec_file(&self, pid: Option<Pid>, buf: &mut [u8], offset: usize) -> TargetResult<usize, Self> {
    let mut stack_buf = [0; MAX_FILENAME_LEN]; // packet buffer might not be big enough for file name
    let file_name = self.get_exec_file_impl(&stack_buf, pid); // performs syscall / whatever
    copy_dst_src_with_truncation_logic(buf, file_name, offset) // force user to implement offset logic
}

get_exec_file doesn't have an offset argument. We have offset argument in pread, will we use it in this way?

fn pread(&self, buf: &mut [u8], fd: usize, count: usize, offset: usize) -> TargetResult<usize, Self> {
    let mut stack_buf = [0; MAX_FILE_LEN];
    let file_data = self.read_full_file(&stack_buf, fd);
    copy_dst_src_with_truncation_logic(buf, file_data, offset, count);
}

No. Because we have pread on Linux, or SeekFrom on rust.
Many other commands with offset may also be file-based.

And even we must implement in this way, I think there is no problem. Dynamically allocated stack-based buffer could be better than a fixed size heap buffer. And if we want to use heap buffer, we can still use it:

fn get_exec_file(&self, pid: Option<Pid>, buf: &mut [u8], offset: usize) -> TargetResult<usize, Self> {
    self.get_exec_file_impl(&self.exec_file_buf, pid); // performs syscall / whatever
    copy_dst_src_with_truncation_logic(buf, &self.exec_file_buf, offset) // force user to implement offset logic
}

In more complex cases - such as MemoryMap - that middle-step of actually generating the output data isn't something the implementer would want to repeat multiple times, as it'd be wasting computation, so it seems very likely that they'd just compute it once, and then stash it somewhere like self.memory_map: Option<String> - i.e: allocating their own buffer.

But the current implementation doesn't solve this problem. If we want to construct the result of MemoryMap dynamically, we still need to recompute the result each time it's called, or store the result somewhere.


Moreover, you'll find that many of the "barebones" GDB stubs will implement their output routines by writing data directly to UART registers, without using an intermediate buffer of any kind.

Yes, but that doesn't mean "unlimited". We can send data with arbitrary length, but we only need to send with the size requested.


And you don't reply my questions previously:

Oh no, this means that if I want to construct the result dynamically, I must have a buf with fixed size defined in the extension? How long of the buf will be enough? We must define such buf in every extension need to construct the result dynamically? And if we can do it this way, why we don't use it in Host I/O pread?

@daniel5151
Copy link
Owner

but at least that means outgoing data from the target can be as looooooong as we want is not true.

Ah, I think you misunderstood what I meant here. I never said that we would be allowed to send more data than the target requested. What I was trying to say here is that the possible length of data the client requests can exceed the size of PacketBuf.

i.e: based on the spec, it's totally fair game for the client to request data of length 1000 even if the packet buffer is only 100 long. In that case, the target is allowed to return less than 1000 bytes (as pointed out in point 1), but at the same time, it's totally within spec to stream the 1000 bytes back to the client without first copying them into an output buffer (e.g: by banging them directly over the UART)

Just because the reference GDB client implementation chooses to clamp response size to a number close to the PacketBuf size, doesn't mean that's part of the spec.

And could you tell me, how to get an arbitrary length buffer on no_std platform?

Many options! You could unsafely use alloca, pull in the alloc crate to support dynamic allocation, write a custom allocator, etc...

That said, if you're implementing this extension on a super barebones no_std system, you'd probably end up using a fixed-size buffer + panicking if you exceed it's length.

One of the main motivators behind me pushing for this simpler-but-less-flexible API is that I don't believe a AVR microcontroller - where every byte is precious - will end up implementing these metadata-heavy qXfer extensions. For pretty much every other target, I think it's more than reasonable to expect they can afford to allocate some kind of buffer for these kinds of extensions.

(if they're really worried about space, you could always reuse the same memory buffer for multiple protocol extensions, such as MemoryMap and ExecFile)

Hopefully this answers your question wrt. "How long of the buf will be enough?"
The answer to that is entirely target + usecase dependent.


Let me ask you this: say we go with the "we give you a &mut [u8] buffer from the underlying PacketBuf" approach, and the client requests data that exceeds the length of the buffer. What then?

On the first call, we'd fill the buffer as best we could. But there's still data to be read, so we'd have to call the function again to fetch the rest of the data. i.e: we'd need to add a offset argument to the method.

On this subsequent call, what happens? Well, if the underlying syscall supports an offset (such as pread), sure, no problem, we can just plumb that through and things will Just Work™️.

Unfortunately, that is not going to be the case of ExecFile nor MemoryMap. In these cases, the "black box" implementation I was suggested - which may be syscall backed, or whatever - will need to write it's data into some kind of large-enough buffer. In the exec-file example I gave, that's the MAX_FILENAME_LEN buffer. For MemoryMap, you'd need something similar.

sidenote: Now that I think about it, we totally could tweak the Pread API to provide the user access to the underlying PacketBuf-backed &mut [u8], saving them the hassle of stack/heap allocating a buffer themselves. The lifetimes should still line up, even with the callback based API... Anyways, that's not important to this specific PR, but we could discuss it further in a follow up issue/discussion.

What I'm saying is that instead of recalculating the MemoryMap / ExecFile each time partial data is requested, it's faaaaaar more likely that an implementation would simply pre-calculate / cache / hardcode the return value of the MemoryMap / ExecFile when the extension is first called (if not before that, during other initialization steps), and then read data back from that pre-allocated buffer when the client requests data.

And indeed, you point this out yourself:

But the current implementation doesn't solve this problem. If we want to construct the result of MemoryMap dynamically, we still need to recompute the result each time it's called, or store the result somewhere.

These cache buffers are what the GDB RSP considers to be the "target's special data area" - i.e: a part of memory that is used by the GDB stub itself to store metadata about the target being debugged.


Long story short, the only benefit I see of plumbing through a PacketBuf-backed &mut [u8] buffer + read offset API to the client is that there is a slight chance that the data is small enough to fit in the buffer, and the implementation doesn't have to allocate its own buffer, and could simply generate + write the data directly into that buffer. And for my money, I don't think that extra "fast path" potential is worth the additional API complexity for APIs where it's expected that the data will need to be generated / cached somewhere regardless.

@bet4it
Copy link
Contributor Author

bet4it commented Aug 26, 2021

Many options! You could unsafely use alloca, pull in the alloc crate to support dynamic allocation, write a custom allocator, etc...

Oh, but what you said before in #66 (comment):

Worse still, on no_std platforms where Vec isn't available, it might not even be possible to implement this method, as there wouldn't be any way to "steam" data directly from a file back to GDB.

If you think return &[u8] is acceptable, and I can replace the HostIoOutput implementation with a return of &[u8], I totally agree with it. The API is simpler and easy to understand, and std is enabled in most situations.

If you allow me to do this, then other questions is not need to discuss.

@daniel5151
Copy link
Owner

daniel5151 commented Aug 26, 2021

As a point of clarification (because I'm not sure how well you understand Rust's no_std / std split), my comment from 66 and my last comment are both correct, and don't contradict each-other in any way. On certain no_std platforms (e.g: some beefier ARM Cortex SoC, for example), there may be enough resources available to provide a global dynamic allocator. On other no_std platforms (e.g: on tiny AVRs), there likely aren't enough resources for a global dyanmic allocator.

That said, I agree that the wording of "it might not even be possible to implement this method" was misleading, and incorrect. You could implement the method by allocating a buffer tied to the lifetime of self, at the expense of using more memory.


The biggest driving force for why I prefer a simpler "return &[u8]" API for qXfer methods vs. "write callback + offset" API for vFile methods are the following two points:

  1. qXfer methods, in the spec, specifically refer to reading data from the "target’s special data area". This heavily implies that these bits of metadata are going to be cached somewhere in memory - either as completely static strings included in the binary at compile time, or dynamically generated once + cached in-memory. As such, it is totally reasonable to keep the API simple, and assume that the lifetime of the response data will outlive the handler method's invocation. i.e: it's unlikely that an implementation would use a stack-allocated buffer to generate the data once, and then stream it back to the client - especially in the case of qXfer methods that transmit potentially large amounts of data, such as XML files.
  2. the vFile methods are very amenable to the pattern of "allocate a temp buffer on the stack, read some data, and then send it back to the client". unlike qXfer methods, there is no expectation that the data being requested will be resident in-memory, and that the target will most certainly need to fetch the data from somewhere.

.
.
.

Shoot.

As I finished writing those two points, I realized that my logic is flawed, and that qXfer APIs could absolutely be file-backed, and the idea that they'd be in memory as part of the "target’s special data area" is totally bogus.

If so, then it means that I'll need to change the APIs of TargetXML and MemoryMap to support a callback + offset API.


So, for action items:

  • Definitely change the API of ExecFile to use a offset + callback based API
  • Optionally look into the plumbing required to pass-through a PacketBuf &mut [u8] buffer as part of the API, giving memory-poor targets the option of using this fixed-size buffer that's "lying around" for writing intermediate data into.

That latter point is entirely orthogonal to the whole "return &[u8] vs. callback" question, but nonetheless, you've convinced me that providing this option would be nice - both in these qXfer APIs, and in the vFile pread and readlink APIs. If targets want to use this buffer, they can, and if not, they can always allocate their own.

And as a reminder, to keep PRs focused, please don't go changing any other APIs as part of this PR. If we want to start tweaking other stuff, lets do it in a follow up.


sigh, I feel bad for stretching out this discussion for long. sometimes it takes me a lot of back and forth and composing my own thoughts to get everything straightened out...

@bet4it
Copy link
Contributor Author

bet4it commented Aug 28, 2021

Oh you finally agree with me. In fact, if you take a look at other commands of qXfer, btrace/libraries/threads, all of them relate with current state, so they must be recalculated each time they are called.

And I must remind you, after you choose the two-branch implementation in #64, all of them must be called and recalculated at least twice, even if the result can be filled in one packet.

I think we really should pass-through a PacketBuf &mut [u8] buffer as part of the API. I don't see any drawbacks in it compared with the current implementation, and we will benefit a lot with it. The API is simpler than callback based API, saves a lot of memory especially for barebone systems, solves the problem you mentioned in #53 so we can take a step forward to dynamic arch selection, and most important, we don't violate any spec.

Let me ask you this: say we go with the "we give you a &mut [u8] buffer from the underlying PacketBuf" approach, and the client requests data that exceeds the length of the buffer. What then?

That's quite easy:

let handler_status = match command {
    HostIo::vFilePread(cmd) => {
        ops.pread(buf, cmd.fd, count.min(buf.len()), offset);
        ......
        HandlerStatus::Handled
    }
};

Client won't know about it, it will just send another with a new offset. And the implementation won't sense it, it just need to fill the buffer with data of requested size.

there is a slight chance that the data is small enough to fit in the buffer.

Slight? No, it's the most common situation. registers data can be filled in the buffer, the m/M or pread commands will request data with the size same as PacketSize(at least in practice), and many other commands' reply are small enough to be sent in one packet, or people could choose enlarging PacketBuf to avoid recalculating.


I think we could just delay this PR until we implement passing the buf as a argument. I really don't want to write another ExecfileOutput which is not elegant.
I hope this can be done by you, because this change is related with the basic infrastructure of the code. If you really don't have time, I can try it, but I may need you give me some starts. And I mentioned some questions we may meet when we implement it before:

It will be easier if we could know the type T in ParseCommand. After ParseCommand, the buf is totally free, we can use it as a temporary buf when implement the commands.
And there is another situation likes Host I/O pwrite, which we need the buf to store input data. But it's lucky such operation don't need us to reply something complex, or we could copy the input data to a stack-allocated buffer?

@daniel5151
Copy link
Owner

Sorry about this massive wall of text.

I really tried to address every point + answer all your questions + make my opinion as clear as possible.


all of them relate with current state, so they must be recalculated each time they are called.

[...] all of them must be called and recalculated at least twice, even if the result can be filled in one packet.

So, I'm not sure about the "recalculated" bit...

More specifically, the "trigger" for recalculating the values (at least in a in a well architected application) shouldn't be the actual qXfer-triggered handler invocation. Rather, some other lifecycle event (e.g: startup, on-library-load, etc...) would cause the underlying value to get recalculated + cached (in memory / on disk), such that the actual handler itself simply reads the pre-calculated data.

Of course, you could also generate the data on the fly for these qXfer packets, but you run into the very real problem of redoing the same work work_len / packet_buffer_size times (as you send back only partial subslices of the data on each handler invocation).

If this implementation pattern isn't immediately obvious, we should work on improving the documentation around these qXfer-backed extensions to explain how the method is intended to be implemented.

And I must remind you, after you choose the two-branch implementation in #64, all of them must be called and recalculated at least twice, even if the result can be filled in one packet.

Yeah, we could probably re-introduce the third branch as an optimization.

The reason I removed it in the first place is because I wanted the "minimal" gdbstub configuration to be as lean as possible, but those couple extra bytes probably aren't the end of the world for slightly more efficient data transfer over the wire...

Of course, this is orthogonal to the rest of the discussion. If we add it or not doesn't change the fact that there will be plenty of times where the handlers will get called multiple times regardless.

I think we really should pass-through a PacketBuf &mut [u8] buffer as part of the API. I don't see any drawbacks in it compared with the current implementation, and we will benefit a lot with it. The API is simpler than callback based API, saves a lot of memory especially for barebone systems, solves the problem you mentioned in #53 so we can take a step forward to dynamic arch selection, and most important, we don't violate any spec.

A few things here:

  • If I understand what you're suggesting, you want to remove the callback-based API entirely, and swap to a "extra PacketBuf space" based one? To be clear, that is not what I suggested, and we will not be removing the callback API. I am all for including the &mut [u8] buffer alongside the callback code, as an optional buffer that the implementation can use for whatever, but the actual operation of writing data back would still be done via callback.
    • I've done extensive profiling and stared at much asm code, and LLVM in release mode is able to inline + optimize the callback code into extremely straightforward asm. This is thanks to gdbstub's heavy use of monomorphised generics, and the fact that in the final binary, handler-specific callbacks are only ever used in a single function (i.e: the handler implementation), which means the function call + callback call gets inlined + eliminated.
  • wrt "saves a lot of memory especially for barebone systems:" I'm assuming this is wrt buffer sizes, rather than codegen? if it's the latter, see my previous comment. If it's the former - yes, but as mentioned above, most of the time you won't be generating the data inline as part of the handler invocation, thanks to the whole "doing the work work_len / packet_buffer_size times" issue. On an embedded system where this thing really matters, it's far more likely that either a) the extension isn't even going to be implemented (and this info will be manually specified in the project's .gdbinit script), or b) if it is implemented, the backing data will be static (i.e: a fixed memory map, a fixed execfile name, etc...).
  • The callback API, while marginally more annoying to use (i.e: instead of just passing back a buffer + len, you have to invoke a method from the implementation), comes with the substantial benefit that if the data you're sending is already resident in memory, there doesn't need to be any extra copies to send it back to the GDB client. i.e: instead of going from self.memory_map_cache -> PacketBuf -> wire, we can just go self.memory_map_cache -> wire.
    • The more I think about it, the more I realize that I should probably swap out the read_addrs API to use a callback as well, since that would also cut that middle-man buffer out of the way in what is arguably a much more common operation.
      • Though OTOH, I recall exploring that option, and IIRC, the reason I didn't do that is because read_addrs was/will be called from more than one place in gdbstub, which means that the callback code doesn't get inlined + optimized out as well as it does in these "one off" cases.
      • Then again, I actually have an idea of how to work around that issue (using an internal GdbStubImpl::read_target_addrs helper method that is the only caller of Target::read_addrs), so I think the option is still on the table?
      • Sorry about that side-track - just trying to give you an idea of my train of thought.

Also, which problem are you referring to in #53?

Slight? No, it's the most common situation. registers data can be filled in the buffer, the m/M or pread commands will request data with the size same as PacketSize(at least in practice), and many other commands' reply are small enough to be sent in one packet, or people could choose enlarging PacketBuf to avoid recalculating.

at least in practice

This seems to be a philosophical difference between us, and unfortunately, this isn't something I'm willing to budge on 😅

Whether or not the spec should be updated to be more restrictive is a different discussion, but at the time being, the fact that the spec allows the client to request more data than the size of PacketSize - going so far as to include explicit provisions for the target to return less than the requested amount of data - means that we should be designing our API to be as permissive, performant, and least restrictive as possible.

For my money, I'm tempted to send a patch upstream to GDB, whereby the client would try and "eagerly" request the entire file from the target before falling back to the Packetbuf-clamped, multi-invocation approach. If that were the case, the a callback-based implementation could stream the entire file back in a single l xx or m xx response (either by invoking the callback once with the entire in-memory buffer, or by invoking the callback multiple times + reading chunks of the data into a stack-allocated buffer before sending it over the wire while streaming the trailing xx byte stream).

The point being, the callback API is definitely going to stay. Plumbing through the PacketBuf buffer is purely an optimization for extremely memory-sensitive targets.

and many other commands' reply are small enough to be sent in one packet

Counterargument: consider a Msp430 target that wants to return a MemoryMap XML. If PacketBuf is assumed to be just big enough to hold the target's registers, that would mean that PacketBuf is a paltry 14 (regs) * 2 (u16) * 2 (hex encoded) = 56 bytes. If we assume that the memory map is roughly the same size as the one in the example code, that would mean the memory map XML would be split across >4 invocations (assuming the GDB client only every requests PacketBuf sized chunks).

The point being, just because "many" commands are small enough to be sent in one packet, doesn't mean all of them are. There are plenty of commands that will require multiple invocations, and the API shouldn't artificially hamper their ability to send back in one go, if a future, smarter GDB client comes along as doesn't artificially limit the amount of data sent back per invocation.

It will be easier if we could know the type T in ParseCommand. After ParseCommand, the buf is totally free, we can use it as a temporary buf when implement the commands.

This is something I'm aware of, and is something I've tried my hand at implementing on numerous occasions. Each time I tried, I made some decent progress, but as time went on the code required to do so got gnarlier and messier, and I ended up throwing my efforts away...

That said, I've come to realize that this isn't actually something I'd like to implement for other reasons: it would break the use case of debugging multiple Arch types at the same time. e.g: a single gdbstub debugging both x86 and x64 processes, or if you're getting real fancy, a single gdbstub debugging both ARM and x64 code (e.g: on macOS). i.e: the single connection would be multiplexed for multiple Targets, making it incredibly difficult (if not impossible) to decide which monomorphised packet parsing code to use when parsing the incoming packets. Heck, even if you were to figure that out, you'd end up having a ton of duplicated packet parsing code in the final binary, which isn't great at all.

Admittedly, this is a feature that isn't likely to get implemented in the near future. It's firmly in the realm of "long-term" plans. Nonetheless, this is a use case that gdbstub will want to support eventually (especially as the entire tech industry enter into this coming renaissance of multi-arch devices), so I don't want to pre-emptively break things + make life harder for myself down the line.

So, in a nutshell, the implementation effort + future compat hazard of plumbing Target into the packet parsing code outweigh the benefit of getting a bit more of the PacketBuffer to use as a scratch buffer - hence why this isn't already the case.

@bet4it
Copy link
Contributor Author

bet4it commented Aug 30, 2021

the fact that the spec allows the client to request more data than the size of PacketSize

Could you point out where does the spec say client can request more data than the size of PacketSize?

I think the whole foundation of your viewpoints base on this sentence:

‘PacketSize=bytes

The remote stub can accept packets up to at least bytes in length. GDB will send packets up to this size for bulk transfers, and will never send larger packets. This is a limit on the data characters in the packet, including the frame and checksum. There is no trailing NUL byte in a remote protocol packet; if the stub stores packets in a NUL-terminated format, it should allow an extra byte in its buffer for the NUL.

Then you draw the conclusion:

outgoing data from the target can be as looooooong as we want, as the protocol assumes that the GDB client has "unlimited" storage on it's end, and doesn't need to worry about overflowing a fixed-size packet buffer.

I think it's really not properly.

Why you think this sentence only affect on incoming packet? Because the accept?

But what if:

The remote stub can accept (outgoing) packets up to at least bytes in length.

Is this possible?

And:

GDB will send packets up to this size for bulk (read) transfers

Still possible?

Oh, then we encountered the problem of how to understand the sentence, what the sentence wants to express.

How could we know it? We could ask the man who created this sentence, but it's created fifteen years ago, so it's not possible.

But we have another way: dig into the implementation.

Oh, implementation again. I know you said we should focus on spec rather than implementation, but GDB docs is not really a spec. This "spec" is not created before the code, but more like a doc alongside the code. The code when the doc created represent the real meaning of the doc, do you agree with it?

qSupported and PacketSize were introduced in bminor/binutils-gdb@be2a5f7.

I think I don't need to explain the code. You can read the code yourself and try to find if PacketSize affect the request length of packet.

And you can try git blame gdb/doc/gdb.texinfo. You can find that the explaination of PacketSize has never changed since it's created fifteen years ago. There were no vFile and qXfer at that time. Do you think the doc is up-to-date with current code?


I'm tempted to send a patch upstream to GDB, whereby the client would try and "eagerly" request the entire file from the target before falling back to the Packetbuf-clamped, multi-invocation approach. If that were the case, the a callback-based implementation could stream the entire file back in a single l xx or m xx response (either by invoking the callback once with the entire in-memory buffer, or by invoking the callback multiple times + reading chunks of the data into a stack-allocated buffer before sending it over the wire while streaming the trailing xx byte stream).

Are you crazy?

Firstly, the request of entire file is really easy to implement. We could just omit the count argument of vFile:pread:fd,count,offset, or set it to -1 or UINT_MAX.

It things like what you said, server can reply data of any length, why the count argument was created at first?

Do you think of the practicability?

Yes, with the callback, you could send data of any length. But is it possible for a software to receive data of arbitrary length? Where does it hold it? You may say that we could receive and process at the same time, but we need to check the checksum before use it.

That's also the reason why we need PacketSize to limit incoming packet size. And if the server don't use output callback, the size of outgoing packet is also limited. (Why? See the below) That's why I always say PacketSize should also impact on outgoing packet.


The callback API, while marginally more annoying to use (i.e: instead of just passing back a buffer + len, you have to invoke a method from the implementation), comes with the substantial benefit that if the data you're sending is already resident in memory, there doesn't need to be any extra copies to send it back to the GDB client. i.e: instead of going from self.memory_map_cache -> PacketBuf -> wire, we can just go self.memory_map_cache -> wire.

I think you use callback API because of two reason:

  1. We can send data with any length. But as what I said above, this feature is meaningless in reality.
  2. Avoid extra copy of data.

But do you think about the price of it?

To achieve it, you use self.write on each byte of data. I don't know if you try to capture the packet when using the example. If you do it you will find that every response packet only contains one byte data. If I read 1000 bytes of a file, there will be 1000 packets at the most! If each packet have a packet header of 66 bytes(TCP), that means the bytes on the wire are many times than what we needed.

but those couple extra bytes probably aren't the end of the world for slightly more efficient data transfer over the wire

So you really care about efficiency? I thought you don't care about it when I was suprise that each packet only sent data of one byte at the first time I found it.

To solve it, we may need something like write_all. If we use write_all, we must have a buffer which contains all the data we want to send. So this buffer and extra copy of data can't be omitted.

And because of the existence of this buffer, the size of outgoing packet is limited.

If we must copy all data to a buffer before send, is the callback API meaningless?


wrt "saves a lot of memory especially for barebone systems:" I'm assuming this is wrt buffer sizes, rather than codegen?

I mean the former. For example, if a server want to implement pread, it need a buffer which is big enough to hold all the data read from file, and it's really a huge cost for barebone systems (most commands don't need such huge amount of memory). But we already have PacketBuf, which is big enough. If we use this buffer, we don't need to allocate a new huge buffer.


Also, which problem are you referring to in #53?

#69 (comment) :

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


consider a Msp430 target that wants to return a MemoryMap XML. If PacketBuf is assumed to be just big enough to hold the target's registers, that would mean that PacketBuf is a paltry 14 (regs) * 2 (u16) * 2 (hex encoded) = 56 bytes.

No, I not mean the minimum possible size of PacketBuf. I mean the default value 0x1000, or the common buffer size which user will provide.

The point being, just because "many" commands are small enough to be sent in one packet, doesn't mean all of them are.

This seems to be a philosophical difference between us, and unfortunately, this isn't something I'm willing to budge on

What I said was to reply is:

there is a slight chance that the data is small enough to fit in the buffer.

Yes, many commands are small and many commands are big. You can say all length of data is possble say according to the spec. But if you use slight to discuss the possibility, I must retort you. You could try to find it. If you can't find any situation the commands are bigger than PacketBuf in actual use, how could you say the possibility is slight?

And the whole sentence I want to reply is:

Long story short, the only benefit I see of plumbing through a PacketBuf-backed &mut [u8] buffer + read offset API to the client is that there is a slight chance that the data is small enough to fit in the buffer, and the implementation doesn't have to allocate its own buffer, and could simply generate + write the data directly into that buffer.

So, if a file is bigger than PacketBuf, but each request count of pread is not bigger than PacketBuf, that means the data is small enough to fit in the buffer.

@daniel5151
Copy link
Owner

daniel5151 commented Aug 30, 2021

GDB will send packets up to this size for bulk transfers, and will never send larger packets.

And there it is! That's the missing line that I didn't see in the spec! Thank you for pointing it out.

Now that I see it clearly spelled out in the docs, rather than empirically observed via the GDB client implementation, I can safely say you're right, I was wrong.

While I still think the callback API has merits wrt. reducing copies + enabling streaming, it's undeniably a "weirder" API to work with for the end user, and copying data into a library provided &mut [u8] is a lot easier to understand.

I'll touch on a few other points you made along the way, but most of those will simply be clarification + philosophy things, and won't be related to the actual work that'll need to get done in this PR. I'll go over action-items at the tail end of this response.


Oh, implementation again. I know you said we should focus on spec rather than implementation, but GDB docs is not really a spec. This "spec" is not created before the code, but more like a doc alongside the code. The code when the doc created represent the real meaning of the doc, do you agree with it?

Yes, I'm completely aware that the spec is descriptive rather than prescriptive. Nonetheless, that doesn't mean that the code is somehow "more correct" than the spec - in fact, the very fact that they go through the trouble of maintaining a spec doc implies that the spec is more important than the underlying implementation, as there are many people who'll end up implementing a gdbstub in their project without ever reading the GDB client's code.

This is most clear wrt. something like the vFile spec, where as you've observed, the code doesn't use all the fields / flags, while the spec does require the target to send / respect all the fields / flags.

So, to be clear, gdbstub will continue to be a spec-focused implementation of the GDB RSP, and I will reiterate my point that while it's perfectly reasonable to read through the reference GDB client code to understand how a feature is being used, at the end of the day, gdbstub will only merge code that is spec-complete and spec-compliant.

Of course, this is all my opinion / personal philosophy, and you are more than welcome to disagree with it. Hopefully, we can shelve this particular aspect of our discussion for now, and leave it at "agree to disagree" 😄

every response packet only contains one byte data

Could you elaborate on this point? This is over TCP, correct? If so, it might be related to #28. If this is behavior you're not a fan of, you can easily write a custom Connection implementation that performs more internal buffering.

So you really care about efficiency?

My comments wrt efficiency and overhead are geared from the perspective of a UART-backed bare-metal case, where the perf/memory cost of extra copies and buffering could be a bit more noticeable. Obviously, in a hosted implementation using TCP, there are the TCP stack's overheads and outgoing packet buffers, etc...

To reiterate: gdbstub is a no_std first library. When designing and implementing features, they are designed with the limitations of bare metal no_std systems in mind. If it wasn't, we wouldn't be having this discussion about callbacks vs. buffers at all: we'd just have the API return a Vec<u8> and call it a day lol.

But is it possible for a software to receive data of arbitrary length?

If I were writing my own GDB client from scratch, where I knew that it'd be running on my beefy 2021 computer with RAM to spare: I'd just use a dynamically sized vector as my incoming data buffer lol.

I consider it an unfortunate detail that the GDB client uses a fixed sized incoming packet buffer, when it really ought to have split the PacketBuf config option into both IncomingPacketBuf and OutgoingPacketBuf. Hopefully you can agree with me that this would have made things a lot clearer and better (in the long term).

Anyways, this ties back into the whole "target - client asymmetry" I've touched upon in the past, where for all intents and purposes, we can assume that the GDB client has "unlimited" resources, while the GDB target is usually far more resource constrained. This is an important property to keep in mind, as it explains many of the seemingly quirky design decisions in the GDB RSP.


Anyways, like I said earlier, those other responses are moreso about clarifying my viewpoint, and aren't related to the work we need to do in this PR.

I am now on board with shedding the callback API, and updating the packet parsing code + API to plumb-through the unused space from the PacketBuf.

Unfortunately, as we discussed earlier, there's the whole problem that the packet parsing code having to preserve target-specific data (e.g: addresses/offsets) as hex-decoded &[u8], which get late-decoded in the handler method itself. This means that for many packets, it isn't possible to use the entire PacketBuf as outgoing scratch space, since the mutable reference to the base of the packet buffer is lost during the decode step.

Now, I am tempted to play with the code a bit, and see if there might be some way to get a &mut [u8] reference to the entire PacketBuf after performing this late-decode step, but I suspect that it'll require some non-trivial, hands-on, architectural changes, with a lot of screaming from the Rust compiler about lifetimes.

As such, for the time being, you'd probably want to do something similar to the m packet, and use as much of the space in the packet buffer as you can. Feel free to open a tacking issue along the lines of "allow using 100% of PacketBuf for outgoing data" so I don't forget.

Once you've gotten this working for ExecFile in this PR, we can circle back to vFile and update the Host IO APIs to use the PacketBuf approach as well (if you'd like).

@daniel5151
Copy link
Owner

daniel5151 commented Sep 1, 2021

Also, just a heads up - I'll be on vacation from Thursday 'till Monday for the labor day long weekend. I might have time to review code, but if I don't get the chance, I'll be back next Tuesday.

@bet4it
Copy link
Contributor Author

bet4it commented Sep 2, 2021

After I remove set_nodelay, things get better, there may be more bytes in a packet, but most packet only contain bytes less than 20.

The situation is quite reasonable. We send data byte by byte, so it's impossible for TCP to know where is the end of data, the only thing it can do is waiting for a timeout, which couldn't be too long. As I said before, if we want to solve this problem, we must collect all the data to be sent into a buffer and use something like write_all.

Just notice you mentioned it before:

// TODO: add `write_all` method to Connection, and allow user to optionally pass outgoing
// packet buffer? This could improve performance (instead of writing a single byte at a time)

If there are no write_all on UART, we could fallback to iteration of write:

pub fn write_all(&mut self, data: &[u8]) -> Result<(), Error<C::Error>> {
    for b in data.iter() {
        self.write(*b)?;
    }
    Ok(())
}

Consider that most users will use TCP, we should use write_all by default.

That also means that we must do escaping and RLE in buffer before send:

pub fn write_binary(&mut self, data: mut &[u8]) -> Result<(), Error<C::Error>> {
    self.escape_data(data);
    self.rle_data(data);
    self.write_all(data)?,
    Ok(())
}

Now we decide to prepare the outgoing data in a buffer, the cost of above should be very low.

P.S. I do all my testing on loopback.


If I were writing my own GDB client from scratch, where I knew that it'd be running on my beefy 2021 computer with RAM to spare: I'd just use a dynamically sized vector as my incoming data buffer lol.

But what if the target send a huge amount of data which will lead to a DoS attack?

And most implementations won't use the output callback. They will use a limited buffer to hold the data to be sent. (So they won't meet with the problem mentioned above)


if there might be some way to get a &mut [u8] reference to the entire PacketBuf after performing this late-decode step

It should be quite easy if we use not Rust but C?

But it's annoying with lifetimes. It seems to be a one-writer, many-readers problem? May RwLock help us?

We could store the raw pointer of PacketBuf somewhere. We must use unsafe to do it, but we can assure it's safe.

Or we could allocate a new buffer to hold the target-specific data? It won't be too big, size_of::<<T::Arch as Arch>::Usize>() is enough, which will be at most 8 byte in common situation.

So you will do it, right? Hope it can be a bit sooner, because I want to release my project base on gdbstub, otherwise I must use my fork of gdbstub with dirty hacks😅

@daniel5151
Copy link
Owner

(I'm writing this the morning as I leave for my trip, so pardon the brevity)

Yes, I've set a reminder to open a tracking issue to use 100% of the packet buffer when formatting responses, and it'll be yet another thing to look into when I finally find time to release 0.6.

As for timing... I truly feel bad about sitting on so many unreleased changes, but the reality is I've been quite busy at work and life that I haven't had much time to sit down and focus on gdbstub 😢

I continue to say "sometime in the next few weeks", and yet the right time never seems to come. As such, as icky as it sounds, I wouldn't hold out on publishing your project just because gdbstub 0.6 hasn't been released... As an OSS maintainer, I genuinely hate having to say that, but I honestly don't know when I'll get the chance to push this release over the finish line (wrt. documentation + polish).

@bet4it
Copy link
Contributor Author

bet4it commented Sep 3, 2021

Happy holiday~ 😄

@daniel5151
Copy link
Owner

Alright, I'm back, and it looks like there hasn't been much movement here...

If I'm reading through the thread correctly, it seems this PR is blocked on changing the API to use a &mut [u8] buffer, correct? As mentioned earlier, for this PR, you'll want to update the packet parsing code to hold on to a reference to the free bit of the packet buffer (i.e: writing code similar to the existing m packet) + plumb that buffer through to the API.

At some point in the future, I'll look into reworking the internal packet parsing architecture to use 100% of the packet buffer (rather than just a trailing "free" bit), but that shouldn't block this PR, as the end user API will stay the same.

src/gdbstub_impl/ext/exec_file.rs Outdated Show resolved Hide resolved
@bet4it bet4it marked this pull request as draft September 11, 2021 02:06
@daniel5151 daniel5151 self-requested a review September 11, 2021 17:26
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.

Not sure why you switched the PR back to a draft, since it's pretty much done 😅

I left a bit more feedback, but after you apply those changes, we should be good to merge.

examples/armv4t/gdb/exec_file.rs Outdated Show resolved Hide resolved
src/target/ext/exec_file.rs Outdated Show resolved Hide resolved
src/protocol/commands/_qXfer_exec_file.rs Outdated Show resolved Hide resolved
Co-authored-by: Daniel Prilik <danielprilik@gmail.com>
@daniel5151
Copy link
Owner

Looks like you'll need to manually go in and add that missing semicolon.

@bet4it
Copy link
Contributor Author

bet4it commented Sep 12, 2021

Some questions:

  1. Do we abandon wirte_binary_range in the future and write the result of range manually? Or we still need to wrap it into a function?
  2. Should we consider the situation that length is larger than buf.len() in get_exec_file? The spec doesn't allow this, but the client may not follow the spec.
  3. &TEST_PROGRAM_ELF[len.min(offset as usize)..len.min((offset + count) as usize)], should we consider the situation that offset + count oversize usize so the conversion will lose data?
  4. And there is also &filename[offset.min(len)..(offset + length).min(len)], should we wrap this operation into a function or a macro?

@daniel5151
Copy link
Owner

daniel5151 commented Sep 12, 2021

  1. Do we abandon wirte_binary_range in the future and write the result of range manually? Or we still need to wrap it into a function?

Off the top of my head, I have no strong opinion one way or the other. The logic is pretty straightforward, so whether it's encapsulated or duplicated might not matter too much. If you have a stronger opinion + rationale for one over the other, I'm all ears.

  1. Should we consider the situation that length is larger than buf.len() in get_exec_file? The spec doesn't allow this, but the client may not follow the spec.

Ah, what an interesting observation!

One thing to consider is that if we were to add this check here, I would also want to add similar checks to other instances of this sort of thing in the codebase.

I think we can hold off on this for now, and potentially add these checks behind something like a cfg(feature = "paranoid_client") flag in the future.

  1. &TEST_PROGRAM_ELF[len.min(offset as usize)..len.min((offset + count) as usize)], should we consider the situation that offset + count oversize usize so the conversion will lose data?

This is inside the armv4t example, and not part of the core gdbstub library. As such, I don't care too much about handling this rare edge case, as in debug builds, the overflowing addition will panic, while in release builds, the wraparound will most likely result in a panic due to having the end of the range be smaller than the start of the range in the indexing operation.

If you'd like to explicitly use something like offset.checked_add(length).expect("overflow"), feel free to do so, but I don't think it's too important.

  1. And there is also &filename[offset.min(len)..(offset + length).min(len)], should we wrap this operation into a function or a macro?

Same as 3., this is in the armv4t example, so I don't have a strong opinion one way or the other. If you think a helper function would make things clearer, then feel free to add one.


Let me know if there's anything else you'd like to do in this PR before merging. Otherwise, feel free to undraft, I'll do one last review, and we can merge it in.

EDIT: looks like the rustfmt CI is failing... you might need to manually fix that as well.

@bet4it
Copy link
Contributor Author

bet4it commented Sep 13, 2021

If we don't use wirte_binary_range, how could we use the tri-branch implementation in #64 to avoid recalculating?

@bet4it bet4it marked this pull request as ready for review September 13, 2021 13:48
@daniel5151
Copy link
Owner

The issue with the tri-branch implementation is that the current API doesn't have a way to signal "data has been read, AND it's EOF". We could tweak the API to have a "tri-state result" type, but IMO, it'd be more ergonomic to leave the API as is + accept the fact that there'll be an extra packet-response to signal EOF. The current API is intuitive, as it follows the same pattern as other similar Rust APIs (e.g: std::io::Read).

If you feel strongly about the inefficiency, I guess we could change the signature to sometime like:

// naming things is hard
enum ReadStatus {
    DataRead(usize),
    DataReadEof(usize),
    Eof
}

fn get_exec_file(
    &self,
    _pid: Option<Pid>,
    offset: usize,
    length: usize,
    buf: &mut [u8],
) -> TargetResult<ReadStatus, Self>;

...but this is clearly harder to grok, so I'd rather not.

@bet4it
Copy link
Contributor Author

bet4it commented Sep 13, 2021

We may check whether the return size is smaller than length requested?

@daniel5151
Copy link
Owner

We may check whether the return size is smaller than length requested?

Can you elaborate on that?

@bet4it
Copy link
Contributor Author

bet4it commented Sep 14, 2021

let ret = ops.get_exec_file(cmd.pid, cmd.offset, cmd.length, cmd.buf).handle_error()?;
if ret == 0 {
    res.write_str("l")?;
} else if ret < cmd.length {
    res.write_str("l")?;
    res.write_binary(cmd.buf.get(..ret).ok_or(Error::PacketBufferOverflow)?)?;
} else {
    res.write_str("m")?;
    res.write_binary(cmd.buf.get(..ret).ok_or(Error::PacketBufferOverflow)?)?;
}

@daniel5151
Copy link
Owner

Ahh, I see.

My concern here (which may or may not be valid) is that as an implementer, it would be "surprising" to me if a partial read into the buffer would also signal "End of Data". This wouldn't match the typical semantics I'd expect of such a method in Rust, such as std::io::Read, where it's perfectly fair to return a partial read before EOF.

As such, instead of having a "surprising" API contract, and leaning on the docs to explain this behavior, I think we should stick with the simpler approach that matches Rust semantics, or take the more verbose approach I sketched out above to make this behavior incredibly obvious.

As before, I personally lean towards "keep things simple, and do an extra roundtrip over the wire" over "complicate the API, but make it possible to save the roundtrip", but if you have strong opinions about this extra inefficiency, we could go for the latter approach.

@bet4it
Copy link
Contributor Author

bet4it commented Sep 15, 2021

Then we can leave this PR as this. This PR can be merged! 🎉

@daniel5151 daniel5151 self-requested a review September 15, 2021 16:02
@daniel5151 daniel5151 merged commit c2ed3e4 into daniel5151:dev/0.6 Sep 15, 2021
@daniel5151
Copy link
Owner

Hooray, it's in 🎉
Awesome work!


To summarize what we'd want to do in a follow up PR:

  • Update the other qXfer-backed protocol extensions to use the (length: usize, offset: u64, buf: &mut [u8]) API
    • At the moment, I think this is just MemoryMap and TargetXmlOverride
  • Update the vFile API to use the &mut [u8] PacketBuf instead of the current callback-based approach.
  • Update the vFile API to use plain 'ol usize for length and offset parameters, instead of Target::Arch::Usize

Let me know if I've missed anything, and I'll update this comment for posterity.

@bet4it bet4it deleted the exec-file branch September 16, 2021 00:34
@bet4it
Copy link
Contributor Author

bet4it commented Sep 16, 2021

At the moment, I think this is just MemoryMap and TargetXmlOverride

TargetDescriptionXmlOverride could not be changed because the return type is &str rather than &[u8]?
Or do we want to consider the situation the XML is generated dynamically?

@daniel5151
Copy link
Owner

In hindsight, I don't think &str should have been the return type regardless, since it just forces the implementation to perform UTF-8 validation on any XML data they read / generate. Using &[u8] would have made more sense.

That is to say, please feel free to change the API signature of TargetDescriptionXmlOverride to write data into the &mut [u8] PacketBuf

@bet4it bet4it mentioned this pull request Sep 17, 2021
13 tasks
@bet4it bet4it mentioned this pull request Sep 26, 2021
13 tasks
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.

None yet

2 participants