Re: [PATCH] improve python finish breakpoint for exceptions/longjmp case.

Andrew Burgess
2012-10-15T20:40:16Z

Jan,

Thanks for reviewing this patch.

On 11/10/2012 5:32 PM, Jan Kratochvil wrote:
> On Fri, 21 Sep 2012 16:57:30 +0200, Andrew Burgess wrote:
>> @@ -141,6 +144,10 @@ bpfinishpy_post_stop_hook (struct breakpoint_object *bp_obj)
>> /* Can't delete it here, but it will be removed at the next stop. */
>> disable_breakpoint (bp_obj->bp);
>> gdb_assert (bp_obj->bp->disposition == disp_del);
>> + bp_obj->bp->disposition = disp_del_at_next_stop;
>> +
>> + /* Disable all the longjmp breakpoints too. */
>> + delete_longjmp_breakpoint_at_next_stop (inferior_thread ()->num);
>
> I believe here should be bp_obj->bp->thread as during this function
> inferior_thread () may be different, I do not see a temporary inferior switch
> here. But this is a problem already with existing Python Finish Breakpoints:

Fixed.

> In the following reproducer breakpoint 3 is correctly thread-specific ("thread
> 1") but as gdbpy_should_stop does not check thread number it thinks the
> breakpoint 3 applies even to thread 2 but frame_id is not valid there so the
> breakpoints get deleted. It sure should not.

Fixed.

>
> FYI I did not review original python/py-finishbreakpoint.c but I do not think
> the whole finish_command logic should have been duplicated. But the original
> review was very long which I skipped as a Python one so I may miss something.

I'm not sure what to make of this. If you have a suggestion on how you'd like thing changed I could do some of the work depending on the time requirement, I only really ended up working on this code as the problems were causing test failures on my local gdb port.

> Besides that on Fedora 16 x86_64 (using -lmcheck if it matters) this testcases
> regresses gdb.python/py-finish-breakpoint.exp for me. I can debug it more if
> it is not reproducible for you, I understand the bug may not be in the new
> patch:
>
> (gdb) PASS: gdb.python/py-finish-breakpoint.exp: set FinishBP after the exit()
> continue^M
> Continuing.^M
> [Inferior 1 (process 28725) exited normally]^M
> SimpleFinishBreakpoint out of scope^M
> thread.c:72: internal-error: inferior_thread: Assertion `tp' failed.^M
> A problem internal to GDB has been detected,^M
> further debugging may prove unreliable.^M
> FAIL: gdb.python/py-finish-breakpoint.exp: catch out of scope after exit (GDB internal error)

I reproduced this failure locally on the original patch, I fixed the bug, on the new patch it seems fine.

> Besides that, not sure if it is caused by it or not:
>
> Valgrind output:
> ==28706== Invalid read of size 1
> ==28706== at 0x4C2B0B2: strlen (mc_replace_strmem.c:399)
> ==28706== by 0x5E4E440: PyString_FromFormatV (stringobject.c:241)
> ==28706== by 0x5E9F62E: PyErr_Format (errors.c:550)
> ==28706== by 0x660904: gdbpy_convert_exception (py-utils.c:292)
> ==28706== by 0x6535FA: bpfinishpy_detect_out_scope_cb (py-finishbreakpoint.c:377)
> ==28706== by 0x6AEB20: iterate_over_breakpoints (breakpoint.c:15639)
> ==28706== by 0x6536B1: bpfinishpy_handle_exit (py-finishbreakpoint.c:410)
> ==28706== by 0x76B33B: observer_inferior_exit_notification_stub (observer.inc:962)

This I also reproduced on the original patch, I no longer see this issue on the latest patch.

Biggest changes since the previous patch,

- The out of scope check now has code to check the thread of the breakpoint, we also handle the case where the thread has exited.
- New test using threads to exercise the above code.

Thanks,

Andrew

gdb/ChangeLog

2012-10-15 Andrew Burgess <aburgess@broadcom.com>

* python/py-finishbreakpoint.c (struct finish_breakpoint_object)
<initiating_frame>: New field.
(bpfinishpy_post_stop_hook): Disable the longjmp breakpoints when
we stop at a finish breakpoint. Have the finish breakpoint
deleted at the next stop, wherever that might be.
(bpfinishpy_init): Set longjmp breakpoints. Remember frame we're
in when creating the finish breakpoint.
(struct bpfinishpy_out_of_scope_data): New structure for passing
parameters to out of scope callback.
(bpfinishpy_detect_out_scope_cb): Look for frame we are hoping to
finish when deciding if we're out of scope, not frame of parent.
Check we're stopped in correct thread, or that the breakpoint
thread has exited before we declare a breakpoint out of scope.
(bpfinishpy_handle_stop): Pass parameter struct to callback.
(bpfinishpy_handle_exit): Pass parameter struct to callback.

diff --git a/gdb/python/py-finishbreakpoint.c b/gdb/python/py-finishbreakpoint.c
index 56ab775..02f14c2 100644
--- a/gdb/python/py-finishbreakpoint.c
+++ b/gdb/python/py-finishbreakpoint.c
@@ -53,6 +53,9 @@ struct finish_breakpoint_object
the function; Py_None if the value is not computable; NULL if GDB is
not stopped at a FinishBreakpoint. */
PyObject *return_value;
+ /* The initiating frame for this operation, used to decide when we have
+ left this frame. */
+ struct frame_id initiating_frame;
};

/* Python function to get the 'return_value' attribute of
@@ -141,6 +144,10 @@ bpfinishpy_post_stop_hook (struct breakpoint_object *bp_obj)
/* Can't delete it here, but it will be removed at the next stop. */
disable_breakpoint (bp_obj->bp);
gdb_assert (bp_obj->bp->disposition == disp_del);
+ bp_obj->bp->disposition = disp_del_at_next_stop;
+
+ /* Disable all the longjmp breakpoints too. */
+ delete_longjmp_breakpoint_at_next_stop (bp_obj->bp->thread);
}
if (except.reason < 0)
{
@@ -161,7 +168,7 @@ bpfinishpy_init (PyObject *self, PyObject *args, PyObject *kwargs)
PyObject *frame_obj = NULL;
int thread;
struct frame_info *frame, *prev_frame = NULL;
- struct frame_id frame_id;
+ struct frame_id prev_frame_id, init_frame_id;
PyObject *internal = NULL;
int internal_bp = 0;
CORE_ADDR finish_pc, pc;
@@ -184,6 +191,8 @@ bpfinishpy_init (PyObject *self, PyObject *args, PyObject *kwargs)

if (frame == NULL)
goto invalid_frame;
+
+ init_frame_id = get_frame_id (frame);

TRY_CATCH (except, RETURN_MASK_ALL)
{
@@ -201,8 +210,8 @@ bpfinishpy_init (PyObject *self, PyObject *args, PyObject *kwargs)
}
else
{
- frame_id = get_frame_id (prev_frame);
- if (frame_id_eq (frame_id, null_frame_id))
+ prev_frame_id = get_frame_id (prev_frame);
+ if (frame_id_eq (prev_frame_id, null_frame_id))
PyErr_SetString (PyExc_ValueError,
_("Invalid ID for the `frame' object."));
}
@@ -295,11 +304,18 @@ bpfinishpy_init (PyObject *self, PyObject *args, PyObject *kwargs)
AUTO_BOOLEAN_TRUE,
&bkpt_breakpoint_ops,
0, 1, internal_bp, 0);
+ set_longjmp_breakpoint (inferior_thread (), null_frame_id);
+
+ /* Set frame to NULL for sanity, creating the breakpoint could cause
+ us to switch threads, thus blowing away the frame cache, rendering
+ the frame pointer invalid. */
+ frame = NULL;
}
GDB_PY_SET_HANDLE_EXCEPTION (except);

