mirror of
https://sourceware.org/git/binutils-gdb.git
synced 2025-03-01 13:26:47 +08:00
Currently, when we add a new python sub-system to GDB, e.g. py-inferior.c, we end up having to create a new function like gdbpy_initialize_inferior, which then has to be called from the function do_start_initialization in python.c. In some cases (py-micmd.c and py-tui.c), we have two functions gdbpy_initialize_*, and gdbpy_finalize_*, with the second being called from finalize_python which is also in python.c. This commit proposes a mechanism to manage these initialization and finalization calls, this means that adding a new Python subsystem will no longer require changes to python.c or python-internal.h, instead, the initialization and finalization functions will be registered directly from the sub-system file, e.g. py-inferior.c, or py-micmd.c. The initialization and finalization functions are managed through a new class gdbpy_initialize_file in python-internal.h. This class contains a single global vector of all the initialization and finalization functions. In each Python sub-system we create a new gdbpy_initialize_file object, the object constructor takes care of registering the two callback functions. Now from python.c we can call static functions on the gdbpy_initialize_file class which take care of walking the callback list and invoking each callback in turn. To slightly simplify the Python sub-system files I added a new macro GDBPY_INITIALIZE_FILE, which hides the need to create an object. We can now just do this: GDBPY_INITIALIZE_FILE (gdbpy_initialize_registers); One possible problem with this change is that there is now no guaranteed ordering of how the various sub-systems are initialized (or finalized). To try and avoid dependencies creeping in I have added a use of the environment variable GDB_REVERSE_INIT_FUNCTIONS, this is the same environment variable used in the generated init.c file. Just like with init.c, when this environment variable is set we reverse the list of Python initialization (and finalization) functions. As there is already a test that starts GDB with the environment variable set then this should offer some level of protection against dependencies creeping in - though for full protection I guess we'd need to run all gdb.python/*.exp tests with the variable set. I have tested this patch with the environment variable set, and saw no regressions, so I think we are fine right now. One other change of note was for gdbpy_initialize_gdb_readline, this function previously returned void. In order to make this function have the correct signature I've updated its return type to int, and we now return 0 to indicate success. All of the other initialize (and finalize) functions have been made static within their respective sub-system files. There should be no user visible changes after this commit.
511 lines
15 KiB
C
511 lines
15 KiB
C
/* Python interface to finish breakpoints
|
||
|
||
Copyright (C) 2011-2023 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 "top.h" /* For quit_force(). */
|
||
#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.Symbol object of the function finished by this breakpoint.
|
||
|
||
nullptr if no debug information was available or return type was VOID. */
|
||
PyObject *func_symbol;
|
||
|
||
/* gdb.Value object of the function finished by this breakpoint.
|
||
|
||
nullptr if no debug information was available or return type was VOID. */
|
||
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;
|
||
|
||
/* The initiating frame for this operation, used to decide when we have
|
||
left this frame. */
|
||
struct frame_id initiating_frame;
|
||
};
|
||
|
||
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->func_symbol);
|
||
Py_XDECREF (self_bpfinish->function_value);
|
||
Py_XDECREF (self_bpfinish->return_value);
|
||
Py_TYPE (self)->tp_free (self);
|
||
}
|
||
|
||
/* Triggered when gdbpy_breakpoint_cond_says_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->func_symbol == nullptr)
|
||
return;
|
||
|
||
try
|
||
{
|
||
scoped_value_mark free_values;
|
||
|
||
struct symbol *func_symbol =
|
||
symbol_object_to_symbol (self_finishbp->func_symbol);
|
||
struct value *function =
|
||
value_object_to_value (self_finishbp->function_value);
|
||
struct value *ret =
|
||
get_return_value (func_symbol, function);
|
||
|
||
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_breakpoint_cond_says_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);
|
||
bp_obj->bp->disposition = disp_del_at_next_stop;
|
||
}
|
||
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;
|
||
frame_info_ptr frame = NULL; /* init for gcc -Wall */
|
||
frame_info_ptr prev_frame = NULL;
|
||
struct frame_id frame_id;
|
||
PyObject *internal = NULL;
|
||
int internal_bp = 0;
|
||
CORE_ADDR pc;
|
||
|
||
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);
|
||
}
|
||
}
|
||
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->func_symbol = nullptr;
|
||
self_bpfinish->function_value = nullptr;
|
||
|
||
try
|
||
{
|
||
if (get_frame_pc_if_available (frame, &pc))
|
||
{
|
||
struct symbol *function = find_pc_function (pc);
|
||
if (function != nullptr)
|
||
{
|
||
struct type *ret_type =
|
||
check_typedef (function->type ()->target_type ());
|
||
|
||
/* Remember only non-void return types. */
|
||
if (ret_type->code () != TYPE_CODE_VOID)
|
||
{
|
||
scoped_value_mark free_values;
|
||
|
||
/* Ignore Python errors at this stage. */
|
||
value *func_value = read_var_value (function, NULL, frame);
|
||
self_bpfinish->function_value
|
||
= value_to_value_object (func_value);
|
||
PyErr_Clear ();
|
||
|
||
self_bpfinish->func_symbol
|
||
= symbol_to_symbol_object (function);
|
||
PyErr_Clear ();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
catch (const gdb_exception_forced_quit &except)
|
||
{
|
||
quit_force (NULL, 0);
|
||
}
|
||
catch (const gdb_exception &except)
|
||
{
|
||
/* Just swallow. Either the return type or the function value
|
||
remain NULL. */
|
||
}
|
||
|
||
if (self_bpfinish->func_symbol == nullptr
|
||
|| self_bpfinish->function_value == nullptr)
|
||
{
|
||
/* Won't be able to compute return value. */
|
||
Py_XDECREF (self_bpfinish->func_symbol);
|
||
Py_XDECREF (self_bpfinish->function_value);
|
||
|
||
self_bpfinish->func_symbol = nullptr;
|
||
self_bpfinish->function_value = nullptr;
|
||
}
|
||
|
||
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. */
|
||
location_spec_up locspec
|
||
= new_address_location_spec (get_frame_pc (prev_frame), NULL, 0);
|
||
create_breakpoint (gdbpy_enter::get_gdbarch (),
|
||
locspec.get (), NULL, thread, NULL, false,
|
||
0,
|
||
1 /*temp_flag*/,
|
||
bp_breakpoint,
|
||
0,
|
||
AUTO_BOOLEAN_TRUE,
|
||
&code_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;
|
||
self_bpfinish->initiating_frame = get_frame_id (frame);
|
||
|
||
/* 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 ();
|
||
}
|
||
}
|
||
|
||
/* Callback for `bpfinishpy_detect_out_scope'. Triggers Python's
|
||
`B->out_of_scope' function if B is a FinishBreakpoint out of its scope.
|
||
|
||
When DELETE_BP is true then breakpoint B will be deleted if B is a
|
||
FinishBreakpoint and it is out of scope, otherwise B will not be
|
||
deleted. */
|
||
|
||
static void
|
||
bpfinishpy_detect_out_scope_cb (struct breakpoint *b,
|
||
struct breakpoint *bp_stopped,
|
||
bool delete_bp)
|
||
{
|
||
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
|
||
{
|
||
struct frame_id initiating_frame = finish_bp->initiating_frame;
|
||
|
||
if (b->pspace == current_inferior ()->pspace
|
||
&& (!target_has_registers ()
|
||
|| frame_find_by_id (initiating_frame) == NULL))
|
||
{
|
||
bpfinishpy_out_of_scope (finish_bp);
|
||
if (delete_bp)
|
||
delete_breakpoint (finish_bp->py_bp.bp);
|
||
}
|
||
}
|
||
catch (const gdb_exception &except)
|
||
{
|
||
gdbpy_convert_exception (except);
|
||
gdbpy_print_stack ();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Called when gdbpy_breakpoint_deleted is about to delete a breakpoint. A
|
||
chance to trigger the out_of_scope callback (if appropriate) for the
|
||
associated Python object. */
|
||
|
||
void
|
||
bpfinishpy_pre_delete_hook (struct gdbpy_breakpoint_object *bp_obj)
|
||
{
|
||
breakpoint *bp = bp_obj->bp;
|
||
bpfinishpy_detect_out_scope_cb (bp, nullptr, false);
|
||
}
|
||
|
||
/* 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, true);
|
||
}
|
||
|
||
/* 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, true);
|
||
}
|
||
|
||
/* Initialize the Python finish breakpoint code. */
|
||
|
||
static int CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION
|
||
gdbpy_initialize_finishbreakpoints (void)
|
||
{
|
||
if (!gdbpy_breakpoint_init_breakpoint_type ())
|
||
return -1;
|
||
|
||
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;
|
||
}
|
||
|
||
GDBPY_INITIALIZE_FILE (gdbpy_initialize_finishbreakpoints);
|
||
|
||
|
||
|
||
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 */
|
||
};
|