diff --git a/lldb/include/lldb/Host/common/ZipFileResolver.h b/lldb/include/lldb/Host/common/ZipFileResolver.h new file mode 100644 --- /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 --- /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 --- a/lldb/source/Host/CMakeLists.txt +++ b/lldb/source/Host/CMakeLists.txt @@ -42,6 +42,7 @@ 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 --- /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 --- a/lldb/source/Plugins/Platform/Android/PlatformAndroid.cpp +++ b/lldb/source/Plugins/Platform/Android/PlatformAndroid.cpp @@ -234,10 +234,32 @@ 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 @@ // Create file remover for the temporary directory created on the device std::unique_ptr> - 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 @@ llvm::StringRef PlatformAndroid::GetLibdlFunctionDeclarations(lldb_private::Process *process) { SymbolContextList matching_symbols; - std::vector dl_open_names = { "__dl_dlopen", "dlopen" }; + std::vector 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 --- 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 @@ 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 @@ 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 --- a/lldb/source/Utility/CMakeLists.txt +++ b/lldb/source/Utility/CMakeLists.txt @@ -74,6 +74,7 @@ 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 --- /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::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(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( + 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(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(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(cd + 1); + size_t file_name_length = cd->file_name_length; + if (file_name + file_name_length >= reinterpret_cast(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( + reinterpret_cast(cd) + sizeof(CdRecord) + + cd->file_name_length + cd->extra_field_length + cd->comment_length); + // Sanity check the pointer. + if (reinterpret_cast(cd) >= + reinterpret_cast(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 --- a/lldb/unittests/Host/CMakeLists.txt +++ b/lldb/unittests/Host/CMakeLists.txt @@ -38,3 +38,5 @@ 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 --- /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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 GIT binary patch literal 0 Hc$@ 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); +}