Bug Summary

File:build/llvm-toolchain-snapshot-15~++20220420111733+e13d2efed663/lldb/source/Plugins/Platform/POSIX/PlatformPOSIX.cpp
Warning:line 650, column 3
Value stored to 'do_dlopen_function' is never read

Annotated Source Code

Press '?' to see keyboard shortcuts

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