1 //===-- ThreadElfCore.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 "lldb/Target/RegisterContext.h"
10 #include "lldb/Target/StopInfo.h"
11 #include "lldb/Target/Target.h"
12 #include "lldb/Target/UnixSignals.h"
13 #include "lldb/Target/Unwind.h"
14 #include "lldb/Utility/DataExtractor.h"
15 #include "lldb/Utility/LLDBLog.h"
16 #include "lldb/Utility/Log.h"
17 #include "lldb/Utility/ProcessInfo.h"
18
19 #include "Plugins/Process/Utility/RegisterContextFreeBSD_i386.h"
20 #include "Plugins/Process/Utility/RegisterContextFreeBSD_mips64.h"
21 #include "Plugins/Process/Utility/RegisterContextFreeBSD_powerpc.h"
22 #include "Plugins/Process/Utility/RegisterContextFreeBSD_x86_64.h"
23 #include "Plugins/Process/Utility/RegisterContextLinux_i386.h"
24 #ifdef LLDB_ENABLE_ALL
25 #include "Plugins/Process/Utility/RegisterContextLinux_s390x.h"
26 #endif // LLDB_ENABLE_ALL
27 #include "Plugins/Process/Utility/RegisterContextLinux_x86_64.h"
28 #include "Plugins/Process/Utility/RegisterContextNetBSD_i386.h"
29 #include "Plugins/Process/Utility/RegisterContextNetBSD_x86_64.h"
30 #include "Plugins/Process/Utility/RegisterContextOpenBSD_i386.h"
31 #include "Plugins/Process/Utility/RegisterContextOpenBSD_x86_64.h"
32 #include "Plugins/Process/Utility/RegisterInfoPOSIX_arm.h"
33 #include "Plugins/Process/Utility/RegisterInfoPOSIX_arm64.h"
34 #include "Plugins/Process/Utility/RegisterInfoPOSIX_ppc64le.h"
35 #include "ProcessElfCore.h"
36 #include "RegisterContextLinuxCore_x86_64.h"
37 #include "RegisterContextPOSIXCore_arm.h"
38 #include "RegisterContextPOSIXCore_arm64.h"
39 #include "RegisterContextPOSIXCore_loongarch64.h"
40 #include "RegisterContextPOSIXCore_mips64.h"
41 #include "RegisterContextPOSIXCore_powerpc.h"
42 #include "RegisterContextPOSIXCore_ppc64le.h"
43 #include "RegisterContextPOSIXCore_riscv32.h"
44 #include "RegisterContextPOSIXCore_riscv64.h"
45 #ifdef LLDB_ENABLE_ALL
46 #include "RegisterContextPOSIXCore_s390x.h"
47 #endif // LLDB_ENABLE_ALL
48 #include "RegisterContextPOSIXCore_x86_64.h"
49 #include "ThreadElfCore.h"
50
51 #include <memory>
52
53 using namespace lldb;
54 using namespace lldb_private;
55
56 // Construct a Thread object with given data
ThreadElfCore(Process & process,const ThreadData & td)57 ThreadElfCore::ThreadElfCore(Process &process, const ThreadData &td)
58 : Thread(process, td.tid), m_thread_name(td.name), m_thread_reg_ctx_sp(),
59 m_gpregset_data(td.gpregset), m_notes(td.notes),
60 m_siginfo_bytes(std::move(td.siginfo_bytes)), m_signo(td.signo) {}
61
~ThreadElfCore()62 ThreadElfCore::~ThreadElfCore() { DestroyThread(); }
63
RefreshStateAfterStop()64 void ThreadElfCore::RefreshStateAfterStop() {
65 GetRegisterContext()->InvalidateIfNeeded(false);
66 }
67
GetRegisterContext()68 RegisterContextSP ThreadElfCore::GetRegisterContext() {
69 if (!m_reg_context_sp) {
70 m_reg_context_sp = CreateRegisterContextForFrame(nullptr);
71 }
72 return m_reg_context_sp;
73 }
74
75 RegisterContextSP
CreateRegisterContextForFrame(StackFrame * frame)76 ThreadElfCore::CreateRegisterContextForFrame(StackFrame *frame) {
77 RegisterContextSP reg_ctx_sp;
78 uint32_t concrete_frame_idx = 0;
79 Log *log = GetLog(LLDBLog::Thread);
80
81 if (frame)
82 concrete_frame_idx = frame->GetConcreteFrameIndex();
83
84 bool is_linux = false;
85 if (concrete_frame_idx == 0) {
86 if (m_thread_reg_ctx_sp)
87 return m_thread_reg_ctx_sp;
88
89 ProcessElfCore *process = static_cast<ProcessElfCore *>(GetProcess().get());
90 ArchSpec arch = process->GetArchitecture();
91 RegisterInfoInterface *reg_interface = nullptr;
92
93 switch (arch.GetTriple().getOS()) {
94 case llvm::Triple::FreeBSD: {
95 switch (arch.GetMachine()) {
96 case llvm::Triple::aarch64:
97 case llvm::Triple::arm:
98 break;
99 case llvm::Triple::ppc:
100 reg_interface = new RegisterContextFreeBSD_powerpc32(arch);
101 break;
102 case llvm::Triple::ppc64:
103 case llvm::Triple::ppc64le:
104 reg_interface = new RegisterContextFreeBSD_powerpc64(arch);
105 break;
106 case llvm::Triple::mips64:
107 reg_interface = new RegisterContextFreeBSD_mips64(arch);
108 break;
109 case llvm::Triple::x86:
110 reg_interface = new RegisterContextFreeBSD_i386(arch);
111 break;
112 case llvm::Triple::x86_64:
113 reg_interface = new RegisterContextFreeBSD_x86_64(arch);
114 break;
115 default:
116 break;
117 }
118 break;
119 }
120
121 case llvm::Triple::NetBSD: {
122 switch (arch.GetMachine()) {
123 case llvm::Triple::aarch64:
124 break;
125 case llvm::Triple::x86:
126 reg_interface = new RegisterContextNetBSD_i386(arch);
127 break;
128 case llvm::Triple::x86_64:
129 reg_interface = new RegisterContextNetBSD_x86_64(arch);
130 break;
131 default:
132 break;
133 }
134 break;
135 }
136
137 case llvm::Triple::Linux: {
138 is_linux = true;
139 switch (arch.GetMachine()) {
140 case llvm::Triple::aarch64:
141 break;
142 case llvm::Triple::ppc64le:
143 reg_interface = new RegisterInfoPOSIX_ppc64le(arch);
144 break;
145 #ifdef LLDB_ENABLE_ALL
146 case llvm::Triple::systemz:
147 reg_interface = new RegisterContextLinux_s390x(arch);
148 break;
149 #endif // LLDB_ENABLE_ALL
150 case llvm::Triple::x86:
151 reg_interface = new RegisterContextLinux_i386(arch);
152 break;
153 case llvm::Triple::x86_64:
154 reg_interface = new RegisterContextLinux_x86_64(arch);
155 break;
156 default:
157 break;
158 }
159 break;
160 }
161
162 case llvm::Triple::OpenBSD: {
163 switch (arch.GetMachine()) {
164 case llvm::Triple::aarch64:
165 break;
166 case llvm::Triple::x86:
167 reg_interface = new RegisterContextOpenBSD_i386(arch);
168 break;
169 case llvm::Triple::x86_64:
170 reg_interface = new RegisterContextOpenBSD_x86_64(arch);
171 break;
172 default:
173 break;
174 }
175 break;
176 }
177
178 default:
179 break;
180 }
181
182 if (!reg_interface && arch.GetMachine() != llvm::Triple::aarch64 &&
183 arch.GetMachine() != llvm::Triple::arm &&
184 arch.GetMachine() != llvm::Triple::loongarch64 &&
185 arch.GetMachine() != llvm::Triple::riscv64 &&
186 arch.GetMachine() != llvm::Triple::riscv32) {
187 LLDB_LOGF(log, "elf-core::%s:: Architecture(%d) or OS(%d) not supported",
188 __FUNCTION__, arch.GetMachine(), arch.GetTriple().getOS());
189 assert(false && "Architecture or OS not supported");
190 }
191
192 switch (arch.GetMachine()) {
193 case llvm::Triple::aarch64:
194 m_thread_reg_ctx_sp = RegisterContextCorePOSIX_arm64::Create(
195 *this, arch, m_gpregset_data, m_notes);
196 break;
197 case llvm::Triple::arm:
198 m_thread_reg_ctx_sp = std::make_shared<RegisterContextCorePOSIX_arm>(
199 *this, std::make_unique<RegisterInfoPOSIX_arm>(arch), m_gpregset_data,
200 m_notes);
201 break;
202 case llvm::Triple::loongarch64:
203 m_thread_reg_ctx_sp = RegisterContextCorePOSIX_loongarch64::Create(
204 *this, arch, m_gpregset_data, m_notes);
205 break;
206 case llvm::Triple::riscv32:
207 m_thread_reg_ctx_sp = RegisterContextCorePOSIX_riscv32::Create(
208 *this, arch, m_gpregset_data, m_notes);
209 break;
210 case llvm::Triple::riscv64:
211 m_thread_reg_ctx_sp = RegisterContextCorePOSIX_riscv64::Create(
212 *this, arch, m_gpregset_data, m_notes);
213 break;
214 case llvm::Triple::mipsel:
215 case llvm::Triple::mips:
216 m_thread_reg_ctx_sp = std::make_shared<RegisterContextCorePOSIX_mips64>(
217 *this, reg_interface, m_gpregset_data, m_notes);
218 break;
219 case llvm::Triple::mips64:
220 case llvm::Triple::mips64el:
221 m_thread_reg_ctx_sp = std::make_shared<RegisterContextCorePOSIX_mips64>(
222 *this, reg_interface, m_gpregset_data, m_notes);
223 break;
224 case llvm::Triple::ppc:
225 case llvm::Triple::ppc64:
226 m_thread_reg_ctx_sp = std::make_shared<RegisterContextCorePOSIX_powerpc>(
227 *this, reg_interface, m_gpregset_data, m_notes);
228 break;
229 case llvm::Triple::ppc64le:
230 m_thread_reg_ctx_sp = std::make_shared<RegisterContextCorePOSIX_ppc64le>(
231 *this, reg_interface, m_gpregset_data, m_notes);
232 break;
233 #ifdef LLDB_ENABLE_ALL
234 case llvm::Triple::systemz:
235 m_thread_reg_ctx_sp = std::make_shared<RegisterContextCorePOSIX_s390x>(
236 *this, reg_interface, m_gpregset_data, m_notes);
237 break;
238 #endif // LLDB_ENABLE_ALL
239 case llvm::Triple::x86:
240 case llvm::Triple::x86_64:
241 if (is_linux) {
242 m_thread_reg_ctx_sp = std::make_shared<RegisterContextLinuxCore_x86_64>(
243 *this, reg_interface, m_gpregset_data, m_notes);
244 } else {
245 m_thread_reg_ctx_sp = std::make_shared<RegisterContextCorePOSIX_x86_64>(
246 *this, reg_interface, m_gpregset_data, m_notes);
247 }
248 break;
249 default:
250 break;
251 }
252
253 reg_ctx_sp = m_thread_reg_ctx_sp;
254 } else {
255 reg_ctx_sp = GetUnwinder().CreateRegisterContextForFrame(frame);
256 }
257 return reg_ctx_sp;
258 }
259
260 llvm::Expected<std::unique_ptr<llvm::MemoryBuffer>>
GetSiginfo(size_t max_size) const261 ThreadElfCore::GetSiginfo(size_t max_size) const {
262 if (m_siginfo_bytes.empty())
263 return llvm::createStringError(llvm::inconvertibleErrorCode(),
264 "no siginfo note");
265
266 return llvm::MemoryBuffer::getMemBufferCopy(m_siginfo_bytes,
267 "siginfo note bytes");
268 }
269
CalculateStopInfo()270 bool ThreadElfCore::CalculateStopInfo() {
271 ProcessSP process_sp(GetProcess());
272 if (!process_sp)
273 return false;
274
275 PlatformSP platform_sp = process_sp->GetTarget().GetPlatform();
276 if (platform_sp) {
277 lldb::StopInfoSP stopinfo_sp = platform_sp->GetStopInfoFromSiginfo(*this);
278 // The platform SP can optionally handle creating the stop info from the
279 // siginfo value however it's not guaraunteed to be implemented on every
280 // platform, so if we fall through this case, we create from just the signo.
281 if (stopinfo_sp) {
282 SetStopInfo(std::move(stopinfo_sp));
283 return true;
284 }
285 }
286
287 SetStopInfo(StopInfo::CreateStopReasonWithSignal(*this, m_signo));
288 return true;
289 }
290
291 // Parse PRSTATUS from NOTE entry
ELFLinuxPrStatus()292 ELFLinuxPrStatus::ELFLinuxPrStatus() {
293 memset(this, 0, sizeof(ELFLinuxPrStatus));
294 }
295
GetSize(const lldb_private::ArchSpec & arch)296 size_t ELFLinuxPrStatus::GetSize(const lldb_private::ArchSpec &arch) {
297 constexpr size_t mips_linux_pr_status_size_o32 = 96;
298 constexpr size_t mips_linux_pr_status_size_n32 = 72;
299 constexpr size_t num_ptr_size_members = 10;
300 if (arch.IsMIPS()) {
301 std::string abi = arch.GetTargetABI();
302 assert(!abi.empty() && "ABI is not set");
303 if (abi == "n64")
304 return sizeof(ELFLinuxPrStatus);
305 else if (abi == "o32")
306 return mips_linux_pr_status_size_o32;
307 // N32 ABI
308 return mips_linux_pr_status_size_n32;
309 }
310 switch (arch.GetCore()) {
311 case lldb_private::ArchSpec::eCore_x86_32_i386:
312 case lldb_private::ArchSpec::eCore_x86_32_i486:
313 return 72;
314 default:
315 if (arch.GetAddressByteSize() == 8)
316 return sizeof(ELFLinuxPrStatus);
317 else
318 return sizeof(ELFLinuxPrStatus) - num_ptr_size_members * 4;
319 }
320 }
321
Parse(const DataExtractor & data,const ArchSpec & arch)322 Status ELFLinuxPrStatus::Parse(const DataExtractor &data,
323 const ArchSpec &arch) {
324 Status error;
325 if (GetSize(arch) > data.GetByteSize()) {
326 error = Status::FromErrorStringWithFormat(
327 "NT_PRSTATUS size should be %zu, but the remaining bytes are: %" PRIu64,
328 GetSize(arch), data.GetByteSize());
329 return error;
330 }
331
332 // Read field by field to correctly account for endianess of both the core
333 // dump and the platform running lldb.
334 offset_t offset = 0;
335 si_signo = data.GetU32(&offset);
336 si_code = data.GetU32(&offset);
337 si_errno = data.GetU32(&offset);
338
339 pr_cursig = data.GetU16(&offset);
340 offset += 2; // pad
341
342 pr_sigpend = data.GetAddress(&offset);
343 pr_sighold = data.GetAddress(&offset);
344
345 pr_pid = data.GetU32(&offset);
346 pr_ppid = data.GetU32(&offset);
347 pr_pgrp = data.GetU32(&offset);
348 pr_sid = data.GetU32(&offset);
349
350 pr_utime.tv_sec = data.GetAddress(&offset);
351 pr_utime.tv_usec = data.GetAddress(&offset);
352
353 pr_stime.tv_sec = data.GetAddress(&offset);
354 pr_stime.tv_usec = data.GetAddress(&offset);
355
356 pr_cutime.tv_sec = data.GetAddress(&offset);
357 pr_cutime.tv_usec = data.GetAddress(&offset);
358
359 pr_cstime.tv_sec = data.GetAddress(&offset);
360 pr_cstime.tv_usec = data.GetAddress(&offset);
361
362 return error;
363 }
364
365 static struct compat_timeval
copy_timespecs(const ProcessInstanceInfo::timespec & oth)366 copy_timespecs(const ProcessInstanceInfo::timespec &oth) {
367 using sec_t = decltype(compat_timeval::tv_sec);
368 using usec_t = decltype(compat_timeval::tv_usec);
369 return {static_cast<sec_t>(oth.tv_sec), static_cast<usec_t>(oth.tv_usec)};
370 }
371
372 std::optional<ELFLinuxPrStatus>
Populate(const lldb::ThreadSP & thread_sp)373 ELFLinuxPrStatus::Populate(const lldb::ThreadSP &thread_sp) {
374 ELFLinuxPrStatus prstatus{};
375 prstatus.pr_pid = thread_sp->GetID();
376 lldb::ProcessSP process_sp = thread_sp->GetProcess();
377 ProcessInstanceInfo info;
378 if (!process_sp->GetProcessInfo(info))
379 return std::nullopt;
380
381 prstatus.pr_ppid = info.GetParentProcessID();
382 prstatus.pr_pgrp = info.GetProcessGroupID();
383 prstatus.pr_sid = info.GetProcessSessionID();
384 prstatus.pr_utime = copy_timespecs(info.GetUserTime());
385 prstatus.pr_stime = copy_timespecs(info.GetSystemTime());
386 prstatus.pr_cutime = copy_timespecs(info.GetCumulativeUserTime());
387 prstatus.pr_cstime = copy_timespecs(info.GetCumulativeSystemTime());
388 return prstatus;
389 }
390
391 // Parse PRPSINFO from NOTE entry
ELFLinuxPrPsInfo()392 ELFLinuxPrPsInfo::ELFLinuxPrPsInfo() {
393 memset(this, 0, sizeof(ELFLinuxPrPsInfo));
394 }
395
GetSize(const lldb_private::ArchSpec & arch)396 size_t ELFLinuxPrPsInfo::GetSize(const lldb_private::ArchSpec &arch) {
397 constexpr size_t mips_linux_pr_psinfo_size_o32_n32 = 128;
398 if (arch.IsMIPS()) {
399 uint8_t address_byte_size = arch.GetAddressByteSize();
400 if (address_byte_size == 8)
401 return sizeof(ELFLinuxPrPsInfo);
402 return mips_linux_pr_psinfo_size_o32_n32;
403 }
404
405 switch (arch.GetCore()) {
406 case lldb_private::ArchSpec::eCore_s390x_generic:
407 case lldb_private::ArchSpec::eCore_x86_64_x86_64:
408 return sizeof(ELFLinuxPrPsInfo);
409 case lldb_private::ArchSpec::eCore_x86_32_i386:
410 case lldb_private::ArchSpec::eCore_x86_32_i486:
411 return 124;
412 default:
413 return 0;
414 }
415 }
416
Parse(const DataExtractor & data,const ArchSpec & arch)417 Status ELFLinuxPrPsInfo::Parse(const DataExtractor &data,
418 const ArchSpec &arch) {
419 Status error;
420 ByteOrder byteorder = data.GetByteOrder();
421 if (GetSize(arch) > data.GetByteSize()) {
422 error = Status::FromErrorStringWithFormat(
423 "NT_PRPSINFO size should be %zu, but the remaining bytes are: %" PRIu64,
424 GetSize(arch), data.GetByteSize());
425 return error;
426 }
427 size_t size = 0;
428 offset_t offset = 0;
429
430 pr_state = data.GetU8(&offset);
431 pr_sname = data.GetU8(&offset);
432 pr_zomb = data.GetU8(&offset);
433 pr_nice = data.GetU8(&offset);
434 if (data.GetAddressByteSize() == 8) {
435 // Word align the next field on 64 bit.
436 offset += 4;
437 }
438
439 pr_flag = data.GetAddress(&offset);
440
441 if (arch.IsMIPS()) {
442 // The pr_uid and pr_gid is always 32 bit irrespective of platforms
443 pr_uid = data.GetU32(&offset);
444 pr_gid = data.GetU32(&offset);
445 } else {
446 // 16 bit on 32 bit platforms, 32 bit on 64 bit platforms
447 pr_uid = data.GetMaxU64(&offset, data.GetAddressByteSize() >> 1);
448 pr_gid = data.GetMaxU64(&offset, data.GetAddressByteSize() >> 1);
449 }
450
451 pr_pid = data.GetU32(&offset);
452 pr_ppid = data.GetU32(&offset);
453 pr_pgrp = data.GetU32(&offset);
454 pr_sid = data.GetU32(&offset);
455
456 size = 16;
457 data.ExtractBytes(offset, size, byteorder, pr_fname);
458 offset += size;
459
460 size = 80;
461 data.ExtractBytes(offset, size, byteorder, pr_psargs);
462 offset += size;
463
464 return error;
465 }
466
467 std::optional<ELFLinuxPrPsInfo>
Populate(const lldb::ProcessSP & process_sp)468 ELFLinuxPrPsInfo::Populate(const lldb::ProcessSP &process_sp) {
469 ProcessInstanceInfo info;
470 if (!process_sp->GetProcessInfo(info))
471 return std::nullopt;
472
473 return Populate(info, process_sp->GetState());
474 }
475
476 std::optional<ELFLinuxPrPsInfo>
Populate(const lldb_private::ProcessInstanceInfo & info,lldb::StateType process_state)477 ELFLinuxPrPsInfo::Populate(const lldb_private::ProcessInstanceInfo &info,
478 lldb::StateType process_state) {
479 ELFLinuxPrPsInfo prpsinfo{};
480 prpsinfo.pr_pid = info.GetProcessID();
481 prpsinfo.pr_nice = info.GetPriorityValue().value_or(0);
482 prpsinfo.pr_zomb = 0;
483 if (auto zombie_opt = info.IsZombie(); zombie_opt.value_or(false)) {
484 prpsinfo.pr_zomb = 1;
485 }
486 /**
487 * In the linux kernel this comes from:
488 * state = READ_ONCE(p->__state);
489 * i = state ? ffz(~state) + 1 : 0;
490 * psinfo->pr_sname = (i > 5) ? '.' : "RSDTZW"[i];
491 *
492 * So we replicate that here. From proc_pid_stats(5)
493 * R = Running
494 * S = Sleeping on uninterrutible wait
495 * D = Waiting on uninterruptable disk sleep
496 * T = Tracing stop
497 * Z = Zombie
498 * W = Paging
499 */
500 switch (process_state) {
501 case lldb::StateType::eStateSuspended:
502 prpsinfo.pr_sname = 'S';
503 prpsinfo.pr_state = 1;
504 break;
505 case lldb::StateType::eStateStopped:
506 [[fallthrough]];
507 case lldb::StateType::eStateStepping:
508 prpsinfo.pr_sname = 'T';
509 prpsinfo.pr_state = 3;
510 break;
511 case lldb::StateType::eStateUnloaded:
512 [[fallthrough]];
513 case lldb::StateType::eStateRunning:
514 prpsinfo.pr_sname = 'R';
515 prpsinfo.pr_state = 0;
516 break;
517 default:
518 break;
519 }
520
521 /**
522 * pr_flags is left as 0. The values (in linux) are specific
523 * to the kernel. We recover them from the proc filesystem
524 * but don't put them in ProcessInfo because it would really
525 * become very linux specific and the utility here seems pretty
526 * dubious
527 */
528
529 if (info.EffectiveUserIDIsValid())
530 prpsinfo.pr_uid = info.GetUserID();
531
532 if (info.EffectiveGroupIDIsValid())
533 prpsinfo.pr_gid = info.GetGroupID();
534
535 if (info.ParentProcessIDIsValid())
536 prpsinfo.pr_ppid = info.GetParentProcessID();
537
538 if (info.ProcessGroupIDIsValid())
539 prpsinfo.pr_pgrp = info.GetProcessGroupID();
540
541 if (info.ProcessSessionIDIsValid())
542 prpsinfo.pr_sid = info.GetProcessSessionID();
543
544 constexpr size_t fname_len = std::extent_v<decltype(prpsinfo.pr_fname)>;
545 static_assert(fname_len > 0, "This should always be non zero");
546 const llvm::StringRef fname = info.GetNameAsStringRef();
547 auto fname_begin = fname.begin();
548 std::copy_n(fname_begin, std::min(fname_len, fname.size()),
549 prpsinfo.pr_fname);
550 prpsinfo.pr_fname[fname_len - 1] = '\0';
551 auto args = info.GetArguments();
552 auto argentry_iterator = std::begin(args);
553 char *psargs = prpsinfo.pr_psargs;
554 char *psargs_end = std::end(prpsinfo.pr_psargs);
555 while (psargs < psargs_end && argentry_iterator != args.end()) {
556 llvm::StringRef argentry = argentry_iterator->ref();
557 size_t len =
558 std::min<size_t>(std::distance(psargs, psargs_end), argentry.size());
559 auto arg_iterator = std::begin(argentry);
560 psargs = std::copy_n(arg_iterator, len, psargs);
561 if (psargs != psargs_end)
562 *(psargs++) = ' ';
563 ++argentry_iterator;
564 }
565 *(psargs - 1) = '\0';
566 return prpsinfo;
567 }
568