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