vmstate: Allow vmstate_info_nullptr to emit non-NULL markers

We used to have one vmstate called "nullptr" which is only used to generate
one-byte hint to say one pointer is NULL.

Let's extend its use so that it will generate another byte to say the
pointer is non-NULL.

With that, the name of the info struct (or functions) do not apply anymore.
Update correspondingly.

Update analyze-migration.py to work with the new layout.

No functional change intended yet.

Reviewed-by: Fabiano Rosas <farosas@suse.de>
Reviewed-by: Alexander Mikhalitsyn <aleksandr.mikhalitsyn@futurfusion.io>
Signed-off-by: Peter Xu <peterx@redhat.com>
Reviewed-by: Juraj Marcin <jmarcin@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260401202844.673494-8-peterx@redhat.com
Signed-off-by: Fabiano Rosas <farosas@suse.de>
This commit is contained in:
Peter Xu
2026-04-01 16:28:40 -04:00
committed by Fabiano Rosas
parent f73d6251fb
commit e86c004fbd
4 changed files with 48 additions and 42 deletions

View File

@@ -282,9 +282,14 @@ extern const VMStateInfo vmstate_info_uint32;
extern const VMStateInfo vmstate_info_uint64;
extern const VMStateInfo vmstate_info_fd;
/** Put this in the stream when migrating a null pointer.*/
/*
* Put this in the stream when migrating a pointer to reflect either a NULL
* or valid pointer.
*/
#define VMS_MARKER_PTR_NULL (0x30U) /* '0' */
extern const VMStateInfo vmstate_info_nullptr;
#define VMS_MARKER_PTR_VALID (0x31U) /* '1' */
extern const VMStateInfo vmstate_info_ptr_marker;
extern const VMStateInfo vmstate_info_cpudouble;

View File

@@ -359,36 +359,34 @@ const VMStateInfo vmstate_info_fd = {
.save = save_fd,
};
static bool load_nullptr(QEMUFile *f, void *pv, size_t size,
const VMStateField *field, Error **errp)
static bool load_ptr_marker(QEMUFile *f, void *pv, size_t size,
const VMStateField *field, Error **errp)
{
if (qemu_get_byte(f) == VMS_MARKER_PTR_NULL) {
int byte = qemu_get_byte(f);
if (byte == VMS_MARKER_PTR_NULL || byte == VMS_MARKER_PTR_VALID) {
/* TODO: process PTR_VALID case */
return true;
}
error_setg(errp, "vmstate: load_nullptr expected VMS_NULLPTR_MARKER");
error_setg(errp, "%s: unexpected ptr marker: %d", __func__, byte);
return false;
}
static bool save_nullptr(QEMUFile *f, void *pv, size_t size,
const VMStateField *field, JSONWriter *vmdesc,
Error **errp)
static bool save_ptr_marker(QEMUFile *f, void *pv, size_t size,
const VMStateField *field, JSONWriter *vmdesc,
Error **errp)
{
if (pv == NULL) {
qemu_put_byte(f, VMS_MARKER_PTR_NULL);
return true;
}
error_setg(errp, "vmstate: save_nullptr must be called with pv == NULL");
return false;
qemu_put_byte(f, pv ? VMS_MARKER_PTR_VALID : VMS_MARKER_PTR_NULL);
return true;
}
const VMStateInfo vmstate_info_nullptr = {
.name = "nullptr",
.load = load_nullptr,
.save = save_nullptr,
const VMStateInfo vmstate_info_ptr_marker = {
.name = "ptr-marker",
.load = load_ptr_marker,
.save = save_ptr_marker,
};
/* 64 bit unsigned int. See that the received value is the same than the one

View File

@@ -55,12 +55,12 @@ vmstate_field_exists(const VMStateDescription *vmsd, const VMStateField *field,
}
/*
* Create a fake nullptr field when there's a NULL pointer detected in the
* Create a ptr marker field when there's a NULL pointer detected in the
* array of a VMS_ARRAY_OF_POINTER VMSD field. It's needed because we
* can't dereference the NULL pointer.
*/
static const VMStateField *
vmsd_create_fake_nullptr_field(const VMStateField *field)
vmsd_create_ptr_marker_field(const VMStateField *field)
{
VMStateField *fake = g_new0(VMStateField, 1);
@@ -71,12 +71,12 @@ vmsd_create_fake_nullptr_field(const VMStateField *field)
fake->name = field->name;
fake->version_id = field->version_id;
/* Do not need "field_exists" check as it always exists (which is null) */
/* Do not need "field_exists" check as it always exists */
fake->field_exists = NULL;
/* See vmstate_info_nullptr - use 1 byte to represent nullptr */
/* See vmstate_info_ptr_marker - use 1 byte to represent ptr status */
fake->size = 1;
fake->info = &vmstate_info_nullptr;
fake->info = &vmstate_info_ptr_marker;
fake->flags = VMS_SINGLE;
/* All the rest fields shouldn't matter.. */
@@ -278,7 +278,7 @@ bool vmstate_load_vmsd(QEMUFile *f, const VMStateDescription *vmsd,
* an array of pointers), use null placeholder and do
* not follow.
*/
inner_field = vmsd_create_fake_nullptr_field(field);
inner_field = vmsd_create_ptr_marker_field(field);
} else {
inner_field = field;
}
@@ -583,26 +583,27 @@ static bool vmstate_save_vmsd_v(QEMUFile *f, const VMStateDescription *vmsd,
for (i = 0; i < n_elems; i++) {
void *curr_elem = first_elem + size * i;
const VMStateField *inner_field;
bool is_null;
/* maximum number of elements to compress in the JSON blob */
int max_elems = vmsd_can_compress(field) ? (n_elems - i) : 1;
bool use_marker_field, is_null;
if (field->flags & VMS_ARRAY_OF_POINTER) {
assert(curr_elem);
curr_elem = *(void **)curr_elem;
}
if (!curr_elem && size) {
is_null = !curr_elem && size;
use_marker_field = is_null;
if (use_marker_field) {
/*
* If null pointer found (which should only happen in
* an array of pointers), use null placeholder and do
* not follow.
*/
inner_field = vmsd_create_fake_nullptr_field(field);
is_null = true;
inner_field = vmsd_create_ptr_marker_field(field);
} else {
inner_field = field;
is_null = false;
}
/*
@@ -638,7 +639,7 @@ static bool vmstate_save_vmsd_v(QEMUFile *f, const VMStateDescription *vmsd,
i, max_elems, errp);
/* If we used a fake temp field.. free it now */
if (is_null) {
if (use_marker_field) {
g_clear_pointer((gpointer *)&inner_field, g_free);
}

View File

@@ -469,26 +469,26 @@ class VMSDFieldIntLE(VMSDFieldInt):
super(VMSDFieldIntLE, self).__init__(desc, file)
self.dtype = '<i%d' % self.size
class VMSDFieldNull(VMSDFieldGeneric):
class VMSDFieldPtrMarker(VMSDFieldGeneric):
NULL_PTR_MARKER = b'0'
VALID_PTR_MARKER = b'1'
def __init__(self, desc, file):
super(VMSDFieldNull, self).__init__(desc, file)
super(VMSDFieldPtrMarker, self).__init__(desc, file)
def __repr__(self):
# A NULL pointer is encoded in the stream as a '0' to
# disambiguate from a mere 0x0 value and avoid consumers
# trying to follow the NULL pointer. Displaying '0', 0x30 or
# 0x0 when analyzing the JSON debug stream could become
# A NULL / non-NULL pointer may be encoded in the stream as a
# '0'/'1' to represent the status of the pointer. Displaying '0',
# 0x30 or 0x0 when analyzing the JSON debug stream could become
# confusing, so use an explicit term instead.
return "nullptr"
return "null-ptr" if self.data == self.NULL_PTR_MARKER else "valid-ptr"
def __str__(self):
return self.__repr__()
def read(self):
super(VMSDFieldNull, self).read()
assert(self.data == self.NULL_PTR_MARKER)
super(VMSDFieldPtrMarker, self).read()
assert(self.data in [self.NULL_PTR_MARKER, self.VALID_PTR_MARKER])
return self.data
class VMSDFieldBool(VMSDFieldGeneric):
@@ -642,7 +642,9 @@ vmsd_field_readers = {
"bitmap" : VMSDFieldGeneric,
"struct" : VMSDFieldStruct,
"capability": VMSDFieldCap,
"nullptr": VMSDFieldNull,
# Keep the old nullptr for old binaries
"nullptr": VMSDFieldPtrMarker,
"ptr-marker": VMSDFieldPtrMarker,
"unknown" : VMSDFieldGeneric,
}