diff --git a/lldb/include/lldb/Utility/StringExtractorGDBRemote.h b/lldb/include/lldb/Utility/StringExtractorGDBRemote.h --- a/lldb/include/lldb/Utility/StringExtractorGDBRemote.h +++ b/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, diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerCommon.h b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerCommon.h --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerCommon.h +++ b/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); 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 @@ -156,6 +156,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); @@ -754,6 +757,48 @@ return SendErrorResponse(24); } +template +static void fill_clamp(T &dest, U src, typename T::value_type fallback) { + dest = src <= std::numeric_limits::max() ? src + : fallback; +} + +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()); + } + + GDBRemoteFStatData data; + fill_clamp(data.gdb_st_dev, file_stats.st_dev, 0); + fill_clamp(data.gdb_st_ino, file_stats.st_ino, 0); + data.gdb_st_mode = file_stats.st_mode; + fill_clamp(data.gdb_st_nlink, file_stats.st_nlink, UINT32_MAX); + fill_clamp(data.gdb_st_uid, file_stats.st_uid, 0); + fill_clamp(data.gdb_st_gid, file_stats.st_gid, 0); + fill_clamp(data.gdb_st_rdev, file_stats.st_rdev, 0); + data.gdb_st_size = file_stats.st_size; +#if !defined(_WIN32) + data.gdb_st_blksize = file_stats.st_blksize; + data.gdb_st_blocks = file_stats.st_blocks; +#endif + fill_clamp(data.gdb_st_atime, file_stats.st_atime, 0); + fill_clamp(data.gdb_st_mtime, file_stats.st_mtime, 0); + fill_clamp(data.gdb_st_ctime, file_stats.st_ctime, 0); + + response.Printf("F%zx;", sizeof(data)); + response.PutEscapedBytes(&data, sizeof(data)); + return SendPacketNoLock(response.GetString()); +} + GDBRemoteCommunication::PacketResult GDBRemoteCommunicationServerCommon::Handle_vFile_Stat( StringExtractorGDBRemote &packet) { diff --git a/lldb/source/Utility/StringExtractorGDBRemote.cpp b/lldb/source/Utility/StringExtractorGDBRemote.cpp --- a/lldb/source/Utility/StringExtractorGDBRemote.cpp +++ b/lldb/source/Utility/StringExtractorGDBRemote.cpp @@ -339,6 +339,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")) diff --git a/lldb/test/API/tools/lldb-server/TestGdbRemotePlatformFile.py b/lldb/test/API/tools/lldb-server/TestGdbRemotePlatformFile.py --- a/lldb/test/API/tools/lldb-server/TestGdbRemotePlatformFile.py +++ b/lldb/test/API/tools/lldb-server/TestGdbRemotePlatformFile.py @@ -9,7 +9,38 @@ from gdbremote_testcase import GdbRemoteTestCaseBase import binascii +import os import stat +import struct +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): @@ -178,6 +209,70 @@ 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",