Changeset View
Changeset View
Standalone View
Standalone View
usr.sbin/bhyve/gdb.c
Show All 31 Lines | |||||
#ifndef WITHOUT_CAPSICUM | #ifndef WITHOUT_CAPSICUM | ||||
#include <sys/capsicum.h> | #include <sys/capsicum.h> | ||||
#endif | #endif | ||||
#include <sys/endian.h> | #include <sys/endian.h> | ||||
#include <sys/ioctl.h> | #include <sys/ioctl.h> | ||||
#include <sys/mman.h> | #include <sys/mman.h> | ||||
#include <sys/queue.h> | #include <sys/queue.h> | ||||
#include <sys/socket.h> | #include <sys/socket.h> | ||||
#include <machine/atomic.h> | #include <machine/atomic.h> | ||||
#include <machine/reg.h> | |||||
#include <machine/specialreg.h> | #include <machine/specialreg.h> | ||||
#include <machine/vmm.h> | #include <machine/vmm.h> | ||||
#include <netinet/in.h> | #include <netinet/in.h> | ||||
#include <assert.h> | #include <assert.h> | ||||
#ifndef WITHOUT_CAPSICUM | #ifndef WITHOUT_CAPSICUM | ||||
#include <capsicum_helpers.h> | #include <capsicum_helpers.h> | ||||
#endif | #endif | ||||
#include <err.h> | #include <err.h> | ||||
#include <errno.h> | #include <errno.h> | ||||
#include <fcntl.h> | #include <fcntl.h> | ||||
#include <netdb.h> | #include <netdb.h> | ||||
Show All 12 Lines | |||||
#include "gdb.h" | #include "gdb.h" | ||||
#include "mem.h" | #include "mem.h" | ||||
#include "mevent.h" | #include "mevent.h" | ||||
/* | /* | ||||
* GDB_SIGNAL_* numbers are part of the GDB remote protocol. Most stops | * GDB_SIGNAL_* numbers are part of the GDB remote protocol. Most stops | ||||
* use SIGTRAP. | * use SIGTRAP. | ||||
*/ | */ | ||||
#define GDB_SIGNAL_TRAP 5 | #define GDB_SIGNAL_TRAP 5 | ||||
#define GDB_SOFTWARE_BPT 0 | |||||
#define GDB_WATCHPOINT_TYPE_WRITE 2 | |||||
#define GDB_WATCHPOINT_TYPE_READ 3 | |||||
#define GDB_WATCHPOINT_TYPE_ACCESS 4 | |||||
#define GDB_WATCHPOINT_MAX 4 | |||||
#define GDB_WATCHPOINT_MASK ((1 << GDB_WATCHPOINT_MAX) - 1) | |||||
#define GDB_WATCHPOINT_CLEAR_NOSKIP -1 | |||||
#define GDB_WATCHPOINT_INIT() \ | |||||
watch_stats.avail_dbregs = (-1 & GDB_WATCHPOINT_MASK) | |||||
#define GDB_FIND_WATCHPOINT() (__builtin_ffs(watch_stats.avail_dbregs) - 1) | |||||
#define GDB_HAS_AVAIL_WATCHPOINT() (watch_stats.avail_dbregs != 0) | |||||
#define GDB_ALLOC_WATCHPOINT(num) \ | |||||
watch_stats.avail_dbregs &= ~(1 << (num & GDB_WATCHPOINT_MASK)) | |||||
#define GDB_FREE_WATCHPOINT(num) \ | |||||
watch_stats.avail_dbregs |= (1 << (num & GDB_WATCHPOINT_MASK)) | |||||
static void gdb_resume_vcpus(void); | static void gdb_resume_vcpus(void); | ||||
static void check_command(int fd); | static void check_command(int fd); | ||||
static struct mevent *read_event, *write_event; | static struct mevent *read_event, *write_event; | ||||
static cpuset_t vcpus_active, vcpus_suspended, vcpus_waiting; | static cpuset_t vcpus_active, vcpus_suspended, vcpus_waiting; | ||||
static pthread_mutex_t gdb_lock; | static pthread_mutex_t gdb_lock; | ||||
static pthread_cond_t idle_vcpus; | static pthread_cond_t idle_vcpus; | ||||
Show All 14 Lines | |||||
}; | }; | ||||
struct breakpoint { | struct breakpoint { | ||||
uint64_t gpa; | uint64_t gpa; | ||||
uint8_t shadow_inst; | uint8_t shadow_inst; | ||||
TAILQ_ENTRY(breakpoint) link; | TAILQ_ENTRY(breakpoint) link; | ||||
}; | }; | ||||
struct watchpoint_stats { | |||||
int no_active; | |||||
int no_evicted; | |||||
int avail_dbregs; /* Tracks DR regs used by the guest */ | |||||
struct watchpoint { | |||||
enum watchpoint_state { | |||||
WATCH_INACTIVE = 0, | |||||
WATCH_ACTIVE, | |||||
WATCH_EVICTED, | |||||
} state; | |||||
uint64_t gva; | |||||
int type; | |||||
int bytes; | |||||
} watchpoints[GDB_WATCHPOINT_MAX]; | |||||
}; | |||||
/* | /* | ||||
* When a vCPU stops to due to an event that should be reported to the | * When a vCPU stops to due to an event that should be reported to the | ||||
* debugger, information about the event is stored in this structure. | * debugger, information about the event is stored in this structure. | ||||
* The vCPU thread then sets 'stopped_vcpu' if it is not already set | * The vCPU thread then sets 'stopped_vcpu' if it is not already set | ||||
* and stops other vCPUs so the event can be reported. The | * and stops other vCPUs so the event can be reported. The | ||||
* report_stop() function reports the event for the 'stopped_vcpu' | * report_stop() function reports the event for the 'stopped_vcpu' | ||||
* vCPU. When the debugger resumes execution via continue or step, | * vCPU. When the debugger resumes execution via continue or step, | ||||
* the event for 'stopped_vcpu' is cleared. vCPUs will loop in their | * the event for 'stopped_vcpu' is cleared. vCPUs will loop in their | ||||
* event handlers until the associated event is reported or disabled. | * event handlers until the associated event is reported or disabled. | ||||
* | * | ||||
* An idle vCPU will have all of the boolean fields set to false. | * An idle vCPU will have all of the boolean fields set to false. | ||||
* | * | ||||
* When a vCPU is stepped, 'stepping' is set to true when the vCPU is | * When a vCPU is stepped, 'stepping' is set to true when the vCPU is | ||||
* released to execute the stepped instruction. When the vCPU reports | * released to execute the stepped instruction. When the vCPU reports | ||||
* the stepping trap, 'stepped' is set. | * the stepping trap, 'stepped' is set. | ||||
* | * | ||||
* When a vCPU hits a breakpoint set by the debug server, | * When a vCPU hits a breakpoint set by the debug server, | ||||
* 'hit_swbreak' is set to true. | * 'hit_swbreak' is set to true. | ||||
* | |||||
* When a vCPU hits a watchpoint set by the debug server, | |||||
* 'hit_watch' is set to point to the corresponding watchpoint. | |||||
*/ | */ | ||||
struct vcpu_state { | struct vcpu_state { | ||||
bool stepping; | bool stepping; | ||||
bool stepped; | bool stepped; | ||||
bool hit_swbreak; | bool hit_swbreak; | ||||
struct watchpoint *hit_watch; | |||||
}; | }; | ||||
static struct io_buffer cur_comm, cur_resp; | static struct io_buffer cur_comm, cur_resp; | ||||
static uint8_t cur_csum; | static uint8_t cur_csum; | ||||
static struct vmctx *ctx; | static struct vmctx *ctx; | ||||
static int cur_fd = -1; | static int cur_fd = -1; | ||||
static TAILQ_HEAD(, breakpoint) breakpoints; | static TAILQ_HEAD(, breakpoint) breakpoints; | ||||
static struct watchpoint_stats watch_stats; | |||||
static struct vcpu_state *vcpu_state; | static struct vcpu_state *vcpu_state; | ||||
static int cur_vcpu, stopped_vcpu; | static int cur_vcpu, stopped_vcpu; | ||||
static bool gdb_active = false; | static bool gdb_active = false; | ||||
const int gdb_regset[] = { | const int gdb_regset[] = { | ||||
VM_REG_GUEST_RAX, | VM_REG_GUEST_RAX, | ||||
VM_REG_GUEST_RBX, | VM_REG_GUEST_RBX, | ||||
VM_REG_GUEST_RCX, | VM_REG_GUEST_RCX, | ||||
▲ Show 20 Lines • Show All 74 Lines • ▼ Show 20 Lines | #endif | ||||
vfprintf(logfile, fmt, ap); | vfprintf(logfile, fmt, ap); | ||||
va_end(ap); | va_end(ap); | ||||
} | } | ||||
#else | #else | ||||
#define debug(...) | #define debug(...) | ||||
#endif | #endif | ||||
static void remove_all_sw_breakpoints(void); | static void remove_all_sw_breakpoints(void); | ||||
static void remove_all_hw_watchpoints(void); | |||||
static int | static int | ||||
guest_paging_info(int vcpu, struct vm_guest_paging *paging) | guest_paging_info(int vcpu, struct vm_guest_paging *paging) | ||||
{ | { | ||||
uint64_t regs[4]; | uint64_t regs[4]; | ||||
const int regset[4] = { | const int regset[4] = { | ||||
VM_REG_GUEST_CR0, | VM_REG_GUEST_CR0, | ||||
VM_REG_GUEST_CR3, | VM_REG_GUEST_CR3, | ||||
▲ Show 20 Lines • Show All 154 Lines • ▼ Show 20 Lines | close_connection(void) | ||||
mevent_delete(write_event); | mevent_delete(write_event); | ||||
mevent_delete_close(read_event); | mevent_delete_close(read_event); | ||||
write_event = NULL; | write_event = NULL; | ||||
read_event = NULL; | read_event = NULL; | ||||
io_buffer_reset(&cur_comm); | io_buffer_reset(&cur_comm); | ||||
io_buffer_reset(&cur_resp); | io_buffer_reset(&cur_resp); | ||||
cur_fd = -1; | cur_fd = -1; | ||||
remove_all_hw_watchpoints(); | |||||
remove_all_sw_breakpoints(); | remove_all_sw_breakpoints(); | ||||
/* Clear any pending events. */ | /* Clear any pending events. */ | ||||
memset(vcpu_state, 0, guest_ncpus * sizeof(*vcpu_state)); | memset(vcpu_state, 0, guest_ncpus * sizeof(*vcpu_state)); | ||||
/* Resume any stopped vCPUs. */ | /* Resume any stopped vCPUs. */ | ||||
gdb_resume_vcpus(); | gdb_resume_vcpus(); | ||||
pthread_mutex_unlock(&gdb_lock); | pthread_mutex_unlock(&gdb_lock); | ||||
} | } | ||||
static const char * | |||||
gdb_watch_type_str(struct watchpoint *wp) | |||||
{ | |||||
switch (wp->type) { | |||||
case GDB_WATCHPOINT_TYPE_ACCESS: | |||||
return "awatch"; | |||||
case GDB_WATCHPOINT_TYPE_READ: | |||||
return "rwatch"; | |||||
case GDB_WATCHPOINT_TYPE_WRITE: | |||||
return "watch"; | |||||
default: | |||||
// TODO: assert? | |||||
return ""; | |||||
} | |||||
} | |||||
static uint8_t | static uint8_t | ||||
hex_digit(uint8_t nibble) | hex_digit(uint8_t nibble) | ||||
{ | { | ||||
if (nibble <= 9) | if (nibble <= 9) | ||||
return (nibble + '0'); | return (nibble + '0'); | ||||
else | else | ||||
return (nibble + 'a' - 10); | return (nibble + 'a' - 10); | ||||
▲ Show 20 Lines • Show All 266 Lines • ▼ Show 20 Lines | if (stopped_vcpu == -1) { | ||||
append_byte(GDB_SIGNAL_TRAP); | append_byte(GDB_SIGNAL_TRAP); | ||||
append_string("thread:"); | append_string("thread:"); | ||||
append_integer(stopped_vcpu + 1); | append_integer(stopped_vcpu + 1); | ||||
append_char(';'); | append_char(';'); | ||||
if (vs->hit_swbreak) { | if (vs->hit_swbreak) { | ||||
debug("$vCPU %d reporting swbreak\n", stopped_vcpu); | debug("$vCPU %d reporting swbreak\n", stopped_vcpu); | ||||
if (swbreak_enabled) | if (swbreak_enabled) | ||||
append_string("swbreak:;"); | append_string("swbreak:;"); | ||||
} else if (vs->stepped) | } else if (vs->stepped) { | ||||
debug("$vCPU %d reporting step\n", stopped_vcpu); | debug("$vCPU %d reporting step\n", stopped_vcpu); | ||||
else | } else if (vs->hit_watch) { | ||||
debug("$vCPU %d reporting watchpoint\n", stopped_vcpu); | |||||
append_string(gdb_watch_type_str(vs->hit_watch)); | |||||
append_char(':'); | |||||
append_unsigned_be( | |||||
vs->hit_watch->gva, sizeof(vs->hit_watch->gva)); | |||||
append_char(';'); | |||||
} else { | |||||
debug("$vCPU %d reporting ???\n", stopped_vcpu); | debug("$vCPU %d reporting ???\n", stopped_vcpu); | ||||
} | } | ||||
} | |||||
finish_packet(); | finish_packet(); | ||||
report_next_stop = false; | report_next_stop = false; | ||||
} | } | ||||
/* | /* | ||||
* If this stop is due to a vCPU event, clear that event to mark it as | * If this stop is due to a vCPU event, clear that event to mark it as | ||||
* acknowledged. | * acknowledged. | ||||
*/ | */ | ||||
static void | static void | ||||
discard_stop(void) | discard_stop(void) | ||||
{ | { | ||||
struct vcpu_state *vs; | struct vcpu_state *vs; | ||||
if (stopped_vcpu != -1) { | if (stopped_vcpu != -1) { | ||||
vs = &vcpu_state[stopped_vcpu]; | vs = &vcpu_state[stopped_vcpu]; | ||||
vs->hit_swbreak = false; | vs->hit_swbreak = false; | ||||
vs->hit_watch = NULL; | |||||
vs->stepped = false; | vs->stepped = false; | ||||
stopped_vcpu = -1; | stopped_vcpu = -1; | ||||
} | } | ||||
report_next_stop = true; | report_next_stop = true; | ||||
} | } | ||||
static void | static void | ||||
gdb_finish_suspend_vcpus(void) | gdb_finish_suspend_vcpus(void) | ||||
Show All 23 Lines | _gdb_cpu_suspend(int vcpu, bool report_stop) | ||||
if (report_stop && CPU_CMP(&vcpus_waiting, &vcpus_suspended) == 0) | if (report_stop && CPU_CMP(&vcpus_waiting, &vcpus_suspended) == 0) | ||||
gdb_finish_suspend_vcpus(); | gdb_finish_suspend_vcpus(); | ||||
while (CPU_ISSET(vcpu, &vcpus_suspended)) | while (CPU_ISSET(vcpu, &vcpus_suspended)) | ||||
pthread_cond_wait(&idle_vcpus, &gdb_lock); | pthread_cond_wait(&idle_vcpus, &gdb_lock); | ||||
CPU_CLR(vcpu, &vcpus_waiting); | CPU_CLR(vcpu, &vcpus_waiting); | ||||
debug("$vCPU %d resuming\n", vcpu); | debug("$vCPU %d resuming\n", vcpu); | ||||
} | } | ||||
static void | |||||
gdb_suspend_vcpus(void) | |||||
{ | |||||
assert(pthread_mutex_isowned_np(&gdb_lock)); | |||||
debug("suspending all CPUs\n"); | |||||
vcpus_suspended = vcpus_active; | |||||
vm_suspend_cpu(ctx, -1); | |||||
if (CPU_CMP(&vcpus_waiting, &vcpus_suspended) == 0) | |||||
gdb_finish_suspend_vcpus(); | |||||
} | |||||
/* | /* | ||||
* Requests vCPU single-stepping using a | |||||
* VMEXIT suitable for the host platform. | |||||
*/ | |||||
static int | |||||
_gdb_set_step(int vcpu, int val) | |||||
{ | |||||
/* If the MTRAP cap fails, we are running on an AMD host. | |||||
* In that case, we request DB exits caused by RFLAGS.TF | |||||
* stepping. | |||||
*/ | |||||
int error = vm_set_capability(ctx, vcpu, VM_CAP_MTRAP_EXIT, val); | |||||
if (error) { | |||||
error = vm_set_capability(ctx, vcpu, VM_CAP_RFLAGS_SSTEP, val); | |||||
} | |||||
return error; | |||||
} | |||||
static int | |||||
_gdb_check_step(int vcpu) | |||||
{ | |||||
int error, val; | |||||
error = vm_get_capability(ctx, vcpu, VM_CAP_MTRAP_EXIT, &val); | |||||
if (error < 0) { | |||||
/* Check whether AMD rflags.tf stepping is supported */ | |||||
if (vm_get_capability(ctx, vcpu, VM_CAP_RFLAGS_SSTEP, &val) < 0) | |||||
return -1; | |||||
} | |||||
return 0; | |||||
} | |||||
/* | |||||
* Invoked by vCPU before resuming execution. This enables stepping | |||||
* if the vCPU is marked as stepping. | |||||
*/ | |||||
static void | |||||
gdb_cpu_resume(int vcpu) | |||||
{ | |||||
struct vcpu_state *vs; | |||||
int error; | |||||
vs = &vcpu_state[vcpu]; | |||||
/* | |||||
* Any pending event should already be reported before | |||||
* resuming. | |||||
*/ | |||||
assert(vs->hit_swbreak == false); | |||||
assert(vs->hit_watch == NULL); | |||||
assert(vs->stepped == false); | |||||
if (vs->stepping) { | |||||
error = _gdb_set_step(vcpu, 1); | |||||
assert(error == 0); | |||||
} | |||||
} | |||||
/* | |||||
* Invoked each time a vmexit handler needs to step a vCPU. | |||||
*/ | |||||
static void | |||||
_gdb_cpu_step(int vcpu) | |||||
{ | |||||
struct vcpu_state *vs; | |||||
debug("$vCPU %d stepped\n", vcpu); | |||||
pthread_mutex_lock(&gdb_lock); | |||||
vs = &vcpu_state[vcpu]; | |||||
if (vs->stepping) { | |||||
vs->stepping = false; | |||||
vs->stepped = true; | |||||
_gdb_set_step(vcpu, 0); | |||||
while (vs->stepped) { | |||||
if (stopped_vcpu == -1) { | |||||
debug("$vCPU %d reporting step\n", vcpu); | |||||
stopped_vcpu = vcpu; | |||||
gdb_suspend_vcpus(); | |||||
} | |||||
_gdb_cpu_suspend(vcpu, true); | |||||
} | |||||
gdb_cpu_resume(vcpu); | |||||
} | |||||
pthread_mutex_unlock(&gdb_lock); | |||||
} | |||||
/* | |||||
* Invoked at the start of a vCPU thread's execution to inform the | * Invoked at the start of a vCPU thread's execution to inform the | ||||
* debug server about the new thread. | * debug server about the new thread. | ||||
*/ | */ | ||||
void | void | ||||
gdb_cpu_add(int vcpu) | gdb_cpu_add(int vcpu) | ||||
{ | { | ||||
if (!gdb_active) | if (!gdb_active) | ||||
Show All 14 Lines | gdb_cpu_add(int vcpu) | ||||
*/ | */ | ||||
if (!CPU_EMPTY(&vcpus_suspended)) { | if (!CPU_EMPTY(&vcpus_suspended)) { | ||||
CPU_SET(vcpu, &vcpus_suspended); | CPU_SET(vcpu, &vcpus_suspended); | ||||
_gdb_cpu_suspend(vcpu, false); | _gdb_cpu_suspend(vcpu, false); | ||||
} | } | ||||
pthread_mutex_unlock(&gdb_lock); | pthread_mutex_unlock(&gdb_lock); | ||||
} | } | ||||
static bool | |||||
set_dbexit_caps(bool enable) | |||||
{ | |||||
cpuset_t mask; | |||||
int vcpu; | |||||
mask = vcpus_active; | |||||
while (!CPU_EMPTY(&mask)) { | |||||
vcpu = CPU_FFS(&mask) - 1; | |||||
CPU_CLR(vcpu, &mask); | |||||
if (vm_set_capability( | |||||
ctx, vcpu, VM_CAP_DB_EXIT, enable ? 1 : 0) < 0) | |||||
return (false); | |||||
debug("$vCPU %d %sabled debug exits\n", vcpu, | |||||
enable ? "en" : "dis"); | |||||
} | |||||
return (true); | |||||
} | |||||
static bool | |||||
set_dbreg_exit_caps(bool enable) | |||||
{ | |||||
cpuset_t mask; | |||||
int vcpu; | |||||
mask = vcpus_active; | |||||
while (!CPU_EMPTY(&mask)) { | |||||
vcpu = CPU_FFS(&mask) - 1; | |||||
CPU_CLR(vcpu, &mask); | |||||
if (vm_set_capability( | |||||
ctx, vcpu, VM_CAP_DR_MOV_EXIT, enable ? 1 : 0) < 0) | |||||
return (false); | |||||
debug("$vCPU %d %sabled debug register access exits\n", vcpu, | |||||
enable ? "en" : "dis"); | |||||
} | |||||
return (true); | |||||
} | |||||
/* | /* | ||||
* Invoked by vCPU before resuming execution. This enables stepping | * A helper routine for setting watchpoints. | ||||
* if the vCPU is marked as stepping. | * Each watchpoint is "global" and is placed into the corresponding DR* | ||||
* registers on all vCPUs. | |||||
*/ | */ | ||||
static void | |||||
gdb_cpu_resume(int vcpu) | static int | ||||
set_watchpoint(uint64_t gva, int type, int bytes, int watchnum) | |||||
{ | { | ||||
struct vcpu_state *vs; | int access, len; | ||||
int error; | struct watchpoint *wp; | ||||
vs = &vcpu_state[vcpu]; | cpuset_t mask; | ||||
int vcpu; | |||||
uint64_t dr7; | |||||
int dbreg = VM_REG_GUEST_DR0 + watchnum; | |||||
switch (type) { | |||||
case GDB_WATCHPOINT_TYPE_WRITE: | |||||
access = DBREG_DR7_WRONLY; | |||||
break; | |||||
case GDB_WATCHPOINT_TYPE_ACCESS: | |||||
case GDB_WATCHPOINT_TYPE_READ: | |||||
access = DBREG_DR7_RDWR; | |||||
break; | |||||
default: | |||||
return (EINVAL); | |||||
} | |||||
switch (bytes) { | |||||
case 1: | |||||
len = DBREG_DR7_LEN_1; | |||||
break; | |||||
case 2: | |||||
len = DBREG_DR7_LEN_2; | |||||
break; | |||||
case 4: | |||||
len = DBREG_DR7_LEN_4; | |||||
break; | |||||
case 8: | |||||
len = DBREG_DR7_LEN_8; | |||||
break; | |||||
default: | |||||
return (EINVAL); | |||||
} | |||||
mask = vcpus_active; | |||||
while (!CPU_EMPTY(&mask)) { | |||||
vcpu = CPU_FFS(&mask) - 1; | |||||
CPU_CLR(vcpu, &mask); | |||||
/* Write gva to debug reg */ | |||||
vm_set_register(ctx, vcpu, dbreg, gva); | |||||
/* Enable watchpoint in DR7 */ | |||||
vm_get_register(ctx, vcpu, VM_REG_GUEST_DR7, &dr7); | |||||
dr7 &= ~DBREG_DR7_MASK(watchnum); | |||||
dr7 |= DBREG_DR7_SET( | |||||
watchnum, len, access, DBREG_DR7_GLOBAL_ENABLE); | |||||
vm_set_register(ctx, vcpu, VM_REG_GUEST_DR7, dr7); | |||||
} | |||||
wp = &watch_stats.watchpoints[watchnum]; | |||||
/* | /* | ||||
* Any pending event should already be reported before | * An already active watchpoint can be passed - don't | ||||
* resuming. | * increment overall active watchpoints. | ||||
*/ | */ | ||||
assert(vs->hit_swbreak == false); | if (wp->state != WATCH_ACTIVE) { | ||||
assert(vs->stepped == false); | watch_stats.no_active++; | ||||
if (vs->stepping) { | |||||
error = vm_set_capability(ctx, vcpu, VM_CAP_MTRAP_EXIT, 1); | |||||
assert(error == 0); | |||||
} | } | ||||
wp->state = WATCH_ACTIVE; | |||||
wp->gva = gva; | |||||
wp->type = type; | |||||
wp->bytes = bytes; | |||||
GDB_ALLOC_WATCHPOINT(watchnum); | |||||
return 0; | |||||
} | } | ||||
/* | /* | ||||
* Handler for VM_EXITCODE_DEBUG used to suspend a vCPU when the guest | * Clears watchpoint metadata and disables it on all guest vCPUs. | ||||
* has been suspended due to an event on different vCPU or in response | * | ||||
* to a guest-wide suspend such as Ctrl-C or the stop on attach. | * The 'skip_vcpu' arg may be passed to prevent this routine from modifying the | ||||
* DR7 register on a specific vCPU (used when handling VMEXITS caused by DR7 | |||||
* write to avoid thrashing the new value). | |||||
* | |||||
* The 'clear_dbreg' arg controls whether the underlying debug register is | |||||
* zeroed. | |||||
*/ | */ | ||||
void | static int | ||||
gdb_cpu_suspend(int vcpu) | clear_watchpoint(int watchnum, int skip_vcpu, bool clear_dbreg) | ||||
{ | { | ||||
cpuset_t mask; | |||||
int vcpu; | |||||
uint64_t dr7; | |||||
if (!gdb_active) | mask = vcpus_active; | ||||
return; | while (!CPU_EMPTY(&mask)) { | ||||
pthread_mutex_lock(&gdb_lock); | |||||
_gdb_cpu_suspend(vcpu, true); | vcpu = CPU_FFS(&mask) - 1; | ||||
gdb_cpu_resume(vcpu); | CPU_CLR(vcpu, &mask); | ||||
pthread_mutex_unlock(&gdb_lock); | if (clear_dbreg) { | ||||
vm_set_register( | |||||
ctx, vcpu, VM_REG_GUEST_DR0 + watchnum, 0); | |||||
} | } | ||||
if (vcpu == skip_vcpu) { | |||||
continue; | |||||
} | |||||
static void | /* Disable watchpoint in DR7 */ | ||||
gdb_suspend_vcpus(void) | vm_get_register(ctx, vcpu, VM_REG_GUEST_DR7, &dr7); | ||||
dr7 &= ~DBREG_DR7_MASK(watchnum); | |||||
vm_set_register(ctx, vcpu, VM_REG_GUEST_DR7, dr7); | |||||
} | |||||
watch_stats.watchpoints[watchnum].state = WATCH_INACTIVE; | |||||
/* Refrain from clearing other fields - this avoids unnecessary copies | |||||
* if migrate_watchpoint is called afterward */ | |||||
watch_stats.no_active--; | |||||
GDB_FREE_WATCHPOINT(watchnum); | |||||
return 0; | |||||
} | |||||
static struct watchpoint * | |||||
find_watchpoint(uint64_t gla) | |||||
{ | { | ||||
struct watchpoint *wp; | |||||
assert(pthread_mutex_isowned_np(&gdb_lock)); | for (int i = 0; i < GDB_WATCHPOINT_MAX; i++) { | ||||
debug("suspending all CPUs\n"); | wp = &watch_stats.watchpoints[i]; | ||||
vcpus_suspended = vcpus_active; | if (wp->state == WATCH_ACTIVE && (wp->gva == gla)) { | ||||
vm_suspend_cpu(ctx, -1); | return wp; | ||||
if (CPU_CMP(&vcpus_waiting, &vcpus_suspended) == 0) | |||||
gdb_finish_suspend_vcpus(); | |||||
} | } | ||||
} | |||||
return (NULL); | |||||
} | |||||
/* | /* | ||||
* Handler for VM_EXITCODE_MTRAP reported when a vCPU single-steps via | * Tries to reactivate a previously evicted watchpoint. | ||||
* the VT-x-specific MTRAP exit. | |||||
*/ | */ | ||||
void | static int | ||||
gdb_cpu_mtrap(int vcpu) | migrate_watchpoint(struct watchpoint *wp) | ||||
{ | { | ||||
int error; | |||||
if (!GDB_HAS_AVAIL_WATCHPOINT()) { | |||||
return -1; | |||||
} | |||||
if (watch_stats.no_active == 0 && watch_stats.no_evicted == 0) { | |||||
if (!set_dbexit_caps(true) || !set_dbreg_exit_caps(true)) { | |||||
return -1; | |||||
} | |||||
} | |||||
int watchnum = GDB_FIND_WATCHPOINT(); | |||||
assert(watchnum >= 0); | |||||
error = set_watchpoint(wp->gva, wp->type, wp->bytes, watchnum); | |||||
if (error == 0) { | |||||
watch_stats.no_evicted--; | |||||
/* check if the watchpoint was migrated to the same slot */ | |||||
if (wp->state != WATCH_ACTIVE) | |||||
wp->state = WATCH_INACTIVE; | |||||
} | |||||
return error; | |||||
} | |||||
static void | |||||
init_watchpoint_metadata(void) | |||||
{ | |||||
cpuset_t mask; | |||||
int vcpu; | |||||
uint64_t dr7; | |||||
GDB_WATCHPOINT_INIT(); | |||||
mask = vcpus_active; | |||||
while (!CPU_EMPTY(&mask)) { | |||||
int vcpu_used_dbreg_mask = 0; | |||||
vcpu = CPU_FFS(&mask) - 1; | |||||
CPU_CLR(vcpu, &mask); | |||||
/* Construct bitmask of active dbregs */ | |||||
vm_get_register(ctx, vcpu, VM_REG_GUEST_DR7, &dr7); | |||||
vcpu_used_dbreg_mask = (DBREG_DR7_ENABLED(dr7, 0) | | |||||
(DBREG_DR7_ENABLED(dr7, 1) << 1) | | |||||
(DBREG_DR7_ENABLED(dr7, 2) << 2) | | |||||
(DBREG_DR7_ENABLED(dr7, 3) << 3)); | |||||
/* Mark any currently enabled dbreg as | |||||
* unavailable */ | |||||
watch_stats.avail_dbregs &= ~vcpu_used_dbreg_mask; | |||||
} | |||||
} | |||||
static void | |||||
rebuild_avail_watchpoints(void) | |||||
{ | |||||
init_watchpoint_metadata(); | |||||
for (int i = 0; i < GDB_WATCHPOINT_MAX; i++) { | |||||
if (watch_stats.watchpoints[i].state == WATCH_ACTIVE) { | |||||
GDB_ALLOC_WATCHPOINT(i); | |||||
} | |||||
} | |||||
} | |||||
static void | |||||
handle_watchpoint_hit(int vcpu, int watch_mask) | |||||
{ | |||||
int watchnum = __builtin_ffs(watch_mask) - 1; | |||||
int dbreg = VM_REG_GUEST_DR0 + watchnum; | |||||
struct vcpu_state *vs; | struct vcpu_state *vs; | ||||
struct watchpoint *watch; | |||||
if (!gdb_active) | uint64_t gla; | ||||
return; | uint64_t dr6; | ||||
debug("$vCPU %d MTRAP\n", vcpu); | |||||
assert(watchnum >= 0); | |||||
pthread_mutex_lock(&gdb_lock); | pthread_mutex_lock(&gdb_lock); | ||||
if (!watch_stats.no_active) { | |||||
vm_inject_exception(ctx, vcpu, IDT_DB, 0, 0, 0); | |||||
pthread_mutex_unlock(&gdb_lock); | |||||
return; | |||||
} | |||||
vm_get_register(ctx, vcpu, dbreg, &gla); | |||||
watch = find_watchpoint(gla); | |||||
if (watch) { | |||||
vs = &vcpu_state[vcpu]; | vs = &vcpu_state[vcpu]; | ||||
if (vs->stepping) { | |||||
vs->stepping = false; | assert(vs->stepping == false); | ||||
vs->stepped = true; | assert(vs->stepped == false); | ||||
vm_set_capability(ctx, vcpu, VM_CAP_MTRAP_EXIT, 0); | assert(vs->hit_swbreak == false); | ||||
while (vs->stepped) { | assert(vs->hit_watch == false); | ||||
vs->hit_watch = watch; | |||||
for (;;) { | |||||
if (stopped_vcpu == -1) { | if (stopped_vcpu == -1) { | ||||
debug("$vCPU %d reporting step\n", vcpu); | |||||
stopped_vcpu = vcpu; | stopped_vcpu = vcpu; | ||||
gdb_suspend_vcpus(); | gdb_suspend_vcpus(); | ||||
} | } | ||||
_gdb_cpu_suspend(vcpu, true); | _gdb_cpu_suspend(vcpu, true); | ||||
if (!vs->hit_watch) { | |||||
/* Watchpoint reported. */ | |||||
break; | |||||
} | } | ||||
if (watch->state == WATCH_INACTIVE) { | |||||
/* Watchpoint removed. */ | |||||
break; | |||||
} | |||||
} | |||||
vm_get_register(ctx, vcpu, VM_REG_GUEST_DR6, &dr6); | |||||
dr6 &= DBREG_DR6_RESERVED1; | |||||
vm_set_register(ctx, vcpu, VM_REG_GUEST_DR6, dr6); | |||||
gdb_cpu_resume(vcpu); | gdb_cpu_resume(vcpu); | ||||
} else { | |||||
/* Reflect the DB exception back into the guest */ | |||||
vm_inject_exception(ctx, vcpu, IDT_DB, 0, 0, 0); | |||||
} | } | ||||
pthread_mutex_unlock(&gdb_lock); | pthread_mutex_unlock(&gdb_lock); | ||||
} | } | ||||
static void | |||||
handle_drx_read(int vcpu, struct vm_exit *vmexit) | |||||
{ | |||||
struct watchpoint *wp; | |||||
int dbreg_num = vmexit->u.dbg.drx_access; | |||||
uint64_t gpr_val; | |||||
int gpr = vmexit->u.dbg.gpr; | |||||
if (dbreg_num >= 4 && dbreg_num <= 6) { | |||||
return; | |||||
} | |||||
pthread_mutex_lock(&gdb_lock); | |||||
wp = &watch_stats.watchpoints[dbreg_num]; | |||||
if (dbreg_num == 7) { | |||||
vm_get_register(ctx, vcpu, gpr, &gpr_val); | |||||
for (int i = 0; i < GDB_WATCHPOINT_MAX; i++) { | |||||
/* Clear newly read dr7 mask for gdbstub watchpoints */ | |||||
if (watch_stats.watchpoints[i].state == WATCH_ACTIVE) { | |||||
gpr_val &= ~DBREG_DR7_MASK(i); | |||||
} | |||||
} | |||||
vm_set_register(ctx, vcpu, gpr, gpr_val); | |||||
} | |||||
/* If the guest attempts to read from a gdbstub-active dbreg, set the | |||||
* gpr register to 0 */ | |||||
if (wp->state == WATCH_ACTIVE) { | |||||
vm_set_register(ctx, vcpu, vmexit->u.dbg.gpr, 0); | |||||
} | |||||
pthread_mutex_unlock(&gdb_lock); | |||||
} | |||||
static void | |||||
handle_drx_write(int vcpu, struct vm_exit *vmexit) | |||||
{ | |||||
int error; | |||||
struct watchpoint *wp; | |||||
uint64_t dbreg_val; | |||||
int dbreg_num = vmexit->u.dbg.drx_access; | |||||
if (dbreg_num >= 4 && dbreg_num <= 6) { | |||||
return; | |||||
} | |||||
pthread_mutex_lock(&gdb_lock); | |||||
wp = &watch_stats.watchpoints[dbreg_num]; | |||||
if (dbreg_num == 7) { | |||||
/* A new DR7 was loaded, update watchpoint metadata */ | |||||
int dr7 = vmexit->u.dbg.watchpoints; | |||||
/* Clear any watchpoints the guest started using */ | |||||
for (int i = 0; i < GDB_WATCHPOINT_MAX; i++) { | |||||
wp = &watch_stats.watchpoints[i]; | |||||
bool dbreg_enabled = DBREG_DR7_ENABLED(dr7, i); | |||||
bool watchpoint_active = wp->state == WATCH_ACTIVE; | |||||
if (dbreg_enabled && watchpoint_active) { | |||||
/* Evict active watchpoint */ | |||||
debug( | |||||
"%s: dr7 write: evicting active watchpoint %d\n", | |||||
__func__, i); | |||||
clear_watchpoint(i, vcpu, true); | |||||
wp->state = WATCH_EVICTED; | |||||
watch_stats.no_evicted++; | |||||
} else if (!dbreg_enabled && watchpoint_active) { | |||||
debug( | |||||
"%s: dr7 write: reactivating active watchpoint %d\n", | |||||
__func__, i); | |||||
set_watchpoint(wp->gva, wp->type, wp->bytes, i); | |||||
} | |||||
} | |||||
rebuild_avail_watchpoints(); | |||||
} else if (wp->state == WATCH_ACTIVE) { | |||||
vm_get_register( | |||||
ctx, vcpu, VM_REG_GUEST_DR0 + dbreg_num, &dbreg_val); | |||||
/* Guest started using an occupied DB reg, | |||||
* remove watchpoint */ | |||||
if (dbreg_val != 0) { | |||||
debug("%s: evicting active watchpoint %d\n", __func__, | |||||
dbreg_num); | |||||
clear_watchpoint( | |||||
dbreg_num, GDB_WATCHPOINT_CLEAR_NOSKIP, false); | |||||
wp->state = WATCH_EVICTED; | |||||
watch_stats.no_evicted++; | |||||
/* Mark watchpoint as in-use */ | |||||
GDB_ALLOC_WATCHPOINT(dbreg_num); | |||||
} else { | |||||
debug( | |||||
"%s: dr7 write: reactivating active watchpoint %d\n", | |||||
__func__, dbreg_num); | |||||
set_watchpoint(wp->gva, wp->type, wp->bytes, dbreg_num); | |||||
} | |||||
// TODO: figure out how to notify remote gdb if a | |||||
// watchpoint cannot be migrated | |||||
} else { | |||||
vm_get_register( | |||||
ctx, vcpu, VM_REG_GUEST_DR0 + dbreg_num, &dbreg_val); | |||||
if (dbreg_val != 0) { | |||||
/* Mark watchpoint as in-use */ | |||||
GDB_ALLOC_WATCHPOINT(dbreg_num); | |||||
} | |||||
} | |||||
/* Try to migrate any evicted watchpoints */ | |||||
for (int i = 0; i < GDB_WATCHPOINT_MAX; i++) { | |||||
if (watch_stats.watchpoints[i].state == WATCH_EVICTED) { | |||||
error = migrate_watchpoint(&watch_stats.watchpoints[i]); | |||||
debug("%s: %s migrating watchpoint %d\n", __func__, | |||||
(error != -1 ? "succeeded" : "failed"), i); | |||||
if (error) { | |||||
break; | |||||
} | |||||
} | |||||
} | |||||
pthread_mutex_unlock(&gdb_lock); | |||||
}; | |||||
/* | |||||
* A general handler for VM_EXITCODE_DB. | |||||
* Handles RFLAGS.TF exits on AMD hosts and HW watchpoints. | |||||
*/ | |||||
void | |||||
gdb_cpu_debug(int vcpu, struct vm_exit *vmexit) | |||||
{ | |||||
if (!gdb_active) | |||||
return; | |||||
/* RFLAGS.TF exit? */ | |||||
if (vmexit->u.dbg.trace_trap) { | |||||
_gdb_cpu_step(vcpu); | |||||
} else if (vmexit->u.dbg.drx_access != -1) { | |||||
if (vmexit->u.dbg.gpr != -1) { | |||||
handle_drx_read(vcpu, vmexit); | |||||
} else { | |||||
handle_drx_write(vcpu, vmexit); | |||||
} | |||||
} else if (vmexit->u.dbg.watchpoints) { | |||||
/* A watchpoint was triggered */ | |||||
handle_watchpoint_hit(vcpu, vmexit->u.dbg.watchpoints); | |||||
} | |||||
} | |||||
/* | |||||
* Handler for VM_EXITCODE_DEBUG used to suspend a vCPU when the guest | |||||
* has been suspended due to an event on different vCPU or in response | |||||
* to a guest-wide suspend such as Ctrl-C or the stop on attach. | |||||
*/ | |||||
void | |||||
gdb_cpu_suspend(int vcpu) | |||||
{ | |||||
pthread_mutex_lock(&gdb_lock); | |||||
_gdb_cpu_suspend(vcpu, true); | |||||
gdb_cpu_resume(vcpu); | |||||
pthread_mutex_unlock(&gdb_lock); | |||||
} | |||||
/* | |||||
* Handler for VM_EXITCODE_MTRAP reported when a vCPU single-steps via | |||||
* the VT-x-specific MTRAP exit. | |||||
*/ | |||||
void | |||||
gdb_cpu_mtrap(int vcpu) | |||||
{ | |||||
if (!gdb_active) | |||||
return; | |||||
_gdb_cpu_step(vcpu); | |||||
} | |||||
static struct breakpoint * | static struct breakpoint * | ||||
find_breakpoint(uint64_t gpa) | find_breakpoint(uint64_t gpa) | ||||
{ | { | ||||
struct breakpoint *bp; | struct breakpoint *bp; | ||||
TAILQ_FOREACH(bp, &breakpoints, link) { | TAILQ_FOREACH (bp, &breakpoints, link) { | ||||
if (bp->gpa == gpa) | if (bp->gpa == gpa) | ||||
return (bp); | return (bp); | ||||
} | } | ||||
return (NULL); | return (NULL); | ||||
} | } | ||||
void | void | ||||
gdb_cpu_breakpoint(int vcpu, struct vm_exit *vmexit) | gdb_cpu_breakpoint(int vcpu, struct vm_exit *vmexit) | ||||
Show All 11 Lines | gdb_cpu_breakpoint(int vcpu, struct vm_exit *vmexit) | ||||
error = guest_vaddr2paddr(vcpu, vmexit->rip, &gpa); | error = guest_vaddr2paddr(vcpu, vmexit->rip, &gpa); | ||||
assert(error == 1); | assert(error == 1); | ||||
bp = find_breakpoint(gpa); | bp = find_breakpoint(gpa); | ||||
if (bp != NULL) { | if (bp != NULL) { | ||||
vs = &vcpu_state[vcpu]; | vs = &vcpu_state[vcpu]; | ||||
assert(vs->stepping == false); | assert(vs->stepping == false); | ||||
assert(vs->stepped == false); | assert(vs->stepped == false); | ||||
assert(vs->hit_swbreak == false); | assert(vs->hit_swbreak == false); | ||||
assert(vs->hit_watch == false); | |||||
vs->hit_swbreak = true; | vs->hit_swbreak = true; | ||||
vm_set_register(ctx, vcpu, VM_REG_GUEST_RIP, vmexit->rip); | vm_set_register(ctx, vcpu, VM_REG_GUEST_RIP, vmexit->rip); | ||||
for (;;) { | for (;;) { | ||||
if (stopped_vcpu == -1) { | if (stopped_vcpu == -1) { | ||||
debug("$vCPU %d reporting breakpoint at rip %#lx\n", vcpu, | debug( | ||||
vmexit->rip); | "$vCPU %d reporting breakpoint at rip %#lx\n", | ||||
vcpu, vmexit->rip); | |||||
stopped_vcpu = vcpu; | stopped_vcpu = vcpu; | ||||
gdb_suspend_vcpus(); | gdb_suspend_vcpus(); | ||||
} | } | ||||
_gdb_cpu_suspend(vcpu, true); | _gdb_cpu_suspend(vcpu, true); | ||||
if (!vs->hit_swbreak) { | if (!vs->hit_swbreak) { | ||||
/* Breakpoint reported. */ | /* Breakpoint reported. */ | ||||
break; | break; | ||||
} | } | ||||
Show All 15 Lines | if (bp != NULL) { | ||||
assert(error == 0); | assert(error == 0); | ||||
} | } | ||||
pthread_mutex_unlock(&gdb_lock); | pthread_mutex_unlock(&gdb_lock); | ||||
} | } | ||||
static bool | static bool | ||||
gdb_step_vcpu(int vcpu) | gdb_step_vcpu(int vcpu) | ||||
{ | { | ||||
int error, val; | int error; | ||||
debug("$vCPU %d step\n", vcpu); | debug("$vCPU %d step\n", vcpu); | ||||
error = vm_get_capability(ctx, vcpu, VM_CAP_MTRAP_EXIT, &val); | error = _gdb_check_step(vcpu); | ||||
if (error < 0) | if (error < 0) { | ||||
return (false); | return (false); | ||||
} | |||||
discard_stop(); | discard_stop(); | ||||
vcpu_state[vcpu].stepping = true; | vcpu_state[vcpu].stepping = true; | ||||
vm_resume_cpu(ctx, vcpu); | vm_resume_cpu(ctx, vcpu); | ||||
CPU_CLR(vcpu, &vcpus_suspended); | CPU_CLR(vcpu, &vcpus_suspended); | ||||
pthread_cond_broadcast(&idle_vcpus); | pthread_cond_broadcast(&idle_vcpus); | ||||
return (true); | return (true); | ||||
} | } | ||||
▲ Show 20 Lines • Show All 282 Lines • ▼ Show 20 Lines | TAILQ_FOREACH_SAFE(bp, &breakpoints, link, nbp) { | ||||
TAILQ_REMOVE(&breakpoints, bp, link); | TAILQ_REMOVE(&breakpoints, bp, link); | ||||
free(bp); | free(bp); | ||||
} | } | ||||
TAILQ_INIT(&breakpoints); | TAILQ_INIT(&breakpoints); | ||||
set_breakpoint_caps(false); | set_breakpoint_caps(false); | ||||
} | } | ||||
static void | static void | ||||
remove_all_hw_watchpoints(void) | |||||
{ | |||||
for (int i = 0; i < GDB_WATCHPOINT_MAX; i++) { | |||||
if (watch_stats.watchpoints[i].state == WATCH_ACTIVE) { | |||||
clear_watchpoint(i, GDB_WATCHPOINT_CLEAR_NOSKIP, true); | |||||
} | |||||
} | |||||
set_dbexit_caps(false); | |||||
set_dbreg_exit_caps(false); | |||||
} | |||||
static void | |||||
update_watchpoint(uint64_t gva, int type, int bytes, int insert) | |||||
{ | |||||
struct watchpoint *wp; | |||||
int error; | |||||
if (!insert && watch_stats.no_active == 0) { | |||||
send_error(EINVAL); | |||||
return; | |||||
} | |||||
if (insert) { | |||||
/* | |||||
* No watchpoints are active - fetch and update | |||||
* watchpoint stats, enable dbreg and db exits. | |||||
*/ | |||||
if (watch_stats.no_active == 0 && watch_stats.no_evicted == 0) { | |||||
/* Activate debug exception vmexits */ | |||||
if (!set_dbexit_caps(true) || | |||||
!set_dbreg_exit_caps(true)) { | |||||
send_error(EINVAL); | |||||
return; | |||||
} | |||||
init_watchpoint_metadata(); | |||||
} | |||||
if (watch_stats.no_active == GDB_WATCHPOINT_MAX || | |||||
!GDB_HAS_AVAIL_WATCHPOINT()) { | |||||
error = (ENOSPC); | |||||
goto err; | |||||
} | |||||
wp = find_watchpoint(gva); | |||||
if (!wp) { | |||||
int dbreg_num = GDB_FIND_WATCHPOINT(); | |||||
assert(dbreg_num >= 0); | |||||
debug("Allocated watchpoint %d\n", dbreg_num); | |||||
error = set_watchpoint(gva, type, bytes, dbreg_num); | |||||
if (error) { | |||||
goto err; | |||||
} | |||||
} | |||||
} else { | |||||
wp = find_watchpoint(gva); | |||||
if (wp) { | |||||
int watchnum = wp - &watch_stats.watchpoints[0]; | |||||
debug("Removing watchpoint %d\n", watchnum); | |||||
clear_watchpoint( | |||||
watchnum, GDB_WATCHPOINT_CLEAR_NOSKIP, true); | |||||
/* If the last watchpoint was removed and none are | |||||
* evicted, disable db and dbreg vmexits */ | |||||
if (watch_stats.no_active == 0 && | |||||
watch_stats.no_evicted == 0) { | |||||
set_dbexit_caps(false); | |||||
set_dbreg_exit_caps(false); | |||||
} | |||||
} | |||||
} | |||||
send_ok(); | |||||
return; | |||||
err: | |||||
if (watch_stats.no_active == 0 && watch_stats.no_evicted == 0) { | |||||
set_dbexit_caps(false); | |||||
set_dbreg_exit_caps(false); | |||||
} | |||||
send_error(error); | |||||
return; | |||||
} | |||||
static void | |||||
update_sw_breakpoint(uint64_t gva, int kind, bool insert) | update_sw_breakpoint(uint64_t gva, int kind, bool insert) | ||||
{ | { | ||||
struct breakpoint *bp; | struct breakpoint *bp; | ||||
uint64_t gpa; | uint64_t gpa; | ||||
uint8_t *cp; | uint8_t *cp; | ||||
int error; | int error; | ||||
if (kind != 1) { | if (kind != 1) { | ||||
▲ Show 20 Lines • Show All 103 Lines • ▼ Show 20 Lines | if (cp != NULL) { | ||||
send_empty_response(); | send_empty_response(); | ||||
return; | return; | ||||
} | } | ||||
kind = parse_integer(data, len); | kind = parse_integer(data, len); | ||||
data += len; | data += len; | ||||
len = 0; | len = 0; | ||||
switch (type) { | switch (type) { | ||||
case 0: | case GDB_SOFTWARE_BPT: | ||||
update_sw_breakpoint(gva, kind, insert); | update_sw_breakpoint(gva, kind, insert); | ||||
break; | break; | ||||
case GDB_WATCHPOINT_TYPE_WRITE: | |||||
case GDB_WATCHPOINT_TYPE_READ: | |||||
case GDB_WATCHPOINT_TYPE_ACCESS: | |||||
update_watchpoint(gva, type, kind, insert); | |||||
break; | |||||
default: | default: | ||||
send_empty_response(); | send_empty_response(); | ||||
break; | break; | ||||
} | } | ||||
} | } | ||||
static bool | static bool | ||||
command_equals(const uint8_t *data, size_t len, const char *cmd) | command_equals(const uint8_t *data, size_t len, const char *cmd) | ||||
▲ Show 20 Lines • Show All 519 Lines • ▼ Show 20 Lines | if (wait) { | ||||
* Set vcpu 0 in vcpus_suspended. This will trigger the | * Set vcpu 0 in vcpus_suspended. This will trigger the | ||||
* logic in gdb_cpu_add() to suspend the first vcpu before | * logic in gdb_cpu_add() to suspend the first vcpu before | ||||
* it starts execution. The vcpu will remain suspended | * it starts execution. The vcpu will remain suspended | ||||
* until a debugger connects. | * until a debugger connects. | ||||
*/ | */ | ||||
CPU_SET(0, &vcpus_suspended); | CPU_SET(0, &vcpus_suspended); | ||||
stopped_vcpu = 0; | stopped_vcpu = 0; | ||||
} | } | ||||
memset(&watch_stats, 0, sizeof(watch_stats)); | |||||
flags = fcntl(s, F_GETFL); | flags = fcntl(s, F_GETFL); | ||||
if (fcntl(s, F_SETFL, flags | O_NONBLOCK) == -1) | if (fcntl(s, F_SETFL, flags | O_NONBLOCK) == -1) | ||||
err(1, "Failed to mark gdb socket non-blocking"); | err(1, "Failed to mark gdb socket non-blocking"); | ||||
#ifndef WITHOUT_CAPSICUM | #ifndef WITHOUT_CAPSICUM | ||||
limit_gdb_socket(s); | limit_gdb_socket(s); | ||||
#endif | #endif | ||||
mevent_add(s, EVF_READ, new_connection, NULL); | mevent_add(s, EVF_READ, new_connection, NULL); | ||||
gdb_active = true; | gdb_active = true; | ||||
freeaddrinfo(gdbaddr); | freeaddrinfo(gdbaddr); | ||||
free(sport); | free(sport); | ||||
} | } |