Re: [RFC] Python Finish Breakpoints

Kevin Pouget
2011-11-30T16:02:50Z

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

On Thu, Nov 24, 2011 at 9:26 AM, Kevin Pouget <kevin.pouget@gmail.com> wrote:
>
> On Thu, Nov 17, 2011 at 6:48 PM, Tom Tromey <tromey@redhat.com> wrote:
> >
> > >>>>> "Kevin" == Kevin Pouget <kevin.pouget@gmail.com> writes:
> >
> > Kevin> py-breakpoint.c::gdbpy_should_stop is executed _before_ we have the
> > Kevin> chance to save the stop_registers, used to compute the return value
> > Kevin> (in infrun.c::normal_stop).
> > Kevin> (the problem existed since the beginning, but I never faced it before)
> >
> > Kevin> I've updated the function infcmd.c::get_return_value to take this
> > Kevin> situation into account, using the current registers if the
> > Kevin> 'stop_registers' are not set, based on what is done in
> > Kevin> infrun.c::normal_stop:
> >
> > This approach seems reasonable to me but I am not sure whether or not it
> > is really ok.  Most times I've tried to change infrun, I've been burned.
>
> I'm not sure what to do here,
> I don't change the value of the global variable "stop_registers" so
> if shouldn't affect more than my code,
> but it also depends of the side effects of
> > regcache_dup (get_current_regcache ())
> which is now triggered 'slightly' before when it used to be ...
>
> > Kevin> I think that I handle that in the following lines:
> > Kevin> +  if (except.reason < 0
> > Kevin> +      || !self_bpfinish->return_type || !self_bpfinish->function_type)
> >
> > The problem is that Python errors are sticky.  So, if one occurs one
> > must either pass it upward (having the current function fail), or clear
> > it somehow.  It isn't ok to ignore them, call other Python functions,
> > and then later check.
>
> > Kevin> +              /* Remember only non-VOID return types.  */
> > Kevin> +              if (TYPE_CODE (ret_type) != TYPE_CODE_VOID)
> > Kevin> +                {
> > Kevin> +                  self_bpfinish->return_type = type_to_type_object (ret_type);
> > Kevin> +                  self_bpfinish->function_type =
> > Kevin> +                      type_to_type_object (SYMBOL_TYPE (function));
> >
> > As discussed above, you need to check for errors immediately after each
> > call, and DTRT.  You can ignore them with PyErr_Clear.
>
> ok, I didn't know this point, so I've rewritten this to
> unconditionally clear the Py exception after these two calls, and then
> test against NULL as before.
>
> > Kevin> I think I wrote a word about that in the previous mail, anyway, my
> > Kevin> feeling was that I don't want to abort the FinishBreakpoint creation
> > Kevin> just because of a return value not computable, so I currently nullify
> > Kevin> these fields and silently disable return value computation. I can't
> > Kevin> see any straightforward way to notify Python about that,
> > Kevin> warning/exceptions won't suite ... otherwise, I could expose the
> > Kevin> return_type to the Python interface, this way, one could check that
> > Kevin> it's not None and now if GDB will/might be able to compute the return
> > Kevin> value when the FinishBP is hit
> >
> > Ok, this makes sense to me.
> >
> > Tom> bpfinishpy_detect_out_scope_cb still acquires the GIL (via
> > Tom> ensure_python_env), but should not.
> >
> > Kevin> I'm not exactly sure what was you concern here, as far as I
> > Kevin> understand, the GIL was acquired in bpfinishpy_handle_stop, so it
> > Kevin> should have no effect in detect_out_scope_cb. So I've removed it from
> > Kevin> detect_out_scope_cb as it was useless.
> >
> > I think it is inefficient to recursively acquire the GIL.
>
> right, the doc doesn't say anything about the efficiency of GIL
> acquisition, so let's
> assume you're right!
>
> > Kevin> +@defun FinishBreakpoint.__init__ (@r{[}frame@r{]} @r{[}, internal@r{]})
> > Kevin> +Create a finish breakpoint at the return address of the @code{gdb.Frame}
> > Kevin> +object @var{frame} (or @code{gdb.selected_frame()} is not provided).
> >
> > I think it should read something like:
> >
> >    Create a finish breakpoint at the return address of the @code{gdb.Frame}
> >    object @var{frame}.  If @var{frame} is not provided, this defaults to
> >    the newest frame.
>
> ok for the sentence construction,
>
> > I think it is better to default to the newest frame, because the
> > selected frame is more of a user-interface thing, and I think code
> > wanting this should explicitly use it.
>
> My thought when I chose to use 'selected_frame' was to match the
> behavior of the CLI finish command. When you type it, you finish the
> 'selected' frame, and not the newest one.
>
> In my use-cases, I always have gdb.selected_frame == gdb.newest_frame,
> so I don't have a strong preference. I've switch the code/doc to
> newest_frame.
>
> > Kevin> +type is @code{VOID}
> >
> > I think just @code{void} is better.
>
> sure
>
> > Kevin> +  /* If stop_registers where not saved, use the current registers.  */
> >
> > s/where/were/
>
> fixed
>
> > Kevin> +  if (cleanup)
> > Kevin> +    do_cleanups (cleanup);
> >
> > This is a gdb anti-pattern.  A call to make_cleanup can return NULL in
> > some scenarios.
> >
> > It is better to install a null cleanup and then unconditionally call
> > do_cleanups.
>
> ok, didn't know it either, fixed with the null cleanup
>
> > Kevin> +  /* Default to gdb.selected_frame if necessary.  */
> > Kevin> +  if (!frame_obj)
> > Kevin> +    frame_obj = gdbpy_selected_frame (NULL, NULL);
> >
> > gdbpy_newest_frame
> >
> > I don't think there is a decref for the frame_obj along this path.
>
> I've changed it to:
>
>  if (!frame_obj)
>    frame_obj = gdbpy_newest_frame (NULL, NULL);
>  else
>    Py_INCREF (frame_obj);
>
>  frame = frame_object_to_frame_info (frame_obj);
>  Py_DECREF (frame_obj);
>
> which looks clearer to me than setting a flag, or testing the kwargs
> for the "frame" keyword
>
> > Kevin> +          PyErr_SetString (PyExc_ValueError,
> > Kevin> +            _("\"FinishBreakpoint\" not meaningful in the outermost frame."));
> >
> > Kevin> +          PyErr_SetString (PyExc_ValueError,
> > Kevin> +                   _("\"FinishBreakpoint\" cannot be set on a dummy frame."));
> >
> > Indentation.
> >
> > Kevin> +            PyErr_SetString (PyExc_ValueError,
> > Kevin> +                                     _("Invalid ID for the `frame' object."));
> >
> > Indentation.
>
> These 3 lines where right-justified to column 79, I've split them.
>
> > Kevin> +static void
> > Kevin> +bpfinishpy_out_of_scope (struct finish_breakpoint_object *bpfinish_obj)
> > Kevin> +{
> > [...]
> > Kevin> +  TRY_CATCH (except, RETURN_MASK_ALL)
> > Kevin> +    {
> > Kevin> +      delete_breakpoint (bpfinish_obj->py_bp.bp);
> > Kevin> +    }
> > Kevin> +  if (except.reason < 0)
> > Kevin> +    {
> > Kevin> +      gdbpy_convert_exception (except);
> > Kevin> +      gdbpy_print_stack ();
> > Kevin> +    }
> >
> > I probably asked you to add this, but if bpfinishpy_out_of_scope can
> > only be called in one spot:
> >
> > Kevin> +          TRY_CATCH (except, RETURN_MASK_ALL)
> > Kevin> +            {
> > Kevin> +              if (b->pspace == current_inferior ()->pspace
> > Kevin> +                 && (!target_has_registers
> > Kevin> +                     || frame_find_by_id (b->frame_id) == NULL))
> > Kevin> +              {
> > Kevin> +                bpfinishpy_out_of_scope (finish_bp);
> > Kevin> +              }
> > Kevin> +            }
> > Kevin> +          if (except.reason < 0)
> > Kevin> +            {
> > Kevin> +              gdbpy_convert_exception (except);
> > Kevin> +              gdbpy_print_stack ();
> > Kevin> +            }
> >
> > ... then the TRY_CATCH in bpfinishpy_out_of_scope is not needed.
>
> right
>
> > Kevin> +  struct cleanup *cleanup = ensure_python_env (target_gdbarch,
> > Kevin> +      current_language);
> >
> > Indentation.
>
> fixed
>
>
> There is no regression against the current tree (2011-11-18)
>
>
> Thanks,
>
> Kevin

Hello,

I noticed a bug in the previous version of the patch, which led to
`out_of_scope' callback being triggered when it was not supposed to be
(namely, when the breakpoint was hit, the execution continued, and
then the callback was triggered before the temporary breakpoint could
be deleted.

I fixed it by ensuring that the breakpoint is active before triggering
the notification:
> if (bpfinish_obj->py_bp.bp->enable_state == bp_enabled
> && PyObject_HasAttrString (py_obj, outofscope_func))
> ...

Cordially,

Kevin

--

2011-11-30 Kevin Pouget <kevin.pouget@st.com>

Introduce gdb.FinishBreakpoint in Python

* Makefile.in (SUBDIR_PYTHON_OBS): Add py-finishbreakpoint.o.
(SUBDIR_PYTHON_SRCS): Add python/py-finishbreakpoint.c.
Add build rule for this file.
* infcmd.c (print_return_value): Split to create get_return_value.
(get_return_value): New function based on print_return_value. Handle
case where stop_registers are not set.
* inferior.h (get_return_value): New prototype.
* python/py-breakpoint.c (bppy_pending_object): Make non-static.
(gdbpy_breakpoint_created): Set is_py_finish_bp is necessary.
(struct breakpoint_object): Move to python-internal.h
(BPPY_REQUIRE_VALID): Likewise.
(BPPY_SET_REQUIRE_VALID): Likewise.
(gdbpy_breakpoint_created): Initialize is_finish_bp.
(gdbpy_should_stop): Add pre/post hooks before/after calling stop
method.
* python/python-internal.h (breakpoint_object_type): Add as extern.
(bppy_pending_object): Likewise.
(typedef struct breakpoint_object) Removed.
(struct breakpoint_object): Moved from py-breakpoint.c.
Add field is_finish_bp.
(BPPY_REQUIRE_VALID): Moved from py-breakpoint.c.
(BPPY_SET_REQUIRE_VALID): Likewise.
(frame_object_to_frame_info): New prototype.
(gdbpy_initialize_finishbreakpoints): New prototype.
(bpfinishpy_is_finish_bp): Likewise.
(bpfinishpy_pre_stop_hook): Likewise.
(bpfinishpy_post_stop_hook): Likewise.
* python/py-finishbreakpoint.c: New file.
* python/py-frame.c(frame_object_to_frame_info): Make non-static and
accept PyObject instead of frame_object.
(frapy_is_valid): Don't cast to frame_object.
(frapy_name): Likewise.
(frapy_type): Likewise.
(frapy_unwind_stop_reason): Likewise.
(frapy_pc): Likewise.
(frapy_block): Likewise.
(frapy_function): Likewise.
(frapy_older): Likewise.
(frapy_newer): Likewise.
(frapy_find_sal): Likewise.
(frapy_read_var): Likewise.
(frapy_select): Likewise.
* python/python.c (gdbpy_is_stopped_at_finish_bp): New noop function.
(_initialize_python): Add gdbpy_initialize_finishbreakpoints.
* python/python.h: Include breakpoint.h
(gdbpy_is_stopped_at_finish_bp): New prototype.

doc/
* gdb.texinfo (Finish Breakpoints in Python): New subsection.
(Python API): Add menu entry for Finish Breakpoints.

testsuite/
* gdb.python/py-breakpoint.exp (mult_line): Define and use variable
instead of line number.
* gdb.python/py-finish-breakpoint.c: New file.
* gdb.python/py-finish-breakpoint.exp: New file.
* gdb.python/py-finish-breakpoint.py: New file.
* gdb.python/py-finish-breakpoint2.cc: New file.
* gdb.python/py-finish-breakpoint2.exp: New file.
* gdb.python/py-finish-breakpoint2.py: New file.

[-- Attachment #2: 0001-Introduce-gdb.FinishBreakpoint.patch --]
[-- Type: text/x-patch, Size: 55855 bytes --]

From 0e18c4912b0263d8db1ea545e35e9a63744c765d Mon Sep 17 00:00:00 2001
From: Kevin Pouget <kevin.pouget@st.com>
Date: Tue, 20 Sep 2011 13:59:23 +0200
Subject: [PATCH] Introduce gdb.FinishBreakpoint

---
gdb/Makefile.in | 6 +
gdb/NEWS | 4 +
gdb/doc/gdb.texinfo | 51 +++
gdb/infcmd.c | 39 ++-
gdb/inferior.h | 3 +
gdb/python/py-breakpoint.c | 48 +--
gdb/python/py-finishbreakpoint.c | 448 ++++++++++++++++++++
gdb/python/py-frame.c | 32 +-
gdb/python/python-internal.h | 50 ++-
gdb/python/python.c | 1 +
gdb/testsuite/gdb.python/py-breakpoint.exp | 7 +-
gdb/testsuite/gdb.python/py-finish-breakpoint.c | 97 +++++
gdb/testsuite/gdb.python/py-finish-breakpoint.exp | 233 ++++++++++
gdb/testsuite/gdb.python/py-finish-breakpoint.py | 89 ++++
gdb/testsuite/gdb.python/py-finish-breakpoint2.cc | 59 +++
gdb/testsuite/gdb.python/py-finish-breakpoint2.exp | 65 +++
gdb/testsuite/gdb.python/py-finish-breakpoint2.py | 33 ++
17 files changed, 1200 insertions(+), 65 deletions(-)
create mode 100644 gdb/python/py-finishbreakpoint.c
create mode 100644 gdb/testsuite/gdb.python/py-finish-breakpoint.c
create mode 100644 gdb/testsuite/gdb.python/py-finish-breakpoint.exp
create mode 100644 gdb/testsuite/gdb.python/py-finish-breakpoint.py
create mode 100644 gdb/testsuite/gdb.python/py-finish-breakpoint2.cc
create mode 100644 gdb/testsuite/gdb.python/py-finish-breakpoint2.exp
create mode 100644 gdb/testsuite/gdb.python/py-finish-breakpoint2.py

diff --git a/gdb/Makefile.in b/gdb/Makefile.in
index 48221f2..724f25d 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -287,6 +287,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 \
@@ -318,6 +319,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 \
@@ -2108,6 +2110,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/NEWS b/gdb/NEWS
index f13caf8..7b3f7f2 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -51,6 +51,10 @@
** The "gdb.breakpoint" function has been deprecated in favor of
"gdb.breakpoints".

+ ** A new class "gdb.FinishBreakpoint" is provided to catch the return
+ of a function. This class is based on the "finish" command
+ available in the CLI.
+
** Type objects for struct and union types now allow access to
the fields using standard Python dictionary (mapping) methods.
For example, "some_type['myfield']" now works, as does
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 3321a77..eebf048 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -21502,6 +21502,8 @@ situation, a Python @code{KeyboardInterrupt} exception is thrown.
* Symbol Tables In Python:: Python representation of symbol tables.
* Lazy Strings In Python:: Python representation of lazy strings.
* Breakpoints In Python:: Manipulating breakpoints using Python.
+* Finish Breakpoints in Python:: Setting Breakpoints on function return
+ using Python.
@end menu

@node Basic Python
@@ -24336,6 +24338,55 @@ commands, separated by newlines. If there are no commands, this
attribute is @code{None}. This attribute is not writable.
@end defvar

+@node Finish Breakpoints in Python
+@subsubsection Finish Breakpoints
+
+@cindex python finish breakpoints
+@tindex gdb.FinishBreakpoint
+
+A finish breakpoint is a temporary breakpoint set at the return address of
+a frame, based on the @code{finish} command. @code{gdb.FinishBreakpoint}
+extends @code{gdb.Breakpoint}. The underlying breakpoint will be disabled
+and deleted when the execution will run out of the breakpoint scope (i.e.@:
+@code{Breakpoint.stop} or @code{FinishBreakpoint.out_of_scope} triggered).
+
+@defun FinishBreakpoint.__init__ (@r{[}frame@r{]} @r{[}, internal@r{]})
+Create a finish breakpoint at the return address of the @code{gdb.Frame}
+object @var{frame}. If @var{frame} is not provided, this defaults to the
+newest 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 defun
+
+@defun FinishBreakpoint.out_of_scope (self)
+In some circumstances (e.g.@: @code{longjmp}, C@t{++} exceptions, @value{GDBN}
+@code{return} command, @dots{}), a function may not properly terminate, and
+thus never hit the finish breakpoint. When @value{GDBN} notices such a
+situation, the @code{out_of_scope} callback will be triggered.
+
+You may want to sub-class @code{gdb.FinishBreakpoint} and override this
+method:
+
+@smallexample
+class MyFinishBreakpoint (gdb.FinishBreakpoint)
+ def stop (self):
+ print "normal finish"
+ return True
+
+ def out_of_scope ():
+ print "abnormal finish"
+@end smallexample
+@end defun
+
+@defvar FinishBreakpoint.return_value
+When @value{GDBN} is stopped at a finish breakpoint and the frame
+used to build the @code{gdb.FinishBreakpoint} object 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 function return
+type is @code{void} or if the return value was not computable. This attribute
+is not writable.
+@end defvar
+
@node Lazy Strings In Python
@subsubsection Python representation of lazy strings.

diff --git a/gdb/infcmd.c b/gdb/infcmd.c
index 7b935fe..76addf0 100644
--- a/gdb/infcmd.c
+++ b/gdb/infcmd.c
@@ -1413,16 +1413,26 @@ 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. */
+/* Return the value of the result of a function 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 regcache *stop_regs = stop_registers;
+ struct gdbarch *gdbarch;
struct value *value;
struct ui_out *uiout = current_uiout;
+ struct cleanup *cleanup = make_cleanup (null_cleanup, NULL);
+
+ /* If stop_registers were not saved, use the current registers. */
+ if (!stop_regs)
+ {
+ stop_regs = regcache_dup (get_current_regcache ());
+ cleanup = make_cleanup_regcache_xfree (stop_regs);
+ }
+
+ gdbarch = get_regcache_arch (stop_regs);

CHECK_TYPEDEF (value_type);
gdb_assert (TYPE_CODE (value_type) != TYPE_CODE_VOID);
@@ -1441,7 +1451,7 @@ print_return_value (struct type *func_type, struct type *value_type)
case RETURN_VALUE_ABI_RETURNS_ADDRESS:
case RETURN_VALUE_ABI_PRESERVES_ADDRESS:
value = allocate_value (value_type);
- gdbarch_return_value (gdbarch, func_type, value_type, stop_registers,
+ gdbarch_return_value (gdbarch, func_type, value_type, stop_regs,
value_contents_raw (value), NULL);
break;
case RETURN_VALUE_STRUCT_CONVENTION:
@@ -1451,6 +1461,21 @@ print_return_value (struct type *func_type, struct type *value_type)
internal_error (__FILE__, __LINE__, _("bad switch"));
}

+ do_cleanups (cleanup);
+
+ 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;
+ struct ui_out *uiout = current_uiout;
+
if (value)
{
struct value_print_options opts;
diff --git a/gdb/inferior.h b/gdb/inferior.h
index cfaea7f..f198696 100644
--- a/gdb/inferior.h
+++ b/gdb/inferior.h
@@ -269,6 +269,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/python/py-breakpoint.c b/gdb/python/py-breakpoint.c
index 11d60fe..64ef45f 100644
--- a/gdb/python/py-breakpoint.c
+++ b/gdb/python/py-breakpoint.c
@@ -31,52 +31,16 @@
#include "arch-utils.h"
#include "language.h"

-static 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
{
@@ -762,6 +726,9 @@ gdbpy_should_stop (struct breakpoint_object *bp_obj)
struct gdbarch *garch = b->gdbarch ? b->gdbarch : get_current_arch ();
struct cleanup *cleanup = ensure_python_env (garch, current_language);

+ if (bp_obj->is_finish_bp)
+ bpfinishpy_pre_stop_hook (bp_obj);
+
if (PyObject_HasAttrString (py_bp, stop_func))
{
PyObject *result = PyObject_CallMethod (py_bp, stop_func, NULL);
@@ -783,6 +750,10 @@ gdbpy_should_stop (struct breakpoint_object *bp_obj)
else
gdbpy_print_stack ();
}
+
+ if (bp_obj->is_finish_bp)
+ bpfinishpy_post_stop_hook (bp_obj);
+
do_cleanups (cleanup);

return stop;
@@ -845,6 +816,7 @@ gdbpy_breakpoint_created (struct breakpoint *bp)
newbp->number = bp->number;
newbp->bp = bp;
newbp->bp->py_bp_object = newbp;
+ newbp->is_finish_bp = 0;
Py_INCREF (newbp);
++bppy_live;
}
@@ -1006,7 +978,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..5dc3532
--- /dev/null
+++ b/gdb/python/py-finishbreakpoint.c
@@ -0,0 +1,448 @@
+/* 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. */
+ breakpoint_object py_bp;
+ /* gdb.Type object of the value return by the breakpointed function.
+ May be NULL if no debug information was available or return type
+ was VOID. */
+ PyObject *return_type;
+ /* gdb.Type object of the function finished by this breakpoint. Will be
+ NULL if return_type is NULL. */
+ PyObject *function_type;
+ /* When stopped at this FinishBreakpoint, gdb.Value object 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 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;
+
+ if (!self_finishbp->return_value)
+ Py_RETURN_NONE;
+
+ Py_INCREF (self_finishbp->return_value);
+ return self_finishbp->return_value;
+}
+
+/* Deallocate FinishBreakpoint object. */
+
+static void
+bpfinishpy_dealloc (PyObject *self)
+{
+ struct finish_breakpoint_object *self_bpfinish =
+ (struct finish_breakpoint_object *) self;
+
+ Py_XDECREF (self_bpfinish->function_type);
+ Py_XDECREF (self_bpfinish->return_type);
+ Py_XDECREF (self_bpfinish->return_value);
+}
+
+/* Triggered when gdbpy_should_stop is about to execute the `stop' callback
+ of the gdb.FinishBreakpoint object BP_OBJ. Will compute and cache the
+ `return_value', if possible. */
+
+void
+bpfinishpy_pre_stop_hook (struct breakpoint_object *bp_obj)
+{
+ struct finish_breakpoint_object *self_finishbp =
+ (struct finish_breakpoint_object *) bp_obj;
+ volatile struct gdb_exception except;
+
+ /* Can compute return_value only once. */
+ gdb_assert (!self_finishbp->return_value);
+
+ if (!self_finishbp->return_type)
+ return;
+
+ TRY_CATCH (except, RETURN_MASK_ALL)
+ {
+ struct value *ret =
+ get_return_value (type_object_to_type (self_finishbp->function_type),
+ type_object_to_type (self_finishbp->return_type));
+
+ if (ret)
+ self_finishbp->return_value = value_to_value_object (ret);
+ else
+ {
+ Py_INCREF (Py_None);
+ self_finishbp->return_value = Py_None;
+ }
+ }
+ if (except.reason < 0)
+ {
+ gdbpy_convert_exception (except);
+ gdbpy_print_stack ();
+ }
+}
+
+/* Triggered when gdbpy_should_stop has triggered the `stop' callback
+ of the gdb.FinishBreakpoint object BP_OBJ. */
+
+void
+bpfinishpy_post_stop_hook (struct breakpoint_object *bp_obj)
+{
+ volatile struct gdb_exception except;
+
+ TRY_CATCH (except, RETURN_MASK_ALL)
+ {
+ /* 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);
+ }
+ if (except.reason < 0)
+ {
+ gdbpy_convert_exception (except);
+ gdbpy_print_stack ();
+ }
+}
+
+/* Python function to create a new breakpoint. */
+
+static int
+bpfinishpy_init (PyObject *self, PyObject *args, PyObject *kwargs)
+{
+ static char *keywords[] = { "frame", "internal", NULL };
+ 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 = NULL;
+ 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, "|OO", keywords,
+ &frame_obj, &internal))
+ return -1;
+
+ /* Default to gdb.newest_frame if necessary. */
+ if (!frame_obj)
+ frame_obj = gdbpy_newest_frame (NULL, NULL);
+ else
+ Py_INCREF (frame_obj);
+
+ frame = frame_object_to_frame_info (frame_obj);
+ Py_DECREF (frame_obj);
+
+ if (frame == NULL)
+ goto invalid_frame;
+
+ TRY_CATCH (except, RETURN_MASK_ALL)
+ {
+ prev_frame = get_prev_frame (frame);
+ if (prev_frame == 0)
+ {
+ PyErr_SetString (PyExc_ValueError, _("\"FinishBreakpoint\" not " \
+ "meaningful in the outermost "\
+ "frame."));
+ }
+ else if (get_frame_type (prev_frame) == DUMMY_FRAME)
+ {
+ PyErr_SetString (PyExc_ValueError, _("\"FinishBreakpoint\" cannot "\
+ "be set on a dummy frame."));
+ }
+ else
+ {
+ frame_id = get_frame_id (prev_frame);
+ if (frame_id_eq (frame_id, null_frame_id))
+ PyErr_SetString (PyExc_ValueError,
+ _("Invalid ID for the `frame' object."));
+ }
+ }
+ if (except.reason < 0)
+ {
+ gdbpy_convert_exception (except);
+ return -1;
+ }
+ else if (PyErr_Occurred ())
+ return -1;
+
+ 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;
+
+ TRY_CATCH (except, RETURN_MASK_ALL)
+ {
+ 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)
+ {
+ /* Ignore Python errors at this stage. */
+ self_bpfinish->return_type = type_to_type_object (ret_type);
+ PyErr_Clear ();
+ self_bpfinish->function_type =
+ type_to_type_object (SYMBOL_TYPE (function));
+ PyErr_Clear ();
+ }
+ }
+ }
+ }
+ if (except.reason < 0
+ || !self_bpfinish->return_type || !self_bpfinish->function_type)
+ {
+ /* Won't be able to compute return value. */
+ Py_XDECREF (self_bpfinish->return_type);
+ Py_XDECREF (self_bpfinish->function_type);
+
+ self_bpfinish->return_type = NULL;
+ self_bpfinish->function_type = NULL;
+ }
+
+ bppy_pending_object = &self_bpfinish->py_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,
+ 1 /*temp_flag*/,
+ bp_breakpoint,
+ 0,
+ AUTO_BOOLEAN_TRUE,
+ &bkpt_breakpoint_ops,
+ 0, 1, internal_bp);
+ }
+ GDB_PY_SET_HANDLE_EXCEPTION (except);
+
+ self_bpfinish->py_bp.bp->frame_id = frame_id;
+ self_bpfinish->py_bp.is_finish_bp = 1;
+
+ return 0;
+
+ invalid_frame:
+ PyErr_SetString (PyExc_ValueError,
+ _("Invalid ID for the `frame' object."));
+ return -1;
+}
+
+/* Called when GDB notices that the finish breakpoint BP_OBJ is out of
+ the current callstack. Triggers the method OUT_OF_SCOPE if implemented,
+ then delete the breakpoint. */
+
+static void
+bpfinishpy_out_of_scope (struct finish_breakpoint_object *bpfinish_obj)
+{
+ volatile struct gdb_exception except;
+ breakpoint_object *bp_obj = (breakpoint_object *) bpfinish_obj;
+ PyObject *py_obj = (PyObject *) bp_obj;
+
+ if (bpfinish_obj->py_bp.bp->enable_state == bp_enabled
+ && PyObject_HasAttrString (py_obj, outofscope_func))
+ {
+ if (!PyObject_CallMethod (py_obj, outofscope_func, NULL))
+ gdbpy_print_stack ();
+ }
+
+ delete_breakpoint (bpfinish_obj->py_bp.bp);
+}
+
+/* Callback for `bpfinishpy_detect_out_scope'. Triggers Python's
+ `B->out_of_scope' function if B is a FinishBreakpoint out of its scope. */
+
+static int
+bpfinishpy_detect_out_scope_cb (struct breakpoint *b, void *args)
+{
+ volatile struct gdb_exception except;
+ 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 ();
+
+ /* 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)
+ {
+ struct finish_breakpoint_object *finish_bp =
+ (struct finish_breakpoint_object *) py_bp;
+
+ /* Check scope if not currently stopped at the FinishBreakpoint. */
+ if (b != bp_stopped)
+ {
+ TRY_CATCH (except, RETURN_MASK_ALL)
+ {
+ if (b->pspace == current_inferior ()->pspace
+ && (!target_has_registers
+ || frame_find_by_id (b->frame_id) == NULL))
+ {
+ bpfinishpy_out_of_scope (finish_bp);
+ }
+ }
+ if (except.reason < 0)
+ {
+ gdbpy_convert_exception (except);
+ gdbpy_print_stack ();
+ }
+ }
+ }
+
+ return 0;
+}
+
+/* Attached to `stop' notifications, check if the execution has run
+ out of the scope of any FinishBreakpoint before it has been hit. */
+
+static void
+bpfinishpy_handle_stop (struct bpstats *bs, int print_frame)
+{
+ struct cleanup *cleanup = ensure_python_env (get_current_arch (),
+ current_language);
+
+ iterate_over_breakpoints (bpfinishpy_detect_out_scope_cb,
+ bs == NULL ? NULL : bs->breakpoint_at);
+
+ do_cleanups (cleanup);
+}
+
+/* Attached to `exit' notifications, triggers all the necessary out of
+ scope notifications. */
+
+static void
+bpfinishpy_handle_exit (struct inferior *inf)
+{
+ struct cleanup *cleanup = ensure_python_env (target_gdbarch,
+ current_language);
+
+ iterate_over_breakpoints (bpfinishpy_detect_out_scope_cb, NULL);
+
+ do_cleanups (cleanup);
+}
+
+/* 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);
+}
+
+static PyGetSetDef finish_breakpoint_object_getset[] = {
+ { "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*/
+ bpfinishpy_dealloc, /*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 */
+ 0, /* 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 20064ca..c334f63 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);
@@ -106,7 +107,7 @@ frapy_is_valid (PyObject *self, PyObject *args)

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

@@ -130,7 +131,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);
}
@@ -159,7 +160,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);
}
@@ -180,7 +181,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);

@@ -201,7 +202,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);
}
@@ -222,7 +223,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);
@@ -263,7 +264,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));
}
@@ -330,7 +331,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)
@@ -359,7 +360,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)
@@ -388,7 +389,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);
@@ -444,7 +445,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);
@@ -472,7 +473,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);
}
@@ -487,12 +488,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 ef39d5d..1ba7133 100644
--- a/gdb/python/python-internal.h
+++ b/gdb/python/python-internal.h
@@ -120,9 +120,50 @@ 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;
+
+ /* 1 is this is a FinishBreakpoint object, 0 otherwise. */
+ int is_finish_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
{
@@ -188,6 +229,7 @@ struct value *convert_value_from_python (PyObject *obj);
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);
+struct frame_info *frame_object_to_frame_info (PyObject *frame_obj);

