
Currently, gdb's Python layer captures the current architecture and language when "entering" Python code. This has some undesirable effects, and so this series changes how this is handled. First, there is code like this: gdbpy_enter enter_py (python_gdbarch, python_language); This is incorrect, because both of these are NULL when not otherwise assigned. This can cause crashes in some cases -- I've added one to the test suite. (Note that this crasher is just an example, other ones along the same lines are possible.) Second, when the language is captured in this way, it means that Python code cannot affect the current language for its own purposes. It's reasonable to want to write code like this: gdb.execute('set language mumble') ... stuff using the current language gdb.execute('set language previous-value') However, this won't actually work, because the language is captured on entry. I've added a test to show this as well. This patch changes gdb to try to avoid capturing the current values. The Python concept of the current gdbarch is only set in those few cases where a non-default value is computed or needed; and the language is not captured at all -- instead, in the cases where it's required, the current language is temporarily changed.
468 lines
14 KiB
C
468 lines
14 KiB
C
/* Python interface to finish breakpoints
|
|
|
|
Copyright (C) 2011-2022 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 "observable.h"
|
|
#include "inferior.h"
|
|
#include "block.h"
|
|
#include "location.h"
|
|
|
|
/* Function that is called when a Python finish bp is found out of scope. */
|
|
static const char 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);
|
|
Py_TYPE (self)->tp_free (self);
|
|
}
|
|
|
|
/* 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 (const gdb_exception &except)
|
|
{
|
|
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 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 (const gdb_exception &except)
|
|
{
|
|
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 const 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 (!gdb_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 (const gdb_exception &except)
|
|
{
|
|
gdbpy_convert_exception (except);
|
|
return -1;
|
|
}
|
|
|
|
if (PyErr_Occurred ())
|
|
return -1;
|
|
|
|
if (inferior_ptid == null_ptid)
|
|
{
|
|
PyErr_SetString (PyExc_ValueError,
|
|
_("No thread currently selected."));
|
|
return -1;
|
|
}
|
|
|
|
thread = inferior_thread ()->global_num;
|
|
|
|
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 =
|
|
check_typedef (TYPE_TARGET_TYPE (SYMBOL_TYPE (function)));
|
|
|
|
/* Remember only non-void return types. */
|
|
if (ret_type->code () != 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 (const gdb_exception &except)
|
|
{
|
|
/* Just swallow. Either the return type or the function value
|
|
remain NULL. */
|
|
}
|
|
|
|
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
|
|
{
|
|
/* Set a breakpoint on the return address. */
|
|
event_location_up location
|
|
= new_address_location (get_frame_pc (prev_frame), NULL, 0);
|
|
create_breakpoint (gdbpy_enter::get_gdbarch (),
|
|
location.get (), NULL, thread, NULL, false,
|
|
0,
|
|
1 /*temp_flag*/,
|
|
bp_breakpoint,
|
|
0,
|
|
AUTO_BOOLEAN_TRUE,
|
|
&bkpt_breakpoint_ops,
|
|
0, 1, internal_bp, 0);
|
|
}
|
|
catch (const gdb_exception &except)
|
|
{
|
|
GDB_PY_SET_HANDLE_EXCEPTION (except);
|
|
}
|
|
|
|
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 void
|
|
bpfinishpy_detect_out_scope_cb (struct breakpoint *b,
|
|
struct breakpoint *bp_stopped)
|
|
{
|
|
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 (const gdb_exception &except)
|
|
{
|
|
gdbpy_convert_exception (except);
|
|
gdbpy_print_stack ();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* 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 bpstat *bs, int print_frame)
|
|
{
|
|
gdbpy_enter enter_py;
|
|
|
|
for (breakpoint *bp : all_breakpoints_safe ())
|
|
bpfinishpy_detect_out_scope_cb (bp, 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 ());
|
|
|
|
for (breakpoint *bp : all_breakpoints_safe ())
|
|
bpfinishpy_detect_out_scope_cb (bp, nullptr);
|
|
}
|
|
|
|
/* 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;
|
|
|
|
gdb::observers::normal_stop.attach (bpfinishpy_handle_stop,
|
|
"py-finishbreakpoint");
|
|
gdb::observers::inferior_exit.attach (bpfinishpy_handle_exit,
|
|
"py-finishbreakpoint");
|
|
|
|
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 */
|
|
};
|