- self_bpfinish->py_bp.bp->frame_id = frame_id;
+ self_bpfinish->py_bp.bp->frame_id = prev_frame_id;
self_bpfinish->py_bp.is_finish_bp = 1;
+ self_bpfinish->initiating_frame = init_frame_id;

/* Bind the breakpoint with the current program space. */
self_bpfinish->py_bp.bp->pspace = current_program_space;
@@ -329,9 +345,18 @@ bpfinishpy_out_of_scope (struct finish_breakpoint_object *bpfinish_obj)
gdbpy_print_stack ();
}

+ delete_longjmp_breakpoint_at_next_stop (bp_obj->bp->thread);
delete_breakpoint (bpfinish_obj->py_bp.bp);
}

+/* Structure used to pass parameter to the out of scope check. */
+
+struct bpfinishpy_out_of_scope_data
+{
+ struct breakpoint *bp;
+ int thread;
+};
+
/* Callback for `bpfinishpy_detect_out_scope'. Triggers Python's
`B->out_of_scope' function if B is a FinishBreakpoint out of its scope. */

@@ -339,25 +364,39 @@ static int
bpfinishpy_detect_out_scope_cb (struct breakpoint *b, void *args)
{
volatile struct gdb_exception except;
- struct breakpoint *bp_stopped = (struct breakpoint *) args;
+ struct bpfinishpy_out_of_scope_data *data =
+ (struct bpfinishpy_out_of_scope_data *) args;
+ struct breakpoint *bp_stopped = data->bp;
+ int thread_num = data->thread;
PyObject *py_bp = (PyObject *) b->py_bp_object;
struct gdbarch *garch = b->gdbarch ? b->gdbarch : get_current_arch ();
-
+
/* Trigger out_of_scope if this is a FinishBreakpoint and its frame is
- not anymore in the current callstack. */
- if (py_bp != NULL && b->py_bp_object->is_finish_bp)
+ no longer in the current callstack, or the thread for the
+ FinishBreakpoint has gone away. */
+ if (py_bp != NULL
+ && b->py_bp_object->is_finish_bp
+ && (b->thread == -1
+ || b->thread == thread_num
+ || find_thread_id (b->thread) == NULL
+ || is_exited (thread_id_to_pid (b->thread))))
{
struct finish_breakpoint_object *finish_bp =
(struct finish_breakpoint_object *) py_bp;

+ /* All finish breakpoints are created for a specific thread. */
+ gdb_assert (b->thread != -1);
+
/* Check scope if not currently stopped at the FinishBreakpoint. */
if (b != bp_stopped)
{
TRY_CATCH (except, RETURN_MASK_ALL)
{
+ struct frame_id fid = finish_bp->initiating_frame;
+
if (b->pspace == current_inferior ()->pspace
&& (!target_has_registers
- || frame_find_by_id (b->frame_id) == NULL))
+ || frame_find_by_id (fid) == NULL))
bpfinishpy_out_of_scope (finish_bp);
}
if (except.reason < 0)
@@ -377,11 +416,18 @@ bpfinishpy_detect_out_scope_cb (struct breakpoint *b, void *args)
static void
bpfinishpy_handle_stop (struct bpstats *bs, int print_frame)
{
- struct cleanup *cleanup = ensure_python_env (get_current_arch (),
- current_language);
+ struct bpfinishpy_out_of_scope_data data;
+ struct cleanup *cleanup;
+
+ if (!find_thread_ptid (inferior_ptid))
+ return;

- iterate_over_breakpoints (bpfinishpy_detect_out_scope_cb,
- bs == NULL ? NULL : bs->breakpoint_at);
+ cleanup = ensure_python_env (get_current_arch (), current_language);
+
+ data.bp = (bs == NULL ? NULL : bs->breakpoint_at);
+ data.thread = inferior_thread ()->num;
+
+ iterate_over_breakpoints (bpfinishpy_detect_out_scope_cb, &data);

do_cleanups (cleanup);
}
@@ -392,10 +438,15 @@ bpfinishpy_handle_stop (struct bpstats *bs, int print_frame)
static void
bpfinishpy_handle_exit (struct inferior *inf)
{
- struct cleanup *cleanup = ensure_python_env (target_gdbarch,
- current_language);
+ struct bpfinishpy_out_of_scope_data data;
+ struct cleanup *cleanup;
+
+ cleanup = ensure_python_env (target_gdbarch, current_language);
+
+ data.bp = NULL;
+ data.thread = -1;

- iterate_over_breakpoints (bpfinishpy_detect_out_scope_cb, NULL);
+ iterate_over_breakpoints (bpfinishpy_detect_out_scope_cb, &data);

do_cleanups (cleanup);
}

gdb/testsuite/ChangeLog

2012-10-15 Andrew Burgess <aburgess@broadcom.com>

Additional testing for FinishBreakpoint.
* gdb.python/py-finish-breakpoint2.cc: Add extra levels of nesting
to allow more testing opportunities.
* gdb.python/py-finish-breakpoint2.exp: Additional test cases.
* gdb.python/py-finish-breakpoint3.c: New file.
* gdb.python/py-finish-breakpoint3.exp: New file.
* gdb.python/py-finish-breakpoint3.py: New file.

diff --git a/gdb/testsuite/gdb.python/py-finish-breakpoint2.cc b/gdb/testsuite/gdb.python/py-finish-breakpoint2.cc
index 8cc756f..930e6bc 100644
--- a/gdb/testsuite/gdb.python/py-finish-breakpoint2.cc
+++ b/gdb/testsuite/gdb.python/py-finish-breakpoint2.cc
@@ -22,7 +22,8 @@
void
throw_exception_1 (int e)
{
- throw new int (e);
+ if (e > 5)
+ throw new int (e);
}

void
@@ -32,28 +33,79 @@ throw_exception (int e)
}

