Files
qemu/hw/core/cpu-common.c
Peter Maydell 300a87c502 physmem: Destroy all CPU AddressSpaces on unrealize
When we unrealize a CPU object (which happens on vCPU hot-unplug), we
should destroy all the AddressSpace objects we created via calls to
cpu_address_space_init() when the CPU was realized.

Commit 24bec42f3d added a function to do this for a specific
AddressSpace, but did not add any places where the function was
called.

Since we always want to destroy all the AddressSpaces on unrealize,
regardless of the target architecture, we don't need to try to keep
track of how many are still undestroyed, or make the target
architecture code manually call a destroy function for each AS it
created.  Instead we can adjust the function to always completely
destroy the whole cpu->ases array, and arrange for it to be called
during CPU unrealize as part of the common code.

Without this fix, AddressSanitizer will report a leak like this
from a run where we hot-plugged and then hot-unplugged an x86 KVM
vCPU:

Direct leak of 416 byte(s) in 1 object(s) allocated from:
    #0 0x5b638565053d in calloc (/data_nvme1n1/linaro/qemu-from-laptop/qemu/build/x86-tgts-asan/qemu-system-x86_64+0x1ee153d) (BuildId: c1cd6022b195142106e1bffeca23498c2b752bca)
    #1 0x7c28083f77b1 in g_malloc0 (/lib/x86_64-linux-gnu/libglib-2.0.so.0+0x637b1) (BuildId: 1eb6131419edb83b2178b682829a6913cf682d75)
    #2 0x5b6386999c7c in cpu_address_space_init /data_nvme1n1/linaro/qemu-from-laptop/qemu/build/x86-tgts-asan/../../system/physmem.c:797:25
    #3 0x5b638727f049 in kvm_cpu_realizefn /data_nvme1n1/linaro/qemu-from-laptop/qemu/build/x86-tgts-asan/../../target/i386/kvm/kvm-cpu.c:102:5
    #4 0x5b6385745f40 in accel_cpu_common_realize /data_nvme1n1/linaro/qemu-from-laptop/qemu/build/x86-tgts-asan/../../accel/accel-common.c:101:13
    #5 0x5b638568fe3c in cpu_exec_realizefn /data_nvme1n1/linaro/qemu-from-laptop/qemu/build/x86-tgts-asan/../../hw/core/cpu-common.c:232:10
    #6 0x5b63874a2cd5 in x86_cpu_realizefn /data_nvme1n1/linaro/qemu-from-laptop/qemu/build/x86-tgts-asan/../../target/i386/cpu.c:9321:5
    #7 0x5b6387a0469a in device_set_realized /data_nvme1n1/linaro/qemu-from-laptop/qemu/build/x86-tgts-asan/../../hw/core/qdev.c:494:13
    #8 0x5b6387a27d9e in property_set_bool /data_nvme1n1/linaro/qemu-from-laptop/qemu/build/x86-tgts-asan/../../qom/object.c:2375:5
    #9 0x5b6387a2090b in object_property_set /data_nvme1n1/linaro/qemu-from-laptop/qemu/build/x86-tgts-asan/../../qom/object.c:1450:5
    #10 0x5b6387a35b05 in object_property_set_qobject /data_nvme1n1/linaro/qemu-from-laptop/qemu/build/x86-tgts-asan/../../qom/qom-qobject.c:28:10
    #11 0x5b6387a21739 in object_property_set_bool /data_nvme1n1/linaro/qemu-from-laptop/qemu/build/x86-tgts-asan/../../qom/object.c:1520:15
    #12 0x5b63879fe510 in qdev_realize /data_nvme1n1/linaro/qemu-from-laptop/qemu/build/x86-tgts-asan/../../hw/core/qdev.c:276:12

Cc: qemu-stable@nongnu.org
Resolves: https://gitlab.com/qemu-project/qemu/-/issues/2517
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
Reviewed-by: David Hildenbrand <david@redhat.com>
Link: https://lore.kernel.org/r/20250929144228.1994037-4-peter.maydell@linaro.org
Signed-off-by: Peter Xu <peterx@redhat.com>
2025-10-03 09:48:02 -04:00

438 lines
11 KiB
C

