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