blob: 580df93cd770e7338749aca16b97b04a6947d991 [file] [log] [blame]
// 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 <fcntl.h>
#include <lib/cmdline/args_parser.h>
#include <lib/fit/defer.h>
#include <signal.h>
#include <sys/wait.h>
#include <unistd.h>
#include <cstdlib>
#include <memory>
#include "src/developer/debug/debug_agent/posix/eintr_wrapper.h"
#include "src/developer/debug/ipc/protocol.h"
#include "src/developer/debug/shared/buffered_bidi_pipe.h"
#include "src/developer/debug/shared/logging/logging.h"
#include "src/developer/debug/shared/message_loop_poll.h"
#include "src/developer/debug/zxdb/client/analytics.h"
#include "src/developer/debug/zxdb/client/session.h"
#include "src/developer/debug/zxdb/client/setting_schema_definition.h"
#include "src/developer/debug/zxdb/common/curl.h"
#include "src/developer/debug/zxdb/console/command_line_options.h"
#include "src/developer/debug/zxdb/console/command_sequence.h"
#include "src/developer/debug/zxdb/console/console_context.h"
#include "src/developer/debug/zxdb/console/console_impl.h"
#include "src/developer/debug/zxdb/console/console_noninteractive.h"
#include "src/developer/debug/zxdb/console/fd_streamer.h"
#include "src/developer/debug/zxdb/console/output_buffer.h"
#include "src/developer/debug/zxdb/console/verbs.h"
#include "src/developer/debug/zxdb/debug_adapter/server.h"
#include "src/developer/debug/zxdb/local_agent.h"
#include "src/lib/fxl/strings/string_printf.h"
#if defined(__linux__) && defined(__x86_64__)
// Indicates that we can support the local built-in debug agent on this platform.
#define SUPPORTS_LOCAL_AGENT
#endif
namespace zxdb {
namespace {
// Loads any actions specified on the command line into the vector.
Err SetupActions(const CommandLineOptions& options, std::vector<std::string>* actions) {
if (options.core) {
if (options.connect) {
return Err("--core can't be used with commands to connect.");
}
actions->push_back(VerbToString(Verb::kOpenDump) + " " + *options.core);
}
if (options.connect)
actions->push_back(VerbToString(Verb::kConnect) + " " + *options.connect);
if (options.unix_connect)
actions->push_back(VerbToString(Verb::kConnect) + " -q -u " + *options.unix_connect);
for (const auto& script_file : options.script_files) {
ErrOr<std::vector<std::string>> cmds_or = ReadCommandsFromFile(script_file);
if (cmds_or.has_error())
return cmds_or.err();
actions->insert(actions->end(), cmds_or.value().begin(), cmds_or.value().end());
}
for (const auto& attach : options.attach) {
actions->push_back(VerbToString(Verb::kAttach) + " " + attach);
}
actions->insert(actions->end(), options.execute_commands.begin(), options.execute_commands.end());
return Err();
}
void InitConsole(zxdb::Console& console) {
console.Init();
// We disabled input during startup. Balance that by enabling input now.
console.EnableInput();
// Help text.
OutputBuffer help;
help.Append(Syntax::kWarning, "👉 ");
help.Append(Syntax::kComment, "To get started, try \"status\" or \"help\".");
console.Output(help);
}
void SetupCommandLineOptions(const CommandLineOptions& options, Session* session) {
auto& system_settings = session->system().settings();
if (options.debug_mode)
system_settings.SetBool(ClientSettings::System::kDebugMode, true);
if (options.console_mode)
system_settings.SetString(ClientSettings::System::kConsoleMode, *options.console_mode);
if (options.embedded_mode_context)
system_settings.SetString(ClientSettings::System::kEmbeddedModeContext,
*options.embedded_mode_context);
if (options.auto_attach_limbo)
system_settings.SetBool(ClientSettings::System::kAutoAttachLimbo, true);
if (options.symbol_cache)
system_settings.SetString(ClientSettings::System::kSymbolCache, *options.symbol_cache);
if (!options.symbol_index_files.empty())
system_settings.SetList(ClientSettings::System::kSymbolIndexFiles, options.symbol_index_files);
if (!options.symbol_servers.empty())
system_settings.SetList(ClientSettings::System::kSymbolServers, options.symbol_servers);
if (!options.symbol_paths.empty())
system_settings.SetList(ClientSettings::System::kSymbolPaths, options.symbol_paths);
if (!options.build_id_dirs.empty())
system_settings.SetList(ClientSettings::System::kBuildIdDirs, options.build_id_dirs);
if (!options.ids_txts.empty())
system_settings.SetList(ClientSettings::System::kIdsTxts, options.ids_txts);
}
} // namespace
int ConsoleMain(int argc, const char* argv[]) {
using ::analytics::core_dev_tools::EarlyProcessAnalyticsOptions;
CommandLineOptions options;
std::vector<std::string> params;
cmdline::Status status = ParseCommandLine(argc, argv, &options, &params);
if (status.has_error()) {
fprintf(stderr, "%s", status.error_message().c_str());
return 1;
}
if (options.requested_version) {
printf("Version: %d\n", debug_ipc::kCurrentProtocolVersion);
return 0;
}
Curl::GlobalInit();
auto deferred_cleanup_curl = fit::defer(Curl::GlobalCleanup);
auto deferred_cleanup_analytics = fit::defer(Analytics::CleanUp);
if (EarlyProcessAnalyticsOptions<Analytics>(options.analytics, options.analytics_show)) {
return 0;
}
#ifdef SUPPORTS_LOCAL_AGENT
// Handle a local connection to the built-in debug agent when supported. We want to fork as early
// as possible to avoid accumulating state that isn't relevant to the debug_agent process
// (especially message loops which will conflict). To accommodate early returns later in this
// function, we set up a deferred callback which kills the agent on return. This is removed
// when we "commit" the connection at which point the client sents IPC messages to manage the
// agent lifetime.
//
// To prevent a zombie, every process should wait on the PID for every subprocess it forks.
std::optional<LocalAgentResult> local_agent;
fit::deferred_callback cleanup_local_agent;
if (options.local) {
local_agent = ForkLocalAgent();
if (local_agent->status == LocalAgentResult::kSuccess) {
cleanup_local_agent = fit::defer_callback([pid = local_agent->agent_pid]() {
// The early returns in the code below will leave the forked agent running and the wait
// will hang, so kill it before waiting. This also catches mistakes if we forget to
// exit the agent via IPC once things are running normally.
if (kill(pid, SIGKILL) == 0) {
waitpid(pid, 0, 0);
}
});
} else {
return local_agent->exit_code;
}
}
#else // No local agent
if (options.local) {
fprintf(stderr, "No local debugging supported on this platform.");
return 1;
}
#endif
std::vector<std::string> actions;
Err err = SetupActions(options, &actions);
if (err.has_error()) {
fprintf(stderr, "%s\n", err.msg().c_str());
return 1;
}
debug::MessageLoopPoll loop;
std::string error_message;
if (!loop.Init(&error_message)) {
fprintf(stderr, "%s", error_message.c_str());
return 1;
}
// This scope forces all the objects to be destroyed before the Cleanup() call which will mark the
// message loop as not-current.
int ret_code = 0;
{
// Hold ownership of any local pipe in our scope to ensure it gets cleaned up before the
// message loop exits (it will attach to the loop when its Start() function is called and
// will disconnect from the loop in its destructor).
std::unique_ptr<debug::BufferedBidiPipe> local_pipe;
std::unique_ptr<Session> session;
if (options.local) {
#ifdef SUPPORTS_LOCAL_AGENT
// The local debug_agent will have been started above.
local_pipe = std::move(local_agent->pipe);
session = std::make_unique<Session>(&local_pipe->stream());
local_pipe->set_data_available_callback([&session]() { session->OnStreamReadable(); });
if (!local_pipe->Start()) {
fprintf(stderr, "Failed to connect to the pipe.");
return 1;
}
// If a binary is supplied on the command line, set it as the default target's process name.
if (!params.empty()) {
auto targets = session->system().GetTargets();
FX_CHECK(!targets.empty()); // Should always have a default target.
targets[0]->SetArgs(params);
}
#endif // SUPPORTS_LOCAL_AGENT
} else {
// Normal remote connections don't start with a connected session.
session = std::make_unique<Session>();
}
Analytics::Init(*session, options.analytics);
session->analytics().Init(session.get());
session->analytics().ReportInvoked();
debug::SetLogCategories({debug::LogCategory::kAll});
SetupCommandLineOptions(options, session.get());
std::unique_ptr<Console> console;
std::unique_ptr<DebugAdapterServer> debug_adapter;
if (options.enable_debug_adapter) {
int port = options.debug_adapter_port;
console = std::make_unique<ConsoleNoninteractive>(session.get());
debug_adapter = std::make_unique<DebugAdapterServer>(console->get(), port);
err = debug_adapter->Init();
if (err.has_error()) {
fprintf(stderr, "Failed to initialize debug adapter: %s\n", err.msg().c_str());
loop.Cleanup();
return EXIT_FAILURE;
}
} else {
console = std::make_unique<ConsoleImpl>(session.get());
}
// Suppress input during startup.
// Balanced in InitConsole.
console->DisableInput();
// Enable streaming as soon as possible. If we're not in embedded mode, it will be disabled
// during |InitConsoleMode| below. We do this before registering the streamers with the console
// so that there's no flickering between here and initializing the console mode below. If the
// program attempting to stream output through zxdb is sensitive to timing issues on the target,
// they should be waiting for |signal_when_ready| below to start sending data through the FD.
console->EnableStreaming();
// This will enable output from the console if we're not in embedded mode, so we can reliably
// use the logging macros now.
console->context().InitConsoleMode();
std::vector<std::unique_ptr<debug::BufferedFD>> file_streamers;
for (auto& path : options.stream_files) {
fbl::unique_fd fd(HANDLE_EINTR(open(path.c_str(), O_RDONLY | O_NONBLOCK)));
if (!fd.is_valid()) {
LOGS(Error) << "Failed to open file for streaming: " << path;
loop.Cleanup();
return EXIT_FAILURE;
}
file_streamers.emplace_back(StreamFDToConsole(std::move(fd), console.get()));
}
// Run the actions and then initialize the console to enter interactive mode. Errors in
// running actions should be fatal and quit the debugger.
RunCommandSequence(
console.get(), std::move(actions),
fxl::MakeRefCounted<ConsoleCommandContext>(console.get(), [&](const Err& err) {
if (err.has_error()) {
ret_code = 1;
loop.QuitNow();
} else {
InitConsole(*console);
// The console is now ready for interactive input.
if (options.signal_when_ready) {
int rv = kill(options.signal_when_ready, SIGUSR1);
if (rv != 0) {
LOGS(Error) << "Failed to send SIGUSR1: " << strerror(errno);
}
}
}
}));
loop.Run();
}
loop.Cleanup();
return ret_code;
}
} // namespace zxdb
int main(int argc, char* argv[]) { return zxdb::ConsoleMain(argc, const_cast<const char**>(argv)); }