[PATCH 04/12] Delete reinsert breakpoints from forked child

Yao Qi
2016-06-02T09:31:15Z

When a thread is stepping over a syscall instruction with software
single step, GDBserver inserts reinsert breakpoints at the next pcs.
If the syscall call is fork, the forked child has reinsert breakpoint
in its space, and GDBserver clones parent's breakpoint list to child's.
When GDBserver resumes the child, its bp_reinsert is zero, but has
reinsert breakpoints, so the following assert is triggered.

gdb/gdbserver/linux-low.c:4292: A problem internal to GDBserver has been detected.^M
void linux_resume_one_lwp_throw(lwp_info*, int, int, siginfo_t*): Assertion `!has_reinsert_breakpoints (proc)' failed.

gdb/gdbserver:

2016-05-20 Yao Qi <yao.qi@linaro.org>

* linux-low.c (handle_extended_wait): If the parent is doing
step-over, remove the reinsert breakpoints from the forked child.

gdb/testsuite:

2016-05-26 Yao Qi <yao.qi@linaro.org>

* gdb.base/step-over-fork-1.c: New file.
* gdb.base/step-over-fork-1.exp: New file.
---
gdb/gdbserver/linux-low.c | 21 ++++++
gdb/testsuite/gdb.base/step-over-fork-1.c | 51 +++++++++++++
gdb/testsuite/gdb.base/step-over-fork-1.exp | 106 ++++++++++++++++++++++++++++
3 files changed, 178 insertions(+)
create mode 100644 gdb/testsuite/gdb.base/step-over-fork-1.c
create mode 100644 gdb/testsuite/gdb.base/step-over-fork-1.exp

diff --git a/gdb/gdbserver/linux-low.c b/gdb/gdbserver/linux-low.c
index 95104d2..3f2d08e 100644
--- a/gdb/gdbserver/linux-low.c
+++ b/gdb/gdbserver/linux-low.c
@@ -569,6 +569,27 @@ handle_extended_wait (struct lwp_info **orig_event_lwp, int wstat)
event_lwp->status_pending_p = 1;
event_lwp->status_pending = wstat;

+ /* If the parent thread is doing step-over with reinsert
+ breakpoints, the reinsert breakpoints are still in forked
+ child's process space and cloned to its breakpoint list
+ from the parent's. Remove them from the child process. */
+ if (event_lwp->bp_reinsert != 0
+ && can_software_single_step ()
+ && event == PTRACE_EVENT_FORK)
+ {
+ struct thread_info *saved_thread = current_thread;
+
+ /* The child process is forked and stopped, so it is safe
+ to access its memory without stopping all other threads
+ from other processes. */
+ current_thread = child_thr;
+ delete_reinsert_breakpoints ();
+ current_thread = saved_thread;
+
+ gdb_assert (has_reinsert_breakpoints (parent_proc));
+ gdb_assert (!has_reinsert_breakpoints (child_proc));
+ }
+
/* Report the event. */
return 0;
}
diff --git a/gdb/testsuite/gdb.base/step-over-fork-1.c b/gdb/testsuite/gdb.base/step-over-fork-1.c
new file mode 100644
index 0000000..2ca145b
--- /dev/null
+++ b/gdb/testsuite/gdb.base/step-over-fork-1.c
@@ -0,0 +1,51 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 2011-2016 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 <stdlib.h>
+#include <unistd.h>
+
+static void
+marker (void)
+{}
+
+int
+main (void)
+{
+ int pid;
+
+ pid = FUNC ();
+ if (pid == 0) /* child */
+ {
+ exit (0);
+ }
+ else
+ {
+ }
+
+ marker ();
+
+ pid = FUNC ();
+ if (pid == 0) /* child */
+ {
+ marker ();
+ exit (0);
+ }
+ else
+ {
+ marker ();
+ }
+}
diff --git a/gdb/testsuite/gdb.base/step-over-fork-1.exp b/gdb/testsuite/gdb.base/step-over-fork-1.exp
new file mode 100644
index 0000000..4a34e61
--- /dev/null
+++ b/gdb/testsuite/gdb.base/step-over-fork-1.exp
@@ -0,0 +1,106 @@
+# Copyright 2016 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 a thread is stepping over a syscall instruction which does
+# fork, and GDB (or GDBserver) can update its state on both parent
+# process and child process correctly.
+
+standard_testfile
+
+proc test { syscall } {
+ global testfile srcfile
+ global gdb_prompt
+ global decimal hex
+
+ if {[prepare_for_testing $testfile.exp $testfile $srcfile "debug additional_flags=-DFUNC=$syscall"]} {
+ untested $testfile.exp
+ return -1
+ }
+
+ # Start with a fresh gdb.
+ clean_restart ${testfile}
+ if ![runto_main] {
+ fail "Can't run to main"
+ return -1
+ }
+
+ # Step 1, find the syscall instruction address by "catch $syscall".
+ # Verify that the system supports "catch $syscall".
+ gdb_test "catch $syscall" "Catchpoint \[0-9\]* \\($syscall\\)" \
+ "insert first catchpoint"
+ set test "continue to first catchpoint"
+ gdb_test_multiple "continue" $test {
+ -re ".*Your system does not support this type\r\nof catchpoint.*$gdb_prompt $" {
+ unsupported "catchpoint $syscall"
+ return
+ }
+ -re ".*Catchpoint.*$gdb_prompt $" {
+ pass $test
+ }
+ }
+
+ set syscall_insn_addr ""
+ set test "get syscall_insn_addr"
+ gdb_test_multiple "disassemble \$pc - 20,+30" $test {
+ -re " ($hex)\[^\r\n\]+\r\n=> .*$gdb_prompt $" {
+ set syscall_insn_addr $expect_out(1,string)
+ pass $test
+ }
+ }
+
+ if { $syscall_insn_addr == "" } {
+ fail $test
+ return
+ }
+
+ delete_breakpoints
+
+ gdb_test "break marker"
+
+ gdb_test "continue" "Continuing\\..*Breakpoint $decimal, .*" \
+ "continue to marker (1)"
+
+ gdb_test "set follow-fork-mode child"
+ gdb_test "set detach-on-fork off"
+
+ set test "set breakpoint condition-evaluation target"
+ gdb_test_multiple $test $test {
+ -re "warning: Target does not support breakpoint condition evaluation.\r\nUsing host evaluation mode instead.\r\n$gdb_prompt $" {
+ # Target doesn't support breakpoint condition
+ # evaluation on its side.
+ }
+ -re "^$test\r\n$gdb_prompt $" {
+ }
+ }
+
+ # Create a breakpoint which evaluates false.
+ gdb_test "break \*$syscall_insn_addr if main == 0" \
+ "Breakpoint \[0-9\]* at .*"
+
+ gdb_test "continue" "Continuing\\..*Breakpoint $decimal, marker \\(\\).*" \
+ "continue to marker (2)"
+
+ gdb_test "continue" "exited normally.*" "continue to end of inf 2"
+
+ gdb_test "inferior 1"
+ gdb_test "continue" \
+ "Continuing\\..*Breakpoint $decimal, marker \\(\\).*" \
+ "continue to marker (3)"
+ gdb_exit
+}
+
+foreach_with_prefix syscall { "fork" } {
+ test $syscall
+}
--
1.9.1