void gdbpy_initialize_auto_load (void);
void gdbpy_initialize_values (void);
@@ -202,6 +244,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);
@@ -275,6 +318,9 @@ PyObject *gdbpy_get_varobj_pretty_printer (struct value *value);
char *gdbpy_get_display_hint (PyObject *printer);
PyObject *gdbpy_default_visualizer (PyObject *self, PyObject *args);

+void bpfinishpy_pre_stop_hook (struct breakpoint_object *bp_obj);
+void bpfinishpy_post_stop_hook (struct breakpoint_object *bp_obj);
+
extern PyObject *gdbpy_doc_cst;
extern PyObject *gdbpy_children_cst;
extern PyObject *gdbpy_to_string_cst;
diff --git a/gdb/python/python.c b/gdb/python/python.c
index 6022572..2f8f828 100644
--- a/gdb/python/python.c
+++ b/gdb/python/python.c
@@ -1247,6 +1247,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/testsuite/gdb.python/py-breakpoint.exp b/gdb/testsuite/gdb.python/py-breakpoint.exp
index e0dd087..0e3adbd 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..7cdbead
--- /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..57dce1b
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-finish-breakpoint.exp
@@ -0,0 +1,233 @@
+# 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 the function to finish"
+
+# set FinishBreakpoint
+
+gdb_test "python finishbp_default = gdb.FinishBreakpoint ()" \
+ "Temporary breakpoint.*" "set FinishBreakpoint with default frame value"
+gdb_test "python finishbp = MyFinishBreakpoint (gdb.parse_and_eval ('a'), gdb.newest_frame ())" \
+ "Temporary breakpoint.*" "set FinishBreakpoint"
+gdb_test "python print finishbp.return_value" "None.*" \
+ "check return_value at init"
+
+# check normal bp hit
+
+gdb_test "continue" "MyFinishBreakpoint stop with.*return_value is: -5.*#0.*increase.*" \
+ "check MyFinishBreakpoint hit"
+gdb_test "python print finishbp.return_value" "-5.*" "check return_value"
+
+gdb_test "python print finishbp_default.hit_count" "1.*" "check finishBP on default frame has been hit"
+gdb_test "python print finishbp.is_valid()" "False.*"\
+ "ensure that finish bp is invalid afer normal hit"
+
+# check FinishBreakpoint in main no allowed
+
+gdb_test "finish" "main.*" "return to 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"
+set cond_line [gdb_get_line_number "Condition Break."]
+
+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"
+gdb_test "python print finishbp.is_valid()" "False.*"\
+ "ensure that finish bp is invalid afer 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"
+
+
+if ![runto_main] then {
+ fail "Cannot run to main."
+ return 0
+}
+
+gdb_test "break ${cond_line} if test_1(i,8)" "Breakpoint .* at .*" \
+ "set a conditional BP"
+gdb_test "python TestBreakpoint()" "TestBreakpoint init" \
+ "set FinishBP in a breakpoint condition"
+gdb_test "continue" \
+ "\"FinishBreakpoint\" cannot be set on a dummy frame.*" \
+ "don't allow FinishBreakpoint on dummy frames"
+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"
+
+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
+}
+
+# return address in dummy frame
+
+gdb_test "python TestExplicitBreakpoint('increase_1')" "Breakpoint.*at.*" \
+ "prepare TestExplicitBreakpoint"
+gdb_test "print increase_1(&i)" \
+ "\"FinishBreakpoint\" cannot be set on a dummy frame.*" \
+ "don't allow FinishBreakpoint on dummy frames"
+
+# return address in normal 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..dea2a73
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-finish-breakpoint.py
@@ -0,0 +1,89 @@
+# 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 ())
+ print "return_value is: %d" % int (self.return_value)
+ gdb.execute ("where 1")
+ return True
+
+ def out_of_scope(self):
+ print "MyFinishBreakpoint out of scope"
+
+class TestBreakpoint(gdb.Breakpoint):
+ def __init__(self):
+ gdb.Breakpoint.__init__ (self, spec="test_1", internal=1)
+ self.silent = True
+ self.count = 0
+ print "TestBreakpoint init"
+
+ def stop(self):
+ self.count += 1
+ try:
+ TestFinishBreakpoint (gdb.newest_frame (), self.count)
+ except ValueError as e:
+ print e
+ return False
+
+class TestFinishBreakpoint (gdb.FinishBreakpoint):
+ def __init__ (self, frame, count):
+ self.count = count
+ gdb.FinishBreakpoint.__init__ (self, frame, internal=1)
+
+
+ def stop(self):
+ print "-->", self.number
+ if (self.count == 3):
+ print "test stop: %d" % self.count
+ return True
+ else:
+ print "test don't stop: %d" % self.count
+ return False
+
+
+ def out_of_scope(self):
+ print "test didn't finish: %d" % self.count
+
+class TestExplicitBreakpoint(gdb.Breakpoint):
+ def stop(self):
+ try:
+ SimpleFinishBreakpoint (gdb.newest_frame ())
+ except ValueError as e:
+ print e
+ 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 importedd"
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..fae798c
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-finish-breakpoint2.exp
@@ -0,0 +1,65 @@
+# 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
+}
+
+#
+# Check FinishBreakpoints against C++ exceptions
+#
+
+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..0fb6955
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-finish-breakpoint2.py
@@ -0,0 +1,33 @@
+# 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"
+ return True
+
+ def out_of_scope(self):
+ print "exception did not finish ..."
+
+
+print "Python script imported"
--
1.7.6.4