diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunication.h b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunication.h --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunication.h +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunication.h @@ -51,6 +51,26 @@ LZMA, // Lempel–Ziv–Markov chain algorithm }; +// Data included in the vFile:fstat packet. +// https://sourceware.org/gdb/onlinedocs/gdb/struct-stat.html#struct-stat +struct GDBRemoteFStatData { + llvm::support::ubig32_t gdb_st_dev; + llvm::support::ubig32_t gdb_st_ino; + llvm::support::ubig32_t gdb_st_mode; + llvm::support::ubig32_t gdb_st_nlink; + llvm::support::ubig32_t gdb_st_uid; + llvm::support::ubig32_t gdb_st_gid; + llvm::support::ubig32_t gdb_st_rdev; + llvm::support::ubig64_t gdb_st_size; + llvm::support::ubig64_t gdb_st_blksize; + llvm::support::ubig64_t gdb_st_blocks; + llvm::support::ubig32_t gdb_st_atime; + llvm::support::ubig32_t gdb_st_mtime; + llvm::support::ubig32_t gdb_st_ctime; +}; +static_assert(sizeof(GDBRemoteFStatData) == 64, + "size of GDBRemoteFStatData is not 64"); + class ProcessGDBRemote; class GDBRemoteCommunication : public Communication { 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 @@ -376,6 +376,12 @@ bool CloseFile(lldb::user_id_t fd, Status &error); + llvm::Optional FStat(lldb::user_id_t fd); + + // NB: this is just a convenience wrapper over open() + fstat(). It does not + // work if the file cannot be opened. + llvm::Optional Stat(const FileSpec &file_spec); + lldb::user_id_t GetFileSize(const FileSpec &file_spec); void AutoCompleteDiskFileOrDirectory(CompletionRequest &request, @@ -581,7 +587,7 @@ m_supports_QEnvironment : 1, m_supports_QEnvironmentHexEncoded : 1, m_supports_qSymbol : 1, m_qSymbol_requests_done : 1, m_supports_qModuleInfo : 1, m_supports_jThreadsInfo : 1, - m_supports_jModulesInfo : 1; + m_supports_jModulesInfo : 1, m_supports_vFileSize : 1; /// Current gdb remote protocol process identifier for all other operations lldb::pid_t m_curr_pid = LLDB_INVALID_PROCESS_ID; 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 @@ -65,6 +65,7 @@ m_supports_QEnvironmentHexEncoded(true), m_supports_qSymbol(true), m_qSymbol_requests_done(false), m_supports_qModuleInfo(true), m_supports_jThreadsInfo(true), m_supports_jModulesInfo(true), + m_supports_vFileSize(true), m_host_arch(), m_process_arch(), m_os_build(), m_os_kernel(), m_hostname(), m_gdb_server_name(), m_default_packet_timeout(0), @@ -3068,22 +3069,66 @@ return false; } -// Extension of host I/O packets to get the file size. -lldb::user_id_t GDBRemoteCommunicationClient::GetFileSize( - const lldb_private::FileSpec &file_spec) { - std::string path(file_spec.GetPath(false)); +llvm::Optional +GDBRemoteCommunicationClient::FStat(lldb::user_id_t fd) { lldb_private::StreamString stream; - stream.PutCString("vFile:size:"); - stream.PutStringAsRawHex8(path); + stream.Printf("vFile:fstat:%" PRIx64, fd); StringExtractorGDBRemote response; if (SendPacketAndWaitForResponse(stream.GetString(), response) == PacketResult::Success) { if (response.GetChar() != 'F') + return llvm::None; + int64_t size = response.GetS64(-1, 16); + if (size > 0 && response.GetChar() == ';') { + std::string buffer; + if (response.GetEscapedBinaryData(buffer)) { + GDBRemoteFStatData out; + if (buffer.size() != sizeof(out)) + return llvm::None; + memcpy(&out, buffer.data(), sizeof(out)); + return out; + } + } + } + return llvm::None; +} + +llvm::Optional +GDBRemoteCommunicationClient::Stat(const lldb_private::FileSpec &file_spec) { + Status error; + lldb::user_id_t fd = OpenFile(file_spec, File::eOpenOptionReadOnly, 0, error); + if (fd == UINT64_MAX) + return llvm::None; + llvm::Optional st = FStat(fd); + CloseFile(fd, error); + return st; +} + +// Extension of host I/O packets to get the file size. +lldb::user_id_t GDBRemoteCommunicationClient::GetFileSize( + const lldb_private::FileSpec &file_spec) { + if (m_supports_vFileSize) { + std::string path(file_spec.GetPath(false)); + lldb_private::StreamString stream; + stream.PutCString("vFile:size:"); + stream.PutStringAsRawHex8(path); + StringExtractorGDBRemote response; + if (SendPacketAndWaitForResponse(stream.GetString(), response) != + PacketResult::Success) return UINT64_MAX; - uint32_t retcode = response.GetHexMaxU64(false, UINT64_MAX); - return retcode; + + if (!response.IsUnsupportedResponse()) { + if (response.GetChar() != 'F') + return UINT64_MAX; + uint32_t retcode = response.GetHexMaxU64(false, UINT64_MAX); + return retcode; + } + m_supports_vFileSize = false; } - return UINT64_MAX; + + // Fallback to fstat. + llvm::Optional st = Stat(file_spec); + return st ? st->gdb_st_size : UINT64_MAX; } void GDBRemoteCommunicationClient::AutoCompleteDiskFileOrDirectory( diff --git a/lldb/test/API/functionalities/gdb_remote_client/TestGDBRemotePlatformFile.py b/lldb/test/API/functionalities/gdb_remote_client/TestGDBRemotePlatformFile.py --- a/lldb/test/API/functionalities/gdb_remote_client/TestGDBRemotePlatformFile.py +++ b/lldb/test/API/functionalities/gdb_remote_client/TestGDBRemotePlatformFile.py @@ -77,3 +77,58 @@ ]) finally: self.dbg.GetSelectedPlatform().DisconnectRemote() + + def test_file_size(self): + """Test 'platform get-size'""" + + class Responder(MockGDBServerResponder): + def vFile(self, packet): + return "F1000" + + self.server.responder = Responder() + + try: + self.runCmd("platform select remote-gdb-server") + self.runCmd("platform connect connect://" + + self.server.get_connect_address()) + self.assertTrue(self.dbg.GetSelectedPlatform().IsConnected()) + + self.match("platform get-size /some/file.txt", + [r"File size of /some/file\.txt \(remote\): 4096"]) + self.assertPacketLogContains([ + "vFile:size:2f736f6d652f66696c652e747874", + ]) + finally: + self.dbg.GetSelectedPlatform().DisconnectRemote() + + def test_file_size_fallback(self): + """Test 'platform get-size fallback to vFile:fstat'""" + + class Responder(MockGDBServerResponder): + def vFile(self, packet): + if packet.startswith("vFile:open:"): + return "F5" + elif packet.startswith("vFile:fstat:"): + return "F40;" + 28 * "\0" + "\0\0\0\0\0\1\2\3" + 28 * "\0" + if packet.startswith("vFile:close:"): + return "F0" + return "" + + self.server.responder = Responder() + + try: + self.runCmd("platform select remote-gdb-server") + self.runCmd("platform connect connect://" + + self.server.get_connect_address()) + self.assertTrue(self.dbg.GetSelectedPlatform().IsConnected()) + + self.match("platform get-size /some/file.txt", + [r"File size of /some/file\.txt \(remote\): 66051"]) + self.assertPacketLogContains([ + "vFile:size:2f736f6d652f66696c652e747874", + "vFile:open:2f736f6d652f66696c652e747874,00000000,00000000", + "vFile:fstat:5", + "vFile:close:5", + ]) + finally: + self.dbg.GetSelectedPlatform().DisconnectRemote()