int
-main (void)
+do_test (int e)
{
- int i;
+ int i = 0;
try
{
- throw_exception_1 (10);
+ throw_exception_1 (e);
+
+ i += 1;
}
catch (const int *e)
{
std::cerr << "Exception #" << *e << std::endl;
}
- i += 1; /* Break after exception 1. */
+ i += 1;

try
{
- throw_exception (10);
+ throw_exception (e);
+
+ i += 1;
}
catch (const int *e)
{
std::cerr << "Exception #" << *e << std::endl;
}
- i += 1; /* Break after exception 2. */
+ i += 1;
+
+ return i;
+}
+
+int
+call_do_test (int e)
+{
+ int i;
+
+ i = do_test (e);
+
+ throw_exception_1 (e);
+
+ return i;
+}
+
+int
+do_nested_test (int e)
+{
+ int i = 0;
+
+ try
+ {
+ call_do_test (e);
+
+ i += 1;
+ }
+ catch (const int *e)
+ {
+ std::cerr << "Exception #" << *e << std::endl;
+ }
+ i += 1;

return i;
}
+
+
+int
+main (void)
+{
+ int i = 0;
+
+ i += do_test (10);
+
+ i += do_test (4);
+
+ i += do_nested_test (10);
+
+ return i; /* Additional breakpoint */
+}
diff --git a/gdb/testsuite/gdb.python/py-finish-breakpoint2.exp b/gdb/testsuite/gdb.python/py-finish-breakpoint2.exp
index 3b08ef8..a3a12fd 100644
--- a/gdb/testsuite/gdb.python/py-finish-breakpoint2.exp
+++ b/gdb/testsuite/gdb.python/py-finish-breakpoint2.exp
@@ -37,7 +37,7 @@ if ![runto_main] then {
# Check FinishBreakpoints against C++ exceptions
#

-gdb_breakpoint [gdb_get_line_number "Break after exception 2"]
+gdb_breakpoint [gdb_get_line_number "Additional breakpoint"]

gdb_test "source $pyfile" ".*Python script imported.*" \
"import python scripts"
@@ -47,9 +47,43 @@ gdb_test "continue" "Breakpoint .*throw_exception_1.*" "run to exception 1"

gdb_test "python print len(gdb.breakpoints())" "3" "check BP count"
gdb_test "python ExceptionFinishBreakpoint(gdb.newest_frame())" "init ExceptionFinishBreakpoint" "set FinishBP after the exception"
-gdb_test "continue" ".*stopped at ExceptionFinishBreakpoint.*" "check FinishBreakpoint in catch()"
+gdb_test "continue" ".*do_test \\(e=10\\).*catch \\(const int \\*e\\).*exception did not finish.*" "FinishBreakpoint with exception thrown caught in parent"
gdb_test "python print len(gdb.breakpoints())" "3" "check finish BP removal"

gdb_test "continue" ".*Breakpoint.* throw_exception_1.*" "continue to second exception"
gdb_test "python ExceptionFinishBreakpoint(gdb.newest_frame())" "init ExceptionFinishBreakpoint" "set FinishBP after the exception"
-gdb_test "continue" ".*exception did not finish.*" "FinishBreakpoint with exception thrown not caught"
+gdb_test "continue" ".*do_test \\(e=10\\).*catch \\(const int \\*e\\).*exception did not finish.*" "FinishBreakpoint with exception thrown caught in grandparent"
+gdb_test "python print len(gdb.breakpoints())" "3" "check finish BP removal"
+
+gdb_test "continue" ".*Breakpoint.* throw_exception_1.*" "continue to first no throw test"
+gdb_test "python ExceptionFinishBreakpoint(gdb.newest_frame())" "init ExceptionFinishBreakpoint" "set FinishBP after the exception"
+gdb_test "continue" ".*stopped at ExceptionFinishBreakpoint.*" "FinishBreakpoint with exception not thrown"
+gdb_test "python print len(gdb.breakpoints())" "3" "check finish BP removal"
+
+gdb_test "continue" ".*Breakpoint.* throw_exception_1.*" "continue to second no throw test"
+gdb_test "python ExceptionFinishBreakpoint(gdb.newest_frame())" "init ExceptionFinishBreakpoint" "set FinishBP after the exception"
+gdb_test "continue" ".*stopped at ExceptionFinishBreakpoint.*" "FinishBreakpoint with exception not thrown"
+gdb_test "python print len(gdb.breakpoints())" "3" "check finish BP removal"
+
+# Now exercies the nested test example, we're creating an
+# ExceptionFinishBreakpoint inside a frame, then going to continue into
+# further child frames before using the "finish" command, finally, we'll
+# continue, and look for the original ExceptionFinishBreakpoint frame to
+# finish.
+
+gdb_breakpoint "call_do_test"
+gdb_test "continue" ".*Breakpoint.* call_do_test.*" "continue to nested test."
+gdb_test "python print len(gdb.breakpoints())" "4" "check BP count before nested test."
+gdb_test "python ExceptionFinishBreakpoint(gdb.newest_frame())" "init ExceptionFinishBreakpoint" "set FinishBP after the exception"
+gdb_test "continue" ".*Breakpoint.* throw_exception_1.*" "continue to second no throw test"
+gdb_test "finish" ".*do_test \\(e=10\\).*catch \\(const int \\*e\\).*" "finish with exception being thrown, caught in parent"
+
+gdb_test "continue" ".*Breakpoint.* throw_exception_1.*" "continue to second no throw test"
+gdb_test "finish" ".*do_test \\(e=10\\).*catch \\(const int \\*e\\).*" "finish with exception being thrown, caught in grand-parent"
+
+gdb_test "continue" ".*Breakpoint.* throw_exception_1.*" "continue to second no throw test"
+
+setup_kfail "BUG/1234" "*-*-*"
+gdb_test "continue" ".*catch \\(const int \\*e\\).*exception did not finish.*" "FinishBreakpoint nested with exception thrown caught in parent"
+
+gdb_test "python print len(gdb.breakpoints())" "4" "check finish BP removal"
diff --git a/gdb/testsuite/gdb.python/py-finish-breakpoint3.c b/gdb/testsuite/gdb.python/py-finish-breakpoint3.c
new file mode 100644
index 0000000..6a52dfc
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-finish-breakpoint3.c
@@ -0,0 +1,102 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 2012 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 <pthread.h>
+#include <assert.h>
+#include <unistd.h>
+
+static volatile int blocks[2];
+
+void
+breakpt (int is_first_thread)
+{
+ if (is_first_thread)
+ {
+ /* Main thread. */
+
+ blocks[0] = 1; /* Set thread 2 going. */
+ while (!blocks[1]); /* Wait for thread 2. */
+ }
+ else
+ {
+ /* Other thread - Nothing. */
+ }
+}
+
+void
+func (int is_first_thread)
+{
+ breakpt (is_first_thread);
+}
+
+static void *
+start_owner_test (void *arg)
+{
+ /* Wait for the first thread to set us going. */
+ while (!blocks[0]);
+
+ func (0);
+
+ /* Release the first thread to finish. */
+ blocks[1] = 1;
+
+ return arg;
+}
+
+void
+do_pthread_exit ()
+{
+ pthread_exit (NULL);
+}
+
+static void *
+start_exit_test (void *arg)
+{
+ do_pthread_exit ();
+}
+
+void
+final_breakpt ()
+{
+ /* Nothing. */
+}
+
+int
+main (void)
+{
+ pthread_t thread1;
+ int i;
+
+ i = pthread_create (&thread1, NULL, start_owner_test, NULL);
+ assert (i == 0);
+
+ func (1);
+
+ i = pthread_join (thread1, NULL);
+ assert (i == 0);
+
+ i = pthread_create (&thread1, NULL, start_exit_test, NULL);
+ assert (i == 0);
+
+ i = pthread_join (thread1, NULL);
+ assert (i == 0);
+
+ final_breakpt ();
+
+ return 0;
+}
diff --git a/gdb/testsuite/gdb.python/py-finish-breakpoint3.exp b/gdb/testsuite/gdb.python/py-finish-breakpoint3.exp
new file mode 100644
index 0000000..5a1417a
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-finish-breakpoint3.exp
@@ -0,0 +1,142 @@
+# Copyright (C) 2012 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/>.
+
+# This file is part of the GDB testsuite.
+
+load_lib gdb-python.exp
+
+standard_testfile
+set pyfile ${srcdir}/${subdir}/${testfile}.py
+
+if {[gdb_compile_pthreads "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable {debug}] != "" } {
+ return -1
+}
+
+# Check that stopping thread #2 in BREAKPT does not cause the
+# FinishBreakpoint for threda #1 to go out of scope.
+with_test_prefix "no_bp_disable" {
+ clean_restart $testfile
+
+ # Skip all tests if Python scripting is not enabled.
+ if { [skip_python_tests] } { continue }
+
+ if ![runto_main] then {
+ fail "Cannot run to main."
+ return 0
+ }
+
+ gdb_test "source $pyfile" ".*Python script imported.*" \
+ "import python scripts"
+
+ gdb_breakpoint "breakpt"
+ gdb_continue_to_breakpoint "breakpt"
+
+ gdb_test "python finishbp = MyFinishBreakpoint (gdb.newest_frame ())" \
+ "Temporary breakpoint \[0-9\]+ \[^\r\n\]+\r\nMyFinishBreakpoint init" \
+ "set FinishBreakpoint in current frame thread 1?"
+
+ gdb_test "info breakpoints" \
+ "1.*breakpoint.*keep.*y.*${hex}.*in main at.*3.*breakpoint.*del.*y.*${hex}.*in func at.*thread 1" \
+ "Check breakpoints after creating finishbp."
+
+ gdb_test "info thread" \
+ "\\* 1\[\t \]+Thread.*" \
+ "Check we're currently stopped in thread #1"
+
+ set test "continue to breakpt in thread 2"
+ gdb_test_multiple "continue" $test {
+ -re "MyFinishBreakpoint out of scope.*$gdb_prompt $" {
+ fail $test
+ }
+ -re "Breakpoint \[0-9\]+, breakpt.*$gdb_prompt $" {
+ pass $test
+ }
+ }
+
+ gdb_test "continue" "MyFinishBreakpoint stop.*Breakpoint \[0-9\], func.*"
+
+ gdb_test "info thread" \
+ "\\* 1\[\t \]+Thread.*" \
+ "Check we're still in thread #1 at func breakpoint"
+}
+
+# Check that thread #2 does not stop at the finish breakpoint for thread #1.
+with_test_prefix "with_bp_disable" {
+
+ # Start with a fresh gdb.
+ clean_restart ${testfile}
+
+ if ![runto_main] then {
+ fail "Cannot run to main."
+ return 0
+ }
+
+ gdb_test "source $pyfile" ".*Python script imported.*" \
+ "import python scripts"
+
+ gdb_breakpoint "breakpt"
+ gdb_test_no_output "set \$breakpt_bp_num=\$bpnum"
+
+ gdb_continue_to_breakpoint "breakpt"
+
+ gdb_test "python finishbp = MyFinishBreakpoint (gdb.newest_frame ())" \
+ "Temporary breakpoint \[0-9\]+ \[^\r\n\]+\r\nMyFinishBreakpoint init" \
+ "set FinishBreakpoint in current frame thread 1?"
+
+ gdb_test "info breakpoints" \
+ "1.*breakpoint.*keep.*y.*${hex}.*in main at.*3.*breakpoint.*del.*y.*${hex}.*in func at.*thread 1" \
+ "Check breakpoints after creating finishbp."
+
+ gdb_test "info thread" \
+ "\\* 1\[\t \]+Thread.*" \
+ "Check we're currently stopped in thread #1"
+
+ gdb_test_no_output "disable \$breakpt_bp_num"
+
+ gdb_test "continue" \
+ "MyFinishBreakpoint stop\r\n\r\nBreakpoint \[0-9\]+, func.*" \
+ "continue to func breakpoint in thread 1"
+
+ gdb_test "info thread" \
+ "\\* 1\[\t \]+Thread.*" \
+ "Check we're still in thread #1 at func breakpoint"
+}
+
+# Check that thread #2 does not stop at the finish breakpoint for thread #1.
+with_test_prefix "pthread_exit_test" {
+
+ # Start with a fresh gdb.
+ clean_restart ${testfile}
+
+ if ![runto_main] then {
+ fail "Cannot run to main."
+ return 0
+ }
+
+ gdb_test "source $pyfile" ".*Python script imported.*" \
+ "import python scripts"
+
+ gdb_breakpoint "final_breakpt"
+ gdb_breakpoint "do_pthread_exit"
+ gdb_continue_to_breakpoint "do_pthread_exit"
+
+ gdb_test "python finishbp = MyFinishBreakpoint (gdb.newest_frame ())" \
+ "Temporary breakpoint \[0-9\]+ \[^\r\n\]+\r\nMyFinishBreakpoint init" \
+ "set FinishBreakpoint in current frame thread 1?"
+
+ gdb_continue_to_breakpoint "final_breakpt"
+}
+
+
diff --git a/gdb/testsuite/gdb.python/py-finish-breakpoint3.py b/gdb/testsuite/gdb.python/py-finish-breakpoint3.py
new file mode 100644
index 0000000..c2c72ca
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-finish-breakpoint3.py
@@ -0,0 +1,30 @@
+# Copyright (C) 2012 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/>.
+
+# This file is part of the GDB testsuite.
+
+class MyFinishBreakpoint (gdb.FinishBreakpoint):
+ def __init__(self, frame):
+ gdb.FinishBreakpoint.__init__ (self, frame)
+ print "MyFinishBreakpoint init"
+
+ def stop(self):
+ print "MyFinishBreakpoint stop"
+ return True
+
+ def out_of_scope(self):
+ print "MyFinishBreakpoint out of scope"
+
+print "Python script imported"