diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h @@ -31,6 +31,22 @@ namespace lldb_private { namespace process_gdb_remote { +/// The offsets used by the target when relocating the executable. Decoded from +/// qOffsets packet response. +struct QOffsets { + /// If true, the offsets field describes segments. Otherwise, it describes + /// sections. + bool segments; + + /// The individual offsets. Section offsets have two or three members. + /// Segment offsets have either one of two. + std::vector offsets; +}; +inline bool operator==(const QOffsets &a, const QOffsets &b) { + return a.segments == b.segments && a.offsets == b.offsets; +} +llvm::raw_ostream &operator<<(llvm::raw_ostream &os, const QOffsets &offsets); + class GDBRemoteCommunicationClient : public GDBRemoteClientBase { public: GDBRemoteCommunicationClient(); @@ -425,6 +441,11 @@ bool GetSharedCacheInfoSupported(); + /// Use qOffsets to query the offset used when relocating the target + /// executable. If successful, the returned structure will contain at least + /// one value in the offsets field. + llvm::Optional GetQOffsets(); + bool GetModuleInfo(const FileSpec &module_file_spec, const ArchSpec &arch_spec, ModuleSpec &module_spec); diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp @@ -45,6 +45,13 @@ using namespace lldb_private; using namespace std::chrono; +llvm::raw_ostream &process_gdb_remote::operator<<(llvm::raw_ostream &os, + const QOffsets &offsets) { + return os << llvm::formatv( + "QOffsets({0}, [{1:@[x]}])", offsets.segments, + llvm::make_range(offsets.offsets.begin(), offsets.offsets.end())); +} + // GDBRemoteCommunicationClient constructor GDBRemoteCommunicationClient::GDBRemoteCommunicationClient() : GDBRemoteClientBase("gdb-remote.client", "gdb-remote.client.rx_packet"), @@ -3531,6 +3538,46 @@ return error; } +llvm::Optional GDBRemoteCommunicationClient::GetQOffsets() { + StringExtractorGDBRemote response; + if (SendPacketAndWaitForResponse( + "qOffsets", response, /*send_async=*/false) != PacketResult::Success) + return llvm::None; + if (!response.IsNormalResponse()) + return llvm::None; + + QOffsets result; + llvm::StringRef ref = response.GetStringRef(); + const auto &GetOffset = [&] { + addr_t offset; + if (ref.consumeInteger(16, offset)) + return false; + result.offsets.push_back(offset); + return true; + }; + + if (ref.consume_front("Text=")) { + result.segments = false; + if (!GetOffset()) + return llvm::None; + if (!ref.consume_front(";Data=") || !GetOffset()) + return llvm::None; + if (ref.empty()) + return result; + if (ref.consume_front(";Bss=") && GetOffset() && ref.empty()) + return result; + } else if (ref.consume_front("TextSeg=")) { + result.segments = true; + if (!GetOffset()) + return llvm::None; + if (ref.empty()) + return result; + if (ref.consume_front(";DataSeg=") && GetOffset() && ref.empty()) + return result; + } + return llvm::None; +} + bool GDBRemoteCommunicationClient::GetModuleInfo( const FileSpec &module_file_spec, const lldb_private::ArchSpec &arch_spec, ModuleSpec &module_spec) { diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h --- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h +++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h @@ -377,6 +377,7 @@ bool UpdateThreadIDList(); void DidLaunchOrAttach(ArchSpec &process_arch); + void MaybeLoadExecutableModule(); Status ConnectToDebugserver(llvm::StringRef host_port); diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp --- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp @@ -1102,6 +1102,8 @@ } } + MaybeLoadExecutableModule(); + // Find out which StructuredDataPlugins are supported by the debug monitor. // These plugins transmit data over async $J packets. if (StructuredData::Array *supported_packets = @@ -1109,6 +1111,31 @@ MapSupportedStructuredDataPlugins(*supported_packets); } +void ProcessGDBRemote::MaybeLoadExecutableModule() { + ModuleSP module_sp = GetTarget().GetExecutableModule(); + if (!module_sp) + return; + + llvm::Optional offsets = m_gdb_comm.GetQOffsets(); + if (!offsets) + return; + + bool is_uniform = + size_t(llvm::count(offsets->offsets, offsets->offsets[0])) == + offsets->offsets.size(); + if (!is_uniform) + return; // TODO: Handle non-uniform responses. + + bool changed = false; + module_sp->SetLoadAddress(GetTarget(), offsets->offsets[0], + /*value_is_offset=*/true, changed); + if (changed) { + ModuleList list; + list.Append(module_sp); + m_process->GetTarget().ModulesDidLoad(list); + } +} + void ProcessGDBRemote::DidLaunch() { ArchSpec process_arch; DidLaunchOrAttach(process_arch); diff --git a/lldb/test/API/functionalities/gdb_remote_client/TestqOffsets.py b/lldb/test/API/functionalities/gdb_remote_client/TestqOffsets.py new file mode 100644 --- /dev/null +++ b/lldb/test/API/functionalities/gdb_remote_client/TestqOffsets.py @@ -0,0 +1,28 @@ +import lldb +from lldbsuite.test.lldbtest import * +from lldbsuite.test.decorators import * +from gdbclientutils import * + + +class TestqOffsets(GDBRemoteTestBase): + + class Responder(MockGDBServerResponder): + def qOffsets(self): + return 'Text=470000;Data=470000' + + def setUp(self): + super(TestqOffsets, self).setUp() + self._initial_platform = lldb.DBG.GetSelectedPlatform() + + def tearDown(self): + lldb.DBG.SetSelectedPlatform(self._initial_platform) + super(TestqOffsets, self).tearDown() + + def test(self): + self.server.responder = TestqOffsets.Responder() + target = self.createTarget("qOffsets.yaml") + text = target.modules[0].FindSection(".text") + self.assertEquals(text.GetLoadAddress(target), lldb.LLDB_INVALID_ADDRESS) + + process = self.connect(target) + self.assertEquals(text.GetLoadAddress(target), 0x471000) diff --git a/lldb/test/API/functionalities/gdb_remote_client/gdbclientutils.py b/lldb/test/API/functionalities/gdb_remote_client/gdbclientutils.py --- a/lldb/test/API/functionalities/gdb_remote_client/gdbclientutils.py +++ b/lldb/test/API/functionalities/gdb_remote_client/gdbclientutils.py @@ -172,6 +172,8 @@ return self.qHostInfo() if packet == "qGetWorkingDir": return self.qGetWorkingDir() + if packet == "qOffsets": + return self.qOffsets(); if packet == "qsProcessInfo": return self.qsProcessInfo() if packet.startswith("qfProcessInfo"): @@ -188,6 +190,9 @@ def qGetWorkingDir(self): return "2f" + def qOffsets(self): + return "" + def qHostInfo(self): return "ptrsize:8;endian:little;" diff --git a/lldb/test/API/functionalities/gdb_remote_client/qOffsets.yaml b/lldb/test/API/functionalities/gdb_remote_client/qOffsets.yaml new file mode 100644 --- /dev/null +++ b/lldb/test/API/functionalities/gdb_remote_client/qOffsets.yaml @@ -0,0 +1,19 @@ +!ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_EXEC + Machine: EM_AARCH64 +Sections: + - Name: .text + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + Address: 0x1000 + AddressAlign: 0x4 + Content: "c3c3c3c3" + - Name: .note.ABI-tag + Type: SHT_NOTE + Flags: [ SHF_ALLOC ] + Address: 0x1004 + AddressAlign: 0x4 + Content: 040000001000000001000000474e550000000000030000000700000000000000 diff --git a/lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp b/lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp --- a/lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp +++ b/lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp @@ -552,3 +552,29 @@ incorrect_custom_params2); ASSERT_FALSE(result4.get().Success()); } + +TEST_F(GDBRemoteCommunicationClientTest, GetQOffsets) { + const auto &GetQOffsets = [&](llvm::StringRef response) { + std::future> result = std::async( + std::launch::async, [&] { return client.GetQOffsets(); }); + + HandlePacket(server, "qOffsets", response); + return result.get(); + }; + EXPECT_EQ((QOffsets{false, {0x1234, 0x1234}}), + GetQOffsets("Text=1234;Data=1234")); + EXPECT_EQ((QOffsets{false, {0x1234, 0x1234, 0x1234}}), + GetQOffsets("Text=1234;Data=1234;Bss=1234")); + EXPECT_EQ((QOffsets{true, {0x1234}}), GetQOffsets("TextSeg=1234")); + EXPECT_EQ((QOffsets{true, {0x1234, 0x2345}}), + GetQOffsets("TextSeg=1234;DataSeg=2345")); + + EXPECT_EQ(llvm::None, GetQOffsets("E05")); + EXPECT_EQ(llvm::None, GetQOffsets("Text=bogus")); + EXPECT_EQ(llvm::None, GetQOffsets("Text=1234")); + EXPECT_EQ(llvm::None, GetQOffsets("Text=1234;Data=1234;")); + EXPECT_EQ(llvm::None, GetQOffsets("Text=1234;Data=1234;Bss=1234;")); + EXPECT_EQ(llvm::None, GetQOffsets("TEXTSEG=1234")); + EXPECT_EQ(llvm::None, GetQOffsets("TextSeg=0x1234")); + EXPECT_EQ(llvm::None, GetQOffsets("TextSeg=12345678123456789")); +}