[PATCH] gdb/infrun: disable pagination in fetch_inferior_event

Tankut Baris Aktemur tankut.baris.aktemur@intel.com
Wed Oct 14 10:24:19 GMT 2020


Having pagination enabled when handling an inferior event gives the
user an option to quit, which causes early exit in GDB's flow and may
lead to half-baked state.  For instance, here is a case where we quit
in the middle of handling an inferior exit:

  $ gdb ./a.out
  Reading symbols from ./a.out...
  (gdb) set height 2
  (gdb) run
  Starting program: ./a.out
  --Type <RET> for more, q to quit, c to continue without paging--q
  Quit
  Couldn't get registers: No such process.
  (gdb) set height unlimited
  Couldn't get registers: No such process.
  (gdb) info threads
    Id   Target Id         Frame
  * 1    process 27098     Couldn't get registers: No such process.
  Couldn't get registers: No such process.
  (gdb)

Or suppose having a multi-threaded program like below:

  static void *
  fun (void *dummy)
  {
    int a = 1; /* break-here */
    return NULL;
  }

  int
  main (void)
  {
    pthread_t thread;
    pthread_create (&thread, NULL, fun, NULL);
    pthread_join (thread, NULL);

    return 0;
  }

If we define a breakpoint at line "break-here", we expect only Thread
2 to hit it.

  $ gdb ./a.out
  Reading symbols from ./a.out...
  (gdb) break 7
  Breakpoint 1 at 0x1182: file mt.c, line 7.
  (gdb) set height 2
  (gdb) run
  Starting program: ./a.out
  [Thread debugging using libthread_db enabled]
  Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
  [New Thread 0x7ffff77c4700 (LWP 23048)]
  --Type <RET> for more, q to quit, c to continue without paging--q
  Quit
  (gdb) set height unlimited
  (gdb) info thread
    Id   Target Id                                 Frame
  * 1    Thread 0x7ffff7fe3740 (LWP 23044) "a.out" 0x00007ffff7bbed2d in ...
    2    Thread 0x7ffff77c4700 (LWP 23048) "a.out" fun (dummy=0x0) at mt.c:7
  (gdb)

