[lldb] [gdb-remote] Add fallbacks for vFile:mode and vFile:exists

Add a GDB-compatible fallback to vFile:fstat for vFile:mode, and to
vFile:open for vFile:exists.  Note that this is only partial fallback,
as it fails if the file cannot be opened.

Differential Revision: https://reviews.llvm.org/D107811
This commit is contained in:
Michał Górny 2021-08-10 13:01:34 +02:00
parent dbb0c14d27
commit 501eaf8877
3 changed files with 136 additions and 43 deletions

View file

@ -65,7 +65,8 @@ GDBRemoteCommunicationClient::GDBRemoteCommunicationClient()
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_supports_vFileSize(true), m_supports_vFileMode(true),
m_supports_vFileExists(true),
m_host_arch(), m_process_arch(), m_os_build(), m_os_kernel(),
m_hostname(), m_gdb_server_name(), m_default_packet_timeout(0),
@ -3159,37 +3160,50 @@ void GDBRemoteCommunicationClient::AutoCompleteDiskFileOrDirectory(
Status
GDBRemoteCommunicationClient::GetFilePermissions(const FileSpec &file_spec,
uint32_t &file_permissions) {
std::string path{file_spec.GetPath(false)};
Status error;
lldb_private::StreamString stream;
stream.PutCString("vFile:mode:");
stream.PutStringAsRawHex8(path);
StringExtractorGDBRemote response;
if (SendPacketAndWaitForResponse(stream.GetString(), response) ==
PacketResult::Success) {
if (response.GetChar() != 'F') {
error.SetErrorStringWithFormat("invalid response to '%s' packet",
if (m_supports_vFileMode) {
std::string path{file_spec.GetPath(false)};
Status error;
lldb_private::StreamString stream;
stream.PutCString("vFile:mode:");
stream.PutStringAsRawHex8(path);
StringExtractorGDBRemote response;
if (SendPacketAndWaitForResponse(stream.GetString(), response) !=
PacketResult::Success) {
error.SetErrorStringWithFormat("failed to send '%s' packet",
stream.GetData());
} else {
const uint32_t mode = response.GetS32(-1, 16);
if (static_cast<int32_t>(mode) == -1) {
if (response.GetChar() == ',') {
int response_errno = response.GetS32(-1, 16);
if (response_errno > 0)
error.SetError(response_errno, lldb::eErrorTypePOSIX);
else
error.SetErrorToGenericError();
} else
error.SetErrorToGenericError();
} else {
file_permissions = mode & (S_IRWXU | S_IRWXG | S_IRWXO);
}
return error;
}
if (!response.IsUnsupportedResponse()) {
if (response.GetChar() != 'F') {
error.SetErrorStringWithFormat("invalid response to '%s' packet",
stream.GetData());
} else {
const uint32_t mode = response.GetS32(-1, 16);
if (static_cast<int32_t>(mode) == -1) {
if (response.GetChar() == ',') {
int response_errno = response.GetS32(-1, 16);
if (response_errno > 0)
error.SetError(response_errno, lldb::eErrorTypePOSIX);
else
error.SetErrorToGenericError();
} else
error.SetErrorToGenericError();
} else {
file_permissions = mode & (S_IRWXU | S_IRWXG | S_IRWXO);
}
}
return error;
} else { // response.IsUnsupportedResponse()
m_supports_vFileMode = false;
}
} else {
error.SetErrorStringWithFormat("failed to send '%s' packet",
stream.GetData());
}
return error;
// Fallback to fstat.
if (llvm::Optional<GDBRemoteFStatData> st = Stat(file_spec)) {
file_permissions = st->gdb_st_mode & (S_IRWXU | S_IRWXG | S_IRWXO);
return Status();
}
return Status("fstat failed");
}
uint64_t GDBRemoteCommunicationClient::ReadFile(lldb::user_id_t fd,
@ -3332,21 +3346,33 @@ Status GDBRemoteCommunicationClient::Unlink(const FileSpec &file_spec) {
// Extension of host I/O packets to get whether a file exists.
bool GDBRemoteCommunicationClient::GetFileExists(
const lldb_private::FileSpec &file_spec) {
std::string path(file_spec.GetPath(false));
lldb_private::StreamString stream;
stream.PutCString("vFile:exists:");
stream.PutStringAsRawHex8(path);
StringExtractorGDBRemote response;
if (SendPacketAndWaitForResponse(stream.GetString(), response) ==
PacketResult::Success) {
if (response.GetChar() != 'F')
if (m_supports_vFileExists) {
std::string path(file_spec.GetPath(false));
lldb_private::StreamString stream;
stream.PutCString("vFile:exists:");
stream.PutStringAsRawHex8(path);
StringExtractorGDBRemote response;
if (SendPacketAndWaitForResponse(stream.GetString(), response) !=
PacketResult::Success)
return false;
if (response.GetChar() != ',')
return false;
bool retcode = (response.GetChar() != '0');
return retcode;
if (!response.IsUnsupportedResponse()) {
if (response.GetChar() != 'F')
return false;
if (response.GetChar() != ',')
return false;
bool retcode = (response.GetChar() != '0');
return retcode;
} else
m_supports_vFileExists = false;
}
return false;
// Fallback to open.
Status error;
lldb::user_id_t fd = OpenFile(file_spec, File::eOpenOptionReadOnly, 0, error);
if (fd == UINT64_MAX)
return false;
CloseFile(fd, error);
return true;
}
bool GDBRemoteCommunicationClient::CalculateMD5(

View file

@ -587,7 +587,8 @@ protected:
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_vFileSize : 1;
m_supports_jModulesInfo : 1, m_supports_vFileSize : 1,
m_supports_vFileMode : 1, m_supports_vFileExists : 1;
/// Current gdb remote protocol process identifier for all other operations
lldb::pid_t m_curr_pid = LLDB_INVALID_PROCESS_ID;

View file

@ -116,6 +116,33 @@ class TestGDBRemotePlatformFile(GDBPlatformClientTestBase):
"vFile:mode:2f736f6d652f66696c652e747874",
])
def test_file_permissions_fallback(self):
"""Test 'platform get-permissions' fallback to fstat"""
class Responder(MockGDBServerResponder):
def vFile(self, packet):
if packet.startswith("vFile:open:"):
return "F5"
elif packet.startswith("vFile:fstat:"):
return "F40;" + 8 * "\0" + "\0\0\1\xA4" + 52 * "\0"
if packet.startswith("vFile:close:"):
return "F0"
return ""
self.server.responder = Responder()
try:
self.match("platform get-permissions /some/file.txt",
[r"File permissions of /some/file\.txt \(remote\): 0o0644"])
self.assertPacketLogContains([
"vFile:mode:2f736f6d652f66696c652e747874",
"vFile:open:2f736f6d652f66696c652e747874,00000000,00000000",
"vFile:fstat:5",
"vFile:close:5",
])
finally:
self.dbg.GetSelectedPlatform().DisconnectRemote()
def test_file_exists(self):
"""Test 'platform file-exists'"""
@ -145,3 +172,42 @@ class TestGDBRemotePlatformFile(GDBPlatformClientTestBase):
self.assertPacketLogContains([
"vFile:exists:2f736f6d652f66696c652e747874",
])
def test_file_exists_fallback(self):
"""Test 'platform file-exists' fallback to open"""
class Responder(MockGDBServerResponder):
def vFile(self, packet):
if packet.startswith("vFile:open:"):
return "F5"
if packet.startswith("vFile:close:"):
return "F0"
return ""
self.server.responder = Responder()
self.match("platform file-exists /some/file.txt",
[r"File /some/file\.txt \(remote\) exists"])
self.assertPacketLogContains([
"vFile:exists:2f736f6d652f66696c652e747874",
"vFile:open:2f736f6d652f66696c652e747874,00000000,00000000",
"vFile:close:5",
])
def test_file_exists_not_fallback(self):
"""Test 'platform file-exists' fallback to open with non-existing file"""
class Responder(MockGDBServerResponder):
def vFile(self, packet):
if packet.startswith("vFile:open:"):
return "F-1,2"
return ""
self.server.responder = Responder()
self.match("platform file-exists /some/file.txt",
[r"File /some/file\.txt \(remote\) does not exist"])
self.assertPacketLogContains([
"vFile:exists:2f736f6d652f66696c652e747874",
"vFile:open:2f736f6d652f66696c652e747874,00000000,00000000",
])