blob: 68e987237cc69abcac5094ebdc6060fcb3545a9b [file] [log] [blame]
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "lldb/Host/Config.h"
#include "lldb/Host/File.h"
#include "lldb/Host/FileSystem.h"
#include "lldb/Host/Host.h"
#include "lldb/Host/MainLoop.h"
#include "lldb/Host/MainLoopBase.h"
#include "lldb/Host/ProcessLaunchInfo.h"
#include "lldb/Host/Socket.h"
#include "lldb/Initialization/SystemInitializerCommon.h"
#include "lldb/Initialization/SystemLifetimeManager.h"
#include "lldb/Protocol/MCP/Server.h"
#include "lldb/Utility/FileSpec.h"
#include "lldb/Utility/Status.h"
#include "lldb/Utility/UriParser.h"
#include "lldb/lldb-forward.h"
#include "llvm/ADT/ScopeExit.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/InitLLVM.h"
#include "llvm/Support/ManagedStatic.h"
#include "llvm/Support/Signals.h"
#include "llvm/Support/WithColor.h"
#include <chrono>
#include <cstdlib>
#include <memory>
#include <thread>
#if defined(_WIN32)
#include <fcntl.h>
#endif
using namespace llvm;
using namespace lldb;
using namespace lldb_protocol::mcp;
using lldb_private::Environment;
using lldb_private::File;
using lldb_private::FileSpec;
using lldb_private::FileSystem;
using lldb_private::Host;
using lldb_private::MainLoop;
using lldb_private::MainLoopBase;
using lldb_private::NativeFile;
namespace {
#if defined(_WIN32)
constexpr StringLiteral kDriverName = "lldb.exe";
#else
constexpr StringLiteral kDriverName = "lldb";
#endif
constexpr size_t kForwardIOBufferSize = 1024;
inline void exitWithError(llvm::Error Err, StringRef Prefix = "") {
handleAllErrors(std::move(Err), [&](ErrorInfoBase &Info) {
WithColor::error(errs(), Prefix) << Info.message() << '\n';
});
std::exit(EXIT_FAILURE);
}
FileSpec driverPath() {
Environment host_env = Host::GetEnvironment();
// Check if an override for which lldb we're using exists, otherwise look next
// to the current binary.
std::string lldb_exe_path = host_env.lookup("LLDB_EXE_PATH");
auto &fs = FileSystem::Instance();
if (fs.Exists(lldb_exe_path))
return FileSpec(lldb_exe_path);
FileSpec lldb_exec_spec = lldb_private::HostInfo::GetProgramFileSpec();
lldb_exec_spec.SetFilename(kDriverName);
return lldb_exec_spec;
}
llvm::Error launch() {
FileSpec lldb_exec = driverPath();
lldb_private::ProcessLaunchInfo info;
info.SetExecutableFile(lldb_exec,
/*add_exe_file_as_first_arg=*/true);
info.GetArguments().AppendArgument("-O");
info.GetArguments().AppendArgument("protocol start MCP");
return Host::LaunchProcess(info).takeError();
}
Expected<ServerInfo> loadOrStart(
// FIXME: This should become a CLI arg.
lldb_private::Timeout<std::micro> timeout = std::chrono::seconds(30)) {
using namespace std::chrono;
bool started = false;
const auto deadline = steady_clock::now() + *timeout;
while (steady_clock::now() < deadline) {
Expected<std::vector<ServerInfo>> servers = ServerInfo::Load();
if (!servers)
return servers.takeError();
if (servers->empty()) {
if (!started) {
started = true;
if (llvm::Error err = launch())
return std::move(err);
}
// FIXME: Can we use MainLoop to watch the directory?
std::this_thread::sleep_for(microseconds(250));
continue;
}
// FIXME: Support selecting / multiplexing a specific lldb instance.
if (servers->size() > 1)
return createStringError("too many MCP servers running, picking a "
"specific one is not yet implemented");
return servers->front();
}
return createStringError("timed out waiting for MCP server to start");
}
void forwardIO(MainLoopBase &loop, IOObjectSP &from, IOObjectSP &to) {
char buf[kForwardIOBufferSize];
size_t num_bytes = sizeof(buf);
if (llvm::Error err = from->Read(buf, num_bytes).takeError())
exitWithError(std::move(err));
// EOF reached.
if (num_bytes == 0)
return loop.RequestTermination();
if (llvm::Error err = to->Write(buf, num_bytes).takeError())
exitWithError(std::move(err));
}
llvm::Error connectAndForwardIO(lldb_private::MainLoop &loop, ServerInfo &info,
IOObjectSP &input_sp, IOObjectSP &output_sp) {
auto uri = lldb_private::URI::Parse(info.connection_uri);
if (!uri)
return createStringError("invalid connection_uri");
std::optional<lldb_private::Socket::ProtocolModePair> protocol_and_mode =
lldb_private::Socket::GetProtocolAndMode(uri->scheme);
if (!protocol_and_mode)
return createStringError("unknown protocol scheme");
lldb_private::Status status;
std::unique_ptr<lldb_private::Socket> sock =
lldb_private::Socket::Create(protocol_and_mode->first, status);
if (status.Fail())
return status.takeError();
if (uri->port && !uri->hostname.empty())
status = sock->Connect(
llvm::formatv("[{0}]:{1}", uri->hostname, *uri->port).str());
else
status = sock->Connect(uri->path);
if (status.Fail())
return status.takeError();
IOObjectSP sock_sp = std::move(sock);
auto input_handle = loop.RegisterReadObject(
input_sp, std::bind(forwardIO, std::placeholders::_1, input_sp, sock_sp),
status);
if (status.Fail())
return status.takeError();
auto socket_handle = loop.RegisterReadObject(
sock_sp, std::bind(forwardIO, std::placeholders::_1, sock_sp, output_sp),
status);
if (status.Fail())
return status.takeError();
return loop.Run().takeError();
}
llvm::ManagedStatic<lldb_private::SystemLifetimeManager> g_debugger_lifetime;
} // namespace
int main(int argc, char *argv[]) {
llvm::InitLLVM IL(argc, argv, /*InstallPipeSignalExitHandler=*/false);
#if !defined(__APPLE__)
llvm::setBugReportMsg("PLEASE submit a bug report to " LLDB_BUG_REPORT_URL
" and include the crash backtrace.\n");
#else
llvm::setBugReportMsg("PLEASE submit a bug report to " LLDB_BUG_REPORT_URL
" and include the crash report from "
"~/Library/Logs/DiagnosticReports/.\n");
#endif
#if defined(_WIN32)
// Windows opens stdout and stdin in text mode which converts \n to 13,10
// while the value is just 10 on Darwin/Linux. Setting the file mode to
// binary fixes this.
int result = _setmode(fileno(stdout), _O_BINARY);
assert(result);
result = _setmode(fileno(stdin), _O_BINARY);
UNUSED_IF_ASSERT_DISABLED(result);
assert(result);
#endif
if (llvm::Error err = g_debugger_lifetime->Initialize(
std::make_unique<lldb_private::SystemInitializerCommon>(nullptr)))
exitWithError(std::move(err));
auto cleanup = make_scope_exit([] { g_debugger_lifetime->Terminate(); });
IOObjectSP input_sp = std::make_shared<NativeFile>(
fileno(stdin), File::eOpenOptionReadOnly, NativeFile::Unowned);
IOObjectSP output_sp = std::make_shared<NativeFile>(
fileno(stdout), File::eOpenOptionWriteOnly, NativeFile::Unowned);
Expected<ServerInfo> server_info = loadOrStart();
if (!server_info)
exitWithError(server_info.takeError());
static MainLoop loop;
sys::SetInterruptFunction([]() {
loop.AddPendingCallback(
[](MainLoopBase &loop) { loop.RequestTermination(); });
});
if (llvm::Error error =
connectAndForwardIO(loop, *server_info, input_sp, output_sp))
exitWithError(std::move(error));
return EXIT_SUCCESS;
}