public inbox for gdb-patches@sourceware.org
 help / color / mirror / Atom feed
* [PATCH v1] gdb/dap - dataBreakpointInfo & setDataBreakpoints
@ 2023-06-14 10:33 Simon Farre
  2023-06-14 11:43 ` Eli Zaretskii
  0 siblings, 1 reply; 4+ messages in thread
From: Simon Farre @ 2023-06-14 10:33 UTC (permalink / raw)
  To: gdb-patches; +Cc: tom, Simon Farre

This is v1 of the implementation of these two requests.

I've tested the implementation in my debug adapter for VSCode (Midas)
and it works fully.

However, I believe these two requests are flawed from a spec-perspective
and has filed an issue for the DAP spec here: https://github.com/microsoft/debug-adapter-protocol/issues/404
As such, it implements *most* of the functionality of these two
requests and as the github issues suggests, I believe implementing all
of it is not possible with the current spec design (also, VSCode does not
use the frameId field, in dataBreakpointInfo request).
---
 gdb/python/lib/gdb/dap/breakpoint.py | 160 ++++++++++++++++++++++++++-
 gdb/python/lib/gdb/dap/frames.py     |   2 +-
 gdb/python/lib/gdb/dap/varref.py     |  12 +-
 3 files changed, 166 insertions(+), 8 deletions(-)

diff --git a/gdb/python/lib/gdb/dap/breakpoint.py b/gdb/python/lib/gdb/dap/breakpoint.py
index 20e65aa0e61..2d5bf7d0ee4 100644
--- a/gdb/python/lib/gdb/dap/breakpoint.py
+++ b/gdb/python/lib/gdb/dap/breakpoint.py
@@ -17,11 +17,42 @@ import gdb
 import os
 
 # These are deprecated in 3.9, but required in older versions.
-from typing import Optional, Sequence
+from typing import Optional, Sequence, Mapping
 
 from .server import request, capability
-from .startup import send_gdb_with_response, in_gdb_thread
+from .startup import send_gdb_with_response, in_gdb_thread, log
+from .varref import find_variable
+from .frames import frame_for_id
+class DataBreakpoint:
+    def wp_type(type: str):
+        if type == "read":
+            return gdb.WP_READ
+        if type == "write":
+            return gdb.WP_WRITE
+        if type == "readWrite":
+            return gdb.WP_ACCESS
+        raise gdb.GdbError("Erroneous watchpoint type")
+
+    def __init__(self, dataId, type, condition=None, hitCondition=None):
+        self.dataId = dataId
+        self.type = type
+        self.condition = condition
+        self.hitCondition = hitCondition
+        # gdb.BP_HARDWARE_WATCHPOINT seem to consistently fail
+        self.gdb_wp = gdb.Breakpoint(spec=dataId, type=gdb.BP_WATCHPOINT, wp_class=DataBreakpoint.wp_type(type))
+        if self.condition is not None:
+            self.gdb_wp.condition = condition
+        if self.hitCondition is not None:
+            self.gdb_wp.ignore_count = hitCondition
+
+    def to_object(self):
+        return {
+            "id": self.gdb_wp.number,
+            "verified": True,
+        }
 
+    def is_same(self, dataId, type, condition=None, hitCondition=None):
+        return self.dataId == dataId and self.type == type and self.condition == condition and self.hitCondition == hitCondition
 
 # Map from the breakpoint "kind" (like "function") to a second map, of
 # breakpoints of that type.  The second map uses the breakpoint spec
@@ -30,14 +61,18 @@ from .startup import send_gdb_with_response, in_gdb_thread
 # allowing for reuse when a breakpoint can be kept.
 breakpoint_map = {}
 
+watchpoint_map: Mapping[str, DataBreakpoint] = {}
 
 @in_gdb_thread
 def breakpoint_descriptor(bp):
     "Return the Breakpoint object descriptor given a gdb Breakpoint."
-    if bp.locations:
+    if bp.location is not None:
         # Just choose the first location, because DAP doesn't allow
         # multiple locations.  See
         # https://github.com/microsoft/debug-adapter-protocol/issues/13
+        # FIXME this does not matter; GDB can translate it's understanding
+        # of breakpoint locations into individual breakpoints and
+        # even though GDB can't delete locs, it can disable them.
         loc = bp.locations[0]
         (basename, line) = loc.source
         result = {
@@ -59,7 +94,7 @@ def breakpoint_descriptor(bp):
     else:
         return {
             "id": bp.number,
-            "verified": False,
+            "verified": True,
         }
 
 
@@ -248,3 +283,120 @@ def set_exception_breakpoints(
     return {
         "breakpoints": result,
     }
+
+@in_gdb_thread
+def _databreakpoint_info(
+    name: str,
+    variablesReference: Optional[int] = None,
+    frameId: Optional[int] = None
+):
+    # frameId does not have an effect when var ref is not none,
+    # however, frameId really never has an effect. An issue has been filed:
+    # https://github.com/microsoft/debug-adapter-protocol/issues/404
+    path = []
+    path.append(name)
+    if variablesReference is not None:
+        # append "path" to name until find_variable throws
+        try:
+            variable = find_variable(variablesReference)
+            while variable is not None and variable.is_variable():
+                path.append(variable.name)
+                parent_ref = variable.parent_var_ref
+                variable = find_variable(parent_ref)
+        except:
+            pass
+
+    # We've resolved the "path" for the variable using varrefs. Construct it
+    # for example: baz being a member variable of bar, which is a member of
+    # foo: foo.bar.baz
+    dataId = ".".join(reversed(path))
+    description = dataId
+
+    try:
+        # since frameId can't be used, always parse global (see issue)
+        v = gdb.parse_and_eval(dataId)
+    except:
+        dataId = None
+        description = "Symbol was not found"
+
+    return {
+        "dataId": dataId,
+        "description": description,
+        "accessTypes": ["read", "write", "readWrite"],
+    }
+
+@capability("supportsDataBreakpoints")
+@request("dataBreakpointInfo")
+def data_breakpoint_info(
+    *,
+    name: str,
+    variablesReference: Optional[int] = None,
+    frameId: Optional[int] = None, **args
+):
+    result = send_gdb_with_response(
+        lambda: _databreakpoint_info(name, variablesReference, frameId))
+    return result
+
+
+@in_gdb_thread
+def _set_watchpoint(breakpoints: Sequence[dict]):
+    """Set (or keep) watchpoints passed in breakpoints and remove set
+        watchpoints not found in breakpoints"""
+    global watchpoint_map
+    if len(breakpoints) == 0:
+        for wp in watchpoint_map:
+            wp.gdb_wp.delete()
+        watchpoint_map.clear()
+        return []
+
+    new_wps_to_add = []
+    # Because we can't remove during iteration.
+    remove_wps = []
+    for req_wp in breakpoints:
+        wp = watchpoint_map.get(req_wp["dataId"])
+        if wp is None:
+            new_wps_to_add.append(req_wp)
+        # If wp with dataId exist; but something about it has changed
+        # (another condition for instance), delete it, so it can be set
+        # new with the new spec.
+        elif not wp.is_same(req_wp["dataId"], req_wp["accessType"],
+                            req_wp["condition"], req_wp["hitCondition"]):
+            remove_wps.append(wp.dataId)
+            new_wps_to_add.append(req_wp)
+    
+    # Delete wps that are not found in `breakpoints` request
+    for wp in watchpoint_map.values():
+        if wp.dataId not in set([x["dataId"] for x in breakpoints]):
+            # tells gdb to remove wp
+            remove_wps.append(wp.dataId)
+
+    for remove_wp in remove_wps:
+        watchpoint_map[remove_wp].gdb_wp.delete()
+        del watchpoint_map[remove_wp]
+
+    # Set the new watchpoints
+    for req_wp in new_wps_to_add:
+        wp = DataBreakpoint(dataId=req_wp["dataId"], type=req_wp["accessType"],
+                            condition=req_wp["condition"],
+                            hitCondition=req_wp["hitCondition"])
+        watchpoint_map[wp.dataId] = wp
+
+    return [x.to_object() for x in watchpoint_map.values()]
+
+def _sanitize_wp_input(breakpoints: Sequence) -> Sequence:
+    """Force the request arg DataBreakpoints to contain these attributes"""
+    res = []
+    for wp in breakpoints:
+        if not hasattr(wp, "condition"):
+            wp["condition"] = None
+        if not hasattr(wp, "hitCondition"):
+            wp["hitCondition"] = None
+        res.append(wp)
+    return res
+
+@request("setDataBreakpoints")
+@capability("supportsDataBreakpoints")
+def watchpoints(*, breakpoints: Sequence, **args):
+    bps = _sanitize_wp_input(breakpoints=breakpoints)
+    result = send_gdb_with_response(lambda: _set_watchpoint(bps))
+    return { "breakpoints": result }
diff --git a/gdb/python/lib/gdb/dap/frames.py b/gdb/python/lib/gdb/dap/frames.py
index 08209d0b361..6102352e2e5 100644
--- a/gdb/python/lib/gdb/dap/frames.py
+++ b/gdb/python/lib/gdb/dap/frames.py
@@ -48,7 +48,7 @@ def frame_id(frame):
 
 
 @in_gdb_thread
-def frame_for_id(id):
+def frame_for_id(id) -> gdb.Frame:
     """Given a frame identifier ID, return the corresponding frame."""
     global _all_frames
     return _all_frames[id]
diff --git a/gdb/python/lib/gdb/dap/varref.py b/gdb/python/lib/gdb/dap/varref.py
index 23f18d647c3..611d30b6a1d 100644
--- a/gdb/python/lib/gdb/dap/varref.py
+++ b/gdb/python/lib/gdb/dap/varref.py
@@ -22,7 +22,6 @@ from abc import abstractmethod
 # A list of all the variable references created during this pause.
 all_variables = []
 
-
 # When the inferior is re-started, we erase all variable references.
 # See the section "Lifetime of Objects References" in the spec.
 @in_gdb_thread
@@ -101,15 +100,19 @@ class BaseReference:
         for idx in range(start, start + count):
             if self.children[idx] is None:
                 (name, value) = self.fetch_one_child(idx)
-                self.children[idx] = VariableReference(name, value)
+                parent_ref = self.ref if isinstance(self, VariableReference) else -1
+                self.children[idx] = VariableReference(name=name, value=value, parent_var_ref=parent_ref)
             result.append(self.children[idx])
         return result
 
+    @abstractmethod
+    def is_variable(self):
+        return False
 
 class VariableReference(BaseReference):
     """Concrete subclass of BaseReference that handles gdb.Value."""
 
-    def __init__(self, name, value, result_name="value"):
+    def __init__(self, name, value, parent_var_ref=-1, result_name="value"):
         """Initializer.
 
         NAME is the name of this reference, see superclass.
@@ -120,6 +123,7 @@ class VariableReference(BaseReference):
         self.value = value
         self.printer = gdb.printing.make_visualizer(value)
         self.result_name = result_name
+        self.parent_var_ref = parent_var_ref
         # We cache all the children we create.
         self.child_cache = None
         if not hasattr(self.printer, "children"):
@@ -174,6 +178,8 @@ class VariableReference(BaseReference):
     def fetch_one_child(self, idx):
         return self.cache_children()[idx]
 
+    def is_variable(self):
+        return True
 
 @in_gdb_thread
 def find_variable(ref):
-- 
2.40.1


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

* Re: [PATCH v1] gdb/dap - dataBreakpointInfo & setDataBreakpoints
  2023-06-14 10:33 [PATCH v1] gdb/dap - dataBreakpointInfo & setDataBreakpoints Simon Farre
@ 2023-06-14 11:43 ` Eli Zaretskii
  2023-06-14 13:40   ` Simon Farre
  0 siblings, 1 reply; 4+ messages in thread
From: Eli Zaretskii @ 2023-06-14 11:43 UTC (permalink / raw)
  To: Simon Farre; +Cc: gdb-patches, tom

> Cc: tom@tromey.com,
> 	Simon Farre <simon.farre.cx@gmail.com>
> Date: Wed, 14 Jun 2023 12:33:27 +0200
> From: Simon Farre via Gdb-patches <gdb-patches@sourceware.org>
> 
> This is v1 of the implementation of these two requests.

I didn't (and cannot) review this in a useful way, but my eye caught a
few nits:

> -    if bp.locations:
> +    if bp.location is not None:
>          # Just choose the first location, because DAP doesn't allow
>          # multiple locations.  See
>          # https://github.com/microsoft/debug-adapter-protocol/issues/13
> +        # FIXME this does not matter; GDB can translate it's understanding
                                                           ^^^^
"its"

> +    # frameId does not have an effect when var ref is not none,
                                                             ^^^^
"None", capitalized?

> +    # however, frameId really never has an effect. An issue has been filed:
                                                    ^^
Two spaces there.

> +    # We've resolved the "path" for the variable using varrefs. Construct it
                                                                 ^^
And there.

> +    # Delete wps that are not found in `breakpoints` request
                                          ^^^^^^^^^^^^^
Are we okay with MD-style markup?

Thanks.

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

* Re: [PATCH v1] gdb/dap - dataBreakpointInfo & setDataBreakpoints
  2023-06-14 11:43 ` Eli Zaretskii
