[Lldb-commits] [lldb] 12dee9d - [lldb][Android] Support zip .so file

Kazuki Sakamoto via lldb-commits lldb-commits at lists.llvm.org
Tue Jun 20 15:41:37 PDT 2023


Author: Kazuki Sakamoto
Date: 2023-06-20T15:21:46-07:00
New Revision: 12dee9d3cd762d9754e2adadffa13c1cce85cf07

URL: https://github.com/llvm/llvm-project/commit/12dee9d3cd762d9754e2adadffa13c1cce85cf07
DIFF: https://github.com/llvm/llvm-project/commit/12dee9d3cd762d9754e2adadffa13c1cce85cf07.diff

LOG: [lldb][Android] Support zip .so file

In Android API level 23 and above, dynamic loader is able to load .so file
directly from APK, which is zip file.
https://android.googlesource.com/platform/bionic/+/master/
android-changes-for-ndk-developers.md#
opening-shared-libraries-directly-from-an-apk

The .so file is page aligned and uncompressed, so
ObjectFileELF::GetModuleSpecifications works with .so file offset and size
directly from zip file without extracting it. (D152757)

GDBRemoteCommunicationServerCommon::GetModuleInfo returns a module spec to LLDB
with "zip_path!/so_path" file spec, which is passed through from Android
dynamic loader, and the .so file offset and size.

PlatformAndroid::DownloadModuleSlice uses 'shell dd' to download the .so file
slice from the zip file with the .so file offset and size.

Differential Revision: https://reviews.llvm.org/D152759

Added: 
    lldb/include/lldb/Host/common/ZipFileResolver.h
    lldb/include/lldb/Utility/ZipFile.h
    lldb/source/Host/common/ZipFileResolver.cpp
    lldb/source/Utility/ZipFile.cpp
    lldb/unittests/Host/common/CMakeLists.txt
    lldb/unittests/Host/common/Inputs/zip-test.zip
    lldb/unittests/Host/common/ZipFileResolverTest.cpp

Modified: 
    lldb/source/Host/CMakeLists.txt
    lldb/source/Plugins/Platform/Android/PlatformAndroid.cpp
    lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerCommon.cpp
    lldb/source/Utility/CMakeLists.txt
    lldb/unittests/Host/CMakeLists.txt

Removed: 
    


################################################################################
diff  --git a/lldb/include/lldb/Host/common/ZipFileResolver.h b/lldb/include/lldb/Host/common/ZipFileResolver.h
new file mode 100644
index 0000000000000..ec7151f3e57c9
--- /dev/null
+++ b/lldb/include/lldb/Host/common/ZipFileResolver.h
@@ -0,0 +1,40 @@
+//===-- ZipFileResolver.h ---------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_HOST_ZIPFILERESOLVER_H
+#define LLDB_HOST_ZIPFILERESOLVER_H
+
+#include "lldb/lldb-private.h"
+
+namespace lldb_private {
+
+/// In Android API level 23 and above, bionic dynamic linker is able to load
+/// .so file directly from APK or .zip file. This is a utility class to resolve
+/// the file spec in order to get the zip path and the .so file offset and size
+/// if the file spec contains "zip_path!/so_path".
+/// https://android.googlesource.com/platform/bionic/+/master/
+/// android-changes-for-ndk-developers.md#
+/// opening-shared-libraries-directly-from-an-apk
+class ZipFileResolver {
+public:
+  enum FileKind {
+    eFileKindInvalid = 0,
+    eFileKindNormal,
+    eFileKindZip,
+  };
+
+  static bool ResolveSharedLibraryPath(const FileSpec &file_spec,
+                                       FileKind &file_kind,
+                                       std::string &file_path,
+                                       lldb::offset_t &so_file_offset,
+                                       lldb::offset_t &so_file_size);
+};
+
+} // end of namespace lldb_private
+
+#endif // LLDB_HOST_ZIPFILERESOLVER_H

