Skip to content

Commit 9867c3a

Browse files
xzpeterFabiano Rosas
authored andcommitted
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 07d4e69 ("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/[email protected] 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 <[email protected]> Message-Id: <[email protected]> Signed-off-by: Fabiano Rosas <[email protected]>
1 parent f52965b commit 9867c3a

File tree

1 file changed

+91
-27
lines changed

1 file changed

+91
-27
lines changed

migration/vmstate.c

Lines changed: 91 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,36 @@ vmstate_field_exists(const VMStateDescription *vmsd, const VMStateField *field,
5151
return result;
5252
}
5353

54+
/*
55+
* Create a fake nullptr field when there's a NULL pointer detected in the
56+
* array of a VMS_ARRAY_OF_POINTER VMSD field. It's needed because we
57+
* can't dereference the NULL pointer.
58+
*/
59+
static const VMStateField *
60+
vmsd_create_fake_nullptr_field(const VMStateField *field)
61+
{
62+
VMStateField *fake = g_new0(VMStateField, 1);
63+
64+
/* It can only happen on an array of pointers! */
65+
assert(field->flags & VMS_ARRAY_OF_POINTER);
66+
67+
/* Some of fake's properties should match the original's */
68+
fake->name = field->name;
69+
fake->version_id = field->version_id;
70+
71+
/* Do not need "field_exists" check as it always exists (which is null) */
72+
fake->field_exists = NULL;
73+
74+
/* See vmstate_info_nullptr - use 1 byte to represent nullptr */
75+
fake->size = 1;
76+
fake->info = &vmstate_info_nullptr;
77+
fake->flags = VMS_SINGLE;
78+
79+
/* All the rest fields shouldn't matter.. */
80+
81+
return (const VMStateField *)fake;
82+
}
83+
5484
static int vmstate_n_elems(void *opaque, const VMStateField *field)
5585
{
5686
int n_elems = 1;
@@ -143,23 +173,39 @@ int vmstate_load_state(QEMUFile *f, const VMStateDescription *vmsd,
143173
}
144174
for (i = 0; i < n_elems; i++) {
145175
void *curr_elem = first_elem + size * i;
176+
const VMStateField *inner_field;
146177

147178
if (field->flags & VMS_ARRAY_OF_POINTER) {
148179
curr_elem = *(void **)curr_elem;
149180
}
181+
150182
if (!curr_elem && size) {
151-
/* if null pointer check placeholder and do not follow */
152-
assert(field->flags & VMS_ARRAY_OF_POINTER);
153-
ret = vmstate_info_nullptr.get(f, curr_elem, size, NULL);
154-
} else if (field->flags & VMS_STRUCT) {
155-
ret = vmstate_load_state(f, field->vmsd, curr_elem,
156-
field->vmsd->version_id);
157-
} else if (field->flags & VMS_VSTRUCT) {
158-
ret = vmstate_load_state(f, field->vmsd, curr_elem,
159-
field->struct_version_id);
183+
/*
184+
* If null pointer found (which should only happen in
185+
* an array of pointers), use null placeholder and do
186+
* not follow.
187+
*/
188+
inner_field = vmsd_create_fake_nullptr_field(field);
189+
} else {
190+
inner_field = field;
191+
}
192+
193+
if (inner_field->flags & VMS_STRUCT) {
194+
ret = vmstate_load_state(f, inner_field->vmsd, curr_elem,
195+
inner_field->vmsd->version_id);
196+
} else if (inner_field->flags & VMS_VSTRUCT) {
197+
ret = vmstate_load_state(f, inner_field->vmsd, curr_elem,
198+
inner_field->struct_version_id);
160199
} else {
161-
ret = field->info->get(f, curr_elem, size, field);
200+
ret = inner_field->info->get(f, curr_elem, size,
201+
inner_field);
162202
}
203+
204+
/* If we used a fake temp field.. free it now */
205+
if (inner_field != field) {
206+
g_clear_pointer((gpointer *)&inner_field, g_free);
207+
}
208+
163209
if (ret >= 0) {
164210
ret = qemu_file_get_error(f);
165211
}
@@ -387,29 +433,50 @@ int vmstate_save_state_v(QEMUFile *f, const VMStateDescription *vmsd,
387433
}
388434
for (i = 0; i < n_elems; i++) {
389435
void *curr_elem = first_elem + size * i;
436+
const VMStateField *inner_field;
390437

391-
vmsd_desc_field_start(vmsd, vmdesc_loop, field, i, n_elems);
392438
old_offset = qemu_file_transferred(f);
393439
if (field->flags & VMS_ARRAY_OF_POINTER) {
394440
assert(curr_elem);
395441
curr_elem = *(void **)curr_elem;
396442
}
443+
397444
if (!curr_elem && size) {
398-
/* if null pointer write placeholder and do not follow */
399-
assert(field->flags & VMS_ARRAY_OF_POINTER);
400-
ret = vmstate_info_nullptr.put(f, curr_elem, size, NULL,
401-
NULL);
402-
} else if (field->flags & VMS_STRUCT) {
403-
ret = vmstate_save_state(f, field->vmsd, curr_elem,
404-
vmdesc_loop);
405-
} else if (field->flags & VMS_VSTRUCT) {
406-
ret = vmstate_save_state_v(f, field->vmsd, curr_elem,
407-
vmdesc_loop,
408-
field->struct_version_id, errp);
445+
/*
446+
* If null pointer found (which should only happen in
447+
* an array of pointers), use null placeholder and do
448+
* not follow.
449+
*/
450+
inner_field = vmsd_create_fake_nullptr_field(field);
451+
} else {
452+
inner_field = field;
453+
}
454+
455+
vmsd_desc_field_start(vmsd, vmdesc_loop, inner_field,
456+
i, n_elems);
457+
458+
if (inner_field->flags & VMS_STRUCT) {
459+
ret = vmstate_save_state(f, inner_field->vmsd,
460+
curr_elem, vmdesc_loop);
461+
} else if (inner_field->flags & VMS_VSTRUCT) {
462+
ret = vmstate_save_state_v(f, inner_field->vmsd,
463+
curr_elem, vmdesc_loop,
464+
inner_field->struct_version_id,
465+
errp);
409466
} else {
410-
ret = field->info->put(f, curr_elem, size, field,
411-
vmdesc_loop);
467+
ret = inner_field->info->put(f, curr_elem, size,
468+
inner_field, vmdesc_loop);
412469
}
470+
471+
written_bytes = qemu_file_transferred(f) - old_offset;
472+
vmsd_desc_field_end(vmsd, vmdesc_loop, inner_field,
473+
written_bytes);
474+
475+
/* If we used a fake temp field.. free it now */
476+
if (inner_field != field) {
477+
g_clear_pointer((gpointer *)&inner_field, g_free);
478+
}
479+
413480
if (ret) {
414481
error_setg(errp, "Save of field %s/%s failed",
415482
vmsd->name, field->name);
@@ -419,9 +486,6 @@ int vmstate_save_state_v(QEMUFile *f, const VMStateDescription *vmsd,
419486
return ret;
420487
}
421488

422-
written_bytes = qemu_file_transferred(f) - old_offset;
423-
vmsd_desc_field_end(vmsd, vmdesc_loop, field, written_bytes);
424-
425489
/* Compressed arrays only care about the first element */
426490
if (vmdesc_loop && vmsd_can_compress(field)) {
427491
vmdesc_loop = NULL;

0 commit comments

Comments
 (0)