diff --git a/hmp-commands.hx b/hmp-commands.hx
index f3fc514..65b5e33 100644
--- a/hmp-commands.hx
+++ b/hmp-commands.hx
@@ -1754,6 +1754,8 @@ show qdev device model list
 show roms
 @item info tpm
 show the TPM device
+@item info qom-tree
+show the QOM tree
 @end table
 ETEXI
 
diff --git a/hmp.c b/hmp.c
index 2f279c4..891a34e 100644
--- a/hmp.c
+++ b/hmp.c
@@ -22,9 +22,11 @@
 #include "qemu/sockets.h"
 #include "monitor/monitor.h"
 #include "qapi/opts-visitor.h"
+#include "qapi/qmp/types.h"
 #include "ui/console.h"
 #include "block/qapi.h"
 #include "qemu-io.h"
+#include "qom/qom-qobject.h"
 
 static void hmp_handle_error(Monitor *mon, Error **errp)
 {
@@ -712,6 +714,147 @@ void hmp_info_tpm(Monitor *mon, const QDict *qdict)
     qapi_free_TPMInfoList(info_list);
 }
 
+static char *qobject_to_str(const QObject *, int);
+
+static char *qlist_to_str(const QObject *obj, int indent)
+{
+    char *buf = NULL;
+    const QListEntry *entry;
+    int i = 0;
+
+    assert(qobject_type(obj) == QTYPE_QLIST);
+
+    entry = qlist_first(qobject_to_qlist(obj));
+    while (entry) {
+        char *new, *str;
+        int type = qobject_type(entry->value);
+        str = qobject_to_str(entry->value, indent);
+        if (type == QTYPE_QLIST || type == QTYPE_QDICT) {
+            new = g_strdup_printf("%s%*sElement #%d:\n%s", buf ?: "",
+                                  indent, "", i++, str);
+        } else {
+            new = g_strdup_printf("%s%*sElement #%d: %s\n", buf ?: "",
+                                  indent, "", i++, str);
+        }
+        g_free(buf);
+        g_free(str);
+        buf = new;
+        entry = qlist_next(entry);
+    }
+    return buf;
+}
+
+static char *qdict_to_str(const QObject *obj, int indent)
+{
+    QDict *dict = qobject_to_qdict(obj);
+    const QDictEntry *entry;
+    char *buf = NULL;
+
+    assert(qobject_type(obj) == QTYPE_QDICT);
+
+    for (entry = qdict_first(dict); entry; entry = qdict_next(dict, entry)) {
+        char *str, *new;
+        int type = qobject_type(entry->value);
+        str = qobject_to_str(entry->value, indent);
+        if (type == QTYPE_QLIST || type == QTYPE_QDICT) {
+            new = g_strdup_printf("%s%*s%s:\n%s", buf ?: "", indent, "",
+                                  entry->key, str);
+        } else {
+            new = g_strdup_printf("%s%*s%s: %s\n", buf ?: "", indent, "",
+                                  entry->key, str);
+        }
+        g_free(buf);
+        g_free(str);
+        buf = new;
+    }
+    return buf;
+}
+
+static char *qobject_to_str(const QObject *obj, int indent)
+{
+    switch (qobject_type(obj)) {
+    case QTYPE_QSTRING: {
+        QString *value = qobject_to_qstring(obj);
+        return g_strdup(qstring_get_str(value));
+    }
+    case QTYPE_QINT: {
+        QInt *value = qobject_to_qint(obj);
+        return g_strdup_printf("%" PRId64, qint_get_int(value));
+    }
+    case QTYPE_QBOOL: {
+        QBool *value = qobject_to_qbool(obj);
+        return g_strdup(qbool_get_int(value) ? "True" : "False");
+    }
+    case QTYPE_QERROR: {
+        QString *value = qerror_human((QError *)obj);
+        return g_strdup(qstring_get_str(value));
+    }
+    case QTYPE_QFLOAT: {
+        QFloat *value = qobject_to_qfloat(obj);
+        return g_strdup_printf("%g", qfloat_get_double(value));
+    }
+    case QTYPE_QLIST:
+        return qlist_to_str(obj, indent + 2);
+    case QTYPE_QDICT:
+        return qdict_to_str(obj, indent + 2);
+    case QTYPE_NONE:
+        break;
+    case QTYPE_MAX:
+    default:
+        abort();
+    }
+    return NULL;
+}
+
+static void hmp_print_qom_tree(Monitor *mon, const char *path, int indent)
+{
+    ObjectPropertyInfoList *list, *start;
+
+    monitor_printf(mon, "%*s%s:\n", indent, "", path);
+    start = list = qmp_qom_list(path, NULL);
+    indent += 2;
+    while (list) {
+        ObjectPropertyInfo *info = list->value;
+
+        if (!strncmp(info->type, "child<", 5)) {
+            char *name = g_strdup_printf("%s/%s", path, info->name);
+            hmp_print_qom_tree(mon, name, indent);
+            g_free(name);
+        } else {
+            Object *obj = NULL;
+            QObject *data = NULL;
+            char *str;
+
+            obj = object_resolve_path(path, NULL);
+            if (!obj) {
+                list = list->next;
+                continue;
+            }
+            data = object_property_get_qobject(obj, info->name, NULL);
+            if (!data) {
+                list = list->next;
+                continue;
+            }
+            str = qobject_to_str(data, indent);
+            if (qobject_type(data) == QTYPE_QDICT
+                || qobject_type(data) == QTYPE_QLIST) {
+                monitor_printf(mon, "%*s%s:\n%s", indent, "", info->name, str);
+            } else {
+                monitor_printf(mon, "%*s%s: %s\n", indent, "", info->name, str);
+            }
+            g_free(str);
+            qobject_decref(data);
+        }
+        list = list->next;
+    }
+    qapi_free_ObjectPropertyInfoList(start);
+}
+
+void hmp_info_qom_tree(Monitor *mon, const QDict *qdict)
+{
+    hmp_print_qom_tree(mon, "/machine", 0);
+}
+
 void hmp_quit(Monitor *mon, const QDict *qdict)
 {
     monitor_suspend(mon);
diff --git a/hmp.h b/hmp.h
index ed58f0e..1733242 100644
--- a/hmp.h
+++ b/hmp.h
@@ -37,6 +37,7 @@ void hmp_info_balloon(Monitor *mon, const QDict *qdict);
 void hmp_info_pci(Monitor *mon, const QDict *qdict);
 void hmp_info_block_jobs(Monitor *mon, const QDict *qdict);
 void hmp_info_tpm(Monitor *mon, const QDict *qdict);
+void hmp_info_qom_tree(Monitor *mon, const QDict *qdict);
 void hmp_quit(Monitor *mon, const QDict *qdict);
 void hmp_stop(Monitor *mon, const QDict *qdict);
 void hmp_system_reset(Monitor *mon, const QDict *qdict);
diff --git a/monitor.c b/monitor.c
index 342e83b..fcb1d0c 100644
--- a/monitor.c
+++ b/monitor.c
@@ -2966,6 +2966,13 @@ static mon_cmd_t info_cmds[] = {
         .mhandler.cmd = hmp_info_tpm,
     },
     {
+        .name       = "qom-tree",
+        .args_type  = "",
+        .params     = "",
+        .help       = "show the QOM tree",
+        .mhandler.cmd = hmp_info_qom_tree,
+    },
+    {
         .name       = NULL,
     },
 };
