* [PATCH] improve python finish breakpoint for exceptions/longjmp case.
@ 2012-09-21 14:58 Andrew Burgess
2012-09-24 10:22 ` Kevin Pouget
` (2 more replies)
0 siblings, 3 replies; 15+ messages in thread
From: Andrew Burgess @ 2012-09-21 14:58 UTC (permalink / raw)
To: gdb-patches
The documentation for the python FinishBreakpoints suggests that when a longjmp or c++ exception is used to terminate a function the out_of_scope method will be called. A quick look inside gdb/python/py-finishbreakpoint.c shows that we take no action to ensure that we will break on exceptions or longjmps (for example using breakpoint.c:set_longjmp_breakpoint), instead if we leave a function using for longjmp/exception we rely on hitting some other stop point to trigger the call to the out_of_scope method.
A further issue is that the testing for FinishBreakpoints, in gdb/testsuite/gdb.python/py-finish-breakpoint2.exp, the test action titled "check FinishBreakpoint in catch()" expects the "stop" method to fire rather than the "out_of_scope" method, this is due to the generated code (on x86 and maybe other targets), the first breakpoint we hit after throwing the exception happens to be the finish breakpoint, however this is not guaranteed, and means that (a) the test does not match the documentation, and (b) the test is platform specific.
I have a patch below that improves and extends the testing to cover more cases, and a patch for py-finishbreakpoint.c that goes some way to fixing some of the problems.
There are a couple of comments to go with the patch,
1. I've changed the c++ test program from using c++ streams to using cstdio. I have to confess self interest here, on my local target the c++ streams are too large to build in, so any test that uses them will not work for me. I'm not the only target for which this is the case, see the comments in gdb/testsuite/lib/gdb.exp. Though we _obviously_ have to have good test coverage for gdb with c++ streams, I think it would be a shame if some important behaviours are only tested on targets that support c++ streams. However, if this is a problem then I can change the test back to using c++ streams.
2. The patch does not solve all the problems with FinishBreakpoints. I use set_longjmp_breakpoint to create the breakpoints for the lonjmp/exceptions, however, between creating an instance of FinishBreakpoint, when the breakpoints are created, and hitting these breakpoints, we are free to issue any commands we like, including things like step/finish/etc all of which use set_longjmp_breakpoint themselves. As these commands (step/finish/etc) complete, they remove the longjmp breakpoints, preventing the FinishBreakpoint from stopping correctly. This behaviour is tested in my updated test and currently kfails; before committing this patch I'll raise a defect and fill in the new bug id.
I have some ideas about how to fix #2 but I'm open to suggestions. As the gdb is no worse than it was in case #2, and is significantly better in other cases, I'd like to push this patch now (given that I'll raise a bug for broken case).
Let me know what you think, OK to commit?
Thanks,
Andrew
gdb/ChangeLog
2012-09-21 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.
(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.
diff --git a/gdb/python/py-finishbreakpoint.c b/gdb/python/py-finishbreakpoint.c
index 56ab775..a2cd980 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 (inferior_thread ()->num);
}
if (except.reason < 0)
{
@@ -295,11 +302,13 @@ 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);
}
GDB_PY_SET_HANDLE_EXCEPTION (except);
self_bpfinish->py_bp.bp->frame_id = frame_id;
self_bpfinish->py_bp.is_finish_bp = 1;
+ self_bpfinish->initiating_frame = get_frame_id (frame);
/* Bind the breakpoint with the current program space. */
self_bpfinish->py_bp.bp->pspace = current_program_space;
@@ -329,6 +338,7 @@ bpfinishpy_out_of_scope (struct finish_breakpoint_object *bpfinish_obj)
gdbpy_print_stack ();
}
+ delete_longjmp_breakpoint_at_next_stop (inferior_thread ()->num);
delete_breakpoint (bpfinish_obj->py_bp.bp);
}
@@ -355,9 +365,11 @@ bpfinishpy_detect_out_scope_cb (struct breakpoint *b, void *args)
{
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)
2012-09-21 Andrew Burgess <aburgess@broadcom.com>
* 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.
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..f1403d9 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-ID" "*-*-*"
+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"
^ permalink raw reply [flat|nested] 15+ messages in thread* Re: [PATCH] improve python finish breakpoint for exceptions/longjmp case. 2012-09-21 14:58 [PATCH] improve python finish breakpoint for exceptions/longjmp case Andrew Burgess @ 2012-09-24 10:22 ` Kevin Pouget 2012-10-01 16:33 ` Andrew Burgess 2012-10-01 16:30 ` Andrew Burgess 2012-10-11 16:32 ` Jan Kratochvil 2 siblings, 1 reply; 15+ messages in thread From: Kevin Pouget @ 2012-09-24 10:22 UTC (permalink / raw) To: Andrew Burgess; +Cc: gdb-patches On Fri, Sep 21, 2012 at 4:57 PM, Andrew Burgess <aburgess@broadcom.com> wrote: > The documentation for the python FinishBreakpoints suggests that when a longjmp or c++ exception is used to terminate a function the out_of_scope method will be called. A quick look inside gdb/python/py-finishbreakpoint.c shows that we take no action to ensure that we will break on exceptions or longjmps (for example using breakpoint.c:set_longjmp_breakpoint), instead if we leave a function using for longjmp/exception we rely on hitting some other stop point to trigger the call to the out_of_scope method. > > A further issue is that the testing for FinishBreakpoints, in gdb/testsuite/gdb.python/py-finish-breakpoint2.exp, the test action titled "check FinishBreakpoint in catch()" expects the "stop" method to fire rather than the "out_of_scope" method, this is due to the generated code (on x86 and maybe other targets), the first breakpoint we hit after throwing the exception happens to be the finish breakpoint, however this is not guaranteed, and means that (a) the test does not match the documentation, and (b) the test is platform specific. > > I have a patch below that improves and extends the testing to cover more cases, and a patch for py-finishbreakpoint.c that goes some way to fixing some of the problems. > > There are a couple of comments to go with the patch, > > 1. I've changed the c++ test program from using c++ streams to using cstdio. I have to confess self interest here, on my local target the c++ streams are too large to build in, so any test that uses them will not work for me. I'm not the only target for which this is the case, see the comments in gdb/testsuite/lib/gdb.exp. Though we _obviously_ have to have good test coverage for gdb with c++ streams, I think it would be a shame if some important behaviours are only tested on targets that support c++ streams. However, if this is a problem then I can change the test back to using c++ streams. > > 2. The patch does not solve all the problems with FinishBreakpoints. I use set_longjmp_breakpoint to create the breakpoints for the lonjmp/exceptions, however, between creating an instance of FinishBreakpoint, when the breakpoints are created, and hitting these breakpoints, we are free to issue any commands we like, including things like step/finish/etc all of which use set_longjmp_breakpoint themselves. As these commands (step/finish/etc) complete, they remove the longjmp breakpoints, preventing the FinishBreakpoint from stopping correctly. This behaviour is tested in my updated test and currently kfails; before committing this patch I'll raise a defect and fill in the new bug id. > > I have some ideas about how to fix #2 but I'm open to suggestions. As the gdb is no worse than it was in case #2, and is significantly better in other cases, I'd like to push this patch now (given that I'll raise a bug for broken case). > > Let me know what you think, OK to commit? > > Thanks, > Andrew Hello, thanks for your interest in Finish Breakpoints. I recognize that I did not really focus on these "abnormal" function returns during the initial work, so I'm glad you decided to improve it :) Your explanations and patch seem to make sense, so let's just wait for a maintainer to review it. BTW, I wrote a patch in January that get lost in the mailing list which _may_ be related and/or useful for your work, if you cant to take a look: http://permalink.gmane.org/gmane.comp.gdb.patches/72592 Cordially, Kevin ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH] improve python finish breakpoint for exceptions/longjmp case. 2012-09-24 10:22 ` Kevin Pouget @ 2012-10-01 16:33 ` Andrew Burgess 0 siblings, 0 replies; 15+ messages in thread From: Andrew Burgess @ 2012-10-01 16:33 UTC (permalink / raw) To: Kevin Pouget; +Cc: gdb-patches On 24/09/2012 11:21 AM, Kevin Pouget wrote: > On Fri, Sep 21, 2012 at 4:57 PM, Andrew Burgess <aburgess@broadcom.com> wrote: >> The documentation for the python FinishBreakpoints suggests that when a longjmp or c++ exception is used to terminate a function the out_of_scope method will be called. A quick look inside gdb/python/py-finishbreakpoint.c shows that we take no action to ensure that we will break on exceptions or longjmps (for example using breakpoint.c:set_longjmp_breakpoint), instead if we leave a function using for longjmp/exception we rely on hitting some other stop point to trigger the call to the out_of_scope method. >> >> A further issue is that the testing for FinishBreakpoints, in gdb/testsuite/gdb.python/py-finish-breakpoint2.exp, the test action titled "check FinishBreakpoint in catch()" expects the "stop" method to fire rather than the "out_of_scope" method, this is due to the generated code (on x86 and maybe other targets), the first breakpoint we hit after throwing the exception happens to be the finish breakpoint, however this is not guaranteed, and means that (a) the test does not match the documentation, and (b) the test is platform specific. >> >> I have a patch below that improves and extends the testing to cover more cases, and a patch for py-finishbreakpoint.c that goes some way to fixing some of the problems. >> >> There are a couple of comments to go with the patch, >> >> 1. I've changed the c++ test program from using c++ streams to using cstdio. I have to confess self interest here, on my local target the c++ streams are too large to build in, so any test that uses them will not work for me. I'm not the only target for which this is the case, see the comments in gdb/testsuite/lib/gdb.exp. Though we _obviously_ have to have good test coverage for gdb with c++ streams, I think it would be a shame if some important behaviours are only tested on targets that support c++ streams. However, if this is a problem then I can change the test back to using c++ streams. >> >> 2. The patch does not solve all the problems with FinishBreakpoints. I use set_longjmp_breakpoint to create the breakpoints for the lonjmp/exceptions, however, between creating an instance of FinishBreakpoint, when the breakpoints are created, and hitting these breakpoints, we are free to issue any commands we like, including things like step/finish/etc all of which use set_longjmp_breakpoint themselves. As these commands (step/finish/etc) complete, they remove the longjmp breakpoints, preventing the FinishBreakpoint from stopping correctly. This behaviour is tested in my updated test and currently kfails; before committing this patch I'll raise a defect and fill in the new bug id. >> >> I have some ideas about how to fix #2 but I'm open to suggestions. As the gdb is no worse than it was in case #2, and is significantly better in other cases, I'd like to push this patch now (given that I'll raise a bug for broken case). >> >> Let me know what you think, OK to commit? >> >> Thanks, >> Andrew > > Hello, > > thanks for your interest in Finish Breakpoints. I recognize that I did > not really focus on these "abnormal" function returns during the > initial work, so I'm glad you decided to improve it :) > > Your explanations and patch seem to make sense, so let's just wait for > a maintainer to review it. > BTW, I wrote a patch in January that get lost in the mailing list > which _may_ be related and/or useful for your work, if you cant to > take a look: > http://permalink.gmane.org/gmane.comp.gdb.patches/72592 Thanks for taking a look through this patch. I had a quick look through the previous patch you posted, it looks like some of those changes would certainly help with the nested longjmp issues. Thanks, Andrew ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH] improve python finish breakpoint for exceptions/longjmp case. 2012-09-21 14:58 [PATCH] improve python finish breakpoint for exceptions/longjmp case Andrew Burgess 2012-09-24 10:22 ` Kevin Pouget @ 2012-10-01 16:30 ` Andrew Burgess 2012-10-10 21:08 ` Andrew Burgess 2012-10-11 16:32 ` Jan Kratochvil 2 siblings, 1 reply; 15+ messages in thread From: Andrew Burgess @ 2012-10-01 16:30 UTC (permalink / raw) To: gdb-patches *ping* I did spot that the patch I attached was not the version with the c++ streams -> cstdio change in. I changed my mind about including this change but forgot to delete the line from my email. The rest should still be true. Thanks, Andrew On 21/09/2012 3:57 PM, Andrew Burgess wrote: > The documentation for the python FinishBreakpoints suggests that when a longjmp or c++ exception is used to terminate a function the out_of_scope method will be called. A quick look inside gdb/python/py-finishbreakpoint.c shows that we take no action to ensure that we will break on exceptions or longjmps (for example using breakpoint.c:set_longjmp_breakpoint), instead if we leave a function using for longjmp/exception we rely on hitting some other stop point to trigger the call to the out_of_scope method. > > A further issue is that the testing for FinishBreakpoints, in gdb/testsuite/gdb.python/py-finish-breakpoint2.exp, the test action titled "check FinishBreakpoint in catch()" expects the "stop" method to fire rather than the "out_of_scope" method, this is due to the generated code (on x86 and maybe other targets), the first breakpoint we hit after throwing the exception happens to be the finish breakpoint, however this is not guaranteed, and means that (a) the test does not match the documentation, and (b) the test is platform specific. > > I have a patch below that improves and extends the testing to cover more cases, and a patch for py-finishbreakpoint.c that goes some way to fixing some of the problems. > > There are a couple of comments to go with the patch, > > 1. I've changed the c++ test program from using c++ streams to using cstdio. I have to confess self interest here, on my local target the c++ streams are too large to build in, so any test that uses them will not work for me. I'm not the only target for which this is the case, see the comments in gdb/testsuite/lib/gdb.exp. Though we _obviously_ have to have good test coverage for gdb with c++ streams, I think it would be a shame if some important behaviours are only tested on targets that support c++ streams. However, if this is a problem then I can change the test back to using c++ streams. > > 2. The patch does not solve all the problems with FinishBreakpoints. I use set_longjmp_breakpoint to create the breakpoints for the lonjmp/exceptions, however, between creating an instance of FinishBreakpoint, when the breakpoints are created, and hitting these breakpoints, we are free to issue any commands we like, including things like step/finish/etc all of which use set_longjmp_breakpoint themselves. As these commands (step/finish/etc) complete, they remove the longjmp breakpoints, preventing the FinishBreakpoint from stopping correctly. This behaviour is tested in my updated test and currently kfails; before committing this patch I'll raise a defect and fill in the new bug id. > > I have some ideas about how to fix #2 but I'm open to suggestions. As the gdb is no worse than it was in case #2, and is significantly better in other cases, I'd like to push this patch now (given that I'll raise a bug for broken case). > > Let me know what you think, OK to commit? > > Thanks, > Andrew > > gdb/ChangeLog > > 2012-09-21 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. > (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. > > diff --git a/gdb/python/py-finishbreakpoint.c b/gdb/python/py-finishbreakpoint.c > index 56ab775..a2cd980 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 (inferior_thread ()->num); > } > if (except.reason < 0) > { > @@ -295,11 +302,13 @@ 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); > } > GDB_PY_SET_HANDLE_EXCEPTION (except); > > self_bpfinish->py_bp.bp->frame_id = frame_id; > self_bpfinish->py_bp.is_finish_bp = 1; > + self_bpfinish->initiating_frame = get_frame_id (frame); > > /* Bind the breakpoint with the current program space. */ > self_bpfinish->py_bp.bp->pspace = current_program_space; > @@ -329,6 +338,7 @@ bpfinishpy_out_of_scope (struct finish_breakpoint_object *bpfinish_obj) > gdbpy_print_stack (); > } > > + delete_longjmp_breakpoint_at_next_stop (inferior_thread ()->num); > delete_breakpoint (bpfinish_obj->py_bp.bp); > } > > @@ -355,9 +365,11 @@ bpfinishpy_detect_out_scope_cb (struct breakpoint *b, void *args) > { > 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) > > 2012-09-21 Andrew Burgess <aburgess@broadcom.com> > > * 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. > > 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..f1403d9 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-ID" "*-*-*" > +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" > > > > ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH] improve python finish breakpoint for exceptions/longjmp case. 2012-10-01 16:30 ` Andrew Burgess @ 2012-10-10 21:08 ` Andrew Burgess 2012-10-11 6:46 ` Phil Muldoon 0 siblings, 1 reply; 15+ messages in thread From: Andrew Burgess @ 2012-10-10 21:08 UTC (permalink / raw) To: gdb-patches *ping* *again* In Summary, - python finish breakpoint, documentation looks good. - implementation does not match documentation in exception/longjmp case - test case does not match documentation - broken test relies on target implementation of exceptions - this patch fixes some, but not all of the issues - as far as I know I introduce no new issues - will raise a bug for one issue I know about before I commit this patch, and fill in bug id. Anyone interested? Thanks, Andrew On 01/10/2012 5:30 PM, Andrew Burgess wrote: > *ping* > > I did spot that the patch I attached was not the version with the c++ > streams -> cstdio change in. I changed my mind about including this > change but forgot to delete the line from my email. > > The rest should still be true. > > Thanks, > > Andrew > > > On 21/09/2012 3:57 PM, Andrew Burgess wrote: >> The documentation for the python FinishBreakpoints suggests that when a longjmp or c++ exception is used to terminate a function the out_of_scope method will be called. A quick look inside gdb/python/py-finishbreakpoint.c shows that we take no action to ensure that we will break on exceptions or longjmps (for example using breakpoint.c:set_longjmp_breakpoint), instead if we leave a function using for longjmp/exception we rely on hitting some other stop point to trigger the call to the out_of_scope method. >> >> A further issue is that the testing for FinishBreakpoints, in gdb/testsuite/gdb.python/py-finish-breakpoint2.exp, the test action titled "check FinishBreakpoint in catch()" expects the "stop" method to fire rather than the "out_of_scope" method, this is due to the generated code (on x86 and maybe other targets), the first breakpoint we hit after throwing the exception happens to be the finish breakpoint, however this is not guaranteed, and means that (a) the test does not match the documentation, and (b) the test is platform specific. >> >> I have a patch below that improves and extends the testing to cover more cases, and a patch for py-finishbreakpoint.c that goes some way to fixing some of the problems. >> >> There are a couple of comments to go with the patch, >> >> 1. I've changed the c++ test program from using c++ streams to using cstdio. I have to confess self interest here, on my local target the c++ streams are too large to build in, so any test that uses them will not work for me. I'm not the only target for which this is the case, see the comments in gdb/testsuite/lib/gdb.exp. Though we _obviously_ have to have good test coverage for gdb with c++ streams, I think it would be a shame if some important behaviours are only tested on targets that support c++ streams. However, if this is a problem then I can change the test back to using c++ streams. >> >> 2. The patch does not solve all the problems with FinishBreakpoints. I use set_longjmp_breakpoint to create the breakpoints for the lonjmp/exceptions, however, between creating an instance of FinishBreakpoint, when the breakpoints are created, and hitting these breakpoints, we are free to issue any commands we like, including things like step/finish/etc all of which use set_longjmp_breakpoint themselves. As these commands (step/finish/etc) complete, they remove the longjmp breakpoints, preventing the FinishBreakpoint from stopping correctly. This behaviour is tested in my updated test and currently kfails; before committing this patch I'll raise a defect and fill in the new bug id. >> >> I have some ideas about how to fix #2 but I'm open to suggestions. As the gdb is no worse than it was in case #2, and is significantly better in other cases, I'd like to push this patch now (given that I'll raise a bug for broken case). >> >> Let me know what you think, OK to commit? >> >> Thanks, >> Andrew >> >> gdb/ChangeLog >> >> 2012-09-21 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. >> (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. >> >> diff --git a/gdb/python/py-finishbreakpoint.c b/gdb/python/py-finishbreakpoint.c >> index 56ab775..a2cd980 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 (inferior_thread ()->num); >> } >> if (except.reason < 0) >> { >> @@ -295,11 +302,13 @@ 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); >> } >> GDB_PY_SET_HANDLE_EXCEPTION (except); >> >> self_bpfinish->py_bp.bp->frame_id = frame_id; >> self_bpfinish->py_bp.is_finish_bp = 1; >> + self_bpfinish->initiating_frame = get_frame_id (frame); >> >> /* Bind the breakpoint with the current program space. */ >> self_bpfinish->py_bp.bp->pspace = current_program_space; >> @@ -329,6 +338,7 @@ bpfinishpy_out_of_scope (struct finish_breakpoint_object *bpfinish_obj) >> gdbpy_print_stack (); >> } >> >> + delete_longjmp_breakpoint_at_next_stop (inferior_thread ()->num); >> delete_breakpoint (bpfinish_obj->py_bp.bp); >> } >> >> @@ -355,9 +365,11 @@ bpfinishpy_detect_out_scope_cb (struct breakpoint *b, void *args) >> { >> 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) >> >> 2012-09-21 Andrew Burgess <aburgess@broadcom.com> >> >> * 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. >> >> 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..f1403d9 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-ID" "*-*-*" >> +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" >> >> >> >> > > > > > ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH] improve python finish breakpoint for exceptions/longjmp case. 2012-10-10 21:08 ` Andrew Burgess @ 2012-10-11 6:46 ` Phil Muldoon 0 siblings, 0 replies; 15+ messages in thread From: Phil Muldoon @ 2012-10-11 6:46 UTC (permalink / raw) To: gdb-patches, Kevin Pouget, Jan Kratochvil, aburgess On 10/10/2012 10:07 PM, Andrew Burgess wrote: > *ping* *again* > > In Summary, > > - python finish breakpoint, documentation looks good. > - implementation does not match documentation in exception/longjmp case > - test case does not match documentation > - broken test relies on target implementation of exceptions > - this patch fixes some, but not all of the issues > - as far as I know I introduce no new issues > - will raise a bug for one issue I know about before I commit this patch, and fill in bug id. > > > Anyone interested? > > Thanks, > Andrew The patch looks fine to me, but I cannot give you permission to commit your work. I have cc'd kevin (the original author), and a maintainer I picked by random. Cheers Phil ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH] improve python finish breakpoint for exceptions/longjmp case. 2012-09-21 14:58 [PATCH] improve python finish breakpoint for exceptions/longjmp case Andrew Burgess 2012-09-24 10:22 ` Kevin Pouget 2012-10-01 16:30 ` Andrew Burgess @ 2012-10-11 16:32 ` Jan Kratochvil 2012-10-15 20:40 ` Andrew Burgess 2 siblings, 1 reply; 15+ messages in thread From: Jan Kratochvil @ 2012-10-11 16:32 UTC (permalink / raw) To: Andrew Burgess; +Cc: gdb-patches 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: 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. 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. Num Type Disp Enb Address What 2 breakpoint keep y 0x00000000004006a3 in g at 5.c:9 breakpoint already hit 1 time 3 breakpoint del y 0x00000000004006e2 in f at 5.c:18 thread 1 stop only in thread 1 Id Target Id Frame 2 Thread 0x7ffff7807700 (LWP 31034) "5" 0x0000000000400706 in start (arg=0x0) at 5.c:23 * 1 Thread 0x7ffff7fe5740 (LWP 31028) "5" g (n=0) at 5.c:9 [Switching to Thread 0x7ffff7807700 (LWP 31034)] Breakpoint 2, g (n=1) at 5.c:9 9 var[n] = 1; MyFinishBreakpoint out of scope None (gdb) info breakpoints Num Type Disp Enb Address What 2 breakpoint keep y 0x00000000004006a3 in g at 5.c:9 breakpoint already hit 2 times (gdb) ------------------------------------------------------------------------------ #0 delete_breakpoint (bpt=0x23ee430) at breakpoint.c:13487 #1 in bpfinishpy_out_of_scope (bpfinish_obj=0x21ee130) at ./python/py-finishbreakpoint.c:332 #2 in bpfinishpy_detect_out_scope_cb (b=0x23ee430, args=0x224b550) at ./python/py-finishbreakpoint.c:361 #3 in iterate_over_breakpoints (callback=0x6533d2 <bpfinishpy_detect_out_scope_cb>, data=0x224b550) at breakpoint.c:15639 #4 in bpfinishpy_handle_stop (bs=0x22b6160, print_frame=1) at ./python/py-finishbreakpoint.c:383 #5 in observer_normal_stop_notification_stub (data=0x653518 <bpfinishpy_handle_stop>, args_data=0x7fffffffd3f0) at observer.inc:36 #6 in generic_observer_notify (subject=0x214e130, args=0x7fffffffd3f0) at observer.c:167 #7 in observer_notify_normal_stop (bs=0x22b6160, print_frame=1) at observer.inc:61 #8 in normal_stop () at infrun.c:6135 ------------------------------------------------------------------------------ set height 0 set width 0 file ./5 start set confirm off source cmd.py break g continue python finishbp = MyFinishBreakpoint (gdb.newest_frame ()) info break info thread continue python print finishbp.return_value ------------------------------------------------------------------------------ class MyFinishBreakpoint (gdb.FinishBreakpoint): def __init__(self, frame): gdb.FinishBreakpoint.__init__ (self, frame) print "MyFinishBreakpoint init" def stop(self): print "MyFinishBreakpoint stop" print "return_value is: %d" % int (self.return_value) gdb.execute ("where 1") gdb.execute ("into thread") return True def out_of_scope(self): print "MyFinishBreakpoint out of scope" ------------------------------------------------------------------------------ #include <pthread.h> #include <assert.h> #include <unistd.h> static volatile int var[2]; void g (int n) { var[n] = 1; while (!var[!n]); } int v; void f (int n) { g (n); v++; } static void *start (void *arg) { while (!var[0]); f (1); return arg; } int main (void) { pthread_t thread1; int i; i = pthread_create (&thread1, NULL, start, NULL); assert (i == 0); f (0); i = pthread_join (thread1, NULL); assert (i == 0); return 0; } ------------------------------------------------------------------------------ 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) 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) ==28706== by 0x769E39: generic_observer_notify (observer.c:167) ==28706== by 0x76B3CD: observer_notify_inferior_exit (observer.inc:987) ==28706== by 0x84E7AD: exit_inferior_1 (inferior.c:260) ==28706== by 0x84E840: exit_inferior (inferior.c:279) ==28706== by 0x758F74: generic_mourn_inferior (target.c:3645) ==28706== by 0x5DE3BF: inf_ptrace_mourn_inferior (inf-ptrace.c:181) ==28706== by 0x5EB78C: linux_nat_mourn_inferior (linux-nat.c:4105) ==28706== by 0x757ADF: target_mourn_inferior (target.c:2804) ==28706== by 0x713440: handle_inferior_event (infrun.c:3392) ==28706== by 0x711DA3: wait_for_inferior (infrun.c:2704) ==28706== by 0x71105D: proceed (infrun.c:2285) ==28706== by 0x7092C9: continue_1 (infcmd.c:736) ==28706== by 0x709540: continue_command (infcmd.c:828) ==28706== by 0x624DA1: do_cfunc (cli-decode.c:114) ==28706== by 0x627E39: cmd_func (cli-decode.c:1846) ==28706== by 0x82541C: execute_command (top.c:486) ==28706== by 0x732E97: command_handler (event-top.c:429) ==28706== by 0x733482: command_line_handler (event-top.c:630) ==28706== by 0x87AEB1: rl_callback_read_char (callback.c:220) ==28706== by 0x7329C8: rl_callback_read_char_wrapper (event-top.c:163) ==28706== by 0x732DAE: stdin_event_handler (event-top.c:369) ==28706== by 0x731930: handle_file_event (event-loop.c:827) ==28706== by 0x730DC5: process_event (event-loop.c:401) ==28706== by 0x730E71: gdb_do_one_event (event-loop.c:453) ==28706== by 0x730EE7: start_event_loop (event-loop.c:490) ==28706== by 0x7329F2: cli_command_loop (event-top.c:176) ==28706== by 0x7298BE: current_interp_command_loop (interps.c:332) ==28706== by 0x72A210: captured_command_loop (main.c:226) ==28706== by 0x7285D9: catch_errors (exceptions.c:546) ==28706== by 0x72B6BE: captured_main (main.c:999) ==28706== by 0x7285D9: catch_errors (exceptions.c:546) ==28706== by 0x72B70A: gdb_main (main.c:1008) ==28706== by 0x48B945: main (gdb.c:34) ==28706== by 0x48B945: main (gdb.c:34) ==28706== Address 0x0 is not stack'd, malloc'd or (recently) free'd Thanks, Jan ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH] improve python finish breakpoint for exceptions/longjmp case. 2012-10-11 16:32 ` Jan Kratochvil @ 2012-10-15 20:40 ` Andrew Burgess 2012-10-17 16:28 ` Jan Kratochvil 0 siblings, 1 reply; 15+ messages in thread From: Andrew Burgess @ 2012-10-15 20:40 UTC (permalink / raw) To: Jan Kratochvil; +Cc: gdb-patches 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" ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH] improve python finish breakpoint for exceptions/longjmp case. 2012-10-15 20:40 ` Andrew Burgess @ 2012-10-17 16:28 ` Jan Kratochvil 2012-10-24 20:48 ` Andrew Burgess 2012-10-25 19:23 ` Andrew Burgess 0 siblings, 2 replies; 15+ messages in thread From: Jan Kratochvil @ 2012-10-17 16:28 UTC (permalink / raw) To: Andrew Burgess; +Cc: gdb-patches Hello Andrew, I have found a patch with some goals similar has been already posted here: http://sourceware.org/ml/gdb-patches/2012-01/msg00267.html (Never replied.) But I find the patch below as a more appropriate approach. On Mon, 15 Oct 2012 22:39:52 +0200, Andrew Burgess wrote: > On 11/10/2012 5:32 PM, Jan Kratochvil wrote: > > 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. Nothing; I have read now the former thread and I understand now that the intention was to catch function finish even after very arbitrary GDB commands happen in the meantime. Hooking to finish_command would no longer track the finish situation in such case. > 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); I find too intrusive to call set_longjmp_breakpoint here. A countercase - I did not try to reproduce it in real: * You have breakpoint installed at TRACEDFUNC and you automatically use Python finish breakpoint to trace return values of TRACEDFUNC. * User at CALLERFUNC will type in GDB CLI "finish". * CALLERFUNC does a lot of processing and it also calls TRACEDFUNC. * Now you overwide tp->INITIATING_FRAME of the user "finish" command by null_frame_id which breaks the behavior in some way. I believe you just want non-stopping notification when the breakpoint went out of scope. This is exactly what was needed in a recently checked in patch: commit 95689df3ff480400019264b3e031de0967f3c8f8 Author: Jan Kratochvil <jan.kratochvil@redhat.com> Date: Mon Jun 18 17:28:33 2012 +0000 Remove stale dummy frames. You want to install the "longjmp breakpoint" there by set_longjmp_breakpoint_for_call_dummy. You want to hook there check_longjmp_breakpoint_for_call_dummy to call bpfinishpy_detect_out_scope_cb in some way. Currently you do it on stop but that is too late, breakpoint may may have been for example placed at stack trampoline function (code at the stack) and the breakpoint instruction now corrupts live stack data there. Please correct me if I have missed something in your patch. You may want to rename some GDB symbols from "call_dummy" to something more generic. If you do so (I do not require it) please make it a separate rename-only patch. I will have to do a second review then as it will change the patch a bit. > + > + /* 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; > 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. Please use some descriptive (I do not mind which specific) short suffix/name but not numbers (2, 3). Numbers are more difficult to later refer to / remember during regressions. [...] > --- /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. */ I used this kind of synchronization to get quickly done a demonstation of the GDB bug. But such hack is not acceptable for a real testcase code. Please use proper pthread_* operations (pthread_barrier_wait in this case I think). Also testcases should not remain hanging indefinitely if something breaks, in this case 'alarm (60);' at the start of 'main' should ensure that I think. > + } > + else > + { > + /* Other thread - Nothing. */ > + } > +} > + [...] > 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 [...] > +# This file is part of the GDB testsuite. > + > +load_lib gdb-python.exp > + > +standard_testfile > +set pyfile ${srcdir}/${subdir}/${testfile}.py Two spaces -> one space. Thanks, Jan ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH] improve python finish breakpoint for exceptions/longjmp case. 2012-10-17 16:28 ` Jan Kratochvil @ 2012-10-24 20:48 ` Andrew Burgess 2012-10-25 6:13 ` Jan Kratochvil 2012-10-25 19:23 ` Andrew Burgess 1 sibling, 1 reply; 15+ messages in thread From: Andrew Burgess @ 2012-10-24 20:48 UTC (permalink / raw) To: Jan Kratochvil; +Cc: gdb-patches Jan, On 17/10/2012 5:27 PM, Jan Kratochvil wrote: > > [...] >> +#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. */ > > Also testcases should not remain hanging indefinitely if something breaks, in > this case 'alarm (60);' at the start of 'main' should ensure that I think. I'm confused why this test would need to take care of shutting itself down, if the test failed would dejagnu not timeout the test, and eventually gdb would exit taking down the inferior as it does? I had a look for other tests that used alarm() but there are only a few and I didn't think any of these were trying to perform a defensive shutdown as you're suggesting, so can you point at any other test that needs to do this sort of thing, or is there something special about this test? Thanks, Andrew ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH] improve python finish breakpoint for exceptions/longjmp case. 2012-10-24 20:48 ` Andrew Burgess @ 2012-10-25 6:13 ` Jan Kratochvil 0 siblings, 0 replies; 15+ messages in thread From: Jan Kratochvil @ 2012-10-25 6:13 UTC (permalink / raw) To: Andrew Burgess; +Cc: gdb-patches On Wed, 24 Oct 2012 22:48:15 +0200, Andrew Burgess wrote: > On 17/10/2012 5:27 PM, Jan Kratochvil wrote: > > Also testcases should not remain hanging indefinitely if something breaks, in > > this case 'alarm (60);' at the start of 'main' should ensure that I think. > > I'm confused why this test would need to take care of shutting itself down, > if the test failed would dejagnu not timeout the test, and eventually gdb > would exit taking down the inferior as it does? For exampe if GDB crashes the inferior process is left running. > I had a look for other tests that used alarm() but there are only a few and I > didn't think any of these were trying to perform a defensive shutdown as you're > suggesting, so can you point at any other test that needs to do this sort of > thing, or is there something special about this test? This is a general problem of the GDB testsuite, this is why Fedora uses http://pkgs.fedoraproject.org/cgit/gdb.git/tree/gdb-orphanripper.c wrapper for running the testsuite to kill any leftover running processes. Otherwise if you do some 'make check &>file' it will hang forever as a single leftover process will still have its STDOUT/STDERR open blocking the finish. If the whole GDB testsuite was written properly this 'orphanripper' wrapper was not needed. Currently FSF GDB does not carry this 'orphanripper' wrapper, for FSF GDB there are possibilities: (1) FSF GDB could adopt this 'orphanripper' wrapper. It is not the right solution but it works. (2) Someone could review all the testsuite/*/*.c (and even *.exp) files and protect them properly. This is easy work so it should be doable despite the number of files. (3) We could switch to non-dejagnu testsuite capable of tracking spawned processes on its own (even if GDB crashes). But I am not aware of any such testsuite framework so it would be a custom tool which is equivalent to (1). Thanks, Jan ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH] improve python finish breakpoint for exceptions/longjmp case. 2012-10-17 16:28 ` Jan Kratochvil 2012-10-24 20:48 ` Andrew Burgess @ 2012-10-25 19:23 ` Andrew Burgess 2012-10-30 17:41 ` Jan Kratochvil 1 sibling, 1 reply; 15+ messages in thread From: Andrew Burgess @ 2012-10-25 19:23 UTC (permalink / raw) To: Jan Kratochvil; +Cc: gdb-patches On 17/10/2012 5:27 PM, Jan Kratochvil wrote: > On Mon, 15 Oct 2012 22:39:52 +0200, Andrew Burgess wrote: >> @@ -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); > > I find too intrusive to call set_longjmp_breakpoint here. > > A countercase - I did not try to reproduce it in real: > > * You have breakpoint installed at TRACEDFUNC and you automatically use > Python finish breakpoint to trace return values of TRACEDFUNC. > * User at CALLERFUNC will type in GDB CLI "finish". > * CALLERFUNC does a lot of processing and it also calls TRACEDFUNC. > * Now you overwide tp->INITIATING_FRAME of the user "finish" command by > null_frame_id which breaks the behavior in some way. I don't think this is a problem, the first finish will be cancelled when we stop for the second time in TRACEDFUNC. So, I think the chain of events will be: - Stop in TRACEDFUNC, create a finish breakpoint setting tp->INITIATING_FRAME to null_frame_id. - From the cli use "finish" command, change tp->INITIATING_FRAME. - User continues. - Recursively enter TRACEDFUNC, stopping. The finish breakpoint is now cancelled. At this point the first finish breakpoint is also cancelled, but this is a known bug at this point that I plan to work on later; and is no worse than current behaviour. - User creates new finish breakpoint, setting tp->INITIATING_FRAME, but that's fine as we have no "finish" in play at this point. Let me know if I've got this wrong and you can see a problem, especially if you think I've broken /other/ commands, that would be worse than just leaving the finish breakpoint stuff with a few broken edge cases. > You want to install the "longjmp breakpoint" there by > set_longjmp_breakpoint_for_call_dummy. You want to hook there > check_longjmp_breakpoint_for_call_dummy to call bpfinishpy_detect_out_scope_cb > in some way. Currently you do it on stop but that is too late, breakpoint may > may have been for example placed at stack trampoline function (code at the > stack) and the breakpoint instruction now corrupts live stack data there. Hmmm, I see the problem, I'll work on that one. Thanks for your time, Andrew ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH] improve python finish breakpoint for exceptions/longjmp case. 2012-10-25 19:23 ` Andrew Burgess @ 2012-10-30 17:41 ` Jan Kratochvil 2012-11-06 14:24 ` Andrew Burgess 0 siblings, 1 reply; 15+ messages in thread From: Jan Kratochvil @ 2012-10-30 17:41 UTC (permalink / raw) To: Andrew Burgess; +Cc: gdb-patches On Thu, 25 Oct 2012 21:23:19 +0200, Andrew Burgess wrote: > On 17/10/2012 5:27 PM, Jan Kratochvil wrote: > > A countercase - I did not try to reproduce it in real: > > > > * You have breakpoint installed at TRACEDFUNC and you automatically use > > Python finish breakpoint to trace return values of TRACEDFUNC. > > * User at CALLERFUNC will type in GDB CLI "finish". > > * CALLERFUNC does a lot of processing and it also calls TRACEDFUNC. > > * Now you overwide tp->INITIATING_FRAME of the user "finish" command by > > null_frame_id which breaks the behavior in some way. > > I don't think this is a problem, the first finish will be cancelled when > we stop for the second time in TRACEDFUNC. So, I think the chain of > events will be: > > - Stop in TRACEDFUNC, create a finish breakpoint setting > tp->INITIATING_FRAME to null_frame_id. > - From the cli use "finish" command, change tp->INITIATING_FRAME. > - User continues. > - Recursively enter TRACEDFUNC, stopping. The finish breakpoint is now > cancelled. At this point the first finish breakpoint is also cancelled, > but this is a known bug at this point that I plan to work on later; and > is no worse than current behaviour. > - User creates new finish breakpoint, setting tp->INITIATING_FRAME, but > that's fine as we have no "finish" in play at this point. > > Let me know if I've got this wrong and you can see a problem, especially > if you think I've broken /other/ commands, that would be worse than just > leaving the finish breakpoint stuff with a few broken edge cases. OK, I see my countercase was not right. Anyway we agree it is not transparent to "finish" anyway but this is more a problem there are no observer-like breakpoints: ==> finish.c <== void g (void) {} void f (void) { g (); } int main (void) { f (); return 0; } ==> finish.cmd <== file ./finish start step break g commands echo hook-g\n continue end finish ------------------- hook-g Actual: [Inferior 1 (process 13204) exited normally] (gdb) _ Expected: main () at finish.c:7 7 return 0; (gdb) _ > > You want to install the "longjmp breakpoint" there by > > set_longjmp_breakpoint_for_call_dummy. You want to hook there > > check_longjmp_breakpoint_for_call_dummy to call bpfinishpy_detect_out_scope_cb > > in some way. Currently you do it on stop but that is too late, breakpoint may > > may have been for example placed at stack trampoline function (code at the > > stack) and the breakpoint instruction now corrupts live stack data there. > > Hmmm, I see the problem, I'll work on that one. Anyway the example above was given to convince you to the cleaner check_longjmp_breakpoint_for_call_dummy solution. Thanks, Jan ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH] improve python finish breakpoint for exceptions/longjmp case. 2012-10-30 17:41 ` Jan Kratochvil @ 2012-11-06 14:24 ` Andrew Burgess 2012-11-22 18:15 ` Jan Kratochvil 0 siblings, 1 reply; 15+ messages in thread From: Andrew Burgess @ 2012-11-06 14:24 UTC (permalink / raw) To: Jan Kratochvil; +Cc: gdb-patches On 30/10/2012 5:40 PM, Jan Kratochvil wrote: > On Thu, 25 Oct 2012 21:23:19 +0200, Andrew Burgess wrote: > > Anyway we agree it is not transparent to "finish" anyway but this is more > a problem there are no observer-like breakpoints: > > ==> finish.c <== > void g (void) {} > void f (void) { > g (); > } > int main (void) { > f (); > return 0; > } > > ==> finish.cmd <== > file ./finish > start > step > break g > commands > echo hook-g\n > continue > end > finish > > ------------------- > > hook-g > Actual: > [Inferior 1 (process 13204) exited normally] > (gdb) _ > Expected: > main () at finish.c:7 > 7 return 0; > (gdb) _ > So currently the finish command stops whenever gdb stops, even though in this case once we stop (at g) we then automatically continue again. In the example is it that you'd like to consider the "g" breakpoint observer-like (just because it contains a continue), or are you suggesting that the "finish" command should not auto-terminate once gdb stops? I can't really see how the above example relates to the issue with my patch, however, I have a new patch in which I believe the level of risk of code corruption should be less. I now create the longjmp breakpoints using the actual frame we want to stop in, not the null_frame_id. This means that when we hit the longjmp we'll immediately stop triggering the out-of-scope callback and deleting the finish breakpoint. This seems like a better solution to me, it's more "finish" like in behaviour. The documentation for "FinishBreakpoint" makes no claim one way or another on if we'll stop for the out-of-scope call; it would be nice if we could control the stop behaviour from that callback, but I'd rather leave that change for another day. As far as I can see the only remaining issue is that using other commands that set/clear the longjmp breakpoints once a FinishBreakpoint is active (finish/step/etc) will remove the longjmp breakpoint, we'll then fallback to only calling the out-of-scope callback at the next stop. To solve this I'd like to change the longjmp breakpoint mechanism to support having multiple longjmp breakpoint sets active at once; but I'd rather do this in a follow-up patch, and as the new behaviour is no worse than the original behaviour this feels like a reasonable compromise to me (but you might disagree). I just remembered you asked me to come up with a better name for the new test than py-finish-breakpoint3, I forgot to do that in the patch version below, but I'll change it to something like py-finish-breakpoint-threads. Let me know what you think, Thanks, Andrew gdb/ChangeLog 2012-11-02 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..4906fac 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 (), init_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-11-02 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..7e89024 --- /dev/null +++ b/gdb/testsuite/gdb.python/py-finish-breakpoint3.c @@ -0,0 +1,148 @@ +/* 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> + +pthread_mutex_t lock_0 = PTHREAD_MUTEX_INITIALIZER; +pthread_mutex_t lock_1 = PTHREAD_MUTEX_INITIALIZER; +pthread_barrier_t barrier; + +void +breakpt (int is_first_thread) +{ + if (is_first_thread) + { + /* Main thread. */ + pthread_mutex_unlock (&lock_0); /* Set thread 2 going. */ + pthread_mutex_lock (&lock_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) +{ + int i; + + i = pthread_mutex_lock (&lock_1); + assert (i == 0); + + i = pthread_barrier_wait (&barrier); + assert (i == 0 || i == PTHREAD_BARRIER_SERIAL_THREAD); + + /* Wait for the first thread to set us going. This lock is initially + held by the main thread. */ + i = pthread_mutex_lock (&lock_0); + assert (i == 0); + + func (0); + + /* Release the first thread to finish. */ + i = pthread_mutex_unlock (&lock_1); + assert (i == 0); + + 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; + + /* Ensure test terminates should gdb crash. */ + alarm (60); + + /* Test 1: Create a child thread, which after some synchronisation will + wait on lock_0, then we call through func into the function breakpt. + The test has gdb stop in breakpt and create a finish breakpoint. + Once the main thread is in breakpt it will wait for the child thread + which then enters and exits the breakpt function. Finally the + threads all join back in main. We're testing the the finish + breakpoint does not fire for the second thread. */ + + i = pthread_barrier_init (&barrier, NULL, 2); + assert (i == 0); + + i = pthread_mutex_lock (&lock_0); + assert (i == 0); + + i = pthread_create (&thread1, NULL, start_owner_test, NULL); + assert (i == 0); + + /* Barrier ensures that lock_0 is held by main thread, and lock_1 is held + by the start_owner_test thread. */ + i = pthread_barrier_wait (&barrier); + assert (i == 0 || i == PTHREAD_BARRIER_SERIAL_THREAD); + + func (1); + + i = pthread_join (thread1, NULL); + assert (i == 0); + + i = pthread_barrier_destroy (&barrier); + assert (i == 0); + + /* End of test 1. */ + + /* Test 2: Have gdb break on entry to start_exit_test function, create + a finish breakpoint then continue. The child thread will then exit, + and so never finish. We're checking that the out-of-scope method of + the finish break point does get called. */ + + i = pthread_create (&thread1, NULL, start_exit_test, NULL); + assert (i == 0); + + i = pthread_join (thread1, NULL); + assert (i == 0); + + final_breakpt (); + + /* End of test 2. */ + + 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..5590759 --- /dev/null +++ b/gdb/testsuite/gdb.python/py-finish-breakpoint3.exp @@ -0,0 +1,145 @@ +# 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_test "continue" \ + "${hex} in start_thread \\(\\) from\[^\r\n\]+\r\nMyFinishBreakpoint out of scope" \ + "detect out of scope when thread exits" + +} + + 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" ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH] improve python finish breakpoint for exceptions/longjmp case. 2012-11-06 14:24 ` Andrew Burgess @ 2012-11-22 18:15 ` Jan Kratochvil 0 siblings, 0 replies; 15+ messages in thread From: Jan Kratochvil @ 2012-11-22 18:15 UTC (permalink / raw) To: Andrew Burgess; +Cc: gdb-patches On Tue, 06 Nov 2012 15:23:49 +0100, Andrew Burgess wrote: > On 30/10/2012 5:40 PM, Jan Kratochvil wrote: > > On Thu, 25 Oct 2012 21:23:19 +0200, Andrew Burgess wrote: > > > > Anyway we agree it is not transparent to "finish" anyway but this is more > > a problem there are no observer-like breakpoints: > > > > ==> finish.c <== > > void g (void) {} > > void f (void) { > > g (); > > } > > int main (void) { > > f (); > > return 0; > > } > > > > ==> finish.cmd <== > > file ./finish > > start > > step > > break g > > commands > > echo hook-g\n > > continue > > end > > finish > > > > ------------------- > > > > hook-g > > Actual: > > [Inferior 1 (process 13204) exited normally] > > (gdb) _ > > Expected: > > main () at finish.c:7 > > 7 return 0; > > (gdb) _ > > > > So currently the finish command stops whenever gdb stops, even though in > this case once we stop (at g) we then automatically continue again. I agree. > In the example is it that you'd like to consider the "g" breakpoint > observer-like (just because it contains a continue), Right, but I see it was only my wish, GDB does not behave that way. > or are you suggesting that the "finish" command should not auto-terminate > once gdb stops? I was suggesting "finish" should continue its "finish" work after the "g" breakpoint commands were run. But I see it does not work that way. Let's forget about this part. > I can't really see how the above example relates to the issue with my patch, however, I agree. > I have a new patch in which I believe the level of risk of code corruption should be less. The problem is it is not as safe as set_longjmp_breakpoint_for_call_dummy which I was suggest to hook into. You are hooked by observer_attach_normal_stop (bpfinishpy_handle_stop); but that does not catch for example a "return" command. ==> 65.c <== static int h (void) { return 1; } static int f (void) { return h (); } static int g (void) { return h (); } int main (void) { f (); g (); return 0; } ==> 65.cmd <== set confirm off set height 0 set width 0 shell rm -f 65 shell gcc -o 65 65.c -Wall -g file ./65 start source 65.py break h continue info break python finishbp = MyFinishBreakpoint (gdb.newest_frame ()) info break return return bt continue info break python print finishbp.return_value ==> 65.py <== class MyFinishBreakpoint (gdb.FinishBreakpoint): def __init__(self, frame): gdb.FinishBreakpoint.__init__ (self, frame) print "MyFinishBreakpoint init" def stop(self): print "MyFinishBreakpoint stop" print "return_value is: %d" % int (self.return_value) return True def out_of_scope(self): print "MyFinishBreakpoint out of scope" ============= and its output: Temporary breakpoint 1 at 0x400511: file 65.c, line 5. Temporary breakpoint 1, main () at 65.c:5 5 f (); Breakpoint 2 at 0x4004f0: file 65.c, line 1. Breakpoint 2, h () at 65.c:1 1 static int h (void) { return 1; } Num Type Disp Enb Address What 2 breakpoint keep y 0x00000000004004f0 in h at 65.c:1 breakpoint already hit 1 time Temporary breakpoint 3 at 0x400500: file 65.c, line 2. MyFinishBreakpoint init Num Type Disp Enb Address What 2 breakpoint keep y 0x00000000004004f0 in h at 65.c:1 breakpoint already hit 1 time 3 breakpoint del y 0x0000000000400500 in f at 65.c:2 thread 1 stop only in thread 1 #0 main () at 65.c:6 Breakpoint 2, h () at 65.c:1 1 static int h (void) { return 1; } Num Type Disp Enb Address What 2 breakpoint keep y 0x00000000004004f0 in h at 65.c:1 breakpoint already hit 2 times 3 breakpoint del y 0x0000000000400500 in f at 65.c:2 thread 1 stop only in thread 1 None (gdb) _ You can see in the end we already exited f() and entered it again but the finish breakpoint still exists, out-of-scope detection did not catch it. Unfortunately set_longjmp_breakpoint_for_call_dummy is a bit dummy frame specific so it would need some extensions to be suitable for this purpose. I believe it would be good to hook also into frame_pop(). > As far as I can see the only remaining issue is that using other commands > that set/clear the longjmp breakpoints once a FinishBreakpoint is active > (finish/step/etc) will remove the longjmp breakpoint, we'll then fallback to > only calling the out-of-scope callback at the next stop. To solve this I'd > like to change the longjmp breakpoint mechanism to support having multiple > longjmp breakpoint sets active at once; You would need a new type besides bp_longjmp and bp_exception. This is why I created that bp_longjmp_call_dummy which I was suggesting to extend also for your purposes. But I am OK with new bp_longjmp_py_finish_bpt or something. I do not like much that you share tp->initiating_frame but I haven't found a countercase reproducer. > but I'd rather do this in a follow-up patch, and as the new behaviour is no > worse than the original behaviour this feels like a reasonable compromise to > me (but you might disagree). I am OK with that, in fact I leave further Python API extensions on the other maintainers. [...] > @@ -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 (), init_frame_id); Here should be prev_frame_id instead. I find it maybe a bit confusing but it is so, check 'case 2' in infrun.c 'case BPSTAT_WHAT_CLEAR_LONGJMP_RESUME'. finish_command puts there the previous frame of where you execute the "finish" command. Countercase: ==> 64.c <== #include <setjmp.h> static jmp_buf env; static int h (void) { if (setjmp (env)) return 1; longjmp (env, 1); return 2; } int main (void) { return h (); } ==> 64.cmd <== set confirm off set height 0 set width 0 shell rm -f 64 shell gcc -o 64 64.c -Wall -g file ./64 start source 64.py break h continue info break python finishbp = MyFinishBreakpoint (gdb.newest_frame ()) info break continue python print finishbp.return_value ==> 64.py <== class MyFinishBreakpoint (gdb.FinishBreakpoint): def __init__(self, frame): gdb.FinishBreakpoint.__init__ (self, frame) print "MyFinishBreakpoint init" def stop(self): print "MyFinishBreakpoint stop" print "return_value is: %d" % int (self.return_value) return True def out_of_scope(self): print "MyFinishBreakpoint out of scope" ============= and its output: Temporary breakpoint 1 at 0x4005aa: file 64.c, line 9. Temporary breakpoint 1, main () at 64.c:9 9 int main (void) { return h (); } Breakpoint 2 at 0x400580: file 64.c, line 4. Breakpoint 2, h () at 64.c:4 4 if (setjmp (env)) Num Type Disp Enb Address What 2 breakpoint keep y 0x0000000000400580 in h at 64.c:4 breakpoint already hit 1 time Temporary breakpoint 3 at 0x4005af: file 64.c, line 9. MyFinishBreakpoint init Num Type Disp Enb Address What 2 breakpoint keep y 0x0000000000400580 in h at 64.c:4 breakpoint already hit 1 time 3 breakpoint del y 0x00000000004005af in main at 64.c:9 thread 1 stop only in thread 1 0x000000000040058a in h () at 64.c:4 4 if (setjmp (env)) None (gdb) _ Existence of the Python FinishBreakpoint causes a stop during longjmp inside h itself but that should not be caught, the function still has not returned. > + > + /* 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; Thanks, Jan ^ permalink raw reply [flat|nested] 15+ messages in thread
end of thread, other threads:[~2012-11-22 18:15 UTC | newest] Thread overview: 15+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2012-09-21 14:58 [PATCH] improve python finish breakpoint for exceptions/longjmp case Andrew Burgess 2012-09-24 10:22 ` Kevin Pouget 2012-10-01 16:33 ` Andrew Burgess 2012-10-01 16:30 ` Andrew Burgess 2012-10-10 21:08 ` Andrew Burgess 2012-10-11 6:46 ` Phil Muldoon 2012-10-11 16:32 ` Jan Kratochvil 2012-10-15 20:40 ` Andrew Burgess 2012-10-17 16:28 ` Jan Kratochvil 2012-10-24 20:48 ` Andrew Burgess 2012-10-25 6:13 ` Jan Kratochvil 2012-10-25 19:23 ` Andrew Burgess 2012-10-30 17:41 ` Jan Kratochvil 2012-11-06 14:24 ` Andrew Burgess 2012-11-22 18:15 ` Jan Kratochvil
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox