summaryrefslogtreecommitdiffstats
path: root/libunwindstack
diff options
context:
space:
mode:
authorChristopher Ferris <cferris@google.com>2017-11-28 19:14:54 -0800
committerChristopher Ferris <cferris@google.com>2017-11-29 16:10:08 -0800
commitc3d79f7c0749a40d523b36807b8d7525cdb90654 (patch)
tree5258b70a35e2d76b7c9eebad3b37ce5c94e88e88 /libunwindstack
parentf819c1d94b732afca3738ba948c377b8bd1aa762 (diff)
downloadsystem_core-c3d79f7c0749a40d523b36807b8d7525cdb90654.tar.gz
system_core-c3d79f7c0749a40d523b36807b8d7525cdb90654.tar.bz2
system_core-c3d79f7c0749a40d523b36807b8d7525cdb90654.zip
Fix incorrect usage of relative pcs.
When stepping, it's necessary to use both the unaltered relative pc and the adjusted relative pc. If the adjusted pc is not used, the wrong unwind information can be used. Added new offline unit tests that take real data and verifies that it unwinds properly. Fix a bug in the map code that would not properly parse map data for a 64 bit process when done in a 32 bit process. Fix bug in eh_frame processing that didn't adjust the pc correctly. Fix unit tests related to the pc adjustment. Bug: 69475565 Test: Passes libbacktrace/libunwindstack unit tests. Test: Run debuggerd -b on processes on a hikey. Change-Id: Ic501a1c4549c5f61d2742a7105c42a960f2c892b
Diffstat (limited to 'libunwindstack')
-rw-r--r--libunwindstack/Android.bp3
-rw-r--r--libunwindstack/DwarfEhFrame.h2
-rw-r--r--libunwindstack/Elf.cpp12
-rw-r--r--libunwindstack/Maps.cpp12
-rw-r--r--libunwindstack/Unwinder.cpp28
-rw-r--r--libunwindstack/include/unwindstack/Elf.h4
-rw-r--r--libunwindstack/include/unwindstack/Unwinder.h2
-rw-r--r--libunwindstack/tests/DwarfEhFrameTest.cpp48
-rw-r--r--libunwindstack/tests/DwarfEhFrameWithHdrTest.cpp8
-rw-r--r--libunwindstack/tests/ElfTest.cpp10
-rw-r--r--libunwindstack/tests/ElfTestUtils.cpp4
-rw-r--r--libunwindstack/tests/ElfTestUtils.h3
-rw-r--r--libunwindstack/tests/UnwindOfflineTest.cpp153
-rw-r--r--libunwindstack/tests/files/offline/straddle_arm32/libbase.sobin0 -> 62552 bytes
-rw-r--r--libunwindstack/tests/files/offline/straddle_arm32/libc.sobin0 -> 840504 bytes
-rw-r--r--libunwindstack/tests/files/offline/straddle_arm32/maps.txt4
-rw-r--r--libunwindstack/tests/files/offline/straddle_arm32/regs.txt3
-rw-r--r--libunwindstack/tests/files/offline/straddle_arm32/stack.databin0 -> 232 bytes
-rw-r--r--libunwindstack/tests/files/offline/straddle_arm64/libunwindstack_testbin0 -> 5198416 bytes
-rw-r--r--libunwindstack/tests/files/offline/straddle_arm64/maps.txt2
-rw-r--r--libunwindstack/tests/files/offline/straddle_arm64/regs.txt4
-rw-r--r--libunwindstack/tests/files/offline/straddle_arm64/stack.databin0 -> 232 bytes
22 files changed, 238 insertions, 64 deletions
diff --git a/libunwindstack/Android.bp b/libunwindstack/Android.bp
index 40364fe40..75aa427f5 100644
--- a/libunwindstack/Android.bp
+++ b/libunwindstack/Android.bp
@@ -130,6 +130,7 @@ cc_test {
"tests/RegsStepIfSignalHandlerTest.cpp",
"tests/RegsTest.cpp",
"tests/SymbolsTest.cpp",
+ "tests/UnwindOfflineTest.cpp",
"tests/UnwindTest.cpp",
"tests/UnwinderTest.cpp",
],
@@ -153,6 +154,8 @@ cc_test {
data: [
"tests/files/elf32.xz",
"tests/files/elf64.xz",
+ "tests/files/offline/straddle_arm32/*",
+ "tests/files/offline/straddle_arm64/*",
],
}
diff --git a/libunwindstack/DwarfEhFrame.h b/libunwindstack/DwarfEhFrame.h
index 561d23a88..2589c89f2 100644
--- a/libunwindstack/DwarfEhFrame.h
+++ b/libunwindstack/DwarfEhFrame.h
@@ -40,7 +40,7 @@ class DwarfEhFrame : public DwarfSectionImpl<AddressType> {
uint64_t AdjustPcFromFde(uint64_t pc) override {
// The eh_frame uses relative pcs.
- return pc + this->memory_.cur_offset();
+ return pc + this->memory_.cur_offset() - 4;
}
};
diff --git a/libunwindstack/Elf.cpp b/libunwindstack/Elf.cpp
index 0c65895b9..48e33ee2b 100644
--- a/libunwindstack/Elf.cpp
+++ b/libunwindstack/Elf.cpp
@@ -104,8 +104,8 @@ bool Elf::GetFunctionName(uint64_t addr, std::string* name, uint64_t* func_offse
}
// The relative pc is always relative to the start of the map from which it comes.
-bool Elf::Step(uint64_t rel_pc, uint64_t elf_offset, Regs* regs, Memory* process_memory,
- bool* finished) {
+bool Elf::Step(uint64_t rel_pc, uint64_t adjusted_rel_pc, uint64_t elf_offset, Regs* regs,
+ Memory* process_memory, bool* finished) {
if (!valid_) {
return false;
}
@@ -117,16 +117,16 @@ bool Elf::Step(uint64_t rel_pc, uint64_t elf_offset, Regs* regs, Memory* process
}
// Adjust the load bias to get the real relative pc.
- if (rel_pc < load_bias_) {
+ if (adjusted_rel_pc < load_bias_) {
return false;
}
- rel_pc -= load_bias_;
+ adjusted_rel_pc -= load_bias_;
// Lock during the step which can update information in the object.
std::lock_guard<std::mutex> guard(lock_);
- return interface_->Step(rel_pc, regs, process_memory, finished) ||
+ return interface_->Step(adjusted_rel_pc, regs, process_memory, finished) ||
(gnu_debugdata_interface_ &&
- gnu_debugdata_interface_->Step(rel_pc, regs, process_memory, finished));
+ gnu_debugdata_interface_->Step(adjusted_rel_pc, regs, process_memory, finished));
}
bool Elf::IsValidElf(Memory* memory) {
diff --git a/libunwindstack/Maps.cpp b/libunwindstack/Maps.cpp
index 5012ffda1..56370c196 100644
--- a/libunwindstack/Maps.cpp
+++ b/libunwindstack/Maps.cpp
@@ -64,13 +64,13 @@ static MapInfo* InternalParseLine(const char* line) {
// 6f000000-6f01e000 rwxp 00000000 00:0c 16389419 /system/lib/libcomposer.so
char* str;
const char* old_str = line;
- uint64_t start = strtoul(old_str, &str, 16);
+ uint64_t start = strtoull(old_str, &str, 16);
if (old_str == str || *str++ != '-') {
return nullptr;
}
old_str = str;
- uint64_t end = strtoul(old_str, &str, 16);
+ uint64_t end = strtoull(old_str, &str, 16);
if (old_str == str || !std::isspace(*str++)) {
return nullptr;
}
@@ -112,14 +112,14 @@ static MapInfo* InternalParseLine(const char* line) {
}
old_str = str;
- uint64_t offset = strtoul(old_str, &str, 16);
+ uint64_t offset = strtoull(old_str, &str, 16);
if (old_str == str || !std::isspace(*str)) {
return nullptr;
}
// Ignore the 00:00 values.
old_str = str;
- (void)strtoul(old_str, &str, 16);
+ (void)strtoull(old_str, &str, 16);
if (old_str == str || *str++ != ':') {
return nullptr;
}
@@ -129,14 +129,14 @@ static MapInfo* InternalParseLine(const char* line) {
// Skip the inode.
old_str = str;
- (void)strtoul(str, &str, 16);
+ (void)strtoull(str, &str, 16);
if (old_str == str || !std::isspace(*str++)) {
return nullptr;
}
// Skip decimal digit.
old_str = str;
- (void)strtoul(old_str, &str, 10);
+ (void)strtoull(old_str, &str, 10);
if (old_str == str || (!std::isspace(*str) && *str != '\0')) {
return nullptr;
}
diff --git a/libunwindstack/Unwinder.cpp b/libunwindstack/Unwinder.cpp
index 2e46a115e..3092a62c2 100644
--- a/libunwindstack/Unwinder.cpp
+++ b/libunwindstack/Unwinder.cpp
@@ -32,27 +32,20 @@
namespace unwindstack {
-void Unwinder::FillInFrame(MapInfo* map_info, Elf* elf, uint64_t rel_pc, bool adjust_pc) {
+void Unwinder::FillInFrame(MapInfo* map_info, Elf* elf, uint64_t adjusted_rel_pc) {
size_t frame_num = frames_.size();
frames_.resize(frame_num + 1);
FrameData* frame = &frames_.at(frame_num);
frame->num = frame_num;
- frame->pc = regs_->pc();
frame->sp = regs_->sp();
- frame->rel_pc = rel_pc;
+ frame->rel_pc = adjusted_rel_pc;
if (map_info == nullptr) {
+ frame->pc = regs_->pc();
return;
}
- if (adjust_pc) {
- // Don't adjust the first frame pc.
- frame->rel_pc = regs_->GetAdjustedPc(rel_pc, elf);
-
- // Adjust the original pc.
- frame->pc -= rel_pc - frame->rel_pc;
- }
-
+ frame->pc = map_info->start + adjusted_rel_pc;
frame->map_name = map_info->name;
frame->map_offset = map_info->offset;
frame->map_start = map_info->start;
@@ -92,21 +85,29 @@ void Unwinder::Unwind(const std::vector<std::string>* initial_map_names_to_skip,
MapInfo* map_info = maps_->Find(regs_->pc());
uint64_t rel_pc;
+ uint64_t adjusted_rel_pc;
Elf* elf;
if (map_info == nullptr) {
rel_pc = regs_->pc();
+ adjusted_rel_pc = rel_pc;
} else {
if (ShouldStop(map_suffixes_to_ignore, map_info->name)) {
break;
}
elf = map_info->GetElf(process_memory_, true);
rel_pc = elf->GetRelPc(regs_->pc(), map_info);
+ if (adjust_pc) {
+ adjusted_rel_pc = regs_->GetAdjustedPc(rel_pc, elf);
+ } else {
+ adjusted_rel_pc = rel_pc;
+ }
}
if (map_info == nullptr || initial_map_names_to_skip == nullptr ||
std::find(initial_map_names_to_skip->begin(), initial_map_names_to_skip->end(),
basename(map_info->name.c_str())) == initial_map_names_to_skip->end()) {
- FillInFrame(map_info, elf, rel_pc, adjust_pc);
+ FillInFrame(map_info, elf, adjusted_rel_pc);
+
// Once a frame is added, stop skipping frames.
initial_map_names_to_skip = nullptr;
}
@@ -133,7 +134,8 @@ void Unwinder::Unwind(const std::vector<std::string>* initial_map_names_to_skip,
in_device_map = true;
} else {
bool finished;
- stepped = elf->Step(rel_pc, map_info->elf_offset, regs_, process_memory_.get(), &finished);
+ stepped = elf->Step(rel_pc, adjusted_rel_pc, map_info->elf_offset, regs_,
+ process_memory_.get(), &finished);
if (stepped && finished) {
break;
}
diff --git a/libunwindstack/include/unwindstack/Elf.h b/libunwindstack/include/unwindstack/Elf.h
index 71d7aca8f..da2ddc027 100644
--- a/libunwindstack/include/unwindstack/Elf.h
+++ b/libunwindstack/include/unwindstack/Elf.h
@@ -51,8 +51,8 @@ class Elf {
uint64_t GetRelPc(uint64_t pc, const MapInfo* map_info);
- bool Step(uint64_t rel_pc, uint64_t elf_offset, Regs* regs, Memory* process_memory,
- bool* finished);
+ bool Step(uint64_t rel_pc, uint64_t adjusted_rel_pc, uint64_t elf_offset, Regs* regs,
+ Memory* process_memory, bool* finished);
ElfInterface* CreateInterfaceFromMemory(Memory* memory);
diff --git a/libunwindstack/include/unwindstack/Unwinder.h b/libunwindstack/include/unwindstack/Unwinder.h
index 37a76b203..b64d46036 100644
--- a/libunwindstack/include/unwindstack/Unwinder.h
+++ b/libunwindstack/include/unwindstack/Unwinder.h
@@ -70,7 +70,7 @@ class Unwinder {
static std::string FormatFrame(const FrameData& frame, bool bits32);
private:
- void FillInFrame(MapInfo* map_info, Elf* elf, uint64_t rel_pc, bool adjust_pc);
+ void FillInFrame(MapInfo* map_info, Elf* elf, uint64_t adjusted_rel_pc);
size_t max_frames_;
Maps* maps_;
diff --git a/libunwindstack/tests/DwarfEhFrameTest.cpp b/libunwindstack/tests/DwarfEhFrameTest.cpp
index 53ee719d2..3a629f886 100644
--- a/libunwindstack/tests/DwarfEhFrameTest.cpp
+++ b/libunwindstack/tests/DwarfEhFrameTest.cpp
@@ -109,23 +109,23 @@ TYPED_TEST_P(DwarfEhFrameTest, Init32) {
this->eh_frame_->TestGetFdeInfo(0, &info);
EXPECT_EQ(0x5100U, info.offset);
- EXPECT_EQ(0x660cU, info.start);
- EXPECT_EQ(0x680cU, info.end);
+ EXPECT_EQ(0x6608U, info.start);
+ EXPECT_EQ(0x6808U, info.end);
this->eh_frame_->TestGetFdeInfo(1, &info);
EXPECT_EQ(0x5200U, info.offset);
- EXPECT_EQ(0x770cU, info.start);
- EXPECT_EQ(0x7a0cU, info.end);
+ EXPECT_EQ(0x7708U, info.start);
+ EXPECT_EQ(0x7a08U, info.end);
this->eh_frame_->TestGetFdeInfo(2, &info);
EXPECT_EQ(0x5400U, info.offset);
- EXPECT_EQ(0x890cU, info.start);
- EXPECT_EQ(0x8d0cU, info.end);
+ EXPECT_EQ(0x8908U, info.start);
+ EXPECT_EQ(0x8d08U, info.end);
this->eh_frame_->TestGetFdeInfo(3, &info);
EXPECT_EQ(0x5500U, info.offset);
- EXPECT_EQ(0x9a0cU, info.start);
- EXPECT_EQ(0x9f0cU, info.end);
+ EXPECT_EQ(0x9a08U, info.start);
+ EXPECT_EQ(0x9f08U, info.end);
}
TYPED_TEST_P(DwarfEhFrameTest, Init32_fde_not_following_cie) {
@@ -193,23 +193,23 @@ TYPED_TEST_P(DwarfEhFrameTest, Init64) {
this->eh_frame_->TestGetFdeInfo(0, &info);
EXPECT_EQ(0x5100U, info.offset);
- EXPECT_EQ(0x661cU, info.start);
- EXPECT_EQ(0x681cU, info.end);
+ EXPECT_EQ(0x6618U, info.start);
+ EXPECT_EQ(0x6818U, info.end);
this->eh_frame_->TestGetFdeInfo(1, &info);
EXPECT_EQ(0x5200U, info.offset);
- EXPECT_EQ(0x771cU, info.start);
- EXPECT_EQ(0x7a1cU, info.end);
+ EXPECT_EQ(0x7718U, info.start);
+ EXPECT_EQ(0x7a18U, info.end);
this->eh_frame_->TestGetFdeInfo(2, &info);
EXPECT_EQ(0x5400U, info.offset);
- EXPECT_EQ(0x891cU, info.start);
- EXPECT_EQ(0x8d1cU, info.end);
+ EXPECT_EQ(0x8918U, info.start);
+ EXPECT_EQ(0x8d18U, info.end);
this->eh_frame_->TestGetFdeInfo(3, &info);
EXPECT_EQ(0x5500U, info.offset);
- EXPECT_EQ(0x9a1cU, info.start);
- EXPECT_EQ(0x9f1cU, info.end);
+ EXPECT_EQ(0x9a18U, info.start);
+ EXPECT_EQ(0x9f18U, info.end);
}
TYPED_TEST_P(DwarfEhFrameTest, Init64_fde_not_following_cie) {
@@ -261,8 +261,8 @@ TYPED_TEST_P(DwarfEhFrameTest, Init_version1) {
typename DwarfEhFrame<TypeParam>::FdeInfo info(0, 0, 0);
this->eh_frame_->TestGetFdeInfo(0, &info);
EXPECT_EQ(0x5100U, info.offset);
- EXPECT_EQ(0x660aU, info.start);
- EXPECT_EQ(0x680aU, info.end);
+ EXPECT_EQ(0x6606U, info.start);
+ EXPECT_EQ(0x6806U, info.end);
}
TYPED_TEST_P(DwarfEhFrameTest, Init_version4) {
@@ -304,8 +304,8 @@ TYPED_TEST_P(DwarfEhFrameTest, Init_version4) {
typename DwarfEhFrame<TypeParam>::FdeInfo info(0, 0, 0);
this->eh_frame_->TestGetFdeInfo(0, &info);
EXPECT_EQ(0x5100U, info.offset);
- EXPECT_EQ(0x660aU, info.start);
- EXPECT_EQ(0x680aU, info.end);
+ EXPECT_EQ(0x6606U, info.start);
+ EXPECT_EQ(0x6806U, info.end);
}
TYPED_TEST_P(DwarfEhFrameTest, GetFdeOffsetFromPc) {
@@ -384,8 +384,8 @@ TYPED_TEST_P(DwarfEhFrameTest, GetCieFde32) {
ASSERT_TRUE(fde != nullptr);
EXPECT_EQ(0x14010U, fde->cfa_instructions_offset);
EXPECT_EQ(0x14024U, fde->cfa_instructions_end);
- EXPECT_EQ(0x1d00cU, fde->pc_start);
- EXPECT_EQ(0x1d10cU, fde->pc_end);
+ EXPECT_EQ(0x1d008U, fde->pc_start);
+ EXPECT_EQ(0x1d108U, fde->pc_end);
EXPECT_EQ(0xf000U, fde->cie_offset);
EXPECT_EQ(0U, fde->lsda_address);
@@ -428,8 +428,8 @@ TYPED_TEST_P(DwarfEhFrameTest, GetCieFde64) {
ASSERT_TRUE(fde != nullptr);
EXPECT_EQ(0x8024U, fde->cfa_instructions_offset);
EXPECT_EQ(0x820cU, fde->cfa_instructions_end);
- EXPECT_EQ(0xd01cU, fde->pc_start);
- EXPECT_EQ(0xd31cU, fde->pc_end);
+ EXPECT_EQ(0xd018U, fde->pc_start);
+ EXPECT_EQ(0xd318U, fde->pc_end);
EXPECT_EQ(0x6000U, fde->cie_offset);
EXPECT_EQ(0U, fde->lsda_address);
diff --git a/libunwindstack/tests/DwarfEhFrameWithHdrTest.cpp b/libunwindstack/tests/DwarfEhFrameWithHdrTest.cpp
index 1028ab9fe..64b325b20 100644
--- a/libunwindstack/tests/DwarfEhFrameWithHdrTest.cpp
+++ b/libunwindstack/tests/DwarfEhFrameWithHdrTest.cpp
@@ -345,8 +345,8 @@ TYPED_TEST_P(DwarfEhFrameWithHdrTest, GetCieFde32) {
ASSERT_TRUE(fde != nullptr);
EXPECT_EQ(0x14010U, fde->cfa_instructions_offset);
EXPECT_EQ(0x14024U, fde->cfa_instructions_end);
- EXPECT_EQ(0x1d00cU, fde->pc_start);
- EXPECT_EQ(0x1d10cU, fde->pc_end);
+ EXPECT_EQ(0x1d008U, fde->pc_start);
+ EXPECT_EQ(0x1d108U, fde->pc_end);
EXPECT_EQ(0xf000U, fde->cie_offset);
EXPECT_EQ(0U, fde->lsda_address);
@@ -387,8 +387,8 @@ TYPED_TEST_P(DwarfEhFrameWithHdrTest, GetCieFde64) {
ASSERT_TRUE(fde != nullptr);
EXPECT_EQ(0x8024U, fde->cfa_instructions_offset);
EXPECT_EQ(0x820cU, fde->cfa_instructions_end);
- EXPECT_EQ(0xd01cU, fde->pc_start);
- EXPECT_EQ(0xd31cU, fde->pc_end);
+ EXPECT_EQ(0xd018U, fde->pc_start);
+ EXPECT_EQ(0xd318U, fde->pc_end);
EXPECT_EQ(0x6000U, fde->cie_offset);
EXPECT_EQ(0U, fde->lsda_address);
diff --git a/libunwindstack/tests/ElfTest.cpp b/libunwindstack/tests/ElfTest.cpp
index 098f5f494..afd113d0d 100644
--- a/libunwindstack/tests/ElfTest.cpp
+++ b/libunwindstack/tests/ElfTest.cpp
@@ -132,7 +132,7 @@ TEST_F(ElfTest, elf_invalid) {
ASSERT_FALSE(elf.GetFunctionName(0, &name, &func_offset));
bool finished;
- ASSERT_FALSE(elf.Step(0, 0, nullptr, nullptr, &finished));
+ ASSERT_FALSE(elf.Step(0, 0, 0, nullptr, nullptr, &finished));
}
TEST_F(ElfTest, elf32_invalid_machine) {
@@ -306,7 +306,7 @@ TEST_F(ElfTest, step_in_signal_map) {
elf.FakeSetValid(true);
elf.FakeSetLoadBias(0);
bool finished;
- ASSERT_TRUE(elf.Step(0x1000, 0x2000, &regs, &process_memory, &finished));
+ ASSERT_TRUE(elf.Step(0x1000, 0x1000, 0x2000, &regs, &process_memory, &finished));
EXPECT_FALSE(finished);
EXPECT_EQ(15U, regs.pc());
EXPECT_EQ(13U, regs.sp());
@@ -339,7 +339,7 @@ TEST_F(ElfTest, step_in_interface) {
EXPECT_CALL(*interface, Step(0x1000, &regs, &process_memory, &finished))
.WillOnce(::testing::Return(true));
- ASSERT_TRUE(elf.Step(0x1000, 0x2000, &regs, &process_memory, &finished));
+ ASSERT_TRUE(elf.Step(0x1004, 0x1000, 0x2000, &regs, &process_memory, &finished));
}
TEST_F(ElfTest, step_in_interface_non_zero_load_bias) {
@@ -355,12 +355,12 @@ TEST_F(ElfTest, step_in_interface_non_zero_load_bias) {
// Invalid relative pc given load_bias.
bool finished;
- ASSERT_FALSE(elf.Step(0x1000, 0x2000, &regs, &process_memory, &finished));
+ ASSERT_FALSE(elf.Step(0x1004, 0x1000, 0x2000, &regs, &process_memory, &finished));
EXPECT_CALL(*interface, Step(0x3300, &regs, &process_memory, &finished))
.WillOnce(::testing::Return(true));
- ASSERT_TRUE(elf.Step(0x7300, 0x2000, &regs, &process_memory, &finished));
+ ASSERT_TRUE(elf.Step(0x7304, 0x7300, 0x2000, &regs, &process_memory, &finished));
}
} // namespace unwindstack
diff --git a/libunwindstack/tests/ElfTestUtils.cpp b/libunwindstack/tests/ElfTestUtils.cpp
index 069386b4e..69163ac94 100644
--- a/libunwindstack/tests/ElfTestUtils.cpp
+++ b/libunwindstack/tests/ElfTestUtils.cpp
@@ -47,7 +47,7 @@ void TestInitEhdr(Ehdr* ehdr, uint32_t elf_class, uint32_t machine_type) {
ehdr->e_ehsize = sizeof(Ehdr);
}
-static std::string GetTestFileDirectory() {
+std::string TestGetFileDirectory() {
std::string exec(testing::internal::GetArgvs()[0]);
auto const value = exec.find_last_of('/');
if (value == std::string::npos) {
@@ -102,7 +102,7 @@ void TestInitGnuDebugdata(uint32_t elf_class, uint32_t machine, bool init_gnu_de
offset = symtab_offset + 0x100;
if (init_gnu_debugdata) {
// Read in the compressed elf data and copy it in.
- name = GetTestFileDirectory();
+ name = TestGetFileDirectory();
if (elf_class == ELFCLASS32) {
name += "elf32.xz";
} else {
diff --git a/libunwindstack/tests/ElfTestUtils.h b/libunwindstack/tests/ElfTestUtils.h
index 6ef00e1d9..62cd59a29 100644
--- a/libunwindstack/tests/ElfTestUtils.h
+++ b/libunwindstack/tests/ElfTestUtils.h
@@ -18,6 +18,7 @@
#define _LIBUNWINDSTACK_TESTS_ELF_TEST_UTILS_H
#include <functional>
+#include <string>
namespace unwindstack {
@@ -30,6 +31,8 @@ template <typename Ehdr, typename Shdr>
void TestInitGnuDebugdata(uint32_t elf_class, uint32_t machine_type, bool init_gnu_debudata,
TestCopyFuncType copy_func);
+std::string TestGetFileDirectory();
+
} // namespace unwindstack
#endif // _LIBUNWINDSTACK_TESTS_ELF_TEST_UTILS_H
diff --git a/libunwindstack/tests/UnwindOfflineTest.cpp b/libunwindstack/tests/UnwindOfflineTest.cpp
new file mode 100644
index 000000000..d24abe493
--- /dev/null
+++ b/libunwindstack/tests/UnwindOfflineTest.cpp
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2017 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 <inttypes.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <gtest/gtest.h>
+
+#include <string>
+#include <vector>
+
+#include <unwindstack/Maps.h>
+#include <unwindstack/Memory.h>
+#include <unwindstack/Regs.h>
+#include <unwindstack/Unwinder.h>
+
+#include "Machine.h"
+
+#include "ElfTestUtils.h"
+
+namespace unwindstack {
+
+static std::string DumpFrames(Unwinder& unwinder) {
+ std::string str;
+ for (size_t i = 0; i < unwinder.NumFrames(); i++) {
+ str += unwinder.FormatFrame(i) + "\n";
+ }
+ return str;
+}
+
+TEST(UnwindOfflineTest, pc_straddle_arm32) {
+ std::string dir(TestGetFileDirectory() + "offline/straddle_arm32/");
+
+ 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", &reg_value));
+ regs[ARM_REG_PC] = reg_value;
+ ASSERT_EQ(1, fscanf(fp, "sp: %" SCNx64 "\n", &reg_value));
+ regs[ARM_REG_SP] = reg_value;
+ ASSERT_EQ(1, fscanf(fp, "lr: %" SCNx64 "\n", &reg_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<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(static_cast<uint32_t>(EM_ARM), regs.MachineType());
+
+ std::shared_ptr<Memory> process_memory(memory);
+
+ char* cwd = getcwd(nullptr, 0);
+ ASSERT_EQ(0, chdir(dir.c_str()));
+ Unwinder unwinder(128, &maps, &regs, 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;
+ EXPECT_EQ(
+ " #00 pc 0001a9f8 libc.so (abort+63)\n"
+ " #01 pc 00006a1b libbase.so (_ZN7android4base14DefaultAborterEPKc+6)\n"
+ " #02 pc 00007441 libbase.so (_ZN7android4base10LogMessageD2Ev+748)\n"
+ " #03 pc 00015149 /does/not/exist/libhidlbase.so\n",
+ frame_info);
+}
+
+TEST(UnwindOfflineTest, pc_straddle_arm64) {
+ std::string dir(TestGetFileDirectory() + "offline/straddle_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", &reg_value));
+ regs[ARM64_REG_PC] = reg_value;
+ ASSERT_EQ(1, fscanf(fp, "sp: %" SCNx64 "\n", &reg_value));
+ regs[ARM64_REG_SP] = reg_value;
+ ASSERT_EQ(1, fscanf(fp, "lr: %" SCNx64 "\n", &reg_value));
+ regs[ARM64_REG_LR] = reg_value;
+ ASSERT_EQ(1, fscanf(fp, "x29: %" SCNx64 "\n", &reg_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(static_cast<uint32_t>(EM_AARCH64), regs.MachineType());
+
+ std::shared_ptr<Memory> process_memory(memory);
+
+ char* cwd = getcwd(nullptr, 0);
+ ASSERT_EQ(0, chdir(dir.c_str()));
+ Unwinder unwinder(128, &maps, &regs, 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;
+ EXPECT_EQ(
+ " #00 pc 0000000000429fd8 libunwindstack_test (SignalInnerFunction+24)\n"
+ " #01 pc 000000000042a078 libunwindstack_test (SignalMiddleFunction+8)\n"
+ " #02 pc 000000000042a08c libunwindstack_test (SignalOuterFunction+8)\n"
+ " #03 pc 000000000042d8fc libunwindstack_test "
+ "(_ZN11unwindstackL19RemoteThroughSignalEij+20)\n"
+ " #04 pc 000000000042d8d8 libunwindstack_test "
+ "(_ZN11unwindstack37UnwindTest_remote_through_signal_Test8TestBodyEv+32)\n"
+ " #05 pc 0000000000455d70 libunwindstack_test (_ZN7testing4Test3RunEv+392)\n",
+ frame_info);
+}
+
+} // namespace unwindstack
diff --git a/libunwindstack/tests/files/offline/straddle_arm32/libbase.so b/libunwindstack/tests/files/offline/straddle_arm32/libbase.so
new file mode 100644
index 000000000..d1f16ee8a
--- /dev/null
+++ b/libunwindstack/tests/files/offline/straddle_arm32/libbase.so
Binary files differ
diff --git a/libunwindstack/tests/files/offline/straddle_arm32/libc.so b/libunwindstack/tests/files/offline/straddle_arm32/libc.so
new file mode 100644
index 000000000..4dc19ca6c
--- /dev/null
+++ b/libunwindstack/tests/files/offline/straddle_arm32/libc.so
Binary files differ
diff --git a/libunwindstack/tests/files/offline/straddle_arm32/maps.txt b/libunwindstack/tests/files/offline/straddle_arm32/maps.txt
new file mode 100644
index 000000000..8c26479fa
--- /dev/null
+++ b/libunwindstack/tests/files/offline/straddle_arm32/maps.txt
@@ -0,0 +1,4 @@
+f2d9a000-f2da7fff r-xp 00000000 00:00 0 libbase.so
+f3002000-f3005fff rw-p 00000000 00:00 0 [stack:25941]
+f31d0000-f326bfff r-xp 00000000 00:00 0 libc.so
+f3352000-f336bfff r-xp 00000000 00:00 0 /does/not/exist/libhidlbase.so
diff --git a/libunwindstack/tests/files/offline/straddle_arm32/regs.txt b/libunwindstack/tests/files/offline/straddle_arm32/regs.txt
new file mode 100644
index 000000000..3baedf365
--- /dev/null
+++ b/libunwindstack/tests/files/offline/straddle_arm32/regs.txt
@@ -0,0 +1,3 @@
+pc: f31ea9f8
+sp: e9c866f8
+lr: f31f179f
diff --git a/libunwindstack/tests/files/offline/straddle_arm32/stack.data b/libunwindstack/tests/files/offline/straddle_arm32/stack.data
new file mode 100644
index 000000000..83aeb4aaa
--- /dev/null
+++ b/libunwindstack/tests/files/offline/straddle_arm32/stack.data
Binary files differ
diff --git a/libunwindstack/tests/files/offline/straddle_arm64/libunwindstack_test b/libunwindstack/tests/files/offline/straddle_arm64/libunwindstack_test
new file mode 100644
index 000000000..092fc3aec
--- /dev/null
+++ b/libunwindstack/tests/files/offline/straddle_arm64/libunwindstack_test
Binary files differ
diff --git a/libunwindstack/tests/files/offline/straddle_arm64/maps.txt b/libunwindstack/tests/files/offline/straddle_arm64/maps.txt
new file mode 100644
index 000000000..bdf29b5fe
--- /dev/null
+++ b/libunwindstack/tests/files/offline/straddle_arm64/maps.txt
@@ -0,0 +1,2 @@
+00000064d05ab000-00000064d0a6cfff r-xp 00000000 00:00 0 libunwindstack_test
+0000007fe0d64000-0000007fe0d84fff rw-p 00000000 00:00 0 [stack]
diff --git a/libunwindstack/tests/files/offline/straddle_arm64/regs.txt b/libunwindstack/tests/files/offline/straddle_arm64/regs.txt
new file mode 100644
index 000000000..ff8a936d2
--- /dev/null
+++ b/libunwindstack/tests/files/offline/straddle_arm64/regs.txt
@@ -0,0 +1,4 @@
+pc: 00000064d09d4fd8
+sp: 0000007fe0d84040
+lr: 00000064d09d507c
+x29: 0000007fe0d84070
diff --git a/libunwindstack/tests/files/offline/straddle_arm64/stack.data b/libunwindstack/tests/files/offline/straddle_arm64/stack.data
new file mode 100644
index 000000000..824d0e2c3
--- /dev/null
+++ b/libunwindstack/tests/files/offline/straddle_arm64/stack.data
Binary files differ