Skip to content

Commit a434cac

Browse files
authored
[lldb] Add Populate Methods for ELFLinuxPrPsInfo and ELFLinuxPrStatus (#104109)
To create elf core files there are multiple notes in the core file that contain these structs as the note. These populate methods take a Process and produce fully specified structures that can be used to fill these note sections. The pr also adds tests to ensure these structs are correctly populated.
1 parent 4a57e83 commit a434cac

File tree

5 files changed

+334
-0
lines changed

5 files changed

+334
-0
lines changed

lldb/source/Plugins/Process/elf-core/ThreadElfCore.cpp

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "lldb/Utility/DataExtractor.h"
1414
#include "lldb/Utility/LLDBLog.h"
1515
#include "lldb/Utility/Log.h"
16+
#include "lldb/Utility/ProcessInfo.h"
1617

1718
#include "Plugins/Process/Utility/RegisterContextFreeBSD_i386.h"
1819
#include "Plugins/Process/Utility/RegisterContextFreeBSD_mips64.h"
@@ -318,6 +319,32 @@ Status ELFLinuxPrStatus::Parse(const DataExtractor &data,
318319
return error;
319320
}
320321

322+
static struct compat_timeval
323+
copy_timespecs(const ProcessInstanceInfo::timespec &oth) {
324+
using sec_t = decltype(compat_timeval::tv_sec);
325+
using usec_t = decltype(compat_timeval::tv_usec);
326+
return {static_cast<sec_t>(oth.tv_sec), static_cast<usec_t>(oth.tv_usec)};
327+
}
328+
329+
std::optional<ELFLinuxPrStatus>
330+
ELFLinuxPrStatus::Populate(const lldb::ThreadSP &thread_sp) {
331+
ELFLinuxPrStatus prstatus{};
332+
prstatus.pr_pid = thread_sp->GetID();
333+
lldb::ProcessSP process_sp = thread_sp->GetProcess();
334+
ProcessInstanceInfo info;
335+
if (!process_sp->GetProcessInfo(info))
336+
return std::nullopt;
337+
338+
prstatus.pr_ppid = info.GetParentProcessID();
339+
prstatus.pr_pgrp = info.GetProcessGroupID();
340+
prstatus.pr_sid = info.GetProcessSessionID();
341+
prstatus.pr_utime = copy_timespecs(info.GetUserTime());
342+
prstatus.pr_stime = copy_timespecs(info.GetSystemTime());
343+
prstatus.pr_cutime = copy_timespecs(info.GetCumulativeUserTime());
344+
prstatus.pr_cstime = copy_timespecs(info.GetCumulativeSystemTime());
345+
return prstatus;
346+
}
347+
321348
// Parse PRPSINFO from NOTE entry
322349
ELFLinuxPrPsInfo::ELFLinuxPrPsInfo() {
323350
memset(this, 0, sizeof(ELFLinuxPrPsInfo));
@@ -394,6 +421,108 @@ Status ELFLinuxPrPsInfo::Parse(const DataExtractor &data,
394421
return error;
395422
}
396423

424+
std::optional<ELFLinuxPrPsInfo>
425+
ELFLinuxPrPsInfo::Populate(const lldb::ProcessSP &process_sp) {
426+
ProcessInstanceInfo info;
427+
if (!process_sp->GetProcessInfo(info))
428+
return std::nullopt;
429+
430+
return Populate(info, process_sp->GetState());
431+
}
432+
433+
std::optional<ELFLinuxPrPsInfo>
434+
ELFLinuxPrPsInfo::Populate(const lldb_private::ProcessInstanceInfo &info,
435+
lldb::StateType process_state) {
436+
ELFLinuxPrPsInfo prpsinfo{};
437+
prpsinfo.pr_pid = info.GetProcessID();
438+
prpsinfo.pr_nice = info.GetPriorityValue().value_or(0);
439+
prpsinfo.pr_zomb = 0;
440+
if (auto zombie_opt = info.IsZombie(); zombie_opt.value_or(false)) {
441+
prpsinfo.pr_zomb = 1;
442+
}
443+
/**
444+
* In the linux kernel this comes from:
445+
* state = READ_ONCE(p->__state);
446+
* i = state ? ffz(~state) + 1 : 0;
447+
* psinfo->pr_sname = (i > 5) ? '.' : "RSDTZW"[i];
448+
*
449+
* So we replicate that here. From proc_pid_stats(5)
450+
* R = Running
451+
* S = Sleeping on uninterrutible wait
452+
* D = Waiting on uninterruptable disk sleep
453+
* T = Tracing stop
454+
* Z = Zombie
455+
* W = Paging
456+
*/
457+
switch (process_state) {
458+
case lldb::StateType::eStateSuspended:
459+
prpsinfo.pr_sname = 'S';
460+
prpsinfo.pr_state = 1;
461+
break;
462+
case lldb::StateType::eStateStopped:
463+
[[fallthrough]];
464+
case lldb::StateType::eStateStepping:
465+
prpsinfo.pr_sname = 'T';
466+
prpsinfo.pr_state = 3;
467+
break;
468+
case lldb::StateType::eStateUnloaded:
469+
[[fallthrough]];
470+
case lldb::StateType::eStateRunning:
471+
prpsinfo.pr_sname = 'R';
472+
prpsinfo.pr_state = 0;
473+
break;
474+
default:
475+
break;
476+
}
477+
478+
/**
479+
* pr_flags is left as 0. The values (in linux) are specific
480+
* to the kernel. We recover them from the proc filesystem
481+
* but don't put them in ProcessInfo because it would really
482+
* become very linux specific and the utility here seems pretty
483+
* dubious
484+
*/
485+
486+
if (info.EffectiveUserIDIsValid())
487+
prpsinfo.pr_uid = info.GetUserID();
488+
489+
if (info.EffectiveGroupIDIsValid())
490+
prpsinfo.pr_gid = info.GetGroupID();
491+
492+
if (info.ParentProcessIDIsValid())
493+
prpsinfo.pr_ppid = info.GetParentProcessID();
494+
495+
if (info.ProcessGroupIDIsValid())
496+
prpsinfo.pr_pgrp = info.GetProcessGroupID();
497+
498+
if (info.ProcessSessionIDIsValid())
499+
prpsinfo.pr_sid = info.GetProcessSessionID();
500+
501+
constexpr size_t fname_len = std::extent_v<decltype(prpsinfo.pr_fname)>;
502+
static_assert(fname_len > 0, "This should always be non zero");
503+
const llvm::StringRef fname = info.GetNameAsStringRef();
504+
auto fname_begin = fname.begin();
505+
std::copy_n(fname_begin, std::min(fname_len, fname.size()),
506+
prpsinfo.pr_fname);
507+
prpsinfo.pr_fname[fname_len - 1] = '\0';
508+
auto args = info.GetArguments();
509+
auto argentry_iterator = std::begin(args);
510+
char *psargs = prpsinfo.pr_psargs;
511+
char *psargs_end = std::end(prpsinfo.pr_psargs);
512+
while (psargs < psargs_end && argentry_iterator != args.end()) {
513+
llvm::StringRef argentry = argentry_iterator->ref();
514+
size_t len =
515+
std::min<size_t>(std::distance(psargs, psargs_end), argentry.size());
516+
auto arg_iterator = std::begin(argentry);
517+
psargs = std::copy_n(arg_iterator, len, psargs);
518+
if (psargs != psargs_end)
519+
*(psargs++) = ' ';
520+
++argentry_iterator;
521+
}
522+
*(psargs - 1) = '\0';
523+
return prpsinfo;
524+
}
525+
397526
// Parse SIGINFO from NOTE entry
398527
ELFLinuxSigInfo::ELFLinuxSigInfo() { memset(this, 0, sizeof(ELFLinuxSigInfo)); }
399528

lldb/source/Plugins/Process/elf-core/ThreadElfCore.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,18 @@
1313
#include "lldb/Target/Thread.h"
1414
#include "lldb/Utility/DataExtractor.h"
1515
#include "llvm/ADT/DenseMap.h"
16+
#include <optional>
1617
#include <string>
1718

1819
struct compat_timeval {
1920
alignas(8) uint64_t tv_sec;
2021
alignas(8) uint64_t tv_usec;
2122
};
2223

24+
namespace lldb_private {
25+
class ProcessInstanceInfo;
26+
}
27+
2328
// PRSTATUS structure's size differs based on architecture.
2429
// This is the layout in the x86-64 arch.
2530
// In the i386 case we parse it manually and fill it again
@@ -56,6 +61,9 @@ struct ELFLinuxPrStatus {
5661
lldb_private::Status Parse(const lldb_private::DataExtractor &data,
5762
const lldb_private::ArchSpec &arch);
5863

64+
static std::optional<ELFLinuxPrStatus>
65+
Populate(const lldb::ThreadSP &thread_sp);
66+
5967
// Return the bytesize of the structure
6068
// 64 bit - just sizeof
6169
// 32 bit - hardcoded because we are reusing the struct, but some of the
@@ -112,6 +120,13 @@ struct ELFLinuxPrPsInfo {
112120
lldb_private::Status Parse(const lldb_private::DataExtractor &data,
113121
const lldb_private::ArchSpec &arch);
114122

123+
static std::optional<ELFLinuxPrPsInfo>
124+
Populate(const lldb::ProcessSP &process_sp);
125+
126+
static std::optional<ELFLinuxPrPsInfo>
127+
Populate(const lldb_private::ProcessInstanceInfo &info,
128+
lldb::StateType state);
129+
115130
// Return the bytesize of the structure
116131
// 64 bit - just sizeof
117132
// 32 bit - hardcoded because we are reusing the struct, but some of the

lldb/unittests/Process/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
add_subdirectory(gdb-remote)
22
if (CMAKE_SYSTEM_NAME MATCHES "Linux|Android")
3+
add_subdirectory(elf-core)
34
add_subdirectory(Linux)
45
add_subdirectory(POSIX)
56
endif()
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
add_lldb_unittest(ProcessElfCoreTests
2+
ThreadElfCoreTest.cpp
3+
4+
LINK_LIBS
5+
lldbCore
6+
lldbHost
7+
lldbUtilityHelpers
8+
lldbPluginProcessElfCore
9+
lldbPluginPlatformLinux
10+
11+
LLVMTestingSupport
12+
13+
LINK_COMPONENTS
14+
Support
15+
)
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
//===-- ThreadElfCoreTest.cpp ------------------------------------*- C++
2+
//-*-===//
3+
//
4+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
5+
// See https://llvm.org/LICENSE.txt for license information.
6+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7+
//
8+
//===----------------------------------------------------------------------===//
9+
#include "Plugins/Process/elf-core/ThreadElfCore.h"
10+
#include "Plugins/Platform/Linux/PlatformLinux.h"
11+
#include "TestingSupport/TestUtilities.h"
12+
#include "lldb/Core/Debugger.h"
13+
#include "lldb/Host/FileSystem.h"
14+
#include "lldb/Host/HostInfo.h"
15+
#include "lldb/Target/Process.h"
16+
#include "lldb/Target/Target.h"
17+
#include "lldb/Target/Thread.h"
18+
#include "lldb/Utility/Listener.h"
19+
#include "llvm/TargetParser/Triple.h"
20+
#include "gtest/gtest.h"
21+
22+
#include <memory>
23+
#include <mutex>
24+
#include <unistd.h>
25+
26+
using namespace lldb_private;
27+
28+
namespace {
29+
30+
struct ElfCoreTest : public testing::Test {
31+
static void SetUpTestCase() {
32+
FileSystem::Initialize();
33+
HostInfo::Initialize();
34+
platform_linux::PlatformLinux::Initialize();
35+
std::call_once(TestUtilities::g_debugger_initialize_flag,
36+
[]() { Debugger::Initialize(nullptr); });
37+
}
38+
static void TearDownTestCase() {
39+
platform_linux::PlatformLinux::Terminate();
40+
HostInfo::Terminate();
41+
FileSystem::Terminate();
42+
}
43+
};
44+
45+
struct DummyProcess : public Process {
46+
DummyProcess(lldb::TargetSP target_sp, lldb::ListenerSP listener_sp)
47+
: Process(target_sp, listener_sp) {
48+
SetID(getpid());
49+
}
50+
51+
bool CanDebug(lldb::TargetSP target, bool plugin_specified_by_name) override {
52+
return true;
53+
}
54+
Status DoDestroy() override { return {}; }
55+
void RefreshStateAfterStop() override {}
56+
size_t DoReadMemory(lldb::addr_t vm_addr, void *buf, size_t size,
57+
Status &error) override {
58+
return 0;
59+
}
60+
bool DoUpdateThreadList(ThreadList &old_thread_list,
61+
ThreadList &new_thread_list) override {
62+
return false;
63+
}
64+
llvm::StringRef GetPluginName() override { return "Dummy"; }
65+
};
66+
67+
struct DummyThread : public Thread {
68+
using Thread::Thread;
69+
70+
~DummyThread() override { DestroyThread(); }
71+
72+
void RefreshStateAfterStop() override {}
73+
74+
lldb::RegisterContextSP GetRegisterContext() override { return nullptr; }
75+
76+
lldb::RegisterContextSP
77+
CreateRegisterContextForFrame(StackFrame *frame) override {
78+
return nullptr;
79+
}
80+
81+
bool CalculateStopInfo() override { return false; }
82+
};
83+
84+
lldb::TargetSP CreateTarget(lldb::DebuggerSP &debugger_sp, ArchSpec &arch) {
85+
lldb::PlatformSP platform_sp;
86+
lldb::TargetSP target_sp;
87+
debugger_sp->GetTargetList().CreateTarget(
88+
*debugger_sp, "", arch, eLoadDependentsNo, platform_sp, target_sp);
89+
return target_sp;
90+
}
91+
92+
lldb::ThreadSP CreateThread(lldb::ProcessSP &process_sp) {
93+
lldb::ThreadSP thread_sp =
94+
std::make_shared<DummyThread>(*process_sp.get(), gettid());
95+
if (thread_sp == nullptr) {
96+
return nullptr;
97+
}
98+
process_sp->GetThreadList().AddThread(thread_sp);
99+
100+
return thread_sp;
101+
}
102+
103+
} // namespace
104+
105+
TEST_F(ElfCoreTest, PopulatePrpsInfoTest) {
106+
ArchSpec arch{HostInfo::GetTargetTriple()};
107+
lldb::DebuggerSP debugger_sp = Debugger::CreateInstance();
108+
ASSERT_TRUE(debugger_sp);
109+
110+
lldb::TargetSP target_sp = CreateTarget(debugger_sp, arch);
111+
ASSERT_TRUE(target_sp);
112+
113+
lldb::ListenerSP listener_sp(Listener::MakeListener("dummy"));
114+
lldb::ProcessSP process_sp =
115+
std::make_shared<DummyProcess>(target_sp, listener_sp);
116+
ASSERT_TRUE(process_sp);
117+
auto prpsinfo_opt = ELFLinuxPrPsInfo::Populate(process_sp);
118+
ASSERT_TRUE(prpsinfo_opt.has_value());
119+
ASSERT_EQ(prpsinfo_opt->pr_pid, getpid());
120+
ASSERT_EQ(prpsinfo_opt->pr_state, 0);
121+
ASSERT_EQ(prpsinfo_opt->pr_sname, 'R');
122+
ASSERT_EQ(prpsinfo_opt->pr_zomb, 0);
123+
ASSERT_EQ(prpsinfo_opt->pr_nice, 0);
124+
ASSERT_EQ(prpsinfo_opt->pr_flag, 0UL);
125+
ASSERT_EQ(prpsinfo_opt->pr_uid, getuid());
126+
ASSERT_EQ(prpsinfo_opt->pr_gid, getgid());
127+
ASSERT_EQ(prpsinfo_opt->pr_pid, getpid());
128+
ASSERT_EQ(prpsinfo_opt->pr_ppid, getppid());
129+
ASSERT_EQ(prpsinfo_opt->pr_pgrp, getpgrp());
130+
ASSERT_EQ(prpsinfo_opt->pr_sid, getsid(getpid()));
131+
ASSERT_EQ(std::string{prpsinfo_opt->pr_fname}, "ProcessElfCoreT");
132+
ASSERT_TRUE(std::string{prpsinfo_opt->pr_psargs}.empty());
133+
lldb_private::ProcessInstanceInfo info;
134+
ASSERT_TRUE(process_sp->GetProcessInfo(info));
135+
const char *args[] = {"a.out", "--foo=bar", "--baz=boo", nullptr};
136+
info.SetArguments(args, true);
137+
prpsinfo_opt =
138+
ELFLinuxPrPsInfo::Populate(info, lldb::StateType::eStateStopped);
139+
ASSERT_TRUE(prpsinfo_opt.has_value());
140+
ASSERT_EQ(prpsinfo_opt->pr_pid, getpid());
141+
ASSERT_EQ(prpsinfo_opt->pr_state, 3);
142+
ASSERT_EQ(prpsinfo_opt->pr_sname, 'T');
143+
ASSERT_EQ(std::string{prpsinfo_opt->pr_fname}, "a.out");
144+
ASSERT_FALSE(std::string{prpsinfo_opt->pr_psargs}.empty());
145+
ASSERT_EQ(std::string{prpsinfo_opt->pr_psargs}, "a.out --foo=bar --baz=boo");
146+
}
147+
148+
TEST_F(ElfCoreTest, PopulatePrStatusTest) {
149+
ArchSpec arch{HostInfo::GetTargetTriple()};
150+
lldb::DebuggerSP debugger_sp = Debugger::CreateInstance();
151+
ASSERT_TRUE(debugger_sp);
152+
153+
lldb::TargetSP target_sp = CreateTarget(debugger_sp, arch);
154+
ASSERT_TRUE(target_sp);
155+
156+
lldb::ListenerSP listener_sp(Listener::MakeListener("dummy"));
157+
lldb::ProcessSP process_sp =
158+
std::make_shared<DummyProcess>(target_sp, listener_sp);
159+
ASSERT_TRUE(process_sp);
160+
lldb::ThreadSP thread_sp = CreateThread(process_sp);
161+
ASSERT_TRUE(thread_sp);
162+
auto prstatus_opt = ELFLinuxPrStatus::Populate(thread_sp);
163+
ASSERT_TRUE(prstatus_opt.has_value());
164+
ASSERT_EQ(prstatus_opt->si_signo, 0);
165+
ASSERT_EQ(prstatus_opt->si_code, 0);
166+
ASSERT_EQ(prstatus_opt->si_errno, 0);
167+
ASSERT_EQ(prstatus_opt->pr_cursig, 0);
168+
ASSERT_EQ(prstatus_opt->pr_sigpend, 0UL);
169+
ASSERT_EQ(prstatus_opt->pr_sighold, 0UL);
170+
ASSERT_EQ(prstatus_opt->pr_pid, static_cast<uint32_t>(gettid()));
171+
ASSERT_EQ(prstatus_opt->pr_ppid, static_cast<uint32_t>(getppid()));
172+
ASSERT_EQ(prstatus_opt->pr_pgrp, static_cast<uint32_t>(getpgrp()));
173+
ASSERT_EQ(prstatus_opt->pr_sid, static_cast<uint32_t>(getsid(gettid())));
174+
}

0 commit comments

Comments
 (0)