1 use super::prelude::*; 2 use super::DisconnectReason; 3 use crate::arch::Arch; 4 use crate::arch::Registers; 5 use crate::common::Pid; 6 use crate::common::Tid; 7 use crate::protocol::commands::ext::Base; 8 use crate::protocol::IdKind; 9 use crate::protocol::SpecificIdKind; 10 use crate::protocol::SpecificThreadId; 11 use crate::target::ext::base::BaseOps; 12 use crate::target::ext::base::ResumeOps; 13 use crate::FAKE_PID; 14 use crate::SINGLE_THREAD_TID; 15 16 impl<T: Target, C: Connection> GdbStubImpl<T, C> { 17 #[inline(always)] get_sane_any_tid( &mut self, target: &mut T, ) -> Result<Option<Tid>, Error<T::Error, C::Error>>18 fn get_sane_any_tid( 19 &mut self, 20 target: &mut T, 21 ) -> Result<Option<Tid>, Error<T::Error, C::Error>> { 22 let tid = match target.base_ops() { 23 BaseOps::SingleThread(_) => Some(SINGLE_THREAD_TID), 24 BaseOps::MultiThread(ops) => { 25 let mut first_tid = None; 26 ops.list_active_threads(&mut |tid| { 27 if first_tid.is_none() { 28 first_tid = Some(tid); 29 } 30 }) 31 .map_err(Error::TargetError)?; 32 // It is possible for this to be `None` in the case where the target has 33 // not yet called `register_thread()`. This can happen, for example, if 34 // there are no active threads in the current target process. 35 first_tid 36 } 37 }; 38 Ok(tid) 39 } 40 get_current_pid( &mut self, target: &mut T, ) -> Result<Pid, Error<T::Error, C::Error>>41 pub(crate) fn get_current_pid( 42 &mut self, 43 target: &mut T, 44 ) -> Result<Pid, Error<T::Error, C::Error>> { 45 if let Some(ops) = target 46 .support_extended_mode() 47 .and_then(|ops| ops.support_current_active_pid()) 48 { 49 ops.current_active_pid().map_err(Error::TargetError) 50 } else { 51 Ok(FAKE_PID) 52 } 53 } 54 55 // Used by `?` and `vAttach` to return a "reasonable" stop reason. 56 // 57 // This is a bit of an implementation wart, since this is really something 58 // the user ought to be able to customize. 59 // 60 // Works fine for now though... report_reasonable_stop_reason( &mut self, res: &mut ResponseWriter<'_, C>, target: &mut T, ) -> Result<HandlerStatus, Error<T::Error, C::Error>>61 pub(crate) fn report_reasonable_stop_reason( 62 &mut self, 63 res: &mut ResponseWriter<'_, C>, 64 target: &mut T, 65 ) -> Result<HandlerStatus, Error<T::Error, C::Error>> { 66 // Reply with a valid thread-id or GDB issues a warning when more 67 // than one thread is active 68 if let Some(tid) = self.get_sane_any_tid(target)? { 69 res.write_str("T05thread:")?; 70 res.write_specific_thread_id(SpecificThreadId { 71 pid: self 72 .features 73 .multiprocess() 74 .then_some(SpecificIdKind::WithId(self.get_current_pid(target)?)), 75 tid: SpecificIdKind::WithId(tid), 76 })?; 77 } else { 78 res.write_str("W00")?; 79 } 80 res.write_str(";")?; 81 Ok(HandlerStatus::Handled) 82 } 83 handle_base( &mut self, res: &mut ResponseWriter<'_, C>, target: &mut T, command: Base<'_>, ) -> Result<HandlerStatus, Error<T::Error, C::Error>>84 pub(crate) fn handle_base( 85 &mut self, 86 res: &mut ResponseWriter<'_, C>, 87 target: &mut T, 88 command: Base<'_>, 89 ) -> Result<HandlerStatus, Error<T::Error, C::Error>> { 90 let handler_status = match command { 91 // ------------------ Handshaking and Queries ------------------- // 92 Base::qSupported(cmd) => { 93 use crate::protocol::commands::_qSupported::Feature; 94 95 // perform incoming feature negotiation 96 for feature in cmd.features.into_iter() { 97 let (feature, supported) = match feature { 98 Ok(Some(v)) => v, 99 Ok(None) => continue, 100 Err(()) => { 101 return Err(Error::PacketParse( 102 crate::protocol::PacketParseError::MalformedCommand, 103 )) 104 } 105 }; 106 107 match feature { 108 Feature::Multiprocess => self.features.set_multiprocess(supported), 109 } 110 } 111 112 res.write_str("PacketSize=")?; 113 res.write_num(cmd.packet_buffer_len)?; 114 115 // these are the few features that gdbstub unconditionally supports 116 res.write_str(concat!(";vContSupported+", ";multiprocess+",))?; 117 118 if target.use_no_ack_mode() { 119 res.write_str(";QStartNoAckMode+")?; 120 } 121 122 if let Some(resume_ops) = target.base_ops().resume_ops() { 123 let (reverse_cont, reverse_step) = match resume_ops { 124 ResumeOps::MultiThread(ops) => ( 125 ops.support_reverse_cont().is_some(), 126 ops.support_reverse_step().is_some(), 127 ), 128 ResumeOps::SingleThread(ops) => ( 129 ops.support_reverse_cont().is_some(), 130 ops.support_reverse_step().is_some(), 131 ), 132 }; 133 134 if reverse_cont { 135 res.write_str(";ReverseContinue+")?; 136 } 137 138 if reverse_step { 139 res.write_str(";ReverseStep+")?; 140 } 141 } 142 143 if let Some(ops) = target.support_extended_mode() { 144 if ops.support_configure_aslr().is_some() { 145 res.write_str(";QDisableRandomization+")?; 146 } 147 148 if ops.support_configure_env().is_some() { 149 res.write_str(";QEnvironmentHexEncoded+")?; 150 res.write_str(";QEnvironmentUnset+")?; 151 res.write_str(";QEnvironmentReset+")?; 152 } 153 154 if ops.support_configure_startup_shell().is_some() { 155 res.write_str(";QStartupWithShell+")?; 156 } 157 158 if ops.support_configure_working_dir().is_some() { 159 res.write_str(";QSetWorkingDir+")?; 160 } 161 } 162 163 if let Some(ops) = target.support_breakpoints() { 164 if ops.support_sw_breakpoint().is_some() { 165 res.write_str(";swbreak+")?; 166 } 167 168 if ops.support_hw_breakpoint().is_some() 169 || ops.support_hw_watchpoint().is_some() 170 { 171 res.write_str(";hwbreak+")?; 172 } 173 } 174 175 if let Some(ops) = target.support_tracepoints() { 176 // There are a number of optional tracepoint extensions that 177 // gdbstub should eventually implement. 178 // * `StaticTracepoint` for static tracepoint support. 179 // * `EnableDisableTracepoints` for enabling/disabling tracepoints during a 180 // trace experiment. 181 // * `tracenz` for the tracenz agent bytecode operation. 182 // * The `Qbtrace:*` family for branch tracing. 183 // * `InstallInTrace` allows for gdbstub to deliver tracepoint configuration 184 // commands while the trace experiment is running instead of them only taking 185 // affect on the next `tstart` command. 186 // 187 // For now, gdbstub doesn't provide trait extensions for these 188 // options and so we don't report support. We do report support 189 // for one extension however: 190 // * `QTBuffer:size` for configuring the trace buffer size, since the target is 191 // allowed to implement it as a no-op. 192 res.write_str(";QTBuffer:size+")?; 193 if ops.support_tracepoint_source().is_some() { 194 res.write_str(";TracepointSource+")?; 195 } 196 } 197 198 if target.support_catch_syscalls().is_some() { 199 res.write_str(";QCatchSyscalls+")?; 200 } 201 202 if target.use_target_description_xml() 203 && (T::Arch::target_description_xml().is_some() 204 || target.support_target_description_xml_override().is_some()) 205 { 206 res.write_str(";qXfer:features:read+")?; 207 } 208 209 if target.support_memory_map().is_some() { 210 res.write_str(";qXfer:memory-map:read+")?; 211 } 212 213 if target.support_exec_file().is_some() { 214 res.write_str(";qXfer:exec-file:read+")?; 215 } 216 217 if target.support_auxv().is_some() { 218 res.write_str(";qXfer:auxv:read+")?; 219 } 220 221 if target.support_libraries_svr4().is_some() { 222 res.write_str(";qXfer:libraries-svr4:read+")?; 223 } 224 225 HandlerStatus::Handled 226 } 227 228 // -------------------- "Core" Functionality -------------------- // 229 Base::QuestionMark(_) => { 230 // TODO: Improve the '?' response. 231 // this will be particularly relevant when working on non-stop mode. 232 self.report_reasonable_stop_reason(res, target)? 233 } 234 Base::qAttached(cmd) => { 235 let is_attached = match target.support_extended_mode() { 236 // when _not_ running in extended mode, just report that we're attaching to an 237 // existing process. 238 None => true, // assume attached to an existing process 239 // When running in extended mode, we must defer to the target 240 Some(ops) => { 241 match cmd.pid { 242 Some(pid) => ops.query_if_attached(pid).handle_error()?.was_attached(), 243 None => true, // assume attached to an existing process 244 } 245 } 246 }; 247 res.write_str(if is_attached { "1" } else { "0" })?; 248 HandlerStatus::Handled 249 } 250 Base::g(_) => { 251 let mut regs: <T::Arch as Arch>::Registers = Default::default(); 252 match target.base_ops() { 253 BaseOps::SingleThread(ops) => ops.read_registers(&mut regs), 254 BaseOps::MultiThread(ops) => { 255 ops.read_registers(&mut regs, self.current_mem_tid) 256 } 257 } 258 .handle_error()?; 259 260 let mut err = Ok(()); 261 regs.gdb_serialize(|val| { 262 let res = match val { 263 Some(b) => res.write_hex_buf(&[b]), 264 None => res.write_str("xx"), 265 }; 266 if let Err(e) = res { 267 err = Err(e); 268 } 269 }); 270 err?; 271 HandlerStatus::Handled 272 } 273 Base::G(cmd) => { 274 let mut regs: <T::Arch as Arch>::Registers = Default::default(); 275 regs.gdb_deserialize(cmd.vals) 276 .map_err(|_| Error::TargetMismatch)?; 277 278 match target.base_ops() { 279 BaseOps::SingleThread(ops) => ops.write_registers(®s), 280 BaseOps::MultiThread(ops) => ops.write_registers(®s, self.current_mem_tid), 281 } 282 .handle_error()?; 283 284 HandlerStatus::NeedsOk 285 } 286 Base::m(cmd) => { 287 let buf = cmd.buf; 288 let addr = <T::Arch as Arch>::Usize::from_be_bytes(cmd.addr) 289 .ok_or(Error::TargetMismatch)?; 290 291 let mut i = 0; 292 let mut n = cmd.len; 293 while n != 0 { 294 let chunk_size = n.min(buf.len()); 295 296 use num_traits::NumCast; 297 298 let addr = addr + NumCast::from(i).ok_or(Error::TargetMismatch)?; 299 let data = &mut buf[..chunk_size]; 300 let data_len = match target.base_ops() { 301 BaseOps::SingleThread(ops) => ops.read_addrs(addr, data), 302 BaseOps::MultiThread(ops) => { 303 ops.read_addrs(addr, data, self.current_mem_tid) 304 } 305 } 306 .handle_error()?; 307 308 n -= chunk_size; 309 i += chunk_size; 310 311 // TODO: add more specific error variant? 312 let data = data.get(..data_len).ok_or(Error::PacketBufferOverflow)?; 313 res.write_hex_buf(data)?; 314 } 315 HandlerStatus::Handled 316 } 317 Base::M(cmd) => { 318 let addr = <T::Arch as Arch>::Usize::from_be_bytes(cmd.addr) 319 .ok_or(Error::TargetMismatch)?; 320 321 match target.base_ops() { 322 BaseOps::SingleThread(ops) => ops.write_addrs(addr, cmd.val), 323 BaseOps::MultiThread(ops) => { 324 ops.write_addrs(addr, cmd.val, self.current_mem_tid) 325 } 326 } 327 .handle_error()?; 328 329 HandlerStatus::NeedsOk 330 } 331 Base::k(_) | Base::vKill(_) => { 332 match target.support_extended_mode() { 333 // When not running in extended mode, stop the `GdbStub` and disconnect. 334 None => HandlerStatus::Disconnect(DisconnectReason::Kill), 335 336 // When running in extended mode, a kill command does not necessarily result in 337 // a disconnect... 338 Some(ops) => { 339 let pid = match command { 340 Base::vKill(cmd) => Some(cmd.pid), 341 _ => None, 342 }; 343 344 let should_terminate = ops.kill(pid).handle_error()?; 345 if should_terminate.into_bool() { 346 // manually write OK, since we need to return a DisconnectReason 347 res.write_str("OK")?; 348 HandlerStatus::Disconnect(DisconnectReason::Kill) 349 } else { 350 HandlerStatus::NeedsOk 351 } 352 } 353 } 354 } 355 Base::D(cmd) => { 356 // TODO: plumb-through Pid when exposing full multiprocess + extended mode 357 let _pid = cmd.pid; 358 res.write_str("OK")?; // manually write OK, since we need to return a DisconnectReason 359 HandlerStatus::Disconnect(DisconnectReason::Disconnect) 360 } 361 362 // ------------------- Multi-threading Support ------------------ // 363 Base::H(cmd) => { 364 use crate::protocol::commands::_h_upcase::Op; 365 match cmd.kind { 366 Op::Other => match cmd.thread.tid { 367 IdKind::Any => match self.get_sane_any_tid(target)? { 368 Some(tid) => self.current_mem_tid = tid, 369 None => { 370 return Err(Error::NonFatalError(1)); 371 } 372 }, 373 // "All" threads doesn't make sense for memory accesses 374 IdKind::All => return Err(Error::PacketUnexpected), 375 IdKind::WithId(tid) => self.current_mem_tid = tid, 376 }, 377 // technically, this variant is deprecated in favor of vCont... 378 Op::StepContinue => match cmd.thread.tid { 379 IdKind::Any => match self.get_sane_any_tid(target)? { 380 Some(tid) => self.current_resume_tid = SpecificIdKind::WithId(tid), 381 None => { 382 return Err(Error::NonFatalError(1)); 383 } 384 }, 385 IdKind::All => self.current_resume_tid = SpecificIdKind::All, 386 IdKind::WithId(tid) => { 387 self.current_resume_tid = SpecificIdKind::WithId(tid) 388 } 389 }, 390 } 391 HandlerStatus::NeedsOk 392 } 393 Base::qfThreadInfo(_) => { 394 res.write_str("m")?; 395 let pid = self.get_current_pid(target)?; 396 397 match target.base_ops() { 398 BaseOps::SingleThread(_) => res.write_specific_thread_id(SpecificThreadId { 399 pid: self 400 .features 401 .multiprocess() 402 .then_some(SpecificIdKind::WithId(pid)), 403 tid: SpecificIdKind::WithId(SINGLE_THREAD_TID), 404 })?, 405 BaseOps::MultiThread(ops) => { 406 let mut err: Result<_, Error<T::Error, C::Error>> = Ok(()); 407 let mut first = true; 408 ops.list_active_threads(&mut |tid| { 409 // TODO: replace this with a try block (once stabilized) 410 let e = (|| { 411 if !first { 412 res.write_str(",")? 413 } 414 first = false; 415 res.write_specific_thread_id(SpecificThreadId { 416 pid: self 417 .features 418 .multiprocess() 419 .then_some(SpecificIdKind::WithId(pid)), 420 tid: SpecificIdKind::WithId(tid), 421 })?; 422 Ok(()) 423 })(); 424 425 if let Err(e) = e { 426 err = Err(e) 427 } 428 }) 429 .map_err(Error::TargetError)?; 430 err?; 431 } 432 } 433 434 HandlerStatus::Handled 435 } 436 Base::qsThreadInfo(_) => { 437 res.write_str("l")?; 438 HandlerStatus::Handled 439 } 440 Base::T(cmd) => { 441 let alive = match cmd.thread.tid { 442 IdKind::WithId(tid) => match target.base_ops() { 443 BaseOps::SingleThread(_) => tid == SINGLE_THREAD_TID, 444 BaseOps::MultiThread(ops) => { 445 ops.is_thread_alive(tid).map_err(Error::TargetError)? 446 } 447 }, 448 _ => return Err(Error::PacketUnexpected), 449 }; 450 if alive { 451 HandlerStatus::NeedsOk 452 } else { 453 // any error code will do 454 return Err(Error::NonFatalError(1)); 455 } 456 } 457 }; 458 Ok(handler_status) 459 } 460 } 461