python/py-cmd.c \
        python/py-connection.c \
        python/py-continueevent.c \
+       python/py-dap.c \
        python/py-disasm.c \
        python/py-event.c \
        python/py-evtregistry.c \
 
 
 * MI version 1 has been removed.
 
+* GDB has initial built-in support for the Debugger Adapter Protocol.
+  This support requires that GDB be built with Python scripting
+  enabled.
+
 *** Changes in GDB 13
 
 * MI version 1 is deprecated, and will be removed in GDB 14.
 
        gdb/command/type_printers.py \
        gdb/command/unwinders.py \
        gdb/command/xmethods.py \
+       gdb/dap/breakpoint.py \
+       gdb/dap/bt.py \
+       gdb/dap/disassemble.py \
+       gdb/dap/evaluate.py \
+       gdb/dap/events.py \
+       gdb/dap/frames.py \
+       gdb/dap/__init__.py \
+       gdb/dap/io.py \
+       gdb/dap/launch.py \
+       gdb/dap/next.py \
+       gdb/dap/pause.py \
+       gdb/dap/scopes.py \
+       gdb/dap/server.py \
+       gdb/dap/startup.py \
+       gdb/dap/state.py \
+       gdb/dap/threads.py \
        gdb/function/__init__.py \
        gdb/function/as_string.py \
        gdb/function/caller_is.py \
 
 used interpreter with @value{GDBN}. With no interpreter specified at runtime,
 @value{GDBN} will use this interpreter.
 
