public inbox for gcc-regression@sourceware.org
help / color / mirror / Atom feed
* [TCWG CI] Failure after gdb-13-branchpoint-245-gde7d7cb58e6: Initial implementation of Debugger Adapter Protocol
@ 2023-01-03 16:04 ci_notify
  0 siblings, 0 replies; 2+ messages in thread
From: ci_notify @ 2023-01-03 16:04 UTC (permalink / raw)
  To: Tom Tromey; +Cc: gcc-regression

[-- Attachment #1: Type: text/plain, Size: 39191 bytes --]

Failure after gdb-13-branchpoint-245-gde7d7cb58e6: Initial implementation of Debugger Adapter Protocol:

Results changed to
-10
# true:
0
# build_abe binutils:
1
# build_abe gcc:
2
# build_abe linux:
4
# build_abe glibc:
5
# build_abe gdb:
# FAILED
# First few build errors in logs:
# 00:01:42 ../../../../../../gdb/gdb/python/py-dap.c:83:27: error: ‘PyObject_CallNoArgs’ was not declared in this scope; did you mean ‘PyObject_Calloc’?
# 00:01:42 make[1]: *** [Makefile:1902: python/py-dap.o] Error 1
# 00:01:42 make: *** [Makefile:12546: all-gdb] Error 2

from
-10
# true:
0
# build_abe binutils:
1
# build_abe gcc:
2
# build_abe linux:
4
# build_abe glibc:
5
# build_abe gdb:
6

THIS IS THE END OF INTERESTING STUFF.  BELOW ARE LINKS TO BUILDS, REPRODUCTION INSTRUCTIONS, AND THE RAW COMMIT.

For latest status see comments in https://linaro.atlassian.net/browse/GNU-692 .
Status of gdb-13-branchpoint-245-gde7d7cb58e6 commit for tcwg_gnu_native_build:
commit de7d7cb58e6209ed11c31f635545ee2ee6ded307
Author: Tom Tromey <tromey@adacore.com>
Date:   Thu Jun 23 11:11:36 2022 -0600

    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.
* master-aarch64
** Failure after gdb-13-branchpoint-245-gde7d7cb58e6: Initial implementation of Debugger Adapter Protocol:
** https://ci.linaro.org/job/tcwg_gnu_native_build-build-master-aarch64/595/

Bad  build: https://ci.linaro.org/job/tcwg_gnu_native_build-build-master-aarch64/595/artifact/artifacts
Good build: https://ci.linaro.org/job/tcwg_gnu_native_build-build-master-aarch64/594/artifact/artifacts

Reproduce current build:
<cut>
mkdir -p investigate-gdb-de7d7cb58e6209ed11c31f635545ee2ee6ded307
cd investigate-gdb-de7d7cb58e6209ed11c31f635545ee2ee6ded307

# Fetch scripts
git clone https://git.linaro.org/toolchain/jenkins-scripts

# Fetch manifests for bad and good builds
mkdir -p bad/artifacts good/artifacts
curl -o bad/artifacts/manifest.sh https://ci.linaro.org/job/tcwg_gnu_native_build-build-master-aarch64/595/artifact/artifacts/manifest.sh --fail
curl -o good/artifacts/manifest.sh https://ci.linaro.org/job/tcwg_gnu_native_build-build-master-aarch64/594/artifact/artifacts/manifest.sh --fail

# Reproduce bad build
(cd bad; ../jenkins-scripts/tcwg_gnu-build.sh ^^ true %%rr[top_artifacts] artifacts)
# Reproduce good build
(cd good; ../jenkins-scripts/tcwg_gnu-build.sh ^^ true %%rr[top_artifacts] artifacts)
</cut>

Full commit (up to 1000 lines):
<cut>
commit de7d7cb58e6209ed11c31f635545ee2ee6ded307
Author: Tom Tromey <tromey@adacore.com>
Date:   Thu Jun 23 11:11:36 2022 -0600

    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.
---
 gdb/Makefile.in                       |   1 +
 gdb/NEWS                              |   4 +
 gdb/data-directory/Makefile.in        |  16 ++
 gdb/doc/gdb.texinfo                   |   9 +
 gdb/python/lib/gdb/dap/__init__.py    |  69 +++++++
 gdb/python/lib/gdb/dap/breakpoint.py  | 143 ++++++++++++++
 gdb/python/lib/gdb/dap/bt.py          |  93 +++++++++
 gdb/python/lib/gdb/dap/disassemble.py |  51 +++++
 gdb/python/lib/gdb/dap/evaluate.py    |  42 +++++
 gdb/python/lib/gdb/dap/events.py      | 166 ++++++++++++++++
 gdb/python/lib/gdb/dap/frames.py      |  57 ++++++
 gdb/python/lib/gdb/dap/io.py          |  67 +++++++
 gdb/python/lib/gdb/dap/launch.py      |  39 ++++
 gdb/python/lib/gdb/dap/next.py        |  51 +++++
 gdb/python/lib/gdb/dap/pause.py       |  23 +++
 gdb/python/lib/gdb/dap/scopes.py      |  65 +++++++
 gdb/python/lib/gdb/dap/server.py      | 205 ++++++++++++++++++++
 gdb/python/lib/gdb/dap/startup.py     | 189 +++++++++++++++++++
 gdb/python/lib/gdb/dap/state.py       |  25 +++
 gdb/python/lib/gdb/dap/threads.py     |  42 +++++
 gdb/python/py-dap.c                   |  99 ++++++++++
 gdb/testsuite/gdb.dap/basic-dap.c     |  44 +++++
 gdb/testsuite/gdb.dap/basic-dap.exp   | 151 +++++++++++++++
 gdb/testsuite/lib/dap-support.exp     | 343 ++++++++++++++++++++++++++++++++++
 gdb/testsuite/lib/mi-support.exp      |   2 -
 gdb/testsuite/lib/ton.tcl             | 303 ++++++++++++++++++++++++++++++
 26 files changed, 2297 insertions(+), 2 deletions(-)

diff --git a/gdb/Makefile.in b/gdb/Makefile.in
index c5d66e480d1..b22a6c624a6 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -396,6 +396,7 @@ SUBDIR_PYTHON_SRCS = \
 	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 \
diff --git a/gdb/NEWS b/gdb/NEWS
index e61f06081de..41d815567ce 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -5,6 +5,10 @@
 
 * 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.
diff --git a/gdb/data-directory/Makefile.in b/gdb/data-directory/Makefile.in
index 557a63b40d9..f1139291eed 100644
--- a/gdb/data-directory/Makefile.in
+++ b/gdb/data-directory/Makefile.in
@@ -87,6 +87,22 @@ PYTHON_FILE_LIST = \
 	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 \
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index a72b2b9eb26..ea54f25b08e 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -29136,6 +29136,15 @@ The traditional console or command-line interpreter.  This is the most often
 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
diff --git a/gdb/python/lib/gdb/dap/__init__.py b/gdb/python/lib/gdb/dap/__init__.py
new file mode 100644
index 00000000000..0df938623a9
--- /dev/null
+++ b/gdb/python/lib/gdb/dap/__init__.py
@@ -0,0 +1,69 @@
+# 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)
diff --git a/gdb/python/lib/gdb/dap/breakpoint.py b/gdb/python/lib/gdb/dap/breakpoint.py
new file mode 100644
index 00000000000..502beb0478e
--- /dev/null
+++ b/gdb/python/lib/gdb/dap/breakpoint.py
@@ -0,0 +1,143 @@
+# 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,
+    }
diff --git a/gdb/python/lib/gdb/dap/bt.py b/gdb/python/lib/gdb/dap/bt.py
new file mode 100644
index 00000000000..990ab135b05
--- /dev/null
+++ b/gdb/python/lib/gdb/dap/bt.py
@@ -0,0 +1,93 @@
+# 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))
diff --git a/gdb/python/lib/gdb/dap/disassemble.py b/gdb/python/lib/gdb/dap/disassemble.py
new file mode 100644
index 00000000000..3d3b3a5695a
--- /dev/null
+++ b/gdb/python/lib/gdb/dap/disassemble.py
@@ -0,0 +1,51 @@
+# 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)
+    )
diff --git a/gdb/python/lib/gdb/dap/evaluate.py b/gdb/python/lib/gdb/dap/evaluate.py
new file mode 100644
index 00000000000..c05e62d17a3
--- /dev/null
+++ b/gdb/python/lib/gdb/dap/evaluate.py
@@ -0,0 +1,42 @@
+# 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,
+    }
diff --git a/gdb/python/lib/gdb/dap/events.py b/gdb/python/lib/gdb/dap/events.py
new file mode 100644
index 00000000000..45e2a1e116d
--- /dev/null
+++ b/gdb/python/lib/gdb/dap/events.py
@@ -0,0 +1,166 @@
+# 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)
diff --git a/gdb/python/lib/gdb/dap/frames.py b/gdb/python/lib/gdb/dap/frames.py
new file mode 100644
index 00000000000..a1c2689c350
--- /dev/null
+++ b/gdb/python/lib/gdb/dap/frames.py
@@ -0,0 +1,57 @@
+# 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]
diff --git a/gdb/python/lib/gdb/dap/io.py b/gdb/python/lib/gdb/dap/io.py
new file mode 100644
index 00000000000..656ac08b4ec
--- /dev/null
+++ b/gdb/python/lib/gdb/dap/io.py
@@ -0,0 +1,67 @@
+# 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)
diff --git a/gdb/python/lib/gdb/dap/launch.py b/gdb/python/lib/gdb/dap/launch.py
new file mode 100644
index 00000000000..7ac81779111
--- /dev/null
+++ b/gdb/python/lib/gdb/dap/launch.py
@@ -0,0 +1,39 @@
+# 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))
diff --git a/gdb/python/lib/gdb/dap/next.py b/gdb/python/lib/gdb/dap/next.py
new file mode 100644
index 00000000000..726b659db7f
--- /dev/null
+++ b/gdb/python/lib/gdb/dap/next.py
@@ -0,0 +1,51 @@
+# 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}
diff --git a/gdb/python/lib/gdb/dap/pause.py b/gdb/python/lib/gdb/dap/pause.py
new file mode 100644
index 00000000000..74fdf48cdfa
--- /dev/null
+++ b/gdb/python/lib/gdb/dap/pause.py
@@ -0,0 +1,23 @@
+# 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
</cut>

