Skip to content

Commit 22c26fa

Browse files
authored
[lldb] Skip remote PutFile when MD5 hashes equal (#88812)
This PR adds a check within `PutFile` to exit early when both local and destination files have matching MD5 hashes. If they differ, or there is trouble getting the hashes, the regular code path to put the file is run. As I needed this to talk to an `lldb-server` which runs the gdb-remote protocol, I enabled `CalculateMD5` within `Platform/gdb-server` and also found and fixed a parsing bug within it as well. Before this PR, the client is incorrectly parsing the response packet containing the checksum; after this PR, hopefully this is fixed. There is a test for the parsing behavior included in this PR. --------- Co-authored-by: Anthony Ha <[email protected]>
1 parent 515269b commit 22c26fa

File tree

6 files changed

+100
-4
lines changed

6 files changed

+100
-4
lines changed

lldb/source/Plugins/Platform/gdb-server/PlatformRemoteGDBServer.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -684,6 +684,14 @@ Status PlatformRemoteGDBServer::RunShellCommand(
684684
signo_ptr, command_output, timeout);
685685
}
686686

687+
bool PlatformRemoteGDBServer::CalculateMD5(const FileSpec &file_spec,
688+
uint64_t &low, uint64_t &high) {
689+
if (!IsConnected())
690+
return false;
691+
692+
return m_gdb_client_up->CalculateMD5(file_spec, low, high);
693+
}
694+
687695
void PlatformRemoteGDBServer::CalculateTrapHandlerSymbolNames() {
688696
m_trap_handlers.push_back(ConstString("_sigtramp"));
689697
}