+@item dap
+@cindex DAP
+@cindex Debugger Adapter Protocol
+When @value{GDBN} has been built with Python support, it also supports
+the Debugger Adapter Protocol.  This protocol can be used by a
+debugger GUI or an IDE to communicate with @value{GDBN}.  This
+protocol is documented at
+@url{https://microsoft.github.io/debug-adapter-protocol/}.
+
 @item mi
 @cindex mi interpreter
 The newest @sc{gdb/mi} interface (currently @code{mi3}).  Used primarily
 
--- /dev/null
+# 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/>.
+
+import os
+import gdb
+
+# This must come before other DAP imports.
+from . import startup
+
+# Load modules that define commands.
+from . import breakpoint
+from . import bt
+from . import disassemble
+from . import evaluate
+from . import launch
+from . import next
+from . import pause
+from . import scopes
+from . import threads
+
+from .server import Server
+
+
+def run():
+    """Main entry point for the DAP server.
+    This is called by the GDB DAP interpreter."""
+    startup.exec_and_log("set python print-stack full")
+    startup.exec_and_log("set pagination off")
+
+    # We want to control gdb stdin and stdout entirely, so we dup
+    # them to new file descriptors.
+    saved_out = os.dup(1)
+    saved_in = os.dup(0)
+    # Make sure these are not inheritable.  This is already the case
+    # for Unix, but not for Windows.
+    os.set_inheritable(saved_out, False)
+    os.set_inheritable(saved_in, False)
+
+    # The new gdb (and inferior) stdin will just be /dev/null.  For
+    # gdb, the "dap" interpreter also rewires the UI so that gdb
+    # doesn't try to read this (and thus see EOF and exit).
+    new_in = os.open(os.devnull, os.O_RDONLY)
+    os.dup2(new_in, 0, True)
+    os.close(new_in)
+
+    # Make the new stdout be a pipe.  This way the DAP code can easily
+    # read from the inferior and send OutputEvent to the client.
+    (rfd, wfd) = os.pipe()
+    os.set_inheritable(rfd, False)
+    os.dup2(wfd, 1, True)
+    # Also send stderr this way.
+    os.dup2(wfd, 2, True)
+    os.close(wfd)
+
+    # Note the inferior output is opened in text mode.
+    server = Server(open(saved_in, "rb"), open(saved_out, "wb"), open(rfd, "r"))
+    startup.start_dap(server.main_loop)
 
--- /dev/null
+# 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/>.
+
+import gdb
+import os
+
+from .server import request, capability
+from .startup import send_gdb_with_response, in_gdb_thread
+
+
+# Map from the breakpoint "kind" (like "function") to a second map, of
+# breakpoints of that type.  The second map uses the breakpoint spec
+# as a key, and the gdb.Breakpoint itself as a value.  This is used to
+# implement the clearing behavior specified by the protocol, while
+# allowing for reuse when a breakpoint can be kept.
+breakpoint_map = {}
+
+
+@in_gdb_thread
+def breakpoint_descriptor(bp):
+    "Return the Breakpoint object descriptor given a gdb Breakpoint."
+    if bp.locations:
+        # Just choose the first location, because DAP doesn't allow
+        # multiple locations.  See
+        # https://github.com/microsoft/debug-adapter-protocol/issues/13
+        loc = bp.locations[0]
+        (basename, line) = loc.source
+        return {
+            "id": bp.number,
+            "verified": True,
+            "source": {
+                "name": os.path.basename(basename),
+                "path": loc.fullname,
+                # We probably don't need this but it doesn't hurt to
+                # be explicit.
+                "sourceReference": 0,
+            },
+            "line": line,
+            "instructionReference": hex(loc.address),
+        }
+    else:
+        return {
+            "id": bp.number,
+            "verified": False,
+        }
+
+
+# Helper function to set some breakpoints according to a list of
+# specifications.
+@in_gdb_thread
+def _set_breakpoints(kind, specs):
+    global breakpoint_map
+    # Try to reuse existing breakpoints if possible.
+    if kind in breakpoint_map:
+        saved_map = breakpoint_map[kind]
+    else:
+        saved_map = {}
+    breakpoint_map[kind] = {}
+    result = []
+    for spec in specs:
+        keyspec = frozenset(spec.items())
+        if keyspec in saved_map:
+            bp = saved_map.pop(keyspec)
+        else:
+            # FIXME handle exceptions here
+            bp = gdb.Breakpoint(**spec)
+        breakpoint_map[kind][keyspec] = bp
+        result.append(breakpoint_descriptor(bp))
+    # Delete any breakpoints that were not reused.
+    for entry in saved_map.values():
+        entry.delete()
+    return result
+
+
+@request("setBreakpoints")
+def set_breakpoint(source, *, breakpoints=[], **args):
+    if "path" not in source:
+        result = []
+    else:
+        specs = []
+        for obj in breakpoints:
+            specs.append(
+                {
+                    "source": source["path"],
+                    "line": obj["line"],
+                }
+            )
+        # Be sure to include the path in the key, so that we only
+        # clear out breakpoints coming from this same source.
+        key = "source:" + source["path"]
+        result = send_gdb_with_response(lambda: _set_breakpoints(key, specs))
+    return {
+        "breakpoints": result,
+    }
+
+
+@request("setFunctionBreakpoints")
+@capability("supportsFunctionBreakpoints")
+def set_fn_breakpoint(breakpoints, **args):
+    specs = []
+    for bp in breakpoints:
+        specs.append(
+            {
+                "function": bp["name"],
+            }
+        )
+    result = send_gdb_with_response(lambda: _set_breakpoints("function", specs))
+    return {
+        "breakpoints": result,
+    }
+
+
+@request("setInstructionBreakpoints")
+@capability("supportsInstructionBreakpoints")
+def set_insn_breakpoints(*, breakpoints, offset=None, **args):
+    specs = []
+    for bp in breakpoints:
+        # There's no way to set an explicit address breakpoint
+        # from Python, so we rely on "spec" instead.
+        val = "*" + bp["instructionReference"]
+        if offset is not None:
+            val = val + " + " + str(offset)
+        specs.append(
+            {
+                "spec": val,
+            }
+        )
+    result = send_gdb_with_response(lambda: _set_breakpoints("instruction", specs))
+    return {
+        "breakpoints": result,
+    }
 
--- /dev/null
+# 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/>.
+
+import gdb
+import os
+
+from .frames import frame_id
+from .server import request, capability
+from .startup import send_gdb_with_response, in_gdb_thread
+from .state import set_thread
+
+
+# Helper function to safely get the name of a frame as a string.
+@in_gdb_thread
+def _frame_name(frame):
+    name = frame.name()
+    if name is None:
+        name = "???"
+    return name
+
+
+# Helper function to get a frame's SAL without an error.
+@in_gdb_thread
+def _safe_sal(frame):
+    try:
+        return frame.find_sal()
+    except gdb.error:
+        return None
+
+
+# Helper function to compute a stack trace.
+@in_gdb_thread
+def _backtrace(thread_id, levels, startFrame):
+    set_thread(thread_id)
+    frames = []
+    current_number = 0
+    # FIXME could invoke frame filters here.
+    try:
+        current_frame = gdb.newest_frame()
+    except gdb.error:
+        current_frame = None
+    # Note that we always iterate over all frames, which is lame, but
+    # seemingly necessary to support the totalFrames response.
+    # FIXME maybe the mildly mysterious note about "monotonically
+    # increasing totalFrames values" would let us fix this.
+    while current_frame is not None:
+        # This condition handles the startFrame==0 case as well.
+        if current_number >= startFrame and (levels == 0 or len(frames) < levels):
+            newframe = {
+                "id": frame_id(current_frame),
+                "name": _frame_name(current_frame),
+                # This must always be supplied, but we will set it
+                # correctly later if that is possible.
+                "line": 0,
+                # GDB doesn't support columns.
+                "column": 0,
+                "instructionPointerReference": hex(current_frame.pc()),
+            }
+            sal = _safe_sal(current_frame)
+            if sal is not None:
+                newframe["source"] = {
+                    "name": os.path.basename(sal.symtab.filename),
+                    "path": sal.symtab.filename,
+                    # We probably don't need this but it doesn't hurt
+                    # to be explicit.
+                    "sourceReference": 0,
+                }
+                newframe["line"] = sal.line
+            frames.append(newframe)
+        current_number = current_number + 1
+        current_frame = current_frame.older()
+    return {
+        "stackFrames": frames,
+        "totalFrames": current_number,
+    }
+
+
+@request("stackTrace")
+@capability("supportsDelayedStackTraceLoading")
+def stacktrace(*, levels=0, startFrame=0, threadId, **extra):
+    return send_gdb_with_response(lambda: _backtrace(threadId, levels, startFrame))
 
--- /dev/null
+# 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/>.
+
+import gdb
+
+from .server import request, capability
+from .startup import send_gdb_with_response, in_gdb_thread
+
+
+@in_gdb_thread
+def _disassemble(pc, skip_insns, count):
+    try:
+        arch = gdb.selected_frame().architecture()
+    except gdb.error:
+        # Maybe there was no frame.
+        arch = gdb.selected_inferior().architecture()
+    result = []
+    total_count = skip_insns + count
+    for elt in arch.disassemble(pc, count=total_count)[skip_insns:]:
+        result.append(
+            {
+                "address": hex(elt["addr"]),
+                "instruction": elt["asm"],
+            }
+        )
+    return {
+        "instructions": result,
+    }
+
+
+@request("disassemble")
+@capability("supportsDisassembleRequest")
+def disassemble(
+    *, memoryReference, offset=0, instructionOffset=0, instructionCount, **extra
+):
+    pc = int(memoryReference, 0) + offset
+    return send_gdb_with_response(
+        lambda: _disassemble(pc, instructionOffset, instructionCount)
+    )
 
--- /dev/null
+# 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/>.
+
+import gdb
+
+from .frames import frame_for_id
+from .server import request
+from .startup import send_gdb_with_response, in_gdb_thread
+
+
+# Helper function to evaluate an expression in a certain frame.
+@in_gdb_thread
+def _evaluate(expr, frame_id):
+    if frame_id is not None:
+        frame = frame_for_id(frame_id)
+        frame.select()
+    return str(gdb.parse_and_eval(expr))
+
+
+# FIXME 'format' & hex
+# FIXME return a structured response using pretty-printers / varobj
+# FIXME supportsVariableType handling
+@request("evaluate")
+def eval_request(expression, *, frameId=None, **args):
+    result = send_gdb_with_response(lambda: _evaluate(expression, frameId))
+    return {
+        "result": result,
+        # FIXME
+        "variablesReference": -1,
+    }
 
--- /dev/null
+# 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/>.
+
+import enum
+import gdb
+
+from .server import send_event
+from .startup import in_gdb_thread, Invoker, log
+from .breakpoint import breakpoint_descriptor
+
+
+@in_gdb_thread
+def _on_exit(event):
+    code = 0
+    if hasattr(event, "exit_code"):
+        code = event.exit_code
+    send_event(
+        "exited",
+        {
+            "exitCode": code,
+        },
+    )
+
+
+@in_gdb_thread
+def _bp_modified(event):
+    send_event(
+        "breakpoint",
+        {
+            "reason": "changed",
+            "breakpoint": breakpoint_descriptor(event),
+        },
+    )
+
+
+@in_gdb_thread
+def _bp_created(event):
+    send_event(
+        "breakpoint",
+        {
+            "reason": "new",
+            "breakpoint": breakpoint_descriptor(event),
+        },
+    )
+
+
+@in_gdb_thread
+def _bp_deleted(event):
+    send_event(
+        "breakpoint",
+        {
+            "reason": "removed",
+            "breakpoint": breakpoint_descriptor(event),
+        },
+    )
+
+
+@in_gdb_thread
+def _new_thread(event):
+    send_event(
+        "thread",
+        {
+            "reason": "started",
+            "threadId": event.inferior_thread.global_num,
+        },
+    )
+
+
+_suppress_cont = False
+
+
+@in_gdb_thread
+def _cont(event):
+    global _suppress_cont
+    if _suppress_cont:
+        log("_suppress_cont case")
+        _suppress_cont = False
+    else:
+        send_event(
+            "continued",
+            {
+                "threadId": gdb.selected_thread().global_num,
+                "allThreadsContinued": True,
+            },
+        )
+
+
+class StopKinds(enum.Enum):
+    # The values here are chosen to follow the DAP spec.
+    STEP = "step"
+    BREAKPOINT = "breakpoint"
+    PAUSE = "pause"
+    EXCEPTION = "exception"
+
+
+_expected_stop = None
+
+
+@in_gdb_thread
+def expect_stop(reason):
+    """Indicate that a stop is expected, for the reason given."""
+    global _expected_stop
+    _expected_stop = reason
+
+
+# A wrapper for Invoker that also sets the expected stop.
+class ExecutionInvoker(Invoker):
+    """A subclass of Invoker that sets the expected stop.
+    Note that this assumes that the command will restart the inferior,
+    so it will also cause ContinuedEvents to be suppressed."""
+
+    def __init__(self, cmd, expected):
+        super().__init__(cmd)
+        self.expected = expected
+
+    @in_gdb_thread
+    def __call__(self):
+        expect_stop(self.expected)
+        global _suppress_cont
+        _suppress_cont = True
+        # FIXME if the call fails should we clear _suppress_cont?
+        super().__call__()
+
+
+@in_gdb_thread
+def _on_stop(event):
+    log("entering _on_stop: " + repr(event))
+    global _expected_stop
+    obj = {
+        "threadId": gdb.selected_thread().global_num,
+        # FIXME we don't support non-stop for now.
+        "allThreadsStopped": True,
+    }
+    if isinstance(event, gdb.BreakpointEvent):
+        # Ignore the expected stop, we hit a breakpoint instead.
+        # FIXME differentiate between 'breakpoint', 'function breakpoint',
+        # 'data breakpoint' and 'instruction breakpoint' here.
+        _expected_stop = StopKinds.BREAKPOINT
+        obj["hitBreakpointIds"] = [x.number for x in event.breakpoints]
+    elif _expected_stop is None:
+        # FIXME what is even correct here
+        _expected_stop = StopKinds.EXCEPTION
+    obj["reason"] = _expected_stop.value
+    _expected_stop = None
+    send_event("stopped", obj)
+
+
+gdb.events.stop.connect(_on_stop)
+gdb.events.exited.connect(_on_exit)
+gdb.events.breakpoint_created.connect(_bp_created)
+gdb.events.breakpoint_modified.connect(_bp_modified)
+gdb.events.breakpoint_deleted.connect(_bp_deleted)
+gdb.events.new_thread.connect(_new_thread)
+gdb.events.cont.connect(_cont)
 
--- /dev/null
+# 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/>.
+
+import gdb
+
+from .startup import in_gdb_thread
+
+
+# Map from frame (thread,level) pair to frame ID numbers.  Note we
+# can't use the frame itself here as it is not hashable.
+_frame_ids = {}
+
+# Map from frame ID number back to frames.
+_id_to_frame = {}
+
+
+# Clear all the frame IDs.
+@in_gdb_thread
+def _clear_frame_ids(evt):
+    global _frame_ids, _id_to_frame
+    _frame_ids = {}
+    _id_to_frame = {}
+
+
+# Clear the frame ID map whenever the inferior runs.
+gdb.events.cont.connect(_clear_frame_ids)
+
+
+@in_gdb_thread
+def frame_id(frame):
+    """Return the frame identifier for FRAME."""
+    global _frame_ids, _id_to_frame
+    pair = (gdb.selected_thread().global_num, frame.level)
+    if pair not in _frame_ids:
+        id = len(_frame_ids)
+        _frame_ids[pair] = id
+        _id_to_frame[id] = frame
+    return _frame_ids[pair]
+
+
+@in_gdb_thread
+def frame_for_id(id):
+    """Given a frame identifier ID, return the corresponding frame."""
+    global _id_to_frame
+    return _id_to_frame[id]
 
--- /dev/null
+# 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/>.
+
+import json
+
+from .startup import start_thread, send_gdb
+
+
+def read_json(stream):
+    """Read a JSON-RPC message from STREAM.
+    The decoded object is returned."""
+    # First read and parse the header.
+    content_length = None
+    while True:
+        line = stream.readline()
+        line = line.strip()
+        if line == b"":
+            break
+        if line.startswith(b"Content-Length:"):
+            line = line[15:].strip()
+            content_length = int(line)
+    data = bytes()
+    while len(data) < content_length:
+        new_data = stream.read(content_length - len(data))
+        data += new_data
+    result = json.loads(data)
+    return result
+
+
+def start_json_writer(stream, queue):
+    """Start the JSON writer thread.
+    It will read objects from QUEUE and write them to STREAM,
+    following the JSON-RPC protocol."""
+
+    def _json_writer():
+        seq = 1
+        while True:
+            obj = queue.get()
+            if obj is None:
+                # This is an exit request.  The stream is already
+                # flushed, so all that's left to do is request an
+                # exit.
+                send_gdb("quit")
+                break
+            obj["seq"] = seq
+            seq = seq + 1
+            encoded = json.dumps(obj)
+            body_bytes = encoded.encode("utf-8")
+            header = f"Content-Length: {len(body_bytes)}\r\n\r\n"
+            header_bytes = header.encode("ASCII")
+            stream.write(header_bytes)
+            stream.write(body_bytes)
+            stream.flush()
+
+    start_thread("JSON writer", _json_writer)
 
--- /dev/null
+# 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/>.
+
+from .events import ExecutionInvoker
+from .server import request, capability
+from .startup import send_gdb
+
+
+_program = None
+
+
+@request("launch")
+def launch(*, program=None, **args):
+    if program is not None:
+        global _program
+        _program = program
+        send_gdb(f"file {_program}")
+
+
+@capability("supportsConfigurationDoneRequest")
+@request("configurationDone")
+def config_done(**args):
+    global _program
+    if _program is not None:
+        # Suppress the continue event, but don't set any particular
+        # expected stop.
+        send_gdb(ExecutionInvoker("run", None))
 
--- /dev/null
+# 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/>.
+
+from .events import StopKinds, ExecutionInvoker
+from .server import capability, request
+from .startup import send_gdb
+from .state import set_thread
+
+
+# Helper function to set the current thread.
+def _handle_thread_step(threadId):
+    # Ensure we're going to step the correct thread.
+    send_gdb(lambda: set_thread(threadId))
+
+
+@request("next")
+def next(*, threadId, granularity="statement", **args):
+    _handle_thread_step(threadId)
+    cmd = "next"
+    if granularity == "instruction":
+        cmd += "i"
+    send_gdb(ExecutionInvoker(cmd, StopKinds.STEP))
+
+
+@capability("supportsSteppingGranularity")
+@request("stepIn")
+def stepIn(*, threadId, granularity="statement", **args):
+    _handle_thread_step(threadId)
+    cmd = "step"
+    if granularity == "instruction":
+        cmd += "i"
+    send_gdb(ExecutionInvoker(cmd, StopKinds.STEP))
+
+
+@request("continue")
+def continue_request(**args):
+    send_gdb(ExecutionInvoker("continue", None))
+    # FIXME Just ignore threadId for the time being, and assume all-stop.
+    return {"allThreadsContinued": True}
 
--- /dev/null
+# 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/>.
+
+from .events import StopKinds, ExecutionInvoker
+from .server import request
+from .startup import send_gdb
+
+
+@request("pause")
+def pause(**args):
+    send_gdb(ExecutionInvoker("interrupt -a", StopKinds.PAUSE))
 
--- /dev/null
+# 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/>.
+
+import gdb
+
+from .frames import frame_for_id
+from .startup import send_gdb_with_response, in_gdb_thread
+from .server import request
+
+
+# Helper function to return a frame's block without error.
+@in_gdb_thread
+def _safe_block(frame):
+    try:
+        return frame.block()
+    except gdb.error:
+        return None
+
+
+# Helper function to return a list of variables of block, up to the
+# enclosing function.
+@in_gdb_thread
+def _block_vars(block):
+    result = []
+    while True:
+        result += list(block)
+        if block.function is not None:
+            break
+        block = block.superblock
+    return result
+
+
+# Helper function to create a DAP scopes for a given frame ID.
+@in_gdb_thread
+def _get_scope(id):
+    frame = frame_for_id(id)
+    block = _safe_block(frame)
+    scopes = []
+    if block is not None:
+        new_scope = {
+            # FIXME
+            "name": "Locals",
+            "expensive": False,
+            "namedVariables": len(_block_vars(block)),
+        }
+        scopes.append(new_scope)
+    return scopes
+
+
+@request("scopes")
+def scopes(*, frameId, **extra):
+    scopes = send_gdb_with_response(lambda: _get_scope(frameId))
+    return {"scopes": scopes}
 
--- /dev/null
+# 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/>.
+
+import json
+import queue
+
+from .io import start_json_writer, read_json
+from .startup import (
+    in_dap_thread,
+    start_thread,
+    log,
+    log_stack,
+    send_gdb_with_response,
+)
+
+
+# Map capability names to values.
+_capabilities = {}
+
+# Map command names to callables.
+_commands = {}
+
+# The global server.
+_server = None
+
+
+class Server:
+    """The DAP server class."""
+
+    def __init__(self, in_stream, out_stream, child_stream):
+        self.in_stream = in_stream
+        self.out_stream = out_stream
+        self.child_stream = child_stream
+        self.delayed_events = []
+        # This queue accepts JSON objects that are then sent to the
+        # DAP client.  Writing is done in a separate thread to avoid
+        # blocking the read loop.
+        self.write_queue = queue.SimpleQueue()
+        self.done = False
+        global _server
+        _server = self
+
+    # Treat PARAMS as a JSON-RPC request and perform its action.
+    # PARAMS is just a dictionary from the JSON.
+    @in_dap_thread
+    def _handle_command(self, params):
+        # We don't handle 'cancel' for now.
+        result = {
+            "request_seq": params["seq"],
+            "type": "response",
+            "command": params["command"],
+        }
+        try:
+            if "arguments" in params:
+                args = params["arguments"]
+            else:
+                args = {}
+            global _commands
+            body = _commands[params["command"]](**args)
+            if body is not None:
+                result["body"] = body
+            result["success"] = True
+        except BaseException as e:
+            log_stack()
+            result["success"] = False
+            result["message"] = str(e)
+        return result
+
+    # Read inferior output and sends OutputEvents to the client.  It
+    # is run in its own thread.
+    def _read_inferior_output(self):
+        while True:
+            line = self.child_stream.readline()
+            self.send_event(
+                "output",
+                {
+                    "category": "stdout",
+                    "output": line,
+                },
+            )
+
+    # Send OBJ to the client, logging first if needed.
+    def _send_json(self, obj):
+        log("WROTE: <<<" + json.dumps(obj) + ">>>")
+        self.write_queue.put(obj)
+
+    # This must be run in the DAP thread, but we can't use
+    # @in_dap_thread here because the global isn't set until after
+    # this starts running.  FIXME.
+    def main_loop(self):
+        """The main loop of the DAP server."""
+        # Before looping, start the thread that writes JSON to the
+        # client, and the thread that reads output from the inferior.
+        start_thread("output reader", self._read_inferior_output)
+        start_json_writer(self.out_stream, self.write_queue)
+        while not self.done:
+            cmd = read_json(self.in_stream)
+            log("READ: <<<" + json.dumps(cmd) + ">>>")
+            result = self._handle_command(cmd)
+            self._send_json(result)
+            events = self.delayed_events
+            self.delayed_events = []
+            for (event, body) in events:
+                self.send_event(event, body)
+        # Got the terminate request.  This is handled by the
+        # JSON-writing thread, so that we can ensure that all
+        # responses are flushed to the client before exiting.
+        self.write_queue.put(None)
+
+    @in_dap_thread
+    def send_event_later(self, event, body=None):
+        """Send a DAP event back to the client, but only after the
+        current request has completed."""
+        self.delayed_events.append((event, body))
+
+    # Note that this does not need to be run in any particular thread,
+    # because it just creates an object and writes it to a thread-safe
+    # queue.
+    def send_event(self, event, body=None):
+        """Send an event to the DAP client.
+        EVENT is the name of the event, a string.
+        BODY is the body of the event, an arbitrary object."""
+        obj = {
+            "type": "event",
+            "event": event,
+        }
+        if body is not None:
+            obj["body"] = body
+        self._send_json(obj)
+
+    def shutdown(self):
+        """Request that the server shut down."""
+        # Just set a flag.  This operation is complicated because we
+        # want to write the result of the request before exiting.  See
+        # main_loop.
+        self.done = True
+
+
+def send_event(event, body):
+    """Send an event to the DAP client.
+    EVENT is the name of the event, a string.
+    BODY is the body of the event, an arbitrary object."""
+    global _server
+    _server.send_event(event, body)
+
+
+def request(name):
+    """A decorator that indicates that the wrapper function implements
+    the DAP request NAME."""
+
+    def wrap(func):
+        global _commands
+        _commands[name] = func
+        # All requests must run in the DAP thread.
+        return in_dap_thread(func)
+
+    return wrap
+
+
+def capability(name):
+    """A decorator that indicates that the wrapper function implements
+    the DAP capability NAME."""
+
+    def wrap(func):
+        global _capabilities
+        _capabilities[name] = True
+        return func
+
+    return wrap
+
+
+@request("initialize")
+def initialize(**args):
+    global _server, _capabilities
+    _server.config = args
+    _server.send_event_later("initialized")
+    return _capabilities.copy()
+
+
+@request("terminate")
+@capability("supportsTerminateRequest")
+def terminate(**args):
+    # We can ignore the result here, because we only really need to
+    # synchronize.
+    send_gdb_with_response("kill")
+
+
+@request("disconnect")
+@capability("supportTerminateDebuggee")
+def disconnect(*, terminateDebuggee=False, **args):
+    if terminateDebuggee:
+        terminate()
+    _server.shutdown()
 
--- /dev/null
+# 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/>.
+
+# Do not import other gdbdap modules here -- this module must come
+# first.
+import functools
+import gdb
+import queue
+import signal
+import threading
+import traceback
+from contextlib import contextmanager
+
+
+# The GDB thread, aka the main thread.
+_gdb_thread = threading.current_thread()
+
+
+# The DAP thread.
+_dap_thread = None
+
+
+@contextmanager
+def blocked_signals():
+    """A helper function that blocks and unblocks signals."""
+    if not hasattr(signal, "pthread_sigmask"):
+        yield
+        return
+
+    to_block = {signal.SIGCHLD, signal.SIGINT, signal.SIGALRM, signal.SIGWINCH}
+    signal.pthread_sigmask(signal.SIG_BLOCK, to_block)
+    try:
+        yield None
+    finally:
+        signal.pthread_sigmask(signal.SIG_UNBLOCK, to_block)
+
+
+def start_thread(name, target, args=()):
+    """Start a new thread, invoking TARGET with *ARGS there.
+    This is a helper function that ensures that any GDB signals are
+    correctly blocked."""
+    # GDB requires that these be delivered to the gdb thread.  We
+    # do this here to avoid any possible race with the creation of
+    # the new thread.  The thread mask is inherited by new
+    # threads.
+    with blocked_signals():
+        result = threading.Thread(target=target, args=args, daemon=True)
+        result.start()
+        return result
+
+
+def start_dap(target):
+    """Start the DAP thread and invoke TARGET there."""
+    global _dap_thread
+    exec_and_log("set breakpoint pending on")
+    _dap_thread = start_thread("DAP", target)
+
+
+def in_gdb_thread(func):
+    """A decorator that asserts that FUNC must be run in the GDB thread."""
+
+    @functools.wraps(func)
+    def ensure_gdb_thread(*args, **kwargs):
+        assert threading.current_thread() is _gdb_thread
+        return func(*args, **kwargs)
+
+    return ensure_gdb_thread
+
+
+def in_dap_thread(func):
+    """A decorator that asserts that FUNC must be run in the DAP thread."""
+
+    @functools.wraps(func)
+    def ensure_dap_thread(*args, **kwargs):
+        assert threading.current_thread() is _dap_thread
+        return func(*args, **kwargs)
+
+    return ensure_dap_thread
+
+
+class LoggingParam(gdb.Parameter):
+    """Whether DAP logging is enabled."""
+
+    set_doc = "Set the DAP logging status."
+    show_doc = "Show the DAP logging status."
+
+    log_file = None
+
+    def __init__(self):
+        super().__init__(
+            "debug dap-log-file", gdb.COMMAND_MAINTENANCE, gdb.PARAM_OPTIONAL_FILENAME
+        )
+        self.value = None
+
+    def get_set_string(self):
+        # Close any existing log file, no matter what.
+        if self.log_file is not None:
+            self.log_file.close()
+            self.log_file = None
+        if self.value is not None:
+            self.log_file = open(self.value, "w")
+        return ""
+
+
+dap_log = LoggingParam()
+
+
+def log(something):
+    """Log SOMETHING to the log file, if logging is enabled."""
+    if dap_log.log_file is not None:
+        print(something, file=dap_log.log_file)
+        dap_log.log_file.flush()
+
+
+def log_stack():
+    """Log a stack trace to the log file, if logging is enabled."""
+    if dap_log.log_file is not None:
+        traceback.print_exc(file=dap_log.log_file)
+
+
+@in_gdb_thread
+def exec_and_log(cmd):
+    """Execute the gdb command CMD.
+    If logging is enabled, log the command and its output."""
+    log("+++ " + cmd)
+    try:
+        output = gdb.execute(cmd, from_tty=True, to_string=True)
+        if output != "":
+            log(">>> " + output)
+    except gdb.error:
+        log_stack()
+
+
+class Invoker(object):
+    """A simple class that can invoke a gdb command."""
+
+    def __init__(self, cmd):
+        self.cmd = cmd
+
+    # This is invoked in the gdb thread to run the command.
+    @in_gdb_thread
+    def __call__(self):
+        exec_and_log(self.cmd)
+
+
+def send_gdb(cmd):
+    """Send CMD to the gdb thread.
+    CMD can be either a function or a string.
+    If it is a string, it is passed to gdb.execute."""
+    if isinstance(cmd, str):
+        cmd = Invoker(cmd)
+    gdb.post_event(cmd)
+
+
+def send_gdb_with_response(fn):
+    """Send FN to the gdb thread and return its result.
+    If FN is a string, it is passed to gdb.execute and None is
+    returned as the result.
+    If FN throws an exception, this function will throw the
+    same exception in the calling thread.
+    """
+    if isinstance(fn, str):
+        fn = Invoker(fn)
+    result_q = queue.SimpleQueue()
+
+    def message():
+        try:
+            val = fn()
+            result_q.put(val)
+        except Exception as e:
+            result_q.put(e)
+
+    send_gdb(message)
+    val = result_q.get()
+    if isinstance(val, Exception):
+        raise val
+    return val
 
--- /dev/null
+# 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/>.
+
+from .startup import in_gdb_thread, exec_and_log, log
+
+
+@in_gdb_thread
+def set_thread(thread_id):
+    """Set the current thread to THREAD_ID."""
+    if thread_id == 0:
+        log("+++ Thread == 0 +++")
+    else:
+        exec_and_log(f"thread {thread_id}")
 
--- /dev/null
+# 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/>.
+
+import gdb
+
+from .server import request
+from .startup import send_gdb_with_response, in_gdb_thread
+
+
+# A helper function to construct the list of threads.
+@in_gdb_thread
+def _get_threads():
+    result = []
+    for thr in gdb.selected_inferior().threads():
+        one_result = {
+            "id": thr.global_num,
+        }
+        name = thr.name
+        if name is not None:
+            one_result["name"] = name
+        result.append(one_result)
+    return result
+
+
+@request("threads")
+def threads(**args):
+    result = send_gdb_with_response(_get_threads)
+    return {
+        "threads": result,
+    }
 
--- /dev/null
+/* Python DAP interpreter
+
+   Copyright (C) 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/>.  */
+
+#include "defs.h"
+#include "python-internal.h"
+#include "interps.h"
+#include "cli-out.h"
+#include "top.h"
+
+class dap_interp final : public interp
+{
+public:
+
+  explicit dap_interp (const char *name)
+    : interp (name),
+      m_ui_out (new cli_ui_out (gdb_stdout))
+  {
+  }
+
+  ~dap_interp () override = default;
+
+  void init (bool top_level) override;
+
+  void suspend () override
+  {
+  }
+
+  void resume () override
+  {
+  }
+
+  gdb_exception exec (const char *command) override
+  {
+    /* Just ignore it.  */
+    return {};
+  }
+
+  void set_logging (ui_file_up logfile, bool logging_redirect,
+                   bool debug_redirect) override
+  {
+    /* Just ignore it.  */
+  }
+
+  ui_out *interp_ui_out () override
+  {
+    return m_ui_out.get ();
+  }
+
+private:
+
+  std::unique_ptr<ui_out> m_ui_out;
+};
+
+void
+dap_interp::init (bool top_level)
+{
+  gdbpy_enter enter_py;
+
+  gdbpy_ref<> dap_module (PyImport_ImportModule ("gdb.dap"));
+  if (dap_module == nullptr)
+    gdbpy_handle_exception ();
+
+  gdbpy_ref<> func (PyObject_GetAttrString (dap_module.get (), "run"));
+  if (func == nullptr)
+    gdbpy_handle_exception ();
+
+  gdbpy_ref<> result_obj (PyObject_CallNoArgs (func.get ()));
+  if (result_obj == nullptr)
+    gdbpy_handle_exception ();
+
+  current_ui->input_fd = -1;
+  current_ui->m_input_interactive_p = false;
+}
+
+void _initialize_py_interp ();
+void
+_initialize_py_interp ()
+{
+  interp_factory_register ("dap", [] (const char *name) -> interp *
+    {
+      return new dap_interp (name);
+    });
+}
 
--- /dev/null
+/* 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 */
+}
 
