Fix bug in DAP handling of 'pause' requests

While working on cancellation, I noticed that a DAP 'pause' request
would set the "do not emit the continue" flag.  This meant that a
subsequent request that should provoke a 'continue' event would
instead suppress the event.

I then tried writing a more obvious test case for this, involving an
inferior call -- and discovered that gdb.events.cont does not fire for
an inferior call.

This patch installs a new event listener for gdb.events.inferior_call
and arranges for this to emit continue and stop events when
appropriate.  It also fixes the original bug, by adding a check to
exec_and_expect_stop.
This commit is contained in:
Tom Tromey
2023-11-17 10:08:50 -07:00
parent f087eb2765
commit c618a1c548
3 changed files with 97 additions and 3 deletions

View File

@@ -128,8 +128,9 @@ def exec_and_expect_stop(cmd, reason):
"""Indicate that a stop is expected, then execute CMD"""
global _expected_stop
_expected_stop = reason
global _suppress_cont
_suppress_cont = True
if reason != StopKinds.PAUSE:
global _suppress_cont
_suppress_cont = True
# FIXME if the call fails should we clear _suppress_cont?
exec_and_log(cmd)
@@ -156,6 +157,26 @@ def _on_stop(event):
send_event("stopped", obj)
# This keeps a bit of state between the start of an inferior call and
# the end. If the inferior was already running when the call started
# (as can happen if a breakpoint condition calls a function), then we
# do not want to emit 'continued' or 'stop' events for the call. Note
# that, for some reason, gdb.events.cont does not fire for an infcall.
_infcall_was_running = False
@in_gdb_thread
def _on_inferior_call(event):
global _infcall_was_running
if isinstance(event, gdb.InferiorCallPreEvent):
_infcall_was_running = inferior_running
if not _infcall_was_running:
_cont(None)
else:
if not _infcall_was_running:
_on_stop(None)
gdb.events.stop.connect(_on_stop)
gdb.events.exited.connect(_on_exit)
gdb.events.new_thread.connect(_new_thread)
@@ -163,3 +184,4 @@ gdb.events.thread_exited.connect(_thread_exited)
gdb.events.cont.connect(_cont)
gdb.events.new_objfile.connect(_new_objfile)
gdb.events.free_objfile.connect(_objfile_removed)
gdb.events.inferior_call.connect(_on_inferior_call)

View File

@@ -0,0 +1,44 @@
/* This testcase is part of GDB, the GNU debugger.
Copyright 2011-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/>. */
#include <unistd.h>
int
do_nothing ()
{
return 91;
}
int
return_false ()
{
return 0;
}
void
sleep_a_bit ()
{
sleep (1); /* STOP */
}
int
main ()
{
while (1)
sleep_a_bit ();
return 0;
}

View File

@@ -19,7 +19,7 @@ require allow_dap_tests
load_lib dap-support.exp
standard_testfile attach.c
standard_testfile
if {[build_executable ${testfile}.exp $testfile $srcfile] == -1} {
return
@@ -29,6 +29,18 @@ if {[dap_launch $testfile] == ""} {
return
}
# Set a conditional breakpoint that will never fire. This is done to
# test the state-tracking in events -- an inferior call from a
# breakpoint condition should not cause any sort of stop or continue
# events.
set line [gdb_get_line_number "STOP"]
dap_check_request_and_response "set conditional breakpoint" \
setBreakpoints \
[format {o source [o path [%s]] \
breakpoints [a [o line [i %d] \
condition [s "return_false()"]]]} \
[list s $srcfile] $line]
dap_check_request_and_response "start inferior" configurationDone
dap_wait_for_event_and_check "inferior started" thread "body reason" started
@@ -45,4 +57,20 @@ dap_check_request_and_response pause pause \
dap_wait_for_event_and_check "stopped by pause" stopped \
"body reason" pause
set result [dap_request_and_response evaluate {o expression [s do_nothing()]}]
gdb_assert {[dict get [lindex $result 0] body result] == 91} \
"check result of evaluation"
set seen fail
foreach event [lindex $result 1] {
if {[dict get $event type] != "event"} {
continue
}
if {[dict get $event event] == "continued"} {
set seen pass
break
}
}
gdb_assert {$seen == "pass"} "continue event from inferior call"
dap_shutdown