mirror of
https://github.com/intel/llvm.git
synced 2026-01-26 12:26:52 +08:00
[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.
This commit is contained in:
@@ -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"
|
||||
@@ -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) {
|
||||
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));
|
||||
@@ -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)); }
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "lldb/Target/Thread.h"
|
||||
#include "lldb/Utility/DataExtractor.h"
|
||||
#include "llvm/ADT/DenseMap.h"
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
struct compat_timeval {
|
||||
@@ -20,6 +21,10 @@ struct compat_timeval {
|
||||
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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
15
lldb/unittests/Process/elf-core/CMakeLists.txt
Normal file
15
lldb/unittests/Process/elf-core/CMakeLists.txt
Normal file
@@ -0,0 +1,15 @@
|
||||
add_lldb_unittest(ProcessElfCoreTests
|
||||
ThreadElfCoreTest.cpp
|
||||
|
||||
LINK_LIBS
|
||||
lldbCore
|
||||
lldbHost
|
||||
lldbUtilityHelpers
|
||||
lldbPluginProcessElfCore
|
||||
lldbPluginPlatformLinux
|
||||
|
||||
LLVMTestingSupport
|
||||
|
||||
LINK_COMPONENTS
|
||||
Support
|
||||
)
|
||||
174
lldb/unittests/Process/elf-core/ThreadElfCoreTest.cpp
Normal file
174
lldb/unittests/Process/elf-core/ThreadElfCoreTest.cpp
Normal file
@@ -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);
|
||||
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())));
|
||||
}
|
||||
Reference in New Issue
Block a user