gdbstub: move breakpoint logic to accel ops

As HW virtualization requires specific support to handle breakpoints
lets push out special casing out of the core gdbstub code and into
AccelOpsClass. This will make it easier to add other accelerator
support and reduces some of the stub shenanigans.

Reviewed-by: Richard Henderson <richard.henderson@linaro.org>
Signed-off-by: Alex Bennée <alex.bennee@linaro.org>
Reviewed-by: Mads Ynddal <mads@ynddal.dk>
Message-Id: <20220929114231.583801-45-alex.bennee@linaro.org>
This commit is contained in:
Alex Bennée 2022-09-29 12:42:24 +01:00
parent 3b7a93880a
commit ae7467b1ac
14 changed files with 259 additions and 160 deletions

View File

@ -16,12 +16,14 @@
#include "qemu/osdep.h" #include "qemu/osdep.h"
#include "qemu/error-report.h" #include "qemu/error-report.h"
#include "qemu/main-loop.h" #include "qemu/main-loop.h"
#include "sysemu/kvm.h"
#include "sysemu/kvm_int.h" #include "sysemu/kvm_int.h"
#include "sysemu/runstate.h" #include "sysemu/runstate.h"
#include "sysemu/cpus.h" #include "sysemu/cpus.h"
#include "qemu/guest-random.h" #include "qemu/guest-random.h"
#include "qapi/error.h" #include "qapi/error.h"
#include <linux/kvm.h>
#include "kvm-cpus.h" #include "kvm-cpus.h"
static void *kvm_vcpu_thread_fn(void *arg) static void *kvm_vcpu_thread_fn(void *arg)
@ -95,6 +97,12 @@ static void kvm_accel_ops_class_init(ObjectClass *oc, void *data)
ops->synchronize_post_init = kvm_cpu_synchronize_post_init; ops->synchronize_post_init = kvm_cpu_synchronize_post_init;
ops->synchronize_state = kvm_cpu_synchronize_state; ops->synchronize_state = kvm_cpu_synchronize_state;
ops->synchronize_pre_loadvm = kvm_cpu_synchronize_pre_loadvm; ops->synchronize_pre_loadvm = kvm_cpu_synchronize_pre_loadvm;
#ifdef KVM_CAP_SET_GUEST_DEBUG
ops->insert_breakpoint = kvm_insert_breakpoint;
ops->remove_breakpoint = kvm_remove_breakpoint;
ops->remove_all_breakpoints = kvm_remove_all_breakpoints;
#endif
} }
static const TypeInfo kvm_accel_ops_type = { static const TypeInfo kvm_accel_ops_type = {

View File

@ -3287,8 +3287,7 @@ int kvm_update_guest_debug(CPUState *cpu, unsigned long reinject_trap)
return data.err; return data.err;
} }
int kvm_insert_breakpoint(CPUState *cpu, target_ulong addr, int kvm_insert_breakpoint(CPUState *cpu, int type, hwaddr addr, hwaddr len)
target_ulong len, int type)
{ {
struct kvm_sw_breakpoint *bp; struct kvm_sw_breakpoint *bp;
int err; int err;
@ -3326,8 +3325,7 @@ int kvm_insert_breakpoint(CPUState *cpu, target_ulong addr,
return 0; return 0;
} }
int kvm_remove_breakpoint(CPUState *cpu, target_ulong addr, int kvm_remove_breakpoint(CPUState *cpu, int type, hwaddr addr, hwaddr len)
target_ulong len, int type)
{ {
struct kvm_sw_breakpoint *bp; struct kvm_sw_breakpoint *bp;
int err; int err;
@ -3393,26 +3391,10 @@ void kvm_remove_all_breakpoints(CPUState *cpu)
#else /* !KVM_CAP_SET_GUEST_DEBUG */ #else /* !KVM_CAP_SET_GUEST_DEBUG */
int kvm_update_guest_debug(CPUState *cpu, unsigned long reinject_trap) static int kvm_update_guest_debug(CPUState *cpu, unsigned long reinject_trap)
{ {
return -EINVAL; return -EINVAL;
} }
int kvm_insert_breakpoint(CPUState *cpu, target_ulong addr,
target_ulong len, int type)
{
return -EINVAL;
}
int kvm_remove_breakpoint(CPUState *cpu, target_ulong addr,
target_ulong len, int type)
{
return -EINVAL;
}
void kvm_remove_all_breakpoints(CPUState *cpu)
{
}
#endif /* !KVM_CAP_SET_GUEST_DEBUG */ #endif /* !KVM_CAP_SET_GUEST_DEBUG */
static int kvm_set_signal_mask(CPUState *cpu, const sigset_t *sigset) static int kvm_set_signal_mask(CPUState *cpu, const sigset_t *sigset)

View File

@ -18,5 +18,8 @@ void kvm_destroy_vcpu(CPUState *cpu);
void kvm_cpu_synchronize_post_reset(CPUState *cpu); void kvm_cpu_synchronize_post_reset(CPUState *cpu);
void kvm_cpu_synchronize_post_init(CPUState *cpu); void kvm_cpu_synchronize_post_init(CPUState *cpu);
void kvm_cpu_synchronize_pre_loadvm(CPUState *cpu); void kvm_cpu_synchronize_pre_loadvm(CPUState *cpu);
int kvm_insert_breakpoint(CPUState *cpu, int type, hwaddr addr, hwaddr len);
int kvm_remove_breakpoint(CPUState *cpu, int type, hwaddr addr, hwaddr len);
void kvm_remove_all_breakpoints(CPUState *cpu);
#endif /* KVM_CPUS_H */ #endif /* KVM_CPUS_H */

View File

@ -51,22 +51,6 @@ int kvm_update_guest_debug(CPUState *cpu, unsigned long reinject_trap)
return -ENOSYS; return -ENOSYS;
} }
int kvm_insert_breakpoint(CPUState *cpu, target_ulong addr,
target_ulong len, int type)
{
return -EINVAL;
}
int kvm_remove_breakpoint(CPUState *cpu, target_ulong addr,
target_ulong len, int type)
{
return -EINVAL;
}
void kvm_remove_all_breakpoints(CPUState *cpu)
{
}
int kvm_on_sigbus_vcpu(CPUState *cpu, int code, void *addr) int kvm_on_sigbus_vcpu(CPUState *cpu, int code, void *addr)
{ {
return 1; return 1;

View File

@ -32,6 +32,8 @@
#include "qemu/main-loop.h" #include "qemu/main-loop.h"
#include "qemu/guest-random.h" #include "qemu/guest-random.h"
#include "exec/exec-all.h" #include "exec/exec-all.h"
#include "exec/hwaddr.h"
#include "exec/gdbstub.h"
#include "tcg-accel-ops.h" #include "tcg-accel-ops.h"
#include "tcg-accel-ops-mttcg.h" #include "tcg-accel-ops-mttcg.h"
@ -91,6 +93,92 @@ void tcg_handle_interrupt(CPUState *cpu, int mask)
} }
} }
/* Translate GDB watchpoint type to a flags value for cpu_watchpoint_* */
static inline int xlat_gdb_type(CPUState *cpu, int gdbtype)
{
static const int xlat[] = {
[GDB_WATCHPOINT_WRITE] = BP_GDB | BP_MEM_WRITE,
[GDB_WATCHPOINT_READ] = BP_GDB | BP_MEM_READ,
[GDB_WATCHPOINT_ACCESS] = BP_GDB | BP_MEM_ACCESS,
};
CPUClass *cc = CPU_GET_CLASS(cpu);
int cputype = xlat[gdbtype];
if (cc->gdb_stop_before_watchpoint) {
cputype |= BP_STOP_BEFORE_ACCESS;
}
return cputype;
}
static int tcg_insert_breakpoint(CPUState *cs, int type, hwaddr addr, hwaddr len)
{
CPUState *cpu;
int err = 0;
switch (type) {
case GDB_BREAKPOINT_SW:
case GDB_BREAKPOINT_HW:
CPU_FOREACH(cpu) {
err = cpu_breakpoint_insert(cpu, addr, BP_GDB, NULL);
if (err) {
break;
}
}
return err;
case GDB_WATCHPOINT_WRITE:
case GDB_WATCHPOINT_READ:
case GDB_WATCHPOINT_ACCESS:
CPU_FOREACH(cpu) {
err = cpu_watchpoint_insert(cpu, addr, len,
xlat_gdb_type(cpu, type), NULL);
if (err) {
break;
}
}
return err;
default:
return -ENOSYS;
}
}
static int tcg_remove_breakpoint(CPUState *cs, int type, hwaddr addr, hwaddr len)
{
CPUState *cpu;
int err = 0;
switch (type) {
case GDB_BREAKPOINT_SW:
case GDB_BREAKPOINT_HW:
CPU_FOREACH(cpu) {
err = cpu_breakpoint_remove(cpu, addr, BP_GDB);
if (err) {
break;
}
}
return err;
case GDB_WATCHPOINT_WRITE:
case GDB_WATCHPOINT_READ:
case GDB_WATCHPOINT_ACCESS:
CPU_FOREACH(cpu) {
err = cpu_watchpoint_remove(cpu, addr, len,
xlat_gdb_type(cpu, type));
if (err) {
break;
}
}
return err;
default:
return -ENOSYS;
}
}
static inline void tcg_remove_all_breakpoints(CPUState *cpu)
{
cpu_breakpoint_remove_all(cpu, BP_GDB);
cpu_watchpoint_remove_all(cpu, BP_GDB);
}
static void tcg_accel_ops_init(AccelOpsClass *ops) static void tcg_accel_ops_init(AccelOpsClass *ops)
{ {
if (qemu_tcg_mttcg_enabled()) { if (qemu_tcg_mttcg_enabled()) {
@ -109,6 +197,10 @@ static void tcg_accel_ops_init(AccelOpsClass *ops)
ops->handle_interrupt = tcg_handle_interrupt; ops->handle_interrupt = tcg_handle_interrupt;
} }
} }
ops->insert_breakpoint = tcg_insert_breakpoint;
ops->remove_breakpoint = tcg_remove_breakpoint;
ops->remove_all_breakpoints = tcg_remove_all_breakpoints;
} }
static void tcg_accel_ops_class_init(ObjectClass *oc, void *data) static void tcg_accel_ops_class_init(ObjectClass *oc, void *data)

