//===----------------------------------------------------------------------===// // // 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/Process/wasm/ProcessWasm.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/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( PlatformWasm::GetPluginNameStatic()); m_collection_sp->Initialize(g_platformwasm_properties_def); } FileSpec GetRuntimePath() const { return GetPropertyAtIndexAs(ePropertyRuntimePath, {}); } Args GetRuntimeArgs() const { Args result; m_collection_sp->GetPropertyAtIndexAsArgs(ePropertyRuntimeArgs, result); return result; } llvm::StringRef GetPortArg() const { return GetPropertyAtIndexAs(ePropertyPortArg, {}); } }; } // namespace static PluginProperties &GetGlobalProperties() { static PluginProperties g_settings; return g_settings; } llvm::StringRef PlatformWasm::GetPluginDescriptionStatic() { return "Platform for debugging Wasm"; } void PlatformWasm::Initialize() { PluginManager::RegisterPlugin( GetPluginNameStatic(), GetPluginDescriptionStatic(), PlatformWasm::CreateInstance, PlatformWasm::DebuggerInitialize); } void PlatformWasm::Terminate() { PluginManager::UnregisterPlugin(PlatformWasm::CreateInstance); } 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() : "", arch ? arch->GetTriple().getTriple() : ""); 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(); } std::vector PlatformWasm::GetSupportedArchitectures(const ArchSpec &process_host_arch) { return {ArchSpec("wasm32-unknown-unknown-wasm"), ArchSpec("wasm64-unknown-unknown-wasm")}; } static auto get_arg_range(const Args &args) { return llvm::make_range(args.GetArgumentArrayRef().begin(), args.GetArgumentArrayRef().end()); } 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; } 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; } uint16_t port = 0; { // Get the next available port by binding a socket to port 0. TCPSocket listen_socket(true); error = listen_socket.Listen("localhost:0", /*backlog=*/5); if (error.Fail()) return nullptr; port = listen_socket.GetLocalPortNumber(); } if (error.Fail()) return nullptr; Args args({runtime.GetPath(), llvm::formatv("{0}{1}", properties.GetPortArg(), port).str()}); args.AppendArguments(properties.GetRuntimeArgs()); args.AppendArguments(launch_info.GetArguments()); launch_info.SetArguments(args, true); launch_info.SetLaunchInSeparateProcessGroup(true); launch_info.GetFlags().Clear(eLaunchFlagDebug); auto exit_code = std::make_shared>(); 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}", get_arg_range(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->SetSTDIOFileDescriptor( launch_info.GetPTY().ReleasePrimaryFileDescriptor()); #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); }