1 //! gdbstub `Target` implementation. 2 3 use crate::Debugger; 4 use crate::addr::AddrSpaceLookup; 5 use crate::api; 6 use gdbstub::arch::lldb::{Encoding, Format, Generic, Register}; 7 use gdbstub::common::{Endianness, Pid, Signal, Tid}; 8 use gdbstub::target::Target; 9 use gdbstub::target::TargetError; 10 use gdbstub::target::TargetResult; 11 use gdbstub::target::ext::base::BaseOps; 12 use gdbstub::target::ext::base::multithread::{ 13 MultiThreadBase, MultiThreadResume, MultiThreadResumeOps, MultiThreadSchedulerLocking, 14 MultiThreadSchedulerLockingOps, MultiThreadSingleStep, MultiThreadSingleStepOps, 15 }; 16 use gdbstub::target::ext::base::single_register_access::{ 17 SingleRegisterAccess, SingleRegisterAccessOps, 18 }; 19 use gdbstub::target::ext::breakpoints::{ 20 Breakpoints, BreakpointsOps, SwBreakpoint, SwBreakpointOps, 21 }; 22 use gdbstub::target::ext::host_info::{HostInfo, HostInfoOps, HostInfoResponse}; 23 use gdbstub::target::ext::libraries::{Libraries, LibrariesOps}; 24 use gdbstub::target::ext::lldb_register_info_override::{ 25 Callback, CallbackToken, LldbRegisterInfoOverride, LldbRegisterInfoOverrideOps, 26 }; 27 use gdbstub::target::ext::memory_map::{MemoryMap, MemoryMapOps}; 28 use gdbstub::target::ext::process_info::{ProcessInfo, ProcessInfoOps, ProcessInfoResponse}; 29 use gdbstub::target::ext::wasm::{Wasm, WasmOps}; 30 use gdbstub_arch::wasm::Wasm as WasmArch; 31 use gdbstub_arch::wasm::addr::WasmAddr; 32 use gdbstub_arch::wasm::reg::WasmRegisters; 33 use gdbstub_arch::wasm::reg::id::WasmRegId; 34 35 impl<'a> Target for Debugger<'a> { 36 type Arch = WasmArch; 37 type Error = anyhow::Error; 38 base_ops(&mut self) -> BaseOps<'_, Self::Arch, Self::Error>39 fn base_ops(&mut self) -> BaseOps<'_, Self::Arch, Self::Error> { 40 BaseOps::MultiThread(self) 41 } 42 support_wasm(&mut self) -> Option<WasmOps<'_, Self>>43 fn support_wasm(&mut self) -> Option<WasmOps<'_, Self>> { 44 Some(self) 45 } 46 use_lldb_register_info(&self) -> bool47 fn use_lldb_register_info(&self) -> bool { 48 true 49 } 50 support_lldb_register_info_override( &mut self, ) -> Option<LldbRegisterInfoOverrideOps<'_, Self>>51 fn support_lldb_register_info_override( 52 &mut self, 53 ) -> Option<LldbRegisterInfoOverrideOps<'_, Self>> { 54 Some(self) 55 } 56 support_breakpoints(&mut self) -> Option<BreakpointsOps<'_, Self>>57 fn support_breakpoints(&mut self) -> Option<BreakpointsOps<'_, Self>> { 58 Some(self) 59 } 60 support_libraries(&mut self) -> Option<LibrariesOps<'_, Self>>61 fn support_libraries(&mut self) -> Option<LibrariesOps<'_, Self>> { 62 Some(self) 63 } 64 support_memory_map(&mut self) -> Option<MemoryMapOps<'_, Self>>65 fn support_memory_map(&mut self) -> Option<MemoryMapOps<'_, Self>> { 66 Some(self) 67 } 68 support_process_info(&mut self) -> Option<ProcessInfoOps<'_, Self>>69 fn support_process_info(&mut self) -> Option<ProcessInfoOps<'_, Self>> { 70 Some(self) 71 } 72 support_host_info(&mut self) -> Option<HostInfoOps<'_, Self>>73 fn support_host_info(&mut self) -> Option<HostInfoOps<'_, Self>> { 74 Some(self) 75 } 76 } 77 78 impl<'a> MultiThreadBase for Debugger<'a> { read_registers(&mut self, regs: &mut WasmRegisters, _tid: Tid) -> TargetResult<(), Self>79 fn read_registers(&mut self, regs: &mut WasmRegisters, _tid: Tid) -> TargetResult<(), Self> { 80 regs.pc = self.current_pc.as_raw(); 81 Ok(()) 82 } 83 write_registers(&mut self, regs: &WasmRegisters, _tid: Tid) -> TargetResult<(), Self>84 fn write_registers(&mut self, regs: &WasmRegisters, _tid: Tid) -> TargetResult<(), Self> { 85 self.current_pc = WasmAddr::from_raw(regs.pc).ok_or(TargetError::NonFatal)?; 86 Ok(()) 87 } 88 read_addrs( &mut self, start_addr: u64, data: &mut [u8], _tid: Tid, ) -> TargetResult<usize, Self>89 fn read_addrs( 90 &mut self, 91 start_addr: u64, 92 data: &mut [u8], 93 _tid: Tid, 94 ) -> TargetResult<usize, Self> { 95 let addr = WasmAddr::from_raw(start_addr).ok_or(TargetError::NonFatal)?; 96 let debuggee = self.debuggee; 97 match self.addr_space.lookup(addr, debuggee) { 98 AddrSpaceLookup::Module { 99 bytecode, offset, .. 100 } => { 101 let offset = usize::try_from(offset).unwrap(); 102 let avail = bytecode.len() - offset; 103 let n = avail.min(data.len()); 104 data[..n].copy_from_slice(&bytecode[offset..offset + n]); 105 Ok(n) 106 } 107 AddrSpaceLookup::Memory { memory, offset } => { 108 match memory.get_bytes(debuggee, offset.into(), u64::try_from(data.len()).unwrap()) 109 { 110 Ok(bytes) => { 111 assert_eq!(bytes.len(), data.len()); 112 data.copy_from_slice(&bytes); 113 Ok(data.len()) 114 } 115 Err(_) => Err(TargetError::NonFatal), 116 } 117 } 118 AddrSpaceLookup::Empty => Err(TargetError::NonFatal), 119 } 120 } 121 write_addrs(&mut self, _start_addr: u64, _data: &[u8], _tid: Tid) -> TargetResult<(), Self>122 fn write_addrs(&mut self, _start_addr: u64, _data: &[u8], _tid: Tid) -> TargetResult<(), Self> { 123 Err(TargetError::NonFatal) 124 } 125 list_active_threads( &mut self, thread_is_active: &mut dyn FnMut(Tid), ) -> Result<(), Self::Error>126 fn list_active_threads( 127 &mut self, 128 thread_is_active: &mut dyn FnMut(Tid), 129 ) -> Result<(), Self::Error> { 130 thread_is_active(self.tid); 131 Ok(()) 132 } 133 support_single_register_access(&mut self) -> Option<SingleRegisterAccessOps<'_, Tid, Self>>134 fn support_single_register_access(&mut self) -> Option<SingleRegisterAccessOps<'_, Tid, Self>> { 135 Some(self) 136 } 137 support_resume(&mut self) -> Option<MultiThreadResumeOps<'_, Self>>138 fn support_resume(&mut self) -> Option<MultiThreadResumeOps<'_, Self>> { 139 Some(self) 140 } 141 } 142 143 impl<'a> SingleRegisterAccess<Tid> for Debugger<'a> { read_register( &mut self, _tid: Tid, reg_id: WasmRegId, buf: &mut [u8], ) -> TargetResult<usize, Self>144 fn read_register( 145 &mut self, 146 _tid: Tid, 147 reg_id: WasmRegId, 148 buf: &mut [u8], 149 ) -> TargetResult<usize, Self> { 150 match reg_id { 151 WasmRegId::Pc => { 152 let bytes = self.current_pc.as_raw().to_le_bytes(); 153 let n = bytes.len().min(buf.len()); 154 buf[..n].copy_from_slice(&bytes[..n]); 155 Ok(n) 156 } 157 _ => Err(TargetError::NonFatal), 158 } 159 } 160 write_register( &mut self, _tid: Tid, reg_id: WasmRegId, val: &[u8], ) -> TargetResult<(), Self>161 fn write_register( 162 &mut self, 163 _tid: Tid, 164 reg_id: WasmRegId, 165 val: &[u8], 166 ) -> TargetResult<(), Self> { 167 match reg_id { 168 WasmRegId::Pc => { 169 if val.len() < 8 { 170 return Err(TargetError::NonFatal); 171 } 172 let raw = u64::from_le_bytes(val[..8].try_into().unwrap()); 173 self.current_pc = WasmAddr::from_raw(raw).ok_or(TargetError::NonFatal)?; 174 Ok(()) 175 } 176 _ => Err(TargetError::NonFatal), 177 } 178 } 179 } 180 181 impl<'a> MultiThreadResume for Debugger<'a> { resume(&mut self) -> Result<(), Self::Error>182 fn resume(&mut self) -> Result<(), Self::Error> { 183 self.frame_cache.clear(); 184 log::trace!("resume() -> single_stepping = {}", self.single_stepping); 185 if self.single_stepping { 186 self.start_single_step(api::ResumptionValue::Normal); 187 } else { 188 self.start_continue(api::ResumptionValue::Normal); 189 } 190 Ok(()) 191 } 192 clear_resume_actions(&mut self) -> Result<(), Self::Error>193 fn clear_resume_actions(&mut self) -> Result<(), Self::Error> { 194 self.single_stepping = false; 195 Ok(()) 196 } 197 set_resume_action_continue( &mut self, _tid: Tid, _signal: Option<Signal>, ) -> Result<(), Self::Error>198 fn set_resume_action_continue( 199 &mut self, 200 _tid: Tid, 201 _signal: Option<Signal>, 202 ) -> Result<(), Self::Error> { 203 self.single_stepping = false; 204 Ok(()) 205 } 206 support_single_step(&mut self) -> Option<MultiThreadSingleStepOps<'_, Self>>207 fn support_single_step(&mut self) -> Option<MultiThreadSingleStepOps<'_, Self>> { 208 Some(self) 209 } 210 support_scheduler_locking(&mut self) -> Option<MultiThreadSchedulerLockingOps<'_, Self>>211 fn support_scheduler_locking(&mut self) -> Option<MultiThreadSchedulerLockingOps<'_, Self>> { 212 Some(self) 213 } 214 } 215 216 impl<'a> MultiThreadSingleStep for Debugger<'a> { set_resume_action_step( &mut self, _tid: Tid, _signal: Option<Signal>, ) -> Result<(), Self::Error>217 fn set_resume_action_step( 218 &mut self, 219 _tid: Tid, 220 _signal: Option<Signal>, 221 ) -> Result<(), Self::Error> { 222 self.single_stepping = true; 223 Ok(()) 224 } 225 } 226 227 impl<'a> MultiThreadSchedulerLocking for Debugger<'a> { set_resume_action_scheduler_lock(&mut self) -> Result<(), Self::Error>228 fn set_resume_action_scheduler_lock(&mut self) -> Result<(), Self::Error> { 229 // We have a single thread, so scheduler locking is a no-op. 230 Ok(()) 231 } 232 } 233 234 impl<'a> Breakpoints for Debugger<'a> { 235 #[inline(always)] support_sw_breakpoint(&mut self) -> Option<SwBreakpointOps<'_, Self>>236 fn support_sw_breakpoint(&mut self) -> Option<SwBreakpointOps<'_, Self>> { 237 Some(self) 238 } 239 } 240 241 impl<'a> SwBreakpoint for Debugger<'a> { add_sw_breakpoint(&mut self, addr: u64, _kind: usize) -> TargetResult<bool, Self>242 fn add_sw_breakpoint(&mut self, addr: u64, _kind: usize) -> TargetResult<bool, Self> { 243 let Some(wasm_addr) = WasmAddr::from_raw(addr) else { 244 return Ok(false); 245 }; 246 let debuggee = self.debuggee; 247 if let AddrSpaceLookup::Module { module, .. } = self.addr_space.lookup(wasm_addr, debuggee) 248 { 249 module 250 .add_breakpoint(debuggee, wasm_addr.offset()) 251 .map_err(|_| TargetError::NonFatal)?; 252 Ok(true) 253 } else { 254 Ok(false) 255 } 256 } 257 remove_sw_breakpoint(&mut self, addr: u64, _kind: usize) -> TargetResult<bool, Self>258 fn remove_sw_breakpoint(&mut self, addr: u64, _kind: usize) -> TargetResult<bool, Self> { 259 let Some(wasm_addr) = WasmAddr::from_raw(addr) else { 260 return Ok(false); 261 }; 262 let debuggee = self.debuggee; 263 if let AddrSpaceLookup::Module { module, .. } = self.addr_space.lookup(wasm_addr, debuggee) 264 { 265 module 266 .remove_breakpoint(debuggee, wasm_addr.offset()) 267 .map_err(|_| TargetError::NonFatal)?; 268 Ok(true) 269 } else { 270 Ok(false) 271 } 272 } 273 } 274 275 impl<'a> LldbRegisterInfoOverride for Debugger<'a> { lldb_register_info<'b>( &mut self, reg_id: usize, reg_info: Callback<'b>, ) -> Result<CallbackToken<'b>, Self::Error>276 fn lldb_register_info<'b>( 277 &mut self, 278 reg_id: usize, 279 reg_info: Callback<'b>, 280 ) -> Result<CallbackToken<'b>, Self::Error> { 281 Ok(match reg_id { 282 0 => reg_info.write(Register { 283 name: "pc", 284 alt_name: Some("pc"), 285 bitsize: 64, 286 offset: 0, 287 encoding: Encoding::Uint, 288 format: Format::Hex, 289 set: "PC", 290 gcc: Some(16), 291 dwarf: Some(16), 292 generic: Some(Generic::Pc), 293 container_regs: None, 294 invalidate_regs: None, 295 }), 296 _ => reg_info.done(), 297 }) 298 } 299 } 300 301 impl<'a> Libraries for Debugger<'a> { get_libraries( &self, offset: u64, length: usize, buf: &mut [u8], ) -> TargetResult<usize, Self>302 fn get_libraries( 303 &self, 304 offset: u64, 305 length: usize, 306 buf: &mut [u8], 307 ) -> TargetResult<usize, Self> { 308 let mut xml = String::from("<library-list>"); 309 for (i, addr) in self.addr_space.module_base_addrs().enumerate() { 310 // Each library entry needs a unique name; LLDB's 311 // DynamicLoader deduplicates by name, so using the same 312 // name for all modules causes only the first to be loaded. 313 xml.push_str(&format!( 314 "<library name=\"wasm-{i}\"><section address=\"{}\"/></library>", 315 addr.as_raw() 316 )); 317 } 318 xml.push_str("</library-list>"); 319 320 let xml_bytes = xml.as_bytes(); 321 let offset = usize::try_from(offset).unwrap(); 322 if offset >= xml_bytes.len() { 323 return Ok(0); 324 } 325 let avail = xml_bytes.len() - offset; 326 let n = avail.min(length).min(buf.len()); 327 buf[..n].copy_from_slice(&xml_bytes[offset..offset + n]); 328 Ok(n) 329 } 330 } 331 332 impl<'a> MemoryMap for Debugger<'a> { memory_map_xml( &self, offset: u64, length: usize, buf: &mut [u8], ) -> TargetResult<usize, Self>333 fn memory_map_xml( 334 &self, 335 offset: u64, 336 length: usize, 337 buf: &mut [u8], 338 ) -> TargetResult<usize, Self> { 339 let xml = self.addr_space.memory_map_xml(self.debuggee); 340 let xml_bytes = xml.as_bytes(); 341 let offset = usize::try_from(offset).unwrap(); 342 if offset >= xml_bytes.len() { 343 return Ok(0); 344 } 345 let avail = xml_bytes.len() - offset; 346 let n = avail.min(length).min(buf.len()); 347 buf[..n].copy_from_slice(&xml_bytes[offset..offset + n]); 348 Ok(n) 349 } 350 } 351 352 impl<'a> Wasm for Debugger<'a> { wasm_call_stack(&self, _tid: Tid, callback: &mut dyn FnMut(u64)) -> Result<(), Self::Error>353 fn wasm_call_stack(&self, _tid: Tid, callback: &mut dyn FnMut(u64)) -> Result<(), Self::Error> { 354 let debuggee = self.debuggee; 355 for (i, f) in self.frame_cache.iter().enumerate() { 356 // For non-innermost frames, report the return address 357 // (the instruction after the call) rather than the call 358 // instruction's PC. This matches the standard debugger 359 // convention and is needed for LLDB's `finish` command 360 // to set a breakpoint at the right address. 361 let pc = if i > 0 { 362 self.addr_space 363 .frame_to_return_addr(f, debuggee) 364 .unwrap_or_else(|| self.addr_space.frame_to_pc(f, debuggee)) 365 } else { 366 self.addr_space.frame_to_pc(f, debuggee) 367 }; 368 callback(pc.as_raw()); 369 } 370 Ok(()) 371 } 372 read_wasm_local( &self, _tid: Tid, frame_depth: usize, index: usize, buf: &mut [u8], ) -> Result<usize, Self::Error>373 fn read_wasm_local( 374 &self, 375 _tid: Tid, 376 frame_depth: usize, 377 index: usize, 378 buf: &mut [u8], 379 ) -> Result<usize, Self::Error> { 380 let Some(f) = self.frame_cache.get(frame_depth) else { 381 return Ok(0); 382 }; 383 let Ok(locals) = f.get_locals(self.debuggee) else { 384 return Ok(0); 385 }; 386 let Some(val) = locals.get(index) else { 387 return Ok(0); 388 }; 389 let bytes = self.value_to_bytes(val.clone()); 390 buf[..bytes.len()].copy_from_slice(&bytes); 391 Ok(bytes.len()) 392 } 393 read_wasm_global( &self, _tid: Tid, frame_depth: usize, index: usize, buf: &mut [u8], ) -> Result<usize, Self::Error>394 fn read_wasm_global( 395 &self, 396 _tid: Tid, 397 frame_depth: usize, 398 index: usize, 399 buf: &mut [u8], 400 ) -> Result<usize, Self::Error> { 401 let Some(f) = self.frame_cache.get(frame_depth) else { 402 return Ok(0); 403 }; 404 let debuggee = self.debuggee; 405 let Ok(instance) = f.get_instance(debuggee) else { 406 return Ok(0); 407 }; 408 let Ok(global) = instance.get_global(debuggee, u32::try_from(index).unwrap()) else { 409 return Ok(0); 410 }; 411 let Ok(val) = global.get(debuggee) else { 412 return Ok(0); 413 }; 414 let bytes = self.value_to_bytes(val); 415 buf[..bytes.len()].copy_from_slice(&bytes); 416 Ok(bytes.len()) 417 } 418 read_wasm_stack( &self, _tid: Tid, frame_depth: usize, index: usize, buf: &mut [u8], ) -> Result<usize, Self::Error>419 fn read_wasm_stack( 420 &self, 421 _tid: Tid, 422 frame_depth: usize, 423 index: usize, 424 buf: &mut [u8], 425 ) -> Result<usize, Self::Error> { 426 let Some(f) = self.frame_cache.get(frame_depth) else { 427 return Ok(0); 428 }; 429 let Ok(stack) = f.get_stack(self.debuggee) else { 430 return Ok(0); 431 }; 432 let Some(val) = stack.get(index) else { 433 return Ok(0); 434 }; 435 let bytes = self.value_to_bytes(val.clone()); 436 buf[..bytes.len()].copy_from_slice(&bytes); 437 Ok(bytes.len()) 438 } 439 } 440 441 impl<'a> HostInfo for Debugger<'a> { host_info( &self, write_item: &mut dyn FnMut(&HostInfoResponse<'_>), ) -> Result<(), Self::Error>442 fn host_info( 443 &self, 444 write_item: &mut dyn FnMut(&HostInfoResponse<'_>), 445 ) -> Result<(), Self::Error> { 446 write_item(&HostInfoResponse::Triple("wasm32-unknown-unknown-wasm")); 447 write_item(&HostInfoResponse::Endianness(Endianness::Little)); 448 write_item(&HostInfoResponse::PointerSize(4)); 449 Ok(()) 450 } 451 } 452 453 impl<'a> ProcessInfo for Debugger<'a> { process_info( &self, write_item: &mut dyn FnMut(&ProcessInfoResponse<'_>), ) -> Result<(), Self::Error>454 fn process_info( 455 &self, 456 write_item: &mut dyn FnMut(&ProcessInfoResponse<'_>), 457 ) -> Result<(), Self::Error> { 458 write_item(&ProcessInfoResponse::Pid(Pid::new(1).unwrap())); 459 write_item(&ProcessInfoResponse::Triple("wasm32-unknown-unknown-wasm")); 460 write_item(&ProcessInfoResponse::Endianness(Endianness::Little)); 461 write_item(&ProcessInfoResponse::PointerSize(4)); 462 Ok(()) 463 } 464 } 465