--- /dev/null
+# 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
 
--- /dev/null
+# 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]
+}
 
        return [mi_gdb_start_separate_mi_tty $flags]
     }
 
-    set inferior_pty no-tty
-
     # Set the default value, it may be overriden later by specific testfile.
     set use_gdb_stub [target_info exists use_gdb_stub]
 
 
--- /dev/null
+# This was imported into gdb from:
+# https://github.com/jorge-leon/ton
+
+# This software is copyrighted by Georg Lehner <jorge@at.anteris.net>.
+# The following terms apply to all files associated with the software
+# unless explicitly disclaimed in individual files.
+
+# The authors hereby grant permission to use, copy, modify, distribute,
+# and license this software and its documentation for any purpose,
+# provided that existing copyright notices are retained in all copies
+# and that this notice is included verbatim in any distributions. No
+# written agreement, license, or royalty fee is required for any of the
+# authorized uses.  Modifications to this software may be copyrighted by
+# their authors and need not follow the licensing terms described here,
+# provided that the new terms are clearly indicated on the first page of
+# each file where they apply.
+
+# IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY
+# FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
+# ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY
+# DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+# THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
+# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND
+# NON-INFRINGEMENT.  THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, AND
+# THE AUTHORS AND DISTRIBUTORS HAVE NO OBLIGATION TO PROVIDE
+# MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+
+# GOVERNMENT USE: If you are acquiring this software on behalf of the
+# U.S. government, the Government shall have only "Restricted Rights" in
+# the software and related documentation as defined in the Federal
+# Acquisition Regulations (FARs) in Clause 52.227.19 (c) (2).  If you
+# are acquiring the software on behalf of the Department of Defense, the
+# software shall be classified as "Commercial Computer Software" and the
+# Government shall have only "Restricted Rights" as defined in Clause
+# 252.227-7013 (c) (1) of DFARs.  Notwithstanding the foregoing, the
+# authors grant the U.S. Government and others acting in its behalf
+# permission to use and distribute the software in accordance with the
+# terms specified in this license.
+
+
+# leg20180331: ton / TON - Tcl Object Notation
+#
+# This package provides manipulation functionality for TON - a data
+# serialization format with a direct mapping to JSON.
+#
+# In its essence, a JSON parser is provided, which can convert a JSON
+# string into a Tcllib json style dictionary (dicts and arrays mixed),
+# into a jimhttp style dictionary (only dicts) or into a nested, typed
+# Tcl list.
+#
+# Finally, TON can be converted into (unformatted) JSON.
+
+namespace eval ton {
+    namespace export json2ton
+
+    variable version 0.4
+    
+}
+proc ton::json2ton json {
+    # Parse JSON string json
+    #
+    # return: TON
+    
+    set i [trr $json [string length $json]]
+    if {!$i} {return ""}
+    lassign [jscan $json $i] i ton
+    if {[set i [trr $json $i]]} {
+       error "json string invalid:[incr i -1]: left over characters."
+    }
+    return $ton
+}
+proc ton::trr {s i} {
+    # Trim righthand whitespace on the first i characters of s.
+    # return: number of remaining characters in s
+    
+    while {[set j $i] &&
+          ([string is space [set c [string index $s [incr i -1]]]]
+           || $c eq "\n")} {}
+    return $j
+}
+proc ton::jscan {json i {d :}} {
+    # Scan JSON in first i characters of string json.
+    # d is the default delimiter list for the next token.
+    #
+    # return list of
+    # - remaining characters in json
+    # - TON
+    #
+    # The string must already be whitespace trimmed from the right.
+
+    incr i -1
+
+    if {[set c [string index $json $i]] eq "\""} {
+       str $json [incr i -1]
+    } elseif {$c eq "\}"} {
+       obj $json $i
+    } elseif {$c eq "\]"} {
+       arr $json $i
+    } elseif {$c in {e l}} {
+       lit $json $i
+    } elseif {[string match {[0-9.]} $c]} {
+       num $json $i $c $d
+    } else {
+       error "json string end invalid:$i: ..[string range $json $i-10 $i].."
+    }
+}
+proc ton::num {json i c d} {
+    # Parse number from position i in string json to the left.
+    # c .. character at position i
+    # d .. delimiter on which to stop
+    #
+    # return list:
+    # - remaining string length
+    # - TON of number
+    
+    set float [expr {$c eq "."}]
+    for {set j $i} {$i} {incr i -1} {
+       if {[string match $d [set c [string index $json $i-1]]]} break
+       set float [expr {$float || [string match "\[eE.]" $c]}]
+    }
+    set num [string trimleft [string range $json $i $j]]
+    if {!$float && [string is entier $num]} {
+           list $i "i $num"
+    } elseif {$float && [string is double $num]} {
+       list $i "d $num"
+    } else {
+       error "number invalid:$i: $num."
+    }
+}
+proc ton::lit {json i} {
+    # Parse literal from position i in string json to the left
+    # return list:
+    # - remaining string length
+    # - TON of literal
+
+    if {[set c [string index $json $i-1]] eq "u"} {
+       list [incr i -3] "l true"
+    } elseif {$c eq "s"} {
+       list [incr i -4] "l false"
+    } elseif {$c eq "l"} {
+       list [incr i -3] "l null"
+    } else {
+       set e [string range $json $i-3 $i]
+       error "literal invalid:[incr i -1]: ..$e."
+    }
+}
+proc ton::str {json i} {
+    # Parse string from position i in string json to the left
+    # return list:
+    # - remaining string length
+    # - TON of string
+    
+    for {set j $i} {$i} {incr i -1} {
+       set i [string last \" $json $i]
+       if {[string index $json $i-1] ne "\\"} break
+    }
+    if {$i==-1} {
+       error "json string start invalid:$i: exhausted while parsing string."
+    }
+    list $i "s [list [string range $json $i+1 $j]]"
+}
+proc ton::arr {json i} {
+    # Parse array from i characters in string json
+    # return list:
+    # - remaining string length
+    # - TON of array
+    
+    set i [trr $json $i]
+    if {!$i} {
+       error "json string invalid:0: exhausted while parsing array."
+    }
+    if {[string index $json $i-1] eq "\["} {
+       return [list [incr i -1] a]
+    }
+    set r {}
+    while {$i} {
+       lassign [jscan $json $i "\[,\[]"] i v
+       lappend r \[$v\]
+       set i [trr $json $i]
+       incr i -1
+       if {[set c [string index $json $i]] eq ","} {
+           set i [trr $json $i]
+           continue
+       } elseif {$c eq "\["} break
+       error "json string invalid:$i: parsing array."
+    }
+    lappend r a
+    return [list $i [join [lreverse $r]]]
+}
+proc ton::obj {json i} {
+    # Parse array from i character in string json
+    # return list:
+    # - remaining string length
+    # - TON of object
+
+    set i [trr $json $i]
+    if {!$i} {
+       error "json string invalid:0: exhausted while parsing object."
+    }
+    if {[string index $json $i-1] eq "\{"} {
+       return [list [incr i -1] o]
+    }
+    set r {}
+    while {$i} {
+       lassign [jscan $json $i] i v
+       set i [trr $json $i]
+       incr i -1
+       if {[string index $json $i] ne ":"} {
+           error "json string invalid:$i: parsing key in object."
+       }
+       set i [trr $json $i]
+       lassign [jscan $json $i] i k
+       lassign $k type k
+       if {$type ne "s"} {
+           error "json string invalid:[incr i -1]: key not a string."
+       }
+       lappend r \[$v\] [list $k]
+       set i [trr $json $i]
+       incr i -1
+       if {[set c [string index $json $i]] eq ","} {
+           set i [trr $json $i]
+           continue
+       } elseif {$c eq "\{"} break
+       error "json string invalid:$i: parsing object." 
+    }
+    lappend r o
+    return [list $i [join [lreverse $r]]]
+}
+# TON decoders
+namespace eval ton::2list {
+    proc atom {type v} {list $type $v}
+    foreach type {i d s l} {
+       interp alias {} $type {} [namespace current]::atom $type
+    }
+    proc a args {
+       set r a
+       foreach v $args {lappend r $v}
+       return $r
+    }
+    proc o args {
+       set r o
+       foreach {k v} $args {lappend r $k $v}
+       return $r
+    }
+    # There is plenty of room for validation in get
+    # array index bounds
+    # object key existence
+    proc get {l args} {
+       foreach k $args {
+           switch [lindex $l 0] {
+               o {set l [dict get [lrange $l 1 end] $k]}
+               a {set l [lindex $l [incr k]]}
+               default {
+                   error "error: key $k to long, or wrong data: [lindex $l 0]"
+               }
+           }
+       }
+       return $l
+    }
+}
+namespace eval ton::2dict {
+    proc atom v {return $v}
+    foreach type {i d l s} {
+       interp alias {} $type {} [namespace current]::atom
+    }
+    proc a args {return $args}
+    proc o args {return $args}
+}
+namespace eval ton::a2dict {
+    proc atom v {return $v}
+    foreach type {i d l s} {
+       interp alias {} $type {} [namespace current]::atom
+    }
+    proc a args {
+       set i -1
+       set r {}
+       foreach v $args {
+           lappend r [incr i] $v
+       }
+       return $r
+    }
+    proc o args {return $args}
+}
+namespace eval ton::2json {
+    proc atom v {return $v}
+    foreach type {i d l} {
+       interp alias {} $type {} [namespace current]::atom
+    }
+    proc a args {
+       return "\[[join $args {, }]]"
+    }
+    proc o args {
+       set r {}
+       foreach {k v} $args {lappend r "\"$k\": $v"}
+       return "{[join $r {, }]}"
+    }
+    proc s s {return "\"$s\""}
+}
+
+package provide ton $ton::version