View File

@ -49,8 +49,11 @@
#include "sysemu/runstate.h" #include "sysemu/runstate.h"
#include "semihosting/semihost.h" #include "semihosting/semihost.h"
#include "exec/exec-all.h" #include "exec/exec-all.h"
#include "exec/hwaddr.h"
#include "sysemu/replay.h" #include "sysemu/replay.h"
#include "internals.h"
#ifdef CONFIG_USER_ONLY #ifdef CONFIG_USER_ONLY
#define GDB_ATTACHED "0" #define GDB_ATTACHED "0"
#else #else
@ -1012,130 +1015,16 @@ void gdb_register_coprocessor(CPUState *cpu,
} }
} }
#ifndef CONFIG_USER_ONLY
/* Translate GDB watchpoint type to a flags value for cpu_watchpoint_* */
static inline int xlat_gdb_type(CPUState *cpu, int gdbtype)
{
static const int xlat[] = {
[GDB_WATCHPOINT_WRITE] = BP_GDB | BP_MEM_WRITE,
[GDB_WATCHPOINT_READ] = BP_GDB | BP_MEM_READ,
[GDB_WATCHPOINT_ACCESS] = BP_GDB | BP_MEM_ACCESS,
};
CPUClass *cc = CPU_GET_CLASS(cpu);
int cputype = xlat[gdbtype];
if (cc->gdb_stop_before_watchpoint) {
cputype |= BP_STOP_BEFORE_ACCESS;
}
return cputype;
}
#endif
static int gdb_breakpoint_insert(int type, target_ulong addr, target_ulong len)
{
CPUState *cpu;
int err = 0;
if (kvm_enabled()) {
return kvm_insert_breakpoint(gdbserver_state.c_cpu, addr, len, type);
}
switch (type) {
case GDB_BREAKPOINT_SW:
case GDB_BREAKPOINT_HW:
CPU_FOREACH(cpu) {
err = cpu_breakpoint_insert(cpu, addr, BP_GDB, NULL);
if (err) {
break;
}
}
return err;
#ifndef CONFIG_USER_ONLY
case GDB_WATCHPOINT_WRITE:
case GDB_WATCHPOINT_READ:
case GDB_WATCHPOINT_ACCESS:
CPU_FOREACH(cpu) {
err = cpu_watchpoint_insert(cpu, addr, len,
xlat_gdb_type(cpu, type), NULL);
if (err) {
break;
}
}
return err;
#endif
default:
return -ENOSYS;
}
}
static int gdb_breakpoint_remove(int type, target_ulong addr, target_ulong len)
{
CPUState *cpu;
int err = 0;
if (kvm_enabled()) {
return kvm_remove_breakpoint(gdbserver_state.c_cpu, addr, len, type);
}
switch (type) {
case GDB_BREAKPOINT_SW:
case GDB_BREAKPOINT_HW:
CPU_FOREACH(cpu) {
err = cpu_breakpoint_remove(cpu, addr, BP_GDB);
if (err) {
break;
}
}
return err;
#ifndef CONFIG_USER_ONLY
case GDB_WATCHPOINT_WRITE:
case GDB_WATCHPOINT_READ:
case GDB_WATCHPOINT_ACCESS:
CPU_FOREACH(cpu) {
err = cpu_watchpoint_remove(cpu, addr, len,
xlat_gdb_type(cpu, type));
if (err)
break;
}
return err;
#endif
default:
return -ENOSYS;
}
}
static inline void gdb_cpu_breakpoint_remove_all(CPUState *cpu)
{
cpu_breakpoint_remove_all(cpu, BP_GDB);
#ifndef CONFIG_USER_ONLY
cpu_watchpoint_remove_all(cpu, BP_GDB);
#endif
}
static void gdb_process_breakpoint_remove_all(GDBProcess *p) static void gdb_process_breakpoint_remove_all(GDBProcess *p)
{ {
CPUState *cpu = get_first_cpu_in_process(p); CPUState *cpu = get_first_cpu_in_process(p);
while (cpu) { while (cpu) {
gdb_cpu_breakpoint_remove_all(cpu); gdb_breakpoint_remove_all(cpu);
cpu = gdb_next_cpu_in_process(cpu); cpu = gdb_next_cpu_in_process(cpu);
} }
} }
static void gdb_breakpoint_remove_all(void)
{
CPUState *cpu;
if (kvm_enabled()) {
kvm_remove_all_breakpoints(gdbserver_state.c_cpu);
return;
}
CPU_FOREACH(cpu) {
gdb_cpu_breakpoint_remove_all(cpu);
}
}
static void gdb_set_cpu_pc(target_ulong pc) static void gdb_set_cpu_pc(target_ulong pc)
{ {
@ -1667,7 +1556,8 @@ static void handle_insert_bp(GArray *params, void *user_ctx)
return; return;
} }
res = gdb_breakpoint_insert(get_param(params, 0)->val_ul, res = gdb_breakpoint_insert(gdbserver_state.c_cpu,
get_param(params, 0)->val_ul,
get_param(params, 1)->val_ull, get_param(params, 1)->val_ull,
get_param(params, 2)->val_ull); get_param(params, 2)->val_ull);
if (res >= 0) { if (res >= 0) {
@ -1690,7 +1580,8 @@ static void handle_remove_bp(GArray *params, void *user_ctx)
return; return;
} }
res = gdb_breakpoint_remove(get_param(params, 0)->val_ul, res = gdb_breakpoint_remove(gdbserver_state.c_cpu,
get_param(params, 0)->val_ul,
get_param(params, 1)->val_ull, get_param(params, 1)->val_ull,
get_param(params, 2)->val_ull); get_param(params, 2)->val_ull);
if (res >= 0) { if (res >= 0) {
@ -2541,7 +2432,7 @@ static void handle_target_halt(GArray *params, void *user_ctx)
* because gdb is doing an initial connect and the state * because gdb is doing an initial connect and the state
* should be cleaned up. * should be cleaned up.
*/ */
gdb_breakpoint_remove_all(); gdb_breakpoint_remove_all(gdbserver_state.c_cpu);
} }
static int gdb_handle_packet(const char *line_buf) static int gdb_handle_packet(const char *line_buf)