^ permalink raw reply	[flat|nested] 2+ messages in thread

* [TCWG CI] Failure after gdb-13-branchpoint-245-gde7d7cb58e6: Initial implementation of Debugger Adapter Protocol
@ 2023-01-03 10:45 ci_notify
  0 siblings, 0 replies; 2+ messages in thread
From: ci_notify @ 2023-01-03 10:45 UTC (permalink / raw)
  To: Tom Tromey; +Cc: gcc-regression

[-- Attachment #1: Type: text/plain, Size: 39266 bytes --]

Failure after gdb-13-branchpoint-245-gde7d7cb58e6: Initial implementation of Debugger Adapter Protocol:

Results changed to
-10
# true:
0
# build_abe binutils:
1
# build_abe stage1:
2
# build_abe linux:
3
# build_abe glibc:
4
# build_abe stage2:
5
# build_abe gdb:
# FAILED
# First few build errors in logs:
# 00:03:06 ../../../../../../gdb/gdb/python/py-dap.c:83:27: error: ‘PyObject_CallNoArgs’ was not declared in this scope; did you mean ‘PyObject_Calloc’?
# 00:03:06 make[1]: *** [Makefile:1898: python/py-dap.o] Error 1
# 00:03:13 make: *** [Makefile:12190: all-gdb] Error 2

from
-10
# true:
0
# build_abe binutils:
1
# build_abe stage1:
2
# build_abe linux:
3
# build_abe glibc:
4
# build_abe stage2:
5
# build_abe gdb:
6
# build_abe qemu:
7

THIS IS THE END OF INTERESTING STUFF.  BELOW ARE LINKS TO BUILDS, REPRODUCTION INSTRUCTIONS, AND THE RAW COMMIT.

For latest status see comments in https://linaro.atlassian.net/browse/GNU-692 .
Status of gdb-13-branchpoint-245-gde7d7cb58e6 commit for tcwg_gnu_cross_build:
commit de7d7cb58e6209ed11c31f635545ee2ee6ded307
Author: Tom Tromey <tromey@adacore.com>
Date:   Thu Jun 23 11:11:36 2022 -0600

    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.
* master-aarch64
** Failure after gdb-13-branchpoint-245-gde7d7cb58e6: Initial implementation of Debugger Adapter Protocol:
** https://ci.linaro.org/job/tcwg_gnu_cross_build-build-master-aarch64/2007/

Bad  build: https://ci.linaro.org/job/tcwg_gnu_cross_build-build-master-aarch64/2007/artifact/artifacts
Good build: https://ci.linaro.org/job/tcwg_gnu_cross_build-build-master-aarch64/2006/artifact/artifacts

Reproduce current build:
<cut>
mkdir -p investigate-gdb-de7d7cb58e6209ed11c31f635545ee2ee6ded307
cd investigate-gdb-de7d7cb58e6209ed11c31f635545ee2ee6ded307

# Fetch scripts
git clone https://git.linaro.org/toolchain/jenkins-scripts

# Fetch manifests for bad and good builds
mkdir -p bad/artifacts good/artifacts
curl -o bad/artifacts/manifest.sh https://ci.linaro.org/job/tcwg_gnu_cross_build-build-master-aarch64/2007/artifact/artifacts/manifest.sh --fail
curl -o good/artifacts/manifest.sh https://ci.linaro.org/job/tcwg_gnu_cross_build-build-master-aarch64/2006/artifact/artifacts/manifest.sh --fail

# Reproduce bad build
(cd bad; ../jenkins-scripts/tcwg_gnu-build.sh ^^ true %%rr[top_artifacts] artifacts)
# Reproduce good build
(cd good; ../jenkins-scripts/tcwg_gnu-build.sh ^^ true %%rr[top_artifacts] artifacts)
</cut>

Full commit (up to 1000 lines):
<cut>
commit de7d7cb58e6209ed11c31f635545ee2ee6ded307
Author: Tom Tromey <tromey@adacore.com>
Date:   Thu Jun 23 11:11:36 2022 -0600

    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.
---
 gdb/Makefile.in                       |   1 +
 gdb/NEWS                              |   4 +
 gdb/data-directory/Makefile.in        |  16 ++
 gdb/doc/gdb.texinfo                   |   9 +
 gdb/python/lib/gdb/dap/__init__.py    |  69 +++++++
 gdb/python/lib/gdb/dap/breakpoint.py  | 143 ++++++++++++++
 gdb/python/lib/gdb/dap/bt.py          |  93 +++++++++
 gdb/python/lib/gdb/dap/disassemble.py |  51 +++++
 gdb/python/lib/gdb/dap/evaluate.py    |  42 +++++
 gdb/python/lib/gdb/dap/events.py      | 166 ++++++++++++++++
 gdb/python/lib/gdb/dap/frames.py      |  57 ++++++
 gdb/python/lib/gdb/dap/io.py          |  67 +++++++
 gdb/python/lib/gdb/dap/launch.py      |  39 ++++
 gdb/python/lib/gdb/dap/next.py        |  51 +++++
 gdb/python/lib/gdb/dap/pause.py       |  23 +++
 gdb/python/lib/gdb/dap/scopes.py      |  65 +++++++
 gdb/python/lib/gdb/dap/server.py      | 205 ++++++++++++++++++++
 gdb/python/lib/gdb/dap/startup.py     | 189 +++++++++++++++++++
 gdb/python/lib/gdb/dap/state.py       |  25 +++
 gdb/python/lib/gdb/dap/threads.py     |  42 +++++
 gdb/python/py-dap.c                   |  99 ++++++++++
 gdb/testsuite/gdb.dap/basic-dap.c     |  44 +++++
 gdb/testsuite/gdb.dap/basic-dap.exp   | 151 +++++++++++++++
 gdb/testsuite/lib/dap-support.exp     | 343 ++++++++++++++++++++++++++++++++++
 gdb/testsuite/lib/mi-support.exp      |   2 -
 gdb/testsuite/lib/ton.tcl             | 303 ++++++++++++++++++++++++++++++
 26 files changed, 2297 insertions(+), 2 deletions(-)

diff --git a/gdb/Makefile.in b/gdb/Makefile.in
index c5d66e480d1..b22a6c624a6 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -396,6 +396,7 @@ SUBDIR_PYTHON_SRCS = \
 	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 \
diff --git a/gdb/NEWS b/gdb/NEWS
index e61f06081de..41d815567ce 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -5,6 +5,10 @@
 
 * 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.
diff --git a/gdb/data-directory/Makefile.in b/gdb/data-directory/Makefile.in
index 557a63b40d9..f1139291eed 100644
--- a/gdb/data-directory/Makefile.in
+++ b/gdb/data-directory/Makefile.in
@@ -87,6 +87,22 @@ PYTHON_FILE_LIST = \
 	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 \
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index a72b2b9eb26..ea54f25b08e 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -29136,6 +29136,15 @@ The traditional console or command-line interpreter.  This is the most often
 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
diff --git a/gdb/python/lib/gdb/dap/__init__.py b/gdb/python/lib/gdb/dap/__init__.py
new file mode 100644
index 00000000000..0df938623a9
--- /dev/null
+++ b/gdb/python/lib/gdb/dap/__init__.py
@@ -0,0 +1,69 @@
+# 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)
diff --git a/gdb/python/lib/gdb/dap/breakpoint.py b/gdb/python/lib/gdb/dap/breakpoint.py
new file mode 100644
index 00000000000..502beb0478e
--- /dev/null
+++ b/gdb/python/lib/gdb/dap/breakpoint.py
@@ -0,0 +1,143 @@
+# 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,
+    }
diff --git a/gdb/python/lib/gdb/dap/bt.py b/gdb/python/lib/gdb/dap/bt.py
new file mode 100644
index 00000000000..990ab135b05
--- /dev/null
+++ b/gdb/python/lib/gdb/dap/bt.py
@@ -0,0 +1,93 @@
+# 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))
diff --git a/gdb/python/lib/gdb/dap/disassemble.py b/gdb/python/lib/gdb/dap/disassemble.py
new file mode 100644
index 00000000000..3d3b3a5695a
--- /dev/null
+++ b/gdb/python/lib/gdb/dap/disassemble.py
@@ -0,0 +1,51 @@
+# 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)
+    )
diff --git a/gdb/python/lib/gdb/dap/evaluate.py b/gdb/python/lib/gdb/dap/evaluate.py
new file mode 100644
index 00000000000..c05e62d17a3
--- /dev/null
+++ b/gdb/python/lib/gdb/dap/evaluate.py
@@ -0,0 +1,42 @@
+# 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,
+    }
diff --git a/gdb/python/lib/gdb/dap/events.py b/gdb/python/lib/gdb/dap/events.py
new file mode 100644
index 00000000000..45e2a1e116d
--- /dev/null
+++ b/gdb/python/lib/gdb/dap/events.py
@@ -0,0 +1,166 @@
+# 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)
diff --git a/gdb/python/lib/gdb/dap/frames.py b/gdb/python/lib/gdb/dap/frames.py
new file mode 100644
index 00000000000..a1c2689c350
--- /dev/null
+++ b/gdb/python/lib/gdb/dap/frames.py
@@ -0,0 +1,57 @@
+# 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]
diff --git a/gdb/python/lib/gdb/dap/io.py b/gdb/python/lib/gdb/dap/io.py
new file mode 100644
index 00000000000..656ac08b4ec
--- /dev/null
+++ b/gdb/python/lib/gdb/dap/io.py
@@ -0,0 +1,67 @@
+# 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)
diff --git a/gdb/python/lib/gdb/dap/launch.py b/gdb/python/lib/gdb/dap/launch.py
new file mode 100644
index 00000000000..7ac81779111
--- /dev/null
+++ b/gdb/python/lib/gdb/dap/launch.py
@@ -0,0 +1,39 @@
+# 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))
diff --git a/gdb/python/lib/gdb/dap/next.py b/gdb/python/lib/gdb/dap/next.py
new file mode 100644
index 00000000000..726b659db7f
--- /dev/null
+++ b/gdb/python/lib/gdb/dap/next.py
@@ -0,0 +1,51 @@
+# 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}
diff --git a/gdb/python/lib/gdb/dap/pause.py b/gdb/python/lib/gdb/dap/pause.py
new file mode 100644
index 00000000000..74fdf48cdfa
--- /dev/null
+++ b/gdb/python/lib/gdb/dap/pause.py
@@ -0,0 +1,23 @@
+# 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
</cut>

^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2023-01-03 16:04 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-01-03 16:04 [TCWG CI] Failure after gdb-13-branchpoint-245-gde7d7cb58e6: Initial implementation of Debugger Adapter Protocol ci_notify
  -- strict thread matches above, loose matches on Subject: below --
2023-01-03 10:45 ci_notify

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).