| // 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 "server.h" |
| |
| #include <arpa/inet.h> |
| #include <lib/async/cpp/task.h> |
| #include <lib/fit/defer.h> |
| #include <sys/socket.h> |
| |
| #include <array> |
| #include <cstdlib> |
| #include <limits> |
| #include <string> |
| #include <vector> |
| |
| #include "garnet/lib/debugger_utils/jobs.h" |
| #include "garnet/lib/debugger_utils/sysinfo.h" |
| #include "garnet/lib/debugger_utils/util.h" |
| |
| #include "lib/fxl/log_settings.h" |
| #include "lib/fxl/logging.h" |
| #include "lib/fxl/strings/string_number_conversions.h" |
| #include "lib/fxl/strings/string_printf.h" |
| #include "lib/fxl/strings/string_view.h" |
| |
| #include "stop_reply_packet.h" |
| #include "util.h" |
| |
| namespace debugserver { |
| |
| namespace { |
| |
| constexpr char kStopNotification[] = "Stop"; |
| constexpr char kStopAck[] = "vStopped"; |
| |
| } // namespace |
| |
| RspServer::PendingNotification::PendingNotification( |
| const fxl::StringView& name, const fxl::StringView& event, |
| const fxl::TimeDelta& timeout) |
| : name(name.data(), name.size()), |
| event(event.data(), event.size()), |
| timeout(timeout) {} |
| |
| RspServer::RspServer(uint16_t port, zx_koid_t initial_attach_pid) |
| : ServerWithIO(debugger_utils::GetRootJob(), |
| debugger_utils::GetDefaultJob()), |
| port_(port), |
| initial_attach_pid_(initial_attach_pid), |
| server_sock_(-1), |
| command_handler_(this) {} |
| |
| bool RspServer::Run() { |
| FXL_DCHECK(!io_loop_); |
| |
| if (!exception_port_.Run()) { |
| FXL_LOG(ERROR) << "Failed to initialize exception port!"; |
| return false; |
| } |
| |
| auto cleanup_exception_port = fit::defer([&]() { |
| // Tell the exception port to quit and wait for it to finish. |
| FXL_VLOG(2) << "Quitting exception port thread."; |
| exception_port_.Quit(); |
| }); |
| |
| // If we're to attach to a running process at start-up, do so here. |
| // This needs to be done after |exception_port_| is set up. |
| if (initial_attach_pid_ != ZX_KOID_INVALID) { |
| auto inferior = current_process(); |
| FXL_DCHECK(!inferior->IsAttached()); |
| if (!inferior->Attach(initial_attach_pid_)) { |
| FXL_LOG(ERROR) << "Failed to attach to inferior"; |
| return false; |
| } |
| FXL_DCHECK(inferior->IsAttached()); |
| // It's Attach()'s job to mark the process as live, since it knows we just |
| // attached to an already running program. |
| FXL_DCHECK(inferior->IsLive()); |
| } |
| |
| // TODO(dje): Continually re-listen for connections when debugger goes |
| // away, with new option to control this (--listen=once|loop or whatever). |
| |
| // Listen for an incoming connection. |
| if (!Listen()) |
| return false; |
| |
| // |client_sock_| should be ready to be consumed now. |
| FXL_DCHECK(client_sock_.is_valid()); |
| |
| io_loop_ = |
| std::make_unique<RspIOLoop>(client_sock_.get(), this, &message_loop_); |
| io_loop_->Run(); |
| |
| // Start the main loop. |
| message_loop_.Run(); |
| |
| FXL_LOG(INFO) << "Main loop exited"; |
| |
| // Tell the I/O loop to quit its message loop and wait for it to finish. |
| io_loop_->Quit(); |
| |
| return run_status_; |
| } |
| |
| void RspServer::QueueNotification(const fxl::StringView& name, |
| const fxl::StringView& event, |
| const fxl::TimeDelta& timeout) { |
| // The GDB Remote protocol defines only the "Stop" notification |
| FXL_DCHECK(name == kStopNotification); |
| |
| FXL_VLOG(1) << "Preparing notification: " << name << ":" << event; |
| |
| notify_queue_.push( |
| std::make_unique<PendingNotification>(name, event, timeout)); |
| TryPostNextNotification(); |
| } |
| |
| void RspServer::QueueStopNotification(const fxl::StringView& event, |
| const fxl::TimeDelta& timeout) { |
| QueueNotification(kStopNotification, event, timeout); |
| } |
| |
| bool RspServer::SetParameter(const fxl::StringView& parameter, |
| const fxl::StringView& value) { |
| if (parameter == "verbosity") { |
| int verbosity; |
| if (!fxl::StringToNumberWithError<int>(value, &verbosity)) { |
| FXL_LOG(ERROR) << "Malformed verbosity level: " << value; |
| return false; |
| } |
| // We only support verbosity levels up to 2 (recorded as -2) but there's |
| // no point in disallowing higher levels (larger negative values). OTOH, |
| // we do want to catch bad severity levels (positive values). |
| if (verbosity >= static_cast<int>(fxl::LOG_NUM_SEVERITIES)) { |
| FXL_LOG(ERROR) << "Invalid verbosity level: " << value; |
| return false; |
| } |
| fxl::LogSettings log_settings = fxl::GetLogSettings(); |
| log_settings.min_log_level = static_cast<fxl::LogSeverity>(verbosity); |
| fxl::SetLogSettings(log_settings); |
| return true; |
| } else { |
| FXL_LOG(ERROR) << "Invalid parameter: " << parameter; |
| return false; |
| } |
| } |
| |
| bool RspServer::GetParameter(const fxl::StringView& parameter, |
| std::string* value) { |
| if (parameter == "verbosity") { |
| *value = fxl::NumberToString<int>(fxl::GetMinLogLevel()); |
| return true; |
| } else { |
| FXL_LOG(ERROR) << "Invalid parameter: " << parameter; |
| return false; |
| } |
| } |
| |
| bool RspServer::Listen() { |
| FXL_DCHECK(!server_sock_.is_valid()); |
| FXL_DCHECK(!client_sock_.is_valid()); |
| |
| fxl::UniqueFD server_sock(socket(AF_INET, SOCK_STREAM, 0)); |
| if (!server_sock.is_valid()) { |
| FXL_LOG(ERROR) << "Failed to open socket" |
| << ", " << debugger_utils::ErrnoString(errno); |
| return false; |
| } |
| |
| // Bind to a local address for listening. |
| struct sockaddr_in addr; |
| memset(&addr, 0, sizeof(addr)); |
| addr.sin_family = AF_INET; |
| addr.sin_addr.s_addr = htonl(INADDR_ANY); |
| addr.sin_port = htons(port_); |
| |
| if (bind(server_sock.get(), (struct sockaddr*)&addr, sizeof(addr)) < 0) { |
| FXL_LOG(ERROR) << "Failed to bind socket" |
| << ", " << debugger_utils::ErrnoString(errno); |
| return false; |
| } |
| |
| if (listen(server_sock.get(), 1) < 0) { |
| FXL_LOG(ERROR) << "Listen failed" |
| << ", " << debugger_utils::ErrnoString(errno); |
| return false; |
| } |
| |
| FXL_LOG(INFO) << "Waiting for a connection on port " << port_ << "..."; |
| |
| // Reuse |addr| here for the destination address. |
| socklen_t addrlen = sizeof(addr); |
| fxl::UniqueFD client_sock( |
| accept(server_sock.get(), (struct sockaddr*)&addr, &addrlen)); |
| if (!client_sock.is_valid()) { |
| FXL_LOG(ERROR) << "Accept failed" |
| << ", " << debugger_utils::ErrnoString(errno); |
| return false; |
| } |
| |
| FXL_LOG(INFO) << "Client connected"; |
| |
| server_sock_ = std::move(server_sock); |
| client_sock_ = std::move(client_sock); |
| |
| return true; |
| } |
| |
| void RspServer::SendAck(bool ack) { |
| // TODO(armansito): Don't send anything if we're in no-acknowledgment mode. We |
| // currently don't support this mode. |
| FXL_DCHECK(io_loop_); |
| char payload = ack ? '+' : '-'; |
| io_loop_->PostWriteTask(fxl::StringView(&payload, 1)); |
| } |
| |
| void RspServer::PostWriteTask(bool notify, const fxl::StringView& data) { |
| FXL_DCHECK(io_loop_); |
| FXL_DCHECK(data.size() + 4 < kMaxBufferSize); |
| |
| // Copy the data into a std::string to capture it in the closure. |
| async::PostTask( |
| message_loop_.dispatcher(), [this, data = data.ToString(), notify] { |
| int index = 0; |
| out_buffer_[index++] = notify ? '%' : '$'; |
| memcpy(out_buffer_.data() + index, data.data(), data.size()); |
| index += data.size(); |
| out_buffer_[index++] = '#'; |
| |
| uint8_t checksum = 0; |
| for (uint8_t byte : data) |
| checksum += byte; |
| |
| debugger_utils::EncodeByteString(checksum, out_buffer_.data() + index); |
| index += 2; |
| |
| io_loop_->PostWriteTask(fxl::StringView(out_buffer_.data(), index)); |
| }); |
| } |
| |
| void RspServer::PostPacketWriteTask(const fxl::StringView& data) { |
| PostWriteTask(false, data); |
| } |
| |
| void RspServer::PostPendingNotificationWriteTask() { |
| FXL_DCHECK(pending_notification_); |
| std::string bytes(pending_notification_->name + ":" + |
| pending_notification_->event); |
| PostWriteTask(true, bytes); |
| } |
| |
| bool RspServer::TryPostNextNotification() { |
| if (pending_notification_ || notify_queue_.empty()) |
| return false; |
| |
| pending_notification_ = std::move(notify_queue_.front()); |
| notify_queue_.pop(); |
| FXL_DCHECK(pending_notification_); |
| |
| // Send the notification. |
| PostPendingNotificationWriteTask(); |
| PostNotificationTimeoutHandler(); |
| return true; |
| } |
| |
| void RspServer::PostNotificationTimeoutHandler() { |
| // Set up a timeout handler. |
| // Continually resend the notification until the remote end acknowledges it, |
| // or until the notification is removed (say because the process exits). |
| zx::duration delay = |
| zx::nsec((pending_notification_->timeout).ToNanoseconds()); |
| async::PostDelayedTask(message_loop_.dispatcher(), |
| [this, pending = pending_notification_.get()] { |
| // If the notification that we set this timeout for |
| // has already been acknowledged by the remote, then |
| // we have nothing to do. |
| // TODO(dje): sequence numbers? |
| if (pending_notification_.get() != pending) |
| return; |
| |
| FXL_LOG(WARNING) |
| << "Notification timed out; retrying"; |
| PostPendingNotificationWriteTask(); |
| PostNotificationTimeoutHandler(); |
| }, |
| delay); |
| } |
| |
| void RspServer::OnBytesRead(const fxl::StringView& bytes_read) { |
| // If this is a packet acknowledgment then ignore it and read again. |
| // TODO(armansito): Re-send previous packet if we got "-". |
| if (bytes_read == "+") |
| return; |
| |
| fxl::StringView packet_data; |
| bool verified = VerifyPacket(bytes_read, &packet_data); |
| |
| // Send acknowledgment back |
| SendAck(verified); |
| |
| // Wait for the next command if we requested retransmission |
| if (!verified) |
| return; |
| |
| // Before anything else, check to see if this is an acknowledgment in |
| // response to a notification. The GDB Remote protocol defines only the |
| // "Stop" notification, so we specially handle its acknowledgment here. |
| if (packet_data == kStopAck) { |
| if (pending_notification_) { |
| FXL_VLOG(2) << "Notification acknowledged"; |
| |
| // At this point we enter a loop of passing all queued notifications |
| // to GDB as normal (ack'd) messages, terminating with "OK". Nothing else |
| // is exchanged until this loop completes. |
| // https://sourceware.org/gdb/current/onlinedocs/gdb/Notification-Packets.html |
| // This is awkward to do given our message loop. What we do is keep |
| // the original notification around as a flag indicating this loop is |
| // active until the queue is empty. |
| // TODO(dje): Redo this. |
| if (!notify_queue_.empty()) { |
| std::unique_ptr<PendingNotification> notif = |
| std::move(notify_queue_.front()); |
| notify_queue_.pop(); |
| PostPacketWriteTask(notif->event); |
| } else { |
| pending_notification_.reset(); |
| PostPacketWriteTask("OK"); |
| } |
| } else { |
| FXL_VLOG(2) << "Notification acknowledged, but notification gone"; |
| } |
| return; |
| } |
| |
| // Route the packet data to the command handler. |
| auto callback = [this](const fxl::StringView& rsp) { |
| // Send the response if there is one. |
| PostPacketWriteTask(rsp); |
| }; |
| |
| // If the command is handled, then |callback| will be called at some point, |
| // so we're done. |
| if (command_handler_.HandleCommand(packet_data, callback)) |
| return; |
| |
| // If the command wasn't handled, that's because we do not support it, so we |
| // respond with an empty response and continue. |
| FXL_LOG(ERROR) << "Command not supported: " << packet_data; |
| callback(""); |
| } |
| |
| void RspServer::OnDisconnected() { |
| // Exit successfully in the case of a remote disconnect. |
| FXL_LOG(INFO) << "Client disconnected"; |
| QuitMessageLoop(true); |
| } |
| |
| void RspServer::OnIOError() { |
| FXL_LOG(ERROR) << "An I/O error has occurred. Exiting the main loop"; |
| QuitMessageLoop(false); |
| } |
| |
| void RspServer::OnThreadStarting(inferior_control::Process* process, |
| inferior_control::Thread* thread, |
| const zx_exception_context_t& context) { |
| FXL_DCHECK(process); |
| |
| // TODO(armansito): We send a stop-reply packet for the new thread. This |
| // inherently completes any pending vRun sequence but technically shouldn't be |
| // sent unless GDB enables QThreadEvents. Add some logic here to send this |
| // conditionally only when necessary. |
| StopReplyPacket stop_reply(StopReplyPacket::Type::kReceivedSignal); |
| stop_reply.SetSignalNumber(5); |
| stop_reply.SetThreadId(process->id(), thread->id()); |
| stop_reply.SetStopReason("create"); |
| |
| auto packet = stop_reply.Build(); |
| |
| switch (process->state()) { |
| case inferior_control::Process::State::kStarting: |
| // vRun receives a synchronous response. After that it's all asynchronous. |
| PostPacketWriteTask(fxl::StringView(packet.data(), packet.size())); |
| process->set_state(inferior_control::Process::State::kRunning); |
| break; |
| case inferior_control::Process::State::kRunning: |
| QueueStopNotification(fxl::StringView(packet.data(), packet.size())); |
| break; |
| default: |
| FXL_DCHECK(false); |
| } |
| } |
| |
| void RspServer::OnThreadExiting(inferior_control::Process* process, |
| inferior_control::Thread* thread, |
| const zx_exception_context_t& context) { |
| std::vector<char> packet; |
| FXL_LOG(INFO) << "Thread " << thread->GetName() << " exited"; |
| int exit_code = 0; // TODO(dje) |
| StopReplyPacket stop_reply(StopReplyPacket::Type::kThreadExited); |
| stop_reply.SetSignalNumber(exit_code); |
| stop_reply.SetThreadId(process->id(), thread->id()); |
| packet = stop_reply.Build(); |
| QueueStopNotification(fxl::StringView(packet.data(), packet.size())); |
| |
| // The Remote Serial Protocol doesn't provide for a means to examine |
| // state when exiting, like it does when starting. The thread needs to be |
| // "resumed" so that the o/s will finish terminating the thread. This also |
| // takes care of marking the thread as kGone. |
| thread->ResumeForExit(); |
| } |
| |
| void RspServer::OnProcessExit(inferior_control::Process* process) { |
| std::vector<char> packet; |
| FXL_LOG(INFO) << "Process " << process->GetName() << " exited"; |
| SetCurrentThread(nullptr); |
| int exit_code = process->ExitCode(); |
| StopReplyPacket stop_reply(StopReplyPacket::Type::kProcessExited); |
| stop_reply.SetSignalNumber(exit_code); |
| packet = stop_reply.Build(); |
| QueueStopNotification(fxl::StringView(packet.data(), packet.size())); |
| } |
| |
| void RspServer::OnArchitecturalException( |
| inferior_control::Process* process, inferior_control::Thread* thread, |
| const zx_excp_type_t type, const zx_exception_context_t& context) { |
| ExceptionHelper(process, thread, type, context); |
| } |
| |
| void RspServer::OnSyntheticException(inferior_control::Process* process, |
| inferior_control::Thread* thread, |
| zx_excp_type_t type, |
| const zx_exception_context_t& context) { |
| // These are basically equivalent to architectural exceptions |
| // for our purposes. Handle them the same way. |
| ExceptionHelper(process, thread, type, context); |
| } |
| |
| void RspServer::ExceptionHelper(inferior_control::Process* process, |
| inferior_control::Thread* thread, |
| zx_excp_type_t type, |
| const zx_exception_context_t& context) { |
| FXL_DCHECK(process); |
| FXL_DCHECK(thread); |
| |
| if (ZX_EXCP_IS_ARCH(type)) { |
| FXL_VLOG(1) << "Architectural Exception: " |
| << debugger_utils::ExceptionToString(type, context); |
| } else { |
| FXL_VLOG(1) << "Synthetic Exception: " |
| << debugger_utils::ExceptionToString(type, context); |
| } |
| |
| // TODO(armansito): Fine-tune this check if we ever support multi-processing. |
| FXL_DCHECK(process == current_process()); |
| |
| inferior_control::GdbSignal sigval = thread->GetGdbSignal(); |
| if (sigval == inferior_control::GdbSignal::kUnsupported) { |
| FXL_LOG(ERROR) << "Exception reporting not supported on current " |
| << "architecture!"; |
| return; |
| } |
| int isigval = static_cast<int>(sigval); |
| |
| FXL_DCHECK(isigval < std::numeric_limits<uint8_t>::max()); |
| |
| StopReplyPacket stop_reply(StopReplyPacket::Type::kReceivedSignal); |
| stop_reply.SetSignalNumber(isigval); |
| stop_reply.SetThreadId(process->id(), thread->id()); |
| |
| // Registers. |
| if (thread->registers()->RefreshGeneralRegisters()) { |
| std::array<int, 3> regnos{{inferior_control::GetFPRegisterNumber(), |
| inferior_control::GetSPRegisterNumber(), |
| inferior_control::GetPCRegisterNumber()}}; |
| for (int regno : regnos) { |
| FXL_DCHECK(regno < std::numeric_limits<uint8_t>::max() && regno >= 0); |
| std::string regval = thread->registers()->GetRegisterAsString(regno); |
| stop_reply.AddRegisterValue(regno, regval); |
| } |
| } else { |
| FXL_LOG(WARNING) |
| << "Couldn't read thread registers while handling exception"; |
| } |
| |
| auto packet = stop_reply.Build(); |
| QueueStopNotification(fxl::StringView(packet.data(), packet.size())); |
| } |
| |
| } // namespace debugserver |