16
gdbstub/internals.h Normal file
View File

@ -0,0 +1,16 @@
/*
* gdbstub internals
*
* Copyright (c) 2022 Linaro Ltd
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef _INTERNALS_H_
#define _INTERNALS_H_
int gdb_breakpoint_insert(CPUState *cs, int type, hwaddr addr, hwaddr len);
int gdb_breakpoint_remove(CPUState *cs, int type, hwaddr addr, hwaddr len);
void gdb_breakpoint_remove_all(CPUState *cs);
#endif /* _INTERNALS_H_ */

View File

@ -1 +1,9 @@
#
# The main gdbstub still relies on per-build definitions of various
# types. The bits pushed to softmmu/user.c try to use guest agnostic
# types such as hwaddr.
#
specific_ss.add(files('gdbstub.c')) specific_ss.add(files('gdbstub.c'))
softmmu_ss.add(files('softmmu.c'))
user_ss.add(files('user.c'))

42
gdbstub/softmmu.c Normal file
View File

@ -0,0 +1,42 @@
/*
* gdb server stub - softmmu specific bits
*
* Debug integration depends on support from the individual
* accelerators so most of this involves calling the ops helpers.
*
* Copyright (c) 2022 Linaro Ltd
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "qemu/osdep.h"
#include "exec/gdbstub.h"
#include "exec/hwaddr.h"
#include "sysemu/cpus.h"
#include "internals.h"
int gdb_breakpoint_insert(CPUState *cs, int type, hwaddr addr, hwaddr len)
{
const AccelOpsClass *ops = cpus_get_accel();
if (ops->insert_breakpoint) {
return ops->insert_breakpoint(cs, type, addr, len);
}
return -ENOSYS;
}
int gdb_breakpoint_remove(CPUState *cs, int type, hwaddr addr, hwaddr len)
{
const AccelOpsClass *ops = cpus_get_accel();
if (ops->remove_breakpoint) {
return ops->remove_breakpoint(cs, type, addr, len);
}
return -ENOSYS;
}
void gdb_breakpoint_remove_all(CPUState *cs)
{
const AccelOpsClass *ops = cpus_get_accel();
if (ops->remove_all_breakpoints) {
ops->remove_all_breakpoints(cs);
}
}

62
gdbstub/user.c Normal file
View File

@ -0,0 +1,62 @@
/*
* gdbstub user-mode helper routines.
*
* We know for user-mode we are using TCG so we can call stuff directly.
*
* Copyright (c) 2022 Linaro Ltd
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "qemu/osdep.h"
#include "exec/hwaddr.h"
#include "exec/gdbstub.h"
#include "hw/core/cpu.h"
#include "internals.h"
int gdb_breakpoint_insert(CPUState *cs, int type, hwaddr addr, hwaddr len)
{
CPUState *cpu;
int err = 0;
switch (type) {
case GDB_BREAKPOINT_SW:
case GDB_BREAKPOINT_HW:
CPU_FOREACH(cpu) {
err = cpu_breakpoint_insert(cpu, addr, BP_GDB, NULL);
if (err) {
break;
}
}
return err;
default:
/* user-mode doesn't support watchpoints */
return -ENOSYS;
}
}
int gdb_breakpoint_remove(CPUState *cs, int type, hwaddr addr, hwaddr len)
{
CPUState *cpu;
int err = 0;
switch (type) {
case GDB_BREAKPOINT_SW:
case GDB_BREAKPOINT_HW:
CPU_FOREACH(cpu) {
err = cpu_breakpoint_remove(cpu, addr, BP_GDB);
if (err) {
break;
}
}
return err;
default:
/* user-mode doesn't support watchpoints */
return -ENOSYS;
}
}
void gdb_breakpoint_remove_all(CPUState *cs)
{
cpu_breakpoint_remove_all(cs, BP_GDB);
}

