Merge "Small behavioral changes to the unwinder."
This commit is contained in:
commit
335675c2fd
18 changed files with 264 additions and 20 deletions
|
@ -170,6 +170,8 @@ cc_test {
|
|||
data: [
|
||||
"tests/files/elf32.xz",
|
||||
"tests/files/elf64.xz",
|
||||
"tests/files/offline/bad_eh_frame_hdr_arm64/*",
|
||||
"tests/files/offline/debug_frame_first_x86/*",
|
||||
"tests/files/offline/jit_debug_arm32/*",
|
||||
"tests/files/offline/jit_debug_x86_32/*",
|
||||
"tests/files/offline/gnu_debugdata_arm32/*",
|
||||
|
|
|
@ -64,6 +64,11 @@ bool DwarfEhFrameWithHdr<AddressType>::Init(uint64_t offset, uint64_t size) {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (fde_count_ == 0) {
|
||||
last_error_ = DWARF_ERROR_NO_FDES;
|
||||
return false;
|
||||
}
|
||||
|
||||
entries_offset_ = memory_.cur_offset();
|
||||
entries_end_ = offset + size;
|
||||
entries_data_offset_ = offset;
|
||||
|
|
|
@ -31,6 +31,7 @@ enum DwarfError : uint8_t {
|
|||
DWARF_ERROR_TOO_MANY_ITERATIONS,
|
||||
DWARF_ERROR_CFA_NOT_DEFINED,
|
||||
DWARF_ERROR_UNSUPPORTED_VERSION,
|
||||
DWARF_ERROR_NO_FDES,
|
||||
};
|
||||
|
||||
} // namespace unwindstack
|
||||
|
|
|
@ -835,9 +835,8 @@ bool DwarfSectionImpl<AddressType>::CreateSortedFdeList() {
|
|||
}
|
||||
|
||||
if (next_entry_offset < memory_.cur_offset()) {
|
||||
// This indicates some kind of corruption, or malformed section data.
|
||||
last_error_ = DWARF_ERROR_ILLEGAL_VALUE;
|
||||
return false;
|
||||
// Simply consider the processing done in this case.
|
||||
break;
|
||||
}
|
||||
memory_.set_cur_offset(next_entry_offset);
|
||||
}
|
||||
|
|
|
@ -126,22 +126,26 @@ void ElfInterface::InitHeadersWithTemplate() {
|
|||
if (eh_frame_hdr_offset_ != 0) {
|
||||
eh_frame_.reset(new DwarfEhFrameWithHdr<AddressType>(memory_));
|
||||
if (!eh_frame_->Init(eh_frame_hdr_offset_, eh_frame_hdr_size_)) {
|
||||
// Even if the eh_frame_offset_ is non-zero, do not bother
|
||||
// trying to read that since something has gone wrong.
|
||||
eh_frame_.reset(nullptr);
|
||||
eh_frame_hdr_offset_ = 0;
|
||||
eh_frame_hdr_size_ = static_cast<uint64_t>(-1);
|
||||
}
|
||||
} else if (eh_frame_offset_ != 0) {
|
||||
// If there is a eh_frame section without a eh_frame_hdr section.
|
||||
}
|
||||
|
||||
if (eh_frame_.get() == nullptr && eh_frame_offset_ != 0) {
|
||||
// If there is an eh_frame section without an eh_frame_hdr section,
|
||||
// or using the frame hdr object failed to init.
|
||||
eh_frame_.reset(new DwarfEhFrame<AddressType>(memory_));
|
||||
if (!eh_frame_->Init(eh_frame_offset_, eh_frame_size_)) {
|
||||
eh_frame_.reset(nullptr);
|
||||
eh_frame_offset_ = 0;
|
||||
eh_frame_size_ = static_cast<uint64_t>(-1);
|
||||
}
|
||||
}
|
||||
|
||||
if (eh_frame_.get() == nullptr) {
|
||||
eh_frame_hdr_offset_ = 0;
|
||||
eh_frame_hdr_size_ = static_cast<uint64_t>(-1);
|
||||
eh_frame_offset_ = 0;
|
||||
eh_frame_size_ = static_cast<uint64_t>(-1);
|
||||
}
|
||||
|
||||
if (debug_frame_offset_ != 0) {
|
||||
debug_frame_.reset(new DwarfDebugFrame<AddressType>(memory_));
|
||||
if (!debug_frame_->Init(debug_frame_offset_, debug_frame_size_)) {
|
||||
|
@ -436,15 +440,16 @@ bool ElfInterface::Step(uint64_t pc, uint64_t load_bias, Regs* regs, Memory* pro
|
|||
}
|
||||
uint64_t adjusted_pc = pc - load_bias;
|
||||
|
||||
// Try the eh_frame first.
|
||||
DwarfSection* eh_frame = eh_frame_.get();
|
||||
if (eh_frame != nullptr && eh_frame->Step(adjusted_pc, regs, process_memory, finished)) {
|
||||
// Try the debug_frame first since it contains the most specific unwind
|
||||
// information.
|
||||
DwarfSection* debug_frame = debug_frame_.get();
|
||||
if (debug_frame != nullptr && debug_frame->Step(adjusted_pc, regs, process_memory, finished)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Try the debug_frame next.
|
||||
DwarfSection* debug_frame = debug_frame_.get();
|
||||
if (debug_frame != nullptr && debug_frame->Step(adjusted_pc, regs, process_memory, finished)) {
|
||||
// Try the eh_frame next.
|
||||
DwarfSection* eh_frame = eh_frame_.get();
|
||||
if (eh_frame != nullptr && eh_frame->Step(adjusted_pc, regs, process_memory, finished)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -145,6 +145,45 @@ TYPED_TEST_P(DwarfDebugFrameTest, Init32_fde_not_following_cie) {
|
|||
ASSERT_EQ(DWARF_ERROR_ILLEGAL_VALUE, this->debug_frame_->last_error());
|
||||
}
|
||||
|
||||
TYPED_TEST_P(DwarfDebugFrameTest, Init32_do_not_fail_on_bad_next_entry) {
|
||||
// CIE 32 information.
|
||||
this->memory_.SetData32(0x5000, 0xfc);
|
||||
this->memory_.SetData32(0x5004, 0xffffffff);
|
||||
this->memory_.SetData8(0x5008, 1);
|
||||
this->memory_.SetData8(0x5009, '\0');
|
||||
|
||||
// FDE 32 information.
|
||||
this->memory_.SetData32(0x5100, 0xfc);
|
||||
this->memory_.SetData32(0x5104, 0);
|
||||
this->memory_.SetData32(0x5108, 0x1500);
|
||||
this->memory_.SetData32(0x510c, 0x200);
|
||||
|
||||
this->memory_.SetData32(0x5200, 0xfc);
|
||||
this->memory_.SetData32(0x5204, 0);
|
||||
this->memory_.SetData32(0x5208, 0x2500);
|
||||
this->memory_.SetData32(0x520c, 0x300);
|
||||
|
||||
// CIE 32 information.
|
||||
this->memory_.SetData32(0x5300, 0);
|
||||
this->memory_.SetData32(0x5304, 0xffffffff);
|
||||
this->memory_.SetData8(0x5308, 1);
|
||||
this->memory_.SetData8(0x5309, '\0');
|
||||
|
||||
// FDE 32 information.
|
||||
this->memory_.SetData32(0x5400, 0xfc);
|
||||
this->memory_.SetData32(0x5404, 0x300);
|
||||
this->memory_.SetData32(0x5408, 0x3500);
|
||||
this->memory_.SetData32(0x540c, 0x400);
|
||||
|
||||
this->memory_.SetData32(0x5500, 0xfc);
|
||||
this->memory_.SetData32(0x5504, 0x300);
|
||||
this->memory_.SetData32(0x5508, 0x4500);
|
||||
this->memory_.SetData32(0x550c, 0x500);
|
||||
|
||||
ASSERT_TRUE(this->debug_frame_->Init(0x5000, 0x600));
|
||||
ASSERT_EQ(2U, this->debug_frame_->TestGetFdeCount());
|
||||
}
|
||||
|
||||
TYPED_TEST_P(DwarfDebugFrameTest, Init64) {
|
||||
// CIE 64 information.
|
||||
this->memory_.SetData32(0x5000, 0xffffffff);
|
||||
|
@ -231,6 +270,51 @@ TYPED_TEST_P(DwarfDebugFrameTest, Init64_fde_not_following_cie) {
|
|||
ASSERT_EQ(DWARF_ERROR_ILLEGAL_VALUE, this->debug_frame_->last_error());
|
||||
}
|
||||
|
||||
TYPED_TEST_P(DwarfDebugFrameTest, Init64_do_not_fail_on_bad_next_entry) {
|
||||
// CIE 64 information.
|
||||
this->memory_.SetData32(0x5000, 0xffffffff);
|
||||
this->memory_.SetData64(0x5004, 0xf4);
|
||||
this->memory_.SetData64(0x500c, 0xffffffffffffffffULL);
|
||||
this->memory_.SetData8(0x5014, 1);
|
||||
this->memory_.SetData8(0x5015, '\0');
|
||||
|
||||
// FDE 64 information.
|
||||
this->memory_.SetData32(0x5100, 0xffffffff);
|
||||
this->memory_.SetData64(0x5104, 0xf4);
|
||||
this->memory_.SetData64(0x510c, 0);
|
||||
this->memory_.SetData64(0x5114, 0x1500);
|
||||
this->memory_.SetData64(0x511c, 0x200);
|
||||
|
||||
this->memory_.SetData32(0x5200, 0xffffffff);
|
||||
this->memory_.SetData64(0x5204, 0xf4);
|
||||
this->memory_.SetData64(0x520c, 0);
|
||||
this->memory_.SetData64(0x5214, 0x2500);
|
||||
this->memory_.SetData64(0x521c, 0x300);
|
||||
|
||||
// CIE 64 information.
|
||||
this->memory_.SetData32(0x5300, 0xffffffff);
|
||||
this->memory_.SetData64(0x5304, 0);
|
||||
this->memory_.SetData64(0x530c, 0xffffffffffffffffULL);
|
||||
this->memory_.SetData8(0x5314, 1);
|
||||
this->memory_.SetData8(0x5315, '\0');
|
||||
|
||||
// FDE 64 information.
|
||||
this->memory_.SetData32(0x5400, 0xffffffff);
|
||||
this->memory_.SetData64(0x5404, 0xf4);
|
||||
this->memory_.SetData64(0x540c, 0x300);
|
||||
this->memory_.SetData64(0x5414, 0x3500);
|
||||
this->memory_.SetData64(0x541c, 0x400);
|
||||
|
||||
this->memory_.SetData32(0x5500, 0xffffffff);
|
||||
this->memory_.SetData64(0x5504, 0xf4);
|
||||
this->memory_.SetData64(0x550c, 0x300);
|
||||
this->memory_.SetData64(0x5514, 0x4500);
|
||||
this->memory_.SetData64(0x551c, 0x500);
|
||||
|
||||
ASSERT_TRUE(this->debug_frame_->Init(0x5000, 0x600));
|
||||
ASSERT_EQ(2U, this->debug_frame_->TestGetFdeCount());
|
||||
}
|
||||
|
||||
TYPED_TEST_P(DwarfDebugFrameTest, Init_version1) {
|
||||
// CIE 32 information.
|
||||
this->memory_.SetData32(0x5000, 0xfc);
|
||||
|
@ -450,9 +534,11 @@ TYPED_TEST_P(DwarfDebugFrameTest, GetCieFde64) {
|
|||
EXPECT_EQ(0x20U, fde->cie->return_address_register);
|
||||
}
|
||||
|
||||
REGISTER_TYPED_TEST_CASE_P(DwarfDebugFrameTest, Init32, Init32_fde_not_following_cie, Init64,
|
||||
Init64_fde_not_following_cie, Init_version1, Init_version4,
|
||||
GetFdeOffsetFromPc, GetCieFde32, GetCieFde64);
|
||||
REGISTER_TYPED_TEST_CASE_P(DwarfDebugFrameTest, Init32, Init32_fde_not_following_cie,
|
||||
Init32_do_not_fail_on_bad_next_entry, Init64,
|
||||
Init64_do_not_fail_on_bad_next_entry, Init64_fde_not_following_cie,
|
||||
Init_version1, Init_version4, GetFdeOffsetFromPc, GetCieFde32,
|
||||
GetCieFde64);
|
||||
|
||||
typedef ::testing::Types<uint32_t, uint64_t> DwarfDebugFrameTestTypes;
|
||||
INSTANTIATE_TYPED_TEST_CASE_P(, DwarfDebugFrameTest, DwarfDebugFrameTestTypes);
|
||||
|
|
|
@ -94,7 +94,13 @@ TYPED_TEST_P(DwarfEhFrameWithHdrTest, Init) {
|
|||
EXPECT_EQ(0x1000U, this->eh_frame_->TestGetEntriesDataOffset());
|
||||
EXPECT_EQ(0x100aU, this->eh_frame_->TestGetCurEntriesOffset());
|
||||
|
||||
// Verify a zero fde count fails to init.
|
||||
this->memory_.SetData32(0x1006, 0);
|
||||
ASSERT_FALSE(this->eh_frame_->Init(0x1000, 0x100));
|
||||
ASSERT_EQ(DWARF_ERROR_NO_FDES, this->eh_frame_->last_error());
|
||||
|
||||
// Verify an unexpected version will cause a fail.
|
||||
this->memory_.SetData32(0x1006, 126);
|
||||
this->memory_.SetData8(0x1000, 0);
|
||||
ASSERT_FALSE(this->eh_frame_->Init(0x1000, 0x100));
|
||||
ASSERT_EQ(DWARF_ERROR_UNSUPPORTED_VERSION, this->eh_frame_->last_error());
|
||||
|
|
|
@ -625,4 +625,127 @@ TEST(UnwindOfflineTest, jit_debug_arm32) {
|
|||
frame_info);
|
||||
}
|
||||
|
||||
// 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/");
|
||||
|
||||
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<char> 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<Memory> process_memory(memory);
|
||||
|
||||
char* cwd = getcwd(nullptr, 0);
|
||||
ASSERT_EQ(0, chdir(dir.c_str()));
|
||||
Unwinder unwinder(128, &maps, ®s, 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;
|
||||
EXPECT_EQ(
|
||||
" #00 pc 0000000000000550 waiter64\n"
|
||||
" #01 pc 0000000000000568 waiter64\n"
|
||||
" #02 pc 000000000000057c waiter64\n"
|
||||
" #03 pc 0000000000000590 waiter64\n"
|
||||
" #04 pc 00000000000a8e98 libc.so (__libc_init+88)\n",
|
||||
frame_info);
|
||||
}
|
||||
|
||||
// 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/");
|
||||
|
||||
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<char> 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<Memory> 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.Unwind();
|
||||
ASSERT_EQ(0, chdir(cwd));
|
||||
free(cwd);
|
||||
|
||||
std::string frame_info(DumpFrames(unwinder));
|
||||
ASSERT_EQ(5U, unwinder.NumFrames()) << "Unwind:\n" << frame_info;
|
||||
EXPECT_EQ(
|
||||
" #00 pc 00000685 waiter (call_level3+53)\n"
|
||||
" #01 pc 000006b7 waiter (call_level2+23)\n"
|
||||
" #02 pc 000006d7 waiter (call_level1+23)\n"
|
||||
" #03 pc 000006f7 waiter (main+23)\n"
|
||||
" #04 pc 00018275 libc.so\n",
|
||||
frame_info);
|
||||
}
|
||||
|
||||
} // namespace unwindstack
|
||||
|
|
Binary file not shown.
|
@ -0,0 +1,2 @@
|
|||
60a9fdf000-60a9fe0000 r-xp 0 00:00 0 waiter64
|
||||
7542cc0000-7542d8e000 r-xp 0 00:00 0 libc.so
|
|
@ -0,0 +1,4 @@
|
|||
pc: 60a9fdf550
|
||||
sp: 7fdd141990
|
||||
lr: 60a9fdf56c
|
||||
x29: 7fdd1419a0
|
Binary file not shown.
Binary file not shown.
BIN
libunwindstack/tests/files/offline/debug_frame_first_x86/libc.so
Normal file
BIN
libunwindstack/tests/files/offline/debug_frame_first_x86/libc.so
Normal file
Binary file not shown.
|
@ -0,0 +1,2 @@
|
|||
56598000-56599000 r-xp 0 00:00 0 waiter
|
||||
f7432000-f75e3000 r-xp 0 00:00 0 libc.so
|
|
@ -0,0 +1,9 @@
|
|||
eax: 1d88ef8c
|
||||
ebx: 56599fe8
|
||||
ecx: 3
|
||||
edx: ffcf9ea4
|
||||
ebp: ffcf9e48
|
||||
edi: f75e5000
|
||||
esi: 1
|
||||
esp: ffcf9e38
|
||||
eip: 56598685
|
Binary file not shown.
BIN
libunwindstack/tests/files/offline/debug_frame_first_x86/waiter
Normal file
BIN
libunwindstack/tests/files/offline/debug_frame_first_x86/waiter
Normal file
Binary file not shown.
Loading…
Reference in a new issue