@ 2023-06-14 13:40   ` Simon Farre
  2023-06-14 13:58     ` Eli Zaretskii
  0 siblings, 1 reply; 4+ messages in thread
From: Simon Farre @ 2023-06-14 13:40 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: gdb-patches

> Are we okay with MD-style markup?
> Thanks.

I'll address the nitpicks. Misspelling of "its" every time, never fails :P.

About the MD-style markup, that's a oopsie daisy from me, I don't think 
GDB actually does that, its just something I'm used to doing. It will be 
removed, I think GDB does ALL_CAPS instead, right?


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

* Re: [PATCH v1] gdb/dap - dataBreakpointInfo & setDataBreakpoints
  2023-06-14 13:40   ` Simon Farre
@ 2023-06-14 13:58     ` Eli Zaretskii
  0 siblings, 0 replies; 4+ messages in thread
From: Eli Zaretskii @ 2023-06-14 13:58 UTC (permalink / raw)
  To: Simon Farre; +Cc: gdb-patches

> Date: Wed, 14 Jun 2023 15:40:18 +0200
> Cc: gdb-patches@sourceware.org
> From: Simon Farre <simon.farre.cx@gmail.com>
> 
> > Are we okay with MD-style markup?
> > Thanks.
> 
> I'll address the nitpicks. Misspelling of "its" every time, never fails :P.
> 
> About the MD-style markup, that's a oopsie daisy from me, I don't think 
> GDB actually does that, its just something I'm used to doing. It will be 
> removed, I think GDB does ALL_CAPS instead, right?

Yes, I think so.

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

end of thread, other threads:[~2023-06-14 13:58 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-06-14 10:33 [PATCH v1] gdb/dap - dataBreakpointInfo & setDataBreakpoints Simon Farre
2023-06-14 11:43 ` Eli Zaretskii
2023-06-14 13:40   ` Simon Farre
2023-06-14 13:58     ` Eli Zaretskii

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).