Skip to content

[lldb] Add Populate Methods for ELFLinuxPrPsInfo and ELFLinuxPrStatus #104109

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 129 additions & 0 deletions lldb/source/Plugins/Process/elf-core/ThreadElfCore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include "lldb/Utility/DataExtractor.h"
#include "lldb/Utility/LLDBLog.h"
#include "lldb/Utility/Log.h"
#include "lldb/Utility/ProcessInfo.h"

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

static struct compat_timeval
copy_timespecs(const ProcessInstanceInfo::timespec &oth) {
using sec_t = decltype(compat_timeval::tv_sec);
using usec_t = decltype(compat_timeval::tv_usec);
return {static_cast<sec_t>(oth.tv_sec), static_cast<usec_t>(oth.tv_usec)};
}

std::optional<ELFLinuxPrStatus>
ELFLinuxPrStatus::Populate(const lldb::ThreadSP &thread_sp) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be be using a thread_sp to populate process info? We would need to ensure we call this with the main thread only right? Can we just pass in a ProcessSP instead so there can be no error?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes we should. In linux (and I guess in FreeBSD as well) a prstatus object is created for each thread which you can see in ProcessELFCore

904     case ELF::NT_PRSTATUS: {
 905       have_prstatus = true;
 906       ELFLinuxPrStatus prstatus;
 907       Status status = prstatus.Parse(note.data, arch);
 908       if (status.Fail())
 909         return status.ToError();
 910       thread_data.prstatus_sig = prstatus.pr_cursig;
 911       thread_data.tid = prstatus.pr_pid;
 912       uint32_t header_size = ELFLinuxPrStatus::GetSize(arch);
 913       size_t len = note.data.GetByteSize() - header_size;
 914       thread_data.gpregset = DataExtractor(note.data, header_size, len);
 915       break;

The tid is the pid. So in SaveCore we'll need to iterate over all the threads to build each struct.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good.

ELFLinuxPrStatus prstatus{};
prstatus.pr_pid = thread_sp->GetID();
lldb::ProcessSP process_sp = thread_sp->GetProcess();
ProcessInstanceInfo info;
if (!process_sp->GetProcessInfo(info))
return std::nullopt;

prstatus.pr_ppid = info.GetParentProcessID();
prstatus.pr_pgrp = info.GetProcessGroupID();
prstatus.pr_sid = info.GetProcessSessionID();
prstatus.pr_utime = copy_timespecs(info.GetUserTime());
prstatus.pr_stime = copy_timespecs(info.GetSystemTime());
prstatus.pr_cutime = copy_timespecs(info.GetCumulativeUserTime());
prstatus.pr_cstime = copy_timespecs(info.GetCumulativeSystemTime());
return prstatus;
}