The prompt for continuation was triggered because Thread 2 hit the
breakpoint.  (If we had hit 'c', we were going to see that stop event,
but we didn't.)  The context did not switch to Thread 2.  GDB also did
not execute several other things it would normally do in
infrun.c:normal_stop after outputting "[Switching to Thread ...]" (but
it seems harmless in this case).  If we 'continue' at this state, both
threads run until termination, and we don't see the breakpoint hit
event ever.

Here is another related and more complicated scenario that leads to a
GDB crash.  Create two inferiors, one sitting on top of a native
target, and the other on a remote target, so that we have a
multi-target setting, like so:

  (gdb) i inferiors
    Num  Description       Connection     Executable
    1    process 13786     1 (native)     a.out
  * 2    process 13806     2 (remote ...) target:a.out

Next, resume both inferiors to run until termination:

  (gdb) set schedule-multiple on
  (gdb) set height 2
  (gdb) continue
  Continuing.
  --Type <RET> for more, q to quit, c to continue without paging--[Inferior 2 (process 13806) exited normally]

  terminate called after throwing an instance of 'gdb_exception_error'
  Aborted

Here, GDB first received a termination event from Inferior 1.  GDB
attempted to print this event, triggering a "prompt for continue", and
GDB started polling for events, hoping to get an input from the user.
However, the exit event from Inferior 2 was received instead.  So, GDB
started processing an exit event while being in the middle of
processing another exit event.  It was not ready for this situation
and eventually crashed.

To address these cases, temporarily disable pagination in
fetch_inferior_event.  This doesn't affect commands like 'info
threads', 'backtrace', or 'thread apply'.

Regression-tested on X86_64 Linux.

gdb/ChangeLog:
2020-10-14  Tankut Baris Aktemur  <tankut.baris.aktemur@intel.com>

	* infrun.c (fetch_inferior_event): Temporarily disable pagination.

gdb/testsuite/ChangeLog:
2020-10-14  Tankut Baris Aktemur  <tankut.baris.aktemur@intel.com>

	* gdb.base/paginate-after-ctrl-c-running.exp: Update with no pagination
	behavior.
	* gdb.base/paginate-bg-execution.exp: Ditto.
	* gdb.base/paginate-inferior-exit.exp: Ditto.
	* gdb.base/double-prompt-target-event-error.c: Remove.
	* gdb.base/double-prompt-target-event-error.exp: Remove.
---
 gdb/infrun.c                                  |   6 +
 .../double-prompt-target-event-error.c        |  25 ----
 .../double-prompt-target-event-error.exp      | 122 ------------------
 .../paginate-after-ctrl-c-running.exp         |  25 ++--
 .../gdb.base/paginate-bg-execution.exp        |  77 ++---------
 .../gdb.base/paginate-inferior-exit.exp       |  36 +-----
 6 files changed, 27 insertions(+), 264 deletions(-)
 delete mode 100644 gdb/testsuite/gdb.base/double-prompt-target-event-error.c
 delete mode 100644 gdb/testsuite/gdb.base/double-prompt-target-event-error.exp

diff --git a/gdb/infrun.c b/gdb/infrun.c
index d552fb3adb2..3e087710f63 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -3866,6 +3866,12 @@ fetch_inferior_event ()
      the main console.  */
   scoped_restore save_ui = make_scoped_restore (&current_ui, main_ui);
 
+  /* Temporarily disable pagination.  Otherwise, the user would be
+     given an option to press 'q' to quit, which would cause an early
+     exit and could leave GDB in a half-baked state.  */
+  scoped_restore save_pagination
+    = make_scoped_restore (&pagination_enabled, false);
+
   /* End up with readline processing input, if necessary.  */
   {
     SCOPE_EXIT { reinstall_readline_callback_handler_cleanup (); };
diff --git a/gdb/testsuite/gdb.base/double-prompt-target-event-error.c b/gdb/testsuite/gdb.base/double-prompt-target-event-error.c
deleted file mode 100644
index 469f91dd1c1..00000000000
--- a/gdb/testsuite/gdb.base/double-prompt-target-event-error.c
+++ /dev/null
@@ -1,25 +0,0 @@
-/* This testcase is part of GDB, the GNU debugger.
-
-   Copyright 2014-2020 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
-main (void)
-{
-  sleep (3);
-  return 0; /* after sleep */
-}
diff --git a/gdb/testsuite/gdb.base/double-prompt-target-event-error.exp b/gdb/testsuite/gdb.base/double-prompt-target-event-error.exp
deleted file mode 100644
index 459d1866f32..00000000000
--- a/gdb/testsuite/gdb.base/double-prompt-target-event-error.exp
+++ /dev/null
@@ -1,122 +0,0 @@
-# Copyright (C) 2014-2020 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/>.
-
-if [target_info exists gdb,nointerrupts] {
-    verbose "Skipping double-prompt-target-event-error.exp because of nointerrupts."
-    return
-}
-
-standard_testfile
-
-if {[prepare_for_testing "failed to prepare" $testfile $srcfile debug] == -1} {
-    return -1
-}
-
-# Test throwing an error while GDB is handling a target event.  We use
-# a ctrl-c/quit in a pagination prompt to emulate an error.  COMMAND
-# is either "continue" or "wrapcont".  The latter is a continue issued
-# from a user-defined command.  That exercises the case of the
-# interpreter forced sync, which was the case that originally had a
-# bug.
-
-proc cancel_pagination_in_target_event { command } {
-    global binfile srcfile
-    global gdb_prompt pagination_prompt
-
-    set testline [gdb_get_line_number "after sleep"]
-
-    with_test_prefix "ctrlc target event: $command" {
-	clean_restart $binfile
-
-	if ![runto_main] then {
-	    fail "can't run to main"
-	    return 0
-	}
-
-	gdb_test "b $srcfile:$testline" \
-	    "Breakpoint .*$srcfile, line $testline.*" \
-	    "set breakpoint"
-
-	gdb_test_no_output "set height 2"
-
-	if { $command == "wrapcont" } {
-	    gdb_test_multiple "define wrapcont" "define user command: wrapcont" {
-		-re "Type commands for definition of \"wrapcont\".\r\nEnd with a line saying just \"end\".\r\n>$" {
-		    # Note that "Continuing." is ommitted when
-		    # "continue" is issued from a user-defined
-		    # command.  Issue it ourselves.
-		    gdb_test "echo Continuing\.\ncontinue\nend" "" \
-			"define user command: wrapcont"
-		}
-	    }
-	}
-
-	# Wait for pagination prompt after the "Continuing" line,
-	# indicating the program was running and then stopped.
-	set saw_continuing 0
-	set test "continue to pagination"
-	gdb_test_multiple "$command" $test {
-	    -re "$pagination_prompt$" {
-		if {$saw_continuing} {
-		    pass $test
-		} else {
-		    send_gdb "\n"
-		    exp_continue
-		}
-	    }
-	    -re "Continuing" {
-		set saw_continuing 1
-		exp_continue
-	    }
-	}
-
-	# We're now stopped in a pagination query while handling a
-	# target event (printing where the program stopped).  Quitting
-	# the pagination should result in only one prompt being
-	# output.
-	send_gdb "\003p 1\n"
-
-	# Note gdb_test_multiple has a default match for the prompt,
-	# which issues a FAIL.  Consume the first prompt.
-	set test "first prompt"
-	gdb_test_multiple "" $test {
-	    -re "$gdb_prompt" {
-		pass "first prompt"
-	    }
-	}
-
-	# We should only see one prompt more, and it should be
-	# preceeded by print's output.
-	set test "no double prompt"
-	gdb_test_multiple "" $test {
-	    -re "$gdb_prompt.*$gdb_prompt $" {
-		# The bug is present, and expect managed to read
-		# enough characters into the buffer to fill it with
-		# both prompts.
-		fail $test
-	    }
-	    -re " = 1\r\n$gdb_prompt $" {
-		pass $test
-	    }
-	}
-
-	# In case the board file wants to send further commands.
-	gdb_test_no_output "set height unlimited"
-    }
-}
-
-foreach variant { "continue" "wrapcont" } {
-    cancel_pagination_in_target_event $variant
-}
diff --git a/gdb/testsuite/gdb.base/paginate-after-ctrl-c-running.exp b/gdb/testsuite/gdb.base/paginate-after-ctrl-c-running.exp
index 09f6fbfeed1..f904912bce3 100644
--- a/gdb/testsuite/gdb.base/paginate-after-ctrl-c-running.exp
+++ b/gdb/testsuite/gdb.base/paginate-after-ctrl-c-running.exp
@@ -24,12 +24,12 @@ if {[build_executable "failed to prepare" $testfile $srcfile debug] == -1} {
     return -1
 }
 
-# Send a ctrl-c while the target is running and the next output causes
-# a pagination prompt.
+# Send a ctrl-c while the target is running and check that the output
+# does not cause a pagination prompt.
 
-proc test_ctrlc_while_target_running_paginates {} {
+proc test_ctrlc_while_target_running_does_not_paginate {} {
     global binfile srcfile
-    global gdb_prompt pagination_prompt
+    global gdb_prompt
 
     set testline [gdb_get_line_number "after sleep"]
 
@@ -62,18 +62,11 @@ proc test_ctrlc_while_target_running_paginates {} {
 	send_gdb "\003"
 
 	# GDB now intercepts the SIGINT and tries to let the user know
-	# about the spurious signal, but that paginates.  Make sure
-	# the user can respond to the pagination query.
-	set test "got prompt"
-	set saw_pagination_prompt 0
-	gdb_test_multiple "" $test {
-	    -re "$pagination_prompt$" {
-		set saw_pagination_prompt 1
-		send_gdb "\n"
-		exp_continue
-	    }
+	# about the spurious signal.  Make sure that this does not
+	# trigger pagination.
+	gdb_test_multiple "" "no pagination" {
 	    -re "$gdb_prompt $" {
-		gdb_assert $saw_pagination_prompt $test
+		pass $gdb_test_name
 	    }
 	}
 
@@ -85,4 +78,4 @@ proc test_ctrlc_while_target_running_paginates {} {
     }
 }
 
-test_ctrlc_while_target_running_paginates
+test_ctrlc_while_target_running_does_not_paginate
diff --git a/gdb/testsuite/gdb.base/paginate-bg-execution.exp b/gdb/testsuite/gdb.base/paginate-bg-execution.exp
index 541362f6e49..388dd52c9c7 100644
--- a/gdb/testsuite/gdb.base/paginate-bg-execution.exp
+++ b/gdb/testsuite/gdb.base/paginate-bg-execution.exp
@@ -13,8 +13,8 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-# A collection of tests related to running execution commands directly
-# from the command line, with "-ex".
+# Test that a breakpoint hit event coming from background execution
+# does not trigger pagination.
 
 standard_testfile
 
@@ -22,12 +22,12 @@ if {[build_executable "failed to prepare" $testfile $srcfile debug] == -1} {
     return -1
 }
 
-# Check that we handle pagination correctly when it triggers due to an
-# background execution command entered directly on the command line.
+# Check that we handle a stop event coming from a background execution
+# command without getting caught in pagination.
 
 proc test_bg_execution_pagination_return {} {
     global binfile
-    global pagination_prompt
+    global decimal
 
     with_test_prefix "paginate" {
 	clean_restart $binfile
@@ -43,66 +43,9 @@ proc test_bg_execution_pagination_return {} {
 
 	gdb_test "continue&" "Continuing\."
 
-	set test "pagination handled, breakpoint hit"
-	set saw_pagination_prompt 0
-	gdb_test_multiple "" $test {
-	    -re "$pagination_prompt$" {
-		set saw_pagination_prompt 1
-		send_gdb "\n"
-		exp_continue
-	    }
-	    -re "after sleep\[^\r\n\]+\r\n$" {
-		gdb_assert $saw_pagination_prompt $test
-	    }
-	}
-
-	# GDB used to crash here.
-	gdb_test "p 1" " = 1" "GDB accepts further input"
-
-	# In case the board file wants to send further commands.
-	gdb_test_no_output "set height unlimited"
-    }
-}
-
-# Check that we handle canceling pagination correctly when it triggers
-# due to a background execution command entered directly on the
-# command line.
-
-proc test_bg_execution_pagination_cancel { how } {
-    global binfile
-    global gdb_prompt pagination_prompt
-
-    with_test_prefix "cancel with $how" {
-	clean_restart $binfile
-
-	if ![runto_main] then {
-	    fail "can't run to main"
-	    return 0
-	}
-
-	gdb_test "b after_sleep"
-
-	gdb_test_no_output "set height 2"
-
-	gdb_test "continue&" "Continuing\."
-
-	set test "continue& paginates"
-	gdb_test_multiple "" $test {
-	    -re "$pagination_prompt$" {
-		pass $test
-	    }
-	}
-
-	set test "cancel pagination"
-	if { $how == "ctrl-c" } {
-	    send_gdb "\003"
-	} else {
-	    send_gdb "q\n"
-
-	}
-	gdb_test_multiple "" $test {
-	    -re "Quit\r\n$gdb_prompt $" {
-		pass $test
+	gdb_test_multiple "" "no pagination, breakpoint hit" {
+	    -re "Breakpoint $decimal, after_sleep\[^\r\n\]+\r\n\[^\r\n\]+\r\n" {
+		pass $gdb_test_name
 	    }
 	}
 
@@ -114,7 +57,3 @@ proc test_bg_execution_pagination_cancel { how } {
 }
 
 test_bg_execution_pagination_return
-if ![target_info exists gdb,nointerrupts] {
-    test_bg_execution_pagination_cancel "ctrl-c"
-}
-test_bg_execution_pagination_cancel "quit"
diff --git a/gdb/testsuite/gdb.base/paginate-inferior-exit.exp b/gdb/testsuite/gdb.base/paginate-inferior-exit.exp
index 8e2d15cbabc..09527635916 100644
--- a/gdb/testsuite/gdb.base/paginate-inferior-exit.exp
+++ b/gdb/testsuite/gdb.base/paginate-inferior-exit.exp
@@ -13,8 +13,7 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-# A collection of tests related to running execution commands directly
-# from the command line, with "-ex".
+# Test that an inferior exit event does not trigger pagination.
 
 standard_testfile
 
@@ -40,36 +39,9 @@ proc test_paginate_inferior_exited {} {
 	# Force pagination.
 	gdb_test_no_output "set height 2"
 
-	set test "continue to pagination"
-
-	# Wait for the "Starting program" line, indicating the program
-	# is running.
-	set saw_starting 0
-	gdb_test_multiple "continue" $test {
-	    -re "$pagination_prompt" {
-		if {$saw_starting} {
-		    pass $test
-		} else {
-		    send_gdb "\n"
-		    exp_continue
-		}
-	    }
-	    -re "Continuing" {
-		set saw_starting 1
-		exp_continue
-	    }
-	}
-
-	# We're now stopped in a pagination output while handling a
-	# target event, trying to print about the program exiting.
-	set test "inferior exits normally"
-
-	send_gdb "\n"
-	gdb_test_multiple "" $test {
-	    -re "$inferior_exited_re normally.*$gdb_prompt $" {
-		pass $test
-	    }
-	}
+	# The program continues until termination, without pagination
+	# being triggered.
+	gdb_continue_to_end
 
 	gdb_test "p 1" " = 1" "GDB accepts further input"
 
-- 
2.17.1



More information about the Gdb-patches mailing list