diff  --git a/lldb/include/lldb/Utility/ZipFile.h b/lldb/include/lldb/Utility/ZipFile.h
new file mode 100644
index 0000000000000..cddd6c44ca638
--- /dev/null
+++ b/lldb/include/lldb/Utility/ZipFile.h
@@ -0,0 +1,30 @@
+//===-- ZipFile.h -----------------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_UTILITY_ZIPFILE_H
+#define LLDB_UTILITY_ZIPFILE_H
+
+#include "lldb/lldb-private.h"
+
+namespace lldb_private {
+
+/// In Android API level 23 and above, bionic dynamic linker is able to load
+/// .so file directly from APK or .zip file. This is a utility class to find
+/// .so file offset and size from zip file.
+/// https://android.googlesource.com/platform/bionic/+/master/
+/// android-changes-for-ndk-developers.md#
+/// opening-shared-libraries-directly-from-an-apk
+class ZipFile {
+public:
+  static bool Find(lldb::DataBufferSP zip_data, const llvm::StringRef file_path,
+                   lldb::offset_t &file_offset, lldb::offset_t &file_size);
+};
+
+} // end of namespace lldb_private
+
+#endif // LLDB_UTILITY_ZIPFILE_H

diff  --git a/lldb/source/Host/CMakeLists.txt b/lldb/source/Host/CMakeLists.txt
index 91f353e50b190..5451c2bcf776e 100644
--- a/lldb/source/Host/CMakeLists.txt
+++ b/lldb/source/Host/CMakeLists.txt
@@ -42,6 +42,7 @@ add_host_subdirectory(common
   common/ThreadLauncher.cpp
   common/UDPSocket.cpp
   common/XML.cpp
+  common/ZipFileResolver.cpp
   )
 
 if (LLDB_ENABLE_LIBEDIT)

diff  --git a/lldb/source/Host/common/ZipFileResolver.cpp b/lldb/source/Host/common/ZipFileResolver.cpp
new file mode 100644
index 0000000000000..abb914cb9ae42
--- /dev/null
+++ b/lldb/source/Host/common/ZipFileResolver.cpp
@@ -0,0 +1,57 @@
+//===-- ZipFileResolver.cpp -----------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "lldb/Host/common/ZipFileResolver.h"
+#include "lldb/Host/FileSystem.h"
+#include "lldb/Utility/DataBuffer.h"
+#include "lldb/Utility/FileSpec.h"
+#include "lldb/Utility/ZipFile.h"
+
+using namespace lldb_private;
+using namespace llvm::support;
+
+bool ZipFileResolver::ResolveSharedLibraryPath(const FileSpec &file_spec,
+                                               FileKind &file_kind,
+                                               std::string &file_path,
+                                               lldb::offset_t &so_file_offset,
+                                               lldb::offset_t &so_file_size) {
+  // When bionic loads .so file from APK or zip file, this file_spec will be
+  // "zip_path!/so_path". Otherwise it is just a normal file path.
+  static constexpr llvm::StringLiteral k_zip_separator("!/");
+  std::string path(file_spec.GetPath());
+  size_t pos = path.find(k_zip_separator);
+  if (pos == std::string::npos) {
+    // This file_spec does not contain the zip separator.
+    // Treat this file_spec as a normal file.
+    // so_file_offset and so_file_size should be 0.
+    file_kind = FileKind::eFileKindNormal;
+    file_path = path;
+    so_file_offset = 0;
+    so_file_size = 0;
+    return true;
+  }
+
+  // This file_spec is a zip .so path. Extract the zip path and the .so path.
+  std::string zip_path(path.substr(0, pos));
+  std::string so_path(path.substr(pos + k_zip_separator.size()));
+
+  // Try to find the .so file from the zip file.
+  FileSpec zip_file_spec(zip_path);
+  uint64_t zip_file_size = FileSystem::Instance().GetByteSize(zip_file_spec);
+  lldb::DataBufferSP zip_data =
+      FileSystem::Instance().CreateDataBuffer(zip_file_spec, zip_file_size);
+  if (ZipFile::Find(zip_data, so_path, so_file_offset, so_file_size)) {
+    // Found the .so file from the zip file and got the file offset and size.
+    // Return the zip path. so_file_offset and so_file_size are already set.
+    file_kind = FileKind::eFileKindZip;
+    file_path = zip_path;
+    return true;
+  }
+
+  return false;
+}