// Parse PRPSINFO from NOTE entry
ELFLinuxPrPsInfo::ELFLinuxPrPsInfo() {
memset(this, 0, sizeof(ELFLinuxPrPsInfo));
Expand Down Expand Up @@ -394,6 +421,108 @@ Status ELFLinuxPrPsInfo::Parse(const DataExtractor &data,
return error;
}

std::optional<ELFLinuxPrPsInfo>
ELFLinuxPrPsInfo::Populate(const lldb::ProcessSP &process_sp) {
ProcessInstanceInfo info;
if (!process_sp->GetProcessInfo(info))
return std::nullopt;

return Populate(info, process_sp->GetState());
}

std::optional<ELFLinuxPrPsInfo>
ELFLinuxPrPsInfo::Populate(const lldb_private::ProcessInstanceInfo &info,
lldb::StateType process_state) {
ELFLinuxPrPsInfo prpsinfo{};
prpsinfo.pr_pid = info.GetProcessID();
prpsinfo.pr_nice = info.GetPriorityValue().value_or(0);
prpsinfo.pr_zomb = 0;
if (auto zombie_opt = info.IsZombie(); zombie_opt.value_or(false)) {
prpsinfo.pr_zomb = 1;
}
/**
* In the linux kernel this comes from:
* state = READ_ONCE(p->__state);
* i = state ? ffz(~state) + 1 : 0;
* psinfo->pr_sname = (i > 5) ? '.' : "RSDTZW"[i];
*
* So we replicate that here. From proc_pid_stats(5)
* R = Running
* S = Sleeping on uninterrutible wait
* D = Waiting on uninterruptable disk sleep
* T = Tracing stop
* Z = Zombie
* W = Paging
*/
switch (process_state) {
case lldb::StateType::eStateSuspended:
prpsinfo.pr_sname = 'S';
prpsinfo.pr_state = 1;
break;
case lldb::StateType::eStateStopped:
[[fallthrough]];
case lldb::StateType::eStateStepping:
prpsinfo.pr_sname = 'T';
prpsinfo.pr_state = 3;
break;
case lldb::StateType::eStateUnloaded:
[[fallthrough]];
case lldb::StateType::eStateRunning:
prpsinfo.pr_sname = 'R';
prpsinfo.pr_state = 0;
break;
default:
break;
}

/**
* pr_flags is left as 0. The values (in linux) are specific
* to the kernel. We recover them from the proc filesystem
* but don't put them in ProcessInfo because it would really
* become very linux specific and the utility here seems pretty
* dubious
*/

if (info.EffectiveUserIDIsValid())
prpsinfo.pr_uid = info.GetUserID();

if (info.EffectiveGroupIDIsValid())
prpsinfo.pr_gid = info.GetGroupID();

if (info.ParentProcessIDIsValid())
prpsinfo.pr_ppid = info.GetParentProcessID();

if (info.ProcessGroupIDIsValid())
prpsinfo.pr_pgrp = info.GetProcessGroupID();

if (info.ProcessSessionIDIsValid())
prpsinfo.pr_sid = info.GetProcessSessionID();

constexpr size_t fname_len = std::extent_v<decltype(prpsinfo.pr_fname)>;
static_assert(fname_len > 0, "This should always be non zero");
const llvm::StringRef fname = info.GetNameAsStringRef();
auto fname_begin = fname.begin();
std::copy_n(fname_begin, std::min(fname_len, fname.size()),
prpsinfo.pr_fname);
prpsinfo.pr_fname[fname_len - 1] = '\0';
auto args = info.GetArguments();
auto argentry_iterator = std::begin(args);
char *psargs = prpsinfo.pr_psargs;
char *psargs_end = std::end(prpsinfo.pr_psargs);
while (psargs < psargs_end && argentry_iterator != args.end()) {
llvm::StringRef argentry = argentry_iterator->ref();
size_t len =
std::min<size_t>(std::distance(psargs, psargs_end), argentry.size());
auto arg_iterator = std::begin(argentry);
psargs = std::copy_n(arg_iterator, len, psargs);
if (psargs != psargs_end)
*(psargs++) = ' ';
++argentry_iterator;
}
*(psargs - 1) = '\0';
return prpsinfo;
}

// Parse SIGINFO from NOTE entry
ELFLinuxSigInfo::ELFLinuxSigInfo() { memset(this, 0, sizeof(ELFLinuxSigInfo)); }

Expand Down
15 changes: 15 additions & 0 deletions lldb/source/Plugins/Process/elf-core/ThreadElfCore.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,18 @@
#include "lldb/Target/Thread.h"
#include "lldb/Utility/DataExtractor.h"
#include "llvm/ADT/DenseMap.h"
#include <optional>
#include <string>

struct compat_timeval {
alignas(8) uint64_t tv_sec;
alignas(8) uint64_t tv_usec;
};

namespace lldb_private {
class ProcessInstanceInfo;
}

// PRSTATUS structure's size differs based on architecture.
// This is the layout in the x86-64 arch.
// In the i386 case we parse it manually and fill it again
Expand Down Expand Up @@ -56,6 +61,9 @@ struct ELFLinuxPrStatus {
lldb_private::Status Parse(const lldb_private::DataExtractor &data,
const lldb_private::ArchSpec &arch);

static std::optional<ELFLinuxPrStatus>
Populate(const lldb::ThreadSP &thread_sp);

// Return the bytesize of the structure
// 64 bit - just sizeof
// 32 bit - hardcoded because we are reusing the struct, but some of the
Expand Down Expand Up @@ -112,6 +120,13 @@ struct ELFLinuxPrPsInfo {
lldb_private::Status Parse(const lldb_private::DataExtractor &data,
const lldb_private::ArchSpec &arch);

static std::optional<ELFLinuxPrPsInfo>
Populate(const lldb::ProcessSP &process_sp);

static std::optional<ELFLinuxPrPsInfo>
Populate(const lldb_private::ProcessInstanceInfo &info,
lldb::StateType state);

// Return the bytesize of the structure
// 64 bit - just sizeof
// 32 bit - hardcoded because we are reusing the struct, but some of the
Expand Down
1 change: 1 addition & 0 deletions lldb/unittests/Process/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
add_subdirectory(gdb-remote)
if (CMAKE_SYSTEM_NAME MATCHES "Linux|Android")
add_subdirectory(elf-core)
add_subdirectory(Linux)
add_subdirectory(POSIX)
endif()
Expand Down
15 changes: 15 additions & 0 deletions lldb/unittests/Process/elf-core/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
add_lldb_unittest(ProcessElfCoreTests
ThreadElfCoreTest.cpp

LINK_LIBS
lldbCore
lldbHost
lldbUtilityHelpers
lldbPluginProcessElfCore
lldbPluginPlatformLinux

LLVMTestingSupport

LINK_COMPONENTS
Support
)
174 changes: 174 additions & 0 deletions lldb/unittests/Process/elf-core/ThreadElfCoreTest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
//===-- ThreadElfCoreTest.cpp ------------------------------------*- C++
//-*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "Plugins/Process/elf-core/ThreadElfCore.h"
#include "Plugins/Platform/Linux/PlatformLinux.h"
#include "TestingSupport/TestUtilities.h"
#include "lldb/Core/Debugger.h"
#include "lldb/Host/FileSystem.h"
#include "lldb/Host/HostInfo.h"
#include "lldb/Target/Process.h"
#include "lldb/Target/Target.h"
#include "lldb/Target/Thread.h"
#include "lldb/Utility/Listener.h"
#include "llvm/TargetParser/Triple.h"
#include "gtest/gtest.h"

#include <memory>
#include <mutex>
#include <unistd.h>

using namespace lldb_private;

namespace {

struct ElfCoreTest : public testing::Test {
static void SetUpTestCase() {
FileSystem::Initialize();
HostInfo::Initialize();
platform_linux::PlatformLinux::Initialize();
std::call_once(TestUtilities::g_debugger_initialize_flag,
[]() { Debugger::Initialize(nullptr); });
}
static void TearDownTestCase() {
platform_linux::PlatformLinux::Terminate();
HostInfo::Terminate();
FileSystem::Terminate();
}
};

struct DummyProcess : public Process {
DummyProcess(lldb::TargetSP target_sp, lldb::ListenerSP listener_sp)
: Process(target_sp, listener_sp) {
SetID(getpid());
}

bool CanDebug(lldb::TargetSP target, bool plugin_specified_by_name) override {
return true;
}
Status DoDestroy() override { return {}; }
void RefreshStateAfterStop() override {}
size_t DoReadMemory(lldb::addr_t vm_addr, void *buf, size_t size,
Status &error) override {
return 0;
}
bool DoUpdateThreadList(ThreadList &old_thread_list,
ThreadList &new_thread_list) override {
return false;
}
llvm::StringRef GetPluginName() override { return "Dummy"; }
};

struct DummyThread : public Thread {
using Thread::Thread;

~DummyThread() override { DestroyThread(); }

void RefreshStateAfterStop() override {}

lldb::RegisterContextSP GetRegisterContext() override { return nullptr; }

lldb::RegisterContextSP
CreateRegisterContextForFrame(StackFrame *frame) override {
return nullptr;
}

bool CalculateStopInfo() override { return false; }
};

lldb::TargetSP CreateTarget(lldb::DebuggerSP &debugger_sp, ArchSpec &arch) {
lldb::PlatformSP platform_sp;
lldb::TargetSP target_sp;
debugger_sp->GetTargetList().CreateTarget(
*debugger_sp, "", arch, eLoadDependentsNo, platform_sp, target_sp);
return target_sp;
}

lldb::ThreadSP CreateThread(lldb::ProcessSP &process_sp) {
lldb::ThreadSP thread_sp =
std::make_shared<DummyThread>(*process_sp.get(), gettid());
if (thread_sp == nullptr) {
return nullptr;
}
process_sp->GetThreadList().AddThread(thread_sp);

return thread_sp;
}

} // namespace

TEST_F(ElfCoreTest, PopulatePrpsInfoTest) {
ArchSpec arch{HostInfo::GetTargetTriple()};
lldb::DebuggerSP debugger_sp = Debugger::CreateInstance();
ASSERT_TRUE(debugger_sp);

lldb::TargetSP target_sp = CreateTarget(debugger_sp, arch);
ASSERT_TRUE(target_sp);

lldb::ListenerSP listener_sp(Listener::MakeListener("dummy"));
lldb::ProcessSP process_sp =
std::make_shared<DummyProcess>(target_sp, listener_sp);
ASSERT_TRUE(process_sp);
auto prpsinfo_opt = ELFLinuxPrPsInfo::Populate(process_sp);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be easier to test if the API was like ELFLinuxPrPsInfo::Populate(ProcessInfo, State), as then you could call the function with values fully under your control. That way you could e.g. test boundary conditions on the arguments string (the most complicated part of the function).

If you want you can still have an overload which takes a process (and calls this function), but given that it will likely only ever have a single caller, it could just be inlined into it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah this is a good idea. I'll alter the interface and add more testing

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

ASSERT_TRUE(prpsinfo_opt.has_value());
ASSERT_EQ(prpsinfo_opt->pr_pid, getpid());
ASSERT_EQ(prpsinfo_opt->pr_state, 0);
ASSERT_EQ(prpsinfo_opt->pr_sname, 'R');
ASSERT_EQ(prpsinfo_opt->pr_zomb, 0);
ASSERT_EQ(prpsinfo_opt->pr_nice, 0);
ASSERT_EQ(prpsinfo_opt->pr_flag, 0UL);
ASSERT_EQ(prpsinfo_opt->pr_uid, getuid());
ASSERT_EQ(prpsinfo_opt->pr_gid, getgid());
ASSERT_EQ(prpsinfo_opt->pr_pid, getpid());
ASSERT_EQ(prpsinfo_opt->pr_ppid, getppid());
ASSERT_EQ(prpsinfo_opt->pr_pgrp, getpgrp());
ASSERT_EQ(prpsinfo_opt->pr_sid, getsid(getpid()));
ASSERT_EQ(std::string{prpsinfo_opt->pr_fname}, "ProcessElfCoreT");
ASSERT_TRUE(std::string{prpsinfo_opt->pr_psargs}.empty());
lldb_private::ProcessInstanceInfo info;
ASSERT_TRUE(process_sp->GetProcessInfo(info));
const char *args[] = {"a.out", "--foo=bar", "--baz=boo", nullptr};
info.SetArguments(args, true);
prpsinfo_opt =
ELFLinuxPrPsInfo::Populate(info, lldb::StateType::eStateStopped);
ASSERT_TRUE(prpsinfo_opt.has_value());
ASSERT_EQ(prpsinfo_opt->pr_pid, getpid());
ASSERT_EQ(prpsinfo_opt->pr_state, 3);
ASSERT_EQ(prpsinfo_opt->pr_sname, 'T');
ASSERT_EQ(std::string{prpsinfo_opt->pr_fname}, "a.out");
ASSERT_FALSE(std::string{prpsinfo_opt->pr_psargs}.empty());
ASSERT_EQ(std::string{prpsinfo_opt->pr_psargs}, "a.out --foo=bar --baz=boo");
}

TEST_F(ElfCoreTest, PopulatePrStatusTest) {
ArchSpec arch{HostInfo::GetTargetTriple()};
lldb::DebuggerSP debugger_sp = Debugger::CreateInstance();
ASSERT_TRUE(debugger_sp);

lldb::TargetSP target_sp = CreateTarget(debugger_sp, arch);
ASSERT_TRUE(target_sp);

lldb::ListenerSP listener_sp(Listener::MakeListener("dummy"));
lldb::ProcessSP process_sp =
std::make_shared<DummyProcess>(target_sp, listener_sp);
ASSERT_TRUE(process_sp);
lldb::ThreadSP thread_sp = CreateThread(process_sp);
ASSERT_TRUE(thread_sp);
auto prstatus_opt = ELFLinuxPrStatus::Populate(thread_sp);
ASSERT_TRUE(prstatus_opt.has_value());
ASSERT_EQ(prstatus_opt->si_signo, 0);
ASSERT_EQ(prstatus_opt->si_code, 0);
ASSERT_EQ(prstatus_opt->si_errno, 0);
ASSERT_EQ(prstatus_opt->pr_cursig, 0);
ASSERT_EQ(prstatus_opt->pr_sigpend, 0UL);
ASSERT_EQ(prstatus_opt->pr_sighold, 0UL);
ASSERT_EQ(prstatus_opt->pr_pid, static_cast<uint32_t>(gettid()));
ASSERT_EQ(prstatus_opt->pr_ppid, static_cast<uint32_t>(getppid()));
ASSERT_EQ(prstatus_opt->pr_pgrp, static_cast<uint32_t>(getpgrp()));
ASSERT_EQ(prstatus_opt->pr_sid, static_cast<uint32_t>(getsid(gettid())));
}
Loading