diff --git a/libunwindstack/Android.bp b/libunwindstack/Android.bp index 9cd3d6571..3695f72f0 100644 --- a/libunwindstack/Android.bp +++ b/libunwindstack/Android.bp @@ -240,6 +240,7 @@ cc_defaults { "tests/files/offline/debug_frame_load_bias_arm/*", "tests/files/offline/eh_frame_bias_x86/*", "tests/files/offline/eh_frame_hdr_begin_x86_64/*", + "tests/files/offline/empty_arm64/*", "tests/files/offline/invalid_elf_offset_arm/*", "tests/files/offline/jit_debug_arm/*", "tests/files/offline/jit_debug_x86/*", diff --git a/libunwindstack/MapInfo.cpp b/libunwindstack/MapInfo.cpp index f2dad843b..31f314434 100644 --- a/libunwindstack/MapInfo.cpp +++ b/libunwindstack/MapInfo.cpp @@ -37,12 +37,12 @@ namespace unwindstack { bool MapInfo::InitFileMemoryFromPreviousReadOnlyMap(MemoryFileAtOffset* memory) { // One last attempt, see if the previous map is read-only with the // same name and stretches across this map. - if (prev_map == nullptr || prev_map->flags != PROT_READ) { + if (prev_real_map == nullptr || prev_real_map->flags != PROT_READ) { return false; } - uint64_t map_size = end - prev_map->end; - if (!memory->Init(name, prev_map->offset, map_size)) { + uint64_t map_size = end - prev_real_map->end; + if (!memory->Init(name, prev_real_map->offset, map_size)) { return false; } @@ -51,12 +51,12 @@ bool MapInfo::InitFileMemoryFromPreviousReadOnlyMap(MemoryFileAtOffset* memory) return false; } - if (!memory->Init(name, prev_map->offset, max_size)) { + if (!memory->Init(name, prev_real_map->offset, max_size)) { return false; } - elf_offset = offset - prev_map->offset; - elf_start_offset = prev_map->offset; + elf_offset = offset - prev_real_map->offset; + elf_start_offset = prev_real_map->offset; return true; } @@ -112,8 +112,8 @@ Memory* MapInfo::GetFileMemory() { // Need to check how to set the elf start offset. If this map is not // the r-x map of a r-- map, then use the real offset value. Otherwise, // use 0. - if (prev_map == nullptr || prev_map->offset != 0 || prev_map->flags != PROT_READ || - prev_map->name != name) { + if (prev_real_map == nullptr || prev_real_map->offset != 0 || + prev_real_map->flags != PROT_READ || prev_real_map->name != name) { elf_start_offset = offset; } return memory.release(); @@ -172,20 +172,20 @@ Memory* MapInfo::CreateMemory(const std::shared_ptr& process_memory) { // doesn't guarantee that this invariant will always be true. However, // if that changes, there is likely something else that will change and // break something. - if (offset == 0 || name.empty() || prev_map == nullptr || prev_map->name != name || - prev_map->offset >= offset) { + if (offset == 0 || name.empty() || prev_real_map == nullptr || prev_real_map->name != name || + prev_real_map->offset >= offset) { return nullptr; } // Make sure that relative pc values are corrected properly. - elf_offset = offset - prev_map->offset; + elf_offset = offset - prev_real_map->offset; // Use this as the elf start offset, otherwise, you always get offsets into // the r-x section, which is not quite the right information. - elf_start_offset = prev_map->offset; + elf_start_offset = prev_real_map->offset; MemoryRanges* ranges = new MemoryRanges; - ranges->Insert( - new MemoryRange(process_memory, prev_map->start, prev_map->end - prev_map->start, 0)); + ranges->Insert(new MemoryRange(process_memory, prev_real_map->start, + prev_real_map->end - prev_real_map->start, 0)); ranges->Insert(new MemoryRange(process_memory, start, end - start, elf_offset)); memory_backed_elf = true; @@ -236,15 +236,15 @@ Elf* MapInfo::GetElf(const std::shared_ptr& process_memory, ArchEnum exp if (!elf->valid()) { elf_start_offset = offset; - } else if (prev_map != nullptr && elf_start_offset != offset && - prev_map->offset == elf_start_offset && prev_map->name == name) { + } else if (prev_real_map != nullptr && elf_start_offset != offset && + prev_real_map->offset == elf_start_offset && prev_real_map->name == name) { // If there is a read-only map then a read-execute map that represents the // same elf object, make sure the previous map is using the same elf // object if it hasn't already been set. - std::lock_guard guard(prev_map->mutex_); - if (prev_map->elf.get() == nullptr) { - prev_map->elf = elf; - prev_map->memory_backed_elf = memory_backed_elf; + std::lock_guard guard(prev_real_map->mutex_); + if (prev_real_map->elf.get() == nullptr) { + prev_real_map->elf = elf; + prev_real_map->memory_backed_elf = memory_backed_elf; } } return elf.get(); diff --git a/libunwindstack/Maps.cpp b/libunwindstack/Maps.cpp index 0ab68dbb5..8f49ad975 100644 --- a/libunwindstack/Maps.cpp +++ b/libunwindstack/Maps.cpp @@ -60,6 +60,8 @@ MapInfo* Maps::Find(uint64_t pc) { } bool Maps::Parse() { + MapInfo* prev_map = nullptr; + MapInfo* prev_real_map = nullptr; return android::procinfo::ReadMapFile( GetMapsFile(), [&](uint64_t start, uint64_t end, uint16_t flags, uint64_t pgoff, ino_t, const char* name) { @@ -67,17 +69,24 @@ bool Maps::Parse() { if (strncmp(name, "/dev/", 5) == 0 && strncmp(name + 5, "ashmem/", 7) != 0) { flags |= unwindstack::MAPS_FLAGS_DEVICE_MAP; } - maps_.emplace_back( - new MapInfo(maps_.empty() ? nullptr : maps_.back().get(), start, end, pgoff, - flags, name)); + maps_.emplace_back(new MapInfo(prev_map, prev_real_map, start, end, pgoff, flags, name)); + prev_map = maps_.back().get(); + if (!prev_map->IsBlank()) { + prev_real_map = prev_map; + } }); } void Maps::Add(uint64_t start, uint64_t end, uint64_t offset, uint64_t flags, const std::string& name, uint64_t load_bias) { + MapInfo* prev_map = maps_.empty() ? nullptr : maps_.back().get(); + MapInfo* prev_real_map = prev_map; + while (prev_real_map != nullptr && prev_real_map->IsBlank()) { + prev_real_map = prev_real_map->prev_map; + } + auto map_info = - std::make_unique(maps_.empty() ? nullptr : maps_.back().get(), start, end, offset, - flags, name); + std::make_unique(prev_map, prev_real_map, start, end, offset, flags, name); map_info->load_bias = load_bias; maps_.emplace_back(std::move(map_info)); } @@ -89,14 +98,21 @@ void Maps::Sort() { // Set the prev_map values on the info objects. MapInfo* prev_map = nullptr; + MapInfo* prev_real_map = nullptr; for (const auto& map_info : maps_) { map_info->prev_map = prev_map; + map_info->prev_real_map = prev_real_map; prev_map = map_info.get(); + if (!prev_map->IsBlank()) { + prev_real_map = prev_map; + } } } bool BufferMaps::Parse() { std::string content(buffer_); + MapInfo* prev_map = nullptr; + MapInfo* prev_real_map = nullptr; return android::procinfo::ReadMapFileContent( &content[0], [&](uint64_t start, uint64_t end, uint16_t flags, uint64_t pgoff, ino_t, const char* name) { @@ -104,9 +120,11 @@ bool BufferMaps::Parse() { if (strncmp(name, "/dev/", 5) == 0 && strncmp(name + 5, "ashmem/", 7) != 0) { flags |= unwindstack::MAPS_FLAGS_DEVICE_MAP; } - maps_.emplace_back( - new MapInfo(maps_.empty() ? nullptr : maps_.back().get(), start, end, pgoff, - flags, name)); + maps_.emplace_back(new MapInfo(prev_map, prev_real_map, start, end, pgoff, flags, name)); + prev_map = maps_.back().get(); + if (!prev_map->IsBlank()) { + prev_real_map = prev_map; + } }); } diff --git a/libunwindstack/include/unwindstack/MapInfo.h b/libunwindstack/include/unwindstack/MapInfo.h index 8f0c5160b..052e79fe1 100644 --- a/libunwindstack/include/unwindstack/MapInfo.h +++ b/libunwindstack/include/unwindstack/MapInfo.h @@ -31,24 +31,26 @@ namespace unwindstack { class MemoryFileAtOffset; struct MapInfo { - MapInfo(MapInfo* map_info, uint64_t start, uint64_t end, uint64_t offset, uint64_t flags, - const char* name) + MapInfo(MapInfo* prev_map, MapInfo* prev_real_map, uint64_t start, uint64_t end, uint64_t offset, + uint64_t flags, const char* name) : start(start), end(end), offset(offset), flags(flags), name(name), - prev_map(map_info), + prev_map(prev_map), + prev_real_map(prev_real_map), load_bias(INT64_MAX), build_id(0) {} - MapInfo(MapInfo* map_info, uint64_t start, uint64_t end, uint64_t offset, uint64_t flags, - const std::string& name) + MapInfo(MapInfo* prev_map, MapInfo* prev_real_map, uint64_t start, uint64_t end, uint64_t offset, + uint64_t flags, const std::string& name) : start(start), end(end), offset(offset), flags(flags), name(name), - prev_map(map_info), + prev_map(prev_map), + prev_real_map(prev_real_map), load_bias(INT64_MAX), build_id(0) {} ~MapInfo(); @@ -71,6 +73,14 @@ struct MapInfo { uint64_t elf_start_offset = 0; MapInfo* prev_map = nullptr; + // This is the previous map that is not empty with a 0 offset. For + // example, this set of maps: + // 1000-2000 r--p 000000 00:00 0 libc.so + // 2000-3000 ---p 000000 00:00 0 libc.so + // 3000-4000 r-xp 003000 00:00 0 libc.so + // The last map's prev_map would point to the 2000-3000 map, while the + // prev_real_map would point to the 1000-2000 map. + MapInfo* prev_real_map = nullptr; std::atomic_int64_t load_bias; @@ -97,6 +107,8 @@ struct MapInfo { // Returns the printable version of the build id (hex dump of raw data). std::string GetPrintableBuildID(); + inline bool IsBlank() { return offset == 0 && flags == 0 && name.empty(); } + private: MapInfo(const MapInfo&) = delete; void operator=(const MapInfo&) = delete; diff --git a/libunwindstack/tests/DexFileTest.cpp b/libunwindstack/tests/DexFileTest.cpp index 0149a42c7..1b54da6fb 100644 --- a/libunwindstack/tests/DexFileTest.cpp +++ b/libunwindstack/tests/DexFileTest.cpp @@ -105,7 +105,7 @@ TEST(DexFileTest, create_using_file) { static_cast(TEMP_FAILURE_RETRY(write(tf.fd, kDexData, sizeof(kDexData))))); MemoryFake memory; - MapInfo info(nullptr, 0, 0x10000, 0, 0x5, tf.path); + MapInfo info(nullptr, nullptr, 0, 0x10000, 0, 0x5, tf.path); EXPECT_TRUE(DexFile::Create(0x500, &memory, &info) != nullptr); } @@ -118,7 +118,7 @@ TEST(DexFileTest, create_using_file_non_zero_start) { static_cast(TEMP_FAILURE_RETRY(write(tf.fd, kDexData, sizeof(kDexData))))); MemoryFake memory; - MapInfo info(nullptr, 0x100, 0x10000, 0, 0x5, tf.path); + MapInfo info(nullptr, nullptr, 0x100, 0x10000, 0, 0x5, tf.path); EXPECT_TRUE(DexFile::Create(0x600, &memory, &info) != nullptr); } @@ -131,21 +131,21 @@ TEST(DexFileTest, create_using_file_non_zero_offset) { static_cast(TEMP_FAILURE_RETRY(write(tf.fd, kDexData, sizeof(kDexData))))); MemoryFake memory; - MapInfo info(nullptr, 0x100, 0x10000, 0x200, 0x5, tf.path); + MapInfo info(nullptr, nullptr, 0x100, 0x10000, 0x200, 0x5, tf.path); EXPECT_TRUE(DexFile::Create(0x400, &memory, &info) != nullptr); } TEST(DexFileTest, create_using_memory_empty_file) { MemoryFake memory; memory.SetMemory(0x4000, kDexData, sizeof(kDexData)); - MapInfo info(nullptr, 0x100, 0x10000, 0x200, 0x5, ""); + MapInfo info(nullptr, nullptr, 0x100, 0x10000, 0x200, 0x5, ""); EXPECT_TRUE(DexFile::Create(0x4000, &memory, &info) != nullptr); } TEST(DexFileTest, create_using_memory_file_does_not_exist) { MemoryFake memory; memory.SetMemory(0x4000, kDexData, sizeof(kDexData)); - MapInfo info(nullptr, 0x100, 0x10000, 0x200, 0x5, "/does/not/exist"); + MapInfo info(nullptr, nullptr, 0x100, 0x10000, 0x200, 0x5, "/does/not/exist"); EXPECT_TRUE(DexFile::Create(0x4000, &memory, &info) != nullptr); } @@ -158,7 +158,7 @@ TEST(DexFileTest, create_using_memory_file_is_malformed) { MemoryFake memory; memory.SetMemory(0x4000, kDexData, sizeof(kDexData)); - MapInfo info(nullptr, 0x4000, 0x10000, 0x200, 0x5, "/does/not/exist"); + MapInfo info(nullptr, nullptr, 0x4000, 0x10000, 0x200, 0x5, "/does/not/exist"); std::unique_ptr dex_file = DexFile::Create(0x4000, &memory, &info); ASSERT_TRUE(dex_file != nullptr); @@ -171,7 +171,7 @@ TEST(DexFileTest, create_using_memory_file_is_malformed) { TEST(DexFileTest, get_method) { MemoryFake memory; memory.SetMemory(0x4000, kDexData, sizeof(kDexData)); - MapInfo info(nullptr, 0x100, 0x10000, 0x200, 0x5, ""); + MapInfo info(nullptr, nullptr, 0x100, 0x10000, 0x200, 0x5, ""); std::unique_ptr dex_file(DexFile::Create(0x4000, &memory, &info)); ASSERT_TRUE(dex_file != nullptr); @@ -189,7 +189,7 @@ TEST(DexFileTest, get_method) { TEST(DexFileTest, get_method_empty) { MemoryFake memory; memory.SetMemory(0x4000, kDexData, sizeof(kDexData)); - MapInfo info(nullptr, 0x100, 0x10000, 0x200, 0x5, ""); + MapInfo info(nullptr, nullptr, 0x100, 0x10000, 0x200, 0x5, ""); std::unique_ptr dex_file(DexFile::Create(0x4000, &memory, &info)); ASSERT_TRUE(dex_file != nullptr); diff --git a/libunwindstack/tests/ElfCacheTest.cpp b/libunwindstack/tests/ElfCacheTest.cpp index 573585837..5f135467d 100644 --- a/libunwindstack/tests/ElfCacheTest.cpp +++ b/libunwindstack/tests/ElfCacheTest.cpp @@ -78,8 +78,8 @@ void ElfCacheTest::VerifySameMap(bool cache_enabled) { uint64_t start = 0x1000; uint64_t end = 0x20000; - MapInfo info1(nullptr, start, end, 0, 0x5, tf.path); - MapInfo info2(nullptr, start, end, 0, 0x5, tf.path); + MapInfo info1(nullptr, nullptr, start, end, 0, 0x5, tf.path); + MapInfo info2(nullptr, nullptr, start, end, 0, 0x5, tf.path); Elf* elf1 = info1.GetElf(memory_, ARCH_ARM); ASSERT_TRUE(elf1->valid()); @@ -119,17 +119,17 @@ void ElfCacheTest::VerifyWithinSameMap(bool cache_enabled) { uint64_t start = 0x1000; uint64_t end = 0x20000; // Will have an elf at offset 0 in file. - MapInfo info0_1(nullptr, start, end, 0, 0x5, tf.path); - MapInfo info0_2(nullptr, start, end, 0, 0x5, tf.path); + MapInfo info0_1(nullptr, nullptr, start, end, 0, 0x5, tf.path); + MapInfo info0_2(nullptr, nullptr, start, end, 0, 0x5, tf.path); // Will have an elf at offset 0x100 in file. - MapInfo info100_1(nullptr, start, end, 0x100, 0x5, tf.path); - MapInfo info100_2(nullptr, start, end, 0x100, 0x5, tf.path); + MapInfo info100_1(nullptr, nullptr, start, end, 0x100, 0x5, tf.path); + MapInfo info100_2(nullptr, nullptr, start, end, 0x100, 0x5, tf.path); // Will have an elf at offset 0x200 in file. - MapInfo info200_1(nullptr, start, end, 0x200, 0x5, tf.path); - MapInfo info200_2(nullptr, start, end, 0x200, 0x5, tf.path); + MapInfo info200_1(nullptr, nullptr, start, end, 0x200, 0x5, tf.path); + MapInfo info200_2(nullptr, nullptr, start, end, 0x200, 0x5, tf.path); // Will have an elf at offset 0 in file. - MapInfo info300_1(nullptr, start, end, 0x300, 0x5, tf.path); - MapInfo info300_2(nullptr, start, end, 0x300, 0x5, tf.path); + MapInfo info300_1(nullptr, nullptr, start, end, 0x300, 0x5, tf.path); + MapInfo info300_2(nullptr, nullptr, start, end, 0x300, 0x5, tf.path); Elf* elf0_1 = info0_1.GetElf(memory_, ARCH_ARM); ASSERT_TRUE(elf0_1->valid()); @@ -216,10 +216,10 @@ void ElfCacheTest::VerifyWithinSameMapNeverReadAtZero(bool cache_enabled) { uint64_t start = 0x1000; uint64_t end = 0x20000; // Multiple info sections at different offsets will have non-zero elf offsets. - MapInfo info300_1(nullptr, start, end, 0x300, 0x5, tf.path); - MapInfo info300_2(nullptr, start, end, 0x300, 0x5, tf.path); - MapInfo info400_1(nullptr, start, end, 0x400, 0x5, tf.path); - MapInfo info400_2(nullptr, start, end, 0x400, 0x5, tf.path); + MapInfo info300_1(nullptr, nullptr, start, end, 0x300, 0x5, tf.path); + MapInfo info300_2(nullptr, nullptr, start, end, 0x300, 0x5, tf.path); + MapInfo info400_1(nullptr, nullptr, start, end, 0x400, 0x5, tf.path); + MapInfo info400_2(nullptr, nullptr, start, end, 0x400, 0x5, tf.path); Elf* elf300_1 = info300_1.GetElf(memory_, ARCH_ARM); ASSERT_TRUE(elf300_1->valid()); diff --git a/libunwindstack/tests/ElfTest.cpp b/libunwindstack/tests/ElfTest.cpp index 4866345b3..1f3ed8190 100644 --- a/libunwindstack/tests/ElfTest.cpp +++ b/libunwindstack/tests/ElfTest.cpp @@ -275,7 +275,7 @@ TEST_F(ElfTest, rel_pc) { elf.FakeSetInterface(interface); elf.FakeSetValid(true); - MapInfo map_info(nullptr, 0x1000, 0x2000, 0, 0, ""); + MapInfo map_info(nullptr, nullptr, 0x1000, 0x2000, 0, 0, ""); ASSERT_EQ(0x101U, elf.GetRelPc(0x1101, &map_info)); diff --git a/libunwindstack/tests/MapInfoCreateMemoryTest.cpp b/libunwindstack/tests/MapInfoCreateMemoryTest.cpp index 6c1cfa222..6d8d58e55 100644 --- a/libunwindstack/tests/MapInfoCreateMemoryTest.cpp +++ b/libunwindstack/tests/MapInfoCreateMemoryTest.cpp @@ -89,7 +89,7 @@ class MapInfoCreateMemoryTest : public ::testing::Test { }; TEST_F(MapInfoCreateMemoryTest, end_le_start) { - MapInfo info(nullptr, 0x100, 0x100, 0, 0, elf_.path); + MapInfo info(nullptr, nullptr, 0x100, 0x100, 0, 0, elf_.path); std::unique_ptr memory(info.CreateMemory(process_memory_)); ASSERT_TRUE(memory.get() == nullptr); @@ -108,7 +108,7 @@ TEST_F(MapInfoCreateMemoryTest, end_le_start) { // Verify that if the offset is non-zero but there is no elf at the offset, // that the full file is used. TEST_F(MapInfoCreateMemoryTest, file_backed_non_zero_offset_full_file) { - MapInfo info(nullptr, 0x100, 0x200, 0x100, 0, elf_.path); + MapInfo info(nullptr, nullptr, 0x100, 0x200, 0x100, 0, elf_.path); std::unique_ptr memory(info.CreateMemory(process_memory_)); ASSERT_TRUE(memory.get() != nullptr); @@ -129,8 +129,9 @@ TEST_F(MapInfoCreateMemoryTest, file_backed_non_zero_offset_full_file) { // Now verify the elf start offset is set correctly based on the previous // info. - MapInfo prev_info(nullptr, 0, 0x100, 0x10, 0, ""); + MapInfo prev_info(nullptr, nullptr, 0, 0x100, 0x10, 0, ""); info.prev_map = &prev_info; + info.prev_real_map = &prev_info; // No preconditions met, change each one until it should set the elf start // offset to zero. @@ -177,7 +178,7 @@ TEST_F(MapInfoCreateMemoryTest, file_backed_non_zero_offset_full_file) { // Verify that if the offset is non-zero and there is an elf at that // offset, that only part of the file is used. TEST_F(MapInfoCreateMemoryTest, file_backed_non_zero_offset_partial_file) { - MapInfo info(nullptr, 0x100, 0x200, 0x1000, 0, elf_at_1000_.path); + MapInfo info(nullptr, nullptr, 0x100, 0x200, 0x1000, 0, elf_at_1000_.path); std::unique_ptr memory(info.CreateMemory(process_memory_)); ASSERT_TRUE(memory.get() != nullptr); @@ -202,7 +203,7 @@ TEST_F(MapInfoCreateMemoryTest, file_backed_non_zero_offset_partial_file) { // embedded elf is bigger than the initial map, the new object is larger // than the original map size. Do this for a 32 bit elf and a 64 bit elf. TEST_F(MapInfoCreateMemoryTest, file_backed_non_zero_offset_partial_file_whole_elf32) { - MapInfo info(nullptr, 0x5000, 0x6000, 0x1000, 0, elf32_at_map_.path); + MapInfo info(nullptr, nullptr, 0x5000, 0x6000, 0x1000, 0, elf32_at_map_.path); std::unique_ptr memory(info.CreateMemory(process_memory_)); ASSERT_TRUE(memory.get() != nullptr); @@ -220,7 +221,7 @@ TEST_F(MapInfoCreateMemoryTest, file_backed_non_zero_offset_partial_file_whole_e } TEST_F(MapInfoCreateMemoryTest, file_backed_non_zero_offset_partial_file_whole_elf64) { - MapInfo info(nullptr, 0x7000, 0x8000, 0x2000, 0, elf64_at_map_.path); + MapInfo info(nullptr, nullptr, 0x7000, 0x8000, 0x2000, 0, elf64_at_map_.path); std::unique_ptr memory(info.CreateMemory(process_memory_)); ASSERT_TRUE(memory.get() != nullptr); @@ -243,14 +244,14 @@ TEST_F(MapInfoCreateMemoryTest, check_device_maps) { // be returned if the file mapping fails, but the device check is incorrect. std::vector buffer(1024); uint64_t start = reinterpret_cast(buffer.data()); - MapInfo info(nullptr, start, start + buffer.size(), 0, 0x8000, "/dev/something"); + MapInfo info(nullptr, nullptr, start, start + buffer.size(), 0, 0x8000, "/dev/something"); std::unique_ptr memory(info.CreateMemory(process_memory_)); ASSERT_TRUE(memory.get() == nullptr); } TEST_F(MapInfoCreateMemoryTest, process_memory) { - MapInfo info(nullptr, 0x2000, 0x3000, 0, PROT_READ, ""); + MapInfo info(nullptr, nullptr, 0x2000, 0x3000, 0, PROT_READ, ""); Elf32_Ehdr ehdr = {}; TestInitEhdr(&ehdr, ELFCLASS32, EM_ARM); diff --git a/libunwindstack/tests/MapInfoGetBuildIDTest.cpp b/libunwindstack/tests/MapInfoGetBuildIDTest.cpp index 16451d115..6953e26f9 100644 --- a/libunwindstack/tests/MapInfoGetBuildIDTest.cpp +++ b/libunwindstack/tests/MapInfoGetBuildIDTest.cpp @@ -50,7 +50,8 @@ class MapInfoGetBuildIDTest : public ::testing::Test { elf_interface_ = new ElfInterfaceFake(memory_); elf_->FakeSetInterface(elf_interface_); elf_container_.reset(elf_); - map_info_.reset(new MapInfo(nullptr, 0x1000, 0x20000, 0, PROT_READ | PROT_WRITE, tf_->path)); + map_info_.reset( + new MapInfo(nullptr, nullptr, 0x1000, 0x20000, 0, PROT_READ | PROT_WRITE, tf_->path)); } void MultipleThreadTest(std::string expected_build_id); @@ -64,7 +65,7 @@ class MapInfoGetBuildIDTest : public ::testing::Test { }; TEST_F(MapInfoGetBuildIDTest, no_elf_and_no_valid_elf_in_memory) { - MapInfo info(nullptr, 0x1000, 0x2000, 0, PROT_READ, ""); + MapInfo info(nullptr, nullptr, 0x1000, 0x2000, 0, PROT_READ, ""); EXPECT_EQ("", info.GetBuildID()); EXPECT_EQ("", info.GetPrintableBuildID()); diff --git a/libunwindstack/tests/MapInfoGetElfTest.cpp b/libunwindstack/tests/MapInfoGetElfTest.cpp index d60b8b1af..7f9781491 100644 --- a/libunwindstack/tests/MapInfoGetElfTest.cpp +++ b/libunwindstack/tests/MapInfoGetElfTest.cpp @@ -68,7 +68,7 @@ class MapInfoGetElfTest : public ::testing::Test { }; TEST_F(MapInfoGetElfTest, invalid) { - MapInfo info(nullptr, 0x1000, 0x2000, 0, PROT_READ, ""); + MapInfo info(nullptr, nullptr, 0x1000, 0x2000, 0, PROT_READ, ""); // The map is empty, but this should still create an invalid elf object. Elf* elf = info.GetElf(process_memory_, ARCH_ARM); @@ -77,7 +77,7 @@ TEST_F(MapInfoGetElfTest, invalid) { } TEST_F(MapInfoGetElfTest, valid32) { - MapInfo info(nullptr, 0x3000, 0x4000, 0, PROT_READ, ""); + MapInfo info(nullptr, nullptr, 0x3000, 0x4000, 0, PROT_READ, ""); Elf32_Ehdr ehdr; TestInitEhdr(&ehdr, ELFCLASS32, EM_ARM); @@ -97,7 +97,7 @@ TEST_F(MapInfoGetElfTest, valid32) { } TEST_F(MapInfoGetElfTest, valid64) { - MapInfo info(nullptr, 0x8000, 0x9000, 0, PROT_READ, ""); + MapInfo info(nullptr, nullptr, 0x8000, 0x9000, 0, PROT_READ, ""); Elf64_Ehdr ehdr; TestInitEhdr(&ehdr, ELFCLASS64, EM_AARCH64); @@ -111,7 +111,7 @@ TEST_F(MapInfoGetElfTest, valid64) { } TEST_F(MapInfoGetElfTest, invalid_arch_mismatch) { - MapInfo info(nullptr, 0x3000, 0x4000, 0, PROT_READ, ""); + MapInfo info(nullptr, nullptr, 0x3000, 0x4000, 0, PROT_READ, ""); Elf32_Ehdr ehdr; TestInitEhdr(&ehdr, ELFCLASS32, EM_ARM); @@ -123,7 +123,7 @@ TEST_F(MapInfoGetElfTest, invalid_arch_mismatch) { } TEST_F(MapInfoGetElfTest, gnu_debugdata_init32) { - MapInfo info(nullptr, 0x2000, 0x3000, 0, PROT_READ, ""); + MapInfo info(nullptr, nullptr, 0x2000, 0x3000, 0, PROT_READ, ""); TestInitGnuDebugdata(ELFCLASS32, EM_ARM, true, [&](uint64_t offset, const void* ptr, size_t size) { @@ -139,7 +139,7 @@ TEST_F(MapInfoGetElfTest, gnu_debugdata_init32) { } TEST_F(MapInfoGetElfTest, gnu_debugdata_init64) { - MapInfo info(nullptr, 0x5000, 0x8000, 0, PROT_READ, ""); + MapInfo info(nullptr, nullptr, 0x5000, 0x8000, 0, PROT_READ, ""); TestInitGnuDebugdata(ELFCLASS64, EM_AARCH64, true, [&](uint64_t offset, const void* ptr, size_t size) { @@ -155,7 +155,7 @@ TEST_F(MapInfoGetElfTest, gnu_debugdata_init64) { } TEST_F(MapInfoGetElfTest, end_le_start) { - MapInfo info(nullptr, 0x1000, 0x1000, 0, PROT_READ, elf_.path); + MapInfo info(nullptr, nullptr, 0x1000, 0x1000, 0, PROT_READ, elf_.path); Elf32_Ehdr ehdr; TestInitEhdr(&ehdr, ELFCLASS32, EM_ARM); @@ -182,7 +182,7 @@ TEST_F(MapInfoGetElfTest, end_le_start) { // Verify that if the offset is non-zero but there is no elf at the offset, // that the full file is used. TEST_F(MapInfoGetElfTest, file_backed_non_zero_offset_full_file) { - MapInfo info(nullptr, 0x1000, 0x2000, 0x100, PROT_READ, elf_.path); + MapInfo info(nullptr, nullptr, 0x1000, 0x2000, 0x100, PROT_READ, elf_.path); std::vector buffer(0x1000); memset(buffer.data(), 0, buffer.size()); @@ -211,7 +211,7 @@ TEST_F(MapInfoGetElfTest, file_backed_non_zero_offset_full_file) { // Verify that if the offset is non-zero and there is an elf at that // offset, that only part of the file is used. TEST_F(MapInfoGetElfTest, file_backed_non_zero_offset_partial_file) { - MapInfo info(nullptr, 0x1000, 0x2000, 0x2000, PROT_READ, elf_.path); + MapInfo info(nullptr, nullptr, 0x1000, 0x2000, 0x2000, PROT_READ, elf_.path); std::vector buffer(0x4000); memset(buffer.data(), 0, buffer.size()); @@ -241,7 +241,7 @@ TEST_F(MapInfoGetElfTest, file_backed_non_zero_offset_partial_file) { // embedded elf is bigger than the initial map, the new object is larger // than the original map size. Do this for a 32 bit elf and a 64 bit elf. TEST_F(MapInfoGetElfTest, file_backed_non_zero_offset_partial_file_whole_elf32) { - MapInfo info(nullptr, 0x5000, 0x6000, 0x1000, PROT_READ, elf_.path); + MapInfo info(nullptr, nullptr, 0x5000, 0x6000, 0x1000, PROT_READ, elf_.path); std::vector buffer(0x4000); memset(buffer.data(), 0, buffer.size()); @@ -269,7 +269,7 @@ TEST_F(MapInfoGetElfTest, file_backed_non_zero_offset_partial_file_whole_elf32) } TEST_F(MapInfoGetElfTest, file_backed_non_zero_offset_partial_file_whole_elf64) { - MapInfo info(nullptr, 0x7000, 0x8000, 0x1000, PROT_READ, elf_.path); + MapInfo info(nullptr, nullptr, 0x7000, 0x8000, 0x1000, PROT_READ, elf_.path); std::vector buffer(0x4000); memset(buffer.data(), 0, buffer.size()); @@ -297,7 +297,7 @@ TEST_F(MapInfoGetElfTest, file_backed_non_zero_offset_partial_file_whole_elf64) } TEST_F(MapInfoGetElfTest, check_device_maps) { - MapInfo info(nullptr, 0x7000, 0x8000, 0x1000, PROT_READ | MAPS_FLAGS_DEVICE_MAP, + MapInfo info(nullptr, nullptr, 0x7000, 0x8000, 0x1000, PROT_READ | MAPS_FLAGS_DEVICE_MAP, "/dev/something"); // Create valid elf data in process memory for this to verify that only @@ -343,7 +343,7 @@ TEST_F(MapInfoGetElfTest, multiple_thread_get_elf) { wait = true; // Create all of the threads and have them do the GetElf at the same time // to make it likely that a race will occur. - MapInfo info(nullptr, 0x7000, 0x8000, 0x1000, PROT_READ, ""); + MapInfo info(nullptr, nullptr, 0x7000, 0x8000, 0x1000, PROT_READ, ""); for (size_t i = 0; i < kNumConcurrentThreads; i++) { std::thread* thread = new std::thread([i, this, &wait, &info, &elf_in_threads]() { while (wait) @@ -373,8 +373,8 @@ TEST_F(MapInfoGetElfTest, multiple_thread_get_elf) { // Verify that previous maps don't automatically get the same elf object. TEST_F(MapInfoGetElfTest, prev_map_elf_not_set) { - MapInfo info1(nullptr, 0x1000, 0x2000, 0, PROT_READ, "/not/present"); - MapInfo info2(&info1, 0x2000, 0x3000, 0, PROT_READ, elf_.path); + MapInfo info1(nullptr, nullptr, 0x1000, 0x2000, 0, PROT_READ, "/not/present"); + MapInfo info2(&info1, &info1, 0x2000, 0x3000, 0, PROT_READ, elf_.path); Elf32_Ehdr ehdr; TestInitEhdr(&ehdr, ELFCLASS32, EM_ARM); @@ -389,8 +389,25 @@ TEST_F(MapInfoGetElfTest, prev_map_elf_not_set) { // Verify that a read-only map followed by a read-execute map will result // in the same elf object in both maps. TEST_F(MapInfoGetElfTest, read_only_followed_by_read_exec_share_elf) { - MapInfo r_info(nullptr, 0x1000, 0x2000, 0, PROT_READ, elf_.path); - MapInfo rw_info(&r_info, 0x2000, 0x3000, 0x1000, PROT_READ | PROT_EXEC, elf_.path); + MapInfo r_info(nullptr, nullptr, 0x1000, 0x2000, 0, PROT_READ, elf_.path); + MapInfo rw_info(&r_info, &r_info, 0x2000, 0x3000, 0x1000, PROT_READ | PROT_EXEC, elf_.path); + + Elf32_Ehdr ehdr; + TestInitEhdr(&ehdr, ELFCLASS32, EM_ARM); + memory_->SetMemory(0x1000, &ehdr, sizeof(ehdr)); + Elf* elf = rw_info.GetElf(process_memory_, ARCH_ARM); + ASSERT_TRUE(elf != nullptr); + ASSERT_TRUE(elf->valid()); + + ASSERT_EQ(elf, r_info.GetElf(process_memory_, ARCH_ARM)); +} + +// Verify that a read-only map followed by an empty map, then followed by +// a read-execute map will result in the same elf object in both maps. +TEST_F(MapInfoGetElfTest, read_only_followed_by_empty_then_read_exec_share_elf) { + MapInfo r_info(nullptr, nullptr, 0x1000, 0x2000, 0, PROT_READ, elf_.path); + MapInfo empty(&r_info, &r_info, 0x2000, 0x3000, 0, 0, ""); + MapInfo rw_info(&empty, &r_info, 0x3000, 0x4000, 0x2000, PROT_READ | PROT_EXEC, elf_.path); Elf32_Ehdr ehdr; TestInitEhdr(&ehdr, ELFCLASS32, EM_ARM); diff --git a/libunwindstack/tests/MapInfoGetLoadBiasTest.cpp b/libunwindstack/tests/MapInfoGetLoadBiasTest.cpp index da3dbf2ee..971d4520e 100644 --- a/libunwindstack/tests/MapInfoGetLoadBiasTest.cpp +++ b/libunwindstack/tests/MapInfoGetLoadBiasTest.cpp @@ -50,7 +50,7 @@ class MapInfoGetLoadBiasTest : public ::testing::Test { process_memory_.reset(memory_); elf_ = new ElfFake(new MemoryFake); elf_container_.reset(elf_); - map_info_.reset(new MapInfo(nullptr, 0x1000, 0x20000, 0, PROT_READ | PROT_WRITE, "")); + map_info_.reset(new MapInfo(nullptr, nullptr, 0x1000, 0x20000, 0, PROT_READ | PROT_WRITE, "")); } void MultipleThreadTest(uint64_t expected_load_bias); @@ -63,7 +63,7 @@ class MapInfoGetLoadBiasTest : public ::testing::Test { }; TEST_F(MapInfoGetLoadBiasTest, no_elf_and_no_valid_elf_in_memory) { - MapInfo info(nullptr, 0x1000, 0x2000, 0, PROT_READ, ""); + MapInfo info(nullptr, nullptr, 0x1000, 0x2000, 0, PROT_READ, ""); EXPECT_EQ(0U, info.GetLoadBias(process_memory_)); } diff --git a/libunwindstack/tests/MapInfoTest.cpp b/libunwindstack/tests/MapInfoTest.cpp index ef76b1b11..98edc0e7c 100644 --- a/libunwindstack/tests/MapInfoTest.cpp +++ b/libunwindstack/tests/MapInfoTest.cpp @@ -26,8 +26,8 @@ namespace unwindstack { TEST(MapInfoTest, maps_constructor_const_char) { - MapInfo prev_map(nullptr, 0, 0, 0, 0, ""); - MapInfo map_info(&prev_map, 1, 2, 3, 4, "map"); + MapInfo prev_map(nullptr, nullptr, 0, 0, 0, 0, ""); + MapInfo map_info(&prev_map, &prev_map, 1, 2, 3, 4, "map"); EXPECT_EQ(&prev_map, map_info.prev_map); EXPECT_EQ(1UL, map_info.start); @@ -42,8 +42,8 @@ TEST(MapInfoTest, maps_constructor_const_char) { TEST(MapInfoTest, maps_constructor_string) { std::string name("string_map"); - MapInfo prev_map(nullptr, 0, 0, 0, 0, ""); - MapInfo map_info(&prev_map, 1, 2, 3, 4, name); + MapInfo prev_map(nullptr, nullptr, 0, 0, 0, 0, ""); + MapInfo map_info(&prev_map, &prev_map, 1, 2, 3, 4, name); EXPECT_EQ(&prev_map, map_info.prev_map); EXPECT_EQ(1UL, map_info.start); @@ -62,7 +62,7 @@ TEST(MapInfoTest, get_function_name) { elf->FakeSetInterface(interface); interface->FakePushFunctionData(FunctionData("function", 1000)); - MapInfo map_info(nullptr, 1, 2, 3, 4, ""); + MapInfo map_info(nullptr, nullptr, 1, 2, 3, 4, ""); map_info.elf.reset(elf); std::string name; diff --git a/libunwindstack/tests/MapsTest.cpp b/libunwindstack/tests/MapsTest.cpp index 9e7a6abc2..724eeb513 100644 --- a/libunwindstack/tests/MapsTest.cpp +++ b/libunwindstack/tests/MapsTest.cpp @@ -82,7 +82,7 @@ TEST(MapsTest, map_move) { } TEST(MapsTest, verify_parse_line) { - MapInfo info(nullptr, 0, 0, 0, 0, ""); + MapInfo info(nullptr, nullptr, 0, 0, 0, 0, ""); VerifyLine("01-02 rwxp 03 04:05 06\n", &info); EXPECT_EQ(1U, info.start); @@ -155,7 +155,7 @@ TEST(MapsTest, verify_parse_line) { } TEST(MapsTest, verify_large_values) { - MapInfo info(nullptr, 0, 0, 0, 0, ""); + MapInfo info(nullptr, nullptr, 0, 0, 0, 0, ""); #if defined(__LP64__) VerifyLine("fabcdef012345678-f12345678abcdef8 rwxp f0b0d0f010305070 00:00 0\n", &info); EXPECT_EQ(0xfabcdef012345678UL, info.start); diff --git a/libunwindstack/tests/RegsTest.cpp b/libunwindstack/tests/RegsTest.cpp index 472d1cfa4..0a33e2ff4 100644 --- a/libunwindstack/tests/RegsTest.cpp +++ b/libunwindstack/tests/RegsTest.cpp @@ -182,7 +182,7 @@ TEST_F(RegsTest, elf_invalid) { RegsX86_64 regs_x86_64; RegsMips regs_mips; RegsMips64 regs_mips64; - MapInfo map_info(nullptr, 0x1000, 0x2000, 0, 0, ""); + MapInfo map_info(nullptr, nullptr, 0x1000, 0x2000, 0, 0, ""); Elf* invalid_elf = new Elf(nullptr); map_info.elf.reset(invalid_elf); diff --git a/libunwindstack/tests/UnwindOfflineTest.cpp b/libunwindstack/tests/UnwindOfflineTest.cpp index 364101a04..c2bd8363c 100644 --- a/libunwindstack/tests/UnwindOfflineTest.cpp +++ b/libunwindstack/tests/UnwindOfflineTest.cpp @@ -169,15 +169,18 @@ std::unordered_map UnwindOfflineTest::arm_regs_ = { }; std::unordered_map UnwindOfflineTest::arm64_regs_ = { - {"x0", ARM64_REG_R0}, {"x1", ARM64_REG_R1}, {"x2", ARM64_REG_R2}, {"x3", ARM64_REG_R3}, - {"x4", ARM64_REG_R4}, {"x5", ARM64_REG_R5}, {"x6", ARM64_REG_R6}, {"x7", ARM64_REG_R7}, - {"x8", ARM64_REG_R8}, {"x9", ARM64_REG_R9}, {"x10", ARM64_REG_R10}, {"x11", ARM64_REG_R11}, - {"x12", ARM64_REG_R12}, {"x13", ARM64_REG_R13}, {"x14", ARM64_REG_R14}, {"x15", ARM64_REG_R15}, - {"x16", ARM64_REG_R16}, {"x17", ARM64_REG_R17}, {"x18", ARM64_REG_R18}, {"x19", ARM64_REG_R19}, - {"x20", ARM64_REG_R20}, {"x21", ARM64_REG_R21}, {"x22", ARM64_REG_R22}, {"x23", ARM64_REG_R23}, - {"x24", ARM64_REG_R24}, {"x25", ARM64_REG_R25}, {"x26", ARM64_REG_R26}, {"x27", ARM64_REG_R27}, - {"x28", ARM64_REG_R28}, {"x29", ARM64_REG_R29}, {"sp", ARM64_REG_SP}, {"lr", ARM64_REG_LR}, - {"pc", ARM64_REG_PC}, + {"x0", ARM64_REG_R0}, {"x1", ARM64_REG_R1}, {"x2", ARM64_REG_R2}, + {"x3", ARM64_REG_R3}, {"x4", ARM64_REG_R4}, {"x5", ARM64_REG_R5}, + {"x6", ARM64_REG_R6}, {"x7", ARM64_REG_R7}, {"x8", ARM64_REG_R8}, + {"x9", ARM64_REG_R9}, {"x10", ARM64_REG_R10}, {"x11", ARM64_REG_R11}, + {"x12", ARM64_REG_R12}, {"x13", ARM64_REG_R13}, {"x14", ARM64_REG_R14}, + {"x15", ARM64_REG_R15}, {"x16", ARM64_REG_R16}, {"x17", ARM64_REG_R17}, + {"x18", ARM64_REG_R18}, {"x19", ARM64_REG_R19}, {"x20", ARM64_REG_R20}, + {"x21", ARM64_REG_R21}, {"x22", ARM64_REG_R22}, {"x23", ARM64_REG_R23}, + {"x24", ARM64_REG_R24}, {"x25", ARM64_REG_R25}, {"x26", ARM64_REG_R26}, + {"x27", ARM64_REG_R27}, {"x28", ARM64_REG_R28}, {"x29", ARM64_REG_R29}, + {"sp", ARM64_REG_SP}, {"lr", ARM64_REG_LR}, {"pc", ARM64_REG_PC}, + {"pst", ARM64_REG_PSTATE}, }; std::unordered_map UnwindOfflineTest::x86_regs_ = { @@ -1697,4 +1700,40 @@ TEST_F(UnwindOfflineTest, signal_load_bias_arm) { EXPECT_EQ(0xffe67d10ULL, unwinder.frames()[16].sp); } +TEST_F(UnwindOfflineTest, empty_arm64) { + ASSERT_NO_FATAL_FAILURE(Init("empty_arm64/", ARCH_ARM64)); + + Unwinder unwinder(128, maps_.get(), regs_.get(), process_memory_); + unwinder.Unwind(); + + std::string frame_info(DumpFrames(unwinder)); + ASSERT_EQ(7U, unwinder.NumFrames()) << "Unwind:\n" << frame_info; + EXPECT_EQ( + " #00 pc 00000000000963a4 libc.so (__ioctl+4)\n" + " #01 pc 000000000005344c libc.so (ioctl+140)\n" + " #02 pc 0000000000050ce4 libbinder.so " + "(android::IPCThreadState::talkWithDriver(bool)+308)\n" + " #03 pc 0000000000050e98 libbinder.so " + "(android::IPCThreadState::getAndExecuteCommand()+24)\n" + " #04 pc 00000000000516ac libbinder.so (android::IPCThreadState::joinThreadPool(bool)+60)\n" + " #05 pc 00000000000443b0 netd (main+1056)\n" + " #06 pc 0000000000045594 libc.so (__libc_init+108)\n", + frame_info); + + EXPECT_EQ(0x72a02203a4U, unwinder.frames()[0].pc); + EXPECT_EQ(0x7ffb6c0b50U, unwinder.frames()[0].sp); + EXPECT_EQ(0x72a01dd44cU, unwinder.frames()[1].pc); + EXPECT_EQ(0x7ffb6c0b50U, unwinder.frames()[1].sp); + EXPECT_EQ(0x729f759ce4U, unwinder.frames()[2].pc); + EXPECT_EQ(0x7ffb6c0c50U, unwinder.frames()[2].sp); + EXPECT_EQ(0x729f759e98U, unwinder.frames()[3].pc); + EXPECT_EQ(0x7ffb6c0ce0U, unwinder.frames()[3].sp); + EXPECT_EQ(0x729f75a6acU, unwinder.frames()[4].pc); + EXPECT_EQ(0x7ffb6c0d10U, unwinder.frames()[4].sp); + EXPECT_EQ(0x5d478af3b0U, unwinder.frames()[5].pc); + EXPECT_EQ(0x7ffb6c0d40U, unwinder.frames()[5].sp); + EXPECT_EQ(0x72a01cf594U, unwinder.frames()[6].pc); + EXPECT_EQ(0x7ffb6c0f30U, unwinder.frames()[6].sp); +} + } // namespace unwindstack diff --git a/libunwindstack/tests/files/offline/empty_arm64/libbinder.so b/libunwindstack/tests/files/offline/empty_arm64/libbinder.so new file mode 100644 index 000000000..f30384cd0 Binary files /dev/null and b/libunwindstack/tests/files/offline/empty_arm64/libbinder.so differ diff --git a/libunwindstack/tests/files/offline/empty_arm64/libc.so b/libunwindstack/tests/files/offline/empty_arm64/libc.so new file mode 100644 index 000000000..b05dcafde Binary files /dev/null and b/libunwindstack/tests/files/offline/empty_arm64/libc.so differ diff --git a/libunwindstack/tests/files/offline/empty_arm64/maps.txt b/libunwindstack/tests/files/offline/empty_arm64/maps.txt new file mode 100644 index 000000000..edb83c6d1 --- /dev/null +++ b/libunwindstack/tests/files/offline/empty_arm64/maps.txt @@ -0,0 +1,9 @@ +5d4786b000-5d47893000 r--p 0 00:00 0 netd +5d47893000-5d47894000 ---p 0 00:00 0 +5d47894000-5d47901000 --xp 29000 00:00 0 netd +729f709000-729f750000 r--p 0 00:00 0 libbinder.so +729f750000-729f751000 ---p 0 00:00 0 +729f751000-729f794000 --xp 48000 00:00 0 libbinder.so +72a018a000-72a01c2000 r--p 0 00:00 0 libc.so +72a01c2000-72a01c3000 ---p 0 00:00 0 +72a01c3000-72a023b000 --xp 39000 00:00 0 libc.so diff --git a/libunwindstack/tests/files/offline/empty_arm64/netd b/libunwindstack/tests/files/offline/empty_arm64/netd new file mode 100644 index 000000000..8a72e9491 Binary files /dev/null and b/libunwindstack/tests/files/offline/empty_arm64/netd differ diff --git a/libunwindstack/tests/files/offline/empty_arm64/regs.txt b/libunwindstack/tests/files/offline/empty_arm64/regs.txt new file mode 100644 index 000000000..3d4279f7a --- /dev/null +++ b/libunwindstack/tests/files/offline/empty_arm64/regs.txt @@ -0,0 +1,34 @@ +x0: 1d +x1: c0306201 +x2: 7ffb6c0c50 +x3: 0 +x4: 0 +x5: 0 +x6: 0 +x7: 0 +x8: 1d +x9: 7ffb6c0c00 +x10: 7ffb6c0c50 +x11: 7ffb6c0bd0 +x12: ffffff80ffffffd0 +x13: 0 +x14: 72a0240ce2 +x15: 20 +x16: 729f7a54e8 +x17: 72a01dd3c0 +x18: 72a0ac2000 +x19: 72a0666000 +x20: 719769b610 +x21: 719769b730 +x22: c0306201 +x23: fffffff7 +x24: 72a0666000 +x25: 0 +x26: 0 +x27: 0 +x28: 0 +x29: 7ffb6c0c30 +sp: 7ffb6c0b50 +lr: 72a01dd450 +pc: 72a02203a4 +pst: a0000000 diff --git a/libunwindstack/tests/files/offline/empty_arm64/stack.data b/libunwindstack/tests/files/offline/empty_arm64/stack.data new file mode 100644 index 000000000..6d6108c60 Binary files /dev/null and b/libunwindstack/tests/files/offline/empty_arm64/stack.data differ