lldb/source/Plugins/Platform/gdb-server/PlatformRemoteGDBServer.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,9 @@ class PlatformRemoteGDBServer : public Platform, private UserIDResolver {
146146

147147
void CalculateTrapHandlerSymbolNames() override;
148148

149+
bool CalculateMD5(const FileSpec &file_spec, uint64_t &low,
150+
uint64_t &high) override;
151+
149152
const lldb::UnixSignalsSP &GetRemoteUnixSignals() override;
150153

151154
size_t ConnectToWaitingProcesses(lldb_private::Debugger &debugger,

lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3419,7 +3419,7 @@ bool GDBRemoteCommunicationClient::GetFileExists(
34193419
}
34203420

34213421
bool GDBRemoteCommunicationClient::CalculateMD5(
3422-
const lldb_private::FileSpec &file_spec, uint64_t &high, uint64_t &low) {
3422+
const lldb_private::FileSpec &file_spec, uint64_t &low, uint64_t &high) {
34233423
std::string path(file_spec.GetPath(false));
34243424
lldb_private::StreamString stream;
34253425
stream.PutCString("vFile:MD5:");
@@ -3433,8 +3433,44 @@ bool GDBRemoteCommunicationClient::CalculateMD5(
34333433
return false;
34343434
if (response.Peek() && *response.Peek() == 'x')
34353435
return false;
3436-
low = response.GetHexMaxU64(false, UINT64_MAX);
3437-
high = response.GetHexMaxU64(false, UINT64_MAX);
3436+
3437+
// GDBRemoteCommunicationServerCommon::Handle_vFile_MD5 concatenates low and
3438+
// high hex strings. We can't use response.GetHexMaxU64 because that can't
3439+
// handle the concatenated hex string. What would happen is parsing the low
3440+
// would consume the whole response packet which would give incorrect
3441+
// results. Instead, we get the byte string for each low and high hex
3442+
// separately, and parse them.
3443+
//
3444+
// An alternate way to handle this is to change the server to put a
3445+
// delimiter between the low/high parts, and change the client to parse the
3446+
// delimiter. However, we choose not to do this so existing lldb-servers
3447+
// don't have to be patched
3448+
3449+
// The checksum is 128 bits encoded as hex
3450+
// This means low/high are halves of 64 bits each, in otherwords, 8 bytes.
3451+
// Each byte takes 2 hex characters in the response.
3452+
const size_t MD5_HALF_LENGTH = sizeof(uint64_t) * 2;
3453+
3454+
// Get low part
3455+
auto part =
3456+
response.GetStringRef().substr(response.GetFilePos(), MD5_HALF_LENGTH);
3457+
if (part.size() != MD5_HALF_LENGTH)
3458+
return false;
3459+
response.SetFilePos(response.GetFilePos() + part.size());
3460+
3461+
if (part.getAsInteger(/*radix=*/16, low))
3462+
return false;
3463+
3464+
// Get high part
3465+
part =
3466+
response.GetStringRef().substr(response.GetFilePos(), MD5_HALF_LENGTH);
3467+
if (part.size() != MD5_HALF_LENGTH)
3468+
return false;
3469+
response.SetFilePos(response.GetFilePos() + part.size());
3470+
3471+
if (part.getAsInteger(/*radix=*/16, high))
3472+
return false;
3473+
34383474
return true;
34393475
}
34403476
return false;

lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -392,7 +392,7 @@ class GDBRemoteCommunicationClient : public GDBRemoteClientBase {
392392
*command_output, // Pass nullptr if you don't want the command output
393393
const Timeout<std::micro> &timeout);
394394

395-
bool CalculateMD5(const FileSpec &file_spec, uint64_t &high, uint64_t &low);
395+
bool CalculateMD5(const FileSpec &file_spec, uint64_t &low, uint64_t &high);
396396

397397
lldb::DataBufferSP ReadRegister(
398398
lldb::tid_t tid,

lldb/source/Target/Platform.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1197,6 +1197,32 @@ Status Platform::PutFile(const FileSpec &source, const FileSpec &destination,
11971197
if (!source_file)
11981198
return Status(source_file.takeError());
11991199
Status error;
1200+
1201+
bool requires_upload = true;
1202+
uint64_t dest_md5_low, dest_md5_high;
1203+
bool success = CalculateMD5(destination, dest_md5_low, dest_md5_high);
1204+
if (!success) {
1205+
LLDB_LOGF(log, "[PutFile] couldn't get md5 sum of destination");
1206+
} else {
1207+
auto local_md5 = llvm::sys::fs::md5_contents(source.GetPath());
1208+
if (!local_md5) {
1209+
LLDB_LOGF(log, "[PutFile] couldn't get md5 sum of source");
1210+
} else {
1211+
const auto [local_md5_high, local_md5_low] = local_md5->words();
1212+
LLDB_LOGF(log, "[PutFile] destination md5: %016" PRIx64 "%016" PRIx64,
1213+
dest_md5_high, dest_md5_low);
1214+
LLDB_LOGF(log, "[PutFile] local md5: %016" PRIx64 "%016" PRIx64,
1215+
local_md5_high, local_md5_low);
1216+
requires_upload =
1217+
local_md5_high != dest_md5_high || local_md5_low != dest_md5_low;
1218+
}
1219+
}
1220+
1221+
if (!requires_upload) {
1222+
LLDB_LOGF(log, "[PutFile] skipping PutFile because md5sums match");
1223+
return error;
1224+
}
1225+
12001226
uint32_t permissions = source_file.get()->GetPermissions(error);
12011227
if (permissions == 0)
12021228
permissions = lldb::eFilePermissionsFileDefault;

lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -592,3 +592,26 @@ TEST_F(GDBRemoteCommunicationClientTest, WriteMemoryTags) {
592592
std::vector<uint8_t>{0x99}, "QMemTags:456789,0:80000000:99",
593593
"E03", false);
594594
}
595+
596+
TEST_F(GDBRemoteCommunicationClientTest, CalculateMD5) {
597+
FileSpec file_spec("/foo/bar", FileSpec::Style::posix);
598+
uint64_t low, high;
599+
std::future<bool> async_result = std::async(std::launch::async, [&] {
600+
return client.CalculateMD5(file_spec, low, high);
601+
});
602+
603+
lldb_private::StreamString stream;
604+
stream.PutCString("vFile:MD5:");
605+
stream.PutStringAsRawHex8(file_spec.GetPath(false));
606+
HandlePacket(server, stream.GetString().str(),
607+
"F,"
608+
"deadbeef01020304"
609+
"05060708deadbeef");
610+
ASSERT_TRUE(async_result.get());
611+
612+
// Server and client puts/parses low, and then high
613+
const uint64_t expected_low = 0xdeadbeef01020304;
614+
const uint64_t expected_high = 0x05060708deadbeef;
615+
EXPECT_EQ(expected_low, low);
616+
EXPECT_EQ(expected_high, high);
617+
}

0 commit comments

Comments
 (0)