Add attributes and methods to gdb.Inferior

This adds two new attributes and three new methods to gdb.Inferior.

The attributes let Python code see the command-line arguments and the
name of "main".  Argument setting is also supported.

The methods let Python code manipulate the inferior's environment
variables.

Reviewed-By: Eli Zaretskii <eliz@gnu.org>
This commit is contained in:
Tom Tromey 2023-05-01 13:53:59 -06:00
parent 2f328f5b92
commit 3153113252
5 changed files with 263 additions and 0 deletions

View File

@ -199,6 +199,16 @@ info main
This can be used to request that the parse only examine global
symbols.
** gdb.Inferior now has a new "arguments" attribute. This holds the
command-line arguments to the inferior, if known.
** gdb.Inferior now has a new "main_name" attribute. This holds the
name of the inferior's "main", if known.
** gdb.Inferior now has new methods "clear_env", "set_env", and
"unset_env". These can be used to modify the inferior's
environment before it is started.
*** Changes in GDB 13
* MI version 1 is deprecated, and will be removed in GDB 14.

View File

@ -3451,10 +3451,30 @@ Boolean signaling whether the inferior was created using `attach', or
started by @value{GDBN} itself.
@end defvar
@defvar Inferior.main_name
A string holding the name of this inferior's ``main'' function, if it
can be determined. If the name of main is not known, this is
@code{None}.
@end defvar
@defvar Inferior.progspace
The inferior's program space. @xref{Progspaces In Python}.
@end defvar
@defvar Inferior.arguments
The inferior's command line arguments, if known. This corresponds to
the @code{set args} and @code{show args} commands. @xref{Arguments}.
When accessed, the value is a string holding all the arguments. The
contents are quoted as they would be when passed to the shell. If
there are no arguments, the value is @code{None}.
Either a string or a sequence of strings can be assigned to this
attribute. When a string is assigned, it is assumed to have any
necessary quoting for the shell; when a sequence is assigned, the
quoting is applied by @value{GDBN}.
@end defvar
A @code{gdb.Inferior} object has the following methods:
@defun Inferior.is_valid ()
@ -3522,6 +3542,30 @@ the same functionality, but use of @code{Inferior.thread_from_thread_handle}
is deprecated.
@end defun
The environment that will be passed to the inferior can be changed
from Python by using the following methods. These methods only take
effect when the inferior is started -- they will not affect an
inferior that is already executing.
@findex Inferior.clear_env
@defun Inferior.clear_env ()
Clear the current environment variables that will be passed to this
inferior.
@end defun
@findex Inferior.set_env
@defun Inferior.set_env (name, value)
Set the environment variable @var{name} to have the indicated value.
Both parameters must be strings.
@end defun
@findex Inferior.unset_env
@defun Inferior.unset_env (name)
Unset the environment variable @var{name}. @var{name} must be a
string.
@end defun
@node Events In Python
@subsubsection Events In Python
@cindex inferior events in Python

View File

@ -770,6 +770,162 @@ infpy_repr (PyObject *obj)
inf->num, inf->pid);
}
/* Implement clear_env. */
static PyObject *
infpy_clear_env (PyObject *obj)
{
inferior_object *self = (inferior_object *) obj;
INFPY_REQUIRE_VALID (self);
self->inferior->environment.clear ();
Py_RETURN_NONE;
}
/* Implement set_env. */
static PyObject *
infpy_set_env (PyObject *obj, PyObject *args, PyObject *kw)
{
inferior_object *self = (inferior_object *) obj;
INFPY_REQUIRE_VALID (self);
const char *name, *val;
static const char *keywords[] = { "name", "value", nullptr };
if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "ss", keywords,
&name, &val))
return nullptr;
self->inferior->environment.set (name, val);
Py_RETURN_NONE;
}
/* Implement unset_env. */
static PyObject *
infpy_unset_env (PyObject *obj, PyObject *args, PyObject *kw)
{
inferior_object *self = (inferior_object *) obj;
INFPY_REQUIRE_VALID (self);
const char *name;
static const char *keywords[] = { "name", nullptr };
if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "s", keywords, &name))
return nullptr;
self->inferior->environment.unset (name);
Py_RETURN_NONE;
}
/* Getter for "arguments". */
static PyObject *
infpy_get_args (PyObject *self, void *closure)
{
inferior_object *inf = (inferior_object *) self;
INFPY_REQUIRE_VALID (inf);
const std::string &args = inf->inferior->args ();
if (args.empty ())
Py_RETURN_NONE;
return host_string_to_python_string (args.c_str ()).release ();
}
/* Setter for "arguments". */
static int
infpy_set_args (PyObject *self, PyObject *value, void *closure)
{
inferior_object *inf = (inferior_object *) self;
if (!inf->inferior)
{
PyErr_SetString (PyExc_RuntimeError, _("Inferior no longer exists."));
return -1;
}
if (value == nullptr)
{
PyErr_SetString (PyExc_TypeError,
_("Cannot delete 'arguments' attribute."));
return -1;
}
if (gdbpy_is_string (value))
{
gdb::unique_xmalloc_ptr<char> str = python_string_to_host_string (value);
if (str == nullptr)
return -1;
inf->inferior->set_args (std::string (str.get ()));
}
else if (PySequence_Check (value))
{
std::vector<gdb::unique_xmalloc_ptr<char>> args;
Py_ssize_t len = PySequence_Size (value);
if (len == -1)
return -1;
for (Py_ssize_t i = 0; i < len; ++i)
{
gdbpy_ref<> item (PySequence_ITEM (value, i));
if (item == nullptr)
return -1;
gdb::unique_xmalloc_ptr<char> str
= python_string_to_host_string (item.get ());
if (str == nullptr)
return -1;
args.push_back (std::move (str));
}
std::vector<char *> argvec;
for (const auto &arg : args)
argvec.push_back (arg.get ());
gdb::array_view<char * const> view (argvec.data (), argvec.size ());
inf->inferior->set_args (view);
}
else
{
PyErr_SetString (PyExc_TypeError,
_("string or sequence required for 'arguments'"));
return -1;
}
return 0;
}
/* Getter for "main_name". */
static PyObject *
infpy_get_main_name (PyObject *self, void *closure)
{
inferior_object *inf = (inferior_object *) self;
INFPY_REQUIRE_VALID (inf);
const char *name = nullptr;
try
{
/* This is unfortunate but the implementation of main_name can
reach into memory. */
scoped_restore_current_inferior restore_inferior;
set_current_inferior (inf->inferior);
scoped_restore_current_program_space restore_current_progspace;
set_current_program_space (inf->inferior->pspace);
name = main_name ();
}
catch (const gdb_exception &except)
{
/* We can just ignore this. */
}
if (name == nullptr)
Py_RETURN_NONE;
return host_string_to_python_string (name).release ();
}
static void
infpy_dealloc (PyObject *obj)
@ -844,6 +1000,8 @@ GDBPY_INITIALIZE_FILE (gdbpy_initialize_inferior);
static gdb_PyGetSetDef inferior_object_getset[] =
{
{ "arguments", infpy_get_args, infpy_set_args,
"Arguments to this program.", nullptr },
{ "num", infpy_get_num, NULL, "ID of inferior, as assigned by GDB.", NULL },
{ "connection", infpy_get_connection, NULL,
"The gdb.TargetConnection for this inferior.", NULL },
@ -854,6 +1012,8 @@ static gdb_PyGetSetDef inferior_object_getset[] =
{ "was_attached", infpy_get_was_attached, NULL,
"True if the inferior was created using 'attach'.", NULL },
{ "progspace", infpy_get_progspace, NULL, "Program space of this inferior" },
{ "main_name", infpy_get_main_name, nullptr,
"Name of 'main' function, if known.", nullptr },
{ NULL }
};
@ -889,6 +1049,15 @@ Return thread object corresponding to thread handle." },
{ "architecture", (PyCFunction) infpy_architecture, METH_NOARGS,
"architecture () -> gdb.Architecture\n\
Return architecture of this inferior." },
{ "clear_env", (PyCFunction) infpy_clear_env, METH_NOARGS,
"clear_env () -> None\n\
Clear environment of this inferior." },
{ "set_env", (PyCFunction) infpy_set_env, METH_VARARGS | METH_KEYWORDS,
"set_env (name, value) -> None\n\
Set an environment variable of this inferior." },
{ "unset_env", (PyCFunction) infpy_unset_env, METH_VARARGS | METH_KEYWORDS,
"unset_env (name) -> None\n\
Unset an environment of this inferior." },
{ NULL }
};

View File

@ -42,3 +42,7 @@ gdb_test "python print(gdb.parse_and_eval('si').type)" \
"foo\\.small_integer" "print type"
gdb_test "python print(gdb.parse_and_eval('si').type.name)" \
"foo\\.small_integer" "print type name"
gdb_test "python print(gdb.selected_inferior().main_name)" \
"_ada_foo" \
"print main name"

View File

@ -342,3 +342,39 @@ with_test_prefix "architecture" {
"True" \
"inferior architecture matches frame architecture"
}
gdb_test "python print(gdb.selected_inferior().main_name)" \
"main" \
"print main name"
gdb_test_no_output "set args x y z"
gdb_test "python print(gdb.selected_inferior().arguments)" \
"x y z" \
"print arguments"
gdb_test_no_output "python gdb.selected_inferior().arguments = 'a b c'" \
"set arguments from string"
gdb_test "show args" \
[string_to_regexp "Argument list to give program being debugged when it is started is \"a b c\"."] \
"show args from string"
gdb_test_no_output "python gdb.selected_inferior().arguments = \['a', 'b c'\]" \
"set arguments from list"
gdb_test "show args" \
[string_to_regexp "Argument list to give program being debugged when it is started is \"a b\\ c\"."] \
"show args from list"
gdb_test_no_output "python gdb.selected_inferior().clear_env()" \
"clear environment"
gdb_test_no_output "show environment"
gdb_test_no_output "python gdb.selected_inferior().set_env('DEI', 'value')" \
"set environment variable"
gdb_test "show environment" \
"DEI=value" \
"examine environment variable"
gdb_test_no_output "python gdb.selected_inferior().unset_env('DEI')" \
"unset environment variable"
gdb_test_no_output "show environment" \
"environment is empty again"