| // Copyright 2016 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "cmd_handler.h" |
| |
| #include <algorithm> |
| #include <cinttypes> |
| #include <string> |
| |
| #include "garnet/lib/debugger_utils/util.h" |
| |
| #include "garnet/lib/inferior_control/registers.h" |
| #include "garnet/lib/inferior_control/thread.h" |
| |
| #include "lib/fxl/arraysize.h" |
| #include "lib/fxl/logging.h" |
| #include "lib/fxl/strings/split_string.h" |
| #include "lib/fxl/strings/string_number_conversions.h" |
| #include "lib/fxl/strings/string_printf.h" |
| |
| #include "server.h" |
| #include "thread_action_list.h" |
| #include "util.h" |
| |
| namespace debugserver { |
| |
| namespace { |
| |
| const char kSupportedFeatures[] = |
| "QNonStop+;" |
| #if 0 // TODO(dje) |
| "QThreadEvents+;" |
| #endif |
| #if 0 // TODO(dje) |
| "swbreak+;" |
| #endif |
| "qXfer:auxv:read+"; |
| |
| const char kAttached[] = "Attached"; |
| const char kCurrentThreadId[] = "C"; |
| const char kFirstThreadInfo[] = "fThreadInfo"; |
| const char kNonStop[] = "NonStop"; |
| const char kRcmd[] = "Rcmd,"; |
| const char kSubsequentThreadInfo[] = "sThreadInfo"; |
| const char kSupported[] = "Supported"; |
| const char kXfer[] = "Xfer"; |
| |
| // v Commands |
| const char kAttach[] = "Attach;"; |
| const char kCont[] = "Cont;"; |
| const char kKill[] = "Kill;"; |
| const char kRun[] = "Run;"; |
| |
| // qRcmd commands |
| const char kExit[] = "exit"; |
| const char kHelp[] = "help"; |
| const char kQuit[] = "quit"; |
| const char kSet[] = "set"; |
| const char kShow[] = "show"; |
| |
| // This always returns true so that command handlers can simple call "return |
| // ReplyOK()" rather than "ReplyOK(); return true; |
| bool ReplyOK(CommandHandler::ResponseCallback callback) { |
| callback("OK"); |
| return true; |
| } |
| |
| // This always returns true so that command handlers can simple call "return |
| // ReplyWithError()" rather than "ReplyWithError(); return true; |
| bool ReplyWithError(ErrorCode error_code, |
| CommandHandler::ResponseCallback callback) { |
| std::string error_rsp = BuildErrorPacket(error_code); |
| callback(error_rsp); |
| return true; |
| } |
| |
| // Returns true if |str| starts with |prefix|. |
| bool StartsWith(const fxl::StringView& str, const fxl::StringView& prefix) { |
| return str.substr(0, prefix.size()) == prefix; |
| } |
| |
| std::vector<std::string> BuildArgvFor_vRun(const fxl::StringView& packet) { |
| std::vector<std::string> argv; |
| size_t len = packet.size(); |
| size_t s = 0; |
| |
| while (s < len) { |
| size_t semi = packet.find(';', s); |
| size_t n; |
| if (semi == fxl::StringView::npos) |
| n = len - s; |
| else |
| n = semi - s; |
| std::vector<uint8_t> arg = |
| debugger_utils::DecodeByteArrayString(packet.substr(s, n)); |
| auto char_arg = reinterpret_cast<char*>(arg.data()); |
| argv.push_back(std::string(char_arg, arg.size())); |
| if (semi == fxl::StringView::npos) |
| s = len; |
| else |
| s = semi + 1; |
| } |
| |
| return argv; |
| } |
| |
| } // namespace |
| |
| CommandHandler::CommandHandler(RspServer* server) |
| : server_(server), in_thread_info_sequence_(false) { |
| FXL_DCHECK(server_); |
| } |
| |
| bool CommandHandler::HandleCommand(const fxl::StringView& packet, |
| ResponseCallback callback) { |
| // GDB packets are prefixed with a letter that maps to a particular command |
| // "family". We do the initial multiplexing here and let each individual |
| // sub-handler deal with the rest. |
| if (packet.empty()) { |
| // TODO(armansito): Is there anything meaningful that we can do here? |
| FXL_LOG(ERROR) << "Empty packet received"; |
| return false; |
| } |
| |
| switch (packet[0]) { |
| case '?': // Indicate the reason the target halted |
| if (packet.size() > 1) |
| break; |
| return HandleQuestionMark(std::move(callback)); |
| case 'c': // Continue (at addr) |
| return Handle_c(packet.substr(1), std::move(callback)); |
| case 'C': // Continue with signal (optionally at addr) |
| return Handle_C(packet.substr(1), std::move(callback)); |
| case 'D': // Detach |
| return Handle_D(packet.substr(1), std::move(callback)); |
| case 'g': // Read general registers |
| if (packet.size() > 1) |
| break; |
| return Handle_g(std::move(callback)); |
| case 'G': // Write general registers |
| return Handle_G(packet.substr(1), std::move(callback)); |
| case 'H': // Set a thread for subsequent operations |
| return Handle_H(packet.substr(1), std::move(callback)); |
| case 'm': // Read memory |
| return Handle_m(packet.substr(1), std::move(callback)); |
| case 'M': // Write memory |
| return Handle_M(packet.substr(1), std::move(callback)); |
| case 'q': // General query packet |
| case 'Q': // General set packet |
| { |
| fxl::StringView prefix, params; |
| ExtractParameters(packet.substr(1), &prefix, ¶ms); |
| |
| FXL_VLOG(1) << "\'" << packet[0] << "\' packet - prefix: " << prefix |
| << ", params: " << params; |
| |
| if (packet[0] == 'q') |
| return Handle_q(prefix, params, std::move(callback)); |
| return Handle_Q(prefix, params, std::move(callback)); |
| } |
| case 'T': // Is thread alive? |
| return Handle_T(packet.substr(1), std::move(callback)); |
| case 'v': // v-packets |
| return Handle_v(packet.substr(1), std::move(callback)); |
| case 'z': // Remove software breakpoint |
| case 'Z': // Insert software breakpoint |
| return Handle_zZ(packet[0] == 'Z', packet.substr(1), std::move(callback)); |
| default: |
| break; |
| } |
| |
| return false; |
| } |
| |
| bool CommandHandler::HandleQuestionMark(ResponseCallback callback) { |
| // TODO(armansito): Implement this once we actually listen to thread/process |
| // exceptions. The logic for NonStop mode is fairly simple: |
| // 1. Tell Server to drop any pending and/or queued Stop Reply |
| // notifications. |
| // |
| // 2. Go through all processes and send a notification for the status of |
| // each. |
| // |
| // 3. If there is no inferior or the current inferior is not started, then |
| // reply "OK". |
| return ReplyOK(std::move(callback)); |
| } |
| |
| bool CommandHandler::Handle_c(const fxl::StringView& packet, |
| ResponseCallback callback) { |
| // If there is no current process or if the current process isn't attached, |
| // then report an error. |
| inferior_control::Process* current_process = server_->current_process(); |
| if (!current_process || !current_process->IsAttached()) { |
| FXL_LOG(ERROR) << "c: No inferior"; |
| return ReplyWithError(ErrorCode::PERM, std::move(callback)); |
| } |
| |
| inferior_control::Thread* current_thread = server_->current_thread(); |
| |
| // If the packet contains an address parameter, then try to set the program |
| // counter to then continue at that address. Otherwise, the PC register will |
| // remain untouched. |
| zx_vaddr_t addr; |
| if (!packet.empty()) { |
| if (!fxl::StringToNumberWithError<zx_vaddr_t>(packet, &addr, |
| fxl::Base::k16)) { |
| FXL_LOG(ERROR) << "c: Malformed address given: " << packet; |
| return ReplyWithError(ErrorCode::INVAL, std::move(callback)); |
| } |
| |
| // If there is no current thread, then report error. This is a special case |
| // that means that the process hasn't started yet. |
| if (!current_thread) { |
| FXL_DCHECK(!current_process->IsLive()); |
| return ReplyWithError(ErrorCode::PERM, std::move(callback)); |
| } |
| |
| if (!current_thread->registers()->RefreshGeneralRegisters()) { |
| return ReplyWithError(ErrorCode::PERM, std::move(callback)); |
| } |
| if (!current_thread->registers()->SetRegister( |
| inferior_control::GetPCRegisterNumber(), &addr, sizeof(addr))) { |
| return ReplyWithError(ErrorCode::PERM, std::move(callback)); |
| } |
| if (!current_thread->registers()->WriteGeneralRegisters()) { |
| return ReplyWithError(ErrorCode::PERM, std::move(callback)); |
| } |
| |
| // TODO(armansito): Restore the PC register to its original state in case of |
| // a failure condition below? |
| } |
| |
| // If there is a current thread, then tell it to continue. |
| if (current_thread) { |
| if (!current_thread->Resume()) |
| return ReplyWithError(ErrorCode::PERM, std::move(callback)); |
| |
| return ReplyOK(std::move(callback)); |
| } |
| |
| // There is no current thread. This means that the process hasn't been started |
| // yet. We start it and set the current thread to the first one the kernel |
| // gives us. |
| // TODO(armansito): Remove this logic now that we handle |
| // ZX_EXCP_THREAD_STARTING? |
| FXL_DCHECK(!current_process->IsLive()); |
| if (!current_process->Start()) { |
| FXL_LOG(ERROR) << "c: Failed to start the current inferior"; |
| return ReplyWithError(ErrorCode::PERM, std::move(callback)); |
| } |
| |
| // Try to set the current thread. |
| // TODO(armansito): Can this be racy? |
| current_thread = current_process->PickOneThread(); |
| if (current_thread) |
| server_->SetCurrentThread(current_thread); |
| |
| return ReplyOK(std::move(callback)); |
| } |
| |
| bool CommandHandler::Handle_C(const fxl::StringView& packet, |
| ResponseCallback callback) { |
| // If there is no current process or if the current process isn't attached, |
| // then report an error. |
| inferior_control::Process* current_process = server_->current_process(); |
| if (!current_process || !current_process->IsAttached()) { |
| FXL_LOG(ERROR) << "C: No inferior"; |
| return ReplyWithError(ErrorCode::PERM, std::move(callback)); |
| } |
| |
| inferior_control::Thread* current_thread = server_->current_thread(); |
| if (!current_thread) { |
| FXL_LOG(ERROR) << "C: No current thread"; |
| return ReplyWithError(ErrorCode::PERM, std::move(callback)); |
| } |
| |
| // Parse the parameters. The packet format is: sig[;addr] |
| size_t semicolon = packet.find(';'); |
| if (semicolon == fxl::StringView::npos) |
| semicolon = packet.size(); |
| |
| int signo; |
| if (!fxl::StringToNumberWithError<int>(packet.substr(0, semicolon), &signo, |
| fxl::Base::k16)) { |
| FXL_LOG(ERROR) << "C: Malformed packet: " << packet; |
| return ReplyWithError(ErrorCode::INVAL, std::move(callback)); |
| } |
| |
| inferior_control::GdbSignal thread_signo = current_thread->GetGdbSignal(); |
| // TODO(dje): kNone may be a better value to use here. |
| if (thread_signo == inferior_control::GdbSignal::kUnsupported) { |
| FXL_LOG(ERROR) << "C: Current thread has received no signal"; |
| return ReplyWithError(ErrorCode::PERM, std::move(callback)); |
| } |
| int int_thread_signo = static_cast<int>(thread_signo); |
| |
| if (int_thread_signo != signo) { |
| FXL_LOG(ERROR) << "C: Signal numbers don't match - actual: " |
| << int_thread_signo << ", received: " << signo; |
| return ReplyWithError(ErrorCode::PERM, std::move(callback)); |
| } |
| |
| auto addr_param = packet.substr(semicolon); |
| |
| // If the packet contains an address parameter, then try to set the program |
| // counter to then continue at that address. Otherwise, the PC register will |
| // remain untouched. |
| // TODO(armansito): Make Thread::Resume take an optional address argument so |
| // we don't have to keep repeating this code. |
| if (!addr_param.empty()) { |
| zx_vaddr_t addr; |
| if (!fxl::StringToNumberWithError<zx_vaddr_t>(addr_param, &addr, |
| fxl::Base::k16)) { |
| FXL_LOG(ERROR) << "C: Malformed address given: " << packet; |
| return ReplyWithError(ErrorCode::INVAL, std::move(callback)); |
| } |
| |
| if (!current_thread->registers()->RefreshGeneralRegisters()) { |
| return ReplyWithError(ErrorCode::PERM, std::move(callback)); |
| } |
| if (!current_thread->registers()->SetRegister( |
| inferior_control::GetPCRegisterNumber(), &addr, sizeof(addr))) { |
| return ReplyWithError(ErrorCode::PERM, std::move(callback)); |
| } |
| if (!current_thread->registers()->WriteGeneralRegisters()) { |
| return ReplyWithError(ErrorCode::PERM, std::move(callback)); |
| } |
| |
| // TODO(armansito): Restore the PC register to its original state in case of |
| // a failure condition below? |
| } |
| |
| if (!current_thread->Resume()) { |
| FXL_LOG(ERROR) << "Failed to resume thread"; |
| return ReplyWithError(ErrorCode::PERM, std::move(callback)); |
| } |
| |
| return ReplyOK(std::move(callback)); |
| } |
| |
| bool CommandHandler::Handle_D(const fxl::StringView& packet, |
| ResponseCallback callback) { |
| // If there is no current process or if the current process isn't attached, |
| // then report an error. |
| inferior_control::Process* current_process = server_->current_process(); |
| if (!current_process) { |
| FXL_LOG(ERROR) << "D: No inferior"; |
| return ReplyWithError(ErrorCode::PERM, std::move(callback)); |
| } |
| |
| // For now we only support detaching from the one process we have. |
| if (packet[0] == ';') { |
| zx_koid_t pid; |
| if (!fxl::StringToNumberWithError<zx_koid_t>(packet.substr(1), &pid, |
| fxl::Base::k16)) { |
| FXL_LOG(ERROR) << "D: bad pid: " << packet; |
| return ReplyWithError(ErrorCode::INVAL, std::move(callback)); |
| } |
| if (pid != current_process->id()) { |
| FXL_LOG(ERROR) << "D: unknown pid: " << pid; |
| return ReplyWithError(ErrorCode::INVAL, std::move(callback)); |
| } |
| } else if (packet != "") { |
| FXL_LOG(ERROR) << "D: Malformed packet: " << packet; |
| return ReplyWithError(ErrorCode::INVAL, std::move(callback)); |
| } |
| |
| if (!current_process->IsAttached()) { |
| FXL_LOG(ERROR) << "D: Not attached to process " << current_process->id(); |
| return ReplyWithError(ErrorCode::NOENT, std::move(callback)); |
| } |
| |
| if (!current_process->Detach()) { |
| // At the moment this shouldn't happen, but we don't want to kill the |
| // debug session because of it. The details of the failure are already |
| // logged by Detach(). |
| return ReplyWithError(ErrorCode::PERM, std::move(callback)); |
| } |
| return ReplyOK(std::move(callback)); |
| } |
| |
| bool CommandHandler::Handle_g(ResponseCallback callback) { |
| // If there is no current process or if the current process isn't attached, |
| // then report an error. |
| inferior_control::Process* current_process = server_->current_process(); |
| if (!current_process || !current_process->IsAttached()) { |
| FXL_LOG(ERROR) << "g: No inferior"; |
| return ReplyWithError(ErrorCode::NOENT, std::move(callback)); |
| } |
| |
| // If there is no current thread, then we reply with "0"s for all registers. |
| // TODO(armansito): gG packets are technically used to read/write "ALL" |
| // registers, not just the general registers. We'll have to take this into |
| // account in the future, though for now we're just supporting general |
| // registers. |
| std::string result; |
| if (!server_->current_thread()) { |
| result = |
| inferior_control::Registers::GetUninitializedGeneralRegistersAsString(); |
| } else { |
| inferior_control::Registers* regs = server_->current_thread()->registers(); |
| FXL_DCHECK(regs); |
| result = regs->GetGeneralRegistersAsString(); |
| } |
| |
| if (result.empty()) { |
| FXL_LOG(ERROR) << "g: Failed to read register values"; |
| return ReplyWithError(ErrorCode::PERM, std::move(callback)); |
| } |
| |
| callback(result); |
| return true; |
| } |
| |
| bool CommandHandler::Handle_G(const fxl::StringView& packet, |
| ResponseCallback callback) { |
| // If there is no current process or if the current process isn't attached, |
| // then report an error. |
| inferior_control::Process* current_process = server_->current_process(); |
| if (!current_process || !current_process->IsAttached()) { |
| FXL_LOG(ERROR) << "G: No inferior"; |
| return ReplyWithError(ErrorCode::NOENT, std::move(callback)); |
| } |
| |
| // If there is no current thread report an error. |
| inferior_control::Thread* current_thread = server_->current_thread(); |
| if (!current_thread) { |
| FXL_LOG(ERROR) << "G: No current thread"; |
| return ReplyWithError(ErrorCode::NOENT, std::move(callback)); |
| } |
| |
| // We pass the packet here directly since Registers handles the parsing. |
| // TODO(armansito): gG packets are technically used to read/write "ALL" |
| // registers, not just the general registers. We'll have to take this into |
| // account in the future, though for now we're just supporting general |
| // registers. |
| if (!current_thread->registers()->SetGeneralRegistersFromString(packet)) { |
| FXL_LOG(ERROR) << "G: Failed to write to general registers"; |
| return ReplyWithError(ErrorCode::PERM, std::move(callback)); |
| } |
| if (!current_thread->registers()->WriteGeneralRegisters()) { |
| return ReplyWithError(ErrorCode::PERM, std::move(callback)); |
| } |
| |
| return ReplyOK(std::move(callback)); |
| } |
| |
| bool CommandHandler::Handle_H(const fxl::StringView& packet, |
| ResponseCallback callback) { |
| // Here we set the "current thread" for subsequent operations |
| // (‘m’, ‘M’, ‘g’, ‘G’, et.al.). |
| // There are two types of an H packet. 'c' and 'g'. We claim to not support |
| // 'c' because it's specified as deprecated. |
| |
| // Packet should at least contain 'c' or 'g' and some characters for the |
| // thread id. |
| if (packet.size() < 2) |
| return ReplyWithError(ErrorCode::INVAL, std::move(callback)); |
| |
| switch (packet[0]) { |
| case 'c': // fall through |
| case 'g': { |
| int64_t pid, tid; |
| bool has_pid; |
| if (!ParseThreadId(packet.substr(1), &has_pid, &pid, &tid)) |
| return ReplyWithError(ErrorCode::INVAL, std::move(callback)); |
| |
| // We currently support debugging only one process. |
| // TODO(armansito): What to do with a process ID? Replying with an empty |
| // packet for now. |
| if (has_pid) { |
| FXL_LOG(WARNING) |
| << "Specifying a pid while setting the current thread is" |
| << " not supported"; |
| return false; |
| } |
| |
| // Setting the current thread to "all threads" doesn't make much sense. |
| if (tid < 0) { |
| FXL_LOG(ERROR) << "Cannot set the current thread to all threads"; |
| return ReplyWithError(ErrorCode::INVAL, std::move(callback)); |
| } |
| |
| inferior_control::Process* current_process = server_->current_process(); |
| |
| // Note that at this point we may have a process but are not necessarily |
| // attached yet. GDB sends the Hg0 packet early on, and expects it to |
| // succeed. |
| if (!current_process) { |
| FXL_LOG(ERROR) << "No inferior exists"; |
| |
| // If we're given a positive thread ID but there is currently no |
| // inferior, then report error? |
| if (!tid) { |
| FXL_LOG(ERROR) << "Cannot set a current thread with no inferior"; |
| return ReplyWithError(ErrorCode::PERM, std::move(callback)); |
| } |
| |
| FXL_LOG(WARNING) << "Setting current thread to NULL for tid=0"; |
| |
| server_->SetCurrentThread(nullptr); |
| return ReplyOK(std::move(callback)); |
| } |
| |
| // If the process hasn't started yet it will have no threads. Since "Hg0" |
| // is one of the first things that GDB sends after a connection (and |
| // since we don't run the process right away), we lie to GDB and set the |
| // current thread to null. |
| if (!current_process->IsLive()) { |
| FXL_LOG(INFO) << "Current process has no threads yet but we pretend to " |
| << "set one"; |
| server_->SetCurrentThread(nullptr); |
| return ReplyOK(std::move(callback)); |
| } |
| |
| current_process->EnsureThreadMapFresh(); |
| |
| inferior_control::Thread* thread; |
| |
| // A thread ID value of 0 means "pick an arbitrary thread". |
| if (tid == 0) |
| thread = current_process->PickOneThread(); |
| else |
| thread = current_process->FindThreadById(tid); |
| |
| if (!thread) { |
| FXL_LOG(ERROR) << "Failed to set the current thread"; |
| return ReplyWithError(ErrorCode::PERM, std::move(callback)); |
| } |
| |
| server_->SetCurrentThread(thread); |
| return ReplyOK(std::move(callback)); |
| } |
| default: |
| break; |
| } |
| |
| return false; |
| } |
| |
| bool CommandHandler::Handle_m(const fxl::StringView& packet, |
| ResponseCallback callback) { |
| // If there is no current process or if the current process isn't attached, |
| // then report an error. |
| inferior_control::Process* current_process = server_->current_process(); |
| if (!current_process || !current_process->IsAttached()) { |
| FXL_LOG(ERROR) << "m: No inferior"; |
| return ReplyWithError(ErrorCode::NOENT, std::move(callback)); |
| } |
| |
| // The "m" packet should have two arguments for addr and length, separated by |
| // a single comma. |
| auto params = fxl::SplitString(packet, ",", fxl::kKeepWhitespace, |
| fxl::kSplitWantNonEmpty); |
| if (params.size() != 2) { |
| FXL_LOG(ERROR) << "m: Malformed packet: " << packet; |
| return ReplyWithError(ErrorCode::INVAL, std::move(callback)); |
| } |
| |
| uintptr_t addr; |
| size_t length; |
| if (!fxl::StringToNumberWithError<uintptr_t>(params[0], &addr, |
| fxl::Base::k16) || |
| !fxl::StringToNumberWithError<size_t>(params[1], &length, |
| fxl::Base::k16)) { |
| FXL_LOG(ERROR) << "m: Malformed params: " << packet; |
| return ReplyWithError(ErrorCode::NOENT, std::move(callback)); |
| } |
| |
| std::unique_ptr<uint8_t[]> buffer(new uint8_t[length]); |
| if (!current_process->ReadMemory(addr, buffer.get(), length)) { |
| FXL_LOG(ERROR) << "m: Failed to read memory"; |
| return ReplyWithError(ErrorCode::PERM, std::move(callback)); |
| } |
| |
| std::string result = |
| debugger_utils::EncodeByteArrayString(buffer.get(), length); |
| callback(result); |
| return true; |
| } |
| |
| bool CommandHandler::Handle_M(const fxl::StringView& packet, |
| ResponseCallback callback) { |
| // If there is no current process or if the current process isn't attached, |
| // then report an error. |
| inferior_control::Process* current_process = server_->current_process(); |
| if (!current_process || !current_process->IsAttached()) { |
| FXL_LOG(ERROR) << "M: No inferior"; |
| return ReplyWithError(ErrorCode::NOENT, std::move(callback)); |
| } |
| |
| // The "M" packet parameters look like this: "addr,length:XX...". |
| // First, extract the addr,len and data sections. Using fxl::kSplitWantAll |
| // here since the data portion could technically be empty if the given length |
| // is 0. |
| auto params = |
| fxl::SplitString(packet, ":", fxl::kKeepWhitespace, fxl::kSplitWantAll); |
| if (params.size() != 2) { |
| FXL_LOG(ERROR) << "M: Malformed packet: " << packet; |
| return ReplyWithError(ErrorCode::INVAL, std::move(callback)); |
| } |
| |
| fxl::StringView data = params[1]; |
| |
| // Extract addr and len |
| params = fxl::SplitString(params[0], ",", fxl::kKeepWhitespace, |
| fxl::kSplitWantNonEmpty); |
| if (params.size() != 2) { |
| FXL_LOG(ERROR) << "M: Malformed packet: " << packet; |
| return ReplyWithError(ErrorCode::INVAL, std::move(callback)); |
| } |
| |
| uintptr_t addr; |
| size_t length; |
| if (!fxl::StringToNumberWithError<uintptr_t>(params[0], &addr, |
| fxl::Base::k16) || |
| !fxl::StringToNumberWithError<size_t>(params[1], &length, |
| fxl::Base::k16)) { |
| FXL_LOG(ERROR) << "M: Malformed params: " << packet; |
| return ReplyWithError(ErrorCode::INVAL, std::move(callback)); |
| } |
| |
| FXL_VLOG(1) << fxl::StringPrintf("M: addr=0x%" PRIxPTR ", len=%lu", addr, |
| length); |
| |
| auto data_bytes = debugger_utils::DecodeByteArrayString(data); |
| if (data_bytes.size() != length) { |
| FXL_LOG(ERROR) << "M: payload length doesn't match length argument - " |
| << "payload size: " << data_bytes.size() |
| << ", length requested: " << length; |
| return ReplyWithError(ErrorCode::INVAL, std::move(callback)); |
| } |
| |
| // Short-circuit if |length| is 0. |
| if (length && |
| !current_process->WriteMemory(addr, data_bytes.data(), length)) { |
| FXL_LOG(ERROR) << "M: Failed to write memory"; |
| |
| // TODO(armansito): The error code definitions from GDB aren't really |
| // granular enough to aid debug various error conditions (e.g. we may want |
| // to report why the memory write failed based on the zx_status_t returned |
| // from Zircon). (See TODO in util.h). |
| return ReplyWithError(ErrorCode::PERM, std::move(callback)); |
| } |
| |
| return ReplyOK(std::move(callback)); |
| } |
| |
| bool CommandHandler::Handle_q(const fxl::StringView& prefix, |
| const fxl::StringView& params, |
| ResponseCallback callback) { |
| if (prefix == kAttached) |
| return HandleQueryAttached(params, std::move(callback)); |
| |
| if (prefix == kCurrentThreadId) |
| return HandleQueryCurrentThreadId(params, std::move(callback)); |
| |
| if (prefix == kFirstThreadInfo) |
| return HandleQueryThreadInfo(true, std::move(callback)); |
| |
| // The qRcmd packet is different than most. It uses , as a delimiter, not :. |
| if (StartsWith(prefix, kRcmd)) |
| return HandleQueryRcmd(prefix.substr(std::strlen(kRcmd)), |
| std::move(callback)); |
| |
| if (prefix == kSubsequentThreadInfo) |
| return HandleQueryThreadInfo(false, std::move(callback)); |
| |
| if (prefix == kSupported) |
| return HandleQuerySupported(params, std::move(callback)); |
| |
| if (prefix == kXfer) |
| return HandleQueryXfer(params, std::move(callback)); |
| |
| // TODO(dje): TO-195 |
| // - QDisableRandomization:VALUE ? |
| // - qGetTLSAddr:THREAD-ID,OFFSET,LM |
| // - qThreadExtraInfo,THREAD-ID ? |
| |
| return false; |
| } |
| |
| bool CommandHandler::Handle_Q(const fxl::StringView& prefix, |
| const fxl::StringView& params, |
| ResponseCallback callback) { |
| if (prefix == kNonStop) |
| return HandleSetNonStop(params, std::move(callback)); |
| |
| return false; |
| } |
| |
| bool CommandHandler::Handle_T(const fxl::StringView& packet, |
| ResponseCallback callback) { |
| // If there is no current process or if the current process isn't attached, |
| // then report an error. |
| inferior_control::Process* current_process = server_->current_process(); |
| if (!current_process || !current_process->IsAttached()) { |
| FXL_LOG(ERROR) << "T: No inferior"; |
| return ReplyWithError(ErrorCode::NOENT, std::move(callback)); |
| } |
| |
| zx_koid_t tid; |
| if (!fxl::StringToNumberWithError<zx_koid_t>(packet, &tid, fxl::Base::k16)) { |
| FXL_LOG(ERROR) << "T: Malformed thread id given: " << packet; |
| return ReplyWithError(ErrorCode::INVAL, std::move(callback)); |
| } |
| |
| inferior_control::Thread* thread = current_process->FindThreadById(tid); |
| if (!thread) { |
| FXL_LOG(ERROR) << "T: no such thread: " << packet; |
| return ReplyWithError(ErrorCode::NOENT, std::move(callback)); |
| } |
| if (!thread->IsLive()) { |
| FXL_LOG(ERROR) << "T: thread found, but not live: " << packet; |
| return ReplyWithError(ErrorCode::NOENT, std::move(callback)); |
| } |
| |
| return ReplyOK(std::move(callback)); |
| } |
| |
| bool CommandHandler::Handle_v(const fxl::StringView& packet, |
| ResponseCallback callback) { |
| if (StartsWith(packet, kAttach)) |
| return Handle_vAttach(packet.substr(std::strlen(kAttach)), |
| std::move(callback)); |
| if (StartsWith(packet, kCont)) |
| return Handle_vCont(packet.substr(std::strlen(kCont)), std::move(callback)); |
| if (StartsWith(packet, kKill)) |
| return Handle_vKill(packet.substr(std::strlen(kKill)), std::move(callback)); |
| if (StartsWith(packet, kRun)) |
| return Handle_vRun(packet.substr(std::strlen(kRun)), std::move(callback)); |
| |
| return false; |
| } |
| |
| bool CommandHandler::Handle_zZ(bool insert, const fxl::StringView& packet, |
| ResponseCallback callback) { |
| // Z0 needs more work. Disabled until ready. |
| // One issue is we need to support the swbreak feature. |
| #if 0 |
| // A Z packet contains the "type,addr,kind" parameters before all other |
| // optional parameters, which follow an optional ';' character. Check to see |
| // if there are any optional parameters: |
| size_t semicolon = packet.find(';'); |
| |
| // fxl::StringView::find returns npos if it can't find the character. Adjust |
| // |semicolon| to point just beyond the end of |packet| so that |
| // packet.substr() works.. |
| if (semicolon == fxl::StringView::npos) |
| semicolon = packet.size(); |
| |
| auto params = fxl::SplitString(packet.substr(0, semicolon), ",", |
| fxl::kKeepWhitespace, fxl::kSplitWantNonEmpty); |
| if (params.size() != 3) { |
| FXL_LOG(ERROR) << "zZ: 3 required parameters missing"; |
| return ReplyWithError(ErrorCode::INVAL, std::move(callback)); |
| } |
| |
| size_t type; |
| uintptr_t addr; |
| size_t kind; |
| if (!fxl::StringToNumberWithError<uintptr_t>(params[0], &type, |
| fxl::Base::k16) || |
| !fxl::StringToNumberWithError<uintptr_t>(params[1], &addr, |
| fxl::Base::k16) || |
| !fxl::StringToNumberWithError<size_t>(params[2], &kind, fxl::Base::k16)) { |
| FXL_LOG(ERROR) << "zZ: Failed to parse |type|, |addr| and |kind|"; |
| return ReplyWithError(ErrorCode::INVAL, std::move(callback)); |
| } |
| |
| auto optional_params = packet.substr(semicolon); |
| |
| // "Remove breakpoint" packets don't contain any optional fields. |
| if (!insert && !optional_params.empty()) { |
| FXL_LOG(ERROR) << "zZ: Malformed packet"; |
| return ReplyWithError(ErrorCode::INVAL, std::move(callback)); |
| } |
| |
| switch (type) { |
| case 0: |
| if (insert) |
| return InsertSoftwareBreakpoint(addr, kind, optional_params, std::move(callback)); |
| return RemoveSoftwareBreakpoint(addr, kind, std::move(callback)); |
| default: |
| break; |
| } |
| |
| FXL_LOG(WARNING) << "Breakpoints of type " << type |
| << " currently not supported"; |
| #endif |
| return false; |
| } |
| |
| bool CommandHandler::HandleQueryAttached(const fxl::StringView& params, |
| ResponseCallback callback) { |
| // We don't support multiprocessing yet, so make sure we received the version |
| // of qAttached that doesn't have a "pid" parameter. |
| if (!params.empty()) |
| return ReplyWithError(ErrorCode::INVAL, std::move(callback)); |
| |
| // The response is "1" if we attached to an existing process, or "0" if we |
| // created a new one. We currently don't support the former, so always send |
| // "0". |
| callback("0"); |
| return true; |
| } |
| |
| bool CommandHandler::HandleQueryCurrentThreadId(const fxl::StringView& params, |
| ResponseCallback callback) { |
| // The "qC" packet has no parameters. |
| if (!params.empty()) |
| return ReplyWithError(ErrorCode::INVAL, std::move(callback)); |
| |
| inferior_control::Thread* current_thread = server_->current_thread(); |
| if (!current_thread) { |
| // If there is a current process and it has been started, pick one thread |
| // and set that as the current one. This is our work around for lying to GDB |
| // about setting a current thread in response to an early Hg0 packet. |
| inferior_control::Process* current_process = server_->current_process(); |
| if (!current_process || !current_process->IsLive()) { |
| FXL_LOG(ERROR) << "qC: Current thread has not been set"; |
| return ReplyWithError(ErrorCode::PERM, std::move(callback)); |
| } |
| |
| FXL_VLOG(1) << "qC: Picking one arbitrary thread"; |
| current_thread = current_process->PickOneThread(); |
| if (!current_thread) { |
| FXL_VLOG(1) << "qC: Failed to pick a thread"; |
| return ReplyWithError(ErrorCode::PERM, std::move(callback)); |
| } |
| } |
| |
| std::string thread_id = |
| fxl::NumberToString<zx_koid_t>(current_thread->id(), fxl::Base::k16); |
| |
| std::string reply = "QC" + thread_id; |
| callback(reply); |
| return true; |
| } |
| |
| bool CommandHandler::HandleQueryRcmd(const fxl::StringView& command, |
| ResponseCallback callback) { |
| auto cmd_string = debugger_utils::DecodeString(command); |
| std::vector<fxl::StringView> argv = fxl::SplitString( |
| cmd_string, " ", fxl::kTrimWhitespace, fxl::kSplitWantNonEmpty); |
| if (argv.size() == 0) { |
| // No command, just reply OK. |
| return ReplyOK(std::move(callback)); |
| } |
| auto cmd = argv[0]; |
| |
| // We support both because qemu uses "quit" and GNU gdbserver uses "exit". |
| if (cmd == kQuit || cmd == kExit) { |
| if (argv.size() != 1) |
| goto bad_command; |
| ReplyOK(std::move(callback)); |
| server_->PostQuitMessageLoop(true); |
| } else if (cmd == kHelp) { |
| if (argv.size() != 1) |
| goto bad_command; |
| static constexpr char kHelpText[] = |
| "help - print this help text\n" |
| "exit - quit debugserver\n" |
| "quit - quit debugserver\n" |
| "set <parameter> <value>\n" |
| "show <parameter>\n" |
| "\n" |
| "Parameters:\n" |
| " verbosity - useful range is -2 to 3 (-2 is most verbose)\n"; |
| callback(debugger_utils::EncodeString(kHelpText)); |
| } else if (cmd == kSet) { |
| if (argv.size() != 3) |
| goto bad_command; |
| if (!server_->SetParameter(argv[1], argv[2])) |
| goto bad_command; |
| ReplyOK(std::move(callback)); |
| } else if (cmd == kShow) { |
| if (argv.size() != 2) |
| goto bad_command; |
| std::string value; |
| if (!server_->GetParameter(argv[1], &value)) |
| goto bad_command; |
| callback(debugger_utils::EncodeString("Value is " + value + "\n")); |
| } else { |
| callback(debugger_utils::EncodeString("Invalid monitor command\n")); |
| } |
| |
| return true; |
| |
| bad_command: |
| // Errors are not reported via the usual mechanism. For rCmd, the usual |
| // mechanism is for things like protocol errors. Instead we just want to |
| // return the desired error message. |
| callback(debugger_utils::EncodeString("Invalid command\n")); |
| return true; |
| } |
| |
| bool CommandHandler::HandleQuerySupported(const fxl::StringView& params, |
| ResponseCallback callback) { |
| // We ignore the parameters for qSupported. Respond with the supported |
| // features. |
| callback(kSupportedFeatures); |
| return true; |
| } |
| |
| bool CommandHandler::HandleSetNonStop(const fxl::StringView& params, |
| ResponseCallback callback) { |
| // The only values we accept are "1" and "0". |
| if (params.size() != 1) |
| return ReplyWithError(ErrorCode::INVAL, std::move(callback)); |
| |
| // We currently only support non-stop mode. |
| char value = params[0]; |
| if (value == '1') |
| return ReplyOK(std::move(callback)); |
| |
| if (value == '0') |
| return ReplyWithError(ErrorCode::PERM, std::move(callback)); |
| |
| FXL_LOG(ERROR) << "QNonStop received with invalid value: " << (unsigned)value; |
| return ReplyWithError(ErrorCode::INVAL, std::move(callback)); |
| } |
| |
| bool CommandHandler::HandleQueryThreadInfo(bool is_first, |
| ResponseCallback callback) { |
| FXL_DCHECK(server_); |
| |
| inferior_control::Process* current_process = server_->current_process(); |
| if (!current_process) { |
| FXL_LOG(ERROR) << "Current process is not set"; |
| return ReplyWithError(ErrorCode::PERM, std::move(callback)); |
| } |
| |
| // For the "first" thread info query we reply with the complete list of |
| // threads and always report "end of list" for subsequent queries. The GDB |
| // Remote Protocol does not seem to define a MTU, however, we could be running |
| // on a platform with resource constraints that may require us to break up the |
| // sequence into multiple packets. For now we do not worry about this. |
| |
| if (!is_first) { |
| // This is a subsequent query. Check that a thread info query sequence was |
| // started (just for sanity) and report end of list. |
| if (!in_thread_info_sequence_) { |
| FXL_LOG(ERROR) << "qsThreadInfo received without first receiving " |
| << "qfThreadInfo"; |
| return ReplyWithError(ErrorCode::PERM, std::move(callback)); |
| } |
| |
| in_thread_info_sequence_ = false; |
| callback("l"); |
| return true; |
| } |
| |
| // This is the first query. Check the sequence state for sanity. |
| if (in_thread_info_sequence_) { |
| FXL_LOG(ERROR) << "qfThreadInfo received while already in an active " |
| << "sequence"; |
| return ReplyWithError(ErrorCode::PERM, std::move(callback)); |
| } |
| |
| current_process->EnsureThreadMapFresh(); |
| |
| std::deque<std::string> thread_ids; |
| size_t buf_size = 0; |
| current_process->ForEachLiveThread( |
| [&thread_ids, &buf_size](inferior_control::Thread* thread) { |
| std::string thread_id = |
| fxl::NumberToString<zx_koid_t>(thread->id(), fxl::Base::k16); |
| buf_size += thread_id.length(); |
| thread_ids.push_back(thread_id); |
| }); |
| |
| if (thread_ids.empty()) { |
| // No ids to report. End of sequence. |
| callback("l"); |
| return true; |
| } |
| |
| in_thread_info_sequence_ = true; |
| |
| // Add the number of commas (|thread_ids.size() - 1|) plus the prefix "m") |
| buf_size += thread_ids.size(); |
| |
| std::unique_ptr<char[]> buffer(new char[buf_size]); |
| buffer.get()[0] = 'm'; |
| debugger_utils::JoinStrings(thread_ids, ',', buffer.get() + 1, buf_size - 1); |
| |
| callback(fxl::StringView(buffer.get(), buf_size)); |
| |
| return true; |
| } |
| |
| bool CommandHandler::HandleQueryXfer(const fxl::StringView& params, |
| ResponseCallback callback) { |
| // We only support qXfer:auxv:read:: |
| // TODO(dje): TO-195 |
| // - qXfer::osdata::read::OFFSET,LENGTH |
| // - qXfer:memory-map:read::OFFSET,LENGTH ? |
| // - qXfer:libraries-svr4:read:ANNEX:OFFSET,LENGTH ? |
| // - qXfer:features:read:ANNEX:OFFSET,LENGTH ? |
| fxl::StringView auxv_read("auxv:read::"); |
| if (!StartsWith(params, auxv_read)) |
| return false; |
| |
| // Parse offset,length |
| auto args = fxl::SplitString(params.substr(auxv_read.size()), ",", |
| fxl::kKeepWhitespace, fxl::kSplitWantNonEmpty); |
| if (args.size() != 2) { |
| FXL_LOG(ERROR) << "qXfer:auxv:read:: Malformed params: " << params; |
| return ReplyWithError(ErrorCode::INVAL, std::move(callback)); |
| } |
| |
| size_t offset, length; |
| if (!fxl::StringToNumberWithError<size_t>(args[0], &offset, fxl::Base::k16) || |
| !fxl::StringToNumberWithError<size_t>(args[1], &length, fxl::Base::k16)) { |
| FXL_LOG(ERROR) << "qXfer:auxv:read:: Malformed params: " << params; |
| return ReplyWithError(ErrorCode::INVAL, std::move(callback)); |
| } |
| |
| inferior_control::Process* current_process = server_->current_process(); |
| if (!current_process) { |
| FXL_LOG(ERROR) << "qXfer:auxv:read: No current process is not set"; |
| return ReplyWithError(ErrorCode::PERM, std::move(callback)); |
| } |
| |
| // Build the auxiliary vector. This definition is provided by the Linux manual |
| // page for the proc pseudo-filesystem (i.e. 'man proc'): |
| // "This contains the contents of the ELF interpreter information passed to |
| // the process at exec time. The format is one unsigned long ID plus one |
| // unsigned long value for each entry. The last entry contains two zeros." |
| // On Fuchsia we borrow this concept to save inventing something new. |
| // We may have to eventually, but this works for now. |
| // There is an extra complication that all the needed values aren't available |
| // when the process starts: e.g., AT_ENTRY - the executable isn't loaded |
| // until sometime after the process starts. |
| constexpr size_t kMaxAuxvEntries = 10; |
| struct { |
| unsigned long key; |
| unsigned long value; |
| } auxv[kMaxAuxvEntries]; |
| |
| #define ADD_AUXV(_key, _value) \ |
| do { \ |
| auxv[n].key = (_key); \ |
| auxv[n].value = (_value); \ |
| ++n; \ |
| } while (0) |
| |
| size_t n = 0; |
| ADD_AUXV(AT_BASE, current_process->base_address()); |
| if (current_process->DsosLoaded()) { |
| const debugger_utils::dsoinfo_t* exec = current_process->GetExecDso(); |
| if (exec) { |
| ADD_AUXV(AT_ENTRY, exec->entry); |
| ADD_AUXV(AT_PHDR, exec->phdr); |
| ADD_AUXV(AT_PHENT, exec->phentsize); |
| ADD_AUXV(AT_PHNUM, exec->phnum); |
| } |
| } |
| ADD_AUXV(AT_NULL, 0); |
| FXL_DCHECK(n <= arraysize(auxv)); |
| |
| #undef ADD_AUXV |
| |
| // We allow setting sizeof(auxv) as the offset, which would effectively result |
| // in reading 0 bytes. |
| if (offset > sizeof(auxv)) { |
| FXL_LOG(ERROR) << "qXfer:auxv:read: invalid offset"; |
| return ReplyWithError(ErrorCode::INVAL, std::move(callback)); |
| } |
| |
| size_t end = n * sizeof(auxv[0]); |
| size_t rsp_len = std::min(end - offset, length); |
| char rsp[1 + rsp_len]; |
| |
| rsp[0] = 'l'; |
| memcpy(rsp + 1, auxv + offset, rsp_len); |
| |
| callback(fxl::StringView(rsp, sizeof(rsp))); |
| return true; |
| } |
| |
| bool CommandHandler::Handle_vAttach(const fxl::StringView& packet, |
| ResponseCallback callback) { |
| // TODO(dje): The terminology we use makes this confusing. |
| // Here when you see "process" think "inferior". An inferior must be created |
| // first, and then we can attach the inferior to a process. |
| inferior_control::Process* current_process = server_->current_process(); |
| if (!current_process) { |
| FXL_LOG(ERROR) << "vAttach: no inferior selected"; |
| return ReplyWithError(ErrorCode::PERM, std::move(callback)); |
| } |
| |
| zx_koid_t pid; |
| if (!fxl::StringToNumberWithError<zx_koid_t>(packet, &pid, fxl::Base::k16)) { |
| FXL_LOG(ERROR) << "vAttach:: Malformed pid: " << packet; |
| return ReplyWithError(ErrorCode::INVAL, std::move(callback)); |
| } |
| |
| switch (current_process->state()) { |
| case inferior_control::Process::State::kNew: |
| case inferior_control::Process::State::kGone: |
| break; |
| default: |
| FXL_LOG(ERROR) |
| << "vAttach: need to kill the currently running process first"; |
| return ReplyWithError(ErrorCode::PERM, std::move(callback)); |
| } |
| |
| if (!current_process->Attach(pid)) { |
| FXL_LOG(ERROR) << "vAttach: failed to attach to inferior " << pid; |
| return ReplyWithError(ErrorCode::PERM, std::move(callback)); |
| } |
| |
| // It's Attach()'s job to mark the process as live, since it knows we just |
| // attached to an already running program. |
| FXL_DCHECK(current_process->IsLive()); |
| |
| return ReplyOK(std::move(callback)); |
| } |
| |
| bool CommandHandler::Handle_vCont(const fxl::StringView& packet, |
| ResponseCallback callback) { |
| inferior_control::Process* current_process = server_->current_process(); |
| if (!current_process) { |
| FXL_LOG(ERROR) << "vCont: no current process to run!"; |
| return ReplyWithError(ErrorCode::PERM, std::move(callback)); |
| } |
| |
| ThreadActionList actions(packet, current_process->id()); |
| if (!actions.valid()) { |
| FXL_LOG(ERROR) << "vCont: \"" << packet << "\": error / not supported."; |
| return ReplyWithError(ErrorCode::INVAL, std::move(callback)); |
| } |
| |
| FXL_DCHECK(current_process->IsLive()); |
| FXL_DCHECK(current_process->IsAttached()); |
| |
| // Before we start calling GetAction we need to resolve "pick one" thread |
| // values. |
| for (auto e : actions.actions()) { |
| if (e.tid() == 0) { |
| FXL_DCHECK(e.pid() > 0); |
| // TODO(dje): For now we assume there is only one process. |
| FXL_DCHECK(current_process->id() == e.pid() || |
| e.pid() == ThreadActionList::kAll); |
| inferior_control::Thread* t = current_process->PickOneThread(); |
| if (t) |
| e.set_picked_tid(t->id()); |
| } |
| } |
| actions.MarkPickOnesResolved(); |
| |
| // First pass over all actions: Find any errors that we can so that we |
| // don't cause any thread to run if there's an error. |
| |
| bool action_list_ok = true; |
| current_process->ForEachLiveThread( |
| [&actions, ok_ptr = &action_list_ok](inferior_control::Thread* thread) { |
| zx_koid_t pid = thread->process()->id(); |
| zx_koid_t tid = thread->id(); |
| ThreadActionList::Action action = actions.GetAction(pid, tid); |
| switch (action) { |
| case ThreadActionList::Action::kStep: |
| switch (thread->state()) { |
| case inferior_control::Thread::State::kNew: |
| FXL_LOG(ERROR) << "vCont;s: can't step thread in kNew state"; |
| *ok_ptr = false; |
| return; |
| default: |
| break; |
| } |
| default: |
| break; |
| } |
| }); |
| if (!action_list_ok) |
| return ReplyWithError(ErrorCode::INVAL, std::move(callback)); |
| |
| current_process->ForEachLiveThread( |
| [&actions](inferior_control::Thread* thread) { |
| zx_koid_t pid = thread->process()->id(); |
| zx_koid_t tid = thread->id(); |
| ThreadActionList::Action action = actions.GetAction(pid, tid); |
| FXL_VLOG(1) << "vCont; Thread " << thread->GetDebugName() |
| << " state: " << thread->StateName(thread->state()) |
| << " action: " << ThreadActionList::ActionToString(action); |
| switch (action) { |
| case ThreadActionList::Action::kContinue: |
| switch (thread->state()) { |
| case inferior_control::Thread::State::kNew: |
| case inferior_control::Thread::State::kStopped: |
| thread->Resume(); |
| break; |
| default: |
| break; |
| } |
| case ThreadActionList::Action::kStep: |
| switch (thread->state()) { |
| case inferior_control::Thread::State::kStopped: |
| thread->Step(); |
| break; |
| default: |
| break; |
| } |
| default: |
| break; |
| } |
| }); |
| |
| // We defer sending a stop-reply packet. Server will send it out when threads |
| // stop. At this point in time GDB is just expecting "OK". |
| return ReplyOK(std::move(callback)); |
| } |
| |
| bool CommandHandler::Handle_vKill(const fxl::StringView& packet, |
| ResponseCallback callback) { |
| FXL_VLOG(2) << "Handle_vKill: " << packet; |
| |
| inferior_control::Process* current_process = server_->current_process(); |
| if (!current_process) { |
| // This can't happen today, but it might eventually. |
| FXL_LOG(ERROR) << "vRun: no current process to kill!"; |
| return ReplyWithError(ErrorCode::PERM, std::move(callback)); |
| } |
| |
| zx_koid_t pid; |
| if (!fxl::StringToNumberWithError<zx_koid_t>(packet, &pid, fxl::Base::k16)) { |
| FXL_LOG(ERROR) << "vAttach:: Malformed pid: " << packet; |
| return ReplyWithError(ErrorCode::INVAL, std::move(callback)); |
| } |
| |
| // Since we only support one process at the moment, only allow killing |
| // that one. |
| if (pid != current_process->id()) { |
| FXL_LOG(ERROR) << "vAttach:: not our pid: " << pid; |
| return ReplyWithError(ErrorCode::INVAL, std::move(callback)); |
| } |
| |
| switch (current_process->state()) { |
| case inferior_control::Process::State::kNew: |
| case inferior_control::Process::State::kGone: |
| FXL_LOG(ERROR) << "vKill: process not running"; |
| return ReplyWithError(ErrorCode::PERM, std::move(callback)); |
| default: |
| break; |
| } |
| |
| if (!current_process->Kill()) { |
| FXL_LOG(ERROR) << "Failed to kill inferior"; |
| return ReplyWithError(ErrorCode::PERM, std::move(callback)); |
| } |
| |
| return ReplyOK(std::move(callback)); |
| } |
| |
| bool CommandHandler::Handle_vRun(const fxl::StringView& packet, |
| ResponseCallback callback) { |
| FXL_VLOG(2) << "Handle_vRun: " << packet; |
| |
| inferior_control::Process* current_process = server_->current_process(); |
| if (!current_process) { |
| // This can't happen today, but it might eventually. |
| FXL_LOG(ERROR) << "vRun: no current process to run!"; |
| return ReplyWithError(ErrorCode::PERM, std::move(callback)); |
| } |
| |
| if (!packet.empty()) { |
| std::vector<std::string> argv = BuildArgvFor_vRun(packet); |
| current_process->set_argv(argv); |
| } |
| |
| switch (current_process->state()) { |
| case inferior_control::Process::State::kNew: |
| case inferior_control::Process::State::kGone: |
| break; |
| default: |
| FXL_LOG(ERROR) |
| << "vRun: need to kill the currently running process first"; |
| return ReplyWithError(ErrorCode::PERM, std::move(callback)); |
| } |
| |
| if (!current_process->Initialize()) { |
| FXL_LOG(ERROR) << "Failed to set up inferior"; |
| return ReplyWithError(ErrorCode::PERM, std::move(callback)); |
| } |
| |
| // On Linux, the program is considered "live" after vRun, e.g. $pc is set. On |
| // Zircon, calling zx_process_start (called by Process::Start()) creates a |
| // synthetic exception of type ZX_EXCP_START if a debugger is attached to the |
| // process and halts until a call to zx_task_resume_from_exception (i.e. |
| // called by Thread::Resume() in gdbserver). |
| if (!current_process->Start()) { |
| FXL_LOG(ERROR) << "vRun: Failed to start process"; |
| return ReplyWithError(ErrorCode::PERM, std::move(callback)); |
| } |
| |
| FXL_DCHECK(current_process->IsLive()); |
| |
| // We defer sending a stop-reply packet. Server will send it out when it |
| // receives an OnThreadStarting() event from |current_process|. |
| |
| return true; |
| } |
| |
| bool CommandHandler::InsertSoftwareBreakpoint( |
| uintptr_t addr, size_t kind, const fxl::StringView& optional_params, |
| ResponseCallback callback) { |
| FXL_VLOG(1) << fxl::StringPrintf( |
| "Insert software breakpoint at %" PRIxPTR ", kind: %lu", addr, kind); |
| |
| inferior_control::Process* current_process = server_->current_process(); |
| if (!current_process) { |
| FXL_LOG(ERROR) << "No current process exists"; |
| return ReplyWithError(ErrorCode::PERM, std::move(callback)); |
| } |
| |
| // TODO(armansito): Handle |optional_params|. |
| |
| if (!current_process->breakpoints()->InsertSoftwareBreakpoint(addr, kind)) { |
| FXL_LOG(ERROR) << "Failed to insert software breakpoint"; |
| return ReplyWithError(ErrorCode::PERM, std::move(callback)); |
| } |
| |
| return ReplyOK(std::move(callback)); |
| } |
| |
| bool CommandHandler::RemoveSoftwareBreakpoint(uintptr_t addr, size_t kind, |
| ResponseCallback callback) { |
| FXL_VLOG(1) << fxl::StringPrintf("Remove software breakpoint at %" PRIxPTR, |
| addr); |
| |
| inferior_control::Process* current_process = server_->current_process(); |
| if (!current_process) { |
| FXL_LOG(ERROR) << "No current process exists"; |
| return ReplyWithError(ErrorCode::PERM, std::move(callback)); |
| } |
| |
| if (!current_process->breakpoints()->RemoveSoftwareBreakpoint(addr)) { |
| FXL_LOG(ERROR) << "Failed to remove software breakpoint"; |
| return ReplyWithError(ErrorCode::PERM, std::move(callback)); |
| } |
| |
| return ReplyOK(std::move(callback)); |
| } |
| |
| } // namespace debugserver |