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.SetErrorString("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 | |