1 // Copyright 2022 Akira Moroo.
2 // Portions Copyright 2020 The Chromium OS Authors. All rights reserved.
3 // Use of this source code is governed by a BSD-style license that can be
4 // found in the LICENSE-BSD-3-Clause file.
5 //
6 // SPDX-License-Identifier: BSD-3-Clause
7
8 use std::os::unix::net::UnixListener;
9 use std::sync::mpsc;
10
11 use gdbstub::arch::Arch;
12 use gdbstub::common::{Signal, Tid};
13 use gdbstub::conn::{Connection, ConnectionExt};
14 use gdbstub::stub::{run_blocking, DisconnectReason, MultiThreadStopReason};
15 use gdbstub::target::ext::base::multithread::{
16 MultiThreadBase, MultiThreadResume, MultiThreadResumeOps, MultiThreadSingleStep,
17 MultiThreadSingleStepOps,
18 };
19 use gdbstub::target::ext::base::BaseOps;
20 use gdbstub::target::ext::breakpoints::{
21 Breakpoints, BreakpointsOps, HwBreakpoint, HwBreakpointOps,
22 };
23 use gdbstub::target::{Target, TargetError, TargetResult};
24 #[cfg(target_arch = "aarch64")]
25 use gdbstub_arch::aarch64::reg::AArch64CoreRegs as CoreRegs;
26 #[cfg(target_arch = "aarch64")]
27 use gdbstub_arch::aarch64::AArch64 as GdbArch;
28 #[cfg(target_arch = "x86_64")]
29 use gdbstub_arch::x86::reg::X86_64CoreRegs as CoreRegs;
30 #[cfg(target_arch = "x86_64")]
31 use gdbstub_arch::x86::X86_64_SSE as GdbArch;
32 use thiserror::Error;
33 use vm_memory::{GuestAddress, GuestMemoryAtomic, GuestMemoryError};
34
35 use crate::GuestMemoryMmap;
36
37 type ArchUsize = u64;
38
39 #[derive(Error, Debug)]
40 pub enum DebuggableError {
41 #[error("Setting debug failed")]
42 SetDebug(#[source] hypervisor::HypervisorCpuError),
43 #[error("Pausing failed")]
44 Pause(#[source] vm_migration::MigratableError),
45 #[error("Resuming failed")]
46 Resume(#[source] vm_migration::MigratableError),
47 #[error("Reading registers failed")]
48 ReadRegs(#[source] crate::cpu::Error),
49 #[error("Writing registers failed")]
50 WriteRegs(#[source] crate::cpu::Error),
51 #[error("Reading memory failed")]
52 ReadMem(#[source] GuestMemoryError),
53 #[error("Writing memory failed")]
54 WriteMem(#[source] GuestMemoryError),
55 #[error("Translating GVA failed")]
56 TranslateGva(#[source] crate::cpu::Error),
57 #[error("The lock is poisened")]
58 PoisonedState,
59 }
60
61 pub trait Debuggable: vm_migration::Pausable {
set_guest_debug( &self, cpu_id: usize, addrs: &[GuestAddress], singlestep: bool, ) -> Result<(), DebuggableError>62 fn set_guest_debug(
63 &self,
64 cpu_id: usize,
65 addrs: &[GuestAddress],
66 singlestep: bool,
67 ) -> Result<(), DebuggableError>;
debug_pause(&mut self) -> std::result::Result<(), DebuggableError>68 fn debug_pause(&mut self) -> std::result::Result<(), DebuggableError>;
debug_resume(&mut self) -> std::result::Result<(), DebuggableError>69 fn debug_resume(&mut self) -> std::result::Result<(), DebuggableError>;
read_regs(&self, cpu_id: usize) -> std::result::Result<CoreRegs, DebuggableError>70 fn read_regs(&self, cpu_id: usize) -> std::result::Result<CoreRegs, DebuggableError>;
write_regs( &self, cpu_id: usize, regs: &CoreRegs, ) -> std::result::Result<(), DebuggableError>71 fn write_regs(
72 &self,
73 cpu_id: usize,
74 regs: &CoreRegs,
75 ) -> std::result::Result<(), DebuggableError>;
read_mem( &self, guest_memory: &GuestMemoryAtomic<GuestMemoryMmap>, cpu_id: usize, vaddr: GuestAddress, len: usize, ) -> std::result::Result<Vec<u8>, DebuggableError>76 fn read_mem(
77 &self,
78 guest_memory: &GuestMemoryAtomic<GuestMemoryMmap>,
79 cpu_id: usize,
80 vaddr: GuestAddress,
81 len: usize,
82 ) -> std::result::Result<Vec<u8>, DebuggableError>;
write_mem( &self, guest_memory: &GuestMemoryAtomic<GuestMemoryMmap>, cpu_id: usize, vaddr: &GuestAddress, data: &[u8], ) -> std::result::Result<(), DebuggableError>83 fn write_mem(
84 &self,
85 guest_memory: &GuestMemoryAtomic<GuestMemoryMmap>,
86 cpu_id: usize,
87 vaddr: &GuestAddress,
88 data: &[u8],
89 ) -> std::result::Result<(), DebuggableError>;
active_vcpus(&self) -> usize90 fn active_vcpus(&self) -> usize;
91 }
92
93 #[derive(Error, Debug)]
94 pub enum Error {
95 #[error("VM failed")]
96 Vm(#[source] crate::vm::Error),
97 #[error("GDB request failed")]
98 GdbRequest,
99 #[error("GDB couldn't be notified")]
100 GdbResponseNotify(#[source] std::io::Error),
101 #[error("GDB response failed")]
102 GdbResponse(#[source] mpsc::RecvError),
103 #[error("GDB response timeout")]
104 GdbResponseTimeout(#[source] mpsc::RecvTimeoutError),
105 }
106 type GdbResult<T> = std::result::Result<T, Error>;
107
108 #[derive(Debug)]
109 pub struct GdbRequest {
110 pub sender: mpsc::Sender<GdbResponse>,
111 pub payload: GdbRequestPayload,
112 pub cpu_id: usize,
113 }
114
115 #[derive(Debug)]
116 pub enum GdbRequestPayload {
117 ReadRegs,
118 WriteRegs(Box<CoreRegs>),
119 ReadMem(GuestAddress, usize),
120 WriteMem(GuestAddress, Vec<u8>),
121 Pause,
122 Resume,
123 SetSingleStep(bool),
124 SetHwBreakPoint(Vec<GuestAddress>),
125 ActiveVcpus,
126 }
127
128 pub type GdbResponse = std::result::Result<GdbResponsePayload, Error>;
129
130 #[derive(Debug)]
131 pub enum GdbResponsePayload {
132 CommandComplete,
133 RegValues(Box<CoreRegs>),
134 MemoryRegion(Vec<u8>),
135 ActiveVcpus(usize),
136 }
137
138 pub struct GdbStub {
139 gdb_sender: mpsc::Sender<GdbRequest>,
140 gdb_event: vmm_sys_util::eventfd::EventFd,
141 vm_event: vmm_sys_util::eventfd::EventFd,
142 hw_breakpoints: Vec<GuestAddress>,
143 single_step: bool,
144 }
145
146 impl GdbStub {
new( gdb_sender: mpsc::Sender<GdbRequest>, gdb_event: vmm_sys_util::eventfd::EventFd, vm_event: vmm_sys_util::eventfd::EventFd, hw_breakpoints: usize, ) -> Self147 pub fn new(
148 gdb_sender: mpsc::Sender<GdbRequest>,
149 gdb_event: vmm_sys_util::eventfd::EventFd,
150 vm_event: vmm_sys_util::eventfd::EventFd,
151 hw_breakpoints: usize,
152 ) -> Self {
153 Self {
154 gdb_sender,
155 gdb_event,
156 vm_event,
157 hw_breakpoints: Vec::with_capacity(hw_breakpoints),
158 single_step: false,
159 }
160 }
161
vm_request( &self, payload: GdbRequestPayload, cpu_id: usize, ) -> GdbResult<GdbResponsePayload>162 fn vm_request(
163 &self,
164 payload: GdbRequestPayload,
165 cpu_id: usize,
166 ) -> GdbResult<GdbResponsePayload> {
167 let (response_sender, response_receiver) = std::sync::mpsc::channel();
168 let request = GdbRequest {
169 sender: response_sender,
170 payload,
171 cpu_id,
172 };
173 self.gdb_sender
174 .send(request)
175 .map_err(|_| Error::GdbRequest)?;
176 self.gdb_event.write(1).map_err(Error::GdbResponseNotify)?;
177 let res = response_receiver.recv().map_err(Error::GdbResponse)??;
178 Ok(res)
179 }
180 }
181
182 impl Target for GdbStub {
183 type Arch = GdbArch;
184 type Error = String;
185
186 #[inline(always)]
base_ops(&mut self) -> BaseOps<'_, Self::Arch, Self::Error>187 fn base_ops(&mut self) -> BaseOps<'_, Self::Arch, Self::Error> {
188 BaseOps::MultiThread(self)
189 }
190
191 #[inline(always)]
support_breakpoints(&mut self) -> Option<BreakpointsOps<'_, Self>>192 fn support_breakpoints(&mut self) -> Option<BreakpointsOps<'_, Self>> {
193 Some(self)
194 }
195
196 #[inline(always)]
guard_rail_implicit_sw_breakpoints(&self) -> bool197 fn guard_rail_implicit_sw_breakpoints(&self) -> bool {
198 true
199 }
200 }
201
tid_to_cpuid(tid: Tid) -> usize202 fn tid_to_cpuid(tid: Tid) -> usize {
203 tid.get() - 1
204 }
205
cpuid_to_tid(cpu_id: usize) -> Tid206 fn cpuid_to_tid(cpu_id: usize) -> Tid {
207 Tid::new(get_raw_tid(cpu_id)).unwrap()
208 }
209
get_raw_tid(cpu_id: usize) -> usize210 pub fn get_raw_tid(cpu_id: usize) -> usize {
211 cpu_id + 1
212 }
213
214 impl MultiThreadBase for GdbStub {
read_registers( &mut self, regs: &mut <Self::Arch as Arch>::Registers, tid: Tid, ) -> TargetResult<(), Self>215 fn read_registers(
216 &mut self,
217 regs: &mut <Self::Arch as Arch>::Registers,
218 tid: Tid,
219 ) -> TargetResult<(), Self> {
220 match self.vm_request(GdbRequestPayload::ReadRegs, tid_to_cpuid(tid)) {
221 Ok(GdbResponsePayload::RegValues(r)) => {
222 *regs = *r;
223 Ok(())
224 }
225 Ok(s) => {
226 error!("Unexpected response for ReadRegs: {:?}", s);
227 Err(TargetError::NonFatal)
228 }
229 Err(e) => {
230 error!("Failed to request ReadRegs: {:?}", e);
231 Err(TargetError::NonFatal)
232 }
233 }
234 }
235
write_registers( &mut self, regs: &<Self::Arch as Arch>::Registers, tid: Tid, ) -> TargetResult<(), Self>236 fn write_registers(
237 &mut self,
238 regs: &<Self::Arch as Arch>::Registers,
239 tid: Tid,
240 ) -> TargetResult<(), Self> {
241 match self.vm_request(
242 GdbRequestPayload::WriteRegs(Box::new(regs.clone())),
243 tid_to_cpuid(tid),
244 ) {
245 Ok(_) => Ok(()),
246 Err(e) => {
247 error!("Failed to request WriteRegs: {:?}", e);
248 Err(TargetError::NonFatal)
249 }
250 }
251 }
252
read_addrs( &mut self, start_addr: <Self::Arch as Arch>::Usize, data: &mut [u8], tid: Tid, ) -> TargetResult<usize, Self>253 fn read_addrs(
254 &mut self,
255 start_addr: <Self::Arch as Arch>::Usize,
256 data: &mut [u8],
257 tid: Tid,
258 ) -> TargetResult<usize, Self> {
259 match self.vm_request(
260 GdbRequestPayload::ReadMem(GuestAddress(start_addr), data.len()),
261 tid_to_cpuid(tid),
262 ) {
263 Ok(GdbResponsePayload::MemoryRegion(r)) => {
264 for (dst, v) in data.iter_mut().zip(r.iter()) {
265 *dst = *v;
266 }
267 Ok(std::cmp::min(data.len(), r.len()))
268 }
269 Ok(s) => {
270 error!("Unexpected response for ReadMem: {:?}", s);
271 Err(TargetError::NonFatal)
272 }
273 Err(e) => {
274 error!("Failed to request ReadMem: {:?}", e);
275 Err(TargetError::NonFatal)
276 }
277 }
278 }
279
write_addrs( &mut self, start_addr: <Self::Arch as Arch>::Usize, data: &[u8], tid: Tid, ) -> TargetResult<(), Self>280 fn write_addrs(
281 &mut self,
282 start_addr: <Self::Arch as Arch>::Usize,
283 data: &[u8],
284 tid: Tid,
285 ) -> TargetResult<(), Self> {
286 match self.vm_request(
287 GdbRequestPayload::WriteMem(GuestAddress(start_addr), data.to_owned()),
288 tid_to_cpuid(tid),
289 ) {
290 Ok(_) => Ok(()),
291 Err(e) => {
292 error!("Failed to request WriteMem: {:?}", e);
293 Err(TargetError::NonFatal)
294 }
295 }
296 }
297
list_active_threads( &mut self, thread_is_active: &mut dyn FnMut(Tid), ) -> Result<(), Self::Error>298 fn list_active_threads(
299 &mut self,
300 thread_is_active: &mut dyn FnMut(Tid),
301 ) -> Result<(), Self::Error> {
302 match self.vm_request(GdbRequestPayload::ActiveVcpus, 0) {
303 Ok(GdbResponsePayload::ActiveVcpus(active_vcpus)) => {
304 (0..active_vcpus).for_each(|cpu_id| {
305 thread_is_active(cpuid_to_tid(cpu_id));
306 });
307 Ok(())
308 }
309 Ok(s) => Err(format!("Unexpected response for ActiveVcpus: {s:?}")),
310 Err(e) => Err(format!("Failed to request ActiveVcpus: {e:?}")),
311 }
312 }
313
314 #[inline(always)]
support_resume(&mut self) -> Option<MultiThreadResumeOps<'_, Self>>315 fn support_resume(&mut self) -> Option<MultiThreadResumeOps<'_, Self>> {
316 Some(self)
317 }
318 }
319
320 impl MultiThreadResume for GdbStub {
resume(&mut self) -> Result<(), Self::Error>321 fn resume(&mut self) -> Result<(), Self::Error> {
322 match self.vm_request(GdbRequestPayload::Resume, 0) {
323 Ok(_) => Ok(()),
324 Err(e) => Err(format!("Failed to resume the target: {e:?}")),
325 }
326 }
327
clear_resume_actions(&mut self) -> Result<(), Self::Error>328 fn clear_resume_actions(&mut self) -> Result<(), Self::Error> {
329 if self.single_step {
330 match self.vm_request(GdbRequestPayload::SetSingleStep(false), 0) {
331 Ok(_) => {
332 self.single_step = false;
333 }
334 Err(e) => {
335 return Err(format!("Failed to request SetSingleStep: {e:?}"));
336 }
337 }
338 }
339 Ok(())
340 }
341
set_resume_action_continue( &mut self, tid: Tid, signal: Option<Signal>, ) -> Result<(), Self::Error>342 fn set_resume_action_continue(
343 &mut self,
344 tid: Tid,
345 signal: Option<Signal>,
346 ) -> Result<(), Self::Error> {
347 if signal.is_some() {
348 return Err("no support for continuing with signal".to_owned());
349 }
350 match self.vm_request(GdbRequestPayload::Resume, tid_to_cpuid(tid)) {
351 Ok(_) => Ok(()),
352 Err(e) => Err(format!("Failed to resume the target: {e:?}")),
353 }
354 }
355
356 #[inline(always)]
support_single_step(&mut self) -> Option<MultiThreadSingleStepOps<'_, Self>>357 fn support_single_step(&mut self) -> Option<MultiThreadSingleStepOps<'_, Self>> {
358 Some(self)
359 }
360 }
361
362 impl MultiThreadSingleStep for GdbStub {
set_resume_action_step( &mut self, tid: Tid, signal: Option<Signal>, ) -> Result<(), Self::Error>363 fn set_resume_action_step(
364 &mut self,
365 tid: Tid,
366 signal: Option<Signal>,
367 ) -> Result<(), Self::Error> {
368 if signal.is_some() {
369 return Err("no support for stepping with signal".to_owned());
370 }
371
372 if !self.single_step {
373 match self.vm_request(GdbRequestPayload::SetSingleStep(true), tid_to_cpuid(tid)) {
374 Ok(_) => {
375 self.single_step = true;
376 }
377 Err(e) => {
378 return Err(format!("Failed to request SetSingleStep: {e:?}"));
379 }
380 }
381 }
382 match self.vm_request(GdbRequestPayload::Resume, tid_to_cpuid(tid)) {
383 Ok(_) => Ok(()),
384 Err(e) => Err(format!("Failed to resume the target: {e:?}")),
385 }
386 }
387 }
388
389 impl Breakpoints for GdbStub {
390 #[inline(always)]
support_hw_breakpoint(&mut self) -> Option<HwBreakpointOps<'_, Self>>391 fn support_hw_breakpoint(&mut self) -> Option<HwBreakpointOps<'_, Self>> {
392 Some(self)
393 }
394 }
395
396 impl HwBreakpoint for GdbStub {
add_hw_breakpoint( &mut self, addr: <Self::Arch as Arch>::Usize, _kind: <Self::Arch as Arch>::BreakpointKind, ) -> TargetResult<bool, Self>397 fn add_hw_breakpoint(
398 &mut self,
399 addr: <Self::Arch as Arch>::Usize,
400 _kind: <Self::Arch as Arch>::BreakpointKind,
401 ) -> TargetResult<bool, Self> {
402 // If the HW breakpoints reach the limit, no more can be added.
403 if self.hw_breakpoints.len() >= self.hw_breakpoints.capacity() {
404 error!(
405 "Not allowed to set more than {} HW breakpoints",
406 self.hw_breakpoints.capacity()
407 );
408 return Ok(false);
409 }
410
411 self.hw_breakpoints.push(GuestAddress(addr));
412
413 let payload = GdbRequestPayload::SetHwBreakPoint(self.hw_breakpoints.clone());
414 match self.vm_request(payload, 0) {
415 Ok(_) => Ok(true),
416 Err(e) => {
417 error!("Failed to request SetHwBreakPoint: {:?}", e);
418 Err(TargetError::NonFatal)
419 }
420 }
421 }
remove_hw_breakpoint( &mut self, addr: <Self::Arch as Arch>::Usize, _kind: <Self::Arch as Arch>::BreakpointKind, ) -> TargetResult<bool, Self>422 fn remove_hw_breakpoint(
423 &mut self,
424 addr: <Self::Arch as Arch>::Usize,
425 _kind: <Self::Arch as Arch>::BreakpointKind,
426 ) -> TargetResult<bool, Self> {
427 match self.hw_breakpoints.iter().position(|&b| b.0 == addr) {
428 None => return Ok(false),
429 Some(pos) => self.hw_breakpoints.remove(pos),
430 };
431
432 let payload = GdbRequestPayload::SetHwBreakPoint(self.hw_breakpoints.clone());
433 match self.vm_request(payload, 0) {
434 Ok(_) => Ok(true),
435 Err(e) => {
436 error!("Failed to request SetHwBreakPoint: {:?}", e);
437 Err(TargetError::NonFatal)
438 }
439 }
440 }
441 }
442
443 enum GdbEventLoop {}
444
445 impl run_blocking::BlockingEventLoop for GdbEventLoop {
446 type Target = GdbStub;
447 type Connection = Box<dyn ConnectionExt<Error = std::io::Error>>;
448 type StopReason = MultiThreadStopReason<ArchUsize>;
449
wait_for_stop_reason( target: &mut Self::Target, conn: &mut Self::Connection, ) -> Result< run_blocking::Event<Self::StopReason>, run_blocking::WaitForStopReasonError< <Self::Target as Target>::Error, <Self::Connection as Connection>::Error, >, >450 fn wait_for_stop_reason(
451 target: &mut Self::Target,
452 conn: &mut Self::Connection,
453 ) -> Result<
454 run_blocking::Event<Self::StopReason>,
455 run_blocking::WaitForStopReasonError<
456 <Self::Target as Target>::Error,
457 <Self::Connection as Connection>::Error,
458 >,
459 > {
460 // Polling
461 loop {
462 // This read is non-blocking.
463 match target.vm_event.read() {
464 Ok(tid) => {
465 target
466 .vm_request(GdbRequestPayload::Pause, 0)
467 .map_err(|_| {
468 run_blocking::WaitForStopReasonError::Target(
469 "Failed to pause VM".to_owned(),
470 )
471 })?;
472 let stop_reason = if target.single_step {
473 MultiThreadStopReason::DoneStep
474 } else {
475 MultiThreadStopReason::HwBreak(Tid::new(tid as usize).unwrap())
476 };
477 return Ok(run_blocking::Event::TargetStopped(stop_reason));
478 }
479 Err(e) => {
480 if e.kind() != std::io::ErrorKind::WouldBlock {
481 return Err(run_blocking::WaitForStopReasonError::Connection(e));
482 }
483 }
484 }
485
486 if conn.peek().map(|b| b.is_some()).unwrap_or(true) {
487 let byte = conn
488 .read()
489 .map_err(run_blocking::WaitForStopReasonError::Connection)?;
490 return Ok(run_blocking::Event::IncomingData(byte));
491 }
492 }
493 }
494
on_interrupt( target: &mut Self::Target, ) -> Result<Option<Self::StopReason>, <Self::Target as Target>::Error>495 fn on_interrupt(
496 target: &mut Self::Target,
497 ) -> Result<Option<Self::StopReason>, <Self::Target as Target>::Error> {
498 target
499 .vm_request(GdbRequestPayload::Pause, 0)
500 .map_err(|e| {
501 error!("Failed to pause the target: {:?}", e);
502 "Failed to pause the target"
503 })?;
504 Ok(Some(MultiThreadStopReason::Signal(Signal::SIGINT)))
505 }
506 }
507
gdb_thread(mut gdbstub: GdbStub, path: &std::path::Path)508 pub fn gdb_thread(mut gdbstub: GdbStub, path: &std::path::Path) {
509 let listener = match UnixListener::bind(path) {
510 Ok(s) => s,
511 Err(e) => {
512 error!("Failed to create a Unix domain socket listener: {}", e);
513 return;
514 }
515 };
516 info!("Waiting for a GDB connection on {}...", path.display());
517
518 let (stream, addr) = match listener.accept() {
519 Ok(v) => v,
520 Err(e) => {
521 error!("Failed to accept a connection from GDB: {}", e);
522 return;
523 }
524 };
525 info!("GDB connected from {:?}", addr);
526
527 let connection: Box<dyn ConnectionExt<Error = std::io::Error>> = Box::new(stream);
528 let gdb = gdbstub::stub::GdbStub::new(connection);
529
530 match gdb.run_blocking::<GdbEventLoop>(&mut gdbstub) {
531 Ok(disconnect_reason) => match disconnect_reason {
532 DisconnectReason::Disconnect => {
533 info!("GDB client has disconnected. Running...");
534
535 if let Err(e) = gdbstub.vm_request(GdbRequestPayload::SetSingleStep(false), 0) {
536 error!("Failed to disable single step: {:?}", e);
537 }
538
539 if let Err(e) =
540 gdbstub.vm_request(GdbRequestPayload::SetHwBreakPoint(Vec::new()), 0)
541 {
542 error!("Failed to remove breakpoints: {:?}", e);
543 }
544
545 if let Err(e) = gdbstub.vm_request(GdbRequestPayload::Resume, 0) {
546 error!("Failed to resume the VM: {:?}", e);
547 }
548 }
549 _ => {
550 error!("Target exited or terminated");
551 }
552 },
553 Err(e) => {
554 error!("error occurred in GDB session: {}", e);
555 }
556 }
557 }
558