aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gdb/NEWS3
-rw-r--r--gdb/doc/python.texi16
-rw-r--r--gdb/python/py-value.c122
-rw-r--r--gdb/testsuite/gdb.python/py-value.exp109
4 files changed, 230 insertions, 20 deletions
diff --git a/gdb/NEWS b/gdb/NEWS
index 30b8cad..6022def 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -11,6 +11,9 @@
** New function gdb.notify_mi(NAME, DATA), that emits custom
GDB/MI async notification.
+ ** New read/write attribute gdb.Value.bytes that contains a bytes
+ object holding the contents of this value.
+
*** Changes in GDB 14
* GDB now supports the AArch64 Scalable Matrix Extension 2 (SME2), which
diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi
index 546b4d4..8cc3f92 100644
--- a/gdb/doc/python.texi
+++ b/gdb/doc/python.texi
@@ -916,6 +916,21 @@ fetched when the value is needed, or when the @code{fetch_lazy}
method is invoked.
@end defvar
+@defvar Value.bytes
+The value of this attribute is a @code{bytes} object containing the
+bytes that make up this @code{Value}'s complete value in little endian
+order. If the complete contents of this value are not available then
+accessing this attribute will raise an exception.
+
+This attribute can also be assigned to. The new value should be a
+buffer object (e.g.@: a @code{bytes} object), the length of the new
+buffer must exactly match the length of this @code{Value}'s type. The
+bytes values in the new buffer should be in little endian order.
+
+As with @code{Value.assign} (@pxref{Value.assign}), if this value
+cannot be assigned to, then an exception will be thrown.
+@end defvar
+
The following methods are provided:
@defun Value.__init__ (val)
@@ -966,6 +981,7 @@ If @var{type} is @code{None} then this version of @code{__init__}
behaves as though @var{type} was not passed at all.
@end defun
+@anchor{Value.assign}
@defun Value.assign (rhs)
Assign @var{rhs} to this value, and return @code{None}. If this value
cannot be assigned to, or if the assignment is invalid for some reason
diff --git a/gdb/python/py-value.c b/gdb/python/py-value.c
index 0bf1d6e..f360f84 100644
--- a/gdb/python/py-value.c
+++ b/gdb/python/py-value.c
@@ -63,6 +63,7 @@ struct value_object {
PyObject *address;
PyObject *type;
PyObject *dynamic_type;
+ PyObject *content_bytes;
};
/* List of all values which are currently exposed to Python. It is
@@ -86,6 +87,7 @@ valpy_clear_value (value_object *self)
Py_CLEAR (self->address);
Py_CLEAR (self->type);
Py_CLEAR (self->dynamic_type);
+ Py_CLEAR (self->content_bytes);
}
/* Called by the Python interpreter when deallocating a value object. */
@@ -135,10 +137,14 @@ note_value (value_object *value_obj)
/* Convert a python object OBJ with type TYPE to a gdb value. The
python object in question must conform to the python buffer
protocol. On success, return the converted value, otherwise
- nullptr. */
+ nullptr. When REQUIRE_EXACT_SIZE_P is true the buffer OBJ must be the
+ exact length of TYPE. When REQUIRE_EXACT_SIZE_P is false then the
+ buffer OBJ can be longer than TYPE, in which case only the least
+ significant bytes from the buffer are used. */
static struct value *
-convert_buffer_and_type_to_value (PyObject *obj, struct type *type)
+convert_buffer_and_type_to_value (PyObject *obj, struct type *type,
+ bool require_exact_size_p)
{
Py_buffer_up buffer_up;
Py_buffer py_buf;
@@ -157,7 +163,13 @@ convert_buffer_and_type_to_value (PyObject *obj, struct type *type)
return nullptr;
}
- if (type->length () > py_buf.len)
+ if (require_exact_size_p && type->length () != py_buf.len)
+ {
+ PyErr_SetString (PyExc_ValueError,
+ _("Size of type is not equal to that of buffer object."));
+ return nullptr;
+ }
+ else if (!require_exact_size_p && type->length () > py_buf.len)
{
PyErr_SetString (PyExc_ValueError,
_("Size of type is larger than that of buffer object."));
@@ -196,7 +208,7 @@ valpy_init (PyObject *self, PyObject *args, PyObject *kwds)
if (type == nullptr)
value = convert_value_from_python (val_obj);
else
- value = convert_buffer_and_type_to_value (val_obj, type);
+ value = convert_buffer_and_type_to_value (val_obj, type, false);
if (value == nullptr)
{
gdb_assert (PyErr_Occurred ());
@@ -888,36 +900,53 @@ valpy_reinterpret_cast (PyObject *self, PyObject *args)
return valpy_do_cast (self, args, UNOP_REINTERPRET_CAST);
}
-/* Implementation of the "assign" method. */
+/* Assign NEW_VALUE into SELF, handles 'struct value' reference counting,
+ and also clearing the bytes data cached within SELF. Return true if
+ the assignment was successful, otherwise return false, in which case a
+ Python exception will be set. */
-static PyObject *
-valpy_assign (PyObject *self_obj, PyObject *args)
+static bool
+valpy_assign_core (value_object *self, struct value *new_value)
{
- PyObject *val_obj;
-
- if (! PyArg_ParseTuple (args, "O", &val_obj))
- return nullptr;
-
- struct value *val = convert_value_from_python (val_obj);
- if (val == nullptr)
- return nullptr;
-
try
{
- value_object *self = (value_object *) self_obj;
- value *new_value = value_assign (self->value, val);
+ new_value = value_assign (self->value, new_value);
+
/* value_as_address returns a new value with the same location
as the old one. Ensure that this gdb.Value is updated to
reflect the new value. */
new_value->incref ();
self->value->decref ();
+ Py_CLEAR (self->content_bytes);
self->value = new_value;
}
catch (const gdb_exception &except)
{
- GDB_PY_HANDLE_EXCEPTION (except);
+ gdbpy_convert_exception (except);
+ return false;
}
+ return true;
+}
+
+/* Implementation of the "assign" method. */
+
+static PyObject *
+valpy_assign (PyObject *self_obj, PyObject *args)
+{
+ PyObject *val_obj;
+
+ if (! PyArg_ParseTuple (args, "O", &val_obj))
+ return nullptr;
+
+ struct value *val = convert_value_from_python (val_obj);
+ if (val == nullptr)
+ return nullptr;
+
+ value_object *self = (value_object *) self_obj;
+ if (!valpy_assign_core (self, val))
+ return nullptr;
+
Py_RETURN_NONE;
}
@@ -1304,6 +1333,58 @@ valpy_get_is_lazy (PyObject *self, void *closure)
Py_RETURN_FALSE;
}
+/* Get gdb.Value.bytes attribute. */
+
+static PyObject *
+valpy_get_bytes (PyObject *self, void *closure)
+{
+ value_object *value_obj = (value_object *) self;
+ struct value *value = value_obj->value;
+
+ if (value_obj->content_bytes != nullptr)
+ {
+ Py_INCREF (value_obj->content_bytes);
+ return value_obj->content_bytes;
+ }
+
+ gdb::array_view<const gdb_byte> contents;
+ try
+ {
+ contents = value->contents ();
+ }
+ catch (const gdb_exception &except)
+ {
+ GDB_PY_HANDLE_EXCEPTION (except);
+ }
+
+ value_obj->content_bytes
+ = PyBytes_FromStringAndSize ((const char *) contents.data (),
+ contents.size ());
+ Py_XINCREF (value_obj->content_bytes);
+ return value_obj->content_bytes;
+}
+
+/* Set gdb.Value.bytes attribute. */
+
+static int
+valpy_set_bytes (PyObject *self_obj, PyObject *new_value_obj, void *closure)
+{
+ value_object *self = (value_object *) self_obj;
+
+ /* Create a new value from the buffer NEW_VALUE_OBJ. We pass true here
+ to indicate that NEW_VALUE_OBJ must match exactly the type length. */
+ struct value *new_value
+ = convert_buffer_and_type_to_value (new_value_obj, self->value->type (),
+ true);
+ if (new_value == nullptr)
+ return -1;
+
+ if (!valpy_assign_core (self, new_value))
+ return -1;
+
+ return 0;
+}
+
/* Implements gdb.Value.fetch_lazy (). */
static PyObject *
valpy_fetch_lazy (PyObject *self, PyObject *args)
@@ -1865,6 +1946,7 @@ value_to_value_object (struct value *val)
val_obj->address = NULL;
val_obj->type = NULL;
val_obj->dynamic_type = NULL;
+ val_obj->content_bytes = nullptr;
note_value (val_obj);
}
@@ -2152,6 +2234,8 @@ static gdb_PyGetSetDef value_object_getset[] = {
"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 },
+ { "bytes", valpy_get_bytes, valpy_set_bytes,
+ "Return a bytearray containing the bytes of this value.", nullptr },
{NULL} /* Sentinel */
};
diff --git a/gdb/testsuite/gdb.python/py-value.exp b/gdb/testsuite/gdb.python/py-value.exp
index cdfcd41..4b826f2 100644
--- a/gdb/testsuite/gdb.python/py-value.exp
+++ b/gdb/testsuite/gdb.python/py-value.exp
@@ -66,7 +66,8 @@ proc test_value_creation {} {
gdb_test "python print ('result = %s' % i.address)" "= None" "test address attribute in non-addressable value"
# Test creating / printing an optimized out value
- gdb_test "python print(gdb.Value(gdb.Value(5).type.optimized_out()))"
+ gdb_test "python print(gdb.Value(gdb.Value(5).type.optimized_out()))" \
+ "<optimized out>"
}
# Check that we can call gdb.Value.__init__ to change a value.
@@ -542,13 +543,30 @@ proc prepare_type_and_buffer {} {
proc test_value_from_buffer {} {
global gdb_prompt
+ # A Python helper function. Create a bytes object from inferior
+ # memory LEN bytes starting at ADDR, and compare this to the bytes
+ # obtained from VAL.bytes. Assert that the two bytes object match.
+ gdb_test_multiline "Create another function to check Value.bytes" \
+ "python" "" \
+ "def compare_value_bytes_to_mem(val, addr, len):" "" \
+ " mem = gdb.selected_inferior().read_memory(addr, len)" "" \
+ " mem_bytes = mem.tobytes()" "" \
+ " val_bytes = val.bytes" "" \
+ " assert mem_bytes == val_bytes" "" \
+ "end" ""
+
prepare_type_and_buffer
gdb_test "python v=gdb.Value(b,tp); print(v)" "1" \
"construct value from buffer"
+ gdb_test_no_output { python compare_value_bytes_to_mem(v, addr, size_a0) }
gdb_test "python v=gdb.Value(b\[size_a0:\],tp); print(v)" "2" \
"convert 2nd elem of buffer to value"
+ gdb_test_no_output \
+ { python compare_value_bytes_to_mem(v, (int(addr) + size_a0), size_a0) }
gdb_test "python v=gdb.Value(b\[2*size_a0:\],tp); print(v)" "3" \
"convert 3rd elem of buffer to value"
+ gdb_test_no_output \
+ { python compare_value_bytes_to_mem(v, (int(addr) + (2 * size_a0)), size_a0) }
gdb_test "python v=gdb.Value(b\[2*size_a0+1:\],tp); print(v)" \
"ValueError: Size of type is larger than that of buffer object\..*" \
"attempt to convert smaller buffer than size of type"
@@ -556,6 +574,8 @@ proc test_value_from_buffer {} {
"make array type" 0
gdb_py_test_silent_cmd "python va=gdb.Value(b,atp)" \
"construct array value from buffer" 0
+ gdb_test_no_output \
+ { python compare_value_bytes_to_mem(va, addr, size_a0 * 3) }
gdb_test "python print(va)" "\\{1, 2, 3\\}" "print array value"
gdb_test "python print(va\[0\])" "1" "print first array element"
gdb_test "python print(va\[1\])" "2" "print second array element"
@@ -633,6 +653,92 @@ proc test_history_count {} {
}
}
+# Test the gdb.Value.bytes API.
+proc_with_prefix test_value_bytes { } {
+ # Test accessing the bytes of an optimised out value.
+ gdb_test "python print(gdb.Value(gdb.Value(5).type.optimized_out()).bytes)" \
+ [multi_line \
+ "gdb\\.error: value has been optimized out" \
+ "Error while executing Python code\\."]
+
+ # A Python helper function. Fetch VAR_NAME from the inferior as a
+ # gdb.Value. Read the bytes of the value based on its address, and
+ # the size of its type. The compare these bytes to the value
+ # obtained from gdb.Value.bytes. Assert that the two bytes objects
+ # match.
+ gdb_test_multiline "Create a function to check Value.bytes" \
+ "python" "" \
+ "def check_value_bytes(var_name):" "" \
+ " val = gdb.parse_and_eval(var_name)" "" \
+ " addr = val.address" "" \
+ " len = val.type.sizeof" "" \
+ " mem = gdb.selected_inferior().read_memory(addr, len)" "" \
+ " mem_bytes = mem.tobytes()" "" \
+ " val_bytes = val.bytes" "" \
+ " assert mem_bytes == val_bytes" "" \
+ "end" ""
+
+ gdb_test_no_output { python check_value_bytes("a") }
+ gdb_test_no_output { python check_value_bytes("p") }
+ gdb_test_no_output { python check_value_bytes("i") }
+ gdb_test_no_output { python check_value_bytes("ptr_i") }
+ gdb_test_no_output { python check_value_bytes("embed") }
+ gdb_test_no_output { python check_value_bytes("fp1") }
+ gdb_test_no_output { python check_value_bytes("nullst") }
+ gdb_test_no_output { python check_value_bytes("st") }
+ gdb_test_no_output { python check_value_bytes("s") }
+ gdb_test_no_output { python check_value_bytes("u") }
+
+ # Check that gdb.Value.bytes changes after calling
+ # gdb.Value.assign(). The bytes value is cached within the Value
+ # object, so calling assign should clear the cache.
+ with_test_prefix "assign clears bytes cache" {
+ gdb_test_no_output "python v = gdb.parse_and_eval(\"i\")"
+ gdb_test_no_output "python bytes_before = v.bytes"
+ gdb_test_no_output "python v.assign(9)"
+ gdb_test_no_output "python bytes_after = v.bytes"
+ gdb_test_no_output "python assert(bytes_after != bytes_before)"
+ }
+
+ # Check that if we re-init a gdb.Value object the cached bytes for
+ # the Value are cleared.
+ with_test_prefix "re-init clears bytes cache" {
+ gdb_test_no_output "python w = gdb.Value(1)"
+ gdb_test_no_output "python bytes_before = w.bytes"
+ gdb_test_no_output "python w.__init__(3)"
+ gdb_test_no_output "python bytes_after = w.bytes"
+ gdb_test_no_output "python assert(bytes_after != bytes_before)"
+ }
+
+ # Check that we can assign to the Value.bytes field.
+ gdb_test_no_output "python i_value = gdb.parse_and_eval('i')" \
+ "evaluate i"
+ gdb_test_no_output "python i_bytes = i_value.bytes"
+ gdb_test_no_output "python i_bytes = bytes(\[b if b != 9 else 5 for b in i_bytes\])"
+ gdb_test_no_output "python i_value.bytes = i_bytes"
+ gdb_test "print i" " = 5"
+
+ # Check we get an exception if attempting to assign a buffer that is
+ # too big, or too small.
+ gdb_test_no_output "python bytes_as_int = \[x for x in i_bytes\]"
+ gdb_test_no_output "python bytes_as_int.append(0)"
+ gdb_test_no_output "python too_many_bytes = bytes(bytes_as_int)"
+ gdb_test "python i_value.bytes = too_many_bytes" \
+ "ValueError: Size of type is not equal to that of buffer object\\..*"
+ gdb_test_no_output "python bytes_as_int = bytes_as_int\[0:-2\]"
+ gdb_test_no_output "python too_few_bytes = bytes(bytes_as_int)"
+ gdb_test "python i_value.bytes = too_few_bytes" \
+ "ValueError: Size of type is not equal to that of buffer object\\..*"
+
+ # Check we get an exception writing to a not_lval.
+ gdb_test_no_output "python i_value = gdb.Value(9)" \
+ "reset i_value"
+ gdb_test_no_output "python i_bytes = i_value.bytes" \
+ "grab new value bytes"
+ gdb_test "python i_value.bytes = i_bytes" "not an lvalue.*" \
+ "cannot assign to not_lval value"
+}
+
# Test Value.assign.
proc test_assign {} {
gdb_test_no_output "python i_value = gdb.parse_and_eval('i')" \
@@ -677,6 +783,7 @@ test_value_from_buffer
test_value_sub_classes
test_inferior_function_call
test_assign
+test_value_bytes
test_value_after_death
# Test either C or C++ values.