mirror of
https://github.com/bminor/binutils-gdb.git
synced 2025-12-25 00:37:38 +00:00
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
(cherry picked from commit 8ac42dbf50)
This commit is contained in:
@@ -21,7 +21,7 @@ from typing import Optional, Sequence
|
|||||||
|
|
||||||
import gdb
|
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 .sources import make_source
|
||||||
from .startup import (
|
from .startup import (
|
||||||
DAPException,
|
DAPException,
|
||||||
@@ -128,7 +128,7 @@ def _breakpoint_descriptor(bp):
|
|||||||
result.update(
|
result.update(
|
||||||
{
|
{
|
||||||
"source": make_source(filename),
|
"source": make_source(filename),
|
||||||
"line": line,
|
"line": export_line(line),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -281,7 +281,7 @@ def _rewrite_src_breakpoint(
|
|||||||
):
|
):
|
||||||
return {
|
return {
|
||||||
"source": source["path"],
|
"source": source["path"],
|
||||||
"line": line,
|
"line": import_line(line),
|
||||||
"condition": condition,
|
"condition": condition,
|
||||||
"hitCondition": hitCondition,
|
"hitCondition": hitCondition,
|
||||||
"logMessage": logMessage,
|
"logMessage": logMessage,
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import gdb
|
|||||||
from .frames import dap_frame_generator
|
from .frames import dap_frame_generator
|
||||||
from .modules import module_id
|
from .modules import module_id
|
||||||
from .scopes import symbol_value
|
from .scopes import symbol_value
|
||||||
from .server import capability, request
|
from .server import capability, export_line, request
|
||||||
from .sources import make_source
|
from .sources import make_source
|
||||||
from .startup import in_gdb_thread
|
from .startup import in_gdb_thread
|
||||||
from .state import set_thread
|
from .state import set_thread
|
||||||
@@ -84,10 +84,13 @@ def _backtrace(thread_id, levels, startFrame, stack_format):
|
|||||||
"column": 0,
|
"column": 0,
|
||||||
"instructionPointerReference": hex(pc),
|
"instructionPointerReference": hex(pc),
|
||||||
}
|
}
|
||||||
line = current_frame.line()
|
line = export_line(current_frame.line())
|
||||||
if line is not None:
|
if line is not None:
|
||||||
newframe["line"] = line
|
newframe["line"] = line
|
||||||
if stack_format["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)
|
name += ", line " + str(line)
|
||||||
objfile = gdb.current_progspace().objfile_for_address(pc)
|
objfile = gdb.current_progspace().objfile_for_address(pc)
|
||||||
if objfile is not None:
|
if objfile is not None:
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
|
|
||||||
import gdb
|
import gdb
|
||||||
|
|
||||||
from .server import capability, request
|
from .server import capability, export_line, request
|
||||||
from .sources import make_source
|
from .sources import make_source
|
||||||
|
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ class _BlockTracker:
|
|||||||
sal = gdb.find_pc_line(pc)
|
sal = gdb.find_pc_line(pc)
|
||||||
if sal.symtab is not None:
|
if sal.symtab is not None:
|
||||||
if sal.line != 0:
|
if sal.line != 0:
|
||||||
result["line"] = sal.line
|
result["line"] = export_line(sal.line)
|
||||||
if sal.symtab.filename is not None:
|
if sal.symtab.filename is not None:
|
||||||
# The spec says this can be omitted in some
|
# The spec says this can be omitted in some
|
||||||
# situations, but it's a little simpler to just always
|
# situations, but it's a little simpler to just always
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
# This is deprecated in 3.9, but required in older versions.
|
# This is deprecated in 3.9, but required in older versions.
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from .server import capability, request
|
from .server import capability, export_line, import_line, request
|
||||||
from .sources import decode_source
|
from .sources import decode_source
|
||||||
from .startup import exec_mi_and_log
|
from .startup import exec_mi_and_log
|
||||||
|
|
||||||
@@ -31,12 +31,15 @@ from .startup import exec_mi_and_log
|
|||||||
@request("breakpointLocations", expect_stopped=False)
|
@request("breakpointLocations", expect_stopped=False)
|
||||||
@capability("supportsBreakpointLocationsRequest")
|
@capability("supportsBreakpointLocationsRequest")
|
||||||
def breakpoint_locations(*, source, line: int, endLine: Optional[int] = None, **extra):
|
def breakpoint_locations(*, source, line: int, endLine: Optional[int] = None, **extra):
|
||||||
|
line = import_line(line)
|
||||||
if endLine is None:
|
if endLine is None:
|
||||||
endLine = line
|
endLine = line
|
||||||
|
else:
|
||||||
|
endLine = import_line(endLine)
|
||||||
filename = decode_source(source)
|
filename = decode_source(source)
|
||||||
lines = set()
|
lines = set()
|
||||||
for entry in exec_mi_and_log("-symbol-list-lines", filename)["lines"]:
|
for entry in exec_mi_and_log("-symbol-list-lines", filename)["lines"]:
|
||||||
this_line = entry["line"]
|
this_line = entry["line"]
|
||||||
if this_line >= line and this_line <= endLine:
|
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)]}
|
return {"breakpoints": [{"line": x} for x in sorted(lines)]}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import gdb
|
|||||||
|
|
||||||
from .frames import frame_for_id
|
from .frames import frame_for_id
|
||||||
from .globalvars import get_global_scope
|
from .globalvars import get_global_scope
|
||||||
from .server import request
|
from .server import export_line, request
|
||||||
from .sources import make_source
|
from .sources import make_source
|
||||||
from .startup import in_gdb_thread
|
from .startup import in_gdb_thread
|
||||||
from .varref import BaseReference
|
from .varref import BaseReference
|
||||||
@@ -92,7 +92,7 @@ class _ScopeReference(BaseReference):
|
|||||||
result["namedVariables"] = self.child_count()
|
result["namedVariables"] = self.child_count()
|
||||||
frame = frame_for_id(self.frameId)
|
frame = frame_for_id(self.frameId)
|
||||||
if frame.line() is not None:
|
if frame.line() is not None:
|
||||||
result["line"] = frame.line()
|
result["line"] = export_line(frame.line())
|
||||||
filename = frame.filename()
|
filename = frame.filename()
|
||||||
if filename is not None:
|
if filename is not None:
|
||||||
result["source"] = make_source(filename)
|
result["source"] = make_source(filename)
|
||||||
|
|||||||
@@ -46,6 +46,10 @@ _commands = {}
|
|||||||
# The global server.
|
# The global server.
|
||||||
_server = None
|
_server = None
|
||||||
|
|
||||||
|
# This is set by the initialize request and is used when rewriting
|
||||||
|
# line numbers.
|
||||||
|
_lines_start_at_1 = False
|
||||||
|
|
||||||
|
|
||||||
class DeferredRequest:
|
class DeferredRequest:
|
||||||
"""If a DAP request function returns a deferred request, no
|
"""If a DAP request function returns a deferred request, no
|
||||||
@@ -571,15 +575,15 @@ def capability(name, value=True):
|
|||||||
return wrap
|
return wrap
|
||||||
|
|
||||||
|
|
||||||
def client_bool_capability(name):
|
def client_bool_capability(name, default=False):
|
||||||
"""Return the value of a boolean client capability.
|
"""Return the value of a boolean client capability.
|
||||||
|
|
||||||
If the capability was not specified, or did not have boolean type,
|
If the capability was not specified, or did not have boolean type,
|
||||||
False is returned."""
|
DEFAULT is returned. DEFAULT defaults to False."""
|
||||||
global _server
|
global _server
|
||||||
if name in _server.config and isinstance(_server.config[name], bool):
|
if name in _server.config and isinstance(_server.config[name], bool):
|
||||||
return _server.config[name]
|
return _server.config[name]
|
||||||
return False
|
return default
|
||||||
|
|
||||||
|
|
||||||
@request("initialize", on_dap_thread=True)
|
@request("initialize", on_dap_thread=True)
|
||||||
@@ -587,6 +591,8 @@ def initialize(**args):
|
|||||||
global _server, _capabilities
|
global _server, _capabilities
|
||||||
_server.config = args
|
_server.config = args
|
||||||
_server.send_event_later("initialized")
|
_server.send_event_later("initialized")
|
||||||
|
global _lines_start_at_1
|
||||||
|
_lines_start_at_1 = client_bool_capability("linesStartAt1", True)
|
||||||
return _capabilities.copy()
|
return _capabilities.copy()
|
||||||
|
|
||||||
|
|
||||||
@@ -690,3 +696,27 @@ def send_gdb_with_response(fn):
|
|||||||
if isinstance(val, (Exception, KeyboardInterrupt)):
|
if isinstance(val, (Exception, KeyboardInterrupt)):
|
||||||
raise val
|
raise val
|
||||||
return 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
|
||||||
|
|||||||
60
gdb/testsuite/gdb.dap/line-zero.exp
Normal file
60
gdb/testsuite/gdb.dap/line-zero.exp
Normal file
@@ -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
|
||||||
@@ -272,16 +272,19 @@ proc dap_check_request_and_response {name command {obj {}}} {
|
|||||||
# Start gdb, send a DAP initialization request and return the
|
# Start gdb, send a DAP initialization request and return the
|
||||||
# response. This approach lets the caller check the feature list, if
|
# response. This approach lets the caller check the feature list, if
|
||||||
# desired. Returns the empty string on failure. NAME is used as the
|
# desired. Returns the empty string on failure. NAME is used as the
|
||||||
# test name.
|
# test name. EXTRA are other settings to pass via the "initialize"
|
||||||
proc dap_initialize {{name "initialize"}} {
|
# request.
|
||||||
|
proc dap_initialize {{name "initialize"} {extra ""}} {
|
||||||
if {[dap_gdb_start]} {
|
if {[dap_gdb_start]} {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
return [dap_check_request_and_response $name initialize \
|
return [dap_check_request_and_response $name initialize \
|
||||||
{o clientID [s "gdb testsuite"] \
|
[format {o clientID [s "gdb testsuite"] \
|
||||||
supportsVariableType [l true] \
|
supportsVariableType [l true] \
|
||||||
supportsVariablePaging [l true] \
|
supportsVariablePaging [l true] \
|
||||||
supportsMemoryReferences [l true]}]
|
supportsMemoryReferences [l true] \
|
||||||
|
%s} \
|
||||||
|
$extra]]
|
||||||
}
|
}
|
||||||
|
|
||||||
# Send a launch request specifying FILE as the program to use for the
|
# Send a launch request specifying FILE as the program to use for the
|
||||||
|
|||||||
Reference in New Issue
Block a user