aboutsummaryrefslogtreecommitdiff
path: root/gdb/testsuite/gdb.dap
diff options
context:
space:
mode:
authorTom Tromey <tromey@adacore.com>2022-06-23 11:11:36 -0600
committerTom Tromey <tromey@adacore.com>2023-01-02 09:49:37 -0700
commitde7d7cb58e6209ed11c31f635545ee2ee6ded307 (patch)
treed0681c4ad9e7207227166990d6133cca83fae24c /gdb/testsuite/gdb.dap
parentc43d829bca5e45c5e6c0255a549abc5766f6de7f (diff)
downloadfsf-binutils-gdb-de7d7cb58e6209ed11c31f635545ee2ee6ded307.zip
fsf-binutils-gdb-de7d7cb58e6209ed11c31f635545ee2ee6ded307.tar.gz
fsf-binutils-gdb-de7d7cb58e6209ed11c31f635545ee2ee6ded307.tar.bz2
Initial implementation of Debugger Adapter Protocol
The Debugger Adapter Protocol is a JSON-RPC protocol that IDEs can use to communicate with debuggers. You can find more information here: https://microsoft.github.io/debug-adapter-protocol/ Frequently this is implemented as a shim, but it seemed to me that GDB could implement it directly, via the Python API. This patch is the initial implementation. DAP is implemented as a new "interp". This is slightly weird, because it doesn't act like an ordinary interpreter -- for example it doesn't implement a command syntax, and doesn't use GDB's ordinary event loop. However, this seemed like the best approach overall. To run GDB in this mode, use: gdb -i=dap The DAP code will accept JSON-RPC messages on stdin and print responses to stdout. GDB redirects the inferior's stdout to a new pipe so that output can be encapsulated by the protocol. The Python code uses multiple threads to do its work. Separate threads are used for reading JSON from the client and for writing JSON to the client. All GDB work is done in the main thread. (The first implementation used asyncio, but this had some limitations, and so I rewrote it to use threads instead.) This is not a complete implementation of the protocol, but it does implement enough to demonstrate that the overall approach works. There is a rudimentary test suite. It uses a JSON parser written in pure Tcl. This parser is under the same license as Tcl itself, so I felt it was acceptable to simply import it into the tree. There is also a bit of documentation -- just documenting the new interpreter name.
Diffstat (limited to 'gdb/testsuite/gdb.dap')
-rw-r--r--gdb/testsuite/gdb.dap/basic-dap.c44
-rw-r--r--gdb/testsuite/gdb.dap/basic-dap.exp151
2 files changed, 195 insertions, 0 deletions
diff --git a/gdb/testsuite/gdb.dap/basic-dap.c b/gdb/testsuite/gdb.dap/basic-dap.c
new file mode 100644
index 0000000..eab1c99
--- /dev/null
+++ b/gdb/testsuite/gdb.dap/basic-dap.c
@@ -0,0 +1,44 @@
+/* Copyright 2022 Free Software Foundation, Inc.
+
+ This file is part of GDB.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+int global_variable = 23;
+
+void
+function_breakpoint_here ()
+{
+ ++global_variable;
+ ++global_variable;
+}
+
+void
+do_not_stop_here ()
+{
+ /* This exists to test that breakpoints are cleared. */
+}
+
+void
+address_breakpoint_here ()
+{
+}
+
+int main ()
+{
+ do_not_stop_here ();
+ function_breakpoint_here ();
+ address_breakpoint_here ();
+ return 0; /* BREAK */
+}
diff --git a/gdb/testsuite/gdb.dap/basic-dap.exp b/gdb/testsuite/gdb.dap/basic-dap.exp
new file mode 100644
index 0000000..d3acf0c
--- /dev/null
+++ b/gdb/testsuite/gdb.dap/basic-dap.exp
@@ -0,0 +1,151 @@
+# Copyright 2022 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# Basic DAP test.
+
+load_lib dap-support.exp
+
+standard_testfile
+
+if {[build_executable ${testfile}.exp $testfile] == -1} {
+ return
+}
+
+if {[dap_launch $testfile] == ""} {
+ return
+}
+
+set obj [dap_check_request_and_response "set breakpoint on two functions" \
+ setFunctionBreakpoints \
+ {o breakpoints [a [o name [s function_breakpoint_here]] \
+ [o name [s do_not_stop_here]]]}]
+set fn_bpno [dap_get_breakpoint_number $obj]
+
+# This also tests that the previous do_not_stop_here breakpoint is
+# cleared.
+set obj [dap_check_request_and_response "set breakpoint on function" \
+ setFunctionBreakpoints \
+ {o breakpoints [a [o name [s function_breakpoint_here]]]}]
+set fn_bpno [dap_get_breakpoint_number $obj]
+
+set obj [dap_check_request_and_response "set breakpoint with invalid filename" \
+ setBreakpoints \
+ [format {o source [o path [s nosuchfilename.c]] breakpoints [a [o line [i 29]]]}]]
+
+set line [gdb_get_line_number "BREAK"]
+set obj [dap_check_request_and_response "set breakpoint by line number" \
+ setBreakpoints \
+ [format {o source [o path [%s]] breakpoints [a [o line [i %d]]]} \
+ [list s $srcfile] $line]]
+set line_bpno [dap_get_breakpoint_number $obj]
+
+# Check the new breakpoint event.
+set ok 0
+foreach event [lindex $obj 1] {
+ set d [namespace eval ton::2dict $event]
+ if {[dict get $d type] != "event"
+ || [dict get $d event] != "breakpoint"} {
+ continue
+ }
+ if {[dict get $d body reason] == "new"
+ && [dict get $d body breakpoint verified] == "true"} {
+ set ok 1
+ pass "check new breakpoint event"
+ break
+ }
+}
+if {!$ok} {
+ fail "check new breakpoint event"
+}
+
+set obj [dap_check_request_and_response "reset breakpoint by line number" \
+ setBreakpoints \
+ [format {o source [o path [%s]] breakpoints [a [o line [i %d]]]} \
+ [list s $srcfile] $line]]
+set new_line_bpno [dap_get_breakpoint_number $obj]
+
+if {$new_line_bpno == $line_bpno} {
+ pass "re-setting kept same breakpoint number"
+} else {
+ fail "re-setting kept same breakpoint number"
+}
+
+# This uses "&address_breakpoint_here" as the address -- this is a
+# hack because we know how this is implemented under the hood.
+set obj [dap_check_request_and_response "set breakpoint by address" \
+ setInstructionBreakpoints \
+ {o breakpoints [a [o instructionReference [s &address_breakpoint_here]]]}]
+set insn_bpno [dap_get_breakpoint_number $obj]
+
+set d [namespace eval ton::2dict [lindex $obj 0]]
+set bplist [dict get $d body breakpoints]
+set insn_pc [dict get [lindex $bplist 0] instructionReference]
+
+dap_check_request_and_response "start inferior" configurationDone
+dap_read_event "inferior started" thread "body reason" started
+
+dap_read_event "stopped at function breakpoint" stopped \
+ "body reason" breakpoint \
+ "body hitBreakpointIds" $fn_bpno
+
+set obj [dap_check_request_and_response "evaluate global in function" \
+ evaluate {o expression [s global_variable]}]
+dap_match_values "global value in function" [lindex $obj 0] \
+ "body result" 23
+
+dap_check_request_and_response step stepIn {o threadId [i 1]}
+dap_read_event "stopped after step" stopped "body reason" step
+
+set obj [dap_check_request_and_response "evaluate global second time" \
+ evaluate {o expression [s global_variable]}]
+dap_match_values "global value after step" [lindex $obj 0] \
+ "body result" 24
+
+dap_check_request_and_response "continue to address" continue
+dap_read_event "stopped at address breakpoint" stopped \
+ "body reason" breakpoint \
+ "body hitBreakpointIds" $insn_bpno
+
+dap_check_request_and_response "continue to line" continue
+dap_read_event "stopped at line breakpoint" stopped \
+ "body reason" breakpoint \
+ "body hitBreakpointIds" $line_bpno
+
+set obj [dap_check_request_and_response "evaluate global in main" \
+ evaluate {o expression [s global_variable]}]
+dap_match_values "global value in main" [lindex $obj 0] \
+ "body result" 25
+
+set obj [dap_request_and_response "evaluate non-existing variable" \
+ evaluate {o expression [s nosuchvariable]}]
+set d [namespace eval ton::2dict [lindex $obj 0]]
+if {[dict get $d success] == "false"} {
+ pass "result of invalid request"
+} else {
+ fail "result of invalid request"
+}
+
+set obj [dap_check_request_and_response "disassemble one instruction" \
+ disassemble \
+ [format {o memoryReference [s %s] instructionCount [i 1]} \
+ $insn_pc]]
+set d [namespace eval ton::2dict [lindex $obj 0]]
+if {[dict exists $d body instructions]} {
+ pass "instructions in disassemble output"
+} else {
+ fail "instructions in disassemble output"
+}
+
+dap_shutdown