xref: /cloud-hypervisor/vmm/src/gdb.rs (revision eea9bcea38e0c5649f444c829f3a4f9c22aa486c)
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 gdbstub::{
9     arch::Arch,
10     common::{Signal, Tid},
11     conn::{Connection, ConnectionExt},
12     stub::{run_blocking, DisconnectReason, MultiThreadStopReason},
13     target::{
14         ext::{
15             base::{
16                 multithread::{
17                     MultiThreadBase, MultiThreadResume, MultiThreadResumeOps,
18                     MultiThreadSingleStep, MultiThreadSingleStepOps,
19                 },
20                 BaseOps,
21             },
22             breakpoints::{Breakpoints, BreakpointsOps, HwBreakpoint, HwBreakpointOps},
23         },
24         Target, TargetError, TargetResult,
25     },
26 };
27 #[cfg(target_arch = "aarch64")]
28 use gdbstub_arch::aarch64::reg::AArch64CoreRegs as CoreRegs;
29 #[cfg(target_arch = "aarch64")]
30 use gdbstub_arch::aarch64::AArch64 as GdbArch;
31 #[cfg(target_arch = "x86_64")]
32 use gdbstub_arch::x86::reg::X86_64CoreRegs as CoreRegs;
33 #[cfg(target_arch = "x86_64")]
34 use gdbstub_arch::x86::X86_64_SSE as GdbArch;
35 use std::{os::unix::net::UnixListener, sync::mpsc};
36 use vm_memory::{GuestAddress, GuestMemoryError};
37 
38 type ArchUsize = u64;
39 
40 #[derive(Debug)]
41 pub enum DebuggableError {
42     SetDebug(hypervisor::HypervisorCpuError),
43     Pause(vm_migration::MigratableError),
44     Resume(vm_migration::MigratableError),
45     ReadRegs(crate::cpu::Error),
46     WriteRegs(crate::cpu::Error),
47     ReadMem(GuestMemoryError),
48     WriteMem(GuestMemoryError),
49     TranslateGva(crate::cpu::Error),
50     PoisonedState,
51 }
52 
53 pub trait Debuggable: vm_migration::Pausable {
54     fn set_guest_debug(
55         &self,
56         cpu_id: usize,
57         addrs: &[GuestAddress],
58         singlestep: bool,
59     ) -> Result<(), DebuggableError>;
60     fn debug_pause(&mut self) -> std::result::Result<(), DebuggableError>;
61     fn debug_resume(&mut self) -> std::result::Result<(), DebuggableError>;
62     fn read_regs(&self, cpu_id: usize) -> std::result::Result<CoreRegs, DebuggableError>;
63     fn write_regs(
64         &self,
65         cpu_id: usize,
66         regs: &CoreRegs,
67     ) -> std::result::Result<(), DebuggableError>;
68     fn read_mem(
69         &self,
70         cpu_id: usize,
71         vaddr: GuestAddress,
72         len: usize,
73     ) -> std::result::Result<Vec<u8>, DebuggableError>;
74     fn write_mem(
75         &self,
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<(), 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(())
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     #[allow(clippy::type_complexity)]
436     fn wait_for_stop_reason(
437         target: &mut Self::Target,
438         conn: &mut Self::Connection,
439     ) -> Result<
440         run_blocking::Event<Self::StopReason>,
441         run_blocking::WaitForStopReasonError<
442             <Self::Target as Target>::Error,
443             <Self::Connection as Connection>::Error,
444         >,
445     > {
446         // Polling
447         loop {
448             // This read is non-blocking.
449             match target.vm_event.read() {
450                 Ok(tid) => {
451                     target
452                         .vm_request(GdbRequestPayload::Pause, 0)
453                         .map_err(|_| {
454                             run_blocking::WaitForStopReasonError::Target(
455                                 "Failed to pause VM".to_owned(),
456                             )
457                         })?;
458                     let stop_reason = if target.single_step {
459                         MultiThreadStopReason::DoneStep
460                     } else {
461                         MultiThreadStopReason::HwBreak(Tid::new(tid as usize).unwrap())
462                     };
463                     return Ok(run_blocking::Event::TargetStopped(stop_reason));
464                 }
465                 Err(e) => {
466                     if e.kind() != std::io::ErrorKind::WouldBlock {
467                         return Err(run_blocking::WaitForStopReasonError::Connection(e));
468                     }
469                 }
470             }
471 
472             if conn.peek().map(|b| b.is_some()).unwrap_or(true) {
473                 let byte = conn
474                     .read()
475                     .map_err(run_blocking::WaitForStopReasonError::Connection)?;
476                 return Ok(run_blocking::Event::IncomingData(byte));
477             }
478         }
479     }
480 
481     fn on_interrupt(
482         target: &mut Self::Target,
483     ) -> Result<Option<Self::StopReason>, <Self::Target as Target>::Error> {
484         target
485             .vm_request(GdbRequestPayload::Pause, 0)
486             .map_err(|e| {
487                 error!("Failed to pause the target: {:?}", e);
488                 "Failed to pause the target"
489             })?;
490         Ok(Some(MultiThreadStopReason::Signal(Signal::SIGINT)))
491     }
492 }
493 
494 pub fn gdb_thread(mut gdbstub: GdbStub, path: &std::path::Path) {
495     let listener = match UnixListener::bind(path) {
496         Ok(s) => s,
497         Err(e) => {
498             error!("Failed to create a Unix domain socket listener: {}", e);
499             return;
500         }
501     };
502     info!("Waiting for a GDB connection on {}...", path.display());
503 
504     let (stream, addr) = match listener.accept() {
505         Ok(v) => v,
506         Err(e) => {
507             error!("Failed to accept a connection from GDB: {}", e);
508             return;
509         }
510     };
511     info!("GDB connected from {:?}", addr);
512 
513     let connection: Box<dyn ConnectionExt<Error = std::io::Error>> = Box::new(stream);
514     let gdb = gdbstub::stub::GdbStub::new(connection);
515 
516     match gdb.run_blocking::<GdbEventLoop>(&mut gdbstub) {
517         Ok(disconnect_reason) => match disconnect_reason {
518             DisconnectReason::Disconnect => {
519                 info!("GDB client has disconnected. Running...");
520 
521                 if let Err(e) = gdbstub.vm_request(GdbRequestPayload::SetSingleStep(false), 0) {
522                     error!("Failed to disable single step: {:?}", e);
523                 }
524 
525                 if let Err(e) =
526                     gdbstub.vm_request(GdbRequestPayload::SetHwBreakPoint(Vec::new()), 0)
527                 {
528                     error!("Failed to remove breakpoints: {:?}", e);
529                 }
530 
531                 if let Err(e) = gdbstub.vm_request(GdbRequestPayload::Resume, 0) {
532                     error!("Failed to resume the VM: {:?}", e);
533                 }
534             }
535             _ => {
536                 error!("Target exited or terminated");
537             }
538         },
539         Err(e) => {
540             error!("error occurred in GDB session: {}", e);
541         }
542     }
543 }
544