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