1 | //===-- PlatformPOSIX.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 "PlatformPOSIX.h" |
10 | |
11 | #include "Plugins/Platform/gdb-server/PlatformRemoteGDBServer.h" |
12 | #include "Plugins/TypeSystem/Clang/TypeSystemClang.h" |
13 | #include "lldb/Core/Debugger.h" |
14 | #include "lldb/Core/Module.h" |
15 | #include "lldb/Core/ValueObject.h" |
16 | #include "lldb/Expression/DiagnosticManager.h" |
17 | #include "lldb/Expression/FunctionCaller.h" |
18 | #include "lldb/Expression/UserExpression.h" |
19 | #include "lldb/Expression/UtilityFunction.h" |
20 | #include "lldb/Host/File.h" |
21 | #include "lldb/Host/FileCache.h" |
22 | #include "lldb/Host/FileSystem.h" |
23 | #include "lldb/Host/Host.h" |
24 | #include "lldb/Host/HostInfo.h" |
25 | #include "lldb/Host/ProcessLaunchInfo.h" |
26 | #include "lldb/Target/DynamicLoader.h" |
27 | #include "lldb/Target/ExecutionContext.h" |
28 | #include "lldb/Target/Process.h" |
29 | #include "lldb/Target/Thread.h" |
30 | #include "lldb/Utility/DataBufferHeap.h" |
31 | #include "lldb/Utility/FileSpec.h" |
32 | #include "lldb/Utility/LLDBLog.h" |
33 | #include "lldb/Utility/Log.h" |
34 | #include "lldb/Utility/StreamString.h" |
35 | #include "llvm/ADT/ScopeExit.h" |
36 | #include <optional> |
37 | |
38 | using namespace lldb; |
39 | using namespace lldb_private; |
40 | |
41 | /// Default Constructor |
42 | PlatformPOSIX::PlatformPOSIX(bool is_host) |
43 | : RemoteAwarePlatform(is_host), // This is the local host platform |
44 | m_option_group_platform_rsync(new OptionGroupPlatformRSync()), |
45 | m_option_group_platform_ssh(new OptionGroupPlatformSSH()), |
46 | m_option_group_platform_caching(new OptionGroupPlatformCaching()) {} |
47 | |
48 | /// Destructor. |
49 | /// |
50 | /// The destructor is virtual since this class is designed to be |
51 | /// inherited from by the plug-in instance. |
52 | PlatformPOSIX::~PlatformPOSIX() = default; |
53 | |
54 | lldb_private::OptionGroupOptions *PlatformPOSIX::GetConnectionOptions( |
55 | lldb_private::CommandInterpreter &interpreter) { |
56 | auto iter = m_options.find(x: &interpreter), end = m_options.end(); |
57 | if (iter == end) { |
58 | std::unique_ptr<lldb_private::OptionGroupOptions> options( |
59 | new OptionGroupOptions()); |
60 | options->Append(group: m_option_group_platform_rsync.get()); |
61 | options->Append(group: m_option_group_platform_ssh.get()); |
62 | options->Append(group: m_option_group_platform_caching.get()); |
63 | m_options[&interpreter] = std::move(options); |
64 | } |
65 | |
66 | return m_options.at(k: &interpreter).get(); |
67 | } |
68 | |
69 | static uint32_t chown_file(Platform *platform, const char *path, |
70 | uint32_t uid = UINT32_MAX, |
71 | uint32_t gid = UINT32_MAX) { |
72 | if (!platform || !path || *path == 0) |
73 | return UINT32_MAX; |
74 | |
75 | if (uid == UINT32_MAX && gid == UINT32_MAX) |
76 | return 0; // pretend I did chown correctly - actually I just didn't care |
77 | |
78 | StreamString command; |
79 | command.PutCString(cstr: "chown " ); |
80 | if (uid != UINT32_MAX) |
81 | command.Printf(format: "%d" , uid); |
82 | if (gid != UINT32_MAX) |
83 | command.Printf(format: ":%d" , gid); |
84 | command.Printf(format: "%s" , path); |
85 | int status; |
86 | platform->RunShellCommand(command: command.GetData(), working_dir: FileSpec(), status_ptr: &status, signo_ptr: nullptr, |
87 | command_output: nullptr, timeout: std::chrono::seconds(10)); |
88 | return status; |
89 | } |
90 | |
91 | lldb_private::Status |
92 | PlatformPOSIX::PutFile(const lldb_private::FileSpec &source, |
93 | const lldb_private::FileSpec &destination, uint32_t uid, |
94 | uint32_t gid) { |
95 | Log *log = GetLog(mask: LLDBLog::Platform); |
96 | |
97 | if (IsHost()) { |
98 | if (source == destination) |
99 | return Status(); |
100 | // cp src dst |
101 | // chown uid:gid dst |
102 | std::string src_path(source.GetPath()); |
103 | if (src_path.empty()) |
104 | return Status("unable to get file path for source" ); |
105 | std::string dst_path(destination.GetPath()); |
106 | if (dst_path.empty()) |
107 | return Status("unable to get file path for destination" ); |
108 | StreamString command; |
109 | command.Printf(format: "cp %s %s" , src_path.c_str(), dst_path.c_str()); |
110 | int status; |
111 | RunShellCommand(command: command.GetData(), working_dir: FileSpec(), status_ptr: &status, signo_ptr: nullptr, command_output: nullptr, |
112 | timeout: std::chrono::seconds(10)); |
113 | if (status != 0) |
114 | return Status("unable to perform copy" ); |
115 | if (uid == UINT32_MAX && gid == UINT32_MAX) |
116 | return Status(); |
117 | if (chown_file(platform: this, path: dst_path.c_str(), uid, gid) != 0) |
118 | return Status("unable to perform chown" ); |
119 | return Status(); |
120 | } else if (m_remote_platform_sp) { |
121 | if (GetSupportsRSync()) { |
122 | std::string src_path(source.GetPath()); |
123 | if (src_path.empty()) |
124 | return Status("unable to get file path for source" ); |
125 | std::string dst_path(destination.GetPath()); |
126 | if (dst_path.empty()) |
127 | return Status("unable to get file path for destination" ); |
128 | StreamString command; |
129 | if (GetIgnoresRemoteHostname()) { |
130 | if (!GetRSyncPrefix()) |
131 | command.Printf(format: "rsync %s %s %s" , GetRSyncOpts(), src_path.c_str(), |
132 | dst_path.c_str()); |
133 | else |
134 | command.Printf(format: "rsync %s %s %s%s" , GetRSyncOpts(), src_path.c_str(), |
135 | GetRSyncPrefix(), dst_path.c_str()); |
136 | } else |
137 | command.Printf(format: "rsync %s %s %s:%s" , GetRSyncOpts(), src_path.c_str(), |
138 | GetHostname(), dst_path.c_str()); |
139 | LLDB_LOGF(log, "[PutFile] Running command: %s\n" , command.GetData()); |
140 | int retcode; |
141 | Host::RunShellCommand(command: command.GetData(), working_dir: FileSpec(), status_ptr: &retcode, signo_ptr: nullptr, |
142 | command_output: nullptr, timeout: std::chrono::minutes(1)); |
143 | if (retcode == 0) { |
144 | // Don't chown a local file for a remote system |
145 | // if (chown_file(this,dst_path.c_str(),uid,gid) != 0) |
146 | // return Status("unable to perform chown"); |
147 | return Status(); |
148 | } |
149 | // if we are still here rsync has failed - let's try the slow way before |
150 | // giving up |
151 | } |
152 | } |
153 | return Platform::PutFile(source, destination, uid, gid); |
154 | } |
155 | |
156 | lldb_private::Status PlatformPOSIX::GetFile( |
157 | const lldb_private::FileSpec &source, // remote file path |
158 | const lldb_private::FileSpec &destination) // local file path |
159 | { |
160 | Log *log = GetLog(mask: LLDBLog::Platform); |
161 | |
162 | // Check the args, first. |
163 | std::string src_path(source.GetPath()); |
164 | if (src_path.empty()) |
165 | return Status("unable to get file path for source" ); |
166 | std::string dst_path(destination.GetPath()); |
167 | if (dst_path.empty()) |
168 | return Status("unable to get file path for destination" ); |
169 | if (IsHost()) { |
170 | if (source == destination) |
171 | return Status("local scenario->source and destination are the same file " |
172 | "path: no operation performed" ); |
173 | // cp src dst |
174 | StreamString cp_command; |
175 | cp_command.Printf(format: "cp %s %s" , src_path.c_str(), dst_path.c_str()); |
176 | int status; |
177 | RunShellCommand(command: cp_command.GetData(), working_dir: FileSpec(), status_ptr: &status, signo_ptr: nullptr, command_output: nullptr, |
178 | timeout: std::chrono::seconds(10)); |
179 | if (status != 0) |
180 | return Status("unable to perform copy" ); |
181 | return Status(); |
182 | } else if (m_remote_platform_sp) { |
183 | if (GetSupportsRSync()) { |
184 | StreamString command; |
185 | if (GetIgnoresRemoteHostname()) { |
186 | if (!GetRSyncPrefix()) |
187 | command.Printf(format: "rsync %s %s %s" , GetRSyncOpts(), src_path.c_str(), |
188 | dst_path.c_str()); |
189 | else |
190 | command.Printf(format: "rsync %s %s%s %s" , GetRSyncOpts(), GetRSyncPrefix(), |
191 | src_path.c_str(), dst_path.c_str()); |
192 | } else |
193 | command.Printf(format: "rsync %s %s:%s %s" , GetRSyncOpts(), |
194 | m_remote_platform_sp->GetHostname(), src_path.c_str(), |
195 | dst_path.c_str()); |
196 | LLDB_LOGF(log, "[GetFile] Running command: %s\n" , command.GetData()); |
197 | int retcode; |
198 | Host::RunShellCommand(command: command.GetData(), working_dir: FileSpec(), status_ptr: &retcode, signo_ptr: nullptr, |
199 | command_output: nullptr, timeout: std::chrono::minutes(1)); |
200 | if (retcode == 0) |
201 | return Status(); |
202 | // If we are here, rsync has failed - let's try the slow way before |
203 | // giving up |
204 | } |
205 | // open src and dst |
206 | // read/write, read/write, read/write, ... |
207 | // close src |
208 | // close dst |
209 | LLDB_LOGF(log, "[GetFile] Using block by block transfer....\n" ); |
210 | Status error; |
211 | user_id_t fd_src = OpenFile(file_spec: source, flags: File::eOpenOptionReadOnly, |
212 | mode: lldb::eFilePermissionsFileDefault, error); |
213 | |
214 | if (fd_src == UINT64_MAX) |
215 | return Status("unable to open source file" ); |
216 | |
217 | uint32_t permissions = 0; |
218 | error = GetFilePermissions(file_spec: source, file_permissions&: permissions); |
219 | |
220 | if (permissions == 0) |
221 | permissions = lldb::eFilePermissionsFileDefault; |
222 | |
223 | user_id_t fd_dst = FileCache::GetInstance().OpenFile( |
224 | file_spec: destination, flags: File::eOpenOptionCanCreate | File::eOpenOptionWriteOnly | |
225 | File::eOpenOptionTruncate, |
226 | mode: permissions, error); |
227 | |
228 | if (fd_dst == UINT64_MAX) { |
229 | if (error.Success()) |
230 | error.SetErrorString("unable to open destination file" ); |
231 | } |
232 | |
233 | if (error.Success()) { |
234 | lldb::WritableDataBufferSP buffer_sp(new DataBufferHeap(1024, 0)); |
235 | uint64_t offset = 0; |
236 | error.Clear(); |
237 | while (error.Success()) { |
238 | const uint64_t n_read = ReadFile(fd: fd_src, offset, dst: buffer_sp->GetBytes(), |
239 | dst_len: buffer_sp->GetByteSize(), error); |
240 | if (error.Fail()) |
241 | break; |
242 | if (n_read == 0) |
243 | break; |
244 | if (FileCache::GetInstance().WriteFile(fd: fd_dst, offset, |
245 | src: buffer_sp->GetBytes(), src_len: n_read, |
246 | error) != n_read) { |
247 | if (!error.Fail()) |
248 | error.SetErrorString("unable to write to destination file" ); |
249 | break; |
250 | } |
251 | offset += n_read; |
252 | } |
253 | } |
254 | // Ignore the close error of src. |
255 | if (fd_src != UINT64_MAX) |
256 | CloseFile(fd: fd_src, error); |
257 | // And close the dst file descriptot. |
258 | if (fd_dst != UINT64_MAX && |
259 | !FileCache::GetInstance().CloseFile(fd: fd_dst, error)) { |
260 | if (!error.Fail()) |
261 | error.SetErrorString("unable to close destination file" ); |
262 | } |
263 | return error; |
264 | } |
265 | return Platform::GetFile(source, destination); |
266 | } |
267 | |
268 | std::string PlatformPOSIX::GetPlatformSpecificConnectionInformation() { |
269 | StreamString stream; |
270 | if (GetSupportsRSync()) { |
271 | stream.PutCString(cstr: "rsync" ); |
272 | if ((GetRSyncOpts() && *GetRSyncOpts()) || |
273 | (GetRSyncPrefix() && *GetRSyncPrefix()) || GetIgnoresRemoteHostname()) { |
274 | stream.Printf(format: ", options: " ); |
275 | if (GetRSyncOpts() && *GetRSyncOpts()) |
276 | stream.Printf(format: "'%s' " , GetRSyncOpts()); |
277 | stream.Printf(format: ", prefix: " ); |
278 | if (GetRSyncPrefix() && *GetRSyncPrefix()) |
279 | stream.Printf(format: "'%s' " , GetRSyncPrefix()); |
280 | if (GetIgnoresRemoteHostname()) |
281 | stream.Printf(format: "ignore remote-hostname " ); |
282 | } |
283 | } |
284 | if (GetSupportsSSH()) { |
285 | stream.PutCString(cstr: "ssh" ); |
286 | if (GetSSHOpts() && *GetSSHOpts()) |
287 | stream.Printf(format: ", options: '%s' " , GetSSHOpts()); |
288 | } |
289 | if (GetLocalCacheDirectory() && *GetLocalCacheDirectory()) |
290 | stream.Printf(format: "cache dir: %s" , GetLocalCacheDirectory()); |
291 | if (stream.GetSize()) |
292 | return std::string(stream.GetString()); |
293 | else |
294 | return "" ; |
295 | } |
296 | |
297 | const lldb::UnixSignalsSP &PlatformPOSIX::GetRemoteUnixSignals() { |
298 | if (IsRemote() && m_remote_platform_sp) |
299 | return m_remote_platform_sp->GetRemoteUnixSignals(); |
300 | return Platform::GetRemoteUnixSignals(); |
301 | } |
302 | |
303 | Status PlatformPOSIX::ConnectRemote(Args &args) { |
304 | Status error; |
305 | if (IsHost()) { |
306 | error.SetErrorStringWithFormatv( |
307 | format: "can't connect to the host platform '{0}', always connected" , |
308 | args: GetPluginName()); |
309 | } else { |
310 | if (!m_remote_platform_sp) |
311 | m_remote_platform_sp = |
312 | platform_gdb_server::PlatformRemoteGDBServer::CreateInstance( |
313 | /*force=*/true, arch: nullptr); |
314 | |
315 | if (m_remote_platform_sp && error.Success()) |
316 | error = m_remote_platform_sp->ConnectRemote(args); |
317 | else |
318 | error.SetErrorString("failed to create a 'remote-gdb-server' platform" ); |
319 | |
320 | if (error.Fail()) |
321 | m_remote_platform_sp.reset(); |
322 | } |
323 | |
324 | if (error.Success() && m_remote_platform_sp) { |
325 | if (m_option_group_platform_rsync.get() && |
326 | m_option_group_platform_ssh.get() && |
327 | m_option_group_platform_caching.get()) { |
328 | if (m_option_group_platform_rsync->m_rsync) { |
329 | SetSupportsRSync(true); |
330 | SetRSyncOpts(m_option_group_platform_rsync->m_rsync_opts.c_str()); |
331 | SetRSyncPrefix(m_option_group_platform_rsync->m_rsync_prefix.c_str()); |
332 | SetIgnoresRemoteHostname( |
333 | m_option_group_platform_rsync->m_ignores_remote_hostname); |
334 | } |
335 | if (m_option_group_platform_ssh->m_ssh) { |
336 | SetSupportsSSH(true); |
337 | SetSSHOpts(m_option_group_platform_ssh->m_ssh_opts.c_str()); |
338 | } |
339 | SetLocalCacheDirectory( |
340 | m_option_group_platform_caching->m_cache_dir.c_str()); |
341 | } |
342 | } |
343 | |
344 | return error; |
345 | } |
346 | |
347 | Status PlatformPOSIX::DisconnectRemote() { |
348 | Status error; |
349 | |
350 | if (IsHost()) { |
351 | error.SetErrorStringWithFormatv( |
352 | format: "can't disconnect from the host platform '{0}', always connected" , |
353 | args: GetPluginName()); |
354 | } else { |
355 | if (m_remote_platform_sp) |
356 | error = m_remote_platform_sp->DisconnectRemote(); |
357 | else |
358 | error.SetErrorString("the platform is not currently connected" ); |
359 | } |
360 | return error; |
361 | } |
362 | |
363 | lldb::ProcessSP PlatformPOSIX::Attach(ProcessAttachInfo &attach_info, |
364 | Debugger &debugger, Target *target, |
365 | Status &error) { |
366 | lldb::ProcessSP process_sp; |
367 | Log *log = GetLog(mask: LLDBLog::Platform); |
368 | |
369 | if (IsHost()) { |
370 | if (target == nullptr) { |
371 | TargetSP new_target_sp; |
372 | |
373 | error = debugger.GetTargetList().CreateTarget( |
374 | debugger, user_exe_path: "" , triple_str: "" , get_dependent_modules: eLoadDependentsNo, platform_options: nullptr, target_sp&: new_target_sp); |
375 | target = new_target_sp.get(); |
376 | LLDB_LOGF(log, "PlatformPOSIX::%s created new target" , __FUNCTION__); |
377 | } else { |
378 | error.Clear(); |
379 | LLDB_LOGF(log, "PlatformPOSIX::%s target already existed, setting target" , |
380 | __FUNCTION__); |
381 | } |
382 | |
383 | if (target && error.Success()) { |
384 | if (log) { |
385 | ModuleSP exe_module_sp = target->GetExecutableModule(); |
386 | LLDB_LOGF(log, "PlatformPOSIX::%s set selected target to %p %s" , |
387 | __FUNCTION__, (void *)target, |
388 | exe_module_sp ? exe_module_sp->GetFileSpec().GetPath().c_str() |
389 | : "<null>" ); |
390 | } |
391 | |
392 | process_sp = |
393 | target->CreateProcess(listener_sp: attach_info.GetListenerForProcess(debugger), |
394 | plugin_name: "gdb-remote" , crash_file: nullptr, can_connect: true); |
395 | |
396 | if (process_sp) { |
397 | ListenerSP listener_sp = attach_info.GetHijackListener(); |
398 | if (listener_sp == nullptr) { |
399 | listener_sp = |
400 | Listener::MakeListener(name: "lldb.PlatformPOSIX.attach.hijack" ); |
401 | attach_info.SetHijackListener(listener_sp); |
402 | } |
403 | process_sp->HijackProcessEvents(listener_sp); |
404 | process_sp->SetShadowListener(attach_info.GetShadowListener()); |
405 | error = process_sp->Attach(attach_info); |
406 | } |
407 | } |
408 | } else { |
409 | if (m_remote_platform_sp) |
410 | process_sp = |
411 | m_remote_platform_sp->Attach(attach_info, debugger, target, error); |
412 | else |
413 | error.SetErrorString("the platform is not currently connected" ); |
414 | } |
415 | return process_sp; |
416 | } |
417 | |
418 | lldb::ProcessSP PlatformPOSIX::DebugProcess(ProcessLaunchInfo &launch_info, |
419 | Debugger &debugger, Target &target, |
420 | Status &error) { |
421 | Log *log = GetLog(mask: LLDBLog::Platform); |
422 | LLDB_LOG(log, "target {0}" , &target); |
423 | |
424 | ProcessSP process_sp; |
425 | |
426 | if (!IsHost()) { |
427 | if (m_remote_platform_sp) |
428 | process_sp = m_remote_platform_sp->DebugProcess(launch_info, debugger, |
429 | target, error); |
430 | else |
431 | error.SetErrorString("the platform is not currently connected" ); |
432 | return process_sp; |
433 | } |
434 | |
435 | // |
436 | // For local debugging, we'll insist on having ProcessGDBRemote create the |
437 | // process. |
438 | // |
439 | |
440 | // Make sure we stop at the entry point |
441 | launch_info.GetFlags().Set(eLaunchFlagDebug); |
442 | |
443 | // We always launch the process we are going to debug in a separate process |
444 | // group, since then we can handle ^C interrupts ourselves w/o having to |
445 | // worry about the target getting them as well. |
446 | launch_info.SetLaunchInSeparateProcessGroup(true); |
447 | |
448 | // Now create the gdb-remote process. |
449 | LLDB_LOG(log, "having target create process with gdb-remote plugin" ); |
450 | process_sp = target.CreateProcess(listener_sp: launch_info.GetListener(), plugin_name: "gdb-remote" , |
451 | crash_file: nullptr, can_connect: true); |
452 | |
453 | if (!process_sp) { |
454 | error.SetErrorString("CreateProcess() failed for gdb-remote process" ); |
455 | LLDB_LOG(log, "error: {0}" , error); |
456 | return process_sp; |
457 | } |
458 | |
459 | LLDB_LOG(log, "successfully created process" ); |
460 | |
461 | process_sp->HijackProcessEvents(listener_sp: launch_info.GetHijackListener()); |
462 | process_sp->SetShadowListener(launch_info.GetShadowListener()); |
463 | |
464 | // Log file actions. |
465 | if (log) { |
466 | LLDB_LOG(log, "launching process with the following file actions:" ); |
467 | StreamString stream; |
468 | size_t i = 0; |
469 | const FileAction *file_action; |
470 | while ((file_action = launch_info.GetFileActionAtIndex(idx: i++)) != nullptr) { |
471 | file_action->Dump(stream); |
472 | LLDB_LOG(log, "{0}" , stream.GetData()); |
473 | stream.Clear(); |
474 | } |
475 | } |
476 | |
477 | // Do the launch. |
478 | error = process_sp->Launch(launch_info); |
479 | if (error.Success()) { |
480 | // Hook up process PTY if we have one (which we should for local debugging |
481 | // with llgs). |
482 | int pty_fd = launch_info.GetPTY().ReleasePrimaryFileDescriptor(); |
483 | if (pty_fd != PseudoTerminal::invalid_fd) { |
484 | process_sp->SetSTDIOFileDescriptor(pty_fd); |
485 | LLDB_LOG(log, "hooked up STDIO pty to process" ); |
486 | } else |
487 | LLDB_LOG(log, "not using process STDIO pty" ); |
488 | } else { |
489 | LLDB_LOG(log, "{0}" , error); |
490 | // FIXME figure out appropriate cleanup here. Do we delete the process? |
491 | // Does our caller do that? |
492 | } |
493 | |
494 | return process_sp; |
495 | } |
496 | |
497 | void PlatformPOSIX::CalculateTrapHandlerSymbolNames() { |
498 | m_trap_handlers.push_back(x: ConstString("_sigtramp" )); |
499 | } |
500 | |
501 | Status PlatformPOSIX::EvaluateLibdlExpression( |
502 | lldb_private::Process *process, const char *expr_cstr, |
503 | llvm::StringRef expr_prefix, lldb::ValueObjectSP &result_valobj_sp) { |
504 | DynamicLoader *loader = process->GetDynamicLoader(); |
505 | if (loader) { |
506 | Status error = loader->CanLoadImage(); |
507 | if (error.Fail()) |
508 | return error; |
509 | } |
510 | |
511 | ThreadSP thread_sp(process->GetThreadList().GetExpressionExecutionThread()); |
512 | if (!thread_sp) |
513 | return Status("Selected thread isn't valid" ); |
514 | |
515 | StackFrameSP frame_sp(thread_sp->GetStackFrameAtIndex(idx: 0)); |
516 | if (!frame_sp) |
517 | return Status("Frame 0 isn't valid" ); |
518 | |
519 | ExecutionContext exe_ctx; |
520 | frame_sp->CalculateExecutionContext(exe_ctx); |
521 | EvaluateExpressionOptions expr_options; |
522 | expr_options.SetUnwindOnError(true); |
523 | expr_options.SetIgnoreBreakpoints(true); |
524 | expr_options.SetExecutionPolicy(eExecutionPolicyAlways); |
525 | expr_options.SetLanguage(eLanguageTypeC_plus_plus); |
526 | expr_options.SetTrapExceptions(false); // dlopen can't throw exceptions, so |
527 | // don't do the work to trap them. |
528 | expr_options.SetTimeout(process->GetUtilityExpressionTimeout()); |
529 | |
530 | Status expr_error; |
531 | ExpressionResults result = |
532 | UserExpression::Evaluate(exe_ctx, options: expr_options, expr_cstr, expr_prefix, |
533 | result_valobj_sp, error&: expr_error); |
534 | if (result != eExpressionCompleted) |
535 | return expr_error; |
536 | |
537 | if (result_valobj_sp->GetError().Fail()) |
538 | return result_valobj_sp->GetError(); |
539 | return Status(); |
540 | } |
541 | |
542 | std::unique_ptr<UtilityFunction> |
543 | PlatformPOSIX::MakeLoadImageUtilityFunction(ExecutionContext &exe_ctx, |
544 | Status &error) { |
545 | // Remember to prepend this with the prefix from |
546 | // GetLibdlFunctionDeclarations. The returned values are all in |
547 | // __lldb_dlopen_result for consistency. The wrapper returns a void * but |
548 | // doesn't use it because UtilityFunctions don't work with void returns at |
549 | // present. |
550 | // |
551 | // Use lazy binding so as to not make dlopen()'s success conditional on |
552 | // forcing every symbol in the library. |
553 | // |
554 | // In general, the debugger should allow programs to load & run with |
555 | // libraries as far as they can, instead of defaulting to being super-picky |
556 | // about unavailable symbols. |
557 | // |
558 | // The value "1" appears to imply lazy binding (RTLD_LAZY) on both Darwin |
559 | // and other POSIX OSes. |
560 | static const char *dlopen_wrapper_code = R"( |
561 | const int RTLD_LAZY = 1; |
562 | |
563 | struct __lldb_dlopen_result { |
564 | void *image_ptr; |
565 | const char *error_str; |
566 | }; |
567 | |
568 | extern "C" void *memcpy(void *, const void *, size_t size); |
569 | extern "C" size_t strlen(const char *); |
570 | |
571 | |
572 | void * __lldb_dlopen_wrapper (const char *name, |
573 | const char *path_strings, |
574 | char *buffer, |
575 | __lldb_dlopen_result *result_ptr) |
576 | { |
577 | // This is the case where the name is the full path: |
578 | if (!path_strings) { |
579 | result_ptr->image_ptr = dlopen(name, RTLD_LAZY); |
580 | if (result_ptr->image_ptr) |
581 | result_ptr->error_str = nullptr; |
582 | else |
583 | result_ptr->error_str = dlerror(); |
584 | return nullptr; |
585 | } |
586 | |
587 | // This is the case where we have a list of paths: |
588 | size_t name_len = strlen(name); |
589 | while (path_strings && path_strings[0] != '\0') { |
590 | size_t path_len = strlen(path_strings); |
591 | memcpy((void *) buffer, (void *) path_strings, path_len); |
592 | buffer[path_len] = '/'; |
593 | char *target_ptr = buffer+path_len+1; |
594 | memcpy((void *) target_ptr, (void *) name, name_len + 1); |
595 | result_ptr->image_ptr = dlopen(buffer, RTLD_LAZY); |
596 | if (result_ptr->image_ptr) { |
597 | result_ptr->error_str = nullptr; |
598 | break; |
599 | } |
600 | result_ptr->error_str = dlerror(); |
601 | path_strings = path_strings + path_len + 1; |
602 | } |
603 | return nullptr; |
604 | } |
605 | )" ; |
606 | |
607 | static const char *dlopen_wrapper_name = "__lldb_dlopen_wrapper" ; |
608 | Process *process = exe_ctx.GetProcessSP().get(); |
609 | // Insert the dlopen shim defines into our generic expression: |
610 | std::string expr(std::string(GetLibdlFunctionDeclarations(process))); |
611 | expr.append(s: dlopen_wrapper_code); |
612 | Status utility_error; |
613 | DiagnosticManager diagnostics; |
614 | |
615 | auto utility_fn_or_error = process->GetTarget().CreateUtilityFunction( |
616 | expression: std::move(expr), name: dlopen_wrapper_name, language: eLanguageTypeC_plus_plus, exe_ctx); |
617 | if (!utility_fn_or_error) { |
618 | std::string error_str = llvm::toString(E: utility_fn_or_error.takeError()); |
619 | error.SetErrorStringWithFormat( |
620 | "dlopen error: could not create utility function: %s" , |
621 | error_str.c_str()); |
622 | return nullptr; |
623 | } |
624 | std::unique_ptr<UtilityFunction> dlopen_utility_func_up = |
625 | std::move(*utility_fn_or_error); |
626 | |
627 | Value value; |
628 | ValueList arguments; |
629 | FunctionCaller *do_dlopen_function = nullptr; |
630 | |
631 | // Fetch the clang types we will need: |
632 | TypeSystemClangSP scratch_ts_sp = |
633 | ScratchTypeSystemClang::GetForTarget(target&: process->GetTarget()); |
634 | if (!scratch_ts_sp) |
635 | return nullptr; |
636 | |
637 | CompilerType clang_void_pointer_type = |
638 | scratch_ts_sp->GetBasicType(type: eBasicTypeVoid).GetPointerType(); |
639 | CompilerType clang_char_pointer_type = |
640 | scratch_ts_sp->GetBasicType(type: eBasicTypeChar).GetPointerType(); |
641 | |
642 | // We are passing four arguments, the basename, the list of places to look, |
643 | // a buffer big enough for all the path + name combos, and |
644 | // a pointer to the storage we've made for the result: |
645 | value.SetValueType(Value::ValueType::Scalar); |
646 | value.SetCompilerType(clang_void_pointer_type); |
647 | arguments.PushValue(value); |
648 | value.SetCompilerType(clang_char_pointer_type); |
649 | arguments.PushValue(value); |
650 | arguments.PushValue(value); |
651 | arguments.PushValue(value); |
652 | |
653 | do_dlopen_function = dlopen_utility_func_up->MakeFunctionCaller( |
654 | return_type: clang_void_pointer_type, arg_value_list: arguments, compilation_thread: exe_ctx.GetThreadSP(), error&: utility_error); |
655 | if (utility_error.Fail()) { |
656 | error.SetErrorStringWithFormat( |
657 | "dlopen error: could not make function caller: %s" , |
658 | utility_error.AsCString()); |
659 | return nullptr; |
660 | } |
661 | |
662 | do_dlopen_function = dlopen_utility_func_up->GetFunctionCaller(); |
663 | if (!do_dlopen_function) { |
664 | error.SetErrorString("dlopen error: could not get function caller." ); |
665 | return nullptr; |
666 | } |
667 | |
668 | // We made a good utility function, so cache it in the process: |
669 | return dlopen_utility_func_up; |
670 | } |
671 | |
672 | uint32_t PlatformPOSIX::DoLoadImage(lldb_private::Process *process, |
673 | const lldb_private::FileSpec &remote_file, |
674 | const std::vector<std::string> *paths, |
675 | lldb_private::Status &error, |
676 | lldb_private::FileSpec *loaded_image) { |
677 | if (loaded_image) |
678 | loaded_image->Clear(); |
679 | |
680 | std::string path; |
681 | path = remote_file.GetPath(); |
682 | |
683 | ThreadSP thread_sp = process->GetThreadList().GetExpressionExecutionThread(); |
684 | if (!thread_sp) { |
685 | error.SetErrorString("dlopen error: no thread available to call dlopen." ); |
686 | return LLDB_INVALID_IMAGE_TOKEN; |
687 | } |
688 | |
689 | DiagnosticManager diagnostics; |
690 | |
691 | ExecutionContext exe_ctx; |
692 | thread_sp->CalculateExecutionContext(exe_ctx); |
693 | |
694 | Status utility_error; |
695 | UtilityFunction *dlopen_utility_func; |
696 | ValueList arguments; |
697 | FunctionCaller *do_dlopen_function = nullptr; |
698 | |
699 | // The UtilityFunction is held in the Process. Platforms don't track the |
700 | // lifespan of the Targets that use them, we can't put this in the Platform. |
701 | dlopen_utility_func = process->GetLoadImageUtilityFunction( |
702 | platform: this, factory: [&]() -> std::unique_ptr<UtilityFunction> { |
703 | return MakeLoadImageUtilityFunction(exe_ctx, error); |
704 | }); |
705 | // If we couldn't make it, the error will be in error, so we can exit here. |
706 | if (!dlopen_utility_func) |
707 | return LLDB_INVALID_IMAGE_TOKEN; |
708 | |
709 | do_dlopen_function = dlopen_utility_func->GetFunctionCaller(); |
710 | if (!do_dlopen_function) { |
711 | error.SetErrorString("dlopen error: could not get function caller." ); |
712 | return LLDB_INVALID_IMAGE_TOKEN; |
713 | } |
714 | arguments = do_dlopen_function->GetArgumentValues(); |
715 | |
716 | // Now insert the path we are searching for and the result structure into the |
717 | // target. |
718 | uint32_t permissions = ePermissionsReadable|ePermissionsWritable; |
719 | size_t path_len = path.size() + 1; |
720 | lldb::addr_t path_addr = process->AllocateMemory(size: path_len, |
721 | permissions, |
722 | error&: utility_error); |
723 | if (path_addr == LLDB_INVALID_ADDRESS) { |
724 | error.SetErrorStringWithFormat( |
725 | "dlopen error: could not allocate memory for path: %s" , |
726 | utility_error.AsCString()); |
727 | return LLDB_INVALID_IMAGE_TOKEN; |
728 | } |
729 | |
730 | // Make sure we deallocate the input string memory: |
731 | auto path_cleanup = llvm::make_scope_exit(F: [process, path_addr] { |
732 | // Deallocate the buffer. |
733 | process->DeallocateMemory(ptr: path_addr); |
734 | }); |
735 | |
736 | process->WriteMemory(vm_addr: path_addr, buf: path.c_str(), size: path_len, error&: utility_error); |
737 | if (utility_error.Fail()) { |
738 | error.SetErrorStringWithFormat( |
739 | "dlopen error: could not write path string: %s" , |
740 | utility_error.AsCString()); |
741 | return LLDB_INVALID_IMAGE_TOKEN; |
742 | } |
743 | |
744 | // Make space for our return structure. It is two pointers big: the token |
745 | // and the error string. |
746 | const uint32_t addr_size = process->GetAddressByteSize(); |
747 | lldb::addr_t return_addr = process->CallocateMemory(size: 2*addr_size, |
748 | permissions, |
749 | error&: utility_error); |
750 | if (utility_error.Fail()) { |
751 | error.SetErrorStringWithFormat( |
752 | "dlopen error: could not allocate memory for path: %s" , |
753 | utility_error.AsCString()); |
754 | return LLDB_INVALID_IMAGE_TOKEN; |
755 | } |
756 | |
757 | // Make sure we deallocate the result structure memory |
758 | auto return_cleanup = llvm::make_scope_exit(F: [process, return_addr] { |
759 | // Deallocate the buffer |
760 | process->DeallocateMemory(ptr: return_addr); |
761 | }); |
762 | |
763 | // This will be the address of the storage for paths, if we are using them, |
764 | // or nullptr to signal we aren't. |
765 | lldb::addr_t path_array_addr = 0x0; |
766 | std::optional<llvm::detail::scope_exit<std::function<void()>>> |
767 | path_array_cleanup; |
768 | |
769 | // This is the address to a buffer large enough to hold the largest path |
770 | // conjoined with the library name we're passing in. This is a convenience |
771 | // to avoid having to call malloc in the dlopen function. |
772 | lldb::addr_t buffer_addr = 0x0; |
773 | std::optional<llvm::detail::scope_exit<std::function<void()>>> buffer_cleanup; |
774 | |
775 | // Set the values into our args and write them to the target: |
776 | if (paths != nullptr) { |
777 | // First insert the paths into the target. This is expected to be a |
778 | // continuous buffer with the strings laid out null terminated and |
779 | // end to end with an empty string terminating the buffer. |
780 | // We also compute the buffer's required size as we go. |
781 | size_t buffer_size = 0; |
782 | std::string path_array; |
783 | for (auto path : *paths) { |
784 | // Don't insert empty paths, they will make us abort the path |
785 | // search prematurely. |
786 | if (path.empty()) |
787 | continue; |
788 | size_t path_size = path.size(); |
789 | path_array.append(str: path); |
790 | path_array.push_back(c: '\0'); |
791 | if (path_size > buffer_size) |
792 | buffer_size = path_size; |
793 | } |
794 | path_array.push_back(c: '\0'); |
795 | |
796 | path_array_addr = process->AllocateMemory(size: path_array.size(), |
797 | permissions, |
798 | error&: utility_error); |
799 | if (path_array_addr == LLDB_INVALID_ADDRESS) { |
800 | error.SetErrorStringWithFormat( |
801 | "dlopen error: could not allocate memory for path array: %s" , |
802 | utility_error.AsCString()); |
803 | return LLDB_INVALID_IMAGE_TOKEN; |
804 | } |
805 | |
806 | // Make sure we deallocate the paths array. |
807 | path_array_cleanup.emplace(args: [process, path_array_addr]() { |
808 | // Deallocate the path array. |
809 | process->DeallocateMemory(ptr: path_array_addr); |
810 | }); |
811 | |
812 | process->WriteMemory(vm_addr: path_array_addr, buf: path_array.data(), |
813 | size: path_array.size(), error&: utility_error); |
814 | |
815 | if (utility_error.Fail()) { |
816 | error.SetErrorStringWithFormat( |
817 | "dlopen error: could not write path array: %s" , |
818 | utility_error.AsCString()); |
819 | return LLDB_INVALID_IMAGE_TOKEN; |
820 | } |
821 | // Now make spaces in the target for the buffer. We need to add one for |
822 | // the '/' that the utility function will insert and one for the '\0': |
823 | buffer_size += path.size() + 2; |
824 | |
825 | buffer_addr = process->AllocateMemory(size: buffer_size, |
826 | permissions, |
827 | error&: utility_error); |
828 | if (buffer_addr == LLDB_INVALID_ADDRESS) { |
829 | error.SetErrorStringWithFormat( |
830 | "dlopen error: could not allocate memory for buffer: %s" , |
831 | utility_error.AsCString()); |
832 | return LLDB_INVALID_IMAGE_TOKEN; |
833 | } |
834 | |
835 | // Make sure we deallocate the buffer memory: |
836 | buffer_cleanup.emplace(args: [process, buffer_addr]() { |
837 | // Deallocate the buffer. |
838 | process->DeallocateMemory(ptr: buffer_addr); |
839 | }); |
840 | } |
841 | |
842 | arguments.GetValueAtIndex(idx: 0)->GetScalar() = path_addr; |
843 | arguments.GetValueAtIndex(idx: 1)->GetScalar() = path_array_addr; |
844 | arguments.GetValueAtIndex(idx: 2)->GetScalar() = buffer_addr; |
845 | arguments.GetValueAtIndex(idx: 3)->GetScalar() = return_addr; |
846 | |
847 | lldb::addr_t func_args_addr = LLDB_INVALID_ADDRESS; |
848 | |
849 | diagnostics.Clear(); |
850 | if (!do_dlopen_function->WriteFunctionArguments(exe_ctx, |
851 | args_addr_ref&: func_args_addr, |
852 | arg_values&: arguments, |
853 | diagnostic_manager&: diagnostics)) { |
854 | error.SetErrorStringWithFormat( |
855 | "dlopen error: could not write function arguments: %s" , |
856 | diagnostics.GetString().c_str()); |
857 | return LLDB_INVALID_IMAGE_TOKEN; |
858 | } |
859 | |
860 | // Make sure we clean up the args structure. We can't reuse it because the |
861 | // Platform lives longer than the process and the Platforms don't get a |
862 | // signal to clean up cached data when a process goes away. |
863 | auto args_cleanup = |
864 | llvm::make_scope_exit(F: [do_dlopen_function, &exe_ctx, func_args_addr] { |
865 | do_dlopen_function->DeallocateFunctionResults(exe_ctx, args_addr: func_args_addr); |
866 | }); |
867 | |
868 | // Now run the caller: |
869 | EvaluateExpressionOptions options; |
870 | options.SetExecutionPolicy(eExecutionPolicyAlways); |
871 | options.SetLanguage(eLanguageTypeC_plus_plus); |
872 | options.SetIgnoreBreakpoints(true); |
873 | options.SetUnwindOnError(true); |
874 | options.SetTrapExceptions(false); // dlopen can't throw exceptions, so |
875 | // don't do the work to trap them. |
876 | options.SetTimeout(process->GetUtilityExpressionTimeout()); |
877 | options.SetIsForUtilityExpr(true); |
878 | |
879 | Value return_value; |
880 | // Fetch the clang types we will need: |
881 | TypeSystemClangSP scratch_ts_sp = |
882 | ScratchTypeSystemClang::GetForTarget(target&: process->GetTarget()); |
883 | if (!scratch_ts_sp) { |
884 | error.SetErrorString("dlopen error: Unable to get TypeSystemClang" ); |
885 | return LLDB_INVALID_IMAGE_TOKEN; |
886 | } |
887 | |
888 | CompilerType clang_void_pointer_type = |
889 | scratch_ts_sp->GetBasicType(type: eBasicTypeVoid).GetPointerType(); |
890 | |
891 | return_value.SetCompilerType(clang_void_pointer_type); |
892 | |
893 | ExpressionResults results = do_dlopen_function->ExecuteFunction( |
894 | exe_ctx, args_addr_ptr: &func_args_addr, options, diagnostic_manager&: diagnostics, results&: return_value); |
895 | if (results != eExpressionCompleted) { |
896 | error.SetErrorStringWithFormat( |
897 | "dlopen error: failed executing dlopen wrapper function: %s" , |
898 | diagnostics.GetString().c_str()); |
899 | return LLDB_INVALID_IMAGE_TOKEN; |
900 | } |
901 | |
902 | // Read the dlopen token from the return area: |
903 | lldb::addr_t token = process->ReadPointerFromMemory(vm_addr: return_addr, |
904 | error&: utility_error); |
905 | if (utility_error.Fail()) { |
906 | error.SetErrorStringWithFormat( |
907 | "dlopen error: could not read the return struct: %s" , |
908 | utility_error.AsCString()); |
909 | return LLDB_INVALID_IMAGE_TOKEN; |
910 | } |
911 | |
912 | // The dlopen succeeded! |
913 | if (token != 0x0) { |
914 | if (loaded_image && buffer_addr != 0x0) |
915 | { |
916 | // Capture the image which was loaded. We leave it in the buffer on |
917 | // exit from the dlopen function, so we can just read it from there: |
918 | std::string name_string; |
919 | process->ReadCStringFromMemory(vm_addr: buffer_addr, out_str&: name_string, error&: utility_error); |
920 | if (utility_error.Success()) |
921 | loaded_image->SetFile(path: name_string, style: llvm::sys::path::Style::posix); |
922 | } |
923 | return process->AddImageToken(image_ptr: token); |
924 | } |
925 | |
926 | // We got an error, lets read in the error string: |
927 | std::string dlopen_error_str; |
928 | lldb::addr_t error_addr |
929 | = process->ReadPointerFromMemory(vm_addr: return_addr + addr_size, error&: utility_error); |
930 | if (utility_error.Fail()) { |
931 | error.SetErrorStringWithFormat( |
932 | "dlopen error: could not read error string: %s" , |
933 | utility_error.AsCString()); |
934 | return LLDB_INVALID_IMAGE_TOKEN; |
935 | } |
936 | |
937 | size_t num_chars = process->ReadCStringFromMemory(vm_addr: error_addr + addr_size, |
938 | out_str&: dlopen_error_str, |
939 | error&: utility_error); |
940 | if (utility_error.Success() && num_chars > 0) |
941 | error.SetErrorStringWithFormat("dlopen error: %s" , |
942 | dlopen_error_str.c_str()); |
943 | else |
944 | error.SetErrorStringWithFormat("dlopen failed for unknown reasons." ); |
945 | |
946 | return LLDB_INVALID_IMAGE_TOKEN; |
947 | } |
948 | |
949 | Status PlatformPOSIX::UnloadImage(lldb_private::Process *process, |
950 | uint32_t image_token) { |
951 | const addr_t image_addr = process->GetImagePtrFromToken(token: image_token); |
952 | if (image_addr == LLDB_INVALID_IMAGE_TOKEN) |
953 | return Status("Invalid image token" ); |
954 | |
955 | StreamString expr; |
956 | expr.Printf(format: "dlclose((void *)0x%" PRIx64 ")" , image_addr); |
957 | llvm::StringRef prefix = GetLibdlFunctionDeclarations(process); |
958 | lldb::ValueObjectSP result_valobj_sp; |
959 | Status error = EvaluateLibdlExpression(process, expr_cstr: expr.GetData(), expr_prefix: prefix, |
960 | result_valobj_sp); |
961 | if (error.Fail()) |
962 | return error; |
963 | |
964 | if (result_valobj_sp->GetError().Fail()) |
965 | return result_valobj_sp->GetError(); |
966 | |
967 | Scalar scalar; |
968 | if (result_valobj_sp->ResolveValue(scalar)) { |
969 | if (scalar.UInt(fail_value: 1)) |
970 | return Status("expression failed: \"%s\"" , expr.GetData()); |
971 | process->ResetImageToken(token: image_token); |
972 | } |
973 | return Status(); |
974 | } |
975 | |
976 | llvm::StringRef |
977 | PlatformPOSIX::GetLibdlFunctionDeclarations(lldb_private::Process *process) { |
978 | return R"( |
979 | extern "C" void* dlopen(const char*, int); |
980 | extern "C" void* dlsym(void*, const char*); |
981 | extern "C" int dlclose(void*); |
982 | extern "C" char* dlerror(void); |
983 | )" ; |
984 | } |
985 | |
986 | ConstString PlatformPOSIX::GetFullNameForDylib(ConstString basename) { |
987 | if (basename.IsEmpty()) |
988 | return basename; |
989 | |
990 | StreamString stream; |
991 | stream.Printf(format: "lib%s.so" , basename.GetCString()); |
992 | return ConstString(stream.GetString()); |
993 | } |
994 | |