Implement DAP logging breakpoints

DAP allows a source breakpoint to specify a log message.  When this is
done, the breakpoint acts more like gdb's dprintf: it logs a message
but does not cause a stop.

I looked into implement this using dprintf with the new %V printf
format.  However, my initial attempt at this did not work, because
when the inferior is continued, the dprintf output is captured by the
gdb.execute call.  Maybe this could be fixed by having all
inferior-continuation commands use the "&" form; the main benefit of
this would be that expressions are only parsed a single time.
This commit is contained in:
Tom Tromey
2023-05-31 11:21:09 -06:00
parent 59e75852dd
commit 0aafd5d038
3 changed files with 132 additions and 3 deletions

View File

@@ -15,11 +15,12 @@
import gdb
import os
import re
# These are deprecated in 3.9, but required in older versions.
from typing import Optional, Sequence
from .server import request, capability
from .server import request, capability, send_event
from .startup import send_gdb_with_response, in_gdb_thread, log_stack
from .typecheck import type_check
@@ -136,11 +137,54 @@ def _set_breakpoints_callback(kind, specs, creator):
return result
# Helper function to set odinary breakpoints according to a list of
class _PrintBreakpoint(gdb.Breakpoint):
def __init__(self, logMessage, **args):
super().__init__(**args)
# Split the message up for easier processing.
self.message = re.split("{(.*?)}", logMessage)
def stop(self):
output = ""
for idx, item in enumerate(self.message):
if idx % 2 == 0:
# Even indices are plain text.
output += item
else:
# Odd indices are expressions to substitute. The {}
# have already been stripped by the placement of the
# regex capture in the 'split' call.
try:
val = gdb.parse_and_eval(item)
output += str(val)
except Exception as e:
output += "<" + str(e) + ">"
send_event(
"output",
{
"category": "console",
"output": output,
},
)
# Do not stop.
return False
# Set a single breakpoint or a log point. Returns the new breakpoint.
# Note that not every spec will pass logMessage, so here we use a
# default.
@in_gdb_thread
def _set_one_breakpoint(*, logMessage=None, **args):
if logMessage is not None:
return _PrintBreakpoint(logMessage, **args)
else:
return gdb.Breakpoint(**args)
# Helper function to set ordinary breakpoints according to a list of
# specifications.
@in_gdb_thread
def _set_breakpoints(kind, specs):
return _set_breakpoints_callback(kind, specs, gdb.Breakpoint)
return _set_breakpoints_callback(kind, specs, _set_one_breakpoint)
# A helper function that rewrites a SourceBreakpoint into the internal
@@ -154,6 +198,7 @@ def _rewrite_src_breakpoint(
line: int,
condition: Optional[str] = None,
hitCondition: Optional[str] = None,
logMessage: Optional[str] = None,
**args,
):
return {
@@ -161,6 +206,7 @@ def _rewrite_src_breakpoint(
"line": line,
"condition": condition,
"hitCondition": hitCondition,
"logMessage": logMessage,
}
@@ -168,6 +214,7 @@ def _rewrite_src_breakpoint(
@request("setBreakpoints")
@capability("supportsHitConditionalBreakpoints")
@capability("supportsConditionalBreakpoints")
@capability("supportsLogPoints")
def set_breakpoint(*, source, breakpoints: Sequence = (), **args):
if "path" not in source:
result = []

View File

@@ -0,0 +1,31 @@
/* Copyright 2023 Free Software Foundation, Inc.
This file is part of GDB.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
int global_variable;
int
func (int local)
{
return global_variable - local; /* HERE */
}
int
main ()
{
global_variable = 23;
return func (23);
}

View File

@@ -0,0 +1,51 @@
# Copyright 2023 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 DAP logging breakpoints.
require allow_dap_tests
load_lib dap-support.exp
standard_testfile
if {[build_executable ${testfile}.exp $testfile] == -1} {
return
}
if {[dap_launch $testfile] == ""} {
return
}
set line [gdb_get_line_number "HERE"]
set obj [dap_check_request_and_response "set breakpoint" \
setBreakpoints \
[format {o source [o path [%s]] \
breakpoints [a [o line [i %d] \
logMessage [s "got {global_variable} - {local} = {global_variable - local}"]]]} \
[list s $srcfile] $line]]
set fn_bpno [dap_get_breakpoint_number $obj]
dap_check_request_and_response "start inferior" configurationDone
dap_wait_for_event_and_check "inferior started" thread "body reason" started
dap_wait_for_event_and_check "logging output" output \
{body category} console \
{body output} "got 23 - 23 = 0"
# Check that the breakpoint did not cause a stop.
dap_wait_for_event_and_check "inferior exited" exited
dap_shutdown