From 0b06a590d9d949b9738266488d7fd2bc2d0b0557 Mon Sep 17 00:00:00 2001 From: Christopher Ferris Date: Fri, 19 Jan 2018 10:26:36 -0800 Subject: [PATCH] Add extra frame when dex_pc is non-zero. Use the art dex file library to read the dex data. Add unit tests for the UnwindDexFile code. Bug: 72070049 Test: All unit tests continue to pass. Test: Dumped the backtrace of the 137-cfi test while running in interpreter Test: mode and verified that the stack trace is correct. Did this on host Test: and for arm/arm64. Change-Id: Ia6f343318c5dd6968a954015a7d59fdf101575b0 --- debuggerd/Android.bp | 1 + libbacktrace/Android.bp | 24 ++ libbacktrace/UnwindDexFile.cpp | 162 ++++++++++++ libbacktrace/UnwindDexFile.h | 70 ++++++ libbacktrace/UnwindStack.cpp | 83 ++++++- libbacktrace/UnwindStackMap.cpp | 29 +++ libbacktrace/UnwindStackMap.h | 13 +- libbacktrace/include/backtrace/Backtrace.h | 1 - libbacktrace/unwind_dex_test.cpp | 273 +++++++++++++++++++++ 9 files changed, 651 insertions(+), 5 deletions(-) create mode 100644 libbacktrace/UnwindDexFile.cpp create mode 100644 libbacktrace/UnwindDexFile.h create mode 100644 libbacktrace/unwind_dex_test.cpp diff --git a/debuggerd/Android.bp b/debuggerd/Android.bp index fb8a20577..532352410 100644 --- a/debuggerd/Android.bp +++ b/debuggerd/Android.bp @@ -114,6 +114,7 @@ cc_library_static { "libbacktrace", "libunwind", "libunwindstack", + "libdexfile", "liblzma", "libcutils", ], diff --git a/libbacktrace/Android.bp b/libbacktrace/Android.bp index d9eed7638..94b193594 100644 --- a/libbacktrace/Android.bp +++ b/libbacktrace/Android.bp @@ -50,6 +50,7 @@ libbacktrace_sources = [ "BacktracePtrace.cpp", "thread_utils.c", "ThreadEntry.cpp", + "UnwindDexFile.cpp", "UnwindStack.cpp", "UnwindStackMap.cpp", ] @@ -92,13 +93,27 @@ cc_library { "liblog", "libunwind", "libunwindstack", + "libdexfile", ], static_libs: ["libcutils"], + + // libdexfile will eventually properly export headers, for now + // include these directly. + include_dirs: [ + "art/runtime", + ], + + header_libs: [ "jni_headers", ], }, android: { static_libs: ["libasync_safe"], }, + vendor: { + cflags: ["-DNO_LIBDEXFILE"], + exclude_srcs: ["UnwindDexFile.cpp"], + exclude_shared_libs: ["libdexfile"], + }, }, whole_static_libs: ["libdemangle"], } @@ -161,6 +176,8 @@ cc_test { "backtrace_test.cpp", "GetPss.cpp", "thread_utils.c", + + "unwind_dex_test.cpp", ], cflags: [ @@ -172,6 +189,7 @@ cc_test { shared_libs: [ "libbacktrace_test", "libbacktrace", + "libdexfile", "libbase", "libcutils", "liblog", @@ -212,6 +230,12 @@ cc_test { }, }, + // libdexfile will eventually properly export headers, for now + // include these directly. + include_dirs: [ + "art/runtime", + ], + data: [ "testdata/arm/*", "testdata/arm64/*", diff --git a/libbacktrace/UnwindDexFile.cpp b/libbacktrace/UnwindDexFile.cpp new file mode 100644 index 000000000..5780fbb9c --- /dev/null +++ b/libbacktrace/UnwindDexFile.cpp @@ -0,0 +1,162 @@ +/* + * 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 "UnwindDexFile.h" + +UnwindDexFile* UnwindDexFile::Create(uint64_t dex_file_offset_in_memory, + unwindstack::Memory* memory, unwindstack::MapInfo* info) { + if (!info->name.empty()) { + std::unique_ptr dex_file(new UnwindDexFileFromFile); + if (dex_file->Open(dex_file_offset_in_memory - info->start + info->offset, info->name)) { + return dex_file.release(); + } + } + + std::unique_ptr dex_file(new UnwindDexFileFromMemory); + if (dex_file->Open(dex_file_offset_in_memory, memory)) { + return dex_file.release(); + } + return nullptr; +} + +void UnwindDexFile::GetMethodInformation(uint64_t dex_offset, std::string* method_name, + uint64_t* method_offset) { + if (dex_file_ == nullptr) { + return; + } + + for (uint32_t i = 0; i < dex_file_->NumClassDefs(); ++i) { + const art::DexFile::ClassDef& class_def = dex_file_->GetClassDef(i); + const uint8_t* class_data = dex_file_->GetClassData(class_def); + if (class_data == nullptr) { + continue; + } + for (art::ClassDataItemIterator it(*dex_file_.get(), class_data); it.HasNext(); it.Next()) { + if (!it.IsAtMethod()) { + continue; + } + const art::DexFile::CodeItem* code_item = it.GetMethodCodeItem(); + if (code_item == nullptr) { + continue; + } + art::CodeItemInstructionAccessor code(*dex_file_.get(), code_item); + if (!code.HasCodeItem()) { + continue; + } + + uint64_t offset = reinterpret_cast(code.Insns()) - dex_file_->Begin(); + size_t size = code.InsnsSizeInCodeUnits() * sizeof(uint16_t); + if (offset <= dex_offset && dex_offset < offset + size) { + *method_name = dex_file_->PrettyMethod(it.GetMemberIndex(), false); + *method_offset = dex_offset - offset; + return; + } + } + } +} + +UnwindDexFileFromFile::~UnwindDexFileFromFile() { + if (size_ != 0) { + munmap(mapped_memory_, size_); + } +} + +bool UnwindDexFileFromFile::Open(uint64_t dex_file_offset_in_file, const std::string& file) { + android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(file.c_str(), O_RDONLY | O_CLOEXEC))); + if (fd == -1) { + return false; + } + struct stat buf; + if (fstat(fd, &buf) == -1) { + return false; + } + uint64_t length; + if (buf.st_size < 0 || + __builtin_add_overflow(dex_file_offset_in_file, sizeof(art::DexFile::Header), &length) || + static_cast(buf.st_size) < length) { + return false; + } + + mapped_memory_ = mmap(nullptr, buf.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if (mapped_memory_ == MAP_FAILED) { + return false; + } + size_ = buf.st_size; + + uint8_t* memory = reinterpret_cast(mapped_memory_); + + art::DexFile::Header* header = + reinterpret_cast(&memory[dex_file_offset_in_file]); + if (!art::StandardDexFile::IsMagicValid(header->magic_) && + !art::CompactDexFile::IsMagicValid(header->magic_)) { + return false; + } + + if (__builtin_add_overflow(dex_file_offset_in_file, header->file_size_, &length) || + static_cast(buf.st_size) < length) { + return false; + } + + art::DexFileLoader loader; + std::string error_msg; + auto dex = loader.Open(&memory[dex_file_offset_in_file], header->file_size_, "", 0, nullptr, + false, false, &error_msg); + dex_file_.reset(dex.release()); + return dex_file_ != nullptr; +} + +bool UnwindDexFileFromMemory::Open(uint64_t dex_file_offset_in_memory, unwindstack::Memory* memory) { + art::DexFile::Header header; + if (!memory->ReadFully(dex_file_offset_in_memory, &header, sizeof(header))) { + return false; + } + + if (!art::StandardDexFile::IsMagicValid(header.magic_) && + !art::CompactDexFile::IsMagicValid(header.magic_)) { + return false; + } + + memory_.resize(header.file_size_); + if (!memory->ReadFully(dex_file_offset_in_memory, memory_.data(), header.file_size_)) { + return false; + } + + art::DexFileLoader loader; + std::string error_msg; + auto dex = + loader.Open(memory_.data(), header.file_size_, "", 0, nullptr, false, false, &error_msg); + dex_file_.reset(dex.release()); + return dex_file_ != nullptr; +} diff --git a/libbacktrace/UnwindDexFile.h b/libbacktrace/UnwindDexFile.h new file mode 100644 index 000000000..dd70abafd --- /dev/null +++ b/libbacktrace/UnwindDexFile.h @@ -0,0 +1,70 @@ +/* + * 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. + */ + +#ifndef _LIBBACKTRACE_UNWIND_DEX_FILE_H +#define _LIBBACKTRACE_UNWIND_DEX_FILE_H + +#include + +#include +#include +#include + +#include + +namespace unwindstack { +class Memory; +struct MapInfo; +} // namespace unwindstack + +class UnwindDexFile { + public: + UnwindDexFile() = default; + virtual ~UnwindDexFile() = default; + + void GetMethodInformation(uint64_t dex_offset, std::string* method_name, uint64_t* method_offset); + + static UnwindDexFile* Create(uint64_t dex_file_offset_in_memory, unwindstack::Memory* memory, + unwindstack::MapInfo* info); + + protected: + std::unique_ptr dex_file_; +}; + +class UnwindDexFileFromFile : public UnwindDexFile { + public: + UnwindDexFileFromFile() = default; + virtual ~UnwindDexFileFromFile(); + + bool Open(uint64_t dex_file_offset_in_file, const std::string& name); + + private: + void* mapped_memory_ = nullptr; + size_t size_ = 0; +}; + +class UnwindDexFileFromMemory : public UnwindDexFile { + public: + UnwindDexFileFromMemory() = default; + virtual ~UnwindDexFileFromMemory() = default; + + bool Open(uint64_t dex_file_offset_in_memory, unwindstack::Memory* memory); + + private: + std::vector memory_; +}; + +#endif // _LIBBACKTRACE_UNWIND_DEX_FILE_H diff --git a/libbacktrace/UnwindStack.cpp b/libbacktrace/UnwindStack.cpp index a748d1b4f..91bcc6688 100644 --- a/libbacktrace/UnwindStack.cpp +++ b/libbacktrace/UnwindStack.cpp @@ -40,9 +40,67 @@ #include #include "BacktraceLog.h" +#ifndef NO_LIBDEXFILE +#include "UnwindDexFile.h" +#endif #include "UnwindStack.h" #include "UnwindStackMap.h" +static void FillInDexFrame(UnwindStackMap* stack_map, uint64_t dex_pc, + backtrace_frame_data_t* frame) { + // The DEX PC points into the .dex section within an ELF file. + // However, this is a BBS section manually mmaped to a .vdex file, + // so we need to get the following map to find the ELF data. + unwindstack::Maps* maps = stack_map->stack_maps(); + auto it = maps->begin(); + uint64_t rel_dex_pc; + unwindstack::MapInfo* info; + for (; it != maps->end(); ++it) { + auto entry = *it; + if (dex_pc >= entry->start && dex_pc < entry->end) { + info = entry; + rel_dex_pc = dex_pc - entry->start; + frame->map.start = entry->start; + frame->map.end = entry->end; + frame->map.offset = entry->offset; + frame->map.load_bias = entry->load_bias; + frame->map.flags = entry->flags; + frame->map.name = entry->name; + frame->rel_pc = rel_dex_pc; + break; + } + } + if (it == maps->end() || ++it == maps->end()) { + return; + } + + auto entry = *it; + auto process_memory = stack_map->process_memory(); + unwindstack::Elf* elf = entry->GetElf(process_memory, true); + if (!elf->valid()) { + return; + } + + // Adjust the relative dex by the offset. + rel_dex_pc += entry->elf_offset; + + uint64_t dex_offset; + if (!elf->GetFunctionName(rel_dex_pc, &frame->func_name, &dex_offset)) { + return; + } + frame->func_offset = dex_offset; + if (frame->func_name != "$dexfile") { + return; + } + +#ifndef NO_LIBDEXFILE + UnwindDexFile* dex_file = stack_map->GetDexFile(dex_pc - dex_offset, info); + if (dex_file != nullptr) { + dex_file->GetMethodInformation(dex_offset, &frame->func_name, &frame->func_offset); + } +#endif +} + bool Backtrace::Unwind(unwindstack::Regs* regs, BacktraceMap* back_map, std::vector* frames, size_t num_ignore_frames, std::vector* skip_names) { @@ -60,19 +118,25 @@ bool Backtrace::Unwind(unwindstack::Regs* regs, BacktraceMap* back_map, return true; } - frames->resize(unwinder.NumFrames() - num_ignore_frames); auto unwinder_frames = unwinder.frames(); + // Get the real number of frames we'll need. + size_t total_frames = 0; + for (size_t i = num_ignore_frames; i < unwinder.NumFrames(); i++, total_frames++) { + if (unwinder_frames[i].dex_pc != 0) { + total_frames++; + } + } + frames->resize(total_frames); size_t cur_frame = 0; for (size_t i = num_ignore_frames; i < unwinder.NumFrames(); i++, cur_frame++) { auto frame = &unwinder_frames[i]; backtrace_frame_data_t* back_frame = &frames->at(cur_frame); - back_frame->num = frame->num - num_ignore_frames; + back_frame->num = cur_frame; back_frame->rel_pc = frame->rel_pc; back_frame->pc = frame->pc; back_frame->sp = frame->sp; - back_frame->dex_pc = frame->dex_pc; back_frame->func_name = demangle(frame->function_name.c_str()); back_frame->func_offset = frame->function_offset; @@ -83,6 +147,19 @@ bool Backtrace::Unwind(unwindstack::Regs* regs, BacktraceMap* back_map, back_frame->map.offset = frame->map_offset; back_frame->map.load_bias = frame->map_load_bias; back_frame->map.flags = frame->map_flags; + + // Inject a frame that represents the dex pc data. + if (frame->dex_pc != 0) { + cur_frame++; + backtrace_frame_data_t* dex_frame = &frames->at(cur_frame); + dex_frame->num = cur_frame; + dex_frame->pc = frame->dex_pc; + dex_frame->rel_pc = frame->dex_pc; + dex_frame->sp = back_frame->sp; + dex_frame->stack_size = 0; + dex_frame->func_offset = 0; + FillInDexFrame(stack_map, frame->dex_pc, dex_frame); + } } return true; diff --git a/libbacktrace/UnwindStackMap.cpp b/libbacktrace/UnwindStackMap.cpp index 60c7952a9..11ff84a8b 100644 --- a/libbacktrace/UnwindStackMap.cpp +++ b/libbacktrace/UnwindStackMap.cpp @@ -26,11 +26,20 @@ #include #include +#include "UnwindDexFile.h" #include "UnwindStackMap.h" //------------------------------------------------------------------------- UnwindStackMap::UnwindStackMap(pid_t pid) : BacktraceMap(pid) {} +UnwindStackMap::~UnwindStackMap() { +#ifndef NO_LIBDEXFILE + for (auto& entry : dex_files_) { + delete entry.second; + } +#endif +} + bool UnwindStackMap::Build() { if (pid_ == 0) { pid_ = getpid(); @@ -118,6 +127,26 @@ std::shared_ptr UnwindStackMap::GetProcessMemory() { return process_memory_; } +#ifdef NO_LIBDEXFILE +UnwindDexFile* UnwindStackMap::GetDexFile(uint64_t, unwindstack::MapInfo*) { + return nullptr; +} +#else +UnwindDexFile* UnwindStackMap::GetDexFile(uint64_t dex_file_offset, unwindstack::MapInfo* info) { + // Lock while we get the data. + std::lock_guard guard(dex_lock_); + UnwindDexFile* dex_file; + auto entry = dex_files_.find(dex_file_offset); + if (entry == dex_files_.end()) { + dex_file = UnwindDexFile::Create(dex_file_offset, process_memory_.get(), info); + dex_files_[dex_file_offset] = dex_file; + } else { + dex_file = entry->second; + } + return dex_file; +} +#endif + //------------------------------------------------------------------------- // BacktraceMap create function. //------------------------------------------------------------------------- diff --git a/libbacktrace/UnwindStackMap.h b/libbacktrace/UnwindStackMap.h index 6b988095c..a815aae71 100644 --- a/libbacktrace/UnwindStackMap.h +++ b/libbacktrace/UnwindStackMap.h @@ -21,15 +21,20 @@ #include #include +#include +#include #include #include #include +// Forward declarations. +class UnwindDexFile; + class UnwindStackMap : public BacktraceMap { public: explicit UnwindStackMap(pid_t pid); - ~UnwindStackMap() = default; + ~UnwindStackMap(); bool Build() override; @@ -44,12 +49,18 @@ class UnwindStackMap : public BacktraceMap { unwindstack::JitDebug* GetJitDebug() { return jit_debug_.get(); } + UnwindDexFile* GetDexFile(uint64_t dex_file_offset, unwindstack::MapInfo* info); + protected: uint64_t GetLoadBias(size_t index) override; std::unique_ptr stack_maps_; std::shared_ptr process_memory_; std::unique_ptr jit_debug_; +#ifndef NO_LIBDEXFILE + std::mutex dex_lock_; + std::unordered_map dex_files_; +#endif }; #endif // _LIBBACKTRACE_UNWINDSTACK_MAP_H diff --git a/libbacktrace/include/backtrace/Backtrace.h b/libbacktrace/include/backtrace/Backtrace.h index 1b8ad19e3..18e9f61e8 100644 --- a/libbacktrace/include/backtrace/Backtrace.h +++ b/libbacktrace/include/backtrace/Backtrace.h @@ -81,7 +81,6 @@ struct backtrace_frame_data_t { uint64_t rel_pc; // The relative pc. uint64_t sp; // The top of the stack. size_t stack_size; // The size of the stack, zero indicate an unknown stack size. - uint64_t dex_pc; // If non-zero, the Dex PC for the ART interpreter. backtrace_map_t map; // The map associated with the given pc. std::string func_name; // The function name associated with this pc, NULL if not found. uint64_t func_offset; // pc relative to the start of the function, only valid if func_name is not diff --git a/libbacktrace/unwind_dex_test.cpp b/libbacktrace/unwind_dex_test.cpp new file mode 100644 index 000000000..449e66296 --- /dev/null +++ b/libbacktrace/unwind_dex_test.cpp @@ -0,0 +1,273 @@ +/* + * 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 "UnwindDexFile.h" + +class MemoryFake : public unwindstack::Memory { + public: + MemoryFake() = default; + virtual ~MemoryFake() = default; + + size_t Read(uint64_t addr, void* buffer, size_t size) override; + + void SetMemory(uint64_t addr, const void* memory, size_t length); + + void Clear() { data_.clear(); } + + private: + std::unordered_map data_; +}; + +void MemoryFake::SetMemory(uint64_t addr, const void* memory, size_t length) { + const uint8_t* src = reinterpret_cast(memory); + for (size_t i = 0; i < length; i++, addr++) { + auto value = data_.find(addr); + if (value != data_.end()) { + value->second = src[i]; + } else { + data_.insert({addr, src[i]}); + } + } +} + +size_t MemoryFake::Read(uint64_t addr, void* memory, size_t size) { + uint8_t* dst = reinterpret_cast(memory); + for (size_t i = 0; i < size; i++, addr++) { + auto value = data_.find(addr); + if (value == data_.end()) { + return i; + } + dst[i] = value->second; + } + return size; +} + +// Borrowed from art/dex/dex_file_test.cc. +static constexpr uint32_t kDexData[] = { + 0x0a786564, 0x00383330, 0xc98b3ab8, 0xf3749d94, 0xaecca4d8, 0xffc7b09a, 0xdca9ca7f, 0x5be5deab, + 0x00000220, 0x00000070, 0x12345678, 0x00000000, 0x00000000, 0x0000018c, 0x00000008, 0x00000070, + 0x00000004, 0x00000090, 0x00000002, 0x000000a0, 0x00000000, 0x00000000, 0x00000003, 0x000000b8, + 0x00000001, 0x000000d0, 0x00000130, 0x000000f0, 0x00000122, 0x0000012a, 0x00000132, 0x00000146, + 0x00000151, 0x00000154, 0x00000158, 0x0000016d, 0x00000001, 0x00000002, 0x00000004, 0x00000006, + 0x00000004, 0x00000002, 0x00000000, 0x00000005, 0x00000002, 0x0000011c, 0x00000000, 0x00000000, + 0x00010000, 0x00000007, 0x00000001, 0x00000000, 0x00000000, 0x00000001, 0x00000001, 0x00000000, + 0x00000003, 0x00000000, 0x0000017e, 0x00000000, 0x00010001, 0x00000001, 0x00000173, 0x00000004, + 0x00021070, 0x000e0000, 0x00010001, 0x00000000, 0x00000178, 0x00000001, 0x0000000e, 0x00000001, + 0x3c060003, 0x74696e69, 0x4c06003e, 0x6e69614d, 0x4c12003b, 0x6176616a, 0x6e616c2f, 0x624f2f67, + 0x7463656a, 0x4d09003b, 0x2e6e6961, 0x6176616a, 0x00560100, 0x004c5602, 0x6a4c5b13, 0x2f617661, + 0x676e616c, 0x7274532f, 0x3b676e69, 0x616d0400, 0x01006e69, 0x000e0700, 0x07000103, 0x0000000e, + 0x81000002, 0x01f00480, 0x02880901, 0x0000000c, 0x00000000, 0x00000001, 0x00000000, 0x00000001, + 0x00000008, 0x00000070, 0x00000002, 0x00000004, 0x00000090, 0x00000003, 0x00000002, 0x000000a0, + 0x00000005, 0x00000003, 0x000000b8, 0x00000006, 0x00000001, 0x000000d0, 0x00002001, 0x00000002, + 0x000000f0, 0x00001001, 0x00000001, 0x0000011c, 0x00002002, 0x00000008, 0x00000122, 0x00002003, + 0x00000002, 0x00000173, 0x00002000, 0x00000001, 0x0000017e, 0x00001000, 0x00000001, 0x0000018c, +}; + +TEST(UnwindDexTest, from_file_open_non_exist) { + UnwindDexFileFromFile dex_file; + ASSERT_FALSE(dex_file.Open(0, "/file/does/not/exist")); +} + +TEST(UnwindDexTest, from_file_open_too_small) { + TemporaryFile tf; + ASSERT_TRUE(tf.fd != -1); + + ASSERT_EQ(sizeof(art::DexFile::Header) - 2, + static_cast( + TEMP_FAILURE_RETRY(write(tf.fd, kDexData, sizeof(art::DexFile::Header)) - 2))); + + // Header too small. + UnwindDexFileFromFile dex_file; + ASSERT_FALSE(dex_file.Open(0, tf.path)); + + // Header correct, file too small. + ASSERT_EQ(0, lseek(tf.fd, 0, SEEK_SET)); + ASSERT_EQ(sizeof(art::DexFile::Header), static_cast(TEMP_FAILURE_RETRY(write( + tf.fd, kDexData, sizeof(art::DexFile::Header))))); + ASSERT_FALSE(dex_file.Open(0, tf.path)); +} + +TEST(UnwindDexTest, from_file_open) { + TemporaryFile tf; + ASSERT_TRUE(tf.fd != -1); + + ASSERT_EQ(sizeof(kDexData), + static_cast(TEMP_FAILURE_RETRY(write(tf.fd, kDexData, sizeof(kDexData))))); + + UnwindDexFileFromFile dex_file; + ASSERT_TRUE(dex_file.Open(0, tf.path)); +} + +TEST(UnwindDexTest, from_file_open_non_zero_offset) { + TemporaryFile tf; + ASSERT_TRUE(tf.fd != -1); + + ASSERT_EQ(0x100, lseek(tf.fd, 0x100, SEEK_SET)); + ASSERT_EQ(sizeof(kDexData), + static_cast(TEMP_FAILURE_RETRY(write(tf.fd, kDexData, sizeof(kDexData))))); + + UnwindDexFileFromFile dex_file; + ASSERT_TRUE(dex_file.Open(0x100, tf.path)); +} + +TEST(UnwindDexTest, from_memory_fail_too_small_for_header) { + MemoryFake memory; + + memory.SetMemory(0x1000, kDexData, sizeof(art::DexFile::Header) - 1); + UnwindDexFileFromMemory dex_file; + + ASSERT_FALSE(dex_file.Open(0x1000, &memory)); +} + +TEST(UnwindDexTest, from_memory_fail_too_small_for_data) { + MemoryFake memory; + + memory.SetMemory(0x1000, kDexData, sizeof(kDexData) - 2); + UnwindDexFileFromMemory dex_file; + + ASSERT_FALSE(dex_file.Open(0x1000, &memory)); +} + +TEST(UnwindDexTest, from_memory_open) { + MemoryFake memory; + + memory.SetMemory(0x1000, kDexData, sizeof(kDexData)); + UnwindDexFileFromMemory dex_file; + + ASSERT_TRUE(dex_file.Open(0x1000, &memory)); +} + +TEST(UnwindDexTest, create_using_file) { + TemporaryFile tf; + ASSERT_TRUE(tf.fd != -1); + + ASSERT_EQ(0x500, lseek(tf.fd, 0x500, SEEK_SET)); + ASSERT_EQ(sizeof(kDexData), + static_cast(TEMP_FAILURE_RETRY(write(tf.fd, kDexData, sizeof(kDexData))))); + + MemoryFake memory; + unwindstack::MapInfo info(0, 0x10000, 0, 0x5, tf.path); + std::unique_ptr dex_file(UnwindDexFile::Create(0x500, &memory, &info)); + ASSERT_TRUE(dex_file != nullptr); +} + +TEST(UnwindDexTest, create_using_file_non_zero_start) { + TemporaryFile tf; + ASSERT_TRUE(tf.fd != -1); + + ASSERT_EQ(0x500, lseek(tf.fd, 0x500, SEEK_SET)); + ASSERT_EQ(sizeof(kDexData), + static_cast(TEMP_FAILURE_RETRY(write(tf.fd, kDexData, sizeof(kDexData))))); + + MemoryFake memory; + unwindstack::MapInfo info(0x100, 0x10000, 0, 0x5, tf.path); + std::unique_ptr dex_file(UnwindDexFile::Create(0x600, &memory, &info)); + ASSERT_TRUE(dex_file != nullptr); +} + +TEST(UnwindDexTest, create_using_file_non_zero_offset) { + TemporaryFile tf; + ASSERT_TRUE(tf.fd != -1); + + ASSERT_EQ(0x500, lseek(tf.fd, 0x500, SEEK_SET)); + ASSERT_EQ(sizeof(kDexData), + static_cast(TEMP_FAILURE_RETRY(write(tf.fd, kDexData, sizeof(kDexData))))); + + MemoryFake memory; + unwindstack::MapInfo info(0x100, 0x10000, 0x200, 0x5, tf.path); + std::unique_ptr dex_file(UnwindDexFile::Create(0x400, &memory, &info)); + ASSERT_TRUE(dex_file != nullptr); +} + +TEST(UnwindDexTest, create_using_memory_empty_file) { + MemoryFake memory; + memory.SetMemory(0x4000, kDexData, sizeof(kDexData)); + unwindstack::MapInfo info(0x100, 0x10000, 0x200, 0x5, ""); + std::unique_ptr dex_file(UnwindDexFile::Create(0x4000, &memory, &info)); + ASSERT_TRUE(dex_file != nullptr); +} + +TEST(UnwindDexTest, create_using_memory_file_does_not_exist) { + MemoryFake memory; + memory.SetMemory(0x4000, kDexData, sizeof(kDexData)); + unwindstack::MapInfo info(0x100, 0x10000, 0x200, 0x5, "/does/not/exist"); + std::unique_ptr dex_file(UnwindDexFile::Create(0x4000, &memory, &info)); + ASSERT_TRUE(dex_file != nullptr); +} + +TEST(UnwindDexTest, create_using_memory_file_is_malformed) { + TemporaryFile tf; + ASSERT_TRUE(tf.fd != -1); + + ASSERT_EQ(sizeof(kDexData) - 10, + static_cast(TEMP_FAILURE_RETRY(write(tf.fd, kDexData, sizeof(kDexData) - 10)))); + + MemoryFake memory; + memory.SetMemory(0x4000, kDexData, sizeof(kDexData)); + unwindstack::MapInfo info(0x4000, 0x10000, 0x200, 0x5, "/does/not/exist"); + std::unique_ptr dex_file(UnwindDexFile::Create(0x4000, &memory, &info)); + ASSERT_TRUE(dex_file != nullptr); + + // Check it came from memory by clearing memory and verifying it fails. + memory.Clear(); + dex_file.reset(UnwindDexFile::Create(0x4000, &memory, &info)); + ASSERT_TRUE(dex_file == nullptr); +} + +TEST(UnwindDexTest, get_method_not_opened) { + std::string method("something"); + uint64_t method_offset = 100; + UnwindDexFile dex_file; + dex_file.GetMethodInformation(0x100, &method, &method_offset); + EXPECT_EQ("something", method); + EXPECT_EQ(100U, method_offset); +} + +TEST(UnwindDexTest, get_method) { + MemoryFake memory; + memory.SetMemory(0x4000, kDexData, sizeof(kDexData)); + unwindstack::MapInfo info(0x100, 0x10000, 0x200, 0x5, ""); + std::unique_ptr dex_file(UnwindDexFile::Create(0x4000, &memory, &info)); + ASSERT_TRUE(dex_file != nullptr); + + std::string method; + uint64_t method_offset; + dex_file->GetMethodInformation(0x102, &method, &method_offset); + EXPECT_EQ("Main.", method); + EXPECT_EQ(2U, method_offset); + + method = "not_in_a_method"; + method_offset = 0x123; + dex_file->GetMethodInformation(0x100000, &method, &method_offset); + EXPECT_EQ("not_in_a_method", method); + EXPECT_EQ(0x123U, method_offset); +}