Index: lldb/include/lldb/Utility/StringExtractorGDBRemote.h =================================================================== --- lldb/include/lldb/Utility/StringExtractorGDBRemote.h +++ lldb/include/lldb/Utility/StringExtractorGDBRemote.h @@ -88,6 +88,7 @@ eServerPacketType_vFile_mode, eServerPacketType_vFile_exists, eServerPacketType_vFile_md5, + eServerPacketType_vFile_fstat, eServerPacketType_vFile_stat, eServerPacketType_vFile_symlink, eServerPacketType_vFile_unlink, Index: lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerCommon.h =================================================================== --- lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerCommon.h +++ lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerCommon.h @@ -71,6 +71,8 @@ PacketResult Handle_vFile_unlink(StringExtractorGDBRemote &packet); + PacketResult Handle_vFile_FStat(StringExtractorGDBRemote &packet); + PacketResult Handle_vFile_Stat(StringExtractorGDBRemote &packet); PacketResult Handle_vFile_MD5(StringExtractorGDBRemote &packet); Index: lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerCommon.cpp =================================================================== --- lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerCommon.cpp +++ lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerCommon.cpp @@ -29,6 +29,7 @@ #include "lldb/Symbol/ObjectFile.h" #include "lldb/Target/Platform.h" #include "lldb/Utility/Endian.h" +#include "lldb/Utility/DataEncoder.h" #include "lldb/Utility/GDBRemote.h" #include "lldb/Utility/Log.h" #include "lldb/Utility/StreamString.h" @@ -156,6 +157,9 @@ RegisterMemberFunctionHandler( StringExtractorGDBRemote::eServerPacketType_vFile_size, &GDBRemoteCommunicationServerCommon::Handle_vFile_Size); + RegisterMemberFunctionHandler( + StringExtractorGDBRemote::eServerPacketType_vFile_fstat, + &GDBRemoteCommunicationServerCommon::Handle_vFile_FStat); RegisterMemberFunctionHandler( StringExtractorGDBRemote::eServerPacketType_vFile_stat, &GDBRemoteCommunicationServerCommon::Handle_vFile_Stat); @@ -755,6 +759,56 @@ return SendErrorResponse(24); } +GDBRemoteCommunication::PacketResult +GDBRemoteCommunicationServerCommon::Handle_vFile_FStat( + StringExtractorGDBRemote &packet) { + StreamGDBRemote response; + packet.SetFilePos(::strlen("vFile:fstat:")); + int fd = packet.GetS32(-1, 16); + + struct stat file_stats; + if (::fstat(fd, &file_stats) == -1) { + const int save_errno = errno; + response.Printf("F-1,%x", save_errno); + return SendPacketNoLock(response.GetString()); + } + + std::array data; + DataEncoder encoder{data.data(), data.size(), lldb::eByteOrderBig, + sizeof(void *)}; + uint32_t offset = 0; + + offset = encoder.PutUnsigned( + offset, 4, file_stats.st_dev <= UINT32_MAX ? file_stats.st_dev : 0); + offset = encoder.PutUnsigned( + offset, 4, file_stats.st_ino <= UINT32_MAX ? file_stats.st_ino : 0); + offset = + encoder.PutUnsigned(offset, 4, static_cast(file_stats.st_mode)); + offset = encoder.PutUnsigned( + offset, 4, + file_stats.st_nlink <= UINT32_MAX ? file_stats.st_nlink : UINT32_MAX); + offset = encoder.PutUnsigned( + offset, 4, file_stats.st_uid <= UINT32_MAX ? file_stats.st_uid : 0); + offset = encoder.PutUnsigned( + offset, 4, file_stats.st_gid <= UINT32_MAX ? file_stats.st_gid : 0); + offset = encoder.PutUnsigned( + offset, 4, file_stats.st_rdev <= UINT32_MAX ? file_stats.st_rdev : 0); + offset = encoder.PutUnsigned(offset, 8, file_stats.st_size); + offset = encoder.PutUnsigned(offset, 8, file_stats.st_blksize); + offset = encoder.PutUnsigned(offset, 8, file_stats.st_blocks); + offset = encoder.PutUnsigned( + offset, 4, file_stats.st_atime <= UINT32_MAX ? file_stats.st_atime : 0); + offset = encoder.PutUnsigned( + offset, 4, file_stats.st_mtime <= UINT32_MAX ? file_stats.st_mtime : 0); + offset = encoder.PutUnsigned( + offset, 4, file_stats.st_ctime <= UINT32_MAX ? file_stats.st_ctime : 0); + assert(offset == 64); + + response.Printf("F%zx;", data.size()); + response.PutEscapedBytes(data.data(), data.size()); + return SendPacketNoLock(response.GetString()); +} + GDBRemoteCommunication::PacketResult GDBRemoteCommunicationServerCommon::Handle_vFile_Stat( StringExtractorGDBRemote &packet) { Index: lldb/source/Utility/StringExtractorGDBRemote.cpp =================================================================== --- lldb/source/Utility/StringExtractorGDBRemote.cpp +++ lldb/source/Utility/StringExtractorGDBRemote.cpp @@ -337,6 +337,8 @@ return eServerPacketType_vFile_size; else if (PACKET_STARTS_WITH("vFile:exists")) return eServerPacketType_vFile_exists; + else if (PACKET_STARTS_WITH("vFile:fstat")) + return eServerPacketType_vFile_fstat; else if (PACKET_STARTS_WITH("vFile:stat")) return eServerPacketType_vFile_stat; else if (PACKET_STARTS_WITH("vFile:mode")) Index: lldb/test/API/tools/lldb-server/TestGdbRemotePlatformFile.py =================================================================== --- lldb/test/API/tools/lldb-server/TestGdbRemotePlatformFile.py +++ lldb/test/API/tools/lldb-server/TestGdbRemotePlatformFile.py @@ -9,8 +9,39 @@ from gdbremote_testcase import GdbRemoteTestCaseBase import binascii +import os import stat +import struct import tempfile +import typing + + +class GDBStat(typing.NamedTuple): + st_dev: int + st_ino: int + st_mode: int + st_nlink: int + st_uid: int + st_gid: int + st_rdev: int + st_size: int + st_blksize: int + st_blocks: int + st_atime: int + st_mtime: int + st_ctime: int + + +def uint32_or_zero(x): + return x if x < 2**32 else 0 + + +def uint32_or_max(x): + return x if x < 2**32 else 2**32 - 1 + + +def uint32_trunc(x): + return x & (2**32 - 1) class TestGdbRemotePlatformFile(GdbRemoteTestCaseBase): @@ -174,6 +205,71 @@ True) self.expect_gdbremote_sequence() + @skipIfWindows + @add_test_categories(["llgs"]) + def test_platform_file_fstat(self): + server = self.connect_to_debug_monitor() + self.assertIsNotNone(server) + + with tempfile.NamedTemporaryFile() as temp_file: + temp_file.write(b"some test data for stat") + temp_file.flush() + + self.do_handshake() + self.test_sequence.add_log_lines( + ["read packet: $vFile:open:%s,0,0#00" % ( + binascii.b2a_hex(temp_file.name.encode()).decode(),), + {"direction": "send", + "regex": r"^\$F([0-9a-fA-F]+)#[0-9a-fA-F]{2}$", + "capture": {1: "fd"}}], + True) + + context = self.expect_gdbremote_sequence() + self.assertIsNotNone(context) + fd = int(context["fd"], 16) + + self.reset_test_sequence() + self.test_sequence.add_log_lines( + ["read packet: $vFile:fstat:%x#00" % (fd,), + {"direction": "send", + "regex": r"^\$F([0-9a-fA-F]+);(.*)#[0-9a-fA-F]{2}$", + "capture": {1: "size", 2: "data"}}], + True) + context = self.expect_gdbremote_sequence() + self.assertEqual(int(context["size"], 16), 64) + # NB: we're using .encode() as a hack because the test suite + # is wrongly using (unicode) str instead of bytes + gdb_stat = GDBStat( + *struct.unpack(">IIIIIIIQQQIII", + self.decode_gdbremote_binary(context["data"]) + .encode("iso-8859-1"))) + sys_stat = os.fstat(temp_file.fileno()) + + self.assertEqual(gdb_stat.st_dev, uint32_or_zero(sys_stat.st_dev)) + self.assertEqual(gdb_stat.st_ino, uint32_or_zero(sys_stat.st_ino)) + self.assertEqual(gdb_stat.st_mode, uint32_trunc(sys_stat.st_mode)) + self.assertEqual(gdb_stat.st_nlink, uint32_or_max(sys_stat.st_nlink)) + self.assertEqual(gdb_stat.st_uid, uint32_or_zero(sys_stat.st_uid)) + self.assertEqual(gdb_stat.st_gid, uint32_or_zero(sys_stat.st_gid)) + self.assertEqual(gdb_stat.st_rdev, uint32_or_zero(sys_stat.st_rdev)) + self.assertEqual(gdb_stat.st_size, sys_stat.st_size) + self.assertEqual(gdb_stat.st_blksize, sys_stat.st_blksize) + self.assertEqual(gdb_stat.st_blocks, sys_stat.st_blocks) + self.assertEqual(gdb_stat.st_atime, + uint32_or_zero(int(sys_stat.st_atime))) + self.assertEqual(gdb_stat.st_mtime, + uint32_or_zero(int(sys_stat.st_mtime))) + self.assertEqual(gdb_stat.st_ctime, + uint32_or_zero(int(sys_stat.st_ctime))) + + self.reset_test_sequence() + self.test_sequence.add_log_lines( + ["read packet: $vFile:close:%x#00" % (fd,), + "send packet: $F0#00"], + True) + self.expect_gdbremote_sequence() + + def expect_error(self): self.test_sequence.add_log_lines( [{"direction": "send",