Skip to content

Commit 35049eb

Browse files
author
Fabiano Rosas
committed
migration: Fix arrays of pointers in JSON writer
Currently, if an array of pointers contains a NULL pointer, that pointer will be encoded as '0' in the stream. Since the JSON writer doesn't define a "pointer" type, that '0' will now be an uint8, which is different from the original type being pointed to, e.g. struct. (we're further calling uint8 "nullptr", but that's irrelevant to the issue) That mixed-type array shouldn't be compressed, otherwise data is lost as the code currently makes the whole array have the type of the first element: css = {NULL, NULL, ..., 0x5555568a7940, NULL}; {"name": "s390_css", "instance_id": 0, "vmsd_name": "s390_css", "version": 1, "fields": [ ..., {"name": "css", "array_len": 256, "type": "nullptr", "size": 1}, ..., ]} In the above, the valid pointer at position 254 got lost among the compressed array of nullptr. While we could disable the array compression when a NULL pointer is found, the JSON part of the stream still makes part of downtime, so we should avoid writing unecessary bytes to it. Keep the array compression in place, but if NULL and non-NULL pointers are mixed break the array into several type-contiguous pieces : css = {NULL, NULL, ..., 0x5555568a7940, NULL}; {"name": "s390_css", "instance_id": 0, "vmsd_name": "s390_css", "version": 1, "fields": [ ..., {"name": "css", "array_len": 254, "type": "nullptr", "size": 1}, {"name": "css", "type": "struct", "struct": {"vmsd_name": "s390_css_img", ... }, "size": 768}, {"name": "css", "type": "nullptr", "size": 1}, ..., ]} Now each type-discontiguous region will become a new JSON entry. The reader should interpret this as a concatenation of values, all part of the same field. Parsing the JSON with analyze-script.py now shows the proper data being pointed to at the places where the pointer is valid and "nullptr" where there's NULL: "s390_css (14)": { ... "css": [ "nullptr", "nullptr", ... "nullptr", { "chpids": [ { "in_use": "0x00", "type": "0x00", "is_virtual": "0x00" }, ... ] }, "nullptr", } Reviewed-by: Peter Xu <[email protected]> Message-Id: <[email protected]> Signed-off-by: Fabiano Rosas <[email protected]>
1 parent 9867c3a commit 35049eb

File tree

2 files changed

+50
-9
lines changed

2 files changed

+50
-9
lines changed

migration/vmstate.c

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -425,15 +425,19 @@ int vmstate_save_state_v(QEMUFile *f, const VMStateDescription *vmsd,
425425
int size = vmstate_size(opaque, field);
426426
uint64_t old_offset, written_bytes;
427427
JSONWriter *vmdesc_loop = vmdesc;
428+
bool is_prev_null = false;
428429

429430
trace_vmstate_save_state_loop(vmsd->name, field->name, n_elems);
430431
if (field->flags & VMS_POINTER) {
431432
first_elem = *(void **)first_elem;
432433
assert(first_elem || !n_elems || !size);
433434
}
435+
434436
for (i = 0; i < n_elems; i++) {
435437
void *curr_elem = first_elem + size * i;
436438
const VMStateField *inner_field;
439+
bool is_null;
440+
int max_elems = n_elems - i;
437441

438442
old_offset = qemu_file_transferred(f);
439443
if (field->flags & VMS_ARRAY_OF_POINTER) {
@@ -448,12 +452,39 @@ int vmstate_save_state_v(QEMUFile *f, const VMStateDescription *vmsd,
448452
* not follow.
449453
*/
450454
inner_field = vmsd_create_fake_nullptr_field(field);
455+
is_null = true;
451456
} else {
452457
inner_field = field;
458+
is_null = false;
459+
}
460+
461+
/*
462+
* Due to the fake nullptr handling above, if there's mixed
463+
* null/non-null data, it doesn't make sense to emit a
464+
* compressed array representation spanning the entire array
465+
* because the field types will be different (e.g. struct
466+
* vs. nullptr). Search ahead for the next null/non-null element
467+
* and start a new compressed array if found.
468+
*/
469+
if (field->flags & VMS_ARRAY_OF_POINTER &&
470+
is_null != is_prev_null) {
471+
472+
is_prev_null = is_null;
473+
vmdesc_loop = vmdesc;
474+
475+
for (int j = i + 1; j < n_elems; j++) {
476+
void *elem = *(void **)(first_elem + size * j);
477+
bool elem_is_null = !elem && size;
478+
479+
if (is_null != elem_is_null) {
480+
max_elems = j - i;
481+
break;
482+
}
483+
}
453484
}
454485

455486
vmsd_desc_field_start(vmsd, vmdesc_loop, inner_field,
456-
i, n_elems);
487+
i, max_elems);
457488

458489
if (inner_field->flags & VMS_STRUCT) {
459490
ret = vmstate_save_state(f, inner_field->vmsd,

scripts/analyze-migration.py

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -502,15 +502,25 @@ def read(self):
502502
field['data'] = reader(field, self.file)
503503
field['data'].read()
504504

505-
if 'index' in field:
506-
if field['name'] not in self.data:
507-
self.data[field['name']] = []
508-
a = self.data[field['name']]
509-
if len(a) != int(field['index']):
510-
raise Exception("internal index of data field unmatched (%d/%d)" % (len(a), int(field['index'])))
511-
a.append(field['data'])
505+
fname = field['name']
506+
fdata = field['data']
507+
508+
# The field could be:
509+
# i) a single data entry, e.g. uint64
510+
# ii) an array, indicated by it containing the 'index' key
511+
#
512+
# However, the overall data after parsing the whole
513+
# stream, could be a mix of arrays and single data fields,
514+
# all sharing the same field name due to how QEMU breaks
515+
# up arrays with NULL pointers into multiple compressed
516+
# array segments.
517+
if fname not in self.data:
518+
self.data[fname] = fdata
519+
elif type(self.data[fname]) == list:
520+
self.data[fname].append(fdata)
512521
else:
513-
self.data[field['name']] = field['data']
522+
tmp = self.data[fname]
523+
self.data[fname] = [tmp, fdata]
514524

515525
if 'subsections' in self.desc['struct']:
516526
for subsection in self.desc['struct']['subsections']:

0 commit comments

Comments
 (0)