/*
* QEMU CPU model
*
* Copyright (c) 2012-2014 SUSE LINUX Products GmbH
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see
* <http://www.gnu.org/licenses/gpl-2.0.html>
*/
#include "qemu/osdep.h"
#include "qapi/error.h"
#include "hw/core/cpu.h"
#include "system/hw_accel.h"
#include "qemu/log.h"
#include "qemu/main-loop.h"
#include "qemu/lockcnt.h"
#include "qemu/error-report.h"
#include "qemu/qemu-print.h"
#include "qemu/target-info.h"
#include "exec/log.h"
#include "exec/gdbstub.h"
#include "system/tcg.h"
#include "hw/boards.h"
#include "hw/qdev-properties.h"
#include "trace.h"
#ifdef CONFIG_PLUGIN
#include "qemu/plugin.h"
#endif
CPUState *cpu_by_arch_id(int64_t id)
{
CPUState *cpu;
CPU_FOREACH(cpu) {
if (cpu->cc->get_arch_id(cpu) == id) {
return cpu;
}
}
return NULL;
}
bool cpu_exists(int64_t id)
{
return !!cpu_by_arch_id(id);
}
CPUState *cpu_create(const char *typename)
{
Error *err = NULL;
CPUState *cpu = CPU(object_new(typename));
if (!qdev_realize(DEVICE(cpu), NULL, &err)) {
error_report_err(err);
object_unref(OBJECT(cpu));
exit(EXIT_FAILURE);
}
return cpu;
}
void cpu_reset_interrupt(CPUState *cpu, int mask)
{
qatomic_and(&cpu->interrupt_request, ~mask);
}
void cpu_exit(CPUState *cpu)
{
/* Ensure cpu_exec will see the reason why the exit request was set. */
qatomic_store_release(&cpu->exit_request, true);
qemu_cpu_kick(cpu);
}
static int cpu_common_gdb_read_register(CPUState *cpu, GByteArray *buf, int reg)
{
return 0;
}
static int cpu_common_gdb_write_register(CPUState *cpu, uint8_t *buf, int reg)
{
return 0;
}
void cpu_dump_state(CPUState *cpu, FILE *f, int flags)
{
if (cpu->cc->dump_state) {
cpu_synchronize_state(cpu);
cpu->cc->dump_state(cpu, f, flags);
}
}
void cpu_reset(CPUState *cpu)
{
device_cold_reset(DEVICE(cpu));
trace_cpu_reset(cpu->cpu_index);
}
static void cpu_common_reset_hold(Object *obj, ResetType type)
{
CPUState *cpu = CPU(obj);
cpu->interrupt_request = 0;
cpu->halted = cpu->start_powered_off;
cpu->mem_io_pc = 0;
cpu->icount_extra = 0;
qatomic_set(&cpu->neg.icount_decr.u32, 0);
cpu->neg.can_do_io = true;
cpu->exception_index = -1;
cpu->crash_occurred = false;
cpu->cflags_next_tb = -1;
cpu_exec_reset_hold(cpu);
}
static void cpu_common_reset_exit(Object *obj, ResetType type)
{
if (qemu_loglevel_mask(CPU_LOG_RESET)) {
FILE *f = qemu_log_trylock();
if (f) {
CPUState *cpu = CPU(obj);
fprintf(f, "CPU Reset (CPU %d)\n", cpu->cpu_index);
cpu_dump_state(cpu, f, cpu->cc->reset_dump_flags);
qemu_log_unlock(f);
}
}
}
ObjectClass *cpu_class_by_name(const char *typename, const char *cpu_model)
{
ObjectClass *oc;
CPUClass *cc;
oc = object_class_by_name(typename);
cc = CPU_CLASS(oc);
assert(cc->class_by_name);
assert(cpu_model);
oc = cc->class_by_name(cpu_model);
if (object_class_dynamic_cast(oc, typename) &&
!object_class_is_abstract(oc)) {
return oc;
}
return NULL;
}
char *cpu_model_from_type(const char *typename)
{
g_autofree char *suffix = g_strdup_printf("-%s", target_cpu_type());
if (!object_class_by_name(typename)) {
return NULL;
}
if (g_str_has_suffix(typename, suffix)) {
return g_strndup(typename, strlen(typename) - strlen(suffix));
}
return g_strdup(typename);
}
static void cpu_common_parse_features(const char *typename, char *features,
Error **errp)
{
char *val;
static bool cpu_globals_initialized;
/* Single "key=value" string being parsed */
char *featurestr = features ? strtok(features, ",") : NULL;
/* should be called only once, catch invalid users */
assert(!cpu_globals_initialized);
cpu_globals_initialized = true;
while (featurestr) {
val = strchr(featurestr, '=');
if (val) {
GlobalProperty *prop = g_new0(typeof(*prop), 1);
*val = 0;
val++;
prop->driver = typename;
prop->property = g_strdup(featurestr);
prop->value = g_strdup(val);
qdev_prop_register_global(prop);
} else {
error_setg(errp, "Expected key=value format, found %s.",
featurestr);
return;
}
featurestr = strtok(NULL, ",");
}
}
const char *parse_cpu_option(const char *cpu_option)
{
ObjectClass *oc;
CPUClass *cc;
gchar **model_pieces;
const char *cpu_type;
model_pieces = g_strsplit(cpu_option, ",", 2);
if (!model_pieces[0]) {
error_report("-cpu option cannot be empty");
exit(1);
}
oc = cpu_class_by_name(target_cpu_type(), model_pieces[0]);
if (oc == NULL) {
error_report("unable to find CPU model '%s'", model_pieces[0]);
g_strfreev(model_pieces);
exit(EXIT_FAILURE);
}
cpu_type = object_class_get_name(oc);
cc = CPU_CLASS(oc);
cc->parse_features(cpu_type, model_pieces[1], &error_fatal);
g_strfreev(model_pieces);
return cpu_type;
}
bool cpu_exec_realizefn(CPUState *cpu, Error **errp)
{
if (!accel_cpu_common_realize(cpu, errp)) {
return false;
}
gdb_init_cpu(cpu);
/* Wait until cpu initialization complete before exposing cpu. */
cpu_list_add(cpu);
cpu_vmstate_register(cpu);
return true;
}
static void cpu_common_realizefn(DeviceState *dev, Error **errp)
{
CPUState *cpu = CPU(dev);
Object *machine = qdev_get_machine();
/* qdev_get_machine() can return something that's not TYPE_MACHINE
* if this is one of the user-only emulators; in that case there's
* no need to check the ignore_memory_transaction_failures board flag.
*/
if (object_dynamic_cast(machine, TYPE_MACHINE)) {
MachineClass *mc = MACHINE_GET_CLASS(machine);
if (mc) {
cpu->ignore_memory_transaction_failures =
mc->ignore_memory_transaction_failures;
}
}
if (dev->hotplugged) {
cpu_synchronize_post_init(cpu);
cpu_resume(cpu);
}
/* NOTE: latest generic point where the cpu is fully realized */
}
static void cpu_common_unrealizefn(DeviceState *dev)
{
CPUState *cpu = CPU(dev);
/* Call the plugin hook before clearing the cpu is fully unrealized */
#ifdef CONFIG_PLUGIN
if (tcg_enabled()) {
qemu_plugin_vcpu_exit_hook(cpu);
}
#endif
/* NOTE: latest generic point before the cpu is fully unrealized */
cpu_exec_unrealizefn(cpu);
}
void cpu_exec_unrealizefn(CPUState *cpu)
{
cpu_vmstate_unregister(cpu);
cpu_list_remove(cpu);
/*
* Now that the vCPU has been removed from the RCU list, we can call
* accel_cpu_common_unrealize, which may free fields using call_rcu.
*/
accel_cpu_common_unrealize(cpu);
cpu_destroy_address_spaces(cpu);
}
static void cpu_common_initfn(Object *obj)
{
CPUState *cpu = CPU(obj);
cpu_exec_class_post_init(CPU_GET_CLASS(obj));
/* cache the cpu class for the hotpath */
cpu->cc = CPU_GET_CLASS(cpu);
cpu->cpu_index = UNASSIGNED_CPU_INDEX;
cpu->cluster_index = UNASSIGNED_CLUSTER_INDEX;
cpu->as = NULL;
cpu->num_ases = 0;
/* user-mode doesn't have configurable SMP topology */
/* the default value is changed by qemu_init_vcpu() for system-mode */
cpu->nr_threads = 1;
/* allocate storage for thread info, initialise condition variables */
cpu->thread = g_new0(QemuThread, 1);
cpu->halt_cond = g_new0(QemuCond, 1);
qemu_cond_init(cpu->halt_cond);
qemu_mutex_init(&cpu->work_mutex);
qemu_lockcnt_init(&cpu->in_ioctl_lock);
QSIMPLEQ_INIT(&cpu->work_list);
QTAILQ_INIT(&cpu->breakpoints);
QTAILQ_INIT(&cpu->watchpoints);
cpu_exec_initfn(cpu);
/*
* Plugin initialization must wait until the cpu start executing
* code, but we must queue this work before the threads are
* created to ensure we don't race.
*/
#ifdef CONFIG_PLUGIN
if (tcg_enabled()) {
cpu->plugin_state = qemu_plugin_create_vcpu_state();
qemu_plugin_vcpu_init_hook(cpu);
}
#endif
}
static void cpu_common_finalize(Object *obj)
{
CPUState *cpu = CPU(obj);
#ifdef CONFIG_PLUGIN
if (tcg_enabled()) {
g_free(cpu->plugin_state);
}
#endif
free_queued_cpu_work(cpu);
/* If cleanup didn't happen in context to gdb_unregister_coprocessor_all */
if (cpu->gdb_regs) {
g_array_free(cpu->gdb_regs, TRUE);
}
qemu_lockcnt_destroy(&cpu->in_ioctl_lock);
qemu_mutex_destroy(&cpu->work_mutex);
qemu_cond_destroy(cpu->halt_cond);
g_free(cpu->halt_cond);
g_free(cpu->thread);
}
static int64_t cpu_common_get_arch_id(CPUState *cpu)
{
return cpu->cpu_index;
}
static void cpu_common_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
ResettableClass *rc = RESETTABLE_CLASS(klass);
CPUClass *k = CPU_CLASS(klass);
k->parse_features = cpu_common_parse_features;
k->get_arch_id = cpu_common_get_arch_id;
k->gdb_read_register = cpu_common_gdb_read_register;
k->gdb_write_register = cpu_common_gdb_write_register;
set_bit(DEVICE_CATEGORY_CPU, dc->categories);
dc->realize = cpu_common_realizefn;
dc->unrealize = cpu_common_unrealizefn;
rc->phases.hold = cpu_common_reset_hold;
rc->phases.exit = cpu_common_reset_exit;
cpu_class_init_props(dc);
/*
* Reason: CPUs still need special care by board code: wiring up
* IRQs, adding reset handlers, halting non-first CPUs, ...
*/
dc->user_creatable = false;
}
static const TypeInfo cpu_type_info = {
.name = TYPE_CPU,
.parent = TYPE_DEVICE,
.instance_size = sizeof(CPUState),
.instance_init = cpu_common_initfn,
.instance_finalize = cpu_common_finalize,
.abstract = true,
.class_size = sizeof(CPUClass),
.class_init = cpu_common_class_init,
};
static void cpu_register_types(void)
{
type_register_static(&cpu_type_info);
}
type_init(cpu_register_types)
static void cpu_list_entry(gpointer data, gpointer user_data)
{
CPUClass *cc = CPU_CLASS(OBJECT_CLASS(data));
const char *typename = object_class_get_name(OBJECT_CLASS(data));
g_autofree char *model = cpu_model_from_type(typename);
if (cc->deprecation_note) {
qemu_printf(" %s (deprecated)\n", model);
} else {
qemu_printf(" %s\n", model);
}
}
void list_cpus(void)
{
CPUClass *cc = CPU_CLASS(object_class_by_name(target_cpu_type()));
if (cc->list_cpus) {
cc->list_cpus();
} else {
GSList *list;
list = object_class_get_list_sorted(TYPE_CPU, false);
qemu_printf("Available CPUs:\n");
g_slist_foreach(list, cpu_list_entry, NULL);
g_slist_free(list);
}
}