diff mbox

[2/3] Add migration debug device

Message ID 1382533884-18739-3-git-send-email-agraf@suse.de
State New
Headers show

Commit Message

Alexander Graf Oct. 23, 2013, 1:11 p.m. UTC
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
diff mbox

Patch

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 <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)