Browse Source

uefi: add firmware log monitor commands

-----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCgAdFiEEoDKM/7k6F6eZAf59TLbY7tPocTgFAmj4uSUACgkQTLbY7tPo
 cTiV7BAA2lRC9GkqIJ6E73Dmrbr9OPccN9YK+G4wccYa0BYlSJOW5FDxzSnLTPCQ
 y9AbsRCLCy2ou8PrVpRQYO/U0Y2zxy07Wzu1YGcVNh7IxxOd3B47mNpzD18YCKnH
 merlQrRdkMID4kVKNfXWvOoCgl7z/XnELQLz2fvMSNGlGD4PTAKKxm+46wxA7gTb
 uKoOX7uclMVYAP4r/dHMOr4T9PAmbhRZCmr4FZpp0EA4+aVWv3GP0KK2VB0mI2Nr
 aZpPo81iFjL+XQJh6A3El9Ez3JOJ+3qDt1+x9vLmTJxQyxUu6MvlCT8T+9v0ZuAi
 V1Gh6qKHCzmfEZLsw1h8sZ2bh7GXsgzGuETPn7sY7cIt7R+qzQxYXs+wLDkC4zcT
 YxRQu7SDS7ImoTvL6LQ9ksIZtC4Xml8dPNG7+r6so0AcKUVPPvaDO6Rd583fk/3X
 ztZ6gymPjVLldMhty+D0A2XxGndIxtFK05ugDHrWQxqLP13aRXrBBbO239h619Ia
 orNzSyHglUtpo6Q8fHb6JHGi3l4uE88kufQGAtltBAtdn3g+OxuinxgOzZTnfm7H
 5uasQM/ykdf8LLAqcTztti78I1Rnv8rusiVU+fq5qYsn5cPvixQqt2fCZBd9MXBk
 CD2fVwHedNBpAUJnflkAMQ188tEWpf+Le1AgI7/n5MIe2rexJEg=
 =h5Tv
 -----END PGP SIGNATURE-----

Merge tag 'uefi-20251022-pull-request' of https://gitlab.com/kraxel/qemu into staging

uefi: add firmware log monitor commands

# -----BEGIN PGP SIGNATURE-----
#
# iQIzBAABCgAdFiEEoDKM/7k6F6eZAf59TLbY7tPocTgFAmj4uSUACgkQTLbY7tPo
# cTiV7BAA2lRC9GkqIJ6E73Dmrbr9OPccN9YK+G4wccYa0BYlSJOW5FDxzSnLTPCQ
# y9AbsRCLCy2ou8PrVpRQYO/U0Y2zxy07Wzu1YGcVNh7IxxOd3B47mNpzD18YCKnH
# merlQrRdkMID4kVKNfXWvOoCgl7z/XnELQLz2fvMSNGlGD4PTAKKxm+46wxA7gTb
# uKoOX7uclMVYAP4r/dHMOr4T9PAmbhRZCmr4FZpp0EA4+aVWv3GP0KK2VB0mI2Nr
# aZpPo81iFjL+XQJh6A3El9Ez3JOJ+3qDt1+x9vLmTJxQyxUu6MvlCT8T+9v0ZuAi
# V1Gh6qKHCzmfEZLsw1h8sZ2bh7GXsgzGuETPn7sY7cIt7R+qzQxYXs+wLDkC4zcT
# YxRQu7SDS7ImoTvL6LQ9ksIZtC4Xml8dPNG7+r6so0AcKUVPPvaDO6Rd583fk/3X
# ztZ6gymPjVLldMhty+D0A2XxGndIxtFK05ugDHrWQxqLP13aRXrBBbO239h619Ia
# orNzSyHglUtpo6Q8fHb6JHGi3l4uE88kufQGAtltBAtdn3g+OxuinxgOzZTnfm7H
# 5uasQM/ykdf8LLAqcTztti78I1Rnv8rusiVU+fq5qYsn5cPvixQqt2fCZBd9MXBk
# CD2fVwHedNBpAUJnflkAMQ188tEWpf+Le1AgI7/n5MIe2rexJEg=
# =h5Tv
# -----END PGP SIGNATURE-----
# gpg: Signature made Wed 22 Oct 2025 05:59:49 AM CDT
# gpg:                using RSA key A0328CFFB93A17A79901FE7D4CB6D8EED3E87138
# gpg: Good signature from "Gerd Hoffmann (work) <kraxel@redhat.com>" [unknown]
# gpg:                 aka "Gerd Hoffmann <gerd@kraxel.org>" [unknown]
# gpg:                 aka "Gerd Hoffmann (private) <kraxel@gmail.com>" [unknown]
# gpg: WARNING: This key is not certified with a trusted signature!
# gpg:          There is no indication that the signature belongs to the owner.
# Primary key fingerprint: A032 8CFF B93A 17A7 9901  FE7D 4CB6 D8EE D3E8 7138

