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>
Peter Xu committed
Apr 1, 2026 at 16:28 UTC
e86c004fbda963191298905d7e02f8efdb0d05cc
4 files changed
+48
-42
include/migration/vmstate.h
+7
-2
@@ -282,9 +282,14 @@ extern const VMStateInfo vmstate_info_uint32;
282
extern const VMStateInfo vmstate_info_uint64;
283
extern const VMStateInfo vmstate_info_fd;
284
285
-/** Put this in the stream when migrating a null pointer.*/
285
+/*
286
+ * Put this in the stream when migrating a pointer to reflect either a NULL
287
+ * or valid pointer.
288
+ */
289
#define VMS_MARKER_PTR_NULL (0x30U) /* '0' */
287
-extern const VMStateInfo vmstate_info_nullptr;
290
+#define VMS_MARKER_PTR_VALID (0x31U) /* '1' */
291
+
292
+extern const VMStateInfo vmstate_info_ptr_marker;
293
294
extern const VMStateInfo vmstate_info_cpudouble;
295
migration/vmstate-types.c
+16
-18
@@ -359,36 +359,34 @@ const VMStateInfo vmstate_info_fd = {
359
.save = save_fd,
360
};
361
362
-static bool load_nullptr(QEMUFile *f, void *pv, size_t size,
363
- const VMStateField *field, Error **errp)
362
+static bool load_ptr_marker(QEMUFile *f, void *pv, size_t size,
363
+ const VMStateField *field, Error **errp)
364
365
{
366
- if (qemu_get_byte(f) == VMS_MARKER_PTR_NULL) {
366
+ int byte = qemu_get_byte(f);
367
+
368
+ if (byte == VMS_MARKER_PTR_NULL || byte == VMS_MARKER_PTR_VALID) {
369
+ /* TODO: process PTR_VALID case */
370
return true;
371
}
372
370
- error_setg(errp, "vmstate: load_nullptr expected VMS_NULLPTR_MARKER");
373
+ error_setg(errp, "%s: unexpected ptr marker: %d", __func__, byte);
374
return false;
375
}
376
374
-static bool save_nullptr(QEMUFile *f, void *pv, size_t size,
375
- const VMStateField *field, JSONWriter *vmdesc,
376
- Error **errp)
377
+static bool save_ptr_marker(QEMUFile *f, void *pv, size_t size,
378
+ const VMStateField *field, JSONWriter *vmdesc,
379
+ Error **errp)
380
381
{
379
- if (pv == NULL) {
380
- qemu_put_byte(f, VMS_MARKER_PTR_NULL);
381
- return true;
382
- }
383
-
384
- error_setg(errp, "vmstate: save_nullptr must be called with pv == NULL");
385
- return false;
382
+ qemu_put_byte(f, pv ? VMS_MARKER_PTR_VALID : VMS_MARKER_PTR_NULL);
383
+ return true;
384
}
385
388
-const VMStateInfo vmstate_info_nullptr = {
389
- .name = "nullptr",
390
- .load = load_nullptr,
391
- .save = save_nullptr,
386
+const VMStateInfo vmstate_info_ptr_marker = {
387
+ .name = "ptr-marker",
388
+ .load = load_ptr_marker,
389
+ .save = save_ptr_marker,
390
};
391
392
/* 64 bit unsigned int. See that the received value is the same than the one
migration/vmstate.c
+13
-12
@@ -55,12 +55,12 @@ vmstate_field_exists(const VMStateDescription *vmsd, const VMStateField *field,
55
}
56
57
/*
58
- * Create a fake nullptr field when there's a NULL pointer detected in the
58
+ * Create a ptr marker field when there's a NULL pointer detected in the
59
* array of a VMS_ARRAY_OF_POINTER VMSD field. It's needed because we
60
* can't dereference the NULL pointer.
61
*/
62
static const VMStateField *
63
-vmsd_create_fake_nullptr_field(const VMStateField *field)
63
+vmsd_create_ptr_marker_field(const VMStateField *field)
64
{
65
VMStateField *fake = g_new0(VMStateField, 1);
66
@@ -71,12 +71,12 @@ vmsd_create_fake_nullptr_field(const VMStateField *field)
71
fake->name = field->name;
72
fake->version_id = field->version_id;
73
74
- /* Do not need "field_exists" check as it always exists (which is null) */
74
+ /* Do not need "field_exists" check as it always exists */
75
fake->field_exists = NULL;
76
77
- /* See vmstate_info_nullptr - use 1 byte to represent nullptr */
77
+ /* See vmstate_info_ptr_marker - use 1 byte to represent ptr status */
78
fake->size = 1;
79
- fake->info = &vmstate_info_nullptr;
79
+ fake->info = &vmstate_info_ptr_marker;
80
fake->flags = VMS_SINGLE;
81
82
/* All the rest fields shouldn't matter.. */
@@ -278,7 +278,7 @@ bool vmstate_load_vmsd(QEMUFile *f, const VMStateDescription *vmsd,
278
* an array of pointers), use null placeholder and do
279
* not follow.
280
*/
281
- inner_field = vmsd_create_fake_nullptr_field(field);
281
+ inner_field = vmsd_create_ptr_marker_field(field);
282
} else {
283
inner_field = field;
284
}
@@ -583,26 +583,27 @@ static bool vmstate_save_vmsd_v(QEMUFile *f, const VMStateDescription *vmsd,
583
for (i = 0; i < n_elems; i++) {
584
void *curr_elem = first_elem + size * i;
585
const VMStateField *inner_field;
586
- bool is_null;
586
/* maximum number of elements to compress in the JSON blob */
587
int max_elems = vmsd_can_compress(field) ? (n_elems - i) : 1;
588
+ bool use_marker_field, is_null;
589
590
if (field->flags & VMS_ARRAY_OF_POINTER) {
591
assert(curr_elem);
592
curr_elem = *(void **)curr_elem;
593
}
594
595
- if (!curr_elem && size) {
595
+ is_null = !curr_elem && size;
596
+ use_marker_field = is_null;
597
+
598
+ if (use_marker_field) {
599
/*
600
* If null pointer found (which should only happen in
601
* an array of pointers), use null placeholder and do
602
* not follow.
603
*/
601
- inner_field = vmsd_create_fake_nullptr_field(field);
602
- is_null = true;
604
+ inner_field = vmsd_create_ptr_marker_field(field);
605
} else {
606
inner_field = field;
605
- is_null = false;
607
}
608
609
/*
@@ -638,7 +639,7 @@ static bool vmstate_save_vmsd_v(QEMUFile *f, const VMStateDescription *vmsd,
639
i, max_elems, errp);
640
641
/* If we used a fake temp field.. free it now */
641
- if (is_null) {
642
+ if (use_marker_field) {
643
g_clear_pointer((gpointer *)&inner_field, g_free);
644
}
645
scripts/analyze-migration.py
+12
-10
@@ -469,26 +469,26 @@ class VMSDFieldIntLE(VMSDFieldInt):
469
super(VMSDFieldIntLE, self).__init__(desc, file)
470
self.dtype = '<i%d' % self.size
471
472
-class VMSDFieldNull(VMSDFieldGeneric):
472
+class VMSDFieldPtrMarker(VMSDFieldGeneric):
473
NULL_PTR_MARKER = b'0'
474
+ VALID_PTR_MARKER = b'1'
475
476
def __init__(self, desc, file):
476
- super(VMSDFieldNull, self).__init__(desc, file)
477
+ super(VMSDFieldPtrMarker, self).__init__(desc, file)
478
479
def __repr__(self):
479
- # A NULL pointer is encoded in the stream as a '0' to
480
- # disambiguate from a mere 0x0 value and avoid consumers
481
- # trying to follow the NULL pointer. Displaying '0', 0x30 or
482
- # 0x0 when analyzing the JSON debug stream could become
480
+ # A NULL / non-NULL pointer may be encoded in the stream as a
481
+ # '0'/'1' to represent the status of the pointer. Displaying '0',
482
+ # 0x30 or 0x0 when analyzing the JSON debug stream could become
483
# confusing, so use an explicit term instead.
484
- return "nullptr"
484
+ return "null-ptr" if self.data == self.NULL_PTR_MARKER else "valid-ptr"
485
486
def __str__(self):
487
return self.__repr__()
488
489
def read(self):
490
- super(VMSDFieldNull, self).read()
491
- assert(self.data == self.NULL_PTR_MARKER)
490
+ super(VMSDFieldPtrMarker, self).read()
491
+ assert(self.data in [self.NULL_PTR_MARKER, self.VALID_PTR_MARKER])
492
return self.data
493
494
class VMSDFieldBool(VMSDFieldGeneric):
@@ -642,7 +642,9 @@ vmsd_field_readers = {
642
"bitmap" : VMSDFieldGeneric,
643
"struct" : VMSDFieldStruct,
644
"capability": VMSDFieldCap,
645
- "nullptr": VMSDFieldNull,
645
+ # Keep the old nullptr for old binaries
646
+ "nullptr": VMSDFieldPtrMarker,
647
+ "ptr-marker": VMSDFieldPtrMarker,
648
"unknown" : VMSDFieldGeneric,
649
}
650