diff options
-rw-r--r-- | gdb/NEWS | 3 | ||||
-rw-r--r-- | gdb/doc/python.texi | 16 | ||||
-rw-r--r-- | gdb/python/py-value.c | 122 | ||||
-rw-r--r-- | gdb/testsuite/gdb.python/py-value.exp | 109 |
4 files changed, 230 insertions, 20 deletions
@@ -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. |