View File

@ -10,6 +10,7 @@
#ifndef ACCEL_OPS_H #ifndef ACCEL_OPS_H
#define ACCEL_OPS_H #define ACCEL_OPS_H
#include "exec/hwaddr.h"
#include "qom/object.h" #include "qom/object.h"
#define ACCEL_OPS_SUFFIX "-ops" #define ACCEL_OPS_SUFFIX "-ops"
@ -44,6 +45,11 @@ struct AccelOpsClass {
int64_t (*get_virtual_clock)(void); int64_t (*get_virtual_clock)(void);
int64_t (*get_elapsed_ticks)(void); int64_t (*get_elapsed_ticks)(void);
/* gdbstub hooks */
int (*insert_breakpoint)(CPUState *cpu, int type, hwaddr addr, hwaddr len);
int (*remove_breakpoint)(CPUState *cpu, int type, hwaddr addr, hwaddr len);
void (*remove_all_breakpoints)(CPUState *cpu);
}; };
#endif /* ACCEL_OPS_H */ #endif /* ACCEL_OPS_H */

View File

@ -7,6 +7,9 @@
/* register accel-specific operations */ /* register accel-specific operations */
void cpus_register_accel(const AccelOpsClass *i); void cpus_register_accel(const AccelOpsClass *i);
/* return registers ops */
const AccelOpsClass *cpus_get_accel(void);
/* accel/dummy-cpus.c */ /* accel/dummy-cpus.c */
/* Create a dummy vcpu for AccelOpsClass->create_vcpu_thread */ /* Create a dummy vcpu for AccelOpsClass->create_vcpu_thread */

View File

@ -254,11 +254,6 @@ int kvm_on_sigbus(int code, void *addr);
void kvm_flush_coalesced_mmio_buffer(void); void kvm_flush_coalesced_mmio_buffer(void);
int kvm_insert_breakpoint(CPUState *cpu, target_ulong addr,
target_ulong len, int type);
int kvm_remove_breakpoint(CPUState *cpu, target_ulong addr,
target_ulong len, int type);
void kvm_remove_all_breakpoints(CPUState *cpu);
int kvm_update_guest_debug(CPUState *cpu, unsigned long reinject_trap); int kvm_update_guest_debug(CPUState *cpu, unsigned long reinject_trap);
/* internal API */ /* internal API */

View File

@ -617,6 +617,13 @@ void cpus_register_accel(const AccelOpsClass *ops)
cpus_accel = ops; cpus_accel = ops;
} }
const AccelOpsClass *cpus_get_accel(void)
{
/* broken if we call this early */
assert(cpus_accel);
return cpus_accel;
}
void qemu_init_vcpu(CPUState *cpu) void qemu_init_vcpu(CPUState *cpu)
{ {
MachineState *ms = MACHINE(qdev_get_machine()); MachineState *ms = MACHINE(qdev_get_machine());