Re: [RFC] Python Finish Breakpoints

Kevin Pouget
2011-05-30T09:29:23Z

[-- Attachment #1: Type: text/plain, Size: 5398 bytes --]

> Kevin> breakpoint.h:
> Kevin> enum py_bp_type
> Kevin>   {
> Kevin>     py_bp_none,         /* No Python object.  */
>
> I don't think this one is needed.
>
> Kevin>     py_bp_standard,     /* Ordinary breakpoint object.  */
> Kevin>     py_bp_finish        /* FinishBreakpoint object.  */
>
> These should be uppercase, but it seems to me that if there are just 2
> states you might as well use an ordinary boolean(-ish) flag.

OK, I wanted to let a room free for further Python-specific breakpoint
handling, but if you feel like it's not necessary ...
I changed it to "int is_py_finish_bp"

> Kevin> as per your two comments, I now only store the `struct type'  of the
> Kevin> function and the return value,
>
> You need to store a gdb.Type wrapper.
> A 'struct type' can also be invalidated when an objfile is destroyed.

I wouldn't mind, but I can't see how gdb.Type ensures validity, as far
as I've seen, there is no "is_valid" method and I can't and no further
verification during the Python -> C translation:

struct type *
type_object_to_type (PyObject *obj)
{
if (! PyObject_TypeCheck (obj, &type_object_type))
return NULL;
return ((type_object *) obj)->type;
}

> Kevin> diff --git a/gdb/testsuite/gdb.python/py-finish-breakpoint-cc.exp b/gdb/testsuite/gdb.python/py-finish-breakpoint-cc.exp
>
> Tom> Funny file name.
>
> Kevin> funny but correct, or too funny? ;)
>
> It is more usual in the gdb test suite to give the .cc and .exp files
> the same base name.

so py-finish-breakpoint2.{exp,cc,py} should look more serious!

>> Other cases to consider are -
>> * inferior exec
>> * inferior exit
>> * explicit inferior function call
>> * "return" command
> Kevin> I'll work on the tests for the next version of the patch ("return"
> Kevin> should already be covered)
>
> I will wait for this to do more review.

these situations should now be covered in `'py-finish-breakpoint.exp"

> Kevin> @defivar FinishBreakpoint out_of_scope_notif
> Kevin> This attribute will be @code{True} until the @code{out_of_scope} method has
> Kevin> been called and @code{False} afterwards. This attribute is writeable, so out
> Kevin> of scope notifications can be re-enabled.
> Kevin> @end defivar
>
> I still don't really understand under what circumstances it is useful
> for a program to set this attribute.
>
> Kevin> - avoid calling `out_of_scope' every normal_stop when the breakpoint
> Kevin> is not anymore in the callstack
>
> I think it would be ok to just leave this up to the subclass to handle.

I thought about just getting rid of this part of the patch, but it
really seem important to me.
Here is a (pseudo-code) example to try to demonstrate that:

function inner (param) {
if (param)
return 1
else
throw exception
}

function outter (param) {
return innter(param)
}

function main () {
try:
outter (TRUE)
except:
pass

try:
outter (FALSE)
except:
pass

try:
outter (TRUE)
except:
pass
}

in a Python script, you want to know the return value of OUTTER (in a
script --> you don't know the actual shape of the Main function -- for
instance think about the 'write(2)` function which returns the size of
the data actually written)

you'll need one gdb.Breakpoint and several gdb.FinishBreakpoint:

class OutterBreakpoint(gdb.Breakpoint):
def __init__(self):
gdb.Breakpoint.__init__(self, "outter", internal=True)
self.silent = True

def stop():
OutterFinishBreakpoint(gdb.newest_frame())
return False

class OutterFinishBreakpoint(gdb.FinishBreakpoint):
def stop():
print "outter finished with :", self.return_value
self.enable = False
gdb.post_event(self.delete)

def out_of_scope()
self.enable = False
gdb.post_event(self.delete)

This `out_of_scope' function is the easiest way to cleanup the
FinishBreakpoint when the finished function didn't return correctly
(and is only useful in this situation, I admit)

> Kevin> - allow the script to re-activate notification when it wants to
> Kevin> 're-use' the FinishBreakpoint (instead of deleting it / creating a new
> Kevin> one)
>
> I am not sure when this makes sense.

that's the opposite situation, you know know the function will only be
triggered from one (limitted set of) place, so you don't want to
create/delete BPs each time:

class OutterBreakpoint(gdb.Breakpoint):
...
def stop():
if self.finish is None:
self.finish = OutterFinishBreakpoint(gdb.newest_frame())
return False

class OutterFinishBreakpoint(gdb.FinishBreakpoint):
...
def stop():
#don't delete
#treat normal termination

def out_of_scope():
#treat exception termination
self.out_of_scope_notif = True # allows to catch further
exception termination

I also added a verification which checks that `out_of_scope' is only
trigged for the inferior for the inferior which owns the breakpoint:

+bpfinishpy_detect_out_scope_cb (struct breakpoint *b, void *args)
...
+ else if (finish_bp->out_of_scope_notif
+ && b->pspace == current_inferior()->pspace
+ && (!target_has_registers || frame_find_by_id(b->frame_id) == NULL))
+ {
+ bpfinishpy_out_of_scope (finish_bp);
+ }

thanks,

Kevin

[-- Attachment #2: finish_bp2.txt --]
[-- Type: text/plain, Size: 56570 bytes --]

diff --git a/gdb/Makefile.in b/gdb/Makefile.in
index 5bab360..2507938 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -285,6 +285,7 @@ SUBDIR_PYTHON_OBS = \
py-evtregistry.o \
py-evts.o \
py-exitedevent.o \
+ py-finishbreakpoint.o \
py-frame.o \
py-function.o \
py-inferior.o \
@@ -315,6 +316,7 @@ SUBDIR_PYTHON_SRCS = \
python/py-evtregistry.c \
python/py-evts.c \
python/py-exitedevent.c \
+ python/py-finishbreakpoint.c \
python/py-frame.c \
python/py-function.c \
python/py-inferior.c \
@@ -2062,6 +2064,10 @@ py-exitedevent.o: $(srcdir)/python/py-exitedevent.c
$(COMPILE) $(PYTHON_CFLAGS) $(srcdir)/python/py-exitedevent.c
$(POSTCOMPILE)

+py-finishbreakpoint.o: $(srcdir)/python/py-finishbreakpoint.c
+ $(COMPILE) $(PYTHON_CFLAGS) $(srcdir)/python/py-finishbreakpoint.c
+ $(POSTCOMPILE)
+
py-frame.o: $(srcdir)/python/py-frame.c
$(COMPILE) $(PYTHON_CFLAGS) $(srcdir)/python/py-frame.c
$(POSTCOMPILE)
diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
index 7093104..7a3da26 100644
--- a/gdb/breakpoint.c
+++ b/gdb/breakpoint.c
@@ -5814,6 +5814,7 @@ set_raw_breakpoint_without_location (struct gdbarch *gdbarch,
b->ops = NULL;
b->condition_not_parsed = 0;
b->py_bp_object = NULL;
+ b->is_py_finish_bp = 0;
b->related_breakpoint = b;

/* Add this breakpoint to the end of the chain so that a list of
diff --git a/gdb/breakpoint.h b/gdb/breakpoint.h
index 7fa705f..bb45512 100644
--- a/gdb/breakpoint.h
+++ b/gdb/breakpoint.h
@@ -654,6 +654,9 @@ struct breakpoint
types are tracked by the Python scripting API. */
struct breakpoint_object *py_bp_object;

+ /* TRUE is py_bp_object is a gdb.FinishBreakpoint object . */
+ int is_py_finish_bp;
+
/* Whether this watchpoint is exact (see target_exact_watchpoints). */
int exact;

diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 584a520..972b608 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -23513,6 +23513,70 @@ commands, separated by newlines. If there are no commands, this
attribute is @code{None}. This attribute is not writable.
@end defivar

+@subsubsection Finish Breakpoints
+
+@cindex python finish breakpoints
+@tindex gdb.FinishBreakpoint
+
+A @fdb{finish breakpoint} is a breakpoint set at the return address of
+a frame, based on the "finish command. @code{gdb.FinishBreakpoint} extends
+@code{gdb.Breakpoint}
+
+@defmethod FinishBreakpoint __init__ frame @r{[}internal@r{]}
+Create a FinishBreakpoint at the return of the @code{gdb.Frame} object
+@var{frame}. The optional @var{internal} argument allows the breakpoint
+to become invisible to the user. @xref{Breakpoints In Python}, for further
+details about this argument.
+@end defmethod
+
+@defop Operation {gdb.Breakpoint} out_of_scope (self)
+In some circonstances (eg, @code{longjmp}, C++ exceptions, @value{GDBN}
+@code{return} command, ...), a function may not properly terminate, and thus
+never hit a @{code} FinishBreakpoint. When @value{GDBN} notices such a
+situation, the @code{out_of_scope} function will be triggered.
+
+This method will only be called if the @code{out_of_scope_notif} attribute
+is @code{True}.
+
+Deleting (@code{gdb.Breakpoint.delete ()}) the @code{FinishBreakpoint} is
+allowed in this function.
+
+You may want to sub-class the @code{gdb.FinishBreakpoint} and implement the
+@code{out_of_scope} method:
+
+@smallexample
+class MyFinishBreakpoint (gdb.FinishBreakpoint)
+ def stop (self):
+ print "normal finish"
+ return True
+
+ def out_of_scope ():
+ print "abnormal finish"
+@end smallexample
+@end defop
+
+@defmethod FinishBreakpoint check_scope
+Because @value{GDBN} currently only checks if @code{FinishBreakpoint}s ran
+out of scope at normal stops, this method allows to force the verification
+and trigger the @code{out_of_scope} method if necessary. It will return
+@code{True} if @code{out_of_scope ()} was triggered, @code{False} otherwise.
+@end defmethod
+
+
+@defivar FinishBreakpoint out_of_scope_notif
+This attribute will be @code{True} until the @code{out_of_scope} method has
+been called and @code{False} afterwards. This attribute is writeable, so out
+of scope notifications can be re-enabled.
+@end defivar
+
+@defivar FinishBreakpoint return_value
+When @value{GDBN} is stopped at a @code{FinishBreakpoint}, and the frame
+used to build the @code{FinishBreakpoint} had debug symbols, this attribute
+will contain a @code{gdb.Value} object corresponding to the return value of
+the function. The value will be @code{None} if the value was not computable.
+This attribute is not writable.
+@end defivar
+
@node Lazy Strings In Python
@subsubsection Python representation of lazy strings.

diff --git a/gdb/infcmd.c b/gdb/infcmd.c
index be1f0a5..2d19ea9 100644
--- a/gdb/infcmd.c
+++ b/gdb/infcmd.c
@@ -1378,14 +1378,12 @@ advance_command (char *arg, int from_tty)
until_break_command (arg, from_tty, 1);
}
\f
-/* Print the result of a function at the end of a 'finish' command. */
+/* Returns the value of the result at the end of a 'finish' command/BP. */

-static void
-print_return_value (struct type *func_type, struct type *value_type)
+struct value *
+get_return_value (struct type *func_type, struct type *value_type)
{
struct gdbarch *gdbarch = get_regcache_arch (stop_registers);
- struct cleanup *old_chain;
- struct ui_stream *stb;
struct value *value;

CHECK_TYPEDEF (value_type);
@@ -1415,6 +1413,18 @@ print_return_value (struct type *func_type, struct type *value_type)
internal_error (__FILE__, __LINE__, _("bad switch"));
}

+ return value;
+}
+
+/* Print the result of a function at the end of a 'finish' command. */
+
+static void
+print_return_value (struct type *func_type, struct type *value_type)
+{
+ struct value *value = get_return_value (func_type, value_type);
+ struct cleanup *old_chain;
+ struct ui_stream *stb;
+
if (value)
{
struct value_print_options opts;
diff --git a/gdb/inferior.h b/gdb/inferior.h
index 15e3ca2..e38ce54 100644
--- a/gdb/inferior.h
+++ b/gdb/inferior.h
@@ -285,6 +285,9 @@ extern void detach_command (char *, int);

extern void notice_new_inferior (ptid_t, int, int);

+extern struct value *get_return_value (struct type *func_type,
+ struct type *value_type);
+
/* Address at which inferior stopped. */

extern CORE_ADDR stop_pc;
diff --git a/gdb/infrun.c b/gdb/infrun.c
index 8d6910d..efe21c3 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -54,6 +54,7 @@
#include "inline-frame.h"
#include "jit.h"
#include "tracepoint.h"
+#include "python/python.h"

/* Prototypes for local functions */

@@ -336,7 +337,8 @@ show_stop_on_solib_events (struct ui_file *file, int from_tty,
int stop_after_trap;

/* Save register contents here when executing a "finish" command or are
- about to pop a stack dummy frame, if-and-only-if proceed_to_finish is set.
+ about to pop a stack dummy frame, if-and-only-if proceed_to_finish is set
+ or a Python FinishBreakpoint have been hit.
Thus this contains the return value from the called function (assuming
values are returned in a register). */

@@ -5823,7 +5825,8 @@ normal_stop (void)

/* Save the function value return registers, if we care.
We might be about to restore their previous contents. */
- if (inferior_thread ()->control.proceed_to_finish)
+ if (gdbpy_is_stopped_at_finish_bp (inferior_thread ()->control.stop_bpstat)
+ || inferior_thread ()->control.proceed_to_finish)
{
/* This should not be necessary. */
if (stop_registers)
diff --git a/gdb/python/py-breakpoint.c b/gdb/python/py-breakpoint.c
index 9c33848..47946d9 100644
--- a/gdb/python/py-breakpoint.c
+++ b/gdb/python/py-breakpoint.c
@@ -31,52 +31,18 @@
#include "arch-utils.h"
#include "language.h"

-static PyTypeObject breakpoint_object_type;
+PyTypeObject breakpoint_object_type;

/* Number of live breakpoints. */
static int bppy_live;

/* Variables used to pass information between the Breakpoint
constructor and the breakpoint-created hook function. */
-static breakpoint_object *bppy_pending_object;
+breakpoint_object *bppy_pending_object;

/* Function that is called when a Python condition is evaluated. */
static char * const stop_func = "stop";

-struct breakpoint_object
-{
- PyObject_HEAD
-
- /* The breakpoint number according to gdb. */
- int number;
-
- /* The gdb breakpoint object, or NULL if the breakpoint has been
- deleted. */
- struct breakpoint *bp;
-};
-
-/* Require that BREAKPOINT be a valid breakpoint ID; throw a Python
- exception if it is invalid. */
-#define BPPY_REQUIRE_VALID(Breakpoint) \
- do { \
- if ((Breakpoint)->bp == NULL) \
- return PyErr_Format (PyExc_RuntimeError, \
- _("Breakpoint %d is invalid."), \
- (Breakpoint)->number); \
- } while (0)
-
-/* Require that BREAKPOINT be a valid breakpoint ID; throw a Python
- exception if it is invalid. This macro is for use in setter functions. */
-#define BPPY_SET_REQUIRE_VALID(Breakpoint) \
- do { \
- if ((Breakpoint)->bp == NULL) \
- { \
- PyErr_Format (PyExc_RuntimeError, _("Breakpoint %d is invalid."), \
- (Breakpoint)->number); \
- return -1; \
- } \
- } while (0)
-
/* This is used to initialize various gdb.bp_* constants. */
struct pybp_code
{
@@ -813,6 +779,8 @@ gdbpy_breakpoint_created (struct breakpoint *bp)
newbp->bp->py_bp_object = newbp;
Py_INCREF (newbp);
++bppy_live;
+
+ bp->is_py_finish_bp = bpfinishpy_is_finish_bp ((PyObject *) newbp);
}
else
{
@@ -971,7 +939,7 @@ static PyMethodDef breakpoint_object_methods[] =
{ NULL } /* Sentinel. */
};

-static PyTypeObject breakpoint_object_type =
+PyTypeObject breakpoint_object_type =
{
PyObject_HEAD_INIT (NULL)
0, /*ob_size*/
diff --git a/gdb/python/py-finishbreakpoint.c b/gdb/python/py-finishbreakpoint.c
new file mode 100644
index 0000000..9b849bf
--- /dev/null
+++ b/gdb/python/py-finishbreakpoint.c
@@ -0,0 +1,497 @@
+/* Python interface to finish breakpoints
+
+ Copyright (C) 2011 Free Software Foundation, Inc.
+
+ This file is part of GDB.
+
+ 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 "defs.h"
+#include "exceptions.h"
+#include "python-internal.h"
+#include "breakpoint.h"
+#include "frame.h"
+#include "gdbthread.h"
+#include "arch-utils.h"
+#include "language.h"
+#include "observer.h"
+#include "inferior.h"
+
+static PyTypeObject finish_breakpoint_object_type;
+
+/* Function that is called when a Python finish bp is found out of scope. */
+static char * const outofscope_func = "out_of_scope";
+
+/* struct implementing the gdb.FinishBreakpoint object by extending
+ the gdb.Breakpoint class. */
+struct finish_breakpoint_object
+{
+ /* gdb.Breakpoint base class. */
+ struct breakpoint_object py_bp;
+ /* Flag indicating that the BP is out of the callstack and Python callback
+ has been triggered. */
+ int out_of_scope_notif;
+ /* Type of the function finished by this breakpoint. */
+ struct type *function_type;
+ /* Type of the value return by the breakpointed function. */
+ struct type *return_type;
+ /* When stopped at this FinishBreakpoint, value returned by the function;
+ Py_None if the value is not computable;
+ NULL if GDB is not stopped at a FinishBreakpoint. */
+ PyObject *return_value;
+};
+
+/* Python function to set the 'out_of_scope_notif' attribute of
+ FinishBreakpoint. */
+
+static int
+bpfinishpy_set_outofscope_notif (PyObject *self, PyObject *newvalue,
+ void *closure)
+{
+ struct finish_breakpoint_object *self_finishbp =
+ (struct finish_breakpoint_object *) self;
+ int cmp;
+
+ BPPY_SET_REQUIRE_VALID (&self_finishbp->py_bp);
+
+ if (newvalue == NULL)
+ {
+ PyErr_SetString (PyExc_TypeError,
+ _("Cannot delete `out_of_scope_notif' attribute."));
+ return -1;
+ }
+ else if (!PyBool_Check (newvalue))
+ {
+ PyErr_SetString (PyExc_TypeError,
+ _("The value of `out_of_scope_notif' must be a boolean."));
+ return -1;
+ }
+
+ cmp = PyObject_IsTrue (newvalue);
+ if (cmp < 0)
+ return -1;
+ else
+ self_finishbp->out_of_scope_notif = cmp;
+
+ return 0;
+}
+
+/* Python function to update and get the 'out_of_scope_notif'
+ attribute of FinishBreakpoint. */
+
+static PyObject *
+bpfinishpy_get_outofscope_notif (PyObject *self, void *closure)
+{
+ struct finish_breakpoint_object *self_finishbp =
+ (struct finish_breakpoint_object *) self;
+
+ BPPY_REQUIRE_VALID (&self_finishbp->py_bp);
+
+ if (self_finishbp->out_of_scope_notif)
+ Py_RETURN_TRUE;
+ Py_RETURN_FALSE;
+}
+
+/* Triggered when GDB stops at PY_BP. Disable `out_of_scope' notifications
+ and, if possible, computes and caches the `return_value'. */
+
+static void
+bpfinish_stopped_at_finish_bp (struct finish_breakpoint_object *py_bp)
+{
+ py_bp->out_of_scope_notif = 0;
+
+ if (py_bp->return_type)
+ {
+ struct value *ret = get_return_value (py_bp->function_type,
+ py_bp->return_type);
+ if (ret)
+ {
+ py_bp->return_value = value_to_value_object (ret);
+ }
+ else
+ py_bp->return_value = Py_None;
+ }
+}
+
+/* Python function to get the 'return_value' attribute of
+ FinishBreakpoint. */
+
+static PyObject *
+bpfinishpy_get_returnvalue (PyObject *self, void *closure)
+{
+ struct finish_breakpoint_object *self_finishbp =
+ (struct finish_breakpoint_object *) self;
+
+ BPPY_REQUIRE_VALID (&self_finishbp->py_bp);
+
+ if (self_finishbp->return_type == NULL)
+ Py_RETURN_NONE;
+
+ /* Check if we have a cached value. */
+ if (!self_finishbp->return_value)
+ {
+ bpstat bs;
+
+ for (bs = inferior_thread ()->control.stop_bpstat;
+ bs; bs = bs->next)
+ {
+ struct breakpoint *bp = bs->breakpoint_at;
+
+ if (bp != NULL && (PyObject *) bp->py_bp_object == self)
+ bpfinish_stopped_at_finish_bp (self_finishbp);
+ }
+ }
+
+ if (!self_finishbp->return_value)
+ Py_RETURN_NONE;
+
+ Py_INCREF (self_finishbp->return_value);
+ return self_finishbp->return_value;
+}
+
+/* Called when GDB notices that the finish breakpoint BP_OBJ is out of
+ the current callstack. If BP_OBJ has the attribute OUT_OF_SCOPE_NOTIF
+ and its value is FALSE, trigger the method OUT_OF_SCOPE and set the flag
+ to TRUE. */
+
+static void
+bpfinishpy_out_of_scope (struct finish_breakpoint_object *bpfinish_obj)
+{
+ breakpoint_object *bp_obj = (breakpoint_object *) bpfinish_obj;
+ PyObject *py_obj = (PyObject *) bp_obj;
+
+ bpfinish_obj->out_of_scope_notif = 0;
+
+ if (PyObject_HasAttrString (py_obj, outofscope_func))
+ {
+ if (!PyObject_CallMethod (py_obj, outofscope_func, NULL))
+ gdbpy_print_stack ();
+ }
+}
+
+/* Python function to check if the FinishBreakpoint SELF is not anymore
+ in the callstack. Triggers self->out_of_scope if necessary. */
+
+static PyObject *
+bpfinishpy_check_scope (PyObject *self, PyObject *args)
+{
+ struct finish_breakpoint_object *self_finishbp =
+ (struct finish_breakpoint_object *) self;
+ struct frame_id bp_frame_id = self_finishbp->py_bp.bp->frame_id;
+
+ if (self_finishbp->out_of_scope_notif
+ && (frame_id_eq (bp_frame_id, get_frame_id (get_current_frame()))
+ || frame_find_by_id (bp_frame_id) == NULL))
+ {
+ bpfinishpy_out_of_scope (self_finishbp);
+
+ Py_RETURN_TRUE;
+ }
+ Py_RETURN_FALSE;
+}
+
+/* If STOP_BPSTAT contains a Python breakpoint whose type is TYPE, returns
+ this breakpoint. Returns NULL otherwise. */
+
+struct breakpoint *
+gdbpy_is_stopped_at_finish_bp (bpstat stop_bpstat)
+{
+ bpstat bs;
+
+ for (bs = stop_bpstat; bs; bs = bs->next)
+ {
+ if (bs->breakpoint_at && bs->breakpoint_at->is_py_finish_bp)
+ return bs->breakpoint_at;
+ }
+
+ return NULL;
+}
+
+/* Python function to create a new breakpoint. */
+
+static int
+bpfinishpy_init (PyObject *self, PyObject *args, PyObject *kwargs)
+{
+ static char *keywords[] = { "frame", "internal", NULL };
+ breakpoint_object *self_bp = (breakpoint_object *) self;
+ struct finish_breakpoint_object *self_bpfinish =
+ (struct finish_breakpoint_object *) self;
+ int type = bp_breakpoint;
+ PyObject *frame_obj = NULL;
+ struct frame_info *frame, *prev_frame;
+ struct frame_id frame_id;
+ PyObject *internal = NULL;
+ int internal_bp = 0;
+ CORE_ADDR finish_pc, pc;
+ volatile struct gdb_exception except;
+ char *addr_str, small_buf[100];
+ struct symbol *function;
+
+ if (! PyArg_ParseTupleAndKeywords (args, kwargs, "O|O", keywords,
+ &frame_obj, &internal))
+ return -1;
+
+ if (!frame_obj)
+ goto invalid_frame;
+
+ frame = frame_object_to_frame_info (frame_obj);
+ if (frame == NULL)
+ goto invalid_frame;
+
+ prev_frame = get_prev_frame (frame);
+ if (prev_frame == 0)
+ {
+ PyErr_SetString (PyExc_ValueError,
+ _("\"FinishBreakpoint\" not meaningful in the outermost frame."));
+ return -1;
+ }
+
+ frame_id = get_frame_id (prev_frame);
+ if (frame_id_eq (frame_id, null_frame_id))
+ goto invalid_frame;
+
+ if (internal)
+ {
+ internal_bp = PyObject_IsTrue (internal);
+ if (internal_bp == -1)
+ {
+ PyErr_SetString (PyExc_ValueError,
+ _("The value of `internal' must be a boolean."));
+ return -1;
+ }
+ }
+
+ /* Find the function we will return from. */
+ self_bpfinish->return_type = NULL;
+ self_bpfinish->function_type = NULL;
+ if (get_frame_pc_if_available (frame, &pc))
+ {
+ function = find_pc_function (pc);
+ if (function != NULL)
+ {
+ struct type *ret_type = TYPE_TARGET_TYPE (SYMBOL_TYPE (function));
+
+ /* Remember only non-VOID return types. */
+ if (TYPE_CODE (ret_type) != TYPE_CODE_VOID)
+ {
+ self_bpfinish->return_type = ret_type;
+ self_bpfinish->function_type = SYMBOL_TYPE (function);
+ }
+ }
+ }
+
+ bppy_pending_object = self_bp;
+ bppy_pending_object->number = -1;
+ bppy_pending_object->bp = NULL;
+
+ TRY_CATCH (except, RETURN_MASK_ALL)
+ {
+ /* Set a breakpoint on the return address. */
+ finish_pc = get_frame_pc (prev_frame);
+ sprintf (small_buf, "*%s", hex_string (finish_pc));
+ addr_str = small_buf;
+
+ create_breakpoint (python_gdbarch,
+ addr_str, NULL, -1,
+ 0,
+ 0, bp_breakpoint,
+ 0,
+ AUTO_BOOLEAN_TRUE,
+ NULL, 0, 1, internal_bp);
+ }
+ if (except.reason < 0)
+ {
+ PyErr_Format (except.reason == RETURN_QUIT
+ ? PyExc_KeyboardInterrupt : PyExc_RuntimeError,
+ "%s", except.message);
+ return -1;
+ }
+ BPPY_SET_REQUIRE_VALID (self_bp);
+
+ self_bp->bp->frame_id = frame_id;
+
+ self_bpfinish->out_of_scope_notif = 1;
+
+ return 0;
+
+ invalid_frame:
+ PyErr_SetString (PyExc_ValueError,
+ _("Invalid ID for the `frame' object."));
+ return -1;
+}
+
+/* Returns 1 if OBJ is not NULL and references a FinishBreakpoint object. */
+
+int
+bpfinishpy_is_finish_bp (PyObject *obj)
+{
+ return obj != NULL
+ && PyObject_TypeCheck (obj, &finish_breakpoint_object_type);
+}
+
+/* Callback for `bpfinishpy_detect_out_scope'. Triggers Python's
+ `B->out_of_scope' function if B is a FinishBreakpoint out of its
+ scope or turn of out_of_scope notification if B has been hit. */
+
+static int
+bpfinishpy_detect_out_scope_cb (struct breakpoint *b, void *args)
+{
+ struct breakpoint *bp_stopped = (struct breakpoint *) args;
+ PyObject *py_bp = (PyObject *) b->py_bp_object;
+ struct gdbarch *garch = b->gdbarch ? b->gdbarch : get_current_arch ();
+ struct cleanup *cleanup = ensure_python_env (garch, current_language);
+
+ /* Trigger out_of_scope if this is a FinishBreakpoint its frame is not in the
+ current callstack and the notification has not been sent yet. */
+ if (bpfinishpy_is_finish_bp (py_bp))
+ {
+ struct finish_breakpoint_object *finish_bp =
+ (struct finish_breakpoint_object *) py_bp;
+
+ if (b == bp_stopped)
+ bpfinish_stopped_at_finish_bp (finish_bp);
+ else if (finish_bp->out_of_scope_notif
+ && b->pspace == current_inferior()->pspace
+ && (!target_has_registers || frame_find_by_id(b->frame_id) == NULL))
+ {
+ bpfinishpy_out_of_scope (finish_bp);
+ }
+ }
+
+ do_cleanups (cleanup);
+
+ return 0;
+}
+
+/* Attached to `stop' notifications, check if the execution has run out
+ out of the scope of any FinishBreakpoint before it has been hit. */
+
+static void
+bpfinishpy_handle_stop (struct bpstats *bs, int print_frame)
+{
+ iterate_over_breakpoints (bpfinishpy_detect_out_scope_cb,
+ bs == NULL ? NULL : bs->breakpoint_at);
+}
+
+/* Attached to `exit' notifications, triggers all the necessary out of
+ scope notifications. */
+
+static void
+bpfinishpy_handle_exit (struct inferior *inf)
+{
+ iterate_over_breakpoints (bpfinishpy_detect_out_scope_cb, NULL);
+}
+
+/* Callback to the bpfinishpy_handle_resume function. */
+
+static int
+bpfinishpy_handle_resume_cb (struct breakpoint *b, void *args)
+{
+ if (bpfinishpy_is_finish_bp ((PyObject *) b->py_bp_object))
+ {
+ struct finish_breakpoint_object *finish_bp =
+ (struct finish_breakpoint_object *) b->py_bp_object;
+ Py_XDECREF (finish_bp->return_value);
+ finish_bp->return_value = NULL;
+ }
+ return 0;
+}
+
+/* Attached to `target_resume' notifications, resets the `return_value'
+ cache. */
+
+static void
+bpfinishpy_handle_resume (ptid_t ptid)
+{
+ iterate_over_breakpoints (bpfinishpy_handle_resume_cb, NULL);
+}
+
+/* Initialize the Python finish breakpoint code. */
+
+void
+gdbpy_initialize_finishbreakpoints (void)
+{
+ if (PyType_Ready (&finish_breakpoint_object_type) < 0)
+ return;
+
+ Py_INCREF (&finish_breakpoint_object_type);
+ PyModule_AddObject (gdb_module, "FinishBreakpoint",
+ (PyObject *) &finish_breakpoint_object_type);
+
+ observer_attach_normal_stop (bpfinishpy_handle_stop);
+ observer_attach_inferior_exit (bpfinishpy_handle_exit);
+ observer_attach_target_resumed (bpfinishpy_handle_resume);
+}
+
+static PyMethodDef finish_breakpoint_object_methods[] = {
+ { "check_scope", bpfinishpy_check_scope, METH_NOARGS,
+ "check_scope () -> Boolean.\n\
+Return true if out_of_scope () has been triggered, false if not." },
+ {NULL} /* Sentinel */
+};
+
+static PyGetSetDef finish_breakpoint_object_getset[] = {
+ { "out_of_scope_notif", bpfinishpy_get_outofscope_notif, bpfinishpy_set_outofscope_notif,
+ "Boolean telling whether the breakpoint is still within the scope \
+of the current callstack.", NULL },
+ { "return_value", bpfinishpy_get_returnvalue, NULL,
+ "gdb.Value object representing the return value, if any. \
+None otherwise.", NULL },
+ { NULL } /* Sentinel. */
+};
+
+static PyTypeObject finish_breakpoint_object_type =
+{
+ PyObject_HEAD_INIT (NULL)
+ 0, /*ob_size*/
+ "gdb.FinishBreakpoint", /*tp_name*/
+ sizeof (struct finish_breakpoint_object), /*tp_basicsize*/
+ 0, /*tp_itemsize*/
+ 0, /*tp_dealloc*/
+ 0, /*tp_print*/
+ 0, /*tp_getattr*/
+ 0, /*tp_setattr*/
+ 0, /*tp_compare*/
+ 0, /*tp_repr*/
+ 0, /*tp_as_number*/
+ 0, /*tp_as_sequence*/
+ 0, /*tp_as_mapping*/
+ 0, /*tp_hash */
+ 0, /*tp_call*/
+ 0, /*tp_str*/
+ 0, /*tp_getattro*/
+ 0, /*tp_setattro */
+ 0, /*tp_as_buffer*/
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
+ "GDB finish breakpoint object", /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ 0, /* tp_iter */
+ 0, /* tp_iternext */
+ finish_breakpoint_object_methods, /* tp_methods */
+ 0, /* tp_members */
+ finish_breakpoint_object_getset,/* tp_getset */
+ &breakpoint_object_type, /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ bpfinishpy_init, /* tp_init */
+ 0, /* tp_alloc */
+ 0 /* tp_new */
+};
diff --git a/gdb/python/py-frame.c b/gdb/python/py-frame.c
index d7128a9..2109c73 100644
--- a/gdb/python/py-frame.c
+++ b/gdb/python/py-frame.c
@@ -60,9 +60,10 @@ static PyTypeObject frame_object_type;
object. If the frame doesn't exist anymore (the frame id doesn't
correspond to any frame in the inferior), returns NULL. */

-static struct frame_info *
-frame_object_to_frame_info (frame_object *frame_obj)
+struct frame_info *
+frame_object_to_frame_info (PyObject *obj)
{
+ frame_object *frame_obj = (frame_object *) obj;
struct frame_info *frame;

frame = frame_find_by_id (frame_obj->frame_id);
@@ -103,7 +104,7 @@ frapy_is_valid (PyObject *self, PyObject *args)
{
struct frame_info *frame;

- frame = frame_object_to_frame_info ((frame_object *) self);
+ frame = frame_object_to_frame_info (self);
if (frame == NULL)
Py_RETURN_FALSE;

@@ -124,7 +125,7 @@ frapy_name (PyObject *self, PyObject *args)

TRY_CATCH (except, RETURN_MASK_ALL)
{
- FRAPY_REQUIRE_VALID ((frame_object *) self, frame);
+ FRAPY_REQUIRE_VALID (self, frame);

find_frame_funname (frame, &name, &lang, NULL);
}
@@ -153,7 +154,7 @@ frapy_type (PyObject *self, PyObject *args)

TRY_CATCH (except, RETURN_MASK_ALL)
{
- FRAPY_REQUIRE_VALID ((frame_object *) self, frame);
+ FRAPY_REQUIRE_VALID (self, frame);

type = get_frame_type (frame);
}
@@ -174,7 +175,7 @@ frapy_unwind_stop_reason (PyObject *self, PyObject *args)

TRY_CATCH (except, RETURN_MASK_ALL)
{
- FRAPY_REQUIRE_VALID ((frame_object *) self, frame);
+ FRAPY_REQUIRE_VALID (self, frame);
}
GDB_PY_HANDLE_EXCEPTION (except);

@@ -195,7 +196,7 @@ frapy_pc (PyObject *self, PyObject *args)

TRY_CATCH (except, RETURN_MASK_ALL)
{
- FRAPY_REQUIRE_VALID ((frame_object *) self, frame);
+ FRAPY_REQUIRE_VALID (self, frame);

pc = get_frame_pc (frame);
}
@@ -216,7 +217,7 @@ frapy_block (PyObject *self, PyObject *args)

TRY_CATCH (except, RETURN_MASK_ALL)
{
- FRAPY_REQUIRE_VALID ((frame_object *) self, frame);
+ FRAPY_REQUIRE_VALID (self, frame);
block = get_frame_block (frame, NULL);
}
GDB_PY_HANDLE_EXCEPTION (except);
@@ -257,7 +258,7 @@ frapy_function (PyObject *self, PyObject *args)

TRY_CATCH (except, RETURN_MASK_ALL)
{
- FRAPY_REQUIRE_VALID ((frame_object *) self, frame);
+ FRAPY_REQUIRE_VALID (self, frame);

sym = find_pc_function (get_frame_address_in_block (frame));
}
@@ -319,7 +320,7 @@ frapy_older (PyObject *self, PyObject *args)

TRY_CATCH (except, RETURN_MASK_ALL)
{
- FRAPY_REQUIRE_VALID ((frame_object *) self, frame);
+ FRAPY_REQUIRE_VALID (self, frame);

prev = get_prev_frame (frame);
if (prev)
@@ -348,7 +349,7 @@ frapy_newer (PyObject *self, PyObject *args)

TRY_CATCH (except, RETURN_MASK_ALL)
{
- FRAPY_REQUIRE_VALID ((frame_object *) self, frame);
+ FRAPY_REQUIRE_VALID (self, frame);

next = get_next_frame (frame);
if (next)
@@ -377,7 +378,7 @@ frapy_find_sal (PyObject *self, PyObject *args)

TRY_CATCH (except, RETURN_MASK_ALL)
{
- FRAPY_REQUIRE_VALID ((frame_object *) self, frame);
+ FRAPY_REQUIRE_VALID (self, frame);

find_frame_sal (frame, &sal);
sal_obj = symtab_and_line_to_sal_object (sal);
@@ -433,7 +434,7 @@ frapy_read_var (PyObject *self, PyObject *args)

TRY_CATCH (except, RETURN_MASK_ALL)
{
- FRAPY_REQUIRE_VALID ((frame_object *) self, frame);
+ FRAPY_REQUIRE_VALID (self, frame);

if (!block)
block = get_frame_block (frame, NULL);
@@ -461,7 +462,7 @@ frapy_read_var (PyObject *self, PyObject *args)

TRY_CATCH (except, RETURN_MASK_ALL)
{
- FRAPY_REQUIRE_VALID ((frame_object *) self, frame);
+ FRAPY_REQUIRE_VALID (self, frame);

val = read_var_value (var, frame);
}
@@ -484,12 +485,11 @@ static PyObject *
frapy_select (PyObject *self, PyObject *args)
{
struct frame_info *fi;
- frame_object *frame = (frame_object *) self;
volatile struct gdb_exception except;

TRY_CATCH (except, RETURN_MASK_ALL)
{
- FRAPY_REQUIRE_VALID (frame, fi);
+ FRAPY_REQUIRE_VALID (self, fi);

select_frame (fi);
}
diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h
index d3cb788..e404a32 100644
--- a/gdb/python/python-internal.h
+++ b/gdb/python/python-internal.h
@@ -114,9 +114,47 @@ extern PyTypeObject symbol_object_type;
extern PyTypeObject event_object_type;
extern PyTypeObject events_object_type;
extern PyTypeObject stop_event_object_type;
+extern PyTypeObject breakpoint_object_type;
+
+typedef struct breakpoint_object
+{
+ PyObject_HEAD
+
+ /* The breakpoint number according to gdb. */
+ int number;
+
+ /* The gdb breakpoint object, or NULL if the breakpoint has been
+ deleted. */
+ struct breakpoint *bp;
+} breakpoint_object;
+
+/* Require that BREAKPOINT be a valid breakpoint ID; throw a Python
+ exception if it is invalid. */
+#define BPPY_REQUIRE_VALID(Breakpoint) \
+ do { \
+ if ((Breakpoint)->bp == NULL) \
+ return PyErr_Format (PyExc_RuntimeError, \
+ _("Breakpoint %d is invalid."), \
+ (Breakpoint)->number); \
+ } while (0)
+
+/* Require that BREAKPOINT be a valid breakpoint ID; throw a Python
+ exception if it is invalid. This macro is for use in setter functions. */
+#define BPPY_SET_REQUIRE_VALID(Breakpoint) \
+ do { \
+ if ((Breakpoint)->bp == NULL) \
+ { \
+ PyErr_Format (PyExc_RuntimeError, _("Breakpoint %d is invalid."), \
+ (Breakpoint)->number); \
+ return -1; \
+ } \
+ } while (0)
+
+
+/* Variables used to pass information between the Breakpoint
+ constructor and the breakpoint-created hook function. */
+extern breakpoint_object *bppy_pending_object;

-/* Defined in py-breakpoint.c */
-typedef struct breakpoint_object breakpoint_object;

typedef struct
{
@@ -161,6 +199,7 @@ PyObject *block_to_block_object (struct block *block, struct objfile *objfile);
PyObject *value_to_value_object (struct value *v);
PyObject *type_to_type_object (struct type *);
PyObject *frame_info_to_frame_object (struct frame_info *frame);
+struct frame_info *frame_object_to_frame_info (PyObject *frame_obj);

PyObject *pspace_to_pspace_object (struct program_space *);
PyObject *pspy_get_printers (PyObject *, void *);
@@ -181,6 +220,8 @@ struct type *type_object_to_type (PyObject *obj);
struct symtab *symtab_object_to_symtab (PyObject *obj);
struct symtab_and_line *sal_object_to_symtab_and_line (PyObject *obj);

+int bpfinishpy_is_finish_bp (PyObject *obj);
+
void gdbpy_initialize_auto_load (void);
void gdbpy_initialize_values (void);
void gdbpy_initialize_frames (void);
@@ -194,6 +235,7 @@ void gdbpy_initialize_functions (void);
void gdbpy_initialize_pspace (void);
void gdbpy_initialize_objfile (void);
void gdbpy_initialize_breakpoints (void);
+void gdbpy_initialize_finishbreakpoints (void);
void gdbpy_initialize_lazy_string (void);
void gdbpy_initialize_parameters (void);
void gdbpy_initialize_thread (void);
diff --git a/gdb/python/python.c b/gdb/python/python.c
index 8a7bc66..e2472fc 100644
--- a/gdb/python/python.c
+++ b/gdb/python/python.c
@@ -926,6 +926,11 @@ gdbpy_breakpoint_has_py_cond (struct breakpoint_object *bp_obj)
"scripting is not supported."));
}

+struct breakpoint *
+gdbpy_is_stopped_at_finish_bp (bpstat stop_bpstat)
+{
+ return NULL;
+}
#endif /* HAVE_PYTHON */

\f
@@ -1060,6 +1065,7 @@ Enables or disables printing of Python stack traces."),
gdbpy_initialize_pspace ();
gdbpy_initialize_objfile ();
gdbpy_initialize_breakpoints ();
+ gdbpy_initialize_finishbreakpoints ();
gdbpy_initialize_lazy_string ();
gdbpy_initialize_thread ();
gdbpy_initialize_inferior ();
diff --git a/gdb/python/python.h b/gdb/python/python.h
index ce0eb35..e341d76 100644
--- a/gdb/python/python.h
+++ b/gdb/python/python.h
@@ -21,6 +21,7 @@
#define GDB_PYTHON_H

#include "value.h"
+#include "breakpoint.h"

struct breakpoint_object;

@@ -47,4 +48,5 @@ int gdbpy_should_stop (struct breakpoint_object *bp_obj);

int gdbpy_breakpoint_has_py_cond (struct breakpoint_object *bp_obj);

+struct breakpoint *gdbpy_is_stopped_at_finish_bp (bpstat stop_bpstat);
#endif /* GDB_PYTHON_H */
diff --git a/gdb/testsuite/gdb.python/py-breakpoint.exp b/gdb/testsuite/gdb.python/py-breakpoint.exp
index f0a83f1..8755888 100644
--- a/gdb/testsuite/gdb.python/py-breakpoint.exp
+++ b/gdb/testsuite/gdb.python/py-breakpoint.exp
@@ -44,7 +44,8 @@ gdb_py_test_silent_cmd "python blist = gdb.breakpoints()" "Get Breakpoint List"
gdb_test "python print blist\[0\]" "<gdb.Breakpoint object at $hex>" "Check obj exists"
gdb_test "python print blist\[0\].location" "main." "Check breakpoint location"

-gdb_breakpoint [gdb_get_line_number "Break at multiply."]
+set mult_line [gdb_get_line_number "Break at multiply."]
+gdb_breakpoint ${mult_line}
gdb_continue_to_breakpoint "Break at multiply."

# Check that the Python breakpoint code noted the addition of a
@@ -54,7 +55,9 @@ gdb_test "python print len(blist)" "2" "Check for two breakpoints"
gdb_test "python print blist\[0\]" "<gdb.Breakpoint object at $hex>" "Check obj exists"
gdb_test "python print blist\[0\].location" "main." "Check breakpoint location"
gdb_test "python print blist\[1\]" "<gdb.Breakpoint object at $hex>" "Check obj exists"
-gdb_test "python print blist\[1\].location" "py-breakpoint\.c:41*" "Check breakpoint location"
+
+gdb_test "python print blist\[1\].location" "py-breakpoint\.c:${mult_line}*" \
+ "Check breakpoint location"

# Check hit and ignore counts.
gdb_test "python print blist\[1\].hit_count" "1" "Check breakpoint hit count"
diff --git a/gdb/testsuite/gdb.python/py-finish-breakpoint.c b/gdb/testsuite/gdb.python/py-finish-breakpoint.c
new file mode 100644
index 0000000..5b708e3
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-finish-breakpoint.c
@@ -0,0 +1,97 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 2011 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 <setjmp.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+int increase_1 (int *a)
+{
+ *a += 1;
+ return -5;
+}
+
+void increase (int *a)
+{
+ increase_1 (a);
+}
+
+int
+test_1 (int i, int j)
+{
+ return i == j;
+}
+
+int
+test(int i, int j)
+{
+ return test_1 (i, j);
+}
+
+int
+call_longjmp_1 (jmp_buf *buf)
+{
+ longjmp (*buf, 1);
+}
+
+int
+call_longjmp (jmp_buf *buf)
+{
+ call_longjmp_1 (buf);
+}
+
+void
+test_exec_exit(int do_exit)
+{
+ if (do_exit)
+ exit(0);
+ else
+ execl ("/bin/echo", "echo", "-1", (char *)0);
+}
+
+int main (int argc, char *argv[])
+{
+ jmp_buf env;
+ int foo = 5;
+ int bar = 42;
+ int i, j;
+
+ getpid();
+
+ i = 0;
+ /* Break at increase. */
+ increase (&i);
+ increase (&i);
+ increase (&i);
+
+ for (i = 0; i < 10; i++)
+ {
+ j += 1; /* Condition Break. */
+ }
+
+ if (setjmp (env) == 0) /* longjmp caught */
+ {
+ call_longjmp (&env);
+ }
+ else
+ j += 1; /* after longjmp. */
+
+ test_exec_exit(1);
+
+ return j; /* Break at end. */
+}
diff --git a/gdb/testsuite/gdb.python/py-finish-breakpoint.exp b/gdb/testsuite/gdb.python/py-finish-breakpoint.exp
new file mode 100644
index 0000000..237e96a
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-finish-breakpoint.exp
@@ -0,0 +1,258 @@
+# Copyright (C) 2011 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. It tests the mechanism
+# exposing values to Python.
+
+if $tracelevel then {
+ strace $tracelevel
+}
+
+load_lib gdb-python.exp
+
+set testfile "py-finish-breakpoint"
+set srcfile ${testfile}.c
+
+if { [prepare_for_testing ${testfile}.exp ${testfile} ${srcfile}] } {
+ return -1
+}
+
+set remote_python_file [remote_download host ${srcdir}/${subdir}/${testfile}.py]
+
+
+# Skip all tests if Python scripting is not enabled.
+if { [skip_python_tests] } { continue }
+
+#
+# Test FinishBreakpoint in normal conditions
+#
+
+clean_restart ${testfile}
+
+if ![runto_main] then {
+ fail "Cannot run to main."
+ return 0
+}
+gdb_test_no_output "set confirm off" "disable confirmation"
+gdb_test "source $remote_python_file" ".*Python script imported.*" \
+ "import python scripts"
+gdb_breakpoint "increase_1"
+gdb_test "continue" ".*Breakpoint .*at.*" "continue to function to finish"
+
+# set FinishBreakpoint
+
+gdb_test "python finishbp = MyFinishBreakpoint (gdb.parse_and_eval (\"a\"), gdb.selected_frame ())" \
+ ".*Breakpoint 3.*" "set FinishBreakpoint"
+gdb_test "python print finishbp.out_of_scope_notif" ".*True.*" \
+ "check out_of_scope_notif at init"
+gdb_test "python print finishbp.return_value" ".*None.*" \
+ "check return_value at init"
+
+# check normal bp hit
+
+gdb_test "continue" ".*MyFinishBreakpoint stop with.*#0.*increase.*" \
+ "check MyFinishBreakpoint hit"
+gdb_test "python print finishbp.return_value" ".*-5.*" "check return_value"
+gdb_test "python print finishbp.out_of_scope_notif" ".*False.*" \
+ "check out_of_scope_notif disabled after hit"
+gdb_test "finish" ".*main.*" "return to main()"
+gdb_test "python print finishbp.return_value" ".*None.*" "check return_value"
+
+# check forced return / check_scope()
+
+gdb_test_no_output "python finishbp.out_of_scope_notif = True" \
+ "re-enable out_of_scope_notif"
+
+gdb_test "continue" ".*Breakpoint.*at.*" "continue to function to finish"
+gdb_test "up" ".*increase_1.*" "go one frame up"
+gdb_test_no_output "return" "return from the frame"
+gdb_test "python print finishbp.check_scope()" ".*MyFinishBreakpoint out of scope.*True.*" \
+ "check_scope of finishBreakpoint"
+
+# check forced return / out_of_scope automatic notification
+
+gdb_test_no_output "python finishbp.out_of_scope_notif = True" \
+ "re-enable out_of_scope_notif"
+
+gdb_test "continue" ".*Breakpoint.*at.*" "continue to function to finish"
+gdb_test "up" ".*increase_1.*" "go one frame up"
+gdb_test_no_output "return" "return from the frame"
+gdb_test "next" ".*MyFinishBreakpoint out of scope.*" "check Finish breakpoint discard"
+gdb_test "python print finishbp.out_of_scope_notif" ".*False.*" "check out_of_scope_notif"
+
+# check FinishBreakpoint in main
+
+gdb_test "python MyFinishBreakpoint (None, gdb.selected_frame ())" \
+ ".*ValueError: \"FinishBreakpoint\" not meaningful in the outermost frame..*" \
+ "check FinishBP not allowed in main"
+
+
+#
+# Test FinishBreakpoint with no debug symbol
+#
+
+clean_restart ${testfile}
+
+gdb_test "source $remote_python_file" ".*Python script imported.*" \
+ "import python scripts"
+
+if ![runto "getpid"] then {
+ fail "Cannot run to getpid."
+ return 0
+}
+
+gdb_test "python finishBP = SimpleFinishBreakpoint(gdb.newest_frame())" \
+ "SimpleFinishBreakpoint init" \
+ "set finish breakpoint"
+gdb_test "continue" ".*SimpleFinishBreakpoint stop.*" "check FinishBreakpoint hit"
+gdb_test "python print finishBP.return_value" "None" "check return value without debug symbol"
+
+#
+# Test FinishBreakpoint in function returned by longjmp
+#
+
+clean_restart ${testfile}
+
+gdb_test "source $remote_python_file" ".*Python script imported.*" \
+ "import python scripts"
+
+if ![runto call_longjmp_1] then {
+ perror "couldn't run to breakpoint call_longjmp"
+ continue
+}
+
+gdb_test "python finishBP = SimpleFinishBreakpoint(gdb.newest_frame())" \
+ "SimpleFinishBreakpoint init" \
+ "set finish breakpoint"
+gdb_test "break [gdb_get_line_number "after longjmp."]" "Breakpoint.* at .*" \
+ "set BP after the jump"
+gdb_test "continue" ".*SimpleFinishBreakpoint out of scope.*" \
+ "check FinishBP out of scope notification"
+
+#
+# Test FinishBreakpoint in BP condition evaluation
+# (finish in dummy frame)
+#
+
+clean_restart ${testfile}
+
+gdb_test "source $remote_python_file" ".*Python script imported.*" \
+ "import python scripts"
+
+set cond_line [gdb_get_line_number "Condition Break."]
+if ![runto_main] then {
+ fail "Cannot run to main."
+ return 0
+}
+
+gdb_test "break ${cond_line} if test_1(i,8)" ".*Breakpoint .* at .*" "set conditional BP"
+gdb_test "python TestBreakpoint()" "TestBreakpoint init" "set BP in condition"
+
+set msg "check FinishBreakpoint don't stop in GDB Dummy Frame"
+gdb_test_multiple "continue" $msg {
+ -re ".*test don't stop 2.*test stop.*test don't stop 4.*" {
+ pass $msg
+ }
+ -re ".*test don't stop 2.*test stop.*$gdb_prompt" {
+ fail $msg
+ }
+}
+
+gdb_test "print i" "8" "check stopped location"
+
+#
+# Test FinishBreakpoint in BP condition evaluation
+# (finish in normal frame)
+#
+
+clean_restart ${testfile}
+
+gdb_test "source $remote_python_file" ".*Python script imported.*" \
+ "import python scripts"
+
+if ![runto_main] then {
+ fail "Cannot run to main."
+ return 0
+}
+
+gdb_test "break ${cond_line} if test(i,8)" ".*Breakpoint .* at .*" "set conditional BP"
+gdb_test "python TestBreakpoint()" "TestBreakpoint init" "set BP in condition"
+
+gdb_test "continue" ".*test don't stop 1.*test don't stop 2.*test stop.*Error in testing breakpoint condition.*The program being debugged stopped while in a function called from GDB.*" \
+ "stop in condition function"
+
+setup_kfail "Breakpoint.stop() does't stop in GDB dummy frame" *-*-*
+gdb_test "python print test_finish_bp.out_of_scope_notif" ".*False.*" "check out_of_scope notification disabled"
+gdb_test_no_output "python test_finish_bp.out_of_scope_notif = False" "reestablish correct value"
+
+gdb_test "continue" "Continuing.*" "finish condition evaluation"
+gdb_test "continue" "Breakpoint.*" "stop at conditional breakpoint"
+gdb_test "print i" "8" "check stopped location"
+
+#
+# Test FinishBreakpoint in explicit inferior function call
+#
+
+clean_restart ${testfile}
+
+gdb_test "source $remote_python_file" ".*Python script imported.*" \
+ "import python scripts"
+
+if ![runto_main] then {
+ fail "Cannot run to main."
+ return 0
+}
+
+gdb_test "python TestExplicitBreakpoint(\"increase_1\")" "Breakpoint.*at.*" \
+ "prepare TestExplicitBreakpoint"
+if {[gdb_test "print increase_1(&i)" \
+ ".*SimpleFinishBreakpoint init.*SimpleFinishBreakpoint stop.*\\\$1 = -5.*" \
+ "don't stop at FinishBreakpoint"] == 0} then {
+ setup_kfail "Breakpoint.stop() does't stop in GDB dummy frame" *-*-*
+ fail "FinishBreakpoint stop in dummy frame"
+}
+
+delete_breakpoints
+gdb_test "python TestExplicitBreakpoint(\"increase_1\")" "Breakpoint.*at.*" \
+ "prepare TestExplicitBreakpoint"
+gdb_test "print increase(&i)" \
+ ".*SimpleFinishBreakpoint init.*SimpleFinishBreakpoint stop.*The program being debugged stopped while in a function called from GDB.*" \
+ "FinishBP stop at during explicit function call"
+
+#
+# Test FinishBreakpoint when inferior exits
+#
+
+if ![runto "test_exec_exit"] then {
+ fail "Cannot run to test_exec_exit."
+ return 0
+}
+
+gdb_test "python SimpleFinishBreakpoint(gdb.newest_frame())" "SimpleFinishBreakpoint init" "set FinishBP after the exit()"
+gdb_test "continue" ".*SimpleFinishBreakpoint out of scope.*" "catch out of scope after exit"
+
+#
+# Test FinishBreakpoint when inferior execs
+#
+
+if ![runto "test_exec_exit"] then {
+ fail "Cannot run to test_exec_exit."
+ return 0
+}
+
+gdb_test_no_output "set var do_exit = 0" "switch to execve() test"
+gdb_test "python SimpleFinishBreakpoint(gdb.newest_frame())" "SimpleFinishBreakpoint init" "set FinishBP after the exec"
+gdb_test "catch exec" ".*Catchpoint.*\(exec\).*" "catch exec"
+gdb_test "continue" ".*SimpleFinishBreakpoint out of scope.*" "catch out of scope after exec"
\ No newline at end of file
diff --git a/gdb/testsuite/gdb.python/py-finish-breakpoint.py b/gdb/testsuite/gdb.python/py-finish-breakpoint.py
new file mode 100644
index 0000000..6f41b5f
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-finish-breakpoint.py
@@ -0,0 +1,84 @@
+# Copyright (C) 2011 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. It tests python Finish
+# Breakpoints.
+
+class MyFinishBreakpoint(gdb.FinishBreakpoint):
+ def __init__(self, val, frame):
+ gdb.FinishBreakpoint.__init__ (self, frame)
+ print "MyFinishBreakpoint init"
+ self.val = val
+
+ def stop(self):
+ print "MyFinishBreakpoint stop with %d" % int(self.val.dereference())
+ gdb.execute("where 1")
+ return True
+
+ def out_of_scope(self):
+ print "MyFinishBreakpoint out of scope..."
+
+test_finish_bp = None
+class TestBreakpoint(gdb.Breakpoint):
+ def __init__(self):
+ gdb.Breakpoint.__init__(self, spec="test_1", internal=1)
+ self.silent = True
+ self.finish = None
+ print "TestBreakpoint init"
+
+ def stop(self):
+ global test_finish_bp
+ if (self.finish == None):
+ self.finish = TestFinishBreakpoint(gdb.newest_frame())
+ test_finish_bp = self.finish
+ return False
+
+
+class TestFinishBreakpoint(gdb.FinishBreakpoint):
+ def __init__(self, frame):
+ gdb.FinishBreakpoint.__init__(self, frame, internal=1)
+ self.count = 0
+
+ def stop(self):
+ self.count += 1
+ if (self.count == 3):
+ print "test stop ..."
+ return True
+ else:
+ print "test don't stop %d" % self.count
+ return False
+
+
+ def out_of_scope(self):
+ print "test didn't finish ..."
+
+class TestExplicitBreakpoint(gdb.Breakpoint):
+ def stop(self):
+ SimpleFinishBreakpoint(gdb.newest_frame())
+ return False
+
+class SimpleFinishBreakpoint(gdb.FinishBreakpoint):
+ def __init__(self, frame):
+ gdb.FinishBreakpoint.__init__ (self, frame)
+ print "SimpleFinishBreakpoint init"
+
+ def stop(self):
+ print "SimpleFinishBreakpoint stop"
+ return True
+
+ def out_of_scope(self):
+ print "SimpleFinishBreakpoint out of scope..."
+
+print "Python script imported"
diff --git a/gdb/testsuite/gdb.python/py-finish-breakpoint2.cc b/gdb/testsuite/gdb.python/py-finish-breakpoint2.cc
new file mode 100644
index 0000000..a0eea06
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-finish-breakpoint2.cc
@@ -0,0 +1,59 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 2011 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 <iostream>
+
+void
+throw_exception_1 (int e)
+{
+ throw new int (e);
+}
+
+void
+throw_exception (int e)
+{
+ throw_exception_1 (e);
+}
+
+int
+main (void)
+{
+ int i;
+ try
+ {
+ throw_exception_1 (10);
+ }
+ catch (const int *e)
+ {
+ std::cerr << "Exception #" << *e << std::endl;
+ }
+ i += 1; /* Break after exception 1. */
+
+ try
+ {
+ throw_exception (10);
+ }
+ catch (const int *e)
+ {
+ std::cerr << "Exception #" << *e << std::endl;
+ }
+ i += 1; /* Break after exception 2. */
+
+ return i;
+}
diff --git a/gdb/testsuite/gdb.python/py-finish-breakpoint2.exp b/gdb/testsuite/gdb.python/py-finish-breakpoint2.exp
new file mode 100644
index 0000000..0715188
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-finish-breakpoint2.exp
@@ -0,0 +1,61 @@
+# Copyright (C) 2011 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. It tests the mechanism
+# exposing values to Python.
+
+if $tracelevel then {
+ strace $tracelevel
+}
+
+load_lib gdb-python.exp
+
+set testfile "py-finish-breakpoint2"
+set srcfile ${testfile}.cc
+set binfile ${objdir}/${subdir}/${testfile}
+set pyfile ${srcdir}/${subdir}/${testfile}.py
+
+# Start with a fresh gdb.
+gdb_exit
+gdb_start
+gdb_reinitialize_dir $srcdir/$subdir
+gdb_load ${binfile}
+
+if { [gdb_compile "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable {debug c++}] != "" } {
+ untested "Couldn't compile ${srcfile}"
+ return -1
+}
+
+if ![runto_main] then {
+ fail "Cannot run to main."
+ return 0
+}
+
+gdb_breakpoint [gdb_get_line_number "Break after exception 2"]
+
+gdb_test "source $pyfile" ".*Python script imported.*" \
+ "import python scripts"
+
+gdb_breakpoint "throw_exception_1"
+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 "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"
diff --git a/gdb/testsuite/gdb.python/py-finish-breakpoint2.py b/gdb/testsuite/gdb.python/py-finish-breakpoint2.py
new file mode 100644
index 0000000..9ea4cf1
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-finish-breakpoint2.py
@@ -0,0 +1,34 @@
+# Copyright (C) 2011 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. It tests python Finish
+# Breakpoints.
+
+class ExceptionFinishBreakpoint(gdb.FinishBreakpoint):
+ def __init__(self, frame):
+ gdb.FinishBreakpoint.__init__(self, frame, internal=1)
+ self.silent = True;
+ print "init ExceptionFinishBreakpoint"
+
+ def stop(self):
+ print "stopped at ExceptionFinishBreakpoint"
+ gdb.post_event(self.delete)
+ return True
+
+ def out_of_scope(self):
+ print "exception did not finish ..."
+
+
+print "Python script imported"