diff  --git a/lldb/source/Plugins/Platform/Android/PlatformAndroid.cpp b/lldb/source/Plugins/Platform/Android/PlatformAndroid.cpp
index 64c1d1e48a9ef..4b09e8b253412 100644
--- a/lldb/source/Plugins/Platform/Android/PlatformAndroid.cpp
+++ b/lldb/source/Plugins/Platform/Android/PlatformAndroid.cpp
@@ -234,10 +234,32 @@ Status PlatformAndroid::DownloadModuleSlice(const FileSpec &src_file_spec,
                                             const uint64_t src_offset,
                                             const uint64_t src_size,
                                             const FileSpec &dst_file_spec) {
-  if (src_offset != 0)
-    return Status("Invalid offset - %" PRIu64, src_offset);
+  // In Android API level 23 and above, dynamic loader is able to load .so
+  // file directly from APK. In that case, src_offset will be an non-zero.
+  if (src_offset == 0) // Use GetFile for a normal file.
+    return GetFile(src_file_spec, dst_file_spec);
 
-  return GetFile(src_file_spec, dst_file_spec);
+  std::string source_file = src_file_spec.GetPath(false);
+  if (source_file.find('\'') != std::string::npos)
+    return Status("Doesn't support single-quotes in filenames");
+
+  // For zip .so file, src_file_spec will be "zip_path!/so_path".
+  // Extract "zip_path" from the source_file.
+  static constexpr llvm::StringLiteral k_zip_separator("!/");
+  size_t pos = source_file.find(k_zip_separator);
+  if (pos != std::string::npos)
+    source_file = source_file.substr(0, pos);
+
+  AdbClient adb(m_device_id);
+
+  // Use 'shell dd' to download the file slice with the offset and size.
+  char cmd[PATH_MAX];
+  snprintf(cmd, sizeof(cmd),
+           "dd if='%s' iflag=skip_bytes,count_bytes "
+           "skip=%" PRIu64 " count=%" PRIu64 " status=none",
+           source_file.c_str(), src_offset, src_size);
+
+  return adb.ShellToFile(cmd, minutes(1), dst_file_spec);
 }
 
 Status PlatformAndroid::DisconnectRemote() {
@@ -310,15 +332,16 @@ Status PlatformAndroid::DownloadSymbolFile(const lldb::ModuleSP &module_sp,
 
   // Create file remover for the temporary directory created on the device
   std::unique_ptr<std::string, std::function<void(std::string *)>>
-  tmpdir_remover(&tmpdir, [&adb](std::string *s) {
-    StreamString command;
-    command.Printf("rm -rf %s", s->c_str());
-    Status error = adb.Shell(command.GetData(), seconds(5), nullptr);
+      tmpdir_remover(&tmpdir, [&adb](std::string *s) {
+        StreamString command;
+        command.Printf("rm -rf %s", s->c_str());
+        Status error = adb.Shell(command.GetData(), seconds(5), nullptr);
 
-    Log *log = GetLog(LLDBLog::Platform);
-    if (log && error.Fail())
-      LLDB_LOGF(log, "Failed to remove temp directory: %s", error.AsCString());
-  });
+        Log *log = GetLog(LLDBLog::Platform);
+        if (log && error.Fail())
+          LLDB_LOGF(log, "Failed to remove temp directory: %s",
+                    error.AsCString());
+      });
 
   FileSpec symfile_platform_filespec(tmpdir);
   symfile_platform_filespec.AppendPathComponent("symbolized.oat");
@@ -344,15 +367,15 @@ bool PlatformAndroid::GetRemoteOSVersion() {
 llvm::StringRef
 PlatformAndroid::GetLibdlFunctionDeclarations(lldb_private::Process *process) {
   SymbolContextList matching_symbols;
-  std::vector<const char *> dl_open_names = { "__dl_dlopen", "dlopen" };
+  std::vector<const char *> dl_open_names = {"__dl_dlopen", "dlopen"};
   const char *dl_open_name = nullptr;
   Target &target = process->GetTarget();
-  for (auto name: dl_open_names) {
+  for (auto name : dl_open_names) {
     target.GetImages().FindFunctionSymbols(
         ConstString(name), eFunctionNameTypeFull, matching_symbols);
     if (matching_symbols.GetSize()) {
-       dl_open_name = name;
-       break;
+      dl_open_name = name;
+      break;
     }
   }
   // Older platform versions have the dl function symbols mangled

diff  --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerCommon.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerCommon.cpp
index 9e90e98b1f2ab..b4fb5b68dd41f 100644
--- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerCommon.cpp
+++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerCommon.cpp
@@ -44,9 +44,9 @@
 
 #ifdef __ANDROID__
 #include "lldb/Host/android/HostInfoAndroid.h"
+#include "lldb/Host/common/ZipFileResolver.h"
 #endif
 
-
 using namespace lldb;
 using namespace lldb_private::process_gdb_remote;
 using namespace lldb_private;
@@ -1138,7 +1138,7 @@ GDBRemoteCommunicationServerCommon::Handle_qModuleInfo(
 
   response.PutCString("file_path:");
   response.PutStringAsRawHex8(
-        matched_module_spec.GetFileSpec().GetPath().c_str());
+      matched_module_spec.GetFileSpec().GetPath().c_str());
   response.PutChar(';');
   response.PutCString("file_offset:");
   response.PutHex64(file_offset);
@@ -1326,17 +1326,50 @@ GDBRemoteCommunicationServerCommon::GetModuleInfo(llvm::StringRef module_path,
 
   const FileSpec module_path_spec =
       FindModuleFile(req_module_path_spec.GetPath(), arch);
-  const ModuleSpec module_spec(module_path_spec, arch);
+
+  lldb::offset_t file_offset = 0;
+  lldb::offset_t file_size = 0;
+#ifdef __ANDROID__
+  // In Android API level 23 and above, dynamic loader is able to load .so file
+  // directly from zip file. In that case, module_path will be
+  // "zip_path!/so_path". Resolve the zip file path, .so file offset and size.
+  ZipFileResolver::FileKind file_kind = ZipFileResolver::eFileKindInvalid;
+  std::string file_path;
+  if (!ZipFileResolver::ResolveSharedLibraryPath(
+          module_path_spec, file_kind, file_path, file_offset, file_size)) {
+    return ModuleSpec();
+  }
+  lldbassert(file_kind != ZipFileResolver::eFileKindInvalid);
+  // For zip .so file, this file_path will contain only the actual zip file
+  // path for the object file processing. Otherwise it is the same as
+  // module_path.
+  const FileSpec actual_module_path_spec(file_path);
+#else
+  // It is just module_path_spec reference for other platforms.
+  const FileSpec &actual_module_path_spec = module_path_spec;
+#endif
+
+  const ModuleSpec module_spec(actual_module_path_spec, arch);
 
   ModuleSpecList module_specs;
-  if (!ObjectFile::GetModuleSpecifications(module_path_spec, 0, 0,
-                                           module_specs))
+  if (!ObjectFile::GetModuleSpecifications(actual_module_path_spec, file_offset,
+                                           file_size, module_specs))
     return ModuleSpec();
 
   ModuleSpec matched_module_spec;
   if (!module_specs.FindMatchingModuleSpec(module_spec, matched_module_spec))
     return ModuleSpec();
 
+#ifdef __ANDROID__
+  if (file_kind == ZipFileResolver::eFileKindZip) {
+    // For zip .so file, matched_module_spec contains only the actual zip file
+    // path for the object file processing. Overwrite the matched_module_spec
+    // file spec with the original module_path_spec to pass "zip_path!/so_path"
+    // through to PlatformAndroid::DownloadModuleSlice.
+    *matched_module_spec.GetFileSpecPtr() = module_path_spec;
+  }
+#endif
+
   return matched_module_spec;
 }
 

diff  --git a/lldb/source/Utility/CMakeLists.txt b/lldb/source/Utility/CMakeLists.txt
index d8c18b189f6c7..b376ace0ebe3d 100644
--- a/lldb/source/Utility/CMakeLists.txt
+++ b/lldb/source/Utility/CMakeLists.txt
@@ -74,6 +74,7 @@ add_lldb_library(lldbUtility NO_INTERNAL_DEPENDENCIES
   VASprintf.cpp
   VMRange.cpp
   XcodeSDK.cpp
+  ZipFile.cpp
 
   LINK_LIBS
     ${LLDB_SYSTEM_LIBS}

diff  --git a/lldb/source/Utility/ZipFile.cpp b/lldb/source/Utility/ZipFile.cpp
new file mode 100644
index 0000000000000..b8ed956cbfcb2
--- /dev/null
+++ b/lldb/source/Utility/ZipFile.cpp
@@ -0,0 +1,180 @@
+//===-- ZipFile.cpp -------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "lldb/Utility/ZipFile.h"
+#include "lldb/Utility/DataBuffer.h"
+#include "lldb/Utility/FileSpec.h"
+#include "llvm/Support/Endian.h"
+
+using namespace lldb_private;
+using namespace llvm::support;
+
+namespace {
+
+// Zip headers.
+// https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT
+
+// The end of central directory record.
+struct EocdRecord {
+  static constexpr char kSignature[] = {0x50, 0x4b, 0x05, 0x06};
+  char signature[sizeof(kSignature)];
+  unaligned_uint16_t disks;
+  unaligned_uint16_t cd_start_disk;
+  unaligned_uint16_t cds_on_this_disk;
+  unaligned_uint16_t cd_records;
+  unaligned_uint32_t cd_size;
+  unaligned_uint32_t cd_offset;
+  unaligned_uint16_t comment_length;
+};
+
+// Logical find limit for the end of central directory record.
+const size_t kEocdRecordFindLimit =
+    sizeof(EocdRecord) +
+    std::numeric_limits<decltype(EocdRecord::comment_length)>::max();
+
+// Central directory record.
+struct CdRecord {
+  static constexpr char kSignature[] = {0x50, 0x4b, 0x01, 0x02};
+  char signature[sizeof(kSignature)];
+  unaligned_uint16_t version_made_by;
+  unaligned_uint16_t version_needed_to_extract;
+  unaligned_uint16_t general_purpose_bit_flag;
+  unaligned_uint16_t compression_method;
+  unaligned_uint16_t last_modification_time;
+  unaligned_uint16_t last_modification_date;
+  unaligned_uint32_t crc32;
+  unaligned_uint32_t compressed_size;
+  unaligned_uint32_t uncompressed_size;
+  unaligned_uint16_t file_name_length;
+  unaligned_uint16_t extra_field_length;
+  unaligned_uint16_t comment_length;
+  unaligned_uint16_t file_start_disk;
+  unaligned_uint16_t internal_file_attributes;
+  unaligned_uint32_t external_file_attributes;
+  unaligned_uint32_t local_file_header_offset;
+};
+// Immediately after CdRecord,
+// - file name (file_name_length)
+// - extra field (extra_field_length)
+// - comment (comment_length)
+
+// Local file header.
+struct LocalFileHeader {
+  static constexpr char kSignature[] = {0x50, 0x4b, 0x03, 0x04};
+  char signature[sizeof(kSignature)];
+  unaligned_uint16_t version_needed_to_extract;
+  unaligned_uint16_t general_purpose_bit_flag;
+  unaligned_uint16_t compression_method;
+  unaligned_uint16_t last_modification_time;
+  unaligned_uint16_t last_modification_date;
+  unaligned_uint32_t crc32;
+  unaligned_uint32_t compressed_size;
+  unaligned_uint32_t uncompressed_size;
+  unaligned_uint16_t file_name_length;
+  unaligned_uint16_t extra_field_length;
+};
+// Immediately after LocalFileHeader,
+// - file name (file_name_length)
+// - extra field (extra_field_length)
+// - file data (should be compressed_size == uncompressed_size, page aligned)
+
+const EocdRecord *FindEocdRecord(lldb::DataBufferSP zip_data) {
+  // Find backward the end of central directory record from the end of the zip
+  // file to the find limit.
+  const uint8_t *zip_data_end = zip_data->GetBytes() + zip_data->GetByteSize();
+  const uint8_t *find_limit = zip_data_end - kEocdRecordFindLimit;
+  const uint8_t *p = zip_data_end - sizeof(EocdRecord);
+  for (; p >= zip_data->GetBytes() && p >= find_limit; p--) {
+    auto eocd = reinterpret_cast<const EocdRecord *>(p);
+    if (::memcmp(eocd->signature, EocdRecord::kSignature,
+                 sizeof(EocdRecord::kSignature)) == 0) {
+      // Found the end of central directory. Sanity check the values.
+      if (eocd->cd_records * sizeof(CdRecord) > eocd->cd_size ||
+          zip_data->GetBytes() + eocd->cd_offset + eocd->cd_size > p)
+        return nullptr;
+
+      // This is a valid end of central directory record.
+      return eocd;
+    }
+  }
+  return nullptr;
+}
+
+bool GetFile(lldb::DataBufferSP zip_data, uint32_t local_file_header_offset,
+             lldb::offset_t &file_offset, lldb::offset_t &file_size) {
+  auto local_file_header = reinterpret_cast<const LocalFileHeader *>(
+      zip_data->GetBytes() + local_file_header_offset);
+  // The signature should match.
+  if (::memcmp(local_file_header->signature, LocalFileHeader::kSignature,
+               sizeof(LocalFileHeader::kSignature)) != 0)
+    return false;
+
+  auto file_data = reinterpret_cast<const uint8_t *>(local_file_header + 1) +
+                   local_file_header->file_name_length +
+                   local_file_header->extra_field_length;
+  // File should be uncompressed.
+  if (local_file_header->compressed_size !=
+      local_file_header->uncompressed_size)
+    return false;
+
+  // This file is valid. Return the file offset and size.
+  file_offset = file_data - zip_data->GetBytes();
+  file_size = local_file_header->uncompressed_size;
+  return true;
+}
+
+bool FindFile(lldb::DataBufferSP zip_data, const EocdRecord *eocd,
+              const llvm::StringRef file_path, lldb::offset_t &file_offset,
+              lldb::offset_t &file_size) {
+  // Find the file from the central directory records.
+  auto cd = reinterpret_cast<const CdRecord *>(zip_data->GetBytes() +
+                                               eocd->cd_offset);
+  size_t cd_records = eocd->cd_records;
+  for (size_t i = 0; i < cd_records; i++) {
+    // The signature should match.
+    if (::memcmp(cd->signature, CdRecord::kSignature,
+                 sizeof(CdRecord::kSignature)) != 0)
+      return false;
+
+    // Sanity check the file name values.
+    auto file_name = reinterpret_cast<const char *>(cd + 1);
+    size_t file_name_length = cd->file_name_length;
+    if (file_name + file_name_length >= reinterpret_cast<const char *>(eocd) ||
+        file_name_length == 0)
+      return false;
+
+    // Compare the file name.
+    if (file_path == llvm::StringRef(file_name, file_name_length)) {
+      // Found the file.
+      return GetFile(zip_data, cd->local_file_header_offset, file_offset,
+                     file_size);
+    } else {
+      // Skip to the next central directory record.
+      cd = reinterpret_cast<const CdRecord *>(
+          reinterpret_cast<const char *>(cd) + sizeof(CdRecord) +
+          cd->file_name_length + cd->extra_field_length + cd->comment_length);
+      // Sanity check the pointer.
+      if (reinterpret_cast<const char *>(cd) >=
+          reinterpret_cast<const char *>(eocd))
+        return false;
+    }
+  }
+
+  return false;
+}
+
+} // end anonymous namespace
+
+bool ZipFile::Find(lldb::DataBufferSP zip_data, const llvm::StringRef file_path,
+                   lldb::offset_t &file_offset, lldb::offset_t &file_size) {
+  const EocdRecord *eocd = FindEocdRecord(zip_data);
+  if (!eocd)
+    return false;
+
+  return FindFile(zip_data, eocd, file_path, file_offset, file_size);
+}

diff  --git a/lldb/unittests/Host/CMakeLists.txt b/lldb/unittests/Host/CMakeLists.txt
index 68bf50d0a7312..c959478970d18 100644
--- a/lldb/unittests/Host/CMakeLists.txt
+++ b/lldb/unittests/Host/CMakeLists.txt
@@ -38,3 +38,5 @@ add_lldb_unittest(HostTests
     LLVMTestingSupport
     LLVMTargetParser
   )
+
+add_subdirectory(common)

diff  --git a/lldb/unittests/Host/common/CMakeLists.txt b/lldb/unittests/Host/common/CMakeLists.txt
new file mode 100644
index 0000000000000..2934e6f0b4315
--- /dev/null
+++ b/lldb/unittests/Host/common/CMakeLists.txt
@@ -0,0 +1,15 @@
+set (FILES
+  ZipFileResolverTest.cpp
+)
+
+add_lldb_unittest(HostCommonTests
+  ${FILES}
+  LINK_LIBS
+    lldbHost
+    lldbUtilityHelpers
+  )
+
+set(test_inputs
+  zip-test.zip
+  )
+add_unittest_inputs(HostCommonTests "${test_inputs}")

diff  --git a/lldb/unittests/Host/common/Inputs/zip-test.zip b/lldb/unittests/Host/common/Inputs/zip-test.zip
new file mode 100644
index 0000000000000..a077b53d5b843
Binary files /dev/null and b/lldb/unittests/Host/common/Inputs/zip-test.zip 
diff er

diff  --git a/lldb/unittests/Host/common/ZipFileResolverTest.cpp b/lldb/unittests/Host/common/ZipFileResolverTest.cpp
new file mode 100644
index 0000000000000..12aba31411305
--- /dev/null
+++ b/lldb/unittests/Host/common/ZipFileResolverTest.cpp
@@ -0,0 +1,72 @@
+//===-- ZipFileResolverTest.cpp -------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "lldb/Host/common/ZipFileResolver.h"
+#include "TestingSupport/SubsystemRAII.h"
+#include "TestingSupport/TestUtilities.h"
+#include "gtest/gtest.h"
+
+using namespace lldb_private;
+using namespace llvm;
+
+namespace {
+class ZipFileResolverTest : public ::testing::Test {
+  SubsystemRAII<FileSystem> subsystems;
+};
+
+std::string TestZipPath() {
+  FileSpec zip_spec(GetInputFilePath("zip-test.zip"));
+  FileSystem::Instance().Resolve(zip_spec);
+  return zip_spec.GetPath();
+}
+} // namespace
+
+TEST_F(ZipFileResolverTest, ResolveSharedLibraryPathWithNormalFile) {
+  const FileSpec file_spec("/system/lib64/libtest.so");
+
+  ZipFileResolver::FileKind file_kind;
+  std::string file_path;
+  lldb::offset_t file_offset;
+  lldb::offset_t file_size;
+  ASSERT_TRUE(ZipFileResolver::ResolveSharedLibraryPath(
+      file_spec, file_kind, file_path, file_offset, file_size));
+
+  EXPECT_EQ(file_kind, ZipFileResolver::FileKind::eFileKindNormal);
+  EXPECT_EQ(file_path, file_spec.GetPath());
+  EXPECT_EQ(file_offset, 0UL);
+  EXPECT_EQ(file_size, 0UL);
+}
+
+TEST_F(ZipFileResolverTest, ResolveSharedLibraryPathWithZipMissing) {
+  const std::string zip_path = TestZipPath();
+  const FileSpec file_spec(zip_path + "!/lib/arm64-v8a/libmissing.so");
+
+  ZipFileResolver::FileKind file_kind;
+  std::string file_path;
+  lldb::offset_t file_offset;
+  lldb::offset_t file_size;
+  ASSERT_FALSE(ZipFileResolver::ResolveSharedLibraryPath(
+      file_spec, file_kind, file_path, file_offset, file_size));
+}
+
+TEST_F(ZipFileResolverTest, ResolveSharedLibraryPathWithZipExisting) {
+  const std::string zip_path = TestZipPath();
+  const FileSpec file_spec(zip_path + "!/lib/arm64-v8a/libzip-test.so");
+
+  ZipFileResolver::FileKind file_kind;
+  std::string file_path;
+  lldb::offset_t file_offset;
+  lldb::offset_t file_size;
+  ASSERT_TRUE(ZipFileResolver::ResolveSharedLibraryPath(
+      file_spec, file_kind, file_path, file_offset, file_size));
+
+  EXPECT_EQ(file_kind, ZipFileResolver::FileKind::eFileKindZip);
+  EXPECT_EQ(file_path, zip_path);
+  EXPECT_EQ(file_offset, 4096UL);
+  EXPECT_EQ(file_size, 3600UL);
+}


        


More information about the lldb-commits mailing list