diff --git a/libunwindstack/Android.bp b/libunwindstack/Android.bp index 892fb486a..34bfceff5 100644 --- a/libunwindstack/Android.bp +++ b/libunwindstack/Android.bp @@ -248,6 +248,7 @@ cc_test { "tests/files/elf64.xz", "tests/files/offline/bad_eh_frame_hdr_arm64/*", "tests/files/offline/debug_frame_first_x86/*", + "tests/files/offline/eh_frame_hdr_begin_x86_64/*", "tests/files/offline/jit_debug_arm/*", "tests/files/offline/jit_debug_x86/*", "tests/files/offline/gnu_debugdata_arm/*", @@ -306,6 +307,15 @@ cc_binary { ], } +cc_binary { + name: "unwind_reg_info", + defaults: ["libunwindstack_tools"], + + srcs: [ + "tools/unwind_reg_info.cpp", + ], +} + // Generates the elf data for use in the tests for .gnu_debugdata frames. // Once these files are generated, use the xz command to compress the data. cc_binary_host { diff --git a/libunwindstack/DwarfEhFrameWithHdr.cpp b/libunwindstack/DwarfEhFrameWithHdr.cpp index a131abea2..9a4901344 100644 --- a/libunwindstack/DwarfEhFrameWithHdr.cpp +++ b/libunwindstack/DwarfEhFrameWithHdr.cpp @@ -109,7 +109,7 @@ DwarfEhFrameWithHdr::GetFdeInfoFromIndex(size_t index) { fde_info_.erase(index); return nullptr; } - info->pc = value + 4; + info->pc = value; return info; } diff --git a/libunwindstack/tests/DwarfEhFrameWithHdrTest.cpp b/libunwindstack/tests/DwarfEhFrameWithHdrTest.cpp index a2ae5ebf7..4240419a5 100644 --- a/libunwindstack/tests/DwarfEhFrameWithHdrTest.cpp +++ b/libunwindstack/tests/DwarfEhFrameWithHdrTest.cpp @@ -134,7 +134,7 @@ TYPED_TEST_P(DwarfEhFrameWithHdrTest, GetFdeInfoFromIndex_read_pcrel) { auto info = this->eh_frame_->GetFdeInfoFromIndex(2); ASSERT_TRUE(info != nullptr); - EXPECT_EQ(0x1384U, info->pc); + EXPECT_EQ(0x1380U, info->pc); EXPECT_EQ(0x1540U, info->offset); } @@ -149,7 +149,7 @@ TYPED_TEST_P(DwarfEhFrameWithHdrTest, GetFdeInfoFromIndex_read_datarel) { auto info = this->eh_frame_->GetFdeInfoFromIndex(2); ASSERT_TRUE(info != nullptr); - EXPECT_EQ(0x3344U, info->pc); + EXPECT_EQ(0x3340U, info->pc); EXPECT_EQ(0x3500U, info->offset); } @@ -163,7 +163,7 @@ TYPED_TEST_P(DwarfEhFrameWithHdrTest, GetFdeInfoFromIndex_cached) { auto info = this->eh_frame_->GetFdeInfoFromIndex(2); ASSERT_TRUE(info != nullptr); - EXPECT_EQ(0x344U, info->pc); + EXPECT_EQ(0x340U, info->pc); EXPECT_EQ(0x500U, info->offset); // Clear the memory so that this will fail if it doesn't read cached data. @@ -171,7 +171,7 @@ TYPED_TEST_P(DwarfEhFrameWithHdrTest, GetFdeInfoFromIndex_cached) { info = this->eh_frame_->GetFdeInfoFromIndex(2); ASSERT_TRUE(info != nullptr); - EXPECT_EQ(0x344U, info->pc); + EXPECT_EQ(0x340U, info->pc); EXPECT_EQ(0x500U, info->offset); } diff --git a/libunwindstack/tests/UnwindOfflineTest.cpp b/libunwindstack/tests/UnwindOfflineTest.cpp index e49959369..df262f525 100644 --- a/libunwindstack/tests/UnwindOfflineTest.cpp +++ b/libunwindstack/tests/UnwindOfflineTest.cpp @@ -23,23 +23,154 @@ #include #include +#include #include +#include + #include #include #include #include +#include #include #include #include #include #include +#include #include #include "ElfTestUtils.h" namespace unwindstack { +class UnwindOfflineTest : public ::testing::Test { + protected: + void TearDown() override { + if (cwd_ != nullptr) { + ASSERT_EQ(0, chdir(cwd_)); + } + free(cwd_); + } + + void Init(const char* file_dir, ArchEnum arch) { + dir_ = TestGetFileDirectory() + "offline/" + file_dir; + + std::string data; + ASSERT_TRUE(android::base::ReadFileToString((dir_ + "maps.txt"), &data)); + + maps_.reset(new BufferMaps(data.c_str())); + ASSERT_TRUE(maps_->Parse()); + + std::unique_ptr stack_memory(new MemoryOffline); + ASSERT_TRUE(stack_memory->Init((dir_ + "stack.data").c_str(), 0)); + process_memory_.reset(stack_memory.release()); + + switch (arch) { + case ARCH_ARM: { + RegsArm* regs = new RegsArm; + regs_.reset(regs); + ReadRegs(regs, arm_regs_); + break; + } + case ARCH_ARM64: { + RegsArm64* regs = new RegsArm64; + regs_.reset(regs); + ReadRegs(regs, arm64_regs_); + break; + } + case ARCH_X86: { + RegsX86* regs = new RegsX86; + regs_.reset(regs); + ReadRegs(regs, x86_regs_); + break; + } + case ARCH_X86_64: { + RegsX86_64* regs = new RegsX86_64; + regs_.reset(regs); + ReadRegs(regs, x86_64_regs_); + break; + } + default: + ASSERT_TRUE(false) << "Unknown arch " << std::to_string(arch); + } + cwd_ = getcwd(nullptr, 0); + // Make dir_ an absolute directory. + if (dir_.empty() || dir_[0] != '/') { + dir_ = std::string(cwd_) + '/' + dir_; + } + ASSERT_EQ(0, chdir(dir_.c_str())); + } + + template + void ReadRegs(RegsImpl* regs, + const std::unordered_map& name_to_reg) { + FILE* fp = fopen((dir_ + "regs.txt").c_str(), "r"); + ASSERT_TRUE(fp != nullptr); + while (!feof(fp)) { + uint64_t value; + char reg_name[100]; + ASSERT_EQ(2, fscanf(fp, "%s %" SCNx64 "\n", reg_name, &value)); + std::string name(reg_name); + if (!name.empty()) { + // Remove the : from the end. + name.resize(name.size() - 1); + } + auto entry = name_to_reg.find(name); + ASSERT_TRUE(entry != name_to_reg.end()) << "Unknown register named " << name; + (*regs)[entry->second] = value; + } + fclose(fp); + regs->SetFromRaw(); + } + + static std::unordered_map arm_regs_; + static std::unordered_map arm64_regs_; + static std::unordered_map x86_regs_; + static std::unordered_map x86_64_regs_; + + char* cwd_ = nullptr; + std::string dir_; + std::unique_ptr regs_; + std::unique_ptr maps_; + std::shared_ptr process_memory_; +}; + +std::unordered_map UnwindOfflineTest::arm_regs_ = { + {"r0", ARM_REG_R0}, {"r1", ARM_REG_R1}, {"r2", ARM_REG_R2}, {"r3", ARM_REG_R3}, + {"r4", ARM_REG_R4}, {"r5", ARM_REG_R5}, {"r6", ARM_REG_R6}, {"r7", ARM_REG_R7}, + {"r8", ARM_REG_R8}, {"r9", ARM_REG_R9}, {"r10", ARM_REG_R10}, {"r11", ARM_REG_R11}, + {"ip", ARM_REG_R12}, {"sp", ARM_REG_SP}, {"lr", ARM_REG_LR}, {"pc", ARM_REG_PC}, +}; + +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}, +}; + +std::unordered_map UnwindOfflineTest::x86_regs_ = { + {"eax", X86_REG_EAX}, {"ebx", X86_REG_EBX}, {"ecx", X86_REG_ECX}, + {"edx", X86_REG_EDX}, {"ebp", X86_REG_EBP}, {"edi", X86_REG_EDI}, + {"esi", X86_REG_ESI}, {"esp", X86_REG_ESP}, {"eip", X86_REG_EIP}, +}; + +std::unordered_map UnwindOfflineTest::x86_64_regs_ = { + {"rax", X86_64_REG_RAX}, {"rbx", X86_64_REG_RBX}, {"rcx", X86_64_REG_RCX}, + {"rdx", X86_64_REG_RDX}, {"r8", X86_64_REG_R8}, {"r9", X86_64_REG_R9}, + {"r10", X86_64_REG_R10}, {"r11", X86_64_REG_R11}, {"r12", X86_64_REG_R12}, + {"r13", X86_64_REG_R13}, {"r14", X86_64_REG_R14}, {"r15", X86_64_REG_R15}, + {"rdi", X86_64_REG_RDI}, {"rsi", X86_64_REG_RSI}, {"rbp", X86_64_REG_RBP}, + {"rsp", X86_64_REG_RSP}, {"rip", X86_64_REG_RIP}, +}; + static std::string DumpFrames(Unwinder& unwinder) { std::string str; for (size_t i = 0; i < unwinder.NumFrames(); i++) { @@ -48,45 +179,11 @@ static std::string DumpFrames(Unwinder& unwinder) { return str; } -TEST(UnwindOfflineTest, pc_straddle_arm) { - std::string dir(TestGetFileDirectory() + "offline/straddle_arm/"); +TEST_F(UnwindOfflineTest, pc_straddle_arm) { + Init("straddle_arm/", ARCH_ARM); - MemoryOffline* memory = new MemoryOffline; - ASSERT_TRUE(memory->Init((dir + "stack.data").c_str(), 0)); - - FILE* fp = fopen((dir + "regs.txt").c_str(), "r"); - ASSERT_TRUE(fp != nullptr); - RegsArm regs; - uint64_t reg_value; - ASSERT_EQ(1, fscanf(fp, "pc: %" SCNx64 "\n", ®_value)); - regs[ARM_REG_PC] = reg_value; - ASSERT_EQ(1, fscanf(fp, "sp: %" SCNx64 "\n", ®_value)); - regs[ARM_REG_SP] = reg_value; - ASSERT_EQ(1, fscanf(fp, "lr: %" SCNx64 "\n", ®_value)); - regs[ARM_REG_LR] = reg_value; - regs.SetFromRaw(); - fclose(fp); - - fp = fopen((dir + "maps.txt").c_str(), "r"); - ASSERT_TRUE(fp != nullptr); - // The file is guaranteed to be less than 4096 bytes. - std::vector buffer(4096); - ASSERT_NE(0U, fread(buffer.data(), 1, buffer.size(), fp)); - fclose(fp); - - BufferMaps maps(buffer.data()); - ASSERT_TRUE(maps.Parse()); - - ASSERT_EQ(ARCH_ARM, regs.Arch()); - - std::shared_ptr process_memory(memory); - - char* cwd = getcwd(nullptr, 0); - ASSERT_EQ(0, chdir(dir.c_str())); - Unwinder unwinder(128, &maps, ®s, process_memory); + Unwinder unwinder(128, maps_.get(), regs_.get(), process_memory_); unwinder.Unwind(); - ASSERT_EQ(0, chdir(cwd)); - free(cwd); std::string frame_info(DumpFrames(unwinder)); ASSERT_EQ(4U, unwinder.NumFrames()) << "Unwind:\n" << frame_info; @@ -98,43 +195,11 @@ TEST(UnwindOfflineTest, pc_straddle_arm) { frame_info); } -TEST(UnwindOfflineTest, pc_in_gnu_debugdata_arm) { - std::string dir(TestGetFileDirectory() + "offline/gnu_debugdata_arm/"); +TEST_F(UnwindOfflineTest, pc_in_gnu_debugdata_arm) { + Init("gnu_debugdata_arm/", ARCH_ARM); - MemoryOffline* memory = new MemoryOffline; - ASSERT_TRUE(memory->Init((dir + "stack.data").c_str(), 0)); - - FILE* fp = fopen((dir + "regs.txt").c_str(), "r"); - ASSERT_TRUE(fp != nullptr); - RegsArm regs; - uint64_t reg_value; - ASSERT_EQ(1, fscanf(fp, "pc: %" SCNx64 "\n", ®_value)); - regs[ARM_REG_PC] = reg_value; - ASSERT_EQ(1, fscanf(fp, "sp: %" SCNx64 "\n", ®_value)); - regs[ARM_REG_SP] = reg_value; - regs.SetFromRaw(); - fclose(fp); - - fp = fopen((dir + "maps.txt").c_str(), "r"); - ASSERT_TRUE(fp != nullptr); - // The file is guaranteed to be less than 4096 bytes. - std::vector buffer(4096); - ASSERT_NE(0U, fread(buffer.data(), 1, buffer.size(), fp)); - fclose(fp); - - BufferMaps maps(buffer.data()); - ASSERT_TRUE(maps.Parse()); - - ASSERT_EQ(ARCH_ARM, regs.Arch()); - - std::shared_ptr process_memory(memory); - - char* cwd = getcwd(nullptr, 0); - ASSERT_EQ(0, chdir(dir.c_str())); - Unwinder unwinder(128, &maps, ®s, process_memory); + Unwinder unwinder(128, maps_.get(), regs_.get(), process_memory_); unwinder.Unwind(); - ASSERT_EQ(0, chdir(cwd)); - free(cwd); std::string frame_info(DumpFrames(unwinder)); ASSERT_EQ(2U, unwinder.NumFrames()) << "Unwind:\n" << frame_info; @@ -146,47 +211,11 @@ TEST(UnwindOfflineTest, pc_in_gnu_debugdata_arm) { frame_info); } -TEST(UnwindOfflineTest, pc_straddle_arm64) { - std::string dir(TestGetFileDirectory() + "offline/straddle_arm64/"); +TEST_F(UnwindOfflineTest, pc_straddle_arm64) { + Init("straddle_arm64/", ARCH_ARM64); - MemoryOffline* memory = new MemoryOffline; - ASSERT_TRUE(memory->Init((dir + "stack.data").c_str(), 0)); - - FILE* fp = fopen((dir + "regs.txt").c_str(), "r"); - ASSERT_TRUE(fp != nullptr); - RegsArm64 regs; - uint64_t reg_value; - ASSERT_EQ(1, fscanf(fp, "pc: %" SCNx64 "\n", ®_value)); - regs[ARM64_REG_PC] = reg_value; - ASSERT_EQ(1, fscanf(fp, "sp: %" SCNx64 "\n", ®_value)); - regs[ARM64_REG_SP] = reg_value; - ASSERT_EQ(1, fscanf(fp, "lr: %" SCNx64 "\n", ®_value)); - regs[ARM64_REG_LR] = reg_value; - ASSERT_EQ(1, fscanf(fp, "x29: %" SCNx64 "\n", ®_value)); - regs[ARM64_REG_R29] = reg_value; - regs.SetFromRaw(); - fclose(fp); - - fp = fopen((dir + "maps.txt").c_str(), "r"); - ASSERT_TRUE(fp != nullptr); - // The file is guaranteed to be less than 4096 bytes. - std::vector buffer(4096); - ASSERT_NE(0U, fread(buffer.data(), 1, buffer.size(), fp)); - fclose(fp); - - BufferMaps maps(buffer.data()); - ASSERT_TRUE(maps.Parse()); - - ASSERT_EQ(ARCH_ARM64, regs.Arch()); - - std::shared_ptr process_memory(memory); - - char* cwd = getcwd(nullptr, 0); - ASSERT_EQ(0, chdir(dir.c_str())); - Unwinder unwinder(128, &maps, ®s, process_memory); + Unwinder unwinder(128, maps_.get(), regs_.get(), process_memory_); unwinder.Unwind(); - ASSERT_EQ(0, chdir(cwd)); - free(cwd); std::string frame_info(DumpFrames(unwinder)); ASSERT_EQ(6U, unwinder.NumFrames()) << "Unwind:\n" << frame_info; @@ -208,64 +237,22 @@ static void AddMemory(std::string file_name, MemoryOfflineParts* parts) { parts->Add(memory); } -TEST(UnwindOfflineTest, jit_debug_x86) { - std::string dir(TestGetFileDirectory() + "offline/jit_debug_x86/"); +TEST_F(UnwindOfflineTest, jit_debug_x86) { + Init("jit_debug_x86/", ARCH_X86); MemoryOfflineParts* memory = new MemoryOfflineParts; - AddMemory(dir + "descriptor.data", memory); - AddMemory(dir + "stack.data", memory); + AddMemory(dir_ + "descriptor.data", memory); + AddMemory(dir_ + "stack.data", memory); for (size_t i = 0; i < 7; i++) { - AddMemory(dir + "entry" + std::to_string(i) + ".data", memory); - AddMemory(dir + "jit" + std::to_string(i) + ".data", memory); + AddMemory(dir_ + "entry" + std::to_string(i) + ".data", memory); + AddMemory(dir_ + "jit" + std::to_string(i) + ".data", memory); } + process_memory_.reset(memory); - FILE* fp = fopen((dir + "regs.txt").c_str(), "r"); - ASSERT_TRUE(fp != nullptr); - RegsX86 regs; - uint64_t reg_value; - ASSERT_EQ(1, fscanf(fp, "eax: %" SCNx64 "\n", ®_value)); - regs[X86_REG_EAX] = reg_value; - ASSERT_EQ(1, fscanf(fp, "ebx: %" SCNx64 "\n", ®_value)); - regs[X86_REG_EBX] = reg_value; - ASSERT_EQ(1, fscanf(fp, "ecx: %" SCNx64 "\n", ®_value)); - regs[X86_REG_ECX] = reg_value; - ASSERT_EQ(1, fscanf(fp, "edx: %" SCNx64 "\n", ®_value)); - regs[X86_REG_EDX] = reg_value; - ASSERT_EQ(1, fscanf(fp, "ebp: %" SCNx64 "\n", ®_value)); - regs[X86_REG_EBP] = reg_value; - ASSERT_EQ(1, fscanf(fp, "edi: %" SCNx64 "\n", ®_value)); - regs[X86_REG_EDI] = reg_value; - ASSERT_EQ(1, fscanf(fp, "esi: %" SCNx64 "\n", ®_value)); - regs[X86_REG_ESI] = reg_value; - ASSERT_EQ(1, fscanf(fp, "esp: %" SCNx64 "\n", ®_value)); - regs[X86_REG_ESP] = reg_value; - ASSERT_EQ(1, fscanf(fp, "eip: %" SCNx64 "\n", ®_value)); - regs[X86_REG_EIP] = reg_value; - regs.SetFromRaw(); - fclose(fp); - - fp = fopen((dir + "maps.txt").c_str(), "r"); - ASSERT_TRUE(fp != nullptr); - // The file is guaranteed to be less than 4096 bytes. - std::vector buffer(4096); - ASSERT_NE(0U, fread(buffer.data(), 1, buffer.size(), fp)); - fclose(fp); - - BufferMaps maps(buffer.data()); - ASSERT_TRUE(maps.Parse()); - - ASSERT_EQ(ARCH_X86, regs.Arch()); - - std::shared_ptr process_memory(memory); - - char* cwd = getcwd(nullptr, 0); - ASSERT_EQ(0, chdir(dir.c_str())); - JitDebug jit_debug(process_memory); - Unwinder unwinder(128, &maps, ®s, process_memory); - unwinder.SetJitDebug(&jit_debug, regs.Arch()); + JitDebug jit_debug(process_memory_); + Unwinder unwinder(128, maps_.get(), regs_.get(), process_memory_); + unwinder.SetJitDebug(&jit_debug, regs_->Arch()); unwinder.Unwind(); - ASSERT_EQ(0, chdir(cwd)); - free(cwd); std::string frame_info(DumpFrames(unwinder)); ASSERT_EQ(69U, unwinder.NumFrames()) << "Unwind:\n" << frame_info; @@ -405,79 +392,23 @@ TEST(UnwindOfflineTest, jit_debug_x86) { frame_info); } -TEST(UnwindOfflineTest, jit_debug_arm) { - std::string dir(TestGetFileDirectory() + "offline/jit_debug_arm/"); +TEST_F(UnwindOfflineTest, jit_debug_arm) { + Init("jit_debug_arm/", ARCH_ARM); MemoryOfflineParts* memory = new MemoryOfflineParts; - AddMemory(dir + "descriptor.data", memory); - AddMemory(dir + "descriptor1.data", memory); - AddMemory(dir + "stack.data", memory); + AddMemory(dir_ + "descriptor.data", memory); + AddMemory(dir_ + "descriptor1.data", memory); + AddMemory(dir_ + "stack.data", memory); for (size_t i = 0; i < 7; i++) { - AddMemory(dir + "entry" + std::to_string(i) + ".data", memory); - AddMemory(dir + "jit" + std::to_string(i) + ".data", memory); + AddMemory(dir_ + "entry" + std::to_string(i) + ".data", memory); + AddMemory(dir_ + "jit" + std::to_string(i) + ".data", memory); } + process_memory_.reset(memory); - FILE* fp = fopen((dir + "regs.txt").c_str(), "r"); - ASSERT_TRUE(fp != nullptr); - RegsArm regs; - uint64_t reg_value; - ASSERT_EQ(1, fscanf(fp, "r0: %" SCNx64 "\n", ®_value)); - regs[ARM_REG_R0] = reg_value; - ASSERT_EQ(1, fscanf(fp, "r1: %" SCNx64 "\n", ®_value)); - regs[ARM_REG_R1] = reg_value; - ASSERT_EQ(1, fscanf(fp, "r2: %" SCNx64 "\n", ®_value)); - regs[ARM_REG_R2] = reg_value; - ASSERT_EQ(1, fscanf(fp, "r3: %" SCNx64 "\n", ®_value)); - regs[ARM_REG_R3] = reg_value; - ASSERT_EQ(1, fscanf(fp, "r4: %" SCNx64 "\n", ®_value)); - regs[ARM_REG_R4] = reg_value; - ASSERT_EQ(1, fscanf(fp, "r5: %" SCNx64 "\n", ®_value)); - regs[ARM_REG_R5] = reg_value; - ASSERT_EQ(1, fscanf(fp, "r6: %" SCNx64 "\n", ®_value)); - regs[ARM_REG_R6] = reg_value; - ASSERT_EQ(1, fscanf(fp, "r7: %" SCNx64 "\n", ®_value)); - regs[ARM_REG_R7] = reg_value; - ASSERT_EQ(1, fscanf(fp, "r8: %" SCNx64 "\n", ®_value)); - regs[ARM_REG_R8] = reg_value; - ASSERT_EQ(1, fscanf(fp, "r9: %" SCNx64 "\n", ®_value)); - regs[ARM_REG_R9] = reg_value; - ASSERT_EQ(1, fscanf(fp, "r10: %" SCNx64 "\n", ®_value)); - regs[ARM_REG_R10] = reg_value; - ASSERT_EQ(1, fscanf(fp, "r11: %" SCNx64 "\n", ®_value)); - regs[ARM_REG_R11] = reg_value; - ASSERT_EQ(1, fscanf(fp, "ip: %" SCNx64 "\n", ®_value)); - regs[ARM_REG_R12] = reg_value; - ASSERT_EQ(1, fscanf(fp, "sp: %" SCNx64 "\n", ®_value)); - regs[ARM_REG_SP] = reg_value; - ASSERT_EQ(1, fscanf(fp, "lr: %" SCNx64 "\n", ®_value)); - regs[ARM_REG_LR] = reg_value; - ASSERT_EQ(1, fscanf(fp, "pc: %" SCNx64 "\n", ®_value)); - regs[ARM_REG_PC] = reg_value; - regs.SetFromRaw(); - fclose(fp); - - fp = fopen((dir + "maps.txt").c_str(), "r"); - ASSERT_TRUE(fp != nullptr); - // The file is guaranteed to be less than 4096 bytes. - std::vector buffer(4096); - ASSERT_NE(0U, fread(buffer.data(), 1, buffer.size(), fp)); - fclose(fp); - - BufferMaps maps(buffer.data()); - ASSERT_TRUE(maps.Parse()); - - ASSERT_EQ(ARCH_ARM, regs.Arch()); - - std::shared_ptr process_memory(memory); - - char* cwd = getcwd(nullptr, 0); - ASSERT_EQ(0, chdir(dir.c_str())); - JitDebug jit_debug(process_memory); - Unwinder unwinder(128, &maps, ®s, process_memory); - unwinder.SetJitDebug(&jit_debug, regs.Arch()); + JitDebug jit_debug(process_memory_); + Unwinder unwinder(128, maps_.get(), regs_.get(), process_memory_); + unwinder.SetJitDebug(&jit_debug, regs_->Arch()); unwinder.Unwind(); - ASSERT_EQ(0, chdir(cwd)); - free(cwd); std::string frame_info(DumpFrames(unwinder)); ASSERT_EQ(76U, unwinder.NumFrames()) << "Unwind:\n" << frame_info; @@ -627,47 +558,11 @@ TEST(UnwindOfflineTest, jit_debug_arm) { // The eh_frame_hdr data is present but set to zero fdes. This should // fallback to iterating over the cies/fdes and ignore the eh_frame_hdr. // No .gnu_debugdata section in the elf file, so no symbols. -TEST(UnwindOfflineTest, bad_eh_frame_hdr_arm64) { - std::string dir(TestGetFileDirectory() + "offline/bad_eh_frame_hdr_arm64/"); +TEST_F(UnwindOfflineTest, bad_eh_frame_hdr_arm64) { + Init("bad_eh_frame_hdr_arm64/", ARCH_ARM64); - MemoryOffline* memory = new MemoryOffline; - ASSERT_TRUE(memory->Init((dir + "stack.data").c_str(), 0)); - - FILE* fp = fopen((dir + "regs.txt").c_str(), "r"); - ASSERT_TRUE(fp != nullptr); - RegsArm64 regs; - uint64_t reg_value; - ASSERT_EQ(1, fscanf(fp, "pc: %" SCNx64 "\n", ®_value)); - regs[ARM64_REG_PC] = reg_value; - ASSERT_EQ(1, fscanf(fp, "sp: %" SCNx64 "\n", ®_value)); - regs[ARM64_REG_SP] = reg_value; - ASSERT_EQ(1, fscanf(fp, "lr: %" SCNx64 "\n", ®_value)); - regs[ARM64_REG_LR] = reg_value; - ASSERT_EQ(1, fscanf(fp, "x29: %" SCNx64 "\n", ®_value)); - regs[ARM64_REG_R29] = reg_value; - regs.SetFromRaw(); - fclose(fp); - - fp = fopen((dir + "maps.txt").c_str(), "r"); - ASSERT_TRUE(fp != nullptr); - // The file is guaranteed to be less than 4096 bytes. - std::vector buffer(4096); - ASSERT_NE(0U, fread(buffer.data(), 1, buffer.size(), fp)); - fclose(fp); - - BufferMaps maps(buffer.data()); - ASSERT_TRUE(maps.Parse()); - - ASSERT_EQ(ARCH_ARM64, regs.Arch()); - - std::shared_ptr process_memory(memory); - - char* cwd = getcwd(nullptr, 0); - ASSERT_EQ(0, chdir(dir.c_str())); - Unwinder unwinder(128, &maps, ®s, process_memory); + Unwinder unwinder(128, maps_.get(), regs_.get(), process_memory_); unwinder.Unwind(); - ASSERT_EQ(0, chdir(cwd)); - free(cwd); std::string frame_info(DumpFrames(unwinder)); ASSERT_EQ(5U, unwinder.NumFrames()) << "Unwind:\n" << frame_info; @@ -682,59 +577,11 @@ TEST(UnwindOfflineTest, bad_eh_frame_hdr_arm64) { // The elf has bad eh_frame unwind information for the pcs. If eh_frame // is used first, the unwind will not match the expected output. -TEST(UnwindOfflineTest, debug_frame_first_x86) { - std::string dir(TestGetFileDirectory() + "offline/debug_frame_first_x86/"); +TEST_F(UnwindOfflineTest, debug_frame_first_x86) { + Init("debug_frame_first_x86/", ARCH_X86); - MemoryOffline* memory = new MemoryOffline; - ASSERT_TRUE(memory->Init((dir + "stack.data").c_str(), 0)); - - FILE* fp = fopen((dir + "regs.txt").c_str(), "r"); - ASSERT_TRUE(fp != nullptr); - RegsX86 regs; - uint64_t reg_value; - ASSERT_EQ(1, fscanf(fp, "eax: %" SCNx64 "\n", ®_value)); - regs[X86_REG_EAX] = reg_value; - ASSERT_EQ(1, fscanf(fp, "ebx: %" SCNx64 "\n", ®_value)); - regs[X86_REG_EBX] = reg_value; - ASSERT_EQ(1, fscanf(fp, "ecx: %" SCNx64 "\n", ®_value)); - regs[X86_REG_ECX] = reg_value; - ASSERT_EQ(1, fscanf(fp, "edx: %" SCNx64 "\n", ®_value)); - regs[X86_REG_EDX] = reg_value; - ASSERT_EQ(1, fscanf(fp, "ebp: %" SCNx64 "\n", ®_value)); - regs[X86_REG_EBP] = reg_value; - ASSERT_EQ(1, fscanf(fp, "edi: %" SCNx64 "\n", ®_value)); - regs[X86_REG_EDI] = reg_value; - ASSERT_EQ(1, fscanf(fp, "esi: %" SCNx64 "\n", ®_value)); - regs[X86_REG_ESI] = reg_value; - ASSERT_EQ(1, fscanf(fp, "esp: %" SCNx64 "\n", ®_value)); - regs[X86_REG_ESP] = reg_value; - ASSERT_EQ(1, fscanf(fp, "eip: %" SCNx64 "\n", ®_value)); - regs[X86_REG_EIP] = reg_value; - regs.SetFromRaw(); - fclose(fp); - - fp = fopen((dir + "maps.txt").c_str(), "r"); - ASSERT_TRUE(fp != nullptr); - // The file is guaranteed to be less than 4096 bytes. - std::vector buffer(4096); - ASSERT_NE(0U, fread(buffer.data(), 1, buffer.size(), fp)); - fclose(fp); - - BufferMaps maps(buffer.data()); - ASSERT_TRUE(maps.Parse()); - - ASSERT_EQ(ARCH_X86, regs.Arch()); - - std::shared_ptr process_memory(memory); - - char* cwd = getcwd(nullptr, 0); - ASSERT_EQ(0, chdir(dir.c_str())); - JitDebug jit_debug(process_memory); - Unwinder unwinder(128, &maps, ®s, process_memory); - unwinder.SetJitDebug(&jit_debug, regs.Arch()); + Unwinder unwinder(128, maps_.get(), regs_.get(), process_memory_); unwinder.Unwind(); - ASSERT_EQ(0, chdir(cwd)); - free(cwd); std::string frame_info(DumpFrames(unwinder)); ASSERT_EQ(5U, unwinder.NumFrames()) << "Unwind:\n" << frame_info; @@ -747,4 +594,22 @@ TEST(UnwindOfflineTest, debug_frame_first_x86) { frame_info); } +// Make sure that a pc that is at the beginning of an fde unwinds correctly. +TEST_F(UnwindOfflineTest, eh_frame_hdr_begin_x86_64) { + Init("eh_frame_hdr_begin_x86_64/", ARCH_X86_64); + + Unwinder unwinder(128, maps_.get(), regs_.get(), process_memory_); + unwinder.Unwind(); + + std::string frame_info(DumpFrames(unwinder)); + ASSERT_EQ(5U, unwinder.NumFrames()) << "Unwind:\n" << frame_info; + EXPECT_EQ( + " #00 pc 0000000000000a80 unwind_test64 (calling3)\n" + " #01 pc 0000000000000dd9 unwind_test64 (calling2+633)\n" + " #02 pc 000000000000121e unwind_test64 (calling1+638)\n" + " #03 pc 00000000000013ed unwind_test64 (main+13)\n" + " #04 pc 00000000000202b0 libc.so\n", + frame_info); +} + } // namespace unwindstack diff --git a/libunwindstack/tests/files/offline/eh_frame_hdr_begin_x86_64/libc.so b/libunwindstack/tests/files/offline/eh_frame_hdr_begin_x86_64/libc.so new file mode 100644 index 000000000..46b6f4560 Binary files /dev/null and b/libunwindstack/tests/files/offline/eh_frame_hdr_begin_x86_64/libc.so differ diff --git a/libunwindstack/tests/files/offline/eh_frame_hdr_begin_x86_64/maps.txt b/libunwindstack/tests/files/offline/eh_frame_hdr_begin_x86_64/maps.txt new file mode 100644 index 000000000..ac2e56432 --- /dev/null +++ b/libunwindstack/tests/files/offline/eh_frame_hdr_begin_x86_64/maps.txt @@ -0,0 +1,2 @@ +561550b17000-561550b1a000 r-xp 0 00:00 0 unwind_test64 +7f4de61f6000-7f4de638b000 r-xp 0 00:00 0 libc.so diff --git a/libunwindstack/tests/files/offline/eh_frame_hdr_begin_x86_64/regs.txt b/libunwindstack/tests/files/offline/eh_frame_hdr_begin_x86_64/regs.txt new file mode 100644 index 000000000..38af274bb --- /dev/null +++ b/libunwindstack/tests/files/offline/eh_frame_hdr_begin_x86_64/regs.txt @@ -0,0 +1,11 @@ +rax: 92134c6fbbdc12ff +rbx: 0 +rcx: 92134c6fbbdc1200 +rdx: 92134c6fbbdc1200 +r8: 561552153034 +r12: 561550b17930 +r13: 7ffcc8597270 +rsi: 561552153034 +rbp: 7ffcc8596f30 +rsp: 7ffcc8596ce8 +rip: 561550b17a80 diff --git a/libunwindstack/tests/files/offline/eh_frame_hdr_begin_x86_64/stack.data b/libunwindstack/tests/files/offline/eh_frame_hdr_begin_x86_64/stack.data new file mode 100644 index 000000000..cc7882b83 Binary files /dev/null and b/libunwindstack/tests/files/offline/eh_frame_hdr_begin_x86_64/stack.data differ diff --git a/libunwindstack/tests/files/offline/eh_frame_hdr_begin_x86_64/unwind_test64 b/libunwindstack/tests/files/offline/eh_frame_hdr_begin_x86_64/unwind_test64 new file mode 100644 index 000000000..ab0ef8f70 Binary files /dev/null and b/libunwindstack/tests/files/offline/eh_frame_hdr_begin_x86_64/unwind_test64 differ diff --git a/libunwindstack/tools/unwind_reg_info.cpp b/libunwindstack/tools/unwind_reg_info.cpp new file mode 100644 index 000000000..4d890879f --- /dev/null +++ b/libunwindstack/tools/unwind_reg_info.cpp @@ -0,0 +1,237 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "DwarfOp.h" + +namespace unwindstack { + +void PrintSignedValue(int64_t value) { + if (value < 0) { + printf("- %" PRId64, -value); + } else if (value > 0) { + printf("+ %" PRId64, value); + } +} + +void PrintExpression(Memory* memory, uint8_t class_type, uint64_t end, uint64_t length) { + std::vector lines; + DwarfMemory dwarf_memory(memory); + if (class_type == ELFCLASS32) { + DwarfOp op(&dwarf_memory, nullptr); + op.GetLogInfo(end - length, end, &lines); + } else { + DwarfOp op(&dwarf_memory, nullptr); + op.GetLogInfo(end - length, end, &lines); + } + for (auto& line : lines) { + printf(" %s\n", line.c_str()); + } +} + +void PrintRegInformation(DwarfSection* section, Memory* memory, uint64_t pc, uint8_t class_type) { + const DwarfFde* fde = section->GetFdeFromPc(pc); + if (fde == nullptr) { + printf(" No fde found.\n"); + return; + } + + dwarf_loc_regs_t regs; + if (!section->GetCfaLocationInfo(pc, fde, ®s)) { + printf(" Cannot get location information.\n"); + return; + } + + std::vector> loc_regs; + for (auto& loc : regs) { + loc_regs.push_back(loc); + } + std::sort(loc_regs.begin(), loc_regs.end(), [](auto a, auto b) { + if (a.first == CFA_REG) { + return true; + } else if (b.first == CFA_REG) { + return false; + } + return a.first < b.first; + }); + + for (auto& entry : loc_regs) { + const DwarfLocation* loc = &entry.second; + if (entry.first == CFA_REG) { + printf(" cfa = "); + } else { + printf(" r%d = ", entry.first); + } + switch (loc->type) { + case DWARF_LOCATION_OFFSET: + printf("[cfa "); + PrintSignedValue(loc->values[0]); + printf("]\n"); + break; + + case DWARF_LOCATION_VAL_OFFSET: + printf("cfa "); + PrintSignedValue(loc->values[0]); + printf("\n"); + break; + + case DWARF_LOCATION_REGISTER: + printf("r%" PRId64 " ", loc->values[0]); + PrintSignedValue(loc->values[1]); + printf("\n"); + break; + + case DWARF_LOCATION_EXPRESSION: { + printf("EXPRESSION\n"); + PrintExpression(memory, class_type, loc->values[1], loc->values[0]); + break; + } + + case DWARF_LOCATION_VAL_EXPRESSION: { + printf("VAL EXPRESSION\n"); + PrintExpression(memory, class_type, loc->values[1], loc->values[0]); + break; + } + + case DWARF_LOCATION_UNDEFINED: + printf("undefine\n"); + break; + + case DWARF_LOCATION_INVALID: + printf("INVALID\n"); + break; + } + } +} + +int GetInfo(const char* file, uint64_t pc) { + MemoryFileAtOffset* memory = new MemoryFileAtOffset; + if (!memory->Init(file, 0)) { + // Initializatation failed. + printf("Failed to init\n"); + return 1; + } + + Elf elf(memory); + if (!elf.Init(true) || !elf.valid()) { + printf("%s is not a valid elf file.\n", file); + return 1; + } + + ElfInterface* interface = elf.interface(); + uint64_t load_bias = elf.GetLoadBias(); + if (pc < load_bias) { + printf("PC is less than load bias.\n"); + return 1; + } + + printf("PC 0x%" PRIx64 ":\n", pc); + + DwarfSection* section = interface->eh_frame(); + if (section != nullptr) { + printf("\neh_frame:\n"); + PrintRegInformation(section, memory, pc - load_bias, elf.class_type()); + } else { + printf("\nno eh_frame information\n"); + } + + section = interface->debug_frame(); + if (section != nullptr) { + printf("\ndebug_frame:\n"); + PrintRegInformation(section, memory, pc - load_bias, elf.class_type()); + printf("\n"); + } else { + printf("\nno debug_frame information\n"); + } + + // If there is a gnu_debugdata interface, dump the information for that. + ElfInterface* gnu_debugdata_interface = elf.gnu_debugdata_interface(); + if (gnu_debugdata_interface != nullptr) { + section = gnu_debugdata_interface->eh_frame(); + if (section != nullptr) { + printf("\ngnu_debugdata (eh_frame):\n"); + PrintRegInformation(section, gnu_debugdata_interface->memory(), pc, elf.class_type()); + printf("\n"); + } else { + printf("\nno gnu_debugdata (eh_frame)\n"); + } + + section = gnu_debugdata_interface->debug_frame(); + if (section != nullptr) { + printf("\ngnu_debugdata (debug_frame):\n"); + PrintRegInformation(section, gnu_debugdata_interface->memory(), pc, elf.class_type()); + printf("\n"); + } else { + printf("\nno gnu_debugdata (debug_frame)\n"); + } + } else { + printf("\nno valid gnu_debugdata information\n"); + } + + return 0; +} + +} // namespace unwindstack + +int main(int argc, char** argv) { + if (argc != 3) { + printf("Usage: unwind_reg_info ELF_FILE PC\n"); + printf(" ELF_FILE\n"); + printf(" The path to an elf file.\n"); + printf(" PC\n"); + printf(" The pc for which the register information should be obtained.\n"); + return 1; + } + + struct stat st; + if (stat(argv[1], &st) == -1) { + printf("Cannot stat %s: %s\n", argv[1], strerror(errno)); + return 1; + } + if (!S_ISREG(st.st_mode)) { + printf("%s is not a regular file.\n", argv[1]); + return 1; + } + + uint64_t pc = 0; + char* end; + pc = strtoull(argv[2], &end, 16); + if (*end != '\0') { + printf("Malformed OFFSET value: %s\n", argv[2]); + return 1; + } + + return unwindstack::GetInfo(argv[1], pc); +}