| // Copyright 2018 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 <lib/cmdline/args_parser.h> | |
| #include <lib/fidl/cpp/binding_set.h> | |
| #include <lib/zx/thread.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <zircon/status.h> | |
| #include <zircon/syscalls.h> | |
| #include <memory> | |
| #include <thread> | |
| #include "src/developer/debug/debug_agent/debug_agent.h" | |
| #include "src/developer/debug/debug_agent/fidl_server.h" | |
| #include "src/developer/debug/debug_agent/socket_connection.h" | |
| #include "src/developer/debug/debug_agent/unwind.h" | |
| #include "src/developer/debug/debug_agent/zircon_system_interface.h" | |
| #include "src/developer/debug/shared/logging/logging.h" | |
| #include "src/developer/debug/shared/platform_message_loop.h" | |
| #include "src/developer/debug/shared/zx_status.h" | |
| #include "src/lib/fxl/strings/string_printf.h" | |
| namespace debug_agent { | |
| namespace { | |
| // Valid options for the --unwind flag. | |
| const char kAospUnwinder[] = "aosp"; | |
| const char kNgUnwinder[] = "ng"; | |
| const char kFuchsiaUnwinder[] = "fuchsia"; | |
| struct CommandLineOptions { | |
| int port = 0; | |
| bool debug_mode = false; | |
| bool channel_mode = false; | |
| std::string unwind = kAospUnwinder; | |
| }; | |
| const char kHelpIntro[] = R"(debug_agent --port=<port> [ <options> ] | |
| The debug_agent provides the on-device stub for the ZXDB frontend to talk | |
| to. Once you launch the debug_agent, connect zxdb to the same port you | |
| provide on the command-line. | |
| Options | |
| )"; | |
| const char kHelpHelp[] = R"( --help | |
| -h | |
| Prints all command-line switches.)"; | |
| const char kPortHelp[] = R"( --port=<port> | |
| [Required] TCP port number to listen to incoming connections on.)"; | |
| const char kDebugModeHelp[] = R"( --debug-mode | |
| -d | |
| Run the agent on debug mode. This will enable conditional logging | |
| messages and timing profiling. Mainly useful for people developing zxdb.)"; | |
| const char kChannelModeHelp[] = R"( --channel-mode | |
| Run the agent on in channel mode. The agent will listen for channels through the | |
| fuchsia.debugger.DebugAgent API. This is necessary for overnet.)"; | |
| const char kUnwindHelp[] = R"( --unwind=[aosp|ng|fuchsia] | |
| Force using a specific unwinder for generating stack traces.)"; | |
| cmdline::Status ParseCommandLine(int argc, const char* argv[], CommandLineOptions* options) { | |
| cmdline::ArgsParser<CommandLineOptions> parser; | |
| parser.AddSwitch("port", 0, kPortHelp, &CommandLineOptions::port); | |
| parser.AddSwitch("debug-mode", 'd', kDebugModeHelp, &CommandLineOptions::debug_mode); | |
| parser.AddSwitch("channel-mode", 0, kChannelModeHelp, &CommandLineOptions::channel_mode); | |
| parser.AddSwitch("unwind", 0, kUnwindHelp, &CommandLineOptions::unwind); | |
| // Special --help switch which doesn't exist in the options structure. | |
| bool requested_help = false; | |
| parser.AddGeneralSwitch("help", 'h', kHelpHelp, [&requested_help]() { requested_help = true; }); | |
| std::vector<std::string> params; | |
| cmdline::Status status = parser.Parse(argc, argv, options, ¶ms); | |
| if (status.has_error()) | |
| return status; | |
| // Handle --help switch since we're the one that knows about the switches. | |
| if (requested_help) | |
| return cmdline::Status::Error(kHelpIntro + parser.GetHelp()); | |
| return cmdline::Status::Ok(); | |
| } | |
| } // namespace | |
| } // namespace debug_agent | |
| // main -------------------------------------------------------------------------------------------- | |
| int main(int argc, const char* argv[]) { | |
| debug_agent::CommandLineOptions options; | |
| cmdline::Status status = ParseCommandLine(argc, argv, &options); | |
| if (status.has_error()) { | |
| FX_LOGS(ERROR) << status.error_message(); | |
| return 1; | |
| } | |
| // Decode the unwinder type. | |
| if (options.unwind == debug_agent::kAospUnwinder) { | |
| debug_agent::SetUnwinderType(debug_agent::UnwinderType::kAndroid); | |
| } else if (options.unwind == debug_agent::kNgUnwinder) { | |
| debug_agent::SetUnwinderType(debug_agent::UnwinderType::kNgUnwind); | |
| } else if (options.unwind == debug_agent::kFuchsiaUnwinder) { | |
| debug_agent::SetUnwinderType(debug_agent::UnwinderType::kFuchsia); | |
| } else { | |
| FX_LOGS(ERROR) << "Invalid option for --unwind. See debug_agent --help."; | |
| return 1; | |
| } | |
| debug::SetLogCategories({debug::LogCategory::kAll}); | |
| if (options.debug_mode) { | |
| printf("Running the debug agent in debug mode.\n"); | |
| debug::SetDebugMode(true); | |
| } | |
| if (options.channel_mode || options.port) { | |
| auto services = sys::ServiceDirectory::CreateFromNamespace(); | |
| zx::channel exception_channel; | |
| auto message_loop = std::make_unique<debug::PlatformMessageLoop>(); | |
| std::string init_error_message; | |
| if (!message_loop->Init(&init_error_message)) { | |
| FX_LOGS(ERROR) << init_error_message; | |
| return 1; | |
| } | |
| // The scope ensures the objects are destroyed before calling Cleanup on the MessageLoop. | |
| { | |
| // The debug agent is independent of whether it's connected or not. | |
| // DebugAgent::Disconnect is called by ~SocketConnection is called by ~SocketServer, so the | |
| // debug agent must be destructed after the SocketServer. | |
| debug_agent::DebugAgent debug_agent(std::make_unique<debug_agent::ZirconSystemInterface>()); | |
| debug_agent::SocketServer server; | |
| if (!server.Init(options.port)) { | |
| message_loop->Cleanup(); | |
| return 1; | |
| } | |
| // The following loop will attempt to patch a stream to the debug agent in order to enable | |
| // communication. | |
| while (true) { | |
| if (options.channel_mode) { | |
| // Connect to FIDL service which will wait for an incoming connection from a client. | |
| // This happens within the message_loop so it does not need a new thread. | |
| debug_agent::DebugAgentImpl fidl_agent(&debug_agent); | |
| fidl::BindingSet<fuchsia::debugger::DebugAgent> bindings; | |
| auto context = sys::ComponentContext::CreateAndServeOutgoingDirectory(); | |
| context->outgoing()->AddPublicService(bindings.GetHandler(&fidl_agent)); | |
| FX_LOGS(INFO) << "Start listening on FIDL fuchsia::debugger::DebugAgent."; | |
| message_loop->Run(); | |
| } else { | |
| // Start a new thread that will listen on a socket from an incoming connection from a | |
| // client. In the meantime, the main thread will block waiting for something to be posted | |
| // to the main thread. | |
| // | |
| // When the connection thread effectively receives a connection, it will post a task to | |
| // the loop to create the agent and begin normal debugger operation. Once the application | |
| // quits the loop, the code will clean the connection thread and create another or exit | |
| // the loop, according to the current agent's configuration. | |
| debug_agent::SocketServer::ConnectionConfig conn_config; | |
| conn_config.message_loop = message_loop.get(); | |
| conn_config.debug_agent = &debug_agent; | |
| conn_config.port = options.port; | |
| std::thread conn_thread(&debug_agent::SocketServer::Run, &server, std::move(conn_config)); | |
| FX_LOGS(INFO) << "Start listening on port " << options.port; | |
| message_loop->Run(); | |
| DEBUG_LOG(Agent) << "Joining connection thread."; | |
| conn_thread.join(); | |
| } | |
| // See if the debug agent was told to exit. | |
| if (debug_agent.should_quit()) | |
| break; | |
| // Prepare for another connection. | |
| // The resources need to be freed on the message loop's thread. | |
| server.Reset(); | |
| } | |
| } | |
| message_loop->Cleanup(); | |
| } else { | |
| FX_LOGS(ERROR) << "--port=<port-number> required. See debug_agent --help."; | |
| return 1; | |
| } | |
| // It's very useful to have a simple message that informs the debug agent | |
| // exited successfully. | |
| FX_LOGS(INFO) << "See you, Space Cowboy..."; | |
| return 0; | |
| } |