* tag 'uefi-20251022-pull-request' of https://gitlab.com/kraxel/qemu:
  hw/uefi/ovmf-log: add maxsize parameter
  hw/uefi: add 'info firmware-log' hmp monitor command.
  hw/uefi: add query-firmware-log monitor command

Signed-off-by: Richard Henderson <richard.henderson@linaro.org>
pull/307/head
Richard Henderson 10 months ago
parent
commit
ad9520e1df
  1. 13
      hmp-commands-info.hx
  2. 2
      hw/uefi/meson.build
  3. 286
      hw/uefi/ovmf-log.c
  4. 1
      include/monitor/hmp.h
  5. 29
      qapi/machine.json
  6. 2
      tests/qtest/qmp-cmd-test.c

13
hmp-commands-info.hx

@ -995,3 +995,16 @@ SRST
``info cryptodev``
Show the crypto devices.
ERST
{
.name = "firmware-log",
.args_type = "max-size:o?",
.params = "[max-size]",
.help = "show the firmware (ovmf) debug log",
.cmd = hmp_info_firmware_log,
},
SRST
``info firmware-log``
Show the firmware (ovmf) debug log.
ERST

2
hw/uefi/meson.build

@ -1,4 +1,4 @@
system_ss.add(files('hardware-info.c'))
system_ss.add(files('hardware-info.c', 'ovmf-log.c'))
uefi_vars_ss = ss.source_set()
if (config_all_devices.has_key('CONFIG_UEFI_VARS'))

286
hw/uefi/ovmf-log.c

