diff --git a/lldb/include/lldb/Host/common/NativeProcessProtocol.h b/lldb/include/lldb/Host/common/NativeProcessProtocol.h --- a/lldb/include/lldb/Host/common/NativeProcessProtocol.h +++ b/lldb/include/lldb/Host/common/NativeProcessProtocol.h @@ -32,6 +32,15 @@ class MemoryRegionInfo; class ResumeActionList; +struct SharedLibraryInfo { + std::string name; + lldb::addr_t link_map; + lldb::addr_t base_addr; + lldb::addr_t ld_addr; + bool main; + lldb::addr_t next; +}; + // NativeProcessProtocol class NativeProcessProtocol { public: @@ -86,6 +95,11 @@ virtual lldb::addr_t GetSharedLibraryInfoAddress() = 0; + virtual Status + GetLoadedSharedLibraries(std::vector &library_list) { + return Status("Not implemented"); + } + virtual bool IsAlive() const; virtual size_t UpdateThreads() = 0; diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-server/TestGdbRemoteLibrariesSvr4Support.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-server/TestGdbRemoteLibrariesSvr4Support.py new file mode 100644 --- /dev/null +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-server/TestGdbRemoteLibrariesSvr4Support.py @@ -0,0 +1,179 @@ +import xml.etree.ElementTree as ET + +import gdbremote_testcase +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * + + +class TestGdbRemoteLibrariesSvr4Support(gdbremote_testcase.GdbRemoteTestCaseBase): + + mydir = TestBase.compute_mydir(__file__) + + FEATURE_NAME = "qXfer:libraries-svr4:read" + + def has_libraries_svr4_support(self): + inferior_args = ["message:main entered", "sleep:5"] + # inferior_args = [ "sleep:500"] + procs = self.prep_debug_monitor_and_inferior(inferior_args=inferior_args) + + # Don't do anything until we match the launched inferior main entry output. + # Then immediately interrupt the process. + # This prevents libraries-svr4 data being asked for before it's ready and leaves + # us in a stopped state. + self.test_sequence.add_log_lines( + [ + # Start the inferior... + "read packet: $c#63", + # ... match output.... + { + "type": "output_match", + "regex": self.maybe_strict_output_regex( + r"message:main entered\r\n" + ), + }, + ], + True, + ) + # ... then interrupt. + self.add_interrupt_packets() + self.add_qSupported_packets() + + context = self.expect_gdbremote_sequence() + self.assertIsNotNone(context) + features = self.parse_qSupported_response(context) + return self.FEATURE_NAME in features and features[self.FEATURE_NAME] == "+" + + def get_libraries_svr4_data(self): + # Start up llgs and inferior, and check for libraries-svr4 support. + if not self.has_libraries_svr4_support(): + self.skipTest("libraries-svr4 not supported") + + OFFSET = 0 + LENGTH = 0xFFFF + + # Grab the libraries-svr4 data. + self.reset_test_sequence() + self.test_sequence.add_log_lines( + [ + "read packet: $qXfer:libraries-svr4:read::{:x},{:x}:#00".format( + OFFSET, LENGTH + ), + { + "direction": "send", + "regex": re.compile( + r"^\$([^E])(.*)#[0-9a-fA-F]{2}$", re.MULTILINE | re.DOTALL + ), + "capture": {1: "response_type", 2: "content_raw"}, + }, + ], + True, + ) + + context = self.expect_gdbremote_sequence() + self.assertIsNotNone(context) + + # Ensure we end up with all libraries-svr4 data in one packet. + self.assertEqual(context.get("response_type"), "l") + + # Decode binary data. + content_raw = context.get("content_raw") + self.assertIsNotNone(content_raw) + return content_raw + + def get_libraries_svr4_xml(self): + libraries_svr4 = self.get_libraries_svr4_data() + xml_root = None + try: + xml_root = ET.fromstring(libraries_svr4) + except xml.etree.ElementTree.ParseError: + pass + self.assertIsNotNone(xml_root, "Malformed libraries-svr4 XML") + return xml_root + + def libraries_svr4_well_formed(self): + xml_root = self.get_libraries_svr4_xml() + self.assertEqual(xml_root.tag, "library-list-svr4") + for child in xml_root: + self.assertEqual(child.tag, "library") + self.assertItemsEqual(child.attrib.keys(), ["name", "lm", "l_addr", "l_ld"]) + + def libraries_svr4_has_correct_load_addr(self): + xml_root = self.get_libraries_svr4_xml() + for child in xml_root: + name = child.attrib.get("name") + if not name: + continue + load_addr = int(child.attrib.get("l_addr"), 16) + self.reset_test_sequence() + self.add_query_memory_region_packets(load_addr) + context = self.expect_gdbremote_sequence() + mem_region = self.parse_memory_region_packet(context) + self.assertEqual(load_addr, int(mem_region.get("start", 0), 16)) + self.assertEqual( + os.path.realpath(name), os.path.realpath(mem_region.get("name", "")) + ) + + def get_pid(self): + self.reset_test_sequence() + self.add_process_info_collection_packets() + context = self.expect_gdbremote_sequence() + self.assertIsNotNone(context) + process_info = self.parse_process_info_response(context) + return int(process_info.get("pid"), 16) + + def get_mapped_libs(self): + libs = [] + exe_name = self.getBuildArtifact("a.out") + with open("/proc/{pid}/maps".format(pid=self.get_pid()), "r") as file: + for line in file: + lib = line.split() + if len(lib) < 6: + continue + name = lib[5] + if exe_name == name or not os.path.exists(name) or name in libs: + continue + libs.append(name) + return libs + + def libraries_svr4_all_present(self): + xml_root = self.get_libraries_svr4_xml() + libraries_svr4_names = [] + for child in xml_root: + name = child.attrib.get("name") + if not name: + continue + libraries_svr4_names.append(os.path.realpath(name)) + libs = self.get_mapped_libs() + self.assertItemsEqual(libraries_svr4_names, libs) + + @llgs_test + @skipUnlessPlatform(["linux", "android"]) + def test_supports_libraries_svr4(self): + self.init_llgs_test() + self.build() + self.set_inferior_startup_launch() + self.assertTrue(self.has_libraries_svr4_support()) + + @llgs_test + @skipUnlessPlatform(["linux", "android"]) + def test_libraries_svr4_well_formed(self): + self.init_llgs_test() + self.build() + self.set_inferior_startup_launch() + self.libraries_svr4_well_formed() + + @llgs_test + @skipUnlessPlatform(["linux", "android"]) + def test_libraries_svr4_load_addr(self): + self.init_llgs_test() + self.build() + self.set_inferior_startup_launch() + self.libraries_svr4_has_correct_load_addr() + + @llgs_test + @skipUnlessPlatform(["linux", "android"]) + def test_libraries_svr4_all_present(self): + self.init_llgs_test() + self.build() + self.set_inferior_startup_launch() + self.libraries_svr4_all_present() diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-server/gdbremote_testcase.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-server/gdbremote_testcase.py --- a/lldb/packages/Python/lldbsuite/test/tools/lldb-server/gdbremote_testcase.py +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-server/gdbremote_testcase.py @@ -816,6 +816,7 @@ "error"]) self.assertIsNotNone(val) + mem_region_dict["name"] = seven.unhexlify(mem_region_dict.get("name", "")) # Return the dictionary of key-value pairs for the memory region. return mem_region_dict 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 @@ -79,6 +79,9 @@ lldb::addr_t GetSharedLibraryInfoAddress() override; + Status GetLoadedSharedLibraries( + std::vector &library_list) override; + size_t UpdateThreads() override; const ArchSpec &GetArchitecture() const override { return m_arch; } @@ -134,6 +137,17 @@ template lldb::addr_t GetELFImageInfoAddress(); + template struct ELFLinkMap { + T l_addr; + T l_name; + T l_ld; + T l_next; + T l_prev; + }; + template + Status ReadSharedLibraryInfo(lldb::addr_t link_map_addr, + SharedLibraryInfo &info); + private: MainLoop::SignalHandleUP m_sigchld_handle; ArchSpec m_arch; 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 @@ -2176,3 +2176,76 @@ return LLDB_INVALID_ADDRESS; } + +template +Status NativeProcessLinux::ReadSharedLibraryInfo(lldb::addr_t link_map_addr, + SharedLibraryInfo &info) { + ELFLinkMap link_map; + size_t bytes_read; + auto error = + ReadMemory(link_map_addr, &link_map, sizeof(link_map), bytes_read); + if (!error.Success()) + return error; + + char name_buffer[PATH_MAX]; + error = ReadMemory(link_map.l_name, &name_buffer, sizeof(name_buffer), + bytes_read); + if (!error.Success()) + return error; + + info.name = std::string(name_buffer); + info.link_map = link_map_addr; + info.base_addr = link_map.l_addr; + info.ld_addr = link_map.l_ld; + info.next = link_map.l_next; +#if defined(__linux__) && !defined(__ANDROID__) + // On non-android linux systems, main executable has an empty path. + info.main = info.name.empty(); +#elif defined(__linux__) && defined(__ANDROID__) + // On android, the main executable has a load address of 0. + info.main = info.ld_addr == 0; +#else + info.main = false; +#endif + + return Status(); +} + +Status NativeProcessLinux::GetLoadedSharedLibraries( + std::vector &library_list) { + // Address of DT_DEBUG.d_ptr which points to r_debug + lldb::addr_t info_address = GetSharedLibraryInfoAddress(); + if (info_address == LLDB_INVALID_ADDRESS) + return Status("Invalid shared library info address"); + // Address of r_debug + lldb::addr_t address = 0; + size_t bytes_read; + auto error = + ReadMemory(info_address, &address, GetAddressByteSize(), bytes_read); + if (!error.Success()) + return error; + if (address == 0) + return Status("Invalid r_debug address"); + // Read r_debug.r_map + lldb::addr_t link_map = 0; + error = ReadMemory(address + GetAddressByteSize(), &link_map, + GetAddressByteSize(), bytes_read); + if (!error.Success()) + return error; + if (address == 0) + return Status("Invalid link_map address"); + + while (link_map) { + SharedLibraryInfo info; + if (GetAddressByteSize() == 8) + error = ReadSharedLibraryInfo(link_map, info); + else + error = ReadSharedLibraryInfo(link_map, info); + if (!error.Success()) + return error; + library_list.push_back(info); + link_map = info.next; + } + + return Status(); +} diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerCommon.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerCommon.cpp --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerCommon.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerCommon.cpp @@ -826,6 +826,9 @@ response.PutCString(";QPassSignals+"); response.PutCString(";qXfer:auxv:read+"); #endif +#if defined(__linux__) + response.PutCString(";qXfer:libraries-svr4:read+"); +#endif return SendPacketNoLock(response.GetString()); } diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp @@ -2765,6 +2765,27 @@ return std::move(*buffer_or_error); } +#if defined(__linux__) + if (object == "libraries-svr4") { + std::vector library_list; + auto status = m_debugged_process_up->GetLoadedSharedLibraries(library_list); + if (!status.Success()) + return status.ToError(); + + StreamString response; + response.Printf(""); + for (auto const &library : library_list) { + response.Printf("", library.ld_addr); + } + response.Printf(""); + return std::move( + MemoryBuffer::getMemBufferCopy(response.GetString(), __FUNCTION__)); + } +#endif + return llvm::make_error( "Xfer object not supported"); }