diff --git a/lldb/source/Plugins/Process/Linux/NativeProcessLinux.h b/lldb/source/Plugins/Process/Linux/NativeProcessLinux.h --- a/lldb/source/Plugins/Process/Linux/NativeProcessLinux.h +++ b/lldb/source/Plugins/Process/Linux/NativeProcessLinux.h @@ -12,7 +12,6 @@ #include #include -#include "Plugins/Process/Utility/AuxVector.h" #include "lldb/Host/Debug.h" #include "lldb/Host/HostThread.h" #include "lldb/Host/linux/Support.h" @@ -22,8 +21,8 @@ #include "lldb/lldb-types.h" #include "NativeThreadLinux.h" +#include "Plugins/Process/POSIX/NativeProcessELF.h" #include "ProcessorTrace.h" -#include "lldb/Host/common/NativeProcessProtocol.h" namespace lldb_private { class Status; @@ -37,7 +36,7 @@ /// for debugging. /// /// Changes in the inferior process state are broadcasted. -class NativeProcessLinux : public NativeProcessProtocol { +class NativeProcessLinux : public NativeProcessELF { public: class Factory : public NativeProcessProtocol::Factory { public: @@ -77,8 +76,6 @@ Status DeallocateMemory(lldb::addr_t addr) override; - lldb::addr_t GetSharedLibraryInfoAddress() override; - size_t UpdateThreads() override; const ArchSpec &GetArchitecture() const override { return m_arch; } @@ -103,8 +100,6 @@ return getProcFile(GetID(), "auxv"); } - llvm::Optional GetAuxValue(enum AuxVector::EntryType type); - lldb::user_id_t StartTrace(const TraceOptions &config, Status &error) override; @@ -135,7 +130,6 @@ private: MainLoop::SignalHandleUP m_sigchld_handle; ArchSpec m_arch; - std::unique_ptr m_aux_vector; LazyBool m_supports_mem_region = eLazyBoolCalculate; std::vector> m_mem_region_cache; diff --git a/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp b/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp --- a/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp +++ b/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp @@ -38,6 +38,7 @@ #include "lldb/Utility/State.h" #include "lldb/Utility/Status.h" #include "lldb/Utility/StringExtractor.h" +#include "llvm/BinaryFormat/ELF.h" #include "llvm/Support/Errno.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/Threading.h" @@ -288,7 +289,7 @@ NativeDelegate &delegate, const ArchSpec &arch, MainLoop &mainloop, llvm::ArrayRef<::pid_t> tids) - : NativeProcessProtocol(pid, terminal_fd, delegate), m_arch(arch) { + : NativeProcessELF(pid, terminal_fd, delegate), m_arch(arch) { if (m_terminal_fd != -1) { Status status = EnsureFDFlags(m_terminal_fd, O_NONBLOCK); assert(status.Success()); @@ -1389,11 +1390,6 @@ return Status("not implemented"); } -lldb::addr_t NativeProcessLinux::GetSharedLibraryInfoAddress() { - // punt on this for now - return LLDB_INVALID_ADDRESS; -} - size_t NativeProcessLinux::UpdateThreads() { // The NativeProcessLinux monitoring threads are always up to date with // respect to thread state and they keep the thread list populated properly. @@ -2082,18 +2078,3 @@ return error; } - -llvm::Optional -NativeProcessLinux::GetAuxValue(enum AuxVector::EntryType type) { - if (m_aux_vector == nullptr) { - auto buffer_or_error = GetAuxvData(); - if (!buffer_or_error) - return llvm::None; - DataExtractor auxv_data(buffer_or_error.get()->getBufferStart(), - buffer_or_error.get()->getBufferSize(), - GetByteOrder(), GetAddressByteSize()); - m_aux_vector = llvm::make_unique(auxv_data); - } - - return m_aux_vector->GetAuxValue(type); -} diff --git a/lldb/source/Plugins/Process/POSIX/CMakeLists.txt b/lldb/source/Plugins/Process/POSIX/CMakeLists.txt --- a/lldb/source/Plugins/Process/POSIX/CMakeLists.txt +++ b/lldb/source/Plugins/Process/POSIX/CMakeLists.txt @@ -1,9 +1,11 @@ add_lldb_library(lldbPluginProcessPOSIX PLUGIN CrashReason.cpp + NativeProcessELF.cpp ProcessMessage.cpp ProcessPOSIXLog.cpp LINK_LIBS + lldbPluginProcessUtility lldbUtility LINK_COMPONENTS Support diff --git a/lldb/source/Plugins/Process/POSIX/NativeProcessELF.h b/lldb/source/Plugins/Process/POSIX/NativeProcessELF.h new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/Process/POSIX/NativeProcessELF.h @@ -0,0 +1,46 @@ +//===-- NativeProcessELF.h ------------------------------------ -*- 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 +// +//===----------------------------------------------------------------------===// + +#ifndef liblldb_NativeProcessELF_H_ +#define liblldb_NativeProcessELF_H_ + +#include "Plugins/Process/Utility/AuxVector.h" +#include "lldb/Host/common/NativeProcessProtocol.h" +#include "llvm/BinaryFormat/ELF.h" + +namespace lldb_private { + +/// \class NativeProcessELF +/// Abstract class that extends \a NativeProcessProtocol with ELF specific +/// logic. Meant to be subclassed by ELF based NativeProcess* implementations. +class NativeProcessELF : public NativeProcessProtocol { + using NativeProcessProtocol::NativeProcessProtocol; + +protected: + template struct ELFLinkMap { + T l_addr; + T l_name; + T l_ld; + T l_next; + T l_prev; + }; + + llvm::Optional GetAuxValue(enum AuxVector::EntryType type); + + lldb::addr_t GetSharedLibraryInfoAddress() override; + + template + lldb::addr_t GetELFImageInfoAddress(); + + std::unique_ptr m_aux_vector; + llvm::Optional m_shared_library_info_addr; +}; + +} // namespace lldb_private + +#endif \ No newline at end of file diff --git a/lldb/source/Plugins/Process/POSIX/NativeProcessELF.cpp b/lldb/source/Plugins/Process/POSIX/NativeProcessELF.cpp new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/Process/POSIX/NativeProcessELF.cpp @@ -0,0 +1,110 @@ +//===-- NativeProcessELF.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 "NativeProcessELF.h" + +#include "lldb/Utility/DataExtractor.h" + +namespace lldb_private { + +llvm::Optional +NativeProcessELF::GetAuxValue(enum AuxVector::EntryType type) { + if (m_aux_vector == nullptr) { + auto buffer_or_error = GetAuxvData(); + if (!buffer_or_error) + return llvm::None; + DataExtractor auxv_data(buffer_or_error.get()->getBufferStart(), + buffer_or_error.get()->getBufferSize(), + GetByteOrder(), GetAddressByteSize()); + m_aux_vector = llvm::make_unique(auxv_data); + } + + return m_aux_vector->GetAuxValue(type); +} + +lldb::addr_t NativeProcessELF::GetSharedLibraryInfoAddress() { + if (!m_shared_library_info_addr.hasValue()) { + if (GetAddressByteSize() == 8) + m_shared_library_info_addr = + GetELFImageInfoAddress(); + else + m_shared_library_info_addr = + GetELFImageInfoAddress(); + } + + return m_shared_library_info_addr.getValue(); +} + +template +lldb::addr_t NativeProcessELF::GetELFImageInfoAddress() { + llvm::Optional maybe_phdr_addr = + GetAuxValue(AuxVector::AUXV_AT_PHDR); + llvm::Optional maybe_phdr_entry_size = + GetAuxValue(AuxVector::AUXV_AT_PHENT); + llvm::Optional maybe_phdr_num_entries = + GetAuxValue(AuxVector::AUXV_AT_PHNUM); + if (!maybe_phdr_addr || !maybe_phdr_entry_size || !maybe_phdr_num_entries) + return LLDB_INVALID_ADDRESS; + lldb::addr_t phdr_addr = *maybe_phdr_addr; + size_t phdr_entry_size = *maybe_phdr_entry_size; + size_t phdr_num_entries = *maybe_phdr_num_entries; + + // Find the PT_DYNAMIC segment (.dynamic section) in the program header and + // what the load bias by calculating the difference of the program header + // load address and its virtual address. + lldb::offset_t load_bias; + bool found_load_bias = false; + lldb::addr_t dynamic_section_addr = 0; + uint64_t dynamic_section_size = 0; + bool found_dynamic_section = false; + ELF_PHDR phdr_entry; + for (size_t i = 0; i < phdr_num_entries; i++) { + size_t bytes_read; + auto error = ReadMemory(phdr_addr + i * phdr_entry_size, &phdr_entry, + sizeof(phdr_entry), bytes_read); + if (!error.Success()) + return LLDB_INVALID_ADDRESS; + if (phdr_entry.p_type == llvm::ELF::PT_PHDR) { + load_bias = phdr_addr - phdr_entry.p_vaddr; + found_load_bias = true; + } + + if (phdr_entry.p_type == llvm::ELF::PT_DYNAMIC) { + dynamic_section_addr = phdr_entry.p_vaddr; + dynamic_section_size = phdr_entry.p_memsz; + found_dynamic_section = true; + } + } + + if (!found_load_bias || !found_dynamic_section) + return LLDB_INVALID_ADDRESS; + + // Find the DT_DEBUG entry in the .dynamic section + dynamic_section_addr += load_bias; + ELF_DYN dynamic_entry; + size_t dynamic_num_entries = dynamic_section_size / sizeof(dynamic_entry); + for (size_t i = 0; i < dynamic_num_entries; i++) { + size_t bytes_read; + auto error = ReadMemory(dynamic_section_addr + i * sizeof(dynamic_entry), + &dynamic_entry, sizeof(dynamic_entry), bytes_read); + if (!error.Success()) + return LLDB_INVALID_ADDRESS; + // Return the &DT_DEBUG->d_ptr which points to r_debug which contains the + // link_map. + if (dynamic_entry.d_tag == llvm::ELF::DT_DEBUG) { + return dynamic_section_addr + i * sizeof(dynamic_entry) + + sizeof(dynamic_entry.d_tag); + } + } + + return LLDB_INVALID_ADDRESS; +} + +} // namespace lldb_private \ No newline at end of file diff --git a/lldb/unittests/Host/NativeProcessProtocolTest.cpp b/lldb/unittests/Host/NativeProcessProtocolTest.cpp --- a/lldb/unittests/Host/NativeProcessProtocolTest.cpp +++ b/lldb/unittests/Host/NativeProcessProtocolTest.cpp @@ -6,6 +6,8 @@ // //===----------------------------------------------------------------------===// +#include "NativeProcessTestUtils.h" + #include "lldb/Host/common/NativeProcessProtocol.h" #include "llvm/Testing/Support/Error.h" #include "gmock/gmock.h" @@ -14,142 +16,10 @@ using namespace lldb; using namespace testing; -namespace { -class MockDelegate : public NativeProcessProtocol::NativeDelegate { -public: - MOCK_METHOD1(InitializeDelegate, void(NativeProcessProtocol *Process)); - MOCK_METHOD2(ProcessStateChanged, - void(NativeProcessProtocol *Process, StateType State)); - MOCK_METHOD1(DidExec, void(NativeProcessProtocol *Process)); -}; - -// NB: This class doesn't use the override keyword to avoid -// -Winconsistent-missing-override warnings from the compiler. The -// inconsistency comes from the overriding definitions in the MOCK_*** macros. -class MockProcess : public NativeProcessProtocol { -public: - MockProcess(NativeDelegate &Delegate, const ArchSpec &Arch, - lldb::pid_t Pid = 1) - : NativeProcessProtocol(Pid, -1, Delegate), Arch(Arch) {} - - MOCK_METHOD1(Resume, Status(const ResumeActionList &ResumeActions)); - MOCK_METHOD0(Halt, Status()); - MOCK_METHOD0(Detach, Status()); - MOCK_METHOD1(Signal, Status(int Signo)); - MOCK_METHOD0(Kill, Status()); - MOCK_METHOD3(AllocateMemory, - Status(size_t Size, uint32_t Permissions, addr_t &Addr)); - MOCK_METHOD1(DeallocateMemory, Status(addr_t Addr)); - MOCK_METHOD0(GetSharedLibraryInfoAddress, addr_t()); - MOCK_METHOD0(UpdateThreads, size_t()); - MOCK_CONST_METHOD0(GetAuxvData, - llvm::ErrorOr>()); - MOCK_METHOD2(GetLoadedModuleFileSpec, - Status(const char *ModulePath, FileSpec &Spec)); - MOCK_METHOD2(GetFileLoadAddress, - Status(const llvm::StringRef &FileName, addr_t &Addr)); - - const ArchSpec &GetArchitecture() const /*override*/ { return Arch; } - Status SetBreakpoint(lldb::addr_t Addr, uint32_t Size, - bool Hardware) /*override*/ { - if (Hardware) - return SetHardwareBreakpoint(Addr, Size); - else - return SetSoftwareBreakpoint(Addr, Size); - } - - // Redirect base class Read/Write Memory methods to functions whose signatures - // are more mock-friendly. - Status ReadMemory(addr_t Addr, void *Buf, size_t Size, - size_t &BytesRead) /*override*/; - Status WriteMemory(addr_t Addr, const void *Buf, size_t Size, - size_t &BytesWritten) /*override*/; - - MOCK_METHOD2(ReadMemory, - llvm::Expected>(addr_t Addr, size_t Size)); - MOCK_METHOD2(WriteMemory, - llvm::Expected(addr_t Addr, - llvm::ArrayRef Data)); - - using NativeProcessProtocol::GetSoftwareBreakpointTrapOpcode; - llvm::Expected> ReadMemoryWithoutTrap(addr_t Addr, - size_t Size); - -private: - ArchSpec Arch; -}; - -class FakeMemory { -public: - FakeMemory(llvm::ArrayRef Data) : Data(Data) {} - llvm::Expected> Read(addr_t Addr, size_t Size); - llvm::Expected Write(addr_t Addr, llvm::ArrayRef Chunk); - -private: - std::vector Data; -}; -} // namespace - -Status MockProcess::ReadMemory(addr_t Addr, void *Buf, size_t Size, - size_t &BytesRead) { - auto ExpectedMemory = ReadMemory(Addr, Size); - if (!ExpectedMemory) { - BytesRead = 0; - return Status(ExpectedMemory.takeError()); - } - BytesRead = ExpectedMemory->size(); - assert(BytesRead <= Size); - std::memcpy(Buf, ExpectedMemory->data(), BytesRead); - return Status(); -} - -Status MockProcess::WriteMemory(addr_t Addr, const void *Buf, size_t Size, - size_t &BytesWritten) { - auto ExpectedBytes = WriteMemory( - Addr, llvm::makeArrayRef(static_cast(Buf), Size)); - if (!ExpectedBytes) { - BytesWritten = 0; - return Status(ExpectedBytes.takeError()); - } - BytesWritten = *ExpectedBytes; - return Status(); -} - -llvm::Expected> -MockProcess::ReadMemoryWithoutTrap(addr_t Addr, size_t Size) { - std::vector Data(Size, 0); - size_t BytesRead; - Status ST = NativeProcessProtocol::ReadMemoryWithoutTrap( - Addr, Data.data(), Data.size(), BytesRead); - if (ST.Fail()) - return ST.ToError(); - Data.resize(BytesRead); - return std::move(Data); -} - -llvm::Expected> FakeMemory::Read(addr_t Addr, - size_t Size) { - if (Addr >= Data.size()) - return llvm::createStringError(llvm::inconvertibleErrorCode(), - "Address out of range."); - Size = std::min(Size, Data.size() - (size_t)Addr); - auto Begin = std::next(Data.begin(), Addr); - return std::vector(Begin, std::next(Begin, Size)); -} - -llvm::Expected FakeMemory::Write(addr_t Addr, - llvm::ArrayRef Chunk) { - if (Addr >= Data.size()) - return llvm::createStringError(llvm::inconvertibleErrorCode(), - "Address out of range."); - size_t Size = std::min(Chunk.size(), Data.size() - (size_t)Addr); - std::copy_n(Chunk.begin(), Size, &Data[Addr]); - return Size; -} - TEST(NativeProcessProtocolTest, SetBreakpoint) { NiceMock DummyDelegate; - MockProcess Process(DummyDelegate, ArchSpec("x86_64-pc-linux")); + MockProcess Process(DummyDelegate, + ArchSpec("x86_64-pc-linux")); auto Trap = cantFail(Process.GetSoftwareBreakpointTrapOpcode(1)); InSequence S; EXPECT_CALL(Process, ReadMemory(0x47, 1)) @@ -162,7 +32,8 @@ TEST(NativeProcessProtocolTest, SetBreakpointFailRead) { NiceMock DummyDelegate; - MockProcess Process(DummyDelegate, ArchSpec("x86_64-pc-linux")); + MockProcess Process(DummyDelegate, + ArchSpec("x86_64-pc-linux")); EXPECT_CALL(Process, ReadMemory(0x47, 1)) .WillOnce(Return(ByMove( llvm::createStringError(llvm::inconvertibleErrorCode(), "Foo")))); @@ -172,7 +43,8 @@ TEST(NativeProcessProtocolTest, SetBreakpointFailWrite) { NiceMock DummyDelegate; - MockProcess Process(DummyDelegate, ArchSpec("x86_64-pc-linux")); + MockProcess Process(DummyDelegate, + ArchSpec("x86_64-pc-linux")); auto Trap = cantFail(Process.GetSoftwareBreakpointTrapOpcode(1)); InSequence S; EXPECT_CALL(Process, ReadMemory(0x47, 1)) @@ -186,7 +58,8 @@ TEST(NativeProcessProtocolTest, SetBreakpointFailVerify) { NiceMock DummyDelegate; - MockProcess Process(DummyDelegate, ArchSpec("x86_64-pc-linux")); + MockProcess Process(DummyDelegate, + ArchSpec("x86_64-pc-linux")); auto Trap = cantFail(Process.GetSoftwareBreakpointTrapOpcode(1)); InSequence S; EXPECT_CALL(Process, ReadMemory(0x47, 1)) @@ -201,7 +74,8 @@ TEST(NativeProcessProtocolTest, ReadMemoryWithoutTrap) { NiceMock DummyDelegate; - MockProcess Process(DummyDelegate, ArchSpec("aarch64-pc-linux")); + MockProcess Process(DummyDelegate, + ArchSpec("aarch64-pc-linux")); FakeMemory M{{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}}; EXPECT_CALL(Process, ReadMemory(_, _)) .WillRepeatedly(Invoke(&M, &FakeMemory::Read)); diff --git a/lldb/unittests/Host/NativeProcessTestUtils.h b/lldb/unittests/Host/NativeProcessTestUtils.h new file mode 100644 --- /dev/null +++ b/lldb/unittests/Host/NativeProcessTestUtils.h @@ -0,0 +1,146 @@ +//===-- NativeProcessTestUtils.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 +// +//===----------------------------------------------------------------------===// + +#ifndef lldb_unittests_Host_NativeProcessTestUtils_h_ +#define lldb_unittests_Host_NativeProcessTestUtils_h_ + +#include "lldb/Host/common/NativeProcessProtocol.h" +#include "llvm/Testing/Support/Error.h" +#include "gmock/gmock.h" + +using namespace lldb_private; +using namespace lldb; +using namespace testing; + +namespace lldb_private { + +class MockDelegate : public NativeProcessProtocol::NativeDelegate { +public: + MOCK_METHOD1(InitializeDelegate, void(NativeProcessProtocol *Process)); + MOCK_METHOD2(ProcessStateChanged, + void(NativeProcessProtocol *Process, StateType State)); + MOCK_METHOD1(DidExec, void(NativeProcessProtocol *Process)); +}; + +// NB: This class doesn't use the override keyword to avoid +// -Winconsistent-missing-override warnings from the compiler. The +// inconsistency comes from the overriding definitions in the MOCK_*** macros. +template class MockProcess : public T { +public: + MockProcess(NativeProcessProtocol::NativeDelegate &Delegate, + const ArchSpec &Arch, lldb::pid_t Pid = 1) + : T(Pid, -1, Delegate), Arch(Arch) {} + + MOCK_METHOD1(Resume, Status(const ResumeActionList &ResumeActions)); + MOCK_METHOD0(Halt, Status()); + MOCK_METHOD0(Detach, Status()); + MOCK_METHOD1(Signal, Status(int Signo)); + MOCK_METHOD0(Kill, Status()); + MOCK_METHOD3(AllocateMemory, + Status(size_t Size, uint32_t Permissions, addr_t &Addr)); + MOCK_METHOD1(DeallocateMemory, Status(addr_t Addr)); + MOCK_METHOD0(GetSharedLibraryInfoAddress, addr_t()); + MOCK_METHOD0(UpdateThreads, size_t()); + MOCK_CONST_METHOD0(GetAuxvData, + llvm::ErrorOr>()); + MOCK_METHOD2(GetLoadedModuleFileSpec, + Status(const char *ModulePath, FileSpec &Spec)); + MOCK_METHOD2(GetFileLoadAddress, + Status(const llvm::StringRef &FileName, addr_t &Addr)); + + const ArchSpec &GetArchitecture() const /*override*/ { return Arch; } + Status SetBreakpoint(lldb::addr_t Addr, uint32_t Size, + bool Hardware) /*override*/ { + if (Hardware) + return this->SetHardwareBreakpoint(Addr, Size); + else + return this->SetSoftwareBreakpoint(Addr, Size); + } + + // Redirect base class Read/Write Memory methods to functions whose signatures + // are more mock-friendly. + Status ReadMemory(addr_t Addr, void *Buf, size_t Size, + size_t &BytesRead) /*override*/ { + auto ExpectedMemory = this->ReadMemory(Addr, Size); + if (!ExpectedMemory) { + BytesRead = 0; + return Status(ExpectedMemory.takeError()); + } + BytesRead = ExpectedMemory->size(); + assert(BytesRead <= Size); + std::memcpy(Buf, ExpectedMemory->data(), BytesRead); + return Status(); + } + + Status WriteMemory(addr_t Addr, const void *Buf, size_t Size, + size_t &BytesWritten) /*override*/ { + auto ExpectedBytes = this->WriteMemory( + Addr, llvm::makeArrayRef(static_cast(Buf), Size)); + if (!ExpectedBytes) { + BytesWritten = 0; + return Status(ExpectedBytes.takeError()); + } + BytesWritten = *ExpectedBytes; + return Status(); + } + + MOCK_METHOD2(ReadMemory, + llvm::Expected>(addr_t Addr, size_t Size)); + MOCK_METHOD2(WriteMemory, + llvm::Expected(addr_t Addr, + llvm::ArrayRef Data)); + + using T::GetSoftwareBreakpointTrapOpcode; + llvm::Expected> ReadMemoryWithoutTrap(addr_t Addr, + size_t Size) { + std::vector Data(Size, 0); + size_t BytesRead; + Status ST = + T::ReadMemoryWithoutTrap(Addr, Data.data(), Data.size(), BytesRead); + if (ST.Fail()) + return ST.ToError(); + Data.resize(BytesRead); + return std::move(Data); + } + +private: + ArchSpec Arch; +}; + +class FakeMemory { +public: + FakeMemory(llvm::ArrayRef Data, addr_t start_addr = 0) + : Data(Data), m_start_addr(start_addr) {} + + llvm::Expected> Read(addr_t Addr, size_t Size) { + Addr -= m_start_addr; + if (Addr >= Data.size()) + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "Address out of range."); + Size = std::min(Size, Data.size() - (size_t)Addr); + auto Begin = std::next(Data.begin(), Addr); + return std::vector(Begin, std::next(Begin, Size)); + } + + llvm::Expected Write(addr_t Addr, llvm::ArrayRef Chunk) { + Addr -= m_start_addr; + if (Addr >= Data.size()) + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "Address out of range."); + size_t Size = std::min(Chunk.size(), Data.size() - (size_t)Addr); + std::copy_n(Chunk.begin(), Size, &Data[Addr]); + return Size; + } + +private: + std::vector Data; + addr_t m_start_addr; +}; +} // namespace lldb_private + +#endif \ No newline at end of file diff --git a/lldb/unittests/Process/CMakeLists.txt b/lldb/unittests/Process/CMakeLists.txt --- a/lldb/unittests/Process/CMakeLists.txt +++ b/lldb/unittests/Process/CMakeLists.txt @@ -1,5 +1,6 @@ add_subdirectory(gdb-remote) if (CMAKE_SYSTEM_NAME MATCHES "Linux|Android") add_subdirectory(Linux) + add_subdirectory(POSIX) endif() add_subdirectory(minidump) diff --git a/lldb/unittests/Process/POSIX/CMakeLists.txt b/lldb/unittests/Process/POSIX/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/lldb/unittests/Process/POSIX/CMakeLists.txt @@ -0,0 +1,8 @@ +include_directories(${LLDB_SOURCE_DIR}/source/Plugins/Process/POSIX) + +add_lldb_unittest(ProcessPOSIXTest + NativeProcessELFTest.cpp + + LINK_LIBS + lldbPluginProcessPOSIX + ) diff --git a/lldb/unittests/Process/POSIX/NativeProcessELFTest.cpp b/lldb/unittests/Process/POSIX/NativeProcessELFTest.cpp new file mode 100644 --- /dev/null +++ b/lldb/unittests/Process/POSIX/NativeProcessELFTest.cpp @@ -0,0 +1,133 @@ +//===-- NativeProcessELFTest.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 "../../Host/NativeProcessTestUtils.h" + +#include "Plugins/Process/POSIX/NativeProcessELF.h" +#include "Plugins/Process/Utility/AuxVector.h" +#include "lldb/Utility/DataBufferHeap.h" +#include "lldb/Utility/DataEncoder.h" +#include "lldb/Utility/DataExtractor.h" +#include "llvm/BinaryFormat/ELF.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Testing/Support/Error.h" +#include "gmock/gmock.h" + +using namespace lldb_private; +using namespace lldb; +using namespace testing; + +namespace { +class MockProcessELF : public MockProcess { +public: + using MockProcess::MockProcess; + using NativeProcessELF::GetAuxValue; + using NativeProcessELF::GetELFImageInfoAddress; +}; + +std::unique_ptr CreateAuxvData( + MockProcessELF &process, + std::vector> auxv_data) { + auto addr_size = process.GetAddressByteSize(); + DataBufferSP buffer_sp( + new DataBufferHeap(auxv_data.size() * addr_size * 2, 0)); + DataEncoder encoder(buffer_sp, process.GetByteOrder(), addr_size); + uint32_t offset = 0; + for (auto &pair : auxv_data) { + offset = encoder.PutAddress(offset, pair.first); + offset = encoder.PutAddress(offset, pair.second); + } + llvm::StringRef stringref((const char *)buffer_sp->GetBytes(), + buffer_sp->GetByteSize()); + return llvm::MemoryBuffer::getMemBufferCopy(stringref, ""); +} +} // namespace + +TEST(NativeProcessELFTest, GetAuxValue) { + NiceMock DummyDelegate; + MockProcessELF process(DummyDelegate, ArchSpec("i386-pc-linux")); + + uint32_t phdr_addr = 0x42; + auto auxv_buffer = CreateAuxvData( + process, {std::make_pair(AuxVector::AUXV_AT_PHDR, phdr_addr)}); + EXPECT_CALL(process, GetAuxvData()) + .WillOnce(Return(ByMove(std::move(auxv_buffer)))); + + llvm::Optional maybe_phdr_addr = + process.GetAuxValue(AuxVector::AUXV_AT_PHDR); + ASSERT_NE(llvm::None, maybe_phdr_addr); + ASSERT_EQ(phdr_addr, *maybe_phdr_addr); +} + +TEST(NativeProcessELFTest, GetELFImageInfoAddress) { + NiceMock DummyDelegate; + MockProcessELF process(DummyDelegate, ArchSpec("i386-pc-linux")); + + uint32_t load_base = 0x1000; + uint32_t info_addr = 0x3741; + uint32_t phdr_addr = load_base + sizeof(llvm::ELF::Elf32_Ehdr); + + auto auxv_buffer = CreateAuxvData( + process, + {std::make_pair(AuxVector::AUXV_AT_PHDR, phdr_addr), + std::make_pair(AuxVector::AUXV_AT_PHENT, sizeof(llvm::ELF::Elf32_Phdr)), + std::make_pair(AuxVector::AUXV_AT_PHNUM, 2)}); + EXPECT_CALL(process, GetAuxvData()) + .WillOnce(Return(ByMove(std::move(auxv_buffer)))); + + // We're going to set up a fake memory with 2 program headers and 1 entry in + // the dynamic section. + // For simplicity sake they will be consecutive in memory: + // +------------+ + // | PT_PHDR | + // +------------+ + // | PT_DYNAMIC | + // +------------+ + // | DT_DEBUG | + // +------------+ + uint8_t mem_data[2 * sizeof(llvm::ELF::Elf32_Phdr) + + sizeof(llvm::ELF::Elf32_Dyn)]; + DataEncoder mem_encoder((void *)&mem_data[0], sizeof(mem_data), + process.GetByteOrder(), process.GetAddressByteSize()); + uint32_t offset = 0; + + llvm::ELF::Elf32_Phdr phdr_load; + llvm::ELF::Elf32_Phdr phdr_dynamic; + llvm::ELF::Elf32_Dyn dyn_debug; + + // Setup the 2 program header entries + phdr_load.p_type = llvm::ELF::PT_PHDR; + phdr_load.p_vaddr = phdr_addr - load_base; + offset = mem_encoder.PutData(offset, &phdr_load, sizeof(phdr_load)); + + phdr_dynamic.p_type = llvm::ELF::PT_DYNAMIC; + phdr_dynamic.p_vaddr = + (phdr_addr + 2 * sizeof(llvm::ELF::Elf32_Phdr)) - load_base; + phdr_dynamic.p_memsz = sizeof(llvm::ELF::Elf32_Dyn); + offset = mem_encoder.PutData(offset, &phdr_dynamic, sizeof(phdr_dynamic)); + + // Setup the single entry in the .dynamic section + dyn_debug.d_tag = llvm::ELF::DT_DEBUG; + dyn_debug.d_un.d_ptr = info_addr; + mem_encoder.PutData(offset, &dyn_debug, sizeof(dyn_debug)); + + FakeMemory M(mem_data, phdr_addr); + EXPECT_CALL(process, ReadMemory(_, _)) + .WillRepeatedly(Invoke(&M, &FakeMemory::Read)); + + lldb::addr_t elf_info_addr = process.GetELFImageInfoAddress< + llvm::ELF::Elf32_Ehdr, llvm::ELF::Elf32_Phdr, llvm::ELF::Elf32_Dyn>(); + + // Read the address at the elf_info_addr location to make sure we're reading + // the correct one. + DataExtractor mem_extractor( + mem_encoder.GetDataStart(), mem_encoder.GetByteSize(), + mem_encoder.GetByteOrder(), mem_encoder.GetAddressByteSize()); + lldb::offset_t info_addr_offset = elf_info_addr - phdr_addr; + ASSERT_EQ(mem_extractor.GetAddress(&info_addr_offset), info_addr); +}