/Users/buildslave/jenkins/workspace/coverage/llvm-project/lldb/source/Plugins/Platform/QemuUser/PlatformQemuUser.cpp
Line | Count | Source (jump to first uncovered line) |
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 | 3.87k | PluginProperties() { |
38 | 3.87k | m_collection_sp = std::make_shared<OptionValueProperties>( |
39 | 3.87k | PlatformQemuUser::GetPluginNameStatic()); |
40 | 3.87k | m_collection_sp->Initialize(g_platformqemuuser_properties); |
41 | 3.87k | } |
42 | | |
43 | 27 | llvm::StringRef GetArchitecture() { |
44 | 27 | return GetPropertyAtIndexAs<llvm::StringRef>(ePropertyArchitecture, ""); |
45 | 27 | } |
46 | | |
47 | 9 | FileSpec GetEmulatorPath() { |
48 | 9 | return GetPropertyAtIndexAs<FileSpec>(ePropertyEmulatorPath, {}); |
49 | 9 | } |
50 | | |
51 | 9 | Args GetEmulatorArgs() { |
52 | 9 | Args result; |
53 | 9 | m_collection_sp->GetPropertyAtIndexAsArgs(ePropertyEmulatorArgs, result); |
54 | 9 | return result; |
55 | 9 | } |
56 | | |
57 | 9 | Environment GetEmulatorEnvVars() { |
58 | 9 | Args args; |
59 | 9 | m_collection_sp->GetPropertyAtIndexAsArgs(ePropertyEmulatorEnvVars, args); |
60 | 9 | return Environment(args); |
61 | 9 | } |
62 | | |
63 | 9 | Environment GetTargetEnvVars() { |
64 | 9 | Args args; |
65 | 9 | m_collection_sp->GetPropertyAtIndexAsArgs(ePropertyTargetEnvVars, args); |
66 | 9 | return Environment(args); |
67 | 9 | } |
68 | | }; |
69 | | |
70 | | } // namespace |
71 | | |
72 | 3.93k | static PluginProperties &GetGlobalProperties() { |
73 | 3.93k | static PluginProperties g_settings; |
74 | 3.93k | return g_settings; |
75 | 3.93k | } |
76 | | |
77 | 3.92k | llvm::StringRef PlatformQemuUser::GetPluginDescriptionStatic() { |
78 | 3.92k | return "Platform for debugging binaries under user mode qemu"; |
79 | 3.92k | } |
80 | | |
81 | 3.92k | void PlatformQemuUser::Initialize() { |
82 | 3.92k | PluginManager::RegisterPlugin( |
83 | 3.92k | GetPluginNameStatic(), GetPluginDescriptionStatic(), |
84 | 3.92k | PlatformQemuUser::CreateInstance, PlatformQemuUser::DebuggerInitialize); |
85 | 3.92k | } |
86 | | |
87 | 3.92k | void PlatformQemuUser::Terminate() { |
88 | 3.92k | PluginManager::UnregisterPlugin(PlatformQemuUser::CreateInstance); |
89 | 3.92k | } |
90 | | |
91 | 6.04k | void PlatformQemuUser::DebuggerInitialize(Debugger &debugger) { |
92 | 6.04k | if (!PluginManager::GetSettingForPlatformPlugin(debugger, |
93 | 6.04k | GetPluginNameStatic())) { |
94 | 3.87k | PluginManager::CreateSettingForPlatformPlugin( |
95 | 3.87k | debugger, GetGlobalProperties().GetValueProperties(), |
96 | 3.87k | "Properties for the qemu-user platform plugin.", |
97 | 3.87k | /*is_global_property=*/true); |
98 | 3.87k | } |
99 | 6.04k | } |
100 | | |
101 | 213 | PlatformSP PlatformQemuUser::CreateInstance(bool force, const ArchSpec *arch) { |
102 | 213 | if (force) |
103 | 15 | return PlatformSP(new PlatformQemuUser()); |
104 | 198 | return nullptr; |
105 | 213 | } |
106 | | |
107 | | std::vector<ArchSpec> |
108 | 26 | PlatformQemuUser::GetSupportedArchitectures(const ArchSpec &process_host_arch) { |
109 | 26 | llvm::Triple triple = HostInfo::GetArchitecture().GetTriple(); |
110 | 26 | triple.setEnvironment(llvm::Triple::UnknownEnvironment); |
111 | 26 | triple.setArchName(GetGlobalProperties().GetArchitecture()); |
112 | 26 | if (triple.getArch() != llvm::Triple::UnknownArch) |
113 | 26 | return {ArchSpec(triple)}; |
114 | 0 | return {}; |
115 | 26 | } |
116 | | |
117 | 0 | static auto get_arg_range(const Args &args) { |
118 | 0 | return llvm::make_range(args.GetArgumentArrayRef().begin(), |
119 | 0 | args.GetArgumentArrayRef().end()); |
120 | 0 | } |
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 | 9 | Environment host) { |
128 | 9 | std::vector<std::string> set_env; |
129 | 143 | 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 | 143 | auto host_it = host.find(KV.first()); |
133 | 143 | if (host_it == host.end() || host_it->second != KV.second) |
134 | 2 | set_env.push_back(Environment::compose(KV)); |
135 | 143 | } |
136 | 9 | llvm::sort(set_env); |
137 | | |
138 | 9 | std::vector<llvm::StringRef> unset_env; |
139 | 186 | for (const auto &KV : host) { |
140 | | // If the target is missing some host entries, then unset them through |
141 | | // QEMU_UNSET_ENV. |
142 | 186 | if (target.count(KV.first()) == 0) |
143 | 43 | unset_env.push_back(KV.first()); |
144 | 186 | } |
145 | 9 | llvm::sort(unset_env); |
146 | | |
147 | | // The actual QEMU_(UN)SET_ENV variables should not be forwarded to the |
148 | | // target. |
149 | 9 | if (!set_env.empty()) { |
150 | 1 | host["QEMU_SET_ENV"] = llvm::join(set_env, ","); |
151 | 1 | unset_env.push_back("QEMU_SET_ENV"); |
152 | 1 | } |
153 | 9 | if (!unset_env.empty()) { |
154 | 4 | unset_env.push_back("QEMU_UNSET_ENV"); |
155 | 4 | host["QEMU_UNSET_ENV"] = llvm::join(unset_env, ","); |
156 | 4 | } |
157 | 9 | return host; |
158 | 9 | } |
159 | | |
160 | | lldb::ProcessSP PlatformQemuUser::DebugProcess(ProcessLaunchInfo &launch_info, |
161 | | Debugger &debugger, |
162 | 9 | Target &target, Status &error) { |
163 | 9 | Log *log = GetLog(LLDBLog::Platform); |
164 | | |
165 | | // If platform.plugin.qemu-user.emulator-path is set, use it. |
166 | 9 | 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 | 9 | if (!qemu) { |
170 | 1 | 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 | 1 | if (arch.empty()) |
174 | 0 | arch = target.GetArchitecture().GetTriple().getArchName(); |
175 | 1 | qemu.SetPath(("qemu-" + arch).str()); |
176 | 1 | } |
177 | 9 | FileSystem::Instance().ResolveExecutableLocation(qemu); |
178 | | |
179 | 9 | llvm::SmallString<0> socket_model, socket_path; |
180 | 9 | HostInfo::GetProcessTempDir().GetPath(socket_model); |
181 | 9 | llvm::sys::path::append(socket_model, "qemu-%%%%%%%%.socket"); |
182 | 9 | do { |
183 | 9 | llvm::sys::fs::createUniquePath(socket_model, socket_path, false); |
184 | 9 | } while (FileSystem::Instance().Exists(socket_path)); |
185 | | |
186 | 9 | Args args({qemu.GetPath(), "-g", socket_path}); |
187 | 9 | if (!launch_info.GetArg0().empty()) { |
188 | 7 | args.AppendArgument("-0"); |
189 | 7 | args.AppendArgument(launch_info.GetArg0()); |
190 | 7 | } |
191 | 9 | args.AppendArguments(GetGlobalProperties().GetEmulatorArgs()); |
192 | 9 | args.AppendArgument("--"); |
193 | 9 | args.AppendArgument(launch_info.GetExecutableFile().GetPath()); |
194 | 23 | for (size_t i = 1; i < launch_info.GetArguments().size(); ++i14 ) |
195 | 14 | args.AppendArgument(launch_info.GetArguments()[i].ref()); |
196 | | |
197 | 9 | LLDB_LOG(log, "{0} -> {1}", get_arg_range(launch_info.GetArguments()), |
198 | 9 | get_arg_range(args)); |
199 | | |
200 | 9 | launch_info.SetArguments(args, true); |
201 | | |
202 | 9 | Environment emulator_env = Host::GetEnvironment(); |
203 | 9 | if (const std::string &sysroot = GetSDKRootDirectory(); !sysroot.empty()) |
204 | 1 | emulator_env["QEMU_LD_PREFIX"] = sysroot; |
205 | 9 | for (const auto &KV : GetGlobalProperties().GetEmulatorEnvVars()) |
206 | 1 | emulator_env[KV.first()] = KV.second; |
207 | 9 | launch_info.GetEnvironment() = ComputeLaunchEnvironment( |
208 | 9 | std::move(launch_info.GetEnvironment()), std::move(emulator_env)); |
209 | | |
210 | 9 | launch_info.SetLaunchInSeparateProcessGroup(true); |
211 | 9 | launch_info.GetFlags().Clear(eLaunchFlagDebug); |
212 | 9 | 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 | 9 | llvm::Error Err = launch_info.SetUpPtyRedirection(); |
217 | 9 | LLDB_LOG_ERROR(log, std::move(Err), "SetUpPtyRedirection failed: {0}"); |
218 | | |
219 | 9 | error = Host::LaunchProcess(launch_info); |
220 | 9 | if (error.Fail()) |
221 | 1 | return nullptr; |
222 | | |
223 | 8 | ProcessSP process_sp = target.CreateProcess( |
224 | 8 | launch_info.GetListener(), |
225 | 8 | process_gdb_remote::ProcessGDBRemote::GetPluginNameStatic(), nullptr, |
226 | 8 | true); |
227 | 8 | if (!process_sp) { |
228 | 0 | error.SetErrorString("Failed to create GDB process"); |
229 | 0 | return nullptr; |
230 | 0 | } |
231 | | |
232 | 8 | process_sp->HijackProcessEvents(launch_info.GetHijackListener()); |
233 | | |
234 | 8 | error = process_sp->ConnectRemote(("unix-connect://" + socket_path).str()); |
235 | 8 | if (error.Fail()) |
236 | 0 | return nullptr; |
237 | | |
238 | 8 | if (launch_info.GetPTY().GetPrimaryFileDescriptor() != |
239 | 8 | PseudoTerminal::invalid_fd) |
240 | 7 | process_sp->SetSTDIOFileDescriptor( |
241 | 7 | launch_info.GetPTY().ReleasePrimaryFileDescriptor()); |
242 | | |
243 | 8 | return process_sp; |
244 | 8 | } |
245 | | |
246 | 9 | Environment PlatformQemuUser::GetEnvironment() { |
247 | 9 | Environment env = Host::GetEnvironment(); |
248 | 9 | for (const auto &KV : GetGlobalProperties().GetTargetEnvVars()) |
249 | 2 | env[KV.first()] = KV.second; |
250 | 9 | return env; |
251 | 9 | } |