aboutsummaryrefslogtreecommitdiff
path: root/gdb/testsuite/lib/dap-support.exp
diff options
context:
space:
mode:
Diffstat (limited to 'gdb/testsuite/lib/dap-support.exp')
-rw-r--r--gdb/testsuite/lib/dap-support.exp343
1 files changed, 343 insertions, 0 deletions
diff --git a/gdb/testsuite/lib/dap-support.exp b/gdb/testsuite/lib/dap-support.exp
new file mode 100644
index 0000000..adf332c
--- /dev/null
+++ b/gdb/testsuite/lib/dap-support.exp
@@ -0,0 +1,343 @@
+# 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/>.
+
+# The JSON parser.
+load_lib ton.tcl
+
+# The sequence number for the next DAP request. This is used by the
+# automatic sequence-counting code below. It is reset each time GDB
+# is restarted.
+set dap_seq 1
+
+# Start gdb using the DAP interpreter.
+proc dap_gdb_start {} {
+ # Keep track of the number of times GDB has been launched.
+ global gdb_instances
+ incr gdb_instances
+
+ gdb_stdin_log_init
+
+ global GDBFLAGS stty_init
+ save_vars { GDBFLAGS stty_init } {
+ set stty_init "-echo raw"
+ set logfile [standard_output_file "dap.log.$gdb_instances"]
+ append GDBFLAGS " -iex \"set debug dap-log-file $logfile\" -q -i=dap"
+ set res [gdb_spawn]
+ if {$res != 0} {
+ return $res
+ }
+ }
+
+ # Reset the counter.
+ set ::dap_seq 1
+
+ return 0
+}
+
+# A helper for dap_to_ton that decides if the list L is a JSON object
+# or if it is an array.
+proc _dap_is_obj {l} {
+ if {[llength $l] % 2 != 0} {
+ return 0
+ }
+ foreach {key value} $l {
+ if {![string is alpha $key]} {
+ return 0
+ }
+ }
+ return 1
+}
+
+# The "TON" format is a bit of a pain to write by hand, so this proc
+# can be used to convert an ordinary Tcl list into TON by guessing at
+# the correct forms to use. This can't be used in all cases, because
+# Tcl can't really differentiate between literal forms. For example,
+# there's no way to decide if "true" should be a string or the literal
+# true.
+#
+# JSON objects must be passed in a particular form here -- as a list
+# with an even number of elements, alternating keys and values. Each
+# key must consist only of letters, no digits or other non-letter
+# characters. Note that this is compatible with the Tcl 'dict'
+# representation.
+proc dap_to_ton {obj} {
+ if {[string is list $obj] && [llength $obj] > 1} {
+ if {[_dap_is_obj $obj]} {
+ set result o
+ foreach {key value} $obj {
+ lappend result $key \[[dap_to_ton $value]\]
+ }
+ } else {
+ set result a
+ foreach val $obj {
+ lappend result \[[dap_to_ton $val]\]
+ }
+ }
+ } elseif {[string is entier $obj]} {
+ set result [list i $obj]
+ } elseif {[string is double $obj]} {
+ set result [list d $obj]
+ } elseif {$obj == "true" || $obj == "false" || $obj == "null"} {
+ set result [list l $obj]
+ } else {
+ set result [list s $obj]
+ }
+ return $result
+}
+
+# Format the object OBJ, in TON format, as JSON and send it to gdb.
+proc dap_send_ton {obj} {
+ set json [namespace eval ton::2json $obj]
+ # FIXME this is wrong for non-ASCII characters.
+ set len [string length $json]
+ verbose ">>> $json"
+ send_gdb "Content-Length: $len\r\n\r\n$json"
+}
+
+# Send a DAP request to gdb. COMMAND is the request's "command"
+# field, and OBJ is the "arguments" field. If OBJ is empty, it is
+# omitted. The sequence number of the request is automatically added,
+# and this is also the return value. OBJ is assumed to already be in
+# TON form.
+proc dap_send_request {command {obj {}}} {
+ # We can construct this directly as a TON object.
+ set result $::dap_seq
+ incr ::dap_seq
+ set req [format {o seq [i %d] type [s request] command [%s]} \
+ $result [list s $command]]
+ if {$obj != ""} {
+ append req " arguments \[$obj\]"
+ }
+ dap_send_ton $req
+ return $result
+}
+
+# Read a JSON response from gdb. This will return a TON object on
+# success, or throw an exception on error.
+proc dap_read_json {} {
+ set length ""
+ gdb_expect {
+ -re "^Content-Length: (\[0-9\]+)\r\n" {
+ set length $expect_out(1,string)
+ exp_continue
+ }
+ -re "^(\[^\r\n\]+)\r\n" {
+ # Any other header field.
+ exp_continue
+ }
+ -re "^\r\n" {
+ # Done.
+ }
+ timeout {
+ error "timeout reading json header"
+ }
+ eof {
+ error "eof reading json header"
+ }
+ }
+
+ if {$length == ""} {
+ error "didn't find content-length"
+ }
+
+ set json ""
+ while {$length > 0} {
+ # Tcl only allows up to 255 characters in a {} expression in a
+ # regexp, so we may need to read in chunks.
+ set this_len [expr {min ($length, 255)}]
+ gdb_expect {
+ -re "^.{$this_len}" {
+ append json $expect_out(0,string)
+ }
+ timeout {
+ error "timeout reading json body"
+ }
+ eof {
+ error "eof reading json body"
+ }
+ }
+ incr length -$this_len
+ }
+
+ return [ton::json2ton $json]
+}
+
+# Read a sequence of JSON objects from gdb, until a response object is
+# seen. If the response object has the request sequence number NUM,
+# and is for command CMD, return a list of two elements: the response
+# object and a list of any preceding events, in the order they were
+# emitted. The objects are in TON format. If a response object is
+# seen but has the wrong sequence number or command, throw an
+# exception
+proc dap_read_response {cmd num} {
+ set result {}
+ while 1 {
+ set obj [dap_read_json]
+ set d [namespace eval ton::2dict $obj]
+ if {[dict get $d type] == "response"} {
+ if {[dict get $d request_seq] != $num} {
+ error "saw wrong request_seq in $obj"
+ } elseif {[dict get $d command] != $cmd} {
+ error "saw wrong command in $obj"
+ } else {
+ return [list $obj $result]
+ }
+ } else {
+ lappend result $obj
+ }
+ }
+}
+
+# A wrapper for dap_send_request and dap_read_response. This sends a
+# request to gdb and returns the result. NAME is used to issue a pass
+# or fail; on failure, this always returns an empty string.
+proc dap_request_and_response {name command {obj {}}} {
+ set result {}
+ if {[catch {
+ set seq [dap_send_request $command $obj]
+ set result [dap_read_response $command $seq]
+ } text]} {
+ verbose "reason: $text"
+ fail $name
+ } else {
+ pass $name
+ }
+ return $result
+}
+
+# Like dap_request_and_response, but also checks that the response
+# indicates success.
+proc dap_check_request_and_response {name command {obj {}}} {
+ set result [dap_request_and_response $name $command $obj]
+ if {$result == ""} {
+ return ""
+ }
+ set d [namespace eval ton::2dict [lindex $result 0]]
+ if {[dict get $d success] != "true"} {
+ verbose "request failure: $result"
+ fail "$name success"
+ return ""
+ }
+ pass "$name success"
+ return $result
+}
+
+# Start gdb, send a DAP initialization request and return the
+# response. This approach lets the caller check the feature list, if
+# desired. Callers not caring about this should probably use
+# dap_launch. Returns the empty string on failure. NAME is used as
+# the test name.
+proc dap_initialize {name} {
+ if {[dap_gdb_start]} {
+ return ""
+ }
+ return [dap_check_request_and_response $name initialize]
+}
+
+# Start gdb, send a DAP initialize request, and then a launch request
+# specifying FILE as the program to use for the inferior. Returns the
+# empty string on failure, or the response object from the launch
+# request. After this is called, gdb will be ready to accept
+# breakpoint requests. NAME is used as the test name. It has a
+# reasonable default but can be overridden in case a test needs to
+# launch gdb more than once.
+proc dap_launch {file {name startup}} {
+ if {[dap_initialize "$name - initialize"] == ""} {
+ return ""
+ }
+ return [dap_check_request_and_response "$name - launch" launch \
+ [format {o program [%s]} \
+ [list s [standard_output_file $file]]]]
+}
+
+# Cleanly shut down gdb. NAME is used as the test name.
+proc dap_shutdown {{name shutdown}} {
+ dap_check_request_and_response $name disconnect
+}
+
+# Search the event list EVENTS for an output event matching the regexp
+# RX. Pass the test NAME if found, fail if not.
+proc dap_search_output {name rx events} {
+ foreach event $events {
+ set d [namespace eval ton::2dict $event]
+ if {[dict get $d type] != "event"
+ || [dict get $d event] != "output"} {
+ continue
+ }
+ if {[regexp $rx [dict get $d body output]]} {
+ pass $name
+ return
+ }
+ }
+ fail $name
+}
+
+# Check that OBJ (a single TON object) has values that match the
+# key/value pairs given in ARGS. NAME is used as the test name.
+proc dap_match_values {name obj args} {
+ set d [namespace eval ton::2dict $obj]
+ foreach {key value} $args {
+ if {[eval dict get [list $d] $key] != $value} {
+ fail "$name (checking $key)"
+ return ""
+ }
+ }
+ pass $name
+}
+
+# A helper for dap_read_event that reads events, looking for one
+# matching TYPE.
+proc _dap_read_event {type} {
+ while 1 {
+ # We don't do any extra error checking here for the time
+ # being; we'll just get a timeout thrown instead.
+ set obj [dap_read_json]
+ set d [namespace eval ton::2dict $obj]
+ if {[dict get $d type] == "event"
+ && [dict get $d event] == $type} {
+ return $obj
+ }
+ }
+}
+
+# Read JSON objects looking for an event whose "event" field is TYPE.
+# NAME is used as the test name; it defaults to TYPE. Extra arguments
+# are used to check fields of the event; the arguments alternate
+# between a field name (in "dict get" form) and its expected value.
+# Returns the TON object for the chosen event, or empty string on
+# error.
+proc dap_read_event {name type args} {
+ if {$name == ""} {
+ set name $type
+ }
+
+ if {[catch {_dap_read_event $type} result]} {
+ fail $name
+ return ""
+ }
+
+ eval dap_match_values [list $name $result] $args
+
+ return $result
+}
+
+# A convenience function to extract the breakpoint number when a new
+# breakpoint is created. OBJ is an object as returned by
+# dap_check_request_and_response.
+proc dap_get_breakpoint_number {obj} {
+ set d [namespace eval ton::2dict [lindex $obj 0]]
+ set bplist [dict get $d body breakpoints]
+ return [dict get [lindex $bplist 0] id]
+}