From e61ed33ace88dd336eea6f5e3b7f750243087822 Mon Sep 17 00:00:00 2001 From: Kevin Pouget Date: Wed, 18 May 2011 10:02:23 -0400 Subject: [PATCH] Add bp_location to Python interface --- gdb/Makefile.in | 6 + gdb/NEWS | 6 + gdb/breakpoint.c | 3 + gdb/breakpoint.h | 6 + gdb/doc/gdb.texinfo | 44 ++++ gdb/python/py-bploc.c | 302 ++++++++++++++++++++++++++++ gdb/python/py-breakpoint.c | 39 ++++ gdb/python/python-internal.h | 6 + gdb/python/python.c | 7 + gdb/python/python.h | 2 + gdb/testsuite/gdb.python/py-breakpoint.exp | 52 +++++ 11 files changed, 473 insertions(+), 0 deletions(-) create mode 100644 gdb/python/py-bploc.c diff --git a/gdb/Makefile.in b/gdb/Makefile.in index cd93768..e4cdd5a 100644 --- a/gdb/Makefile.in +++ b/gdb/Makefile.in @@ -273,6 +273,7 @@ SUBDIR_PYTHON_OBS = \ py-auto-load.o \ py-block.o \ py-bpevent.o \ + py-bploc.o \ py-breakpoint.o \ py-cmd.o \ py-continueevent.o \ @@ -305,6 +306,7 @@ SUBDIR_PYTHON_SRCS = \ python/py-auto-load.c \ python/py-block.c \ python/py-bpevent.c \ + python/py-bploc.c \ python/py-breakpoint.c \ python/py-cmd.c \ python/py-continueevent.c \ @@ -2033,6 +2035,10 @@ py-bpevent.o: $(srcdir)/python/py-bpevent.c $(COMPILE) $(PYTHON_CFLAGS) $(srcdir)/python/py-bpevent.c $(POSTCOMPILE) +py-bploc.o: $(srcdir)/python/py-bploc.c + $(COMPILE) $(PYTHON_CFLAGS) $(srcdir)/python/py-bploc.c + $(POSTCOMPILE) + py-breakpoint.o: $(srcdir)/python/py-breakpoint.c $(COMPILE) $(PYTHON_CFLAGS) $(srcdir)/python/py-breakpoint.c $(POSTCOMPILE) diff --git a/gdb/NEWS b/gdb/NEWS index a9a7859..c529f96 100644 --- a/gdb/NEWS +++ b/gdb/NEWS @@ -9,6 +9,12 @@ * The binary "gdbtui" can no longer be built or installed. Use "gdb -tui" instead. +* Python scripting + + ** A new method "gdb.Breakpoint.locations" has been added, as well as + the class gdb.BpLocation to provide further details about breakpoint + locations. + *** Changes in GDB 7.4 * GDB now handles ambiguous linespecs more consistently; the existing diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c index 6bcedc4..623f2df 100644 --- a/gdb/breakpoint.c +++ b/gdb/breakpoint.c @@ -5679,6 +5679,9 @@ static void free_bp_location (struct bp_location *loc) { loc->ops->dtor (loc); + + gdbpy_bplocation_free (loc); + xfree (loc); } diff --git a/gdb/breakpoint.h b/gdb/breakpoint.h index c1d3be9..8d6fa19 100644 --- a/gdb/breakpoint.h +++ b/gdb/breakpoint.h @@ -26,6 +26,7 @@ struct value; struct block; struct breakpoint_object; +struct bploc_object; struct get_number_or_range_state; struct thread_info; struct bpstats; @@ -403,6 +404,11 @@ struct bp_location /* Source file name of this address. */ char *source_file; + + /* Python object associated with this location. May be NULL if the location + is not yet exported to Python. */ + + struct bploc_object *py_bploc_obj; }; /* This structure is a collection of function pointers that, if available, diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo index 2f4aa4f..f8375d6 100644 --- a/gdb/doc/gdb.texinfo +++ b/gdb/doc/gdb.texinfo @@ -24358,6 +24358,50 @@ commands, separated by newlines. If there are no commands, this attribute is @code{None}. This attribute is not writable. @end defvar +@findex gdb.locations +@defun gdb.locations () +Return a tuple containing a sequence of @code{gdb.BpLocation} objects +(see below) associated with this breakpoint. A breakpoint with no location +is a pending breakpoint (@xref{Set Breaks, , pending breakpoints}). +@end defun + +A breakpoint location is represented with a @code{gdb.BpLocation} object, +which offers the following attributes (all read only) and methods. +Please note that breakpoint locations are very transient entities in +@value{GDBN}, so one should avoid keeping references to them. + +@defvar BpLocation.owner +This attribute holds a reference to the @code{gdb.Breakpoint} object which +owns this location. This attribute is not writable. From an implementation +point of view, there is a @code{1 ... n} relation between a breakpoint and +its locations, even if two breakpoints are set on at same address. + +@end defvar + +@defvar BpLocation.enabled +This attribute indicates whether this location is currently enabled or not. +This attribute is not writable. +@end defvar + +@defvar BpLocation.inferior +This attribute holds a reference to the @code{gdb.Inferior} inferior object +in which this breakpoint location has been inserted. The value will be +@code{None} if there is no inferior associated with this location. This +attribute is not writable. +@end defvar + +@defvar BpLocation.address +This attribute holds a @code{gdb.Value} object corresponding to the address +at which the breakpoint has been inserted. This attribute is not writable. +@end defvar + +@defun BpLocation.is_valid () +Returns @code{True} if the @code{gdb.BpLocation} object is valid, +@code{False} if not. A @code{gdb.BpLocation} object may be invalidated by +GDB at any moment for internal reasons. All other @code{gdb.BpLocation} +methods and attributes will throw an exception if the object is invalid. +@end defun + @node Finish Breakpoints in Python @subsubsection Finish Breakpoints diff --git a/gdb/python/py-bploc.c b/gdb/python/py-bploc.c new file mode 100644 index 0000000..5ba3948 --- /dev/null +++ b/gdb/python/py-bploc.c @@ -0,0 +1,302 @@ +/* Python interface to breakpoint locations. + + 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 . */ + + +#include "defs.h" +#include "inferior.h" +#include "python-internal.h" +#include "observer.h" +#include "gdbarch.h" + +struct bploc_object +{ + PyObject_HEAD + + /* The location corresponding to this gdb.BpLocation object. It's the same + idea as gdb.Breakpoint, if the 'backend' location is deleted, LOC is + set to NULL. No reference to the location is owned here (in terms of + ref. counting) in order not to change the internal behavior. */ + struct bp_location *loc; + + /* 1 if the owner BP has been deleted, 0 otherwise. */ + int invalid_owner; +}; + +/* Require that LOCATION be a valid bp_location; throw a Python + exception if it is invalid. */ +#define BPLOCPY_REQUIRE_VALID(Location) \ + do { \ + if ((Location)->loc == NULL) \ + return PyErr_Format (PyExc_RuntimeError, \ + _("BpLocation invalid.")); \ + } while (0) + +static PyTypeObject bploc_object_type; + +/* Call by free_bp_location when loc is about to be freed. */ + +void +gdbpy_bplocation_free (struct bp_location *loc) +{ + if (loc->py_bploc_obj) + { + loc->py_bploc_obj->loc = NULL; + Py_DECREF (loc->py_bploc_obj); + } +} + +/* Dissociate the bp_location from the Python object. */ + +static void +bplocpy_dealloc (PyObject *self) +{ + bploc_object *self_bploc = (bploc_object *) self; + + if (self_bploc->loc != NULL) + self_bploc->loc->py_bploc_obj = NULL; + + self->ob_type->tp_free (self); +} + +/* Create or acquire a ref to the bp_location object (gdb.BpLocation) + that encapsulates the struct bp_location from GDB. */ + +PyObject * +bplocation_to_bplocation_object (struct bp_location *loc) +{ + bploc_object *bploc_obj; + + gdb_assert (loc != NULL); + if (loc->py_bploc_obj) + { + Py_INCREF (loc->py_bploc_obj); + return (PyObject *) loc->py_bploc_obj; + } + + bploc_obj = PyObject_New (bploc_object, &bploc_object_type); + if (!bploc_obj) + return NULL; + + bploc_obj->loc = loc; + bploc_obj->invalid_owner = 0; + Py_INCREF (bploc_obj); + loc->py_bploc_obj = bploc_obj; + + return (PyObject *) bploc_obj; +} + +/* Python function to get the BP owning this location, if any. */ + +static PyObject * +bplocpy_get_owner (PyObject *self, void *closure) +{ + bploc_object *self_bploc = (bploc_object *) self; + + BPLOCPY_REQUIRE_VALID (self_bploc); + + if (self_bploc->invalid_owner) + Py_RETURN_NONE; + + if (self_bploc->loc->owner + && self_bploc->loc->owner->py_bp_object) + { + Py_INCREF (self_bploc->loc->owner->py_bp_object); + return (PyObject *) self_bploc->loc->owner->py_bp_object; + } + + Py_RETURN_NONE; +} + +/* Python function to test whether or not this breakpoint location is + enabled. */ + +static PyObject * +bplocpy_get_enabled (PyObject *self, void *closure) +{ + bploc_object *self_bploc = (bploc_object *) self; + + BPLOCPY_REQUIRE_VALID (self_bploc); + + if (self_bploc->loc->enabled) + Py_RETURN_TRUE; + + Py_RETURN_FALSE; +} + +/* Python function to get the address of this breakpoint location. The + gdb.Value object will be cached if this is the first access. Returns + NULL in case of failure, with a Python exception set. */ + +static PyObject * +bplocpy_get_address (PyObject *self, void *closure) +{ + bploc_object *self_bploc = (bploc_object *) self; + volatile struct gdb_exception except; + struct type *void_ptr_type; + struct value *val = NULL; /* Initialize to appease gcc warning. */ + PyObject *py_address; + + BPLOCPY_REQUIRE_VALID (self_bploc); + + /* Get the address Value object as a void * value. */ + TRY_CATCH (except, RETURN_MASK_ALL) + { + void_ptr_type = lookup_pointer_type ( + builtin_type (python_gdbarch)->builtin_void); + val = value_from_pointer (void_ptr_type, self_bploc->loc->address); + } + GDB_PY_HANDLE_EXCEPTION (except); + + py_address = value_to_value_object (val); + if (!py_address) + return NULL; + + return py_address; +} + +/* Python function to get the inferior hosting this breakpoint location. + Return Py_None if there is no inferior associated with the program space of + this location, or NULL in case of failure, with a python exception set. */ + +static PyObject * +bplocpy_get_inferior (PyObject *self, void *closure) +{ + bploc_object *self_bploc = (bploc_object *) self; + struct inferior *inf; + PyObject *infobj; + + BPLOCPY_REQUIRE_VALID (self_bploc); + + inf = find_inferior_for_program_space (self_bploc->loc->pspace); + if (!inf) + Py_RETURN_NONE; + + infobj = inferior_to_inferior_object (inf); + Py_XINCREF (infobj); + + return infobj; +} + +/* Python function which checks the validity of a bp location object. */ + +static PyObject * +bplocpy_is_valid (PyObject *self, PyObject *args) +{ + bploc_object *self_bploc = (bploc_object *) self; + + if (self_bploc->loc) + Py_RETURN_TRUE; + Py_RETURN_FALSE; +} + +/* Callback triggered when a breakpoint is deleted. This will invalidate + the corresponding bp_location Python object owners. */ + +static void +bplocpy_breakpoint_deleted (struct breakpoint *b) { + struct bp_location *loc; + + for (loc = b->loc; loc; loc = loc->next) + { + if (loc->py_bploc_obj) + loc->py_bploc_obj->invalid_owner = 1; + } +} + +/* Initialize the Python bp_location code. */ + +void +gdbpy_initialize_bplocation (void) +{ + if (PyType_Ready (&bploc_object_type) < 0) + return; + + Py_INCREF (&bploc_object_type); + if (PyModule_AddObject (gdb_module, "BpLocation", + (PyObject *) &bploc_object_type) < 0) + return; + + observer_attach_breakpoint_deleted (bplocpy_breakpoint_deleted); +} + +static PyGetSetDef bploc_object_getset[] = +{ + { "owner", bplocpy_get_owner, NULL, + "Each breakpoint location must belong to exactly one higher-level \ +breakpoint. This pointer is NULL iff this bp_location is no \ +longer attached to a breakpoint (read-only).", + NULL }, + { "enabled", bplocpy_get_enabled, NULL, + "Is this particular location enabled.", NULL }, + { "address", bplocpy_get_address, NULL, + "The address at which the breakpoint has been set.", NULL }, + { "inferior", bplocpy_get_inferior, NULL, + "The inferior in which this breakpoint location has been set.", NULL }, + { NULL } /* Sentinel. */ +}; + + +static PyMethodDef bploc_object_methods[] = +{ + { "is_valid", bplocpy_is_valid, METH_NOARGS, + "Return true if this breakpoint location is valid, false if not." }, + { NULL } /* Sentinel. */ +}; + +static PyTypeObject bploc_object_type = +{ + PyObject_HEAD_INIT (NULL) + 0, /* ob_size */ + "gdb.BpLocation", /* tp_name */ + sizeof (bploc_object), /* tp_basicsize */ + 0, /* tp_itemsize */ + bplocpy_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 breakpoint location object", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + bploc_object_methods, /* tp_methods */ + 0, /* tp_members */ + bploc_object_getset, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0 /* tp_alloc */ +}; diff --git a/gdb/python/py-breakpoint.c b/gdb/python/py-breakpoint.c index 195ed2b..25ef62f 100644 --- a/gdb/python/py-breakpoint.c +++ b/gdb/python/py-breakpoint.c @@ -584,6 +584,43 @@ bppy_get_ignore_count (PyObject *self, void *closure) return PyInt_FromLong (self_bp->bp->ignore_count); } + +/* Python function which returns the BpLocation objects associated + with this breakpoint. */ + +static PyObject * +bppy_locations (PyObject *self, PyObject *args) +{ + breakpoint_object *self_bp = (breakpoint_object *) self; + PyObject *list, *tuple; + struct bp_location *loc; + int err; + + BPPY_REQUIRE_VALID (self_bp); + + list = PyList_New (0); + if (!list) + return NULL; + + err = 0; + for (loc = self_bp->bp->loc; loc; loc = loc->next) + { + PyObject *loc_obj = bplocation_to_bplocation_object (loc); + err = PyList_Append (list, loc_obj); + if (err == -1) + { + Py_DECREF (list); + return NULL; + } + Py_DECREF (loc_obj); + } + + tuple = PyList_AsTuple (list); + Py_DECREF (list); + + return tuple; +} + /* Python function to create a new breakpoint. */ static int bppy_init (PyObject *self, PyObject *args, PyObject *kwargs) @@ -975,6 +1012,8 @@ static PyMethodDef breakpoint_object_methods[] = "Return true if this breakpoint is valid, false if not." }, { "delete", bppy_delete_breakpoint, METH_NOARGS, "Delete the underlying GDB breakpoint." }, + { "locations", bppy_locations, METH_NOARGS, + "Get a list of gdb.BpLocation objects associated with this breakpoint." }, { NULL } /* Sentinel. */ }; diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h index 328e5d8..14608c2 100644 --- a/gdb/python/python-internal.h +++ b/gdb/python/python-internal.h @@ -165,6 +165,9 @@ typedef struct breakpoint_object extern breakpoint_object *bppy_pending_object; +/* Defined in py-bploc.c */ +typedef struct bploc_object bploc_object; + typedef struct { PyObject_HEAD @@ -217,6 +220,8 @@ PyObject *pspy_get_printers (PyObject *, void *); PyObject *objfile_to_objfile_object (struct objfile *); PyObject *objfpy_get_printers (PyObject *, void *); +PyObject *bplocation_to_bplocation_object (struct bp_location *loc); + thread_object *create_thread_object (struct thread_info *tp); thread_object *find_thread_object (ptid_t ptid); PyObject *find_inferior_object (int pid); @@ -245,6 +250,7 @@ void gdbpy_initialize_pspace (void); void gdbpy_initialize_objfile (void); void gdbpy_initialize_breakpoints (void); void gdbpy_initialize_finishbreakpoints (void); +void gdbpy_initialize_bplocation (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 5212d4e..fb88b5e 100644 --- a/gdb/python/python.c +++ b/gdb/python/python.c @@ -1101,6 +1101,12 @@ gdbpy_breakpoint_has_py_cond (struct breakpoint_object *bp_obj) "scripting is not supported.")); } +void +gdbpy_bplocation_free (struct breakpoint_object *bp_obj) +{ + return; +} + #endif /* HAVE_PYTHON */ @@ -1244,6 +1250,7 @@ message == an error message without a stack will be printed."), gdbpy_initialize_objfile (); gdbpy_initialize_breakpoints (); gdbpy_initialize_finishbreakpoints (); + gdbpy_initialize_bplocation (); gdbpy_initialize_lazy_string (); gdbpy_initialize_thread (); gdbpy_initialize_inferior (); diff --git a/gdb/python/python.h b/gdb/python/python.h index 9e461f7..516a94c 100644 --- a/gdb/python/python.h +++ b/gdb/python/python.h @@ -47,4 +47,6 @@ int gdbpy_should_stop (struct breakpoint_object *bp_obj); int gdbpy_breakpoint_has_py_cond (struct breakpoint_object *bp_obj); +void gdbpy_bplocation_free (struct bp_location *loc); + #endif /* GDB_PYTHON_H */ diff --git a/gdb/testsuite/gdb.python/py-breakpoint.exp b/gdb/testsuite/gdb.python/py-breakpoint.exp index 8ed3742..6a1a43b 100644 --- a/gdb/testsuite/gdb.python/py-breakpoint.exp +++ b/gdb/testsuite/gdb.python/py-breakpoint.exp @@ -304,3 +304,55 @@ gdb_py_test_silent_cmd "python wp1 = wp_eval (\"result\", type=gdb.BP_WATCHPOIN gdb_test "continue" ".*\[Ww\]atchpoint.*result.*Old value =.*New value = 788.*" "Test watchpoint write" gdb_test "python print never_eval_bp1.count" "0" \ "Check that this unrelated breakpoints eval function was never called." + +# gdb.BpLocation + +# Start with a fresh gdb. +clean_restart ${testfile} + +if ![runto_main] then { + fail "Cannot run to main." + return 0 +} +delete_breakpoints +gdb_test_no_output "set detach-on-fork off" "don't detach on fork" +gdb_test "call fork()" "New process .*" "create a second inferior" + +gdb_breakpoint "main" +gdb_test "py print len(gdb.breakpoints())" "1" "ensure that threre is only one BP" +gdb_test_no_output {py bp0 = gdb.breakpoints()[0]} "save breakpoint 0" +gdb_test "py print len(bp0.locations())" "2" "ensure that threre are 2 locations" + +gdb_test_no_output {py loc0 = bp0.locations()[0]} "save location 0" +gdb_test_no_output {py loc1 = bp0.locations()[1]} "save location 1" + +gdb_test "py print loc0.owner == loc1.owner == bp0" "True" "verify ownership" +gdb_test "py print loc0.address == loc1.address " "True" "verify addresses are identical" +# how to check address location ? != address(main) + +gdb_test {py print loc0.inferior == gdb.inferiors()[0]} "True" "verify inferior for loc 0" #inf 2 +gdb_test {py print loc1.inferior == gdb.inferiors()[1]} "True" "verify inferior for loc 1" #inf 1 + +gdb_test "py print loc0.enabled == loc1.enabled == True" "True" "verify that locations are enabled" + +gdb_test "py print loc0.inferior.num" "2" "ensure that loc0 is on inferior 2" + +gdb_test "detach inferior 2" "Detaching from program:.*" "detach inferior 2" +gdb_test "inferior 1" "Switching to inferior .*" "switch to inferior 1" +gdb_test_no_output "remove-inferiors 2" "remove inferior 2" +gdb_test "py print loc0.inferior" "None" "removed inferior set to None" + +delete_breakpoints +gdb_test "py print bp0.is_valid()" "False" "verify that BP has been invalidated" +gdb_test "py bp0.locations()" ".*RuntimeError: Breakpoint .* is invalid.*"\ + "verify that locations can't accessed on an invalid breakpoint" + +gdb_test "py print loc0.is_valid()" "False" "verify that location is invalid" +gdb_test "py print loc0.owner" ".*RuntimeError: BpLocation invalid.*"\ + "verify that owner can't be accessed" +gdb_test "py print loc0.enabled" ".*RuntimeError: BpLocation invalid.*"\ + "verify that location can't be accessed" +gdb_test "py print loc0.address" ".*RuntimeError: BpLocation invalid.*"\ + "verify that location can't be accessed" +gdb_test "py print loc0.inferior" ".*RuntimeError: BpLocation invalid.*"\ + "verify that inferior can't be accessed" -- 1.7.6.5