From patchwork Sun May 23 10:59:27 2010 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jan Kiszka X-Patchwork-Id: 53322 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.gnu.org (lists.gnu.org [199.232.76.165]) (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)) (Client did not present a certificate) by ozlabs.org (Postfix) with ESMTPS id B16FFB7D24 for ; Sun, 23 May 2010 21:31:15 +1000 (EST) Received: from localhost ([127.0.0.1]:44683 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1OG9L1-0007og-Af for incoming@patchwork.ozlabs.org; Sun, 23 May 2010 07:27:23 -0400 Received: from [140.186.70.92] (port=48697 helo=eggs.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1OG8uU-0004JV-VA for qemu-devel@nongnu.org; Sun, 23 May 2010 07:00:06 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.69) (envelope-from ) id 1OG8uF-0004RF-4a for qemu-devel@nongnu.org; Sun, 23 May 2010 06:59:58 -0400 Received: from fmmailgate01.web.de ([217.72.192.221]:34391) by eggs.gnu.org with esmtp (Exim 4.69) (envelope-from ) id 1OG8uC-0004Pg-Gu for qemu-devel@nongnu.org; Sun, 23 May 2010 06:59:41 -0400 Received: from smtp01.web.de ( [172.20.0.243]) by fmmailgate01.web.de (Postfix) with ESMTP id 25D5C15B16D33; Sun, 23 May 2010 12:59:40 +0200 (CEST) Received: from [88.65.39.229] (helo=localhost.localdomain) by smtp01.web.de with asmtp (TLSv1:AES256-SHA:256) (WEB.DE 4.110 #4) id 1OG8uA-0003cZ-00; Sun, 23 May 2010 12:59:38 +0200 From: Jan Kiszka To: qemu-devel@nongnu.org, Anthony Liguori Date: Sun, 23 May 2010 12:59:27 +0200 Message-Id: X-Mailer: git-send-email 1.6.0.2 In-Reply-To: References: In-Reply-To: References: X-Sender: jan.kiszka@web.de X-Provags-ID: V01U2FsdGVkX18SwOYQCCyusGBRhEsLVtr6BgY+IDCoxLStf1pQ kL01JMzH/Df7WJ5mAa3GFozngTzH08FHrqImmG+rcS42P9gcbI zXYJhia7w= X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.4-2.6 Cc: Juan Quintela , Jan Kiszka , Markus Armbruster , Luiz Capitulino , Blue Swirl , Avi Kivity Subject: [Qemu-devel] [PATCH v3 14/17] monitor: Add basic device state visualization X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.5 Precedence: list List-Id: qemu-devel.nongnu.org List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org From: Jan Kiszka This introduces device_show, a monitor command that saves the vmstate of a qdev device and visualizes it. Buffers are cut after 16 byte by default, but the full content can be requested via '-f'. To pretty-print sub-arrays, vmstate is extended to store the start index name. A new qerror is introduced to signal a missing vmstate. QMP is not supported as we cannot provide a stable interface, at least at this point. Signed-off-by: Jan Kiszka --- hw/hw.h | 2 + hw/qdev.c | 243 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ hw/qdev.h | 2 + qemu-monitor.hx | 19 +++++ qerror.c | 4 + qerror.h | 3 + 6 files changed, 273 insertions(+), 0 deletions(-) diff --git a/hw/hw.h b/hw/hw.h index fc2d184..cc4bd5f 100644 --- a/hw/hw.h +++ b/hw/hw.h @@ -299,6 +299,7 @@ enum VMStateFlags { typedef struct { const char *name; + const char *start_index; size_t offset; size_t size; size_t start; @@ -413,6 +414,7 @@ extern const VMStateInfo vmstate_info_unused_buffer; .size = sizeof(_type), \ .flags = VMS_ARRAY, \ .offset = vmstate_offset_sub_array(_state, _field, _type, _start), \ + .start_index = (stringify(_start)), \ } #define VMSTATE_VARRAY_INT32(_field, _state, _field_num, _version, _info, _type) {\ diff --git a/hw/qdev.c b/hw/qdev.c index 6f7d745..b5bf72c 100644 --- a/hw/qdev.c +++ b/hw/qdev.c @@ -29,6 +29,9 @@ #include "qdev.h" #include "sysemu.h" #include "monitor.h" +#include "qjson.h" +#include "qint.h" +#include "qbuffer.h" static int qdev_hotplug = 0; @@ -889,3 +892,243 @@ int do_device_del(Monitor *mon, const QDict *qdict, QObject **ret_data) } return qdev_unplug(dev); } + +#define NAME_COLUMN_WIDTH 23 + +static void print_field(Monitor *mon, const QDict *qfield, int indent); + +static void print_elem(Monitor *mon, const QObject *qelem, size_t size, + int column_pos, int indent) +{ + int64_t data_size; + const void *data; + int n; + + if (qobject_type(qelem) == QTYPE_QDICT) { + if (column_pos >= 0) { + monitor_printf(mon, ".\n"); + } + } else { + monitor_printf(mon, ":"); + column_pos++; + if (column_pos < NAME_COLUMN_WIDTH) { + monitor_printf(mon, "%*c", NAME_COLUMN_WIDTH - column_pos, ' '); + } + } + + switch (qobject_type(qelem)) { + case QTYPE_QDICT: + print_field(mon, qobject_to_qdict(qelem), indent + 2); + break; + case QTYPE_QBUFFER: + data = qbuffer_get_data(qobject_to_qbuffer(qelem)); + data_size = qbuffer_get_size(qobject_to_qbuffer(qelem)); + for (n = 0; n < data_size; ) { + monitor_printf(mon, " %02x", *((uint8_t *)data+n)); + if (++n < size) { + if (n % 16 == 0) { + monitor_printf(mon, "\n%*c", NAME_COLUMN_WIDTH, ' '); + } else if (n % 8 == 0) { + monitor_printf(mon, " -"); + } + } + } + if (data_size < size) { + monitor_printf(mon, " ..."); + } + monitor_printf(mon, "\n"); + break; + case QTYPE_QINT: + monitor_printf(mon, " %0*" PRIx64 "\n", (int)size * 2, + qint_get_int(qobject_to_qint(qelem))); + break; + default: + assert(0); + } +} + +static void print_field(Monitor *mon, const QDict *qfield, int indent) +{ + const char *name = qdict_get_str(qfield, "name"); + const char *start = qdict_get_try_str(qfield, "start"); + int64_t size = qdict_get_int(qfield, "size"); + QList *qlist = qdict_get_qlist(qfield, "elems"); + QListEntry *entry, *sub_entry; + QList *sub_list; + int elem_no = 0; + + QLIST_FOREACH_ENTRY(qlist, entry) { + QObject *qelem = qlist_entry_obj(entry); + int pos = indent + strlen(name); + + if (qobject_type(qelem) == QTYPE_QLIST) { + monitor_printf(mon, "%*c%s", indent, ' ', name); + if (start) { + pos += monitor_printf(mon, "[%s+%02x]", start, elem_no); + } else { + pos += monitor_printf(mon, "[%02x]", elem_no); + } + sub_list = qobject_to_qlist(qelem); + QLIST_FOREACH_ENTRY(sub_list, sub_entry) { + print_elem(mon, qlist_entry_obj(sub_entry), size, pos, + indent + 2); + pos = -1; + } + } else { + if (elem_no == 0) { + monitor_printf(mon, "%*c%s", indent, ' ', name); + } else { + pos = -1; + } + print_elem(mon, qelem, size, pos, indent); + } + elem_no++; + } +} + +void device_user_print(Monitor *mon, const QObject *data) +{ + QDict *qdict = qobject_to_qdict(data); + QList *qlist = qdict_get_qlist(qdict, "fields"); + QListEntry *entry; + + monitor_printf(mon, "dev: %s, id \"%s\", version %" PRId64 "\n", + qdict_get_str(qdict, "device"), + qdict_get_str(qdict, "id"), + qdict_get_int(qdict, "version")); + + QLIST_FOREACH_ENTRY(qlist, entry) { + print_field(mon, qobject_to_qdict(qlist_entry_obj(entry)), 2); + } +} + +static size_t parse_vmstate(const VMStateDescription *vmsd, void *opaque, + QList *qlist, int full_buffers) +{ + VMStateField *field = vmsd->fields; + size_t overall_size = 0; + + if (vmsd->pre_save) { + vmsd->pre_save(opaque); + } + while(field->name) { + if (!field->field_exists || + field->field_exists(opaque, vmsd->version_id)) { + void *base_addr = opaque + field->offset; + int i, n_elems = 1; + int is_array = 1; + size_t size = field->size; + size_t real_size = 0; + size_t dump_size; + QDict *qfield = qdict_new(); + QList *qelems = qlist_new(); + + qlist_append_obj(qlist, QOBJECT(qfield)); + + qdict_put_obj(qfield, "name", + QOBJECT(qstring_from_str(field->name))); + qdict_put_obj(qfield, "elems", QOBJECT(qelems)); + + if (field->flags & VMS_VBUFFER) { + size = *(int32_t *)(opaque + field->size_offset); + if (field->flags & VMS_MULTIPLY) { + size *= field->size; + } + } + if (field->start_index) { + qdict_put_obj(qfield, "start", + QOBJECT(qstring_from_str(field->start_index))); + } + + 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_UINT16) { + n_elems = *(uint16_t *)(opaque + field->num_offset); + } else { + is_array = 0; + } + 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; + QList *sub_elems = qelems; + int val; + + if (is_array) { + sub_elems = qlist_new(); + qlist_append_obj(qelems, QOBJECT(sub_elems)); + } + if (field->flags & VMS_ARRAY_OF_POINTER) { + addr = *(void **)addr; + } + if (field->flags & VMS_STRUCT) { + real_size = parse_vmstate(field->vmsd, addr, + sub_elems, full_buffers); + } else { + real_size = size; + if (field->flags & (VMS_BUFFER | VMS_VBUFFER)) { + dump_size = (full_buffers || size <= 16) ? size : 16; + qlist_append_obj(sub_elems, + QOBJECT(qbuffer_from_data(addr, dump_size))); + } else { + switch (size) { + case 1: + val = *(uint8_t *)addr; + break; + case 2: + val = *(uint16_t *)addr; + break; + case 4: + val = *(uint32_t *)addr; + break; + case 8: + val = *(uint64_t *)addr; + break; + default: + assert(0); + } + qlist_append_obj(sub_elems, + QOBJECT(qint_from_int(val))); + } + } + overall_size += real_size; + } + qdict_put_obj(qfield, "size", QOBJECT(qint_from_int(real_size))); + } + field++; + } + return overall_size; +} + +int do_device_show(Monitor *mon, const QDict *qdict, QObject **ret_data) +{ + const char *path = qdict_get_str(qdict, "path"); + const VMStateDescription *vmsd; + DeviceState *dev; + QList *qlist; + + dev = qdev_find_internal(path, true); + if (!dev) { + return -1; + } + + vmsd = dev->info->vmsd; + if (!vmsd) { + qerror_report(QERR_DEVICE_NO_STATE, dev->info->name); + error_printf_unless_qmp("Note: device may simply lack complete qdev " + "conversion\n"); + return -1; + } + + *ret_data = qobject_from_jsonf("{ 'device': %s, 'id': %s, 'version': %d }", + dev->info->name, dev->id ? : "", + vmsd->version_id); + qlist = qlist_new(); + parse_vmstate(vmsd, dev, qlist, qdict_get_int(qdict, "full")); + qdict_put_obj(qobject_to_qdict(*ret_data), "fields", QOBJECT(qlist)); + + return 0; +} diff --git a/hw/qdev.h b/hw/qdev.h index b27d33b..447d13c 100644 --- a/hw/qdev.h +++ b/hw/qdev.h @@ -183,6 +183,8 @@ void do_info_qtree(Monitor *mon); void do_info_qdm(Monitor *mon); int do_device_add(Monitor *mon, const QDict *qdict, QObject **ret_data); int do_device_del(Monitor *mon, const QDict *qdict, QObject **ret_data); +void device_user_print(Monitor *mon, const QObject *data); +int do_device_show(Monitor *mon, const QDict *qdict, QObject **ret_data); /*** qdev-properties.c ***/ diff --git a/qemu-monitor.hx b/qemu-monitor.hx index eb257a6..f4c9ffd 100644 --- a/qemu-monitor.hx +++ b/qemu-monitor.hx @@ -734,6 +734,25 @@ Example: EQMP { + .name = "device_show", + .args_type = "path:Q,full:-f", + .params = "device [-f]", + .help = "show device state (specify -f for full buffer dumping)", + .user_print = device_user_print, + .mhandler.cmd_new = do_device_show, + .user_only = true, + }, + +STEXI +@item device_show @var{path} [@code{-f}] + +Show state of device @var{path} in a human-readable form. @var{path} can be +an unique id specified during device creation or a full path in the device +tree (see @code{info qtree}). Buffers are cut after 16 bytes unless a full +dump is requested via @code{-f}. +ETEXI + + { .name = "cpu", .args_type = "index:i", .params = "index", diff --git a/qerror.c b/qerror.c index 034c7de..c50ff91 100644 --- a/qerror.c +++ b/qerror.c @@ -101,6 +101,10 @@ static const QErrorStringTable qerror_table[] = { .desc = "Device '%(device)' has no child bus", }, { + .error_fmt = QERR_DEVICE_NO_STATE, + .desc = "No state available for device '%(device)'", + }, + { .error_fmt = QERR_DUPLICATE_ID, .desc = "Duplicate ID '%(id)' for %(object)", }, diff --git a/qerror.h b/qerror.h index c98c61a..e5de6dd 100644 --- a/qerror.h +++ b/qerror.h @@ -91,6 +91,9 @@ QError *qobject_to_qerror(const QObject *obj); #define QERR_DEVICE_NO_BUS \ "{ 'class': 'DeviceNoBus', 'data': { 'device': %s } }" +#define QERR_DEVICE_NO_STATE \ + "{ 'class': 'DeviceNoState', 'data': { 'device': %s } }" + #define QERR_DUPLICATE_ID \ "{ 'class': 'DuplicateId', 'data': { 'id': %s, 'object': %s } }"