| 1 | //===-- PlatformQemuUser.cpp ----------------------------------------------===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | |
| 9 | #include "Plugins/Platform/QemuUser/PlatformQemuUser.h" |
| 10 | #include "Plugins/Process/gdb-remote/ProcessGDBRemote.h" |
| 11 | #include "lldb/Core/PluginManager.h" |
| 12 | #include "lldb/Host/FileSystem.h" |
| 13 | #include "lldb/Host/ProcessLaunchInfo.h" |
| 14 | #include "lldb/Interpreter/OptionValueProperties.h" |
| 15 | #include "lldb/Target/Process.h" |
| 16 | #include "lldb/Target/Target.h" |
| 17 | #include "lldb/Utility/LLDBLog.h" |
| 18 | #include "lldb/Utility/Listener.h" |
| 19 | #include "lldb/Utility/Log.h" |
| 20 | |
| 21 | using namespace lldb; |
| 22 | using namespace lldb_private; |
| 23 | |
| 24 | LLDB_PLUGIN_DEFINE(PlatformQemuUser) |
| 25 | |
| 26 | namespace { |
| 27 | #define LLDB_PROPERTIES_platformqemuuser |
| 28 | #include "PlatformQemuUserProperties.inc" |
| 29 | |
| 30 | enum { |
| 31 | #define LLDB_PROPERTIES_platformqemuuser |
| 32 | #include "PlatformQemuUserPropertiesEnum.inc" |
| 33 | }; |
| 34 | |
| 35 | class PluginProperties : public Properties { |
| 36 | public: |
| 37 | PluginProperties() { |
| 38 | m_collection_sp = std::make_shared<OptionValueProperties>( |
| 39 | PlatformQemuUser::GetPluginNameStatic()); |
| 40 | m_collection_sp->Initialize(g_platformqemuuser_properties); |
| 41 | } |
| 42 | |
| 43 | llvm::StringRef GetArchitecture() { |
| 44 | return GetPropertyAtIndexAs<llvm::StringRef>(ePropertyArchitecture, "" ); |
| 45 | } |
| 46 | |
| 47 | FileSpec GetEmulatorPath() { |
| 48 | return GetPropertyAtIndexAs<FileSpec>(ePropertyEmulatorPath, {}); |
| 49 | } |
| 50 | |
| 51 | Args GetEmulatorArgs() { |
| 52 | Args result; |
| 53 | m_collection_sp->GetPropertyAtIndexAsArgs(ePropertyEmulatorArgs, result); |
| 54 | return result; |
| 55 | } |
| 56 | |
| 57 | Environment GetEmulatorEnvVars() { |
| 58 | Args args; |
| 59 | m_collection_sp->GetPropertyAtIndexAsArgs(ePropertyEmulatorEnvVars, args); |
| 60 | return Environment(args); |
| 61 | } |
| 62 | |
| 63 | Environment GetTargetEnvVars() { |
| 64 | Args args; |
| 65 | m_collection_sp->GetPropertyAtIndexAsArgs(ePropertyTargetEnvVars, args); |
| 66 | return Environment(args); |
| 67 | } |
| 68 | }; |
| 69 | |
| 70 | } // namespace |
| 71 | |
| 72 | static PluginProperties &GetGlobalProperties() { |
| 73 | static PluginProperties g_settings; |
| 74 | return g_settings; |
| 75 | } |
| 76 | |
| 77 | llvm::StringRef PlatformQemuUser::GetPluginDescriptionStatic() { |
| 78 | return "Platform for debugging binaries under user mode qemu" ; |
| 79 | } |
| 80 | |
| 81 | void PlatformQemuUser::Initialize() { |
| 82 | PluginManager::RegisterPlugin( |
| 83 | name: GetPluginNameStatic(), description: GetPluginDescriptionStatic(), |
| 84 | create_callback: PlatformQemuUser::CreateInstance, debugger_init_callback: PlatformQemuUser::DebuggerInitialize); |
| 85 | } |
| 86 | |
| 87 | void PlatformQemuUser::Terminate() { |
| 88 | PluginManager::UnregisterPlugin(create_callback: PlatformQemuUser::CreateInstance); |
| 89 | } |
| 90 | |
| 91 | void PlatformQemuUser::DebuggerInitialize(Debugger &debugger) { |
| 92 | if (!PluginManager::GetSettingForPlatformPlugin(debugger, |
| 93 | setting_name: GetPluginNameStatic())) { |
| 94 | PluginManager::CreateSettingForPlatformPlugin( |
| 95 | debugger, properties_sp: GetGlobalProperties().GetValueProperties(), |
| 96 | description: "Properties for the qemu-user platform plugin." , |
| 97 | /*is_global_property=*/true); |
| 98 | } |
| 99 | } |
| 100 | |
| 101 | PlatformSP PlatformQemuUser::CreateInstance(bool force, const ArchSpec *arch) { |
| 102 | if (force) |
| 103 | return PlatformSP(new PlatformQemuUser()); |
| 104 | return nullptr; |
| 105 | } |
| 106 | |
| 107 | std::vector<ArchSpec> |
| 108 | PlatformQemuUser::GetSupportedArchitectures(const ArchSpec &process_host_arch) { |
| 109 | llvm::Triple triple = HostInfo::GetArchitecture().GetTriple(); |
| 110 | triple.setEnvironment(llvm::Triple::UnknownEnvironment); |
| 111 | triple.setArchName(GetGlobalProperties().GetArchitecture()); |
| 112 | if (triple.getArch() != llvm::Triple::UnknownArch) |
| 113 | return {ArchSpec(triple)}; |
| 114 | return {}; |
| 115 | } |
| 116 | |
| 117 | static auto get_arg_range(const Args &args) { |
| 118 | return llvm::make_range(x: args.GetArgumentArrayRef().begin(), |
| 119 | y: args.GetArgumentArrayRef().end()); |
| 120 | } |
| 121 | |
| 122 | // Returns the emulator environment which result in the desired environment |
| 123 | // being presented to the emulated process. We want to be careful about |
| 124 | // preserving the host environment, as it may contain entries (LD_LIBRARY_PATH, |
| 125 | // for example) needed for the operation of the emulator itself. |
| 126 | static Environment ComputeLaunchEnvironment(Environment target, |
| 127 | Environment host) { |
| 128 | std::vector<std::string> set_env; |
| 129 | for (const auto &KV : target) { |
| 130 | // If the host value differs from the target (or is unset), then set it |
| 131 | // through QEMU_SET_ENV. Identical entries will be forwarded automatically. |
| 132 | auto host_it = host.find(Key: KV.first()); |
| 133 | if (host_it == host.end() || host_it->second != KV.second) |
| 134 | set_env.push_back(x: Environment::compose(KeyValue: KV)); |
| 135 | } |
| 136 | llvm::sort(C&: set_env); |
| 137 | |
| 138 | std::vector<llvm::StringRef> unset_env; |
| 139 | for (const auto &KV : host) { |
| 140 | // If the target is missing some host entries, then unset them through |
| 141 | // QEMU_UNSET_ENV. |
| 142 | if (target.count(Key: KV.first()) == 0) |
| 143 | unset_env.push_back(x: KV.first()); |
| 144 | } |
| 145 | llvm::sort(C&: unset_env); |
| 146 | |
| 147 | // The actual QEMU_(UN)SET_ENV variables should not be forwarded to the |
| 148 | // target. |
| 149 | if (!set_env.empty()) { |
| 150 | host["QEMU_SET_ENV" ] = llvm::join(R&: set_env, Separator: "," ); |
| 151 | unset_env.push_back(x: "QEMU_SET_ENV" ); |
| 152 | } |
| 153 | if (!unset_env.empty()) { |
| 154 | unset_env.push_back(x: "QEMU_UNSET_ENV" ); |
| 155 | host["QEMU_UNSET_ENV" ] = llvm::join(R&: unset_env, Separator: "," ); |
| 156 | } |
| 157 | return host; |
| 158 | } |
| 159 | |
| 160 | lldb::ProcessSP PlatformQemuUser::DebugProcess(ProcessLaunchInfo &launch_info, |
| 161 | Debugger &debugger, |
| 162 | Target &target, Status &error) { |
| 163 | Log *log = GetLog(mask: LLDBLog::Platform); |
| 164 | |
| 165 | // If platform.plugin.qemu-user.emulator-path is set, use it. |
| 166 | FileSpec qemu = GetGlobalProperties().GetEmulatorPath(); |
| 167 | // If platform.plugin.qemu-user.emulator-path is not set, build the |
| 168 | // executable name from platform.plugin.qemu-user.architecture. |
| 169 | if (!qemu) { |
| 170 | llvm::StringRef arch = GetGlobalProperties().GetArchitecture(); |
| 171 | // If platform.plugin.qemu-user.architecture is not set, build the |
| 172 | // executable name from the target Triple's ArchName |
| 173 | if (arch.empty()) |
| 174 | arch = target.GetArchitecture().GetTriple().getArchName(); |
| 175 | qemu.SetPath(("qemu-" + arch).str()); |
| 176 | } |
| 177 | FileSystem::Instance().ResolveExecutableLocation(file_spec&: qemu); |
| 178 | |
| 179 | llvm::SmallString<0> socket_model, socket_path; |
| 180 | HostInfo::GetProcessTempDir().GetPath(path&: socket_model); |
| 181 | llvm::sys::path::append(path&: socket_model, a: "qemu-%%%%%%%%.socket" ); |
| 182 | do { |
| 183 | llvm::sys::fs::createUniquePath(Model: socket_model, ResultPath&: socket_path, MakeAbsolute: false); |
| 184 | } while (FileSystem::Instance().Exists(path: socket_path)); |
| 185 | |
| 186 | Args args({qemu.GetPath(), "-g" , socket_path}); |
| 187 | if (!launch_info.GetArg0().empty()) { |
| 188 | args.AppendArgument(arg_str: "-0" ); |
| 189 | args.AppendArgument(arg_str: launch_info.GetArg0()); |
| 190 | } |
| 191 | args.AppendArguments(rhs: GetGlobalProperties().GetEmulatorArgs()); |
| 192 | args.AppendArgument(arg_str: "--" ); |
| 193 | args.AppendArgument(arg_str: launch_info.GetExecutableFile().GetPath()); |
| 194 | for (size_t i = 1; i < launch_info.GetArguments().size(); ++i) |
| 195 | args.AppendArgument(arg_str: launch_info.GetArguments()[i].ref()); |
| 196 | |
| 197 | LLDB_LOG(log, "{0} -> {1}" , get_arg_range(launch_info.GetArguments()), |
| 198 | get_arg_range(args)); |
| 199 | |
| 200 | launch_info.SetArguments(args, first_arg_is_executable: true); |
| 201 | |
| 202 | Environment emulator_env = Host::GetEnvironment(); |
| 203 | if (const std::string &sysroot = GetSDKRootDirectory(); !sysroot.empty()) |
| 204 | emulator_env["QEMU_LD_PREFIX" ] = sysroot; |
| 205 | for (const auto &KV : GetGlobalProperties().GetEmulatorEnvVars()) |
| 206 | emulator_env[KV.first()] = KV.second; |
| 207 | launch_info.GetEnvironment() = ComputeLaunchEnvironment( |
| 208 | target: std::move(launch_info.GetEnvironment()), host: std::move(emulator_env)); |
| 209 | |
| 210 | launch_info.SetLaunchInSeparateProcessGroup(true); |
| 211 | launch_info.GetFlags().Clear(mask: eLaunchFlagDebug); |
| 212 | launch_info.SetMonitorProcessCallback(ProcessLaunchInfo::NoOpMonitorCallback); |
| 213 | |
| 214 | // This is automatically done for host platform in |
| 215 | // Target::FinalizeFileActions, but we're not a host platform. |
| 216 | llvm::Error Err = launch_info.SetUpPtyRedirection(); |
| 217 | LLDB_LOG_ERROR(log, std::move(Err), "SetUpPtyRedirection failed: {0}" ); |
| 218 | |
| 219 | error = Host::LaunchProcess(launch_info); |
| 220 | if (error.Fail()) |
| 221 | return nullptr; |
| 222 | |
| 223 | ProcessSP process_sp = target.CreateProcess( |
| 224 | listener_sp: launch_info.GetListener(), |
| 225 | plugin_name: process_gdb_remote::ProcessGDBRemote::GetPluginNameStatic(), crash_file: nullptr, |
| 226 | can_connect: true); |
| 227 | if (!process_sp) { |
| 228 | error = Status::FromErrorString(str: "Failed to create GDB process" ); |
| 229 | return nullptr; |
| 230 | } |
| 231 | |
| 232 | process_sp->HijackProcessEvents(listener_sp: launch_info.GetHijackListener()); |
| 233 | |
| 234 | error = process_sp->ConnectRemote(remote_url: ("unix-connect://" + socket_path).str()); |
| 235 | if (error.Fail()) |
| 236 | return nullptr; |
| 237 | |
| 238 | if (launch_info.GetPTY().GetPrimaryFileDescriptor() != |
| 239 | PseudoTerminal::invalid_fd) |
| 240 | process_sp->SetSTDIOFileDescriptor( |
| 241 | launch_info.GetPTY().ReleasePrimaryFileDescriptor()); |
| 242 | |
| 243 | return process_sp; |
| 244 | } |
| 245 | |
| 246 | Environment PlatformQemuUser::GetEnvironment() { |
| 247 | Environment env = Host::GetEnvironment(); |
| 248 | for (const auto &KV : GetGlobalProperties().GetTargetEnvVars()) |
| 249 | env[KV.first()] = KV.second; |
| 250 | return env; |
| 251 | } |
| 252 | |