| //===----------------------------------------------------------------------===// | |
| // | |
| // 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 "Plugins/Platform/WebAssembly/PlatformWasm.h" | |
| #include "Plugins/Platform/WebAssembly/PlatformWasmRemoteGDBServer.h" | |
| #include "Plugins/Platform/WebAssembly/PlatformWebInspectorWasm.h" | |
| #include "Plugins/Process/wasm/ProcessWasm.h" | |
| #include "lldb/Core/Module.h" | |
| #include "lldb/Core/PluginManager.h" | |
| #include "lldb/Host/FileSystem.h" | |
| #include "lldb/Host/ProcessLaunchInfo.h" | |
| #include "lldb/Host/common/TCPSocket.h" | |
| #include "lldb/Interpreter/OptionValueProperties.h" | |
| #include "lldb/Target/Process.h" | |
| #include "lldb/Target/Target.h" | |
| #include "lldb/Utility/Environment.h" | |
| #include "lldb/Utility/LLDBLog.h" | |
| #include "lldb/Utility/Listener.h" | |
| #include "lldb/Utility/Log.h" | |
| #include "llvm/ADT/StringExtras.h" | |
| #include "llvm/Support/ErrorExtras.h" | |
| using namespace lldb; | |
| using namespace lldb_private; | |
| LLDB_PLUGIN_DEFINE(PlatformWasm) | |
| namespace { | |
| #define LLDB_PROPERTIES_platformwasm | |
| #include "PlatformWasmProperties.inc" | |
| enum { | |
| #define LLDB_PROPERTIES_platformwasm | |
| #include "PlatformWasmPropertiesEnum.inc" | |
| }; | |
| class PluginProperties : public Properties { | |
| public: | |
| PluginProperties() { | |
| m_collection_sp = std::make_shared<OptionValueProperties>( | |
| PlatformWasm::GetPluginNameStatic()); | |
| m_collection_sp->Initialize(g_platformwasm_properties_def); | |
| } | |
| FileSpec GetRuntimePath() const { | |
| return GetPropertyAtIndexAs<FileSpec>(ePropertyRuntimePath, {}); | |
| } | |
| Args GetRuntimeArgs() const { | |
| Args result; | |
| m_collection_sp->GetPropertyAtIndexAsArgs(ePropertyRuntimeArgs, result); | |
| return result; | |
| } | |
| llvm::StringRef GetPortArg() const { | |
| return GetPropertyAtIndexAs<llvm::StringRef>(ePropertyPortArg, {}); | |
| } | |
| llvm::StringRef GetEnvArg() const { | |
| return GetPropertyAtIndexAs<llvm::StringRef>(ePropertyEnvArg, {}); | |
| } | |
| }; | |
| } // namespace | |
| static PluginProperties &GetGlobalProperties() { | |
| static PluginProperties g_settings; | |
| return g_settings; | |
| } | |
| llvm::StringRef PlatformWasm::GetPluginDescriptionStatic() { | |
| return "Platform for debugging Wasm"; | |
| } | |
| void PlatformWasm::Initialize() { | |
| PlatformWebInspectorWasm::Initialize(); | |
| PluginManager::RegisterPlugin( | |
| GetPluginNameStatic(), GetPluginDescriptionStatic(), | |
| PlatformWasm::CreateInstance, PlatformWasm::DebuggerInitialize); | |
| } | |
| void PlatformWasm::Terminate() { | |
| PluginManager::UnregisterPlugin(PlatformWasm::CreateInstance); | |
| PlatformWebInspectorWasm::Terminate(); | |
| } | |
| void PlatformWasm::DebuggerInitialize(Debugger &debugger) { | |
| if (!PluginManager::GetSettingForPlatformPlugin(debugger, | |
| GetPluginNameStatic())) { | |
| PluginManager::CreateSettingForPlatformPlugin( | |
| debugger, GetGlobalProperties().GetValueProperties(), | |
| "Properties for the wasm platform plugin.", | |
| /*is_global_property=*/true); | |
| } | |
| } | |
| PlatformSP PlatformWasm::CreateInstance(bool force, const ArchSpec *arch) { | |
| Log *log = GetLog(LLDBLog::Platform); | |
| LLDB_LOG(log, "force = {0}, arch = ({1}, {2})", force, | |
| arch ? arch->GetArchitectureName() : "<null>", | |
| arch ? arch->GetTriple().getTriple() : "<null>"); | |
| bool create = force; | |
| if (!create && arch && arch->IsValid()) { | |
| const llvm::Triple &triple = arch->GetTriple(); | |
| switch (triple.getArch()) { | |
| case llvm::Triple::wasm32: | |
| case llvm::Triple::wasm64: | |
| create = true; | |
| break; | |
| default: | |
| break; | |
| } | |
| } | |
| LLDB_LOG(log, "create = {0}", create); | |
| return create ? PlatformSP(new PlatformWasm()) : PlatformSP(); | |
| } | |
| llvm::Expected<uint16_t> PlatformWasm::FindFreeTCPPort() { | |
| TCPSocket sock(/*should_close=*/true); | |
| Status status = sock.Listen("localhost:0", /*backlog=*/5); | |
| if (status.Fail()) | |
| return status.takeError(); | |
| return sock.GetLocalPortNumber(); | |
| } | |
| std::vector<ArchSpec> | |
| PlatformWasm::GetSupportedArchitectures(const ArchSpec &process_host_arch) { | |
| return {ArchSpec("wasm32"), ArchSpec("wasm64")}; | |
| } | |
| lldb::ProcessSP PlatformWasm::Attach(ProcessAttachInfo &attach_info, | |
| Debugger &debugger, Target *target, | |
| Status &status) { | |
| if (m_remote_platform_sp) | |
| return m_remote_platform_sp->Attach(attach_info, debugger, target, status); | |
| status = Status::FromErrorString( | |
| "attaching is only supported when connected to a remote Wasm platform"); | |
| return nullptr; | |
| } | |
| Args PlatformWasm::MakeRuntimeCommand(llvm::StringRef runtime_path, | |
| const Args &runtime_args, | |
| llvm::StringRef port_arg, uint16_t port, | |
| llvm::StringRef env_arg, | |
| const Environment &env, | |
| llvm::StringRef module_path, | |
| const Args &inferior_args) { | |
| Args args({runtime_path}); | |
| args.AppendArguments(runtime_args); | |
| args.AppendArgument(llvm::formatv("{0}{1}", port_arg, port).str()); | |
| if (!env_arg.empty()) | |
| for (const auto &kv : env) | |
| args.AppendArgument( | |
| llvm::formatv("{0}{1}", env_arg, Environment::compose(kv)).str()); | |
| // The runtime resolves the module as a host path, while arg0 is the name the | |
| // platform reports for the executable and need not resolve here. | |
| Args module_args = inferior_args; | |
| if (!module_path.empty()) { | |
| if (module_args.GetArgumentCount() > 0) | |
| module_args.ReplaceArgumentAtIndex(0, module_path); | |
| else | |
| module_args.AppendArgument(module_path); | |
| } | |
| args.AppendArguments(module_args); | |
| return args; | |
| } | |
| lldb::ProcessSP PlatformWasm::DebugProcess(ProcessLaunchInfo &launch_info, | |
| Debugger &debugger, Target &target, | |
| Status &error) { | |
| if (m_remote_platform_sp) | |
| return m_remote_platform_sp->DebugProcess(launch_info, debugger, target, | |
| error); | |
| Log *log = GetLog(LLDBLog::Platform); | |
| const PluginProperties &properties = GetGlobalProperties(); | |
| FileSpec runtime = properties.GetRuntimePath(); | |
| FileSystem::Instance().ResolveExecutableLocation(runtime); | |
| if (!FileSystem::Instance().Exists(runtime)) { | |
| error = Status::FromErrorStringWithFormatv( | |
| "WebAssembly runtime does not exist: {0}", runtime.GetPath()); | |
| return nullptr; | |
| } | |
| llvm::Expected<uint16_t> expected_port = FindFreeTCPPort(); | |
| if (!expected_port) { | |
| error = Status::FromError(expected_port.takeError()); | |
| return nullptr; | |
| } | |
| uint16_t port = *expected_port; | |
| std::string module_path; | |
| if (ModuleSP exe_module_sp = target.GetExecutableModule()) | |
| module_path = exe_module_sp->GetFileSpec().GetPath(); | |
| Args args = MakeRuntimeCommand( | |
| runtime.GetPath(), properties.GetRuntimeArgs(), properties.GetPortArg(), | |
| port, properties.GetEnvArg(), launch_info.GetEnvironment(), module_path, | |
| launch_info.GetArguments()); | |
| launch_info.SetArguments(args, true); | |
| launch_info.SetLaunchInSeparateProcessGroup(true); | |
| // We're launching the Wasm runtime (a native host binary), not the target | |
| // being debugged. Clear flags that don't apply to the runtime process. | |
| launch_info.GetFlags().Clear(eLaunchFlagDebug | eLaunchFlagDisableASLR); | |
| // The runtime itself runs with the host environment. | |
| launch_info.GetEnvironment() = Host::GetEnvironment(); | |
| auto exit_code = std::make_shared<std::optional<int>>(); | |
| launch_info.SetMonitorProcessCallback( | |
| [=](lldb::pid_t pid, int signal, int status) { | |
| LLDB_LOG( | |
| log, | |
| "WebAssembly runtime exited: pid = {0}, signal = {1}, status = {2}", | |
| pid, signal, status); | |
| exit_code->emplace(status); | |
| }); | |
| // This is automatically done for host platform in | |
| // Target::FinalizeFileActions, but we're not a host platform. | |
| llvm::Error Err = launch_info.SetUpPtyRedirection(); | |
| LLDB_LOG_ERROR(log, std::move(Err), "SetUpPtyRedirection failed: {0}"); | |
| LLDB_LOG(log, "{0}", GetArgRange(launch_info.GetArguments())); | |
| error = Host::LaunchProcess(launch_info); | |
| if (error.Fail()) | |
| return nullptr; | |
| ProcessSP process_sp = target.CreateProcess( | |
| launch_info.GetListener(), wasm::ProcessWasm::GetPluginNameStatic(), | |
| nullptr, true); | |
| if (!process_sp) { | |
| error = Status::FromErrorString("failed to create WebAssembly process"); | |
| return nullptr; | |
| } | |
| process_sp->HijackProcessEvents(launch_info.GetHijackListener()); | |
| error = process_sp->ConnectRemote( | |
| llvm::formatv("connect://localhost:{0}", port).str()); | |
| if (error.Fail()) { | |
| // If we know the runtime has exited, that's a better error message than | |
| // failing to connect. | |
| if (*exit_code) | |
| error = Status::FromError(llvm::joinErrors( | |
| llvm::createStringErrorV( | |
| "WebAssembly runtime exited with exit code {0}", **exit_code), | |
| error.takeError())); | |
| return nullptr; | |
| } | |
| #ifndef _WIN32 | |
| if (launch_info.GetPTY().GetPrimaryFileDescriptor() != | |
| PseudoTerminal::invalid_fd) | |
| process_sp->SetSTDIOPseudoTerminal(launch_info.GetPTY()); | |
| #endif | |
| return process_sp; | |
| } | |
| Status PlatformWasm::ConnectRemote(Args &args) { | |
| if (IsHost()) | |
| return Status::FromErrorString( | |
| "can't connect to the host platform, always connected"); | |
| if (!m_remote_platform_sp) | |
| m_remote_platform_sp = PlatformSP(new PlatformWasmRemoteGDBServer()); | |
| return m_remote_platform_sp->ConnectRemote(args); | |
| } |