diff options
author | Tom Tromey <tromey@adacore.com> | 2024-12-16 10:44:55 -0700 |
---|---|---|
committer | Tom Tromey <tromey@adacore.com> | 2025-01-06 07:32:11 -0700 |
commit | 8ac42dbf5001416e6b741c5361be14186afde5b0 (patch) | |
tree | a05b26736fc12b4813f00d487cba2e5b6e0796d8 | |
parent | 350609bb98cee92c6b4fbd334b99066683b9e5c1 (diff) | |
download | gdb-8ac42dbf5001416e6b741c5361be14186afde5b0.zip gdb-8ac42dbf5001416e6b741c5361be14186afde5b0.tar.gz gdb-8ac42dbf5001416e6b741c5361be14186afde5b0.tar.bz2 |
Handle linesStartAt1 in DAP
The DAP initialize request has a "linesStartAt1" option, where the
client can indicate that it prefers whether line numbers be 0-based or
1-based.
This patch implements this. I audited all the line-related code in
the DAP implementation.
Note that while a similar option exists for column numbers, gdb
doesn't handle these yet, so nothing is done here.
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=32468
-rw-r--r-- | gdb/python/lib/gdb/dap/breakpoint.py | 6 | ||||
-rw-r--r-- | gdb/python/lib/gdb/dap/bt.py | 7 | ||||
-rw-r--r-- | gdb/python/lib/gdb/dap/disassemble.py | 4 | ||||
-rw-r--r-- | gdb/python/lib/gdb/dap/locations.py | 7 | ||||
-rw-r--r-- | gdb/python/lib/gdb/dap/scopes.py | 4 | ||||
-rw-r--r-- | gdb/python/lib/gdb/dap/server.py | 36 | ||||
-rw-r--r-- | gdb/testsuite/gdb.dap/line-zero.exp | 60 | ||||
-rw-r--r-- | gdb/testsuite/lib/dap-support.exp | 15 |
8 files changed, 119 insertions, 20 deletions
diff --git a/gdb/python/lib/gdb/dap/breakpoint.py b/gdb/python/lib/gdb/dap/breakpoint.py index 6adb826..f0fe073 100644 --- a/gdb/python/lib/gdb/dap/breakpoint.py +++ b/gdb/python/lib/gdb/dap/breakpoint.py @@ -21,7 +21,7 @@ from typing import Optional, Sequence import gdb -from .server import capability, request, send_event +from .server import capability, export_line, import_line, request, send_event from .sources import make_source from .startup import ( DAPException, @@ -128,7 +128,7 @@ def _breakpoint_descriptor(bp): result.update( { "source": make_source(filename), - "line": line, + "line": export_line(line), } ) @@ -281,7 +281,7 @@ def _rewrite_src_breakpoint( ): return { "source": source["path"], - "line": line, + "line": import_line(line), "condition": condition, "hitCondition": hitCondition, "logMessage": logMessage, diff --git a/gdb/python/lib/gdb/dap/bt.py b/gdb/python/lib/gdb/dap/bt.py index 668bcc7..a27c61a 100644 --- a/gdb/python/lib/gdb/dap/bt.py +++ b/gdb/python/lib/gdb/dap/bt.py @@ -21,7 +21,7 @@ import gdb from .frames import dap_frame_generator from .modules import module_id from .scopes import symbol_value -from .server import capability, request +from .server import capability, export_line, request from .sources import make_source from .startup import in_gdb_thread from .state import set_thread @@ -84,10 +84,13 @@ def _backtrace(thread_id, levels, startFrame, stack_format): "column": 0, "instructionPointerReference": hex(pc), } - line = current_frame.line() + line = export_line(current_frame.line()) if line is not None: newframe["line"] = line if stack_format["line"]: + # Unclear whether export_line should be called + # here, but since it's just for users we pick the + # gdb representation. name += ", line " + str(line) objfile = gdb.current_progspace().objfile_for_address(pc) if objfile is not None: diff --git a/gdb/python/lib/gdb/dap/disassemble.py b/gdb/python/lib/gdb/dap/disassemble.py index a2e27e5..5389803 100644 --- a/gdb/python/lib/gdb/dap/disassemble.py +++ b/gdb/python/lib/gdb/dap/disassemble.py @@ -15,7 +15,7 @@ import gdb -from .server import capability, request +from .server import capability, export_line, request from .sources import make_source @@ -53,7 +53,7 @@ class _BlockTracker: sal = gdb.find_pc_line(pc) if sal.symtab is not None: if sal.line != 0: - result["line"] = sal.line + result["line"] = export_line(sal.line) if sal.symtab.filename is not None: # The spec says this can be omitted in some # situations, but it's a little simpler to just always diff --git a/gdb/python/lib/gdb/dap/locations.py b/gdb/python/lib/gdb/dap/locations.py index befb7bf..1ef5a34 100644 --- a/gdb/python/lib/gdb/dap/locations.py +++ b/gdb/python/lib/gdb/dap/locations.py @@ -16,7 +16,7 @@ # This is deprecated in 3.9, but required in older versions. from typing import Optional -from .server import capability, request +from .server import capability, export_line, import_line, request from .sources import decode_source from .startup import exec_mi_and_log @@ -31,12 +31,15 @@ from .startup import exec_mi_and_log @request("breakpointLocations", expect_stopped=False) @capability("supportsBreakpointLocationsRequest") def breakpoint_locations(*, source, line: int, endLine: Optional[int] = None, **extra): + line = import_line(line) if endLine is None: endLine = line + else: + endLine = import_line(endLine) filename = decode_source(source) lines = set() for entry in exec_mi_and_log("-symbol-list-lines", filename)["lines"]: this_line = entry["line"] if this_line >= line and this_line <= endLine: - lines.add(this_line) + lines.add(export_line(this_line)) return {"breakpoints": [{"line": x} for x in sorted(lines)]} diff --git a/gdb/python/lib/gdb/dap/scopes.py b/gdb/python/lib/gdb/dap/scopes.py index 2fb3f77..221ae35 100644 --- a/gdb/python/lib/gdb/dap/scopes.py +++ b/gdb/python/lib/gdb/dap/scopes.py @@ -17,7 +17,7 @@ import gdb from .frames import frame_for_id from .globalvars import get_global_scope -from .server import request +from .server import export_line, request from .sources import make_source from .startup import in_gdb_thread from .varref import BaseReference @@ -92,7 +92,7 @@ class _ScopeReference(BaseReference): result["namedVariables"] = self.child_count() frame = frame_for_id(self.frameId) if frame.line() is not None: - result["line"] = frame.line() + result["line"] = export_line(frame.line()) filename = frame.filename() if filename is not None: result["source"] = make_source(filename) diff --git a/gdb/python/lib/gdb/dap/server.py b/gdb/python/lib/gdb/dap/server.py index 68801ef..6f3af73 100644 --- a/gdb/python/lib/gdb/dap/server.py +++ b/gdb/python/lib/gdb/dap/server.py @@ -46,6 +46,10 @@ _commands = {} # The global server. _server = None +# This is set by the initialize request and is used when rewriting +# line numbers. +_lines_start_at_1 = False + class DeferredRequest: """If a DAP request function returns a deferred request, no @@ -571,15 +575,15 @@ def capability(name, value=True): return wrap -def client_bool_capability(name): +def client_bool_capability(name, default=False): """Return the value of a boolean client capability. If the capability was not specified, or did not have boolean type, - False is returned.""" + DEFAULT is returned. DEFAULT defaults to False.""" global _server if name in _server.config and isinstance(_server.config[name], bool): return _server.config[name] - return False + return default @request("initialize", on_dap_thread=True) @@ -587,6 +591,8 @@ def initialize(**args): global _server, _capabilities _server.config = args _server.send_event_later("initialized") + global _lines_start_at_1 + _lines_start_at_1 = client_bool_capability("linesStartAt1", True) return _capabilities.copy() @@ -690,3 +696,27 @@ def send_gdb_with_response(fn): if isinstance(val, (Exception, KeyboardInterrupt)): raise val return val + + +def export_line(line): + """Rewrite LINE according to client capability. + This applies the linesStartAt1 capability as needed, + when sending a line number from gdb to the client.""" + global _lines_start_at_1 + if not _lines_start_at_1: + # In gdb, lines start at 1, so we only need to change this if + # the client starts at 0. + line = line - 1 + return line + + +def import_line(line): + """Rewrite LINE according to client capability. + This applies the linesStartAt1 capability as needed, + when the client sends a line number to gdb.""" + global _lines_start_at_1 + if not _lines_start_at_1: + # In gdb, lines start at 1, so we only need to change this if + # the client starts at 0. + line = line + 1 + return line diff --git a/gdb/testsuite/gdb.dap/line-zero.exp b/gdb/testsuite/gdb.dap/line-zero.exp new file mode 100644 index 0000000..e6309dc --- /dev/null +++ b/gdb/testsuite/gdb.dap/line-zero.exp @@ -0,0 +1,60 @@ +# Copyright 2024 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/>. + +# Test breakpoints when lines start at zero. + +require allow_dap_tests + +load_lib dap-support.exp + +standard_testfile basic-dap.c + +if {[build_executable ${testfile}.exp $testfile $srcfile] == -1} { + return +} + +if {[dap_initialize {linesStartAt1 [l false]}] == ""} { + return +} + +set launch_id [dap_launch $testfile] + +# We told gdb that lines start at 0, so subtract one. +set line [expr {[gdb_get_line_number "BREAK"] - 1}] +set obj [dap_check_request_and_response "set breakpoint by line number" \ + setBreakpoints \ + [format {o source [o path [%s]] breakpoints [a [o line [i %d]]]} \ + [list s $srcfile] $line]] +set line_bpno [dap_get_breakpoint_number $obj] + +dap_check_request_and_response "configurationDone" configurationDone + +dap_check_response "launch response" launch $launch_id + +dap_wait_for_event_and_check "inferior started" thread "body reason" started + +dap_wait_for_event_and_check "stopped at line breakpoint" stopped \ + "body reason" breakpoint \ + "body hitBreakpointIds" $line_bpno \ + "body allThreadsStopped" true + +set bt [lindex [dap_check_request_and_response "backtrace" stackTrace \ + {o threadId [i 1]}] \ + 0] +set stop_line [dict get [lindex [dict get $bt body stackFrames] 0] line] + +gdb_assert {$stop_line == $line} "stop line is 0-based" + +dap_shutdown diff --git a/gdb/testsuite/lib/dap-support.exp b/gdb/testsuite/lib/dap-support.exp index 0e481ac..5c192e5 100644 --- a/gdb/testsuite/lib/dap-support.exp +++ b/gdb/testsuite/lib/dap-support.exp @@ -272,16 +272,19 @@ proc dap_check_request_and_response {name command {obj {}}} { # Start gdb, send a DAP initialization request and return the # response. This approach lets the caller check the feature list, if # desired. Returns the empty string on failure. NAME is used as the -# test name. -proc dap_initialize {{name "initialize"}} { +# test name. EXTRA are other settings to pass via the "initialize" +# request. +proc dap_initialize {{name "initialize"} {extra ""}} { if {[dap_gdb_start]} { return "" } return [dap_check_request_and_response $name initialize \ - {o clientID [s "gdb testsuite"] \ - supportsVariableType [l true] \ - supportsVariablePaging [l true] \ - supportsMemoryReferences [l true]}] + [format {o clientID [s "gdb testsuite"] \ + supportsVariableType [l true] \ + supportsVariablePaging [l true] \ + supportsMemoryReferences [l true] \ + %s} \ + $extra]] } # Send a launch request specifying FILE as the program to use for the |