@ -0,0 +1,286 @@
/*
* SPDX-License-Identifier: GPL-2.0-or-later
*
* print ovmf debug log
*
* see OvmfPkg/Library/MemDebugLogLib/ in edk2
*/
#include "qemu/osdep.h"
#include "qemu/units.h"
#include "qemu/target-info-qapi.h"
#include "hw/boards.h"
#include "hw/i386/x86.h"
#include "hw/arm/virt.h"
#include "system/dma.h"
#include "monitor/hmp.h"
#include "monitor/monitor.h"
#include "qapi/error.h"
#include "qapi/type-helpers.h"
#include "qapi/qapi-commands-machine.h"
#include "qobject/qdict.h"
/* ----------------------------------------------------------------------- */
/* copy from edk2 */
#define MEM_DEBUG_LOG_MAGIC1 0x3167646d666d766f /* "ovmfmdg1" */
#define MEM_DEBUG_LOG_MAGIC2 0x3267646d666d766f /* "ovmfmdg2" */
/*
* Mem Debug Log buffer header.
* The Log buffer is circular. Only the most
* recent messages are retained. Older messages
* will be discarded if the buffer overflows.
* The Debug Log starts just after the header.
*/
typedef struct {
/*
* Magic values
* These fields are used by tools to locate the buffer in
* memory. These MUST be the first two fields of the structure.
* Use a 128 bit Magic to vastly reduce the possibility of
* a collision with random data in memory.
*/
uint64_t Magic1;
uint64_t Magic2;
/*
* Header Size
* This MUST be the third field of the structure
*/
uint64_t HeaderSize;
/*
* Debug log size (minus header)
*/
uint64_t DebugLogSize;
/*
* edk2 uses this for locking access.
*/
uint64_t MemDebugLogLock;
/*
* Debug log head offset
*/
uint64_t DebugLogHeadOffset;
/*
* Debug log tail offset
*/
uint64_t DebugLogTailOffset;
/*
* Flag to indicate if the buffer wrapped and was thus truncated.
*/
uint64_t Truncated;
/*
* Firmware Build Version (PcdFirmwareVersionString)
*/
char FirmwareVersion[128];
} MEM_DEBUG_LOG_HDR;
/* ----------------------------------------------------------------------- */
/* qemu monitor command */
typedef struct {
uint64_t magic1;
uint64_t magic2;
} MemDebugLogMagic;
/* find log buffer in guest memory by searching for the magic cookie */
static dma_addr_t find_ovmf_log_range(dma_addr_t start, dma_addr_t end)
{
static const MemDebugLogMagic magic = {
.magic1 = MEM_DEBUG_LOG_MAGIC1,
.magic2 = MEM_DEBUG_LOG_MAGIC2,
};
MemDebugLogMagic check;
dma_addr_t step = 4 * KiB;
dma_addr_t offset;
for (offset = start; offset < end; offset += step) {
if (dma_memory_read(&address_space_memory, offset,
&check, sizeof(check),
MEMTXATTRS_UNSPECIFIED)) {
/* dma error -> stop searching */
break;
}
if (memcmp(&magic, &check, sizeof(check)) == 0) {
return offset;
}
}
return (dma_addr_t)-1;
}
static dma_addr_t find_ovmf_log(void)
{
MachineState *ms = MACHINE(qdev_get_machine());
dma_addr_t start, end, offset;
if (target_arch() == SYS_EMU_TARGET_X86_64 &&
object_dynamic_cast(OBJECT(ms), TYPE_X86_MACHINE)) {
X86MachineState *x86ms = X86_MACHINE(ms);
/* early log buffer, static allocation in memfd, sec + early pei */
offset = find_ovmf_log_range(0x800000, 0x900000);
if (offset != -1) {
return offset;
}
/*
* normal log buffer, dynamically allocated close to end of low memory,
* late pei + dxe phase
*/
end = x86ms->below_4g_mem_size;
start = end - MIN(end, 128 * MiB);
return find_ovmf_log_range(start, end);
}
if (target_arch() == SYS_EMU_TARGET_AARCH64 &&
object_dynamic_cast(OBJECT(ms), TYPE_VIRT_MACHINE)) {
VirtMachineState *vms = VIRT_MACHINE(ms);
/* edk2 ArmVirt firmware allocations are in the first 128 MB */
start = vms->memmap[VIRT_MEM].base;
end = start + 128 * MiB;
return find_ovmf_log_range(start, end);
}
return (dma_addr_t)-1;
}
static void handle_ovmf_log_range(GString *out,
dma_addr_t start,
dma_addr_t end,
Error **errp)
{
if (start > end) {
return;
}
size_t len = end - start;
g_string_set_size(out, out->len + len);
if (dma_memory_read(&address_space_memory, start,
out->str + (out->len - len),
len, MEMTXATTRS_UNSPECIFIED)) {
error_setg(errp, "can not read firmware log buffer contents");
return;
}
}
FirmwareLog *qmp_query_firmware_log(bool have_max_size, uint64_t max_size,
Error **errp)
{
MEM_DEBUG_LOG_HDR header;
dma_addr_t offset, base;
FirmwareLog *ret;
g_autoptr(GString) log = g_string_new("");
offset = find_ovmf_log();
if (offset == -1) {
error_setg(errp, "firmware log buffer not found");
return NULL;
}
if (dma_memory_read(&address_space_memory, offset,
&header, sizeof(header),
MEMTXATTRS_UNSPECIFIED)) {
error_setg(errp, "can not read firmware log buffer header");
return NULL;
}
if (header.DebugLogHeadOffset > header.DebugLogSize ||
header.DebugLogTailOffset > header.DebugLogSize) {
error_setg(errp, "firmware log buffer header is invalid");
return NULL;
}
if (have_max_size) {
if (max_size > MiB) {
error_setg(errp, "parameter 'max-size' exceeds 1MiB");
return NULL;
}
} else {
max_size = MiB;
}
/* adjust header.DebugLogHeadOffset so we return at most maxsize bytes */
if (header.DebugLogHeadOffset > header.DebugLogTailOffset) {
/* wrap around */
if (header.DebugLogTailOffset > max_size) {
header.DebugLogHeadOffset = header.DebugLogTailOffset - max_size;
} else {
uint64_t max_chunk = max_size - header.DebugLogTailOffset;
if (header.DebugLogSize > max_chunk &&
header.DebugLogHeadOffset < header.DebugLogSize - max_chunk) {
header.DebugLogHeadOffset = header.DebugLogSize - max_chunk;
}
}
} else {
if (header.DebugLogTailOffset > max_size &&
header.DebugLogHeadOffset < header.DebugLogTailOffset - max_size) {
header.DebugLogHeadOffset = header.DebugLogTailOffset - max_size;
}
}
base = offset + header.HeaderSize;
if (header.DebugLogHeadOffset > header.DebugLogTailOffset) {
/* wrap around */
handle_ovmf_log_range(log,
base + header.DebugLogHeadOffset,
base + header.DebugLogSize,
errp);
if (*errp) {
return NULL;
}
handle_ovmf_log_range(log,
base + 0,
base + header.DebugLogTailOffset,
errp);
if (*errp) {
return NULL;
}
} else {
handle_ovmf_log_range(log,
base + header.DebugLogHeadOffset,
base + header.DebugLogTailOffset,
errp);
if (*errp) {
return NULL;
}
}
ret = g_new0(FirmwareLog, 1);
if (header.FirmwareVersion[0] != '\0') {
ret->version = g_strndup(header.FirmwareVersion,
sizeof(header.FirmwareVersion));
}
ret->log = g_base64_encode((const guchar *)log->str, log->len);
return ret;
}
void hmp_info_firmware_log(Monitor *mon, const QDict *qdict)
{
g_autofree gchar *log_esc = NULL;
g_autofree guchar *log_out = NULL;
Error *err = NULL;
FirmwareLog *log;
gsize log_len;
int64_t maxsize;
maxsize = qdict_get_try_int(qdict, "max-size", -1);
log = qmp_query_firmware_log(maxsize != -1, (uint64_t)maxsize, &err);
if (err) {
hmp_handle_error(mon, err);
return;
}
g_assert(log != NULL);
g_assert(log->log != NULL);
if (log->version) {
g_autofree gchar *esc = g_strescape(log->version, NULL);
monitor_printf(mon, "[ firmware version: %s ]\n", esc);
}
log_out = g_base64_decode(log->log, &log_len);
log_esc = g_strescape((gchar *)log_out, "\r\n");
monitor_printf(mon, "%s\n", log_esc);
}

1
include/monitor/hmp.h

@ -179,5 +179,6 @@ void hmp_boot_set(Monitor *mon, const QDict *qdict);
void hmp_info_mtree(Monitor *mon, const QDict *qdict);
void hmp_info_cryptodev(Monitor *mon, const QDict *qdict);
void hmp_dumpdtb(Monitor *mon, const QDict *qdict);
void hmp_info_firmware_log(Monitor *mon, const QDict *qdict);
#endif

29
qapi/machine.json

@ -1839,6 +1839,35 @@
'returns': 'HumanReadableText',
'features': [ 'unstable' ]}
##
# @FirmwareLog:
#
# @version: Firmware version.
#
# @log: Firmware debug log, in base64 encoding. First and last log
# line might be incomplete.
#
# Since: 10.2
##
{ 'struct': 'FirmwareLog',
'data': { '*version': 'str',
'log': 'str' } }
##
# @query-firmware-log:
#
# Find firmware memory log buffer in guest memory, return content.
#
# @max-size: limit the amount of log data returned. Up to 1 MiB of
# log data is allowed. In case the amount of log data is
# larger than @max-size the tail of the log is returned.
#
# Since: 10.2
##
{ 'command': 'query-firmware-log',
'data': { '*max-size': 'size' },
'returns': 'FirmwareLog' }
##
# @dump-skeys:
#

2
tests/qtest/qmp-cmd-test.c

@ -52,6 +52,8 @@ static int query_error_class(const char *cmd)
/* Only valid with accel=tcg */
{ "x-query-jit", ERROR_CLASS_GENERIC_ERROR },
{ "xen-event-list", ERROR_CLASS_GENERIC_ERROR },
/* requires firmware with memory buffer logging support */
{ "query-firmware-log", ERROR_CLASS_GENERIC_ERROR },
{ NULL, -1 }
};
int i;

Loading…
Cancel
Save