mirror of
https://github.com/Motorhead1991/qemu.git
synced 2025-08-03 07:43:54 -06:00
migration: Dump correct JSON format for nullptr replacement
QEMU plays a trick with null pointers inside an array of pointers in a VMSD
field. See 07d4e69147
("migration/vmstate: fix array of ptr with
nullptrs") for more details on why. The idea makes sense in general, but
it may overlooked the JSON writer where it could write nothing in a
"struct" in the JSON hints section.
We hit some analyze-migration.py issues on s390 recently, showing that some
of the struct field contains nothing, like:
{"name": "css", "array_len": 256, "type": "struct", "struct": {}, "size": 1}
As described in details by Fabiano:
https://lore.kernel.org/r/87pll37cin.fsf@suse.de
It could be that we hit some null pointers there, and JSON was gone when
they're null pointers.
To fix it, instead of hacking around only at VMStateInfo level, do that
from VMStateField level, so that JSON writer can also be involved. In this
case, JSON writer will replace the pointer array (which used to be a
"struct") to be the real representation of the nullptr field.
Signed-off-by: Peter Xu <peterx@redhat.com>
Message-Id: <20250109185249.23952-6-farosas@suse.de>
Signed-off-by: Fabiano Rosas <farosas@suse.de>
This commit is contained in:
parent
f52965bf0e
commit
9867c3a7ce
1 changed files with 91 additions and 27 deletions
|
@ -51,6 +51,36 @@ vmstate_field_exists(const VMStateDescription *vmsd, const VMStateField *field,
|
|||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Create a fake nullptr 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)
|
||||
{
|
||||
VMStateField *fake = g_new0(VMStateField, 1);
|
||||
|
||||
/* It can only happen on an array of pointers! */
|
||||
assert(field->flags & VMS_ARRAY_OF_POINTER);
|
||||
|
||||
/* Some of fake's properties should match the original's */
|
||||
fake->name = field->name;
|
||||
fake->version_id = field->version_id;
|
||||
|
||||
/* Do not need "field_exists" check as it always exists (which is null) */
|
||||
fake->field_exists = NULL;
|
||||
|
||||
/* See vmstate_info_nullptr - use 1 byte to represent nullptr */
|
||||
fake->size = 1;
|
||||
fake->info = &vmstate_info_nullptr;
|
||||
fake->flags = VMS_SINGLE;
|
||||
|
||||
/* All the rest fields shouldn't matter.. */
|
||||
|
||||
return (const VMStateField *)fake;
|
||||
}
|
||||
|
||||
static int vmstate_n_elems(void *opaque, const VMStateField *field)
|
||||
{
|
||||
int n_elems = 1;
|
||||
|
@ -143,23 +173,39 @@ int vmstate_load_state(QEMUFile *f, const VMStateDescription *vmsd,
|
|||
}
|
||||
for (i = 0; i < n_elems; i++) {
|
||||
void *curr_elem = first_elem + size * i;
|
||||
const VMStateField *inner_field;
|
||||
|
||||
if (field->flags & VMS_ARRAY_OF_POINTER) {
|
||||
curr_elem = *(void **)curr_elem;
|
||||
}
|
||||
|
||||
if (!curr_elem && size) {
|
||||
/* if null pointer check placeholder and do not follow */
|
||||
assert(field->flags & VMS_ARRAY_OF_POINTER);
|
||||
ret = vmstate_info_nullptr.get(f, curr_elem, size, NULL);
|
||||
} else if (field->flags & VMS_STRUCT) {
|
||||
ret = vmstate_load_state(f, field->vmsd, curr_elem,
|
||||
field->vmsd->version_id);
|
||||
} else if (field->flags & VMS_VSTRUCT) {
|
||||
ret = vmstate_load_state(f, field->vmsd, curr_elem,
|
||||
field->struct_version_id);
|
||||
/*
|
||||
* 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);
|
||||
} else {
|
||||
ret = field->info->get(f, curr_elem, size, field);
|
||||
inner_field = field;
|
||||
}
|
||||
|
||||
if (inner_field->flags & VMS_STRUCT) {
|
||||
ret = vmstate_load_state(f, inner_field->vmsd, curr_elem,
|
||||
inner_field->vmsd->version_id);
|
||||
} else if (inner_field->flags & VMS_VSTRUCT) {
|
||||
ret = vmstate_load_state(f, inner_field->vmsd, curr_elem,
|
||||
inner_field->struct_version_id);
|
||||
} else {
|
||||
ret = inner_field->info->get(f, curr_elem, size,
|
||||
inner_field);
|
||||
}
|
||||
|
||||
/* If we used a fake temp field.. free it now */
|
||||
if (inner_field != field) {
|
||||
g_clear_pointer((gpointer *)&inner_field, g_free);
|
||||
}
|
||||
|
||||
if (ret >= 0) {
|
||||
ret = qemu_file_get_error(f);
|
||||
}
|
||||
|
@ -387,29 +433,50 @@ int vmstate_save_state_v(QEMUFile *f, const VMStateDescription *vmsd,
|
|||
}
|
||||
for (i = 0; i < n_elems; i++) {
|
||||
void *curr_elem = first_elem + size * i;
|
||||
const VMStateField *inner_field;
|
||||
|
||||
vmsd_desc_field_start(vmsd, vmdesc_loop, field, i, n_elems);
|
||||
old_offset = qemu_file_transferred(f);
|
||||
if (field->flags & VMS_ARRAY_OF_POINTER) {
|
||||
assert(curr_elem);
|
||||
curr_elem = *(void **)curr_elem;
|
||||
}
|
||||
|
||||
if (!curr_elem && size) {
|
||||
/* if null pointer write placeholder and do not follow */
|
||||
assert(field->flags & VMS_ARRAY_OF_POINTER);
|
||||
ret = vmstate_info_nullptr.put(f, curr_elem, size, NULL,
|
||||
NULL);
|
||||
} else if (field->flags & VMS_STRUCT) {
|
||||
ret = vmstate_save_state(f, field->vmsd, curr_elem,
|
||||
vmdesc_loop);
|
||||
} else if (field->flags & VMS_VSTRUCT) {
|
||||
ret = vmstate_save_state_v(f, field->vmsd, curr_elem,
|
||||
vmdesc_loop,
|
||||
field->struct_version_id, errp);
|
||||
/*
|
||||
* 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);
|
||||
} else {
|
||||
ret = field->info->put(f, curr_elem, size, field,
|
||||
vmdesc_loop);
|
||||
inner_field = field;
|
||||
}
|
||||
|
||||
vmsd_desc_field_start(vmsd, vmdesc_loop, inner_field,
|
||||
i, n_elems);
|
||||
|
||||
if (inner_field->flags & VMS_STRUCT) {
|
||||
ret = vmstate_save_state(f, inner_field->vmsd,
|
||||
curr_elem, vmdesc_loop);
|
||||
} else if (inner_field->flags & VMS_VSTRUCT) {
|
||||
ret = vmstate_save_state_v(f, inner_field->vmsd,
|
||||
curr_elem, vmdesc_loop,
|
||||
inner_field->struct_version_id,
|
||||
errp);
|
||||
} else {
|
||||
ret = inner_field->info->put(f, curr_elem, size,
|
||||
inner_field, vmdesc_loop);
|
||||
}
|
||||
|
||||
written_bytes = qemu_file_transferred(f) - old_offset;
|
||||
vmsd_desc_field_end(vmsd, vmdesc_loop, inner_field,
|
||||
written_bytes);
|
||||
|
||||
/* If we used a fake temp field.. free it now */
|
||||
if (inner_field != field) {
|
||||
g_clear_pointer((gpointer *)&inner_field, g_free);
|
||||
}
|
||||
|
||||
if (ret) {
|
||||
error_setg(errp, "Save of field %s/%s failed",
|
||||
vmsd->name, field->name);
|
||||
|
@ -419,9 +486,6 @@ int vmstate_save_state_v(QEMUFile *f, const VMStateDescription *vmsd,
|
|||
return ret;
|
||||
}
|
||||
|
||||
written_bytes = qemu_file_transferred(f) - old_offset;
|
||||
vmsd_desc_field_end(vmsd, vmdesc_loop, field, written_bytes);
|
||||
|
||||
/* Compressed arrays only care about the first element */
|
||||
if (vmdesc_loop && vmsd_can_compress(field)) {
|
||||
vmdesc_loop = NULL;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue