@@ -41,3 +41,4 @@ obj-$(CONFIG_SLAVIO) += slavio_misc.o
obj-$(CONFIG_ZYNQ) += zynq_slcr.o
obj-$(CONFIG_PVPANIC) += pvpanic.o
+obj-y += debug_migration.o
new file mode 100644
@@ -0,0 +1,498 @@
+/*
+ * QEMU pseudo-device to expose migration details
+ *
+ * Copyright (c) 2013 Alexander Graf <agraf@suse.de>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/hw.h"
+#include "qemu/savevm.h"
+#include "qapi/qmp/qstring.h"
+
+#define TYPE_DEBUG_MIGRATION_DEVICE "debug-migration"
+
+typedef struct DebugMigration {
+ DeviceState parent_obj;
+
+ uint8_t magic[16];
+ int32_t size;
+ char *data;
+} DebugMigration;
+
+
+/**************** QJSON *****************/
+
+typedef struct QJSON {
+ QString *str;
+ bool omit_comma;
+ unsigned long self_size_offset;
+} QJSON;
+
+static void json_emit_element(QJSON *json, const char *name)
+{
+ /* Check whether we need to print a , before an element */
+ if (json->omit_comma) {
+ json->omit_comma = false;
+ } else {
+ qstring_append(json->str, ", ");
+ }
+
+ if (name) {
+ qstring_append(json->str, "\"");
+ qstring_append(json->str, name);
+ qstring_append(json->str, "\" : ");
+ }
+}
+
+static void json_start_object(QJSON *json, const char *name)
+{
+ json_emit_element(json, name);
+ qstring_append(json->str, "{ ");
+ json->omit_comma = true;
+}
+
+static void json_end_object(QJSON *json)
+{
+ qstring_append(json->str, " }");
+ json->omit_comma = false;
+}
+
+static void json_start_array(QJSON *json, const char *name)
+{
+ json_emit_element(json, name);
+ qstring_append(json->str, "[ ");
+ json->omit_comma = true;
+}
+
+static void json_end_array(QJSON *json)
+{
+ qstring_append(json->str, " ]");
+ json->omit_comma = false;
+}
+
+static void json_prop_int(QJSON *json, const char *name, int64_t val)
+{
+ json_emit_element(json, name);
+ qstring_append_int(json->str, val);
+}
+
+static void json_prop_str(QJSON *json, const char *name, const char *str)
+{
+ json_emit_element(json, name);
+ qstring_append_chr(json->str, '"');
+ qstring_append(json->str, str);
+ qstring_append_chr(json->str, '"');
+}
+
+static QJSON *qjson_new(void)
+{
+ QJSON *json = g_new(QJSON, 1);
+ json->str = qstring_from_str("{ ");
+ json->omit_comma = true;
+ return json;
+}
+
+static void qjson_finish(QJSON *json)
+{
+ json_end_object(json);
+}
+
+
+/**************** fake_file *****************/
+
+
+static int fake_file_put_buffer(void *opaque, const uint8_t *json,
+ int64_t pos, int size)
+{
+ size_t *offset = (size_t *)opaque;
+ *offset += size;
+ return size;
+}
+
+const QEMUFileOps fake_file_ops = {
+ .put_buffer = fake_file_put_buffer,
+};
+
+
+/**************** debug_migration *****************/
+
+static void print_vmsd(QJSON *json, const VMStateDescription *vmsd,
+ void *opaque);
+static void print_vmsd_one(QJSON *json, const VMStateDescription *vmsd,
+ void *opaque, int version_id);
+static const VMStateDescription vmstate_debug_migration;
+
+
+static void print_non_vmstate(QJSON *json, SaveStateEntry *se)
+{
+ QEMUFile *fakefile;
+ size_t offset = 0;
+
+ fakefile = qemu_fopen_ops(&offset, &fake_file_ops);
+
+ offset = 0;
+ se->ops->save_state(fakefile, se->opaque);
+ qemu_fflush(fakefile);
+
+ json_prop_int(json, "size", offset);
+ json_start_array(json, "fields");
+ json_start_object(json, NULL);
+ json_prop_str(json, "name", "data");
+ json_prop_int(json, "size", offset);
+ json_prop_str(json, "type", "buffer");
+ json_end_object(json);
+ json_end_array(json);
+
+ qemu_fclose(fakefile);
+}
+
+static const char *unknown = "unknown";
+static const char *get_vmfield_type_name(QJSON *json,
+ const VMStateDescription *vmsd,
+ void *opaque, VMStateField *field)
+{
+ const char *type = unknown;
+
+ if (field->info == &vmstate_info_bool) {
+ type = "bool";
+ } else if (field->info == &vmstate_info_int8) {
+ type = "int8";
+ } else if (field->info == &vmstate_info_int16) {
+ type = "int16";
+ } else if (field->info == &vmstate_info_int32) {
+ type = "int32";
+ } else if (field->info == &vmstate_info_int64) {
+ type = "int64";
+ } else if (field->info == &vmstate_info_uint8_equal) {
+ type = "uint8";
+ } else if (field->info == &vmstate_info_uint16_equal) {
+ type = "uint16";
+ } else if (field->info == &vmstate_info_int32_equal) {
+ type = "int32_equal";
+ } else if (field->info == &vmstate_info_uint32_equal) {
+ type = "uint32";
+ } else if (field->info == &vmstate_info_uint64_equal) {
+ type = "uint64";
+ } else if (field->info == &vmstate_info_int32_le) {
+ type = "int32_le";
+ } else if (field->info == &vmstate_info_uint8) {
+ type = "uint8";
+ } else if (field->info == &vmstate_info_uint16) {
+ type = "uint16";
+ } else if (field->info == &vmstate_info_uint32) {
+ type = "uint32";
+ } else if (field->info == &vmstate_info_uint64) {
+ type = "uint64";
+ } else if (field->info == &vmstate_info_float64) {
+ type = "float64";
+ } else if (field->info == &vmstate_info_timer) {
+ type = "timer";
+ } else if (field->info == &vmstate_info_buffer) {
+ type = "buffer";
+ } else if (field->info == &vmstate_info_unused_buffer) {
+ type = "unused_buffer";
+ } else if (field->info == &vmstate_info_bitmap) {
+ type = "bitmap";
+ } else if (field->flags & VMS_STRUCT) {
+ type = "struct";
+ }
+
+ return type;
+}
+
+static void print_vmfield(QJSON *json, const VMStateDescription *vmsd,
+ void *opaque, VMStateField *field, const char *name)
+{
+ void *base_addr = opaque + field->offset;
+ int size = field->size;
+ QEMUFile *fakefile;
+ size_t offset = 0;
+ int n_elems = 1;
+ int i;
+
+ fakefile = qemu_fopen_ops(&offset, &fake_file_ops);
+
+ if (field->flags & VMS_VBUFFER) {
+ size = *(int32_t *)(opaque + field->size_offset);
+ if (field->flags & VMS_MULTIPLY) {
+ size *= field->size;
+ }
+ }
+
+ if (field->flags & VMS_ARRAY) {
+ n_elems = field->num;
+ } else if (field->flags & VMS_VARRAY_INT32) {
+ n_elems = *(int32_t *)(opaque + field->num_offset);
+ } else if (field->flags & VMS_VARRAY_UINT32) {
+ n_elems = *(uint32_t *)(opaque + field->num_offset);
+ } else if (field->flags & VMS_VARRAY_UINT16) {
+ n_elems = *(uint16_t *)(opaque + field->num_offset);
+ } else if (field->flags & VMS_VARRAY_UINT8) {
+ n_elems = *(uint8_t *)(opaque + field->num_offset);
+ }
+
+ if (field->flags & VMS_POINTER) {
+ base_addr = *(void **)base_addr + field->start;
+ }
+
+ for (i = 0; i < n_elems; i++) {
+ void *addr = base_addr + size * i;
+ const char *type_name = get_vmfield_type_name(json, vmsd, addr, field);
+
+ if (field->flags & VMS_ARRAY_OF_POINTER) {
+ addr = *(void **)addr;
+ }
+
+ json_start_object(json, NULL);
+ json_prop_str(json, "name", name);
+ if (n_elems > 1) {
+ json_prop_int(json, "index", i);
+ }
+
+ /* Hack for the buffer we're writing to */
+ if (vmsd == &vmstate_debug_migration &&
+ field->info == &vmstate_info_buffer &&
+ field->flags & VMS_VBUFFER) {
+ json_prop_int(json, "size", 100000000);
+ json->self_size_offset = qstring_get_length(json->str) -
+ strlen("100000000");
+ } else if (type_name == unknown) {
+ offset = 0;
+ field->info->put(fakefile, opaque, size);
+ qemu_fflush(fakefile);
+ json_prop_int(json, "size", offset);
+ } else {
+ json_prop_int(json, "size", size);
+ }
+
+ json_prop_str(json, "type", type_name);
+
+ if (field->flags & VMS_STRUCT) {
+ /* Structs have hardcoded version IDs */
+ json_start_object(json, "struct");
+ json_prop_int(json, "version_id", field->vmsd->version_id);
+ print_vmsd_one(json, field->vmsd, addr, field->vmsd->version_id);
+ json_end_object(json);
+ }
+
+ json_end_object(json);
+ }
+
+ qemu_fclose(fakefile);
+}
+
+static int vmfield_name_num(VMStateField *start, VMStateField *search)
+{
+ VMStateField *field;
+ int found = 0;
+
+ for (field = start; field->name; field++) {
+ if (!strcmp(field->name, search->name)) {
+ if (field == search) {
+ return found;
+ }
+ found++;
+ }
+ }
+
+ return -1;
+}
+
+static bool vmfield_name_is_unique(VMStateField *start, VMStateField *search)
+{
+ VMStateField *field;
+ int found = 0;
+
+ for (field = start; field->name; field++) {
+ if (!strcmp(field->name, search->name)) {
+ found++;
+ /* name found more than once, so it's not unique */
+ if (found > 1) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+static void print_vmsd_one(QJSON *json, const VMStateDescription *vmsd,
+ void *opaque, int version_id)
+{
+ const VMStateSubsection *sub;
+ VMStateField *field;
+ bool subsection_found = false;
+
+ json_start_array(json, "fields");
+ for (field = vmsd->fields; field->name; field++) {
+ char *name = g_strdup(field->name);
+ bool (*fe)(void *opaque, int version_id) = field->field_exists;
+
+ if (!vmfield_name_is_unique(vmsd->fields, field)) {
+ /* Field name is not unique, need to make it unique */
+ int num = vmfield_name_num(vmsd->fields, field);
+ name = g_strdup_printf("%s[%d]", name, num);
+ }
+
+ if ((fe && fe(opaque, version_id)) ||
+ (!fe && field->version_id <= version_id)) {
+ /* Field exists in the current version, print it */
+ print_vmfield(json, vmsd, opaque, field, name);
+ }
+
+ g_free(name);
+ }
+ json_end_array(json);
+
+ for (sub = vmsd->subsections; sub && sub->needed; sub++) {
+ if (sub->needed(opaque)) {
+ /* This particular vmsd also contains this subsection */
+
+ /* Only create subsection array when we have any */
+ if (!subsection_found) {
+ json_start_array(json, "subsections");
+ subsection_found = true;
+ }
+
+ /* Dump the subsection description */
+ json_start_object(json, NULL);
+ print_vmsd(json, sub->vmsd, opaque);
+ json_end_object(json);
+
+ }
+ }
+ if (subsection_found) {
+ json_end_array(json);
+ }
+}
+
+static void print_vmsd(QJSON *json, const VMStateDescription *vmsd,
+ void *opaque)
+{
+ int version_id;
+ int min_ver = MIN(vmsd->minimum_version_id_old, vmsd->minimum_version_id);
+
+ json_prop_str(json, "vmsd_name", vmsd->name);
+ json_start_object(json, "versions");
+ for (version_id = min_ver; version_id <= vmsd->version_id; version_id++) {
+ char *str = g_strdup_printf("%d", version_id);
+ json_start_object(json, str);
+ print_vmsd_one(json, vmsd, opaque, version_id);
+ json_end_object(json);
+ g_free(str);
+ }
+ json_end_object(json);
+}
+
+static void debug_migration_pre_save(void *opaque)
+{
+ SaveStateEntry *se;
+ DebugMigration *dm = opaque;
+ char sizestr[10];
+
+ QJSON *json = qjson_new();
+
+ strcpy((char*)dm->magic, "Debug Migration");
+ json_start_array(json, "devices");
+
+ /* Print description of all device sections */
+ QTAILQ_FOREACH(se, &savevm_handlers, entry) {
+ if ((!se->ops || !se->ops->save_state) && !se->vmsd) {
+ continue;
+ }
+
+ json_start_object(json, NULL);
+ json_prop_str(json, "name", se->idstr);
+ json_prop_int(json, "instance_id", se->instance_id);
+
+ if (!se->vmsd) {
+ /* Not converted to VMState yet */
+ print_non_vmstate(json, se);
+ } else {
+ /* VMState based */
+ print_vmsd(json, se->vmsd, se->opaque);
+ }
+
+ json_end_object(json);
+ }
+
+ json_end_array(json);
+ qjson_finish(json);
+
+ dm->data = (char*)qstring_get_str(json->str);
+ dm->size = qstring_get_length(json->str) + 1;
+
+ /* Fix up my own size information */
+ sprintf(sizestr, "%9d", dm->size);
+ strncpy(&dm->data[json->self_size_offset], sizestr, 9);
+}
+
+static int debug_migration_pre_load(void *opaque)
+{
+ DebugMigration *dm = opaque;
+
+ /* Allocate big temporary buffer */
+ dm->data = g_malloc(10 * 1024 * 1024);
+
+ return 0;
+}
+
+static int debug_migration_post_load(void *opaque, int version_id)
+{
+ DebugMigration *dm = opaque;
+
+ /* Free the big buffer again */
+ g_free(dm->data);
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_debug_migration = {
+ .name = "debug-migration",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .pre_save = debug_migration_pre_save,
+ .pre_load = debug_migration_pre_load,
+ .post_load = debug_migration_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT32(size, DebugMigration),
+ VMSTATE_BUFFER(magic, DebugMigration),
+ VMSTATE_VBUFFER(data, DebugMigration, 0, NULL, 0, size),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void debug_migration_class_initfn(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->vmsd = &vmstate_debug_migration;
+ set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+}
+
+static const TypeInfo debug_migration_info = {
+ .name = TYPE_DEBUG_MIGRATION_DEVICE,
+ .parent = TYPE_DEVICE,
+ .instance_size = sizeof(DebugMigration),
+ .class_init = debug_migration_class_initfn,
+};
+
+static void debug_migration_register_types(void)
+{
+ type_register_static(&debug_migration_info);
+}
+
+type_init(debug_migration_register_types)
This patch adds a pseudo device whose sole purpose is to encapsulate a machine readable layout description of the vmstate stream layout inside of the stream. With this device enabled in the system while a migration is happening, we have to chance to decypher the contents of the stream from an external program without any knowledge of the device layout of the guest. Signed-off-by: Alexander Graf <agraf@suse.de> --- hw/misc/Makefile.objs | 1 + hw/misc/debug_migration.c | 498 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 499 insertions(+) create mode 100644 hw/misc/debug_migration.c