forked from Imagelibrary/binutils-gdb
Unfortunately, PyGetSetDef's 'name' and 'doc' members are 'char *'
instead of 'const char *', meaning that in order to list-initialize
PyGetSetDef arrays using string literals requires writing explicit
'char *' casts. For example:
static PyGetSetDef value_object_getset[] = {
- { "address", valpy_get_address, NULL, "The address of the value.",
+ { (char *) "address", valpy_get_address, NULL,
+ (char *) "The address of the value.",
NULL },
- { "is_optimized_out", valpy_get_is_optimized_out, NULL,
- "Boolean telling whether the value is optimized "
+ { (char *) "is_optimized_out", valpy_get_is_optimized_out, NULL,
+ (char *) "Boolean telling whether the value is optimized "
"out (i.e., not available).",
NULL },
- { "type", valpy_get_type, NULL, "Type of the value.", NULL },
- { "dynamic_type", valpy_get_dynamic_type, NULL,
- "Dynamic type of the value.", NULL },
- { "is_lazy", valpy_get_is_lazy, NULL,
- "Boolean telling whether the value is lazy (not fetched yet\n\
+ { (char *) "type", valpy_get_type, NULL,
+ (char *) "Type of the value.", NULL },
+ { (char *) "dynamic_type", valpy_get_dynamic_type, NULL,
+ (char *) "Dynamic type of the value.", NULL },
+ { (char *) "is_lazy", valpy_get_is_lazy, NULL,
+ (char *) "Boolean telling whether the value is lazy (not fetched yet\n\
from the inferior). A lazy value is fetched when needed, or when\n\
the \"fetch_lazy()\" method is called.", NULL },
{NULL} /* Sentinel */
We have ~20 such arrays, and I first wrote a patch that fixed all of
them like that... It's not pretty...
One way to make these a bit less ugly would be add a new macro that
hides the casts, like:
#define GDBPY_GSDEF(NAME, GET, SET, DOC, CLOSURE) \
{ (char *) NAME, GET, SET, (char *) DOC, CLOSURE }
and then use it like:
static PyGetSetDef value_object_getset[] = {
GDBPY_GSDEF ("address", valpy_get_address, NULL,
"The address of the value.", NULL),
GDBPY_GSDEF ("is_optimized_out", valpy_get_is_optimized_out, NULL,
"Boolean telling whether the value is optimized ", NULL),
{NULL} /* Sentinel */
};
But since we have C++11, which gives us constexpr and list
initialization, I thought of a way that requires no changes where the
arrays are initialized:
We add a new type that extends PyGetSetDef (called gdb_PyGetSetDef),
and add constexpr constructors that accept const 'name' and 'doc', and
then list/aggregate initialization simply "calls" these matching
constructors instead.
I put "calls" in quotes, because given "constexpr", it's all done at
compile time, and there's no overhead either in binary size or at run
time. In fact, we get identical binaries, before/after this change.
Unlike the fixes that fix some old Python API to match the API of more
recent Python, this switches to using explicit "gdb_PyGetSetDef"
everywhere, just to be clear that we are using our own version of it.
gdb/ChangeLog:
2017-04-05 Pedro Alves <palves@redhat.com>
* python/python-internal.h (gdb_PyGetSetDef): New type.
* python/py-block.c (block_object_getset)
(breakpoint_object_getset): Now a gdb_PyGetSetDef array.
* python/py-event.c (event_object_getset)
(finish_breakpoint_object_getset): Likewise.
* python/py-inferior.c (inferior_object_getset): Likewise.
* python/py-infthread.c (thread_object_getset): Likewise.
* python/py-lazy-string.c (lazy_string_object_getset): Likewise.
* python/py-linetable.c (linetable_entry_object_getset): Likewise.
* python/py-objfile.c (objfile_getset): Likewise.
* python/py-progspace.c (pspace_getset): Likewise.
* python/py-record-btrace.c (btpy_insn_getset, btpy_call_getset):
Likewise.
* python/py-record.c (recpy_record_getset): Likewise.
* python/py-symbol.c (symbol_object_getset): Likewise.
* python/py-symtab.c (symtab_object_getset, sal_object_getset):
Likewise.
* python/py-type.c (type_object_getset, field_object_getset):
Likewise.
* python/py-value.c (value_object_getset): Likewise.
477 lines
14 KiB
C
477 lines
14 KiB
C
/* Python interface to finish breakpoints
|
|
|
|
Copyright (C) 2011-2017 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 "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"
|
|
#include "block.h"
|
|
#include "location.h"
|
|
#include "py-ref.h"
|
|
|
|
/* 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. */
|
|
gdbpy_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.Value object of the function finished by this breakpoint. Will be
|
|
NULL if return_type is NULL. */
|
|
PyObject *function_value;
|
|
/* 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;
|
|
};
|
|
|
|
extern PyTypeObject finish_breakpoint_object_type
|
|
CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF ("finish_breakpoint_object");
|
|
|
|
/* 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_value);
|
|
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 gdbpy_breakpoint_object *bp_obj)
|
|
{
|
|
struct finish_breakpoint_object *self_finishbp =
|
|
(struct finish_breakpoint_object *) bp_obj;
|
|
|
|
/* Can compute return_value only once. */
|
|
gdb_assert (!self_finishbp->return_value);
|
|
|
|
if (!self_finishbp->return_type)
|
|
return;
|
|
|
|
TRY
|
|
{
|
|
struct value *function =
|
|
value_object_to_value (self_finishbp->function_value);
|
|
struct type *value_type =
|
|
type_object_to_type (self_finishbp->return_type);
|
|
struct value *ret = get_return_value (function, value_type);
|
|
|
|
if (ret)
|
|
{
|
|
self_finishbp->return_value = value_to_value_object (ret);
|
|
if (!self_finishbp->return_value)
|
|
gdbpy_print_stack ();
|
|
}
|
|
else
|
|
{
|
|
Py_INCREF (Py_None);
|
|
self_finishbp->return_value = Py_None;
|
|
}
|
|
}
|
|
CATCH (except, RETURN_MASK_ALL)
|
|
{
|
|
gdbpy_convert_exception (except);
|
|
gdbpy_print_stack ();
|
|
}
|
|
END_CATCH
|
|
}
|
|
|
|
/* Triggered when gdbpy_should_stop has triggered the `stop' callback
|
|
of the gdb.FinishBreakpoint object BP_OBJ. */
|
|
|
|
void
|
|
bpfinishpy_post_stop_hook (struct gdbpy_breakpoint_object *bp_obj)
|
|
{
|
|
|
|
TRY
|
|
{
|
|
/* 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);
|
|
}
|
|
CATCH (except, RETURN_MASK_ALL)
|
|
{
|
|
gdbpy_convert_exception (except);
|
|
gdbpy_print_stack ();
|
|
}
|
|
END_CATCH
|
|
}
|
|
|
|
/* 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;
|
|
PyObject *frame_obj = NULL;
|
|
int thread;
|
|
struct frame_info *frame = NULL; /* init for gcc -Wall */
|
|
struct frame_info *prev_frame = NULL;
|
|
struct frame_id frame_id;
|
|
PyObject *internal = NULL;
|
|
int internal_bp = 0;
|
|
CORE_ADDR pc;
|
|
struct symbol *function;
|
|
|
|
if (!PyArg_ParseTupleAndKeywords (args, kwargs, "|OO", keywords,
|
|
&frame_obj, &internal))
|
|
return -1;
|
|
|
|
TRY
|
|
{
|
|
/* Default frame to newest frame if necessary. */
|
|
if (frame_obj == NULL)
|
|
frame = get_current_frame ();
|
|
else
|
|
frame = frame_object_to_frame_info (frame_obj);
|
|
|
|
if (frame == NULL)
|
|
{
|
|
PyErr_SetString (PyExc_ValueError,
|
|
_("Invalid ID for the `frame' object."));
|
|
}
|
|
else
|
|
{
|
|
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."));
|
|
}
|
|
}
|
|
}
|
|
CATCH (except, RETURN_MASK_ALL)
|
|
{
|
|
gdbpy_convert_exception (except);
|
|
return -1;
|
|
}
|
|
END_CATCH
|
|
|
|
if (PyErr_Occurred ())
|
|
return -1;
|
|
|
|
thread = ptid_to_global_thread_id (inferior_ptid);
|
|
if (thread == 0)
|
|
{
|
|
PyErr_SetString (PyExc_ValueError,
|
|
_("No thread currently selected."));
|
|
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_value = NULL;
|
|
|
|
TRY
|
|
{
|
|
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)
|
|
{
|
|
struct value *func_value;
|
|
|
|
/* Ignore Python errors at this stage. */
|
|
self_bpfinish->return_type = type_to_type_object (ret_type);
|
|
PyErr_Clear ();
|
|
func_value = read_var_value (function, NULL, frame);
|
|
self_bpfinish->function_value =
|
|
value_to_value_object (func_value);
|
|
PyErr_Clear ();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
CATCH (except, RETURN_MASK_ALL)
|
|
{
|
|
/* Just swallow. Either the return type or the function value
|
|
remain NULL. */
|
|
}
|
|
END_CATCH
|
|
|
|
if (self_bpfinish->return_type == NULL || self_bpfinish->function_value == NULL)
|
|
{
|
|
/* Won't be able to compute return value. */
|
|
Py_XDECREF (self_bpfinish->return_type);
|
|
Py_XDECREF (self_bpfinish->function_value);
|
|
|
|
self_bpfinish->return_type = NULL;
|
|
self_bpfinish->function_value = NULL;
|
|
}
|
|
|
|
bppy_pending_object = &self_bpfinish->py_bp;
|
|
bppy_pending_object->number = -1;
|
|
bppy_pending_object->bp = NULL;
|
|
|
|
TRY
|
|
{
|
|
struct event_location *location;
|
|
struct cleanup *back_to;
|
|
|
|
/* Set a breakpoint on the return address. */
|
|
location = new_address_location (get_frame_pc (prev_frame), NULL, 0);
|
|
back_to = make_cleanup_delete_event_location (location);
|
|
create_breakpoint (python_gdbarch,
|
|
location, NULL, thread, NULL,
|
|
0,
|
|
1 /*temp_flag*/,
|
|
bp_breakpoint,
|
|
0,
|
|
AUTO_BOOLEAN_TRUE,
|
|
&bkpt_breakpoint_ops,
|
|
0, 1, internal_bp, 0);
|
|
do_cleanups (back_to);
|
|
}
|
|
CATCH (except, RETURN_MASK_ALL)
|
|
{
|
|
GDB_PY_SET_HANDLE_EXCEPTION (except);
|
|
}
|
|
END_CATCH
|
|
|
|
self_bpfinish->py_bp.bp->frame_id = frame_id;
|
|
self_bpfinish->py_bp.is_finish_bp = 1;
|
|
|
|
/* Bind the breakpoint with the current program space. */
|
|
self_bpfinish->py_bp.bp->pspace = current_program_space;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* 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)
|
|
{
|
|
gdbpy_breakpoint_object *bp_obj = (gdbpy_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))
|
|
{
|
|
gdbpy_ref<> meth_result (PyObject_CallMethod (py_obj, outofscope_func,
|
|
NULL));
|
|
if (meth_result == 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)
|
|
{
|
|
struct breakpoint *bp_stopped = (struct breakpoint *) args;
|
|
PyObject *py_bp = (PyObject *) b->py_bp_object;
|
|
|
|
/* 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
|
|
{
|
|
if (b->pspace == current_inferior ()->pspace
|
|
&& (!target_has_registers
|
|
|| frame_find_by_id (b->frame_id) == NULL))
|
|
bpfinishpy_out_of_scope (finish_bp);
|
|
}
|
|
CATCH (except, RETURN_MASK_ALL)
|
|
{
|
|
gdbpy_convert_exception (except);
|
|
gdbpy_print_stack ();
|
|
}
|
|
END_CATCH
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
gdbpy_enter enter_py (get_current_arch (), current_language);
|
|
|
|
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)
|
|
{
|
|
gdbpy_enter enter_py (target_gdbarch (), current_language);
|
|
|
|
iterate_over_breakpoints (bpfinishpy_detect_out_scope_cb, NULL);
|
|
}
|
|
|
|
/* Initialize the Python finish breakpoint code. */
|
|
|
|
int
|
|
gdbpy_initialize_finishbreakpoints (void)
|
|
{
|
|
if (PyType_Ready (&finish_breakpoint_object_type) < 0)
|
|
return -1;
|
|
|
|
if (gdb_pymodule_addobject (gdb_module, "FinishBreakpoint",
|
|
(PyObject *) &finish_breakpoint_object_type) < 0)
|
|
return -1;
|
|
|
|
observer_attach_normal_stop (bpfinishpy_handle_stop);
|
|
observer_attach_inferior_exit (bpfinishpy_handle_exit);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static gdb_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. */
|
|
};
|
|
|
|
PyTypeObject finish_breakpoint_object_type =
|
|
{
|
|
PyVarObject_HEAD_INIT (NULL, 0)
|
|
"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 */
|
|
};
|