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 <ag...@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 diff --git a/hw/misc/Makefile.objs b/hw/misc/Makefile.objs index 2578e29..4cfe8a4 100644 --- a/hw/misc/Makefile.objs +++ b/hw/misc/Makefile.objs @@ -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 diff --git a/hw/misc/debug_migration.c b/hw/misc/debug_migration.c new file mode 100644 index 0000000..813041e --- /dev/null +++ b/hw/misc/debug_migration.c @@ -0,0 +1,498 @@ +/* + * QEMU pseudo-device to expose migration details + * + * Copyright (c) 2013 Alexander Graf <ag...@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) -- 1.7.12.4