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