Merge "libbacktrace_offline: support .ARM.exidx."
am: aece425166
Change-Id: I7b3821a215ed07753e3f8e71509807cf7639af3f
This commit is contained in:
commit
76977937e2
17 changed files with 877 additions and 227 deletions
|
|
@ -117,4 +117,17 @@ cc_library_shared {
|
||||||
},
|
},
|
||||||
cflags: ["-O0"],
|
cflags: ["-O0"],
|
||||||
srcs: ["backtrace_testlib.c"],
|
srcs: ["backtrace_testlib.c"],
|
||||||
}
|
|
||||||
|
target: {
|
||||||
|
linux: {
|
||||||
|
shared_libs: [
|
||||||
|
"libunwind",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
android: {
|
||||||
|
shared_libs: [
|
||||||
|
"libunwind",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -111,8 +111,6 @@ backtrace_test_static_libraries_host := \
|
||||||
backtrace_test_ldlibs_host += \
|
backtrace_test_ldlibs_host += \
|
||||||
-ldl \
|
-ldl \
|
||||||
|
|
||||||
backtrace_test_strip_module := false
|
|
||||||
|
|
||||||
module := backtrace_test
|
module := backtrace_test
|
||||||
module_tag := debug
|
module_tag := debug
|
||||||
build_type := target
|
build_type := target
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,48 @@ extern "C" {
|
||||||
|
|
||||||
#include "BacktraceLog.h"
|
#include "BacktraceLog.h"
|
||||||
|
|
||||||
|
struct EhFrame {
|
||||||
|
uint64_t hdr_vaddr;
|
||||||
|
uint64_t vaddr;
|
||||||
|
uint64_t fde_table_offset;
|
||||||
|
uintptr_t min_func_vaddr;
|
||||||
|
std::vector<uint8_t> hdr_data;
|
||||||
|
std::vector<uint8_t> data;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ArmIdxEntry {
|
||||||
|
uint32_t func_offset;
|
||||||
|
uint32_t value;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ArmExidx {
|
||||||
|
uint64_t exidx_vaddr;
|
||||||
|
uint64_t extab_vaddr;
|
||||||
|
std::vector<ArmIdxEntry> exidx_data;
|
||||||
|
std::vector<uint8_t> extab_data;
|
||||||
|
// There is a one-to-one map from exidx_data.func_offset to func_vaddr_array.
|
||||||
|
std::vector<uint32_t> func_vaddr_array;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DebugFrameInfo {
|
||||||
|
bool has_arm_exidx;
|
||||||
|
bool has_eh_frame;
|
||||||
|
bool has_debug_frame;
|
||||||
|
bool has_gnu_debugdata;
|
||||||
|
|
||||||
|
EhFrame eh_frame;
|
||||||
|
ArmExidx arm_exidx;
|
||||||
|
|
||||||
|
uint64_t min_vaddr;
|
||||||
|
uint64_t text_end_vaddr;
|
||||||
|
|
||||||
|
DebugFrameInfo() : has_arm_exidx(false), has_eh_frame(false),
|
||||||
|
has_debug_frame(false), has_gnu_debugdata(false) { }
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::unordered_map<std::string, std::unique_ptr<DebugFrameInfo>>& g_debug_frames =
|
||||||
|
*new std::unordered_map<std::string, std::unique_ptr<DebugFrameInfo>>;
|
||||||
|
|
||||||
void Space::Clear() {
|
void Space::Clear() {
|
||||||
start = 0;
|
start = 0;
|
||||||
end = 0;
|
end = 0;
|
||||||
|
|
@ -207,23 +249,18 @@ size_t BacktraceOffline::Read(uintptr_t addr, uint8_t* buffer, size_t bytes) {
|
||||||
if (read_size != 0) {
|
if (read_size != 0) {
|
||||||
return read_size;
|
return read_size;
|
||||||
}
|
}
|
||||||
|
read_size = arm_exidx_space_.Read(addr, buffer, bytes);
|
||||||
|
if (read_size != 0) {
|
||||||
|
return read_size;
|
||||||
|
}
|
||||||
|
read_size = arm_extab_space_.Read(addr, buffer, bytes);
|
||||||
|
if (read_size != 0) {
|
||||||
|
return read_size;
|
||||||
|
}
|
||||||
read_size = stack_space_.Read(addr, buffer, bytes);
|
read_size = stack_space_.Read(addr, buffer, bytes);
|
||||||
return read_size;
|
return read_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool FileOffsetToVaddr(
|
|
||||||
const std::vector<DebugFrameInfo::EhFrame::ProgramHeader>& program_headers,
|
|
||||||
uint64_t file_offset, uint64_t* vaddr) {
|
|
||||||
for (auto& header : program_headers) {
|
|
||||||
if (file_offset >= header.file_offset && file_offset < header.file_offset + header.file_size) {
|
|
||||||
// TODO: Consider load_bias?
|
|
||||||
*vaddr = file_offset - header.file_offset + header.vaddr;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool BacktraceOffline::FindProcInfo(unw_addr_space_t addr_space, uint64_t ip,
|
bool BacktraceOffline::FindProcInfo(unw_addr_space_t addr_space, uint64_t ip,
|
||||||
unw_proc_info_t* proc_info, int need_unwind_info) {
|
unw_proc_info_t* proc_info, int need_unwind_info) {
|
||||||
backtrace_map_t map;
|
backtrace_map_t map;
|
||||||
|
|
@ -236,45 +273,90 @@ bool BacktraceOffline::FindProcInfo(unw_addr_space_t addr_space, uint64_t ip,
|
||||||
if (debug_frame == nullptr) {
|
if (debug_frame == nullptr) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (debug_frame->is_eh_frame) {
|
|
||||||
uint64_t ip_offset = ip - map.start + map.offset;
|
|
||||||
uint64_t ip_vaddr; // vaddr in the elf file.
|
|
||||||
bool result = FileOffsetToVaddr(debug_frame->eh_frame.program_headers, ip_offset, &ip_vaddr);
|
|
||||||
if (!result) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Calculate the addresses where .eh_frame_hdr and .eh_frame stay when the process was running.
|
|
||||||
eh_frame_hdr_space_.start = (ip - ip_vaddr) + debug_frame->eh_frame.eh_frame_hdr_vaddr;
|
|
||||||
eh_frame_hdr_space_.end =
|
|
||||||
eh_frame_hdr_space_.start + debug_frame->eh_frame.eh_frame_hdr_data.size();
|
|
||||||
eh_frame_hdr_space_.data = debug_frame->eh_frame.eh_frame_hdr_data.data();
|
|
||||||
|
|
||||||
eh_frame_space_.start = (ip - ip_vaddr) + debug_frame->eh_frame.eh_frame_vaddr;
|
|
||||||
eh_frame_space_.end = eh_frame_space_.start + debug_frame->eh_frame.eh_frame_data.size();
|
|
||||||
eh_frame_space_.data = debug_frame->eh_frame.eh_frame_data.data();
|
|
||||||
|
|
||||||
unw_dyn_info di;
|
|
||||||
memset(&di, '\0', sizeof(di));
|
|
||||||
di.start_ip = map.start;
|
|
||||||
di.end_ip = map.end;
|
|
||||||
di.format = UNW_INFO_FORMAT_REMOTE_TABLE;
|
|
||||||
di.u.rti.name_ptr = 0;
|
|
||||||
di.u.rti.segbase = eh_frame_hdr_space_.start;
|
|
||||||
di.u.rti.table_data =
|
|
||||||
eh_frame_hdr_space_.start + debug_frame->eh_frame.fde_table_offset_in_eh_frame_hdr;
|
|
||||||
di.u.rti.table_len = (eh_frame_hdr_space_.end - di.u.rti.table_data) / sizeof(unw_word_t);
|
|
||||||
int ret = dwarf_search_unwind_table(addr_space, ip, &di, proc_info, need_unwind_info, this);
|
|
||||||
return ret == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
eh_frame_hdr_space_.Clear();
|
eh_frame_hdr_space_.Clear();
|
||||||
eh_frame_space_.Clear();
|
eh_frame_space_.Clear();
|
||||||
unw_dyn_info_t di;
|
arm_exidx_space_.Clear();
|
||||||
unw_word_t segbase = map.start - map.offset;
|
arm_extab_space_.Clear();
|
||||||
int found = dwarf_find_debug_frame(0, &di, ip, segbase, filename.c_str(), map.start, map.end);
|
|
||||||
if (found == 1) {
|
// vaddr in the elf file.
|
||||||
int ret = dwarf_search_unwind_table(addr_space, ip, &di, proc_info, need_unwind_info, this);
|
uint64_t ip_vaddr = ip - map.start + debug_frame->min_vaddr;
|
||||||
return ret == 0;
|
if (debug_frame->has_arm_exidx) {
|
||||||
|
auto& func_vaddrs = debug_frame->arm_exidx.func_vaddr_array;
|
||||||
|
if (ip_vaddr >= func_vaddrs[0] && ip_vaddr < debug_frame->text_end_vaddr) {
|
||||||
|
// Use binary search to find the correct function.
|
||||||
|
auto it = std::upper_bound(func_vaddrs.begin(), func_vaddrs.end(),
|
||||||
|
static_cast<uint32_t>(ip_vaddr));
|
||||||
|
if (it != func_vaddrs.begin()) {
|
||||||
|
--it;
|
||||||
|
// Found the exidx entry.
|
||||||
|
size_t index = it - func_vaddrs.begin();
|
||||||
|
|
||||||
|
proc_info->format = UNW_INFO_FORMAT_ARM_EXIDX;
|
||||||
|
proc_info->unwind_info = reinterpret_cast<void*>(
|
||||||
|
static_cast<uintptr_t>(index * sizeof(ArmIdxEntry) +
|
||||||
|
debug_frame->arm_exidx.exidx_vaddr +
|
||||||
|
debug_frame->min_vaddr));
|
||||||
|
|
||||||
|
// Prepare arm_exidx space and arm_extab space.
|
||||||
|
arm_exidx_space_.start = debug_frame->min_vaddr + debug_frame->arm_exidx.exidx_vaddr;
|
||||||
|
arm_exidx_space_.end = arm_exidx_space_.start +
|
||||||
|
debug_frame->arm_exidx.exidx_data.size() * sizeof(ArmIdxEntry);
|
||||||
|
arm_exidx_space_.data = reinterpret_cast<const uint8_t*>(
|
||||||
|
debug_frame->arm_exidx.exidx_data.data());
|
||||||
|
|
||||||
|
arm_extab_space_.start = debug_frame->min_vaddr + debug_frame->arm_exidx.extab_vaddr;
|
||||||
|
arm_extab_space_.end = arm_extab_space_.start +
|
||||||
|
debug_frame->arm_exidx.extab_data.size();
|
||||||
|
arm_extab_space_.data = debug_frame->arm_exidx.extab_data.data();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (debug_frame->has_eh_frame) {
|
||||||
|
if (ip_vaddr >= debug_frame->eh_frame.min_func_vaddr &&
|
||||||
|
ip_vaddr < debug_frame->text_end_vaddr) {
|
||||||
|
// Prepare eh_frame_hdr space and eh_frame space.
|
||||||
|
eh_frame_hdr_space_.start = ip - ip_vaddr + debug_frame->eh_frame.hdr_vaddr;
|
||||||
|
eh_frame_hdr_space_.end =
|
||||||
|
eh_frame_hdr_space_.start + debug_frame->eh_frame.hdr_data.size();
|
||||||
|
eh_frame_hdr_space_.data = debug_frame->eh_frame.hdr_data.data();
|
||||||
|
|
||||||
|
eh_frame_space_.start = ip - ip_vaddr + debug_frame->eh_frame.vaddr;
|
||||||
|
eh_frame_space_.end = eh_frame_space_.start + debug_frame->eh_frame.data.size();
|
||||||
|
eh_frame_space_.data = debug_frame->eh_frame.data.data();
|
||||||
|
|
||||||
|
unw_dyn_info di;
|
||||||
|
memset(&di, '\0', sizeof(di));
|
||||||
|
di.start_ip = map.start;
|
||||||
|
di.end_ip = map.end;
|
||||||
|
di.format = UNW_INFO_FORMAT_REMOTE_TABLE;
|
||||||
|
di.u.rti.name_ptr = 0;
|
||||||
|
di.u.rti.segbase = eh_frame_hdr_space_.start;
|
||||||
|
di.u.rti.table_data =
|
||||||
|
eh_frame_hdr_space_.start + debug_frame->eh_frame.fde_table_offset;
|
||||||
|
di.u.rti.table_len = (eh_frame_hdr_space_.end - di.u.rti.table_data) / sizeof(unw_word_t);
|
||||||
|
// TODO: Do it ourselves is more efficient than calling this function.
|
||||||
|
int ret = dwarf_search_unwind_table(addr_space, ip, &di, proc_info, need_unwind_info, this);
|
||||||
|
if (ret == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (debug_frame->has_debug_frame || debug_frame->has_gnu_debugdata) {
|
||||||
|
unw_dyn_info_t di;
|
||||||
|
unw_word_t segbase = map.start - map.offset;
|
||||||
|
// TODO: http://b/32916571
|
||||||
|
// TODO: Do it ourselves is more efficient than calling libunwind functions.
|
||||||
|
int found = dwarf_find_debug_frame(0, &di, ip, segbase, filename.c_str(), map.start, map.end);
|
||||||
|
if (found == 1) {
|
||||||
|
int ret = dwarf_search_unwind_table(addr_space, ip, &di, proc_info, need_unwind_info, this);
|
||||||
|
if (ret == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -462,33 +544,18 @@ std::string BacktraceOffline::GetFunctionNameRaw(uintptr_t, uintptr_t* offset) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unordered_map<std::string, std::unique_ptr<DebugFrameInfo>> BacktraceOffline::debug_frames_;
|
|
||||||
std::unordered_set<std::string> BacktraceOffline::debug_frame_missing_files_;
|
|
||||||
|
|
||||||
static DebugFrameInfo* ReadDebugFrameFromFile(const std::string& filename);
|
static DebugFrameInfo* ReadDebugFrameFromFile(const std::string& filename);
|
||||||
|
|
||||||
DebugFrameInfo* BacktraceOffline::GetDebugFrameInFile(const std::string& filename) {
|
DebugFrameInfo* BacktraceOffline::GetDebugFrameInFile(const std::string& filename) {
|
||||||
if (cache_file_) {
|
if (cache_file_) {
|
||||||
auto it = debug_frames_.find(filename);
|
auto it = g_debug_frames.find(filename);
|
||||||
if (it != debug_frames_.end()) {
|
if (it != g_debug_frames.end()) {
|
||||||
return it->second.get();
|
return it->second.get();
|
||||||
}
|
}
|
||||||
if (debug_frame_missing_files_.find(filename) != debug_frame_missing_files_.end()) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
DebugFrameInfo* debug_frame = ReadDebugFrameFromFile(filename);
|
DebugFrameInfo* debug_frame = ReadDebugFrameFromFile(filename);
|
||||||
if (cache_file_) {
|
if (cache_file_) {
|
||||||
if (debug_frame != nullptr) {
|
g_debug_frames.emplace(filename, std::unique_ptr<DebugFrameInfo>(debug_frame));
|
||||||
debug_frames_.emplace(filename, std::unique_ptr<DebugFrameInfo>(debug_frame));
|
|
||||||
} else {
|
|
||||||
debug_frame_missing_files_.insert(filename);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (last_debug_frame_ != nullptr) {
|
|
||||||
delete last_debug_frame_;
|
|
||||||
}
|
|
||||||
last_debug_frame_ = debug_frame;
|
|
||||||
}
|
}
|
||||||
return debug_frame;
|
return debug_frame;
|
||||||
}
|
}
|
||||||
|
|
@ -556,72 +623,106 @@ static bool GetFdeTableOffsetInEhFrameHdr(const std::vector<uint8_t>& data,
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
using ProgramHeader = DebugFrameInfo::EhFrame::ProgramHeader;
|
|
||||||
|
|
||||||
template <class ELFT>
|
template <class ELFT>
|
||||||
DebugFrameInfo* ReadDebugFrameFromELFFile(const llvm::object::ELFFile<ELFT>* elf) {
|
DebugFrameInfo* ReadDebugFrameFromELFFile(const llvm::object::ELFFile<ELFT>* elf) {
|
||||||
|
DebugFrameInfo* result = new DebugFrameInfo;
|
||||||
|
result->text_end_vaddr = std::numeric_limits<uint64_t>::max();
|
||||||
|
|
||||||
bool has_eh_frame_hdr = false;
|
bool has_eh_frame_hdr = false;
|
||||||
uint64_t eh_frame_hdr_vaddr = 0;
|
|
||||||
std::vector<uint8_t> eh_frame_hdr_data;
|
|
||||||
bool has_eh_frame = false;
|
bool has_eh_frame = false;
|
||||||
uint64_t eh_frame_vaddr = 0;
|
|
||||||
std::vector<uint8_t> eh_frame_data;
|
|
||||||
|
|
||||||
for (auto it = elf->section_begin(); it != elf->section_end(); ++it) {
|
for (auto it = elf->section_begin(); it != elf->section_end(); ++it) {
|
||||||
llvm::ErrorOr<llvm::StringRef> name = elf->getSectionName(&*it);
|
llvm::ErrorOr<llvm::StringRef> name = elf->getSectionName(&*it);
|
||||||
if (name) {
|
if (name) {
|
||||||
if (name.get() == ".debug_frame") {
|
std::string s = name.get();
|
||||||
DebugFrameInfo* debug_frame = new DebugFrameInfo;
|
if (s == ".debug_frame") {
|
||||||
debug_frame->is_eh_frame = false;
|
result->has_debug_frame = true;
|
||||||
return debug_frame;
|
} else if (s == ".gnu_debugdata") {
|
||||||
}
|
result->has_gnu_debugdata = true;
|
||||||
if (name.get() == ".eh_frame_hdr") {
|
} else if (s == ".eh_frame_hdr") {
|
||||||
has_eh_frame_hdr = true;
|
result->eh_frame.hdr_vaddr = it->sh_addr;
|
||||||
eh_frame_hdr_vaddr = it->sh_addr;
|
|
||||||
llvm::ErrorOr<llvm::ArrayRef<uint8_t>> data = elf->getSectionContents(&*it);
|
llvm::ErrorOr<llvm::ArrayRef<uint8_t>> data = elf->getSectionContents(&*it);
|
||||||
if (data) {
|
if (data) {
|
||||||
eh_frame_hdr_data.insert(eh_frame_hdr_data.begin(), data->data(),
|
result->eh_frame.hdr_data.insert(result->eh_frame.hdr_data.end(),
|
||||||
data->data() + data->size());
|
data->data(), data->data() + data->size());
|
||||||
} else {
|
|
||||||
return nullptr;
|
uint64_t fde_table_offset;
|
||||||
|
if (GetFdeTableOffsetInEhFrameHdr(result->eh_frame.hdr_data,
|
||||||
|
&fde_table_offset)) {
|
||||||
|
result->eh_frame.fde_table_offset = fde_table_offset;
|
||||||
|
// Make sure we have at least one entry in fde_table.
|
||||||
|
if (fde_table_offset + 2 * sizeof(int32_t) <= data->size()) {
|
||||||
|
intptr_t eh_frame_hdr_vaddr = it->sh_addr;
|
||||||
|
int32_t sdata;
|
||||||
|
uint8_t* p = result->eh_frame.hdr_data.data() + fde_table_offset;
|
||||||
|
memcpy(&sdata, p, sizeof(sdata));
|
||||||
|
result->eh_frame.min_func_vaddr = eh_frame_hdr_vaddr + sdata;
|
||||||
|
has_eh_frame_hdr = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (name.get() == ".eh_frame") {
|
} else if (s == ".eh_frame") {
|
||||||
has_eh_frame = true;
|
result->eh_frame.vaddr = it->sh_addr;
|
||||||
eh_frame_vaddr = it->sh_addr;
|
|
||||||
llvm::ErrorOr<llvm::ArrayRef<uint8_t>> data = elf->getSectionContents(&*it);
|
llvm::ErrorOr<llvm::ArrayRef<uint8_t>> data = elf->getSectionContents(&*it);
|
||||||
if (data) {
|
if (data) {
|
||||||
eh_frame_data.insert(eh_frame_data.begin(), data->data(), data->data() + data->size());
|
result->eh_frame.data.insert(result->eh_frame.data.end(),
|
||||||
} else {
|
data->data(), data->data() + data->size());
|
||||||
return nullptr;
|
has_eh_frame = true;
|
||||||
}
|
}
|
||||||
|
} else if (s == ".ARM.exidx") {
|
||||||
|
result->arm_exidx.exidx_vaddr = it->sh_addr;
|
||||||
|
llvm::ErrorOr<llvm::ArrayRef<uint8_t>> data = elf->getSectionContents(&*it);
|
||||||
|
if (data) {
|
||||||
|
size_t entry_count = data->size() / sizeof(ArmIdxEntry);
|
||||||
|
result->arm_exidx.exidx_data.resize(entry_count);
|
||||||
|
memcpy(result->arm_exidx.exidx_data.data(), data->data(),
|
||||||
|
entry_count * sizeof(ArmIdxEntry));
|
||||||
|
if (entry_count > 0u) {
|
||||||
|
// Change IdxEntry.func_offset into vaddr.
|
||||||
|
result->arm_exidx.func_vaddr_array.reserve(entry_count);
|
||||||
|
uint32_t vaddr = it->sh_addr;
|
||||||
|
for (auto& entry : result->arm_exidx.exidx_data) {
|
||||||
|
uint32_t func_offset = entry.func_offset + vaddr;
|
||||||
|
// Clear bit 31 for the prel31 offset.
|
||||||
|
// Arm sets bit 0 to mark it as thumb code, remove the flag.
|
||||||
|
result->arm_exidx.func_vaddr_array.push_back(
|
||||||
|
func_offset & 0x7ffffffe);
|
||||||
|
vaddr += 8;
|
||||||
|
}
|
||||||
|
result->has_arm_exidx = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (s == ".ARM.extab") {
|
||||||
|
result->arm_exidx.extab_vaddr = it->sh_addr;
|
||||||
|
llvm::ErrorOr<llvm::ArrayRef<uint8_t>> data = elf->getSectionContents(&*it);
|
||||||
|
if (data) {
|
||||||
|
result->arm_exidx.extab_data.insert(result->arm_exidx.extab_data.end(),
|
||||||
|
data->data(), data->data() + data->size());
|
||||||
|
}
|
||||||
|
} else if (s == ".text") {
|
||||||
|
result->text_end_vaddr = it->sh_addr + it->sh_size;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!(has_eh_frame_hdr && has_eh_frame)) {
|
|
||||||
return nullptr;
|
if (has_eh_frame_hdr && has_eh_frame) {
|
||||||
}
|
result->has_eh_frame = true;
|
||||||
uint64_t fde_table_offset;
|
|
||||||
if (!GetFdeTableOffsetInEhFrameHdr(eh_frame_hdr_data, &fde_table_offset)) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<ProgramHeader> program_headers;
|
result->min_vaddr = std::numeric_limits<uint64_t>::max();
|
||||||
for (auto it = elf->program_header_begin(); it != elf->program_header_end(); ++it) {
|
for (auto it = elf->program_header_begin(); it != elf->program_header_end(); ++it) {
|
||||||
ProgramHeader header;
|
if ((it->p_type == llvm::ELF::PT_LOAD) && (it->p_flags & llvm::ELF::PF_X)) {
|
||||||
header.vaddr = it->p_vaddr;
|
if (it->p_vaddr < result->min_vaddr) {
|
||||||
header.file_offset = it->p_offset;
|
result->min_vaddr = it->p_vaddr;
|
||||||
header.file_size = it->p_filesz;
|
}
|
||||||
program_headers.push_back(header);
|
}
|
||||||
}
|
}
|
||||||
DebugFrameInfo* debug_frame = new DebugFrameInfo;
|
if (!result->has_eh_frame && !result->has_arm_exidx && !result->has_debug_frame &&
|
||||||
debug_frame->is_eh_frame = true;
|
!result->has_gnu_debugdata) {
|
||||||
debug_frame->eh_frame.eh_frame_hdr_vaddr = eh_frame_hdr_vaddr;
|
delete result;
|
||||||
debug_frame->eh_frame.eh_frame_vaddr = eh_frame_vaddr;
|
return nullptr;
|
||||||
debug_frame->eh_frame.fde_table_offset_in_eh_frame_hdr = fde_table_offset;
|
}
|
||||||
debug_frame->eh_frame.eh_frame_hdr_data = std::move(eh_frame_hdr_data);
|
return result;
|
||||||
debug_frame->eh_frame.eh_frame_data = std::move(eh_frame_data);
|
|
||||||
debug_frame->eh_frame.program_headers = program_headers;
|
|
||||||
return debug_frame;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool IsValidElfPath(const std::string& filename) {
|
static bool IsValidElfPath(const std::string& filename) {
|
||||||
|
|
|
||||||
|
|
@ -40,22 +40,7 @@ struct Space {
|
||||||
size_t Read(uint64_t addr, uint8_t* buffer, size_t size);
|
size_t Read(uint64_t addr, uint8_t* buffer, size_t size);
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DebugFrameInfo {
|
struct DebugFrameInfo;
|
||||||
bool is_eh_frame;
|
|
||||||
struct EhFrame {
|
|
||||||
uint64_t eh_frame_hdr_vaddr;
|
|
||||||
uint64_t eh_frame_vaddr;
|
|
||||||
uint64_t fde_table_offset_in_eh_frame_hdr;
|
|
||||||
std::vector<uint8_t> eh_frame_hdr_data;
|
|
||||||
std::vector<uint8_t> eh_frame_data;
|
|
||||||
struct ProgramHeader {
|
|
||||||
uint64_t vaddr;
|
|
||||||
uint64_t file_offset;
|
|
||||||
uint64_t file_size;
|
|
||||||
};
|
|
||||||
std::vector<ProgramHeader> program_headers;
|
|
||||||
} eh_frame;
|
|
||||||
};
|
|
||||||
|
|
||||||
class BacktraceOffline : public Backtrace {
|
class BacktraceOffline : public Backtrace {
|
||||||
public:
|
public:
|
||||||
|
|
@ -63,18 +48,13 @@ class BacktraceOffline : public Backtrace {
|
||||||
bool cache_file)
|
bool cache_file)
|
||||||
: Backtrace(pid, tid, map),
|
: Backtrace(pid, tid, map),
|
||||||
cache_file_(cache_file),
|
cache_file_(cache_file),
|
||||||
context_(nullptr),
|
context_(nullptr) {
|
||||||
last_debug_frame_(nullptr) {
|
|
||||||
stack_space_.start = stack.start;
|
stack_space_.start = stack.start;
|
||||||
stack_space_.end = stack.end;
|
stack_space_.end = stack.end;
|
||||||
stack_space_.data = stack.data;
|
stack_space_.data = stack.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual ~BacktraceOffline() {
|
virtual ~BacktraceOffline() = default;
|
||||||
if (last_debug_frame_ != nullptr) {
|
|
||||||
delete last_debug_frame_;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Unwind(size_t num_ignore_frames, ucontext_t* context) override;
|
bool Unwind(size_t num_ignore_frames, ucontext_t* context) override;
|
||||||
|
|
||||||
|
|
@ -91,15 +71,13 @@ class BacktraceOffline : public Backtrace {
|
||||||
std::string GetFunctionNameRaw(uintptr_t pc, uintptr_t* offset) override;
|
std::string GetFunctionNameRaw(uintptr_t pc, uintptr_t* offset) override;
|
||||||
DebugFrameInfo* GetDebugFrameInFile(const std::string& filename);
|
DebugFrameInfo* GetDebugFrameInFile(const std::string& filename);
|
||||||
|
|
||||||
static std::unordered_map<std::string, std::unique_ptr<DebugFrameInfo>> debug_frames_;
|
|
||||||
static std::unordered_set<std::string> debug_frame_missing_files_;
|
|
||||||
|
|
||||||
bool cache_file_;
|
bool cache_file_;
|
||||||
ucontext_t* context_;
|
ucontext_t* context_;
|
||||||
Space eh_frame_hdr_space_;
|
Space eh_frame_hdr_space_;
|
||||||
Space eh_frame_space_;
|
Space eh_frame_space_;
|
||||||
|
Space arm_extab_space_;
|
||||||
|
Space arm_exidx_space_;
|
||||||
Space stack_space_;
|
Space stack_space_;
|
||||||
DebugFrameInfo* last_debug_frame_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // _LIBBACKTRACE_BACKTRACE_OFFLINE_H
|
#endif // _LIBBACKTRACE_BACKTRACE_OFFLINE_H
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,20 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2015 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 <libunwind.h>
|
#include <libunwind.h>
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
@ -9,6 +26,9 @@
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include <android-base/file.h>
|
||||||
|
#include <android-base/stringprintf.h>
|
||||||
|
#include <android-base/strings.h>
|
||||||
#include <backtrace/Backtrace.h>
|
#include <backtrace/Backtrace.h>
|
||||||
#include <backtrace/BacktraceMap.h>
|
#include <backtrace/BacktraceMap.h>
|
||||||
#include <cutils/threads.h>
|
#include <cutils/threads.h>
|
||||||
|
|
@ -22,29 +42,7 @@ int test_level_two(int, int, int, int, void (*)(void*), void*);
|
||||||
int test_level_three(int, int, int, int, void (*)(void*), void*);
|
int test_level_three(int, int, int, int, void (*)(void*), void*);
|
||||||
int test_level_four(int, int, int, int, void (*)(void*), void*);
|
int test_level_four(int, int, int, int, void (*)(void*), void*);
|
||||||
int test_recursive_call(int, void (*)(void*), void*);
|
int test_recursive_call(int, void (*)(void*), void*);
|
||||||
}
|
void test_get_context_and_wait(unw_context_t* unw_context, volatile int* exit_flag);
|
||||||
|
|
||||||
static volatile bool g_exit_flag = false;
|
|
||||||
|
|
||||||
static void GetContextAndExit(void* arg) {
|
|
||||||
unw_context_t* unw_context = reinterpret_cast<unw_context_t*>(arg);
|
|
||||||
unw_getcontext(unw_context);
|
|
||||||
// Don't touch the stack anymore.
|
|
||||||
while (!g_exit_flag) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct OfflineThreadArg {
|
|
||||||
unw_context_t unw_context;
|
|
||||||
pid_t tid;
|
|
||||||
std::function<int(void (*)(void*), void*)> function;
|
|
||||||
};
|
|
||||||
|
|
||||||
static void* OfflineThreadFunc(void* arg) {
|
|
||||||
OfflineThreadArg* fn_arg = reinterpret_cast<OfflineThreadArg*>(arg);
|
|
||||||
fn_arg->tid = gettid();
|
|
||||||
fn_arg->function(GetContextAndExit, &fn_arg->unw_context);
|
|
||||||
return nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static ucontext_t GetUContextFromUnwContext(const unw_context_t& unw_context) {
|
static ucontext_t GetUContextFromUnwContext(const unw_context_t& unw_context) {
|
||||||
|
|
@ -73,11 +71,68 @@ static ucontext_t GetUContextFromUnwContext(const unw_context_t& unw_context) {
|
||||||
return ucontext;
|
return ucontext;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void OfflineBacktraceFunctionCall(const std::function<int(void (*)(void*), void*)>& function,
|
struct FunctionSymbol {
|
||||||
std::vector<uintptr_t>* pc_values) {
|
std::string name;
|
||||||
|
uintptr_t start;
|
||||||
|
uintptr_t end;
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::vector<FunctionSymbol> GetFunctionSymbols() {
|
||||||
|
std::vector<FunctionSymbol> symbols = {
|
||||||
|
{"unknown_start", 0, 0},
|
||||||
|
{"test_level_one", reinterpret_cast<uintptr_t>(&test_level_one), 0},
|
||||||
|
{"test_level_two", reinterpret_cast<uintptr_t>(&test_level_two), 0},
|
||||||
|
{"test_level_three", reinterpret_cast<uintptr_t>(&test_level_three), 0},
|
||||||
|
{"test_level_four", reinterpret_cast<uintptr_t>(&test_level_four), 0},
|
||||||
|
{"test_recursive_call", reinterpret_cast<uintptr_t>(&test_recursive_call), 0},
|
||||||
|
{"test_get_context_and_wait", reinterpret_cast<uintptr_t>(&test_get_context_and_wait), 0},
|
||||||
|
{"unknown_end", static_cast<uintptr_t>(-1), static_cast<uintptr_t>(-1)},
|
||||||
|
};
|
||||||
|
std::sort(
|
||||||
|
symbols.begin(), symbols.end(),
|
||||||
|
[](const FunctionSymbol& s1, const FunctionSymbol& s2) { return s1.start < s2.start; });
|
||||||
|
for (size_t i = 0; i + 1 < symbols.size(); ++i) {
|
||||||
|
symbols[i].end = symbols[i + 1].start;
|
||||||
|
}
|
||||||
|
return symbols;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string RawDataToHexString(const void* data, size_t size) {
|
||||||
|
const uint8_t* p = static_cast<const uint8_t*>(data);
|
||||||
|
std::string s;
|
||||||
|
for (size_t i = 0; i < size; ++i) {
|
||||||
|
s += android::base::StringPrintf("%02x", p[i]);
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void HexStringToRawData(const char* s, void* data, size_t size) {
|
||||||
|
uint8_t* p = static_cast<uint8_t*>(data);
|
||||||
|
for (size_t i = 0; i < size; ++i) {
|
||||||
|
int value;
|
||||||
|
sscanf(s, "%02x", &value);
|
||||||
|
*p++ = static_cast<uint8_t>(value);
|
||||||
|
s += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct OfflineThreadArg {
|
||||||
|
unw_context_t unw_context;
|
||||||
|
pid_t tid;
|
||||||
|
volatile int exit_flag;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void* OfflineThreadFunc(void* arg) {
|
||||||
|
OfflineThreadArg* fn_arg = reinterpret_cast<OfflineThreadArg*>(arg);
|
||||||
|
fn_arg->tid = gettid();
|
||||||
|
test_get_context_and_wait(&fn_arg->unw_context, &fn_arg->exit_flag);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This test is disable because it is for generating test data.
|
||||||
|
TEST(libbacktrace, DISABLED_generate_offline_testdata) {
|
||||||
// Create a thread to generate the needed stack and registers information.
|
// Create a thread to generate the needed stack and registers information.
|
||||||
g_exit_flag = false;
|
const size_t stack_size = 16 * 1024;
|
||||||
const size_t stack_size = 1024 * 1024;
|
|
||||||
void* stack = mmap(NULL, stack_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
void* stack = mmap(NULL, stack_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||||
ASSERT_NE(MAP_FAILED, stack);
|
ASSERT_NE(MAP_FAILED, stack);
|
||||||
uintptr_t stack_addr = reinterpret_cast<uintptr_t>(stack);
|
uintptr_t stack_addr = reinterpret_cast<uintptr_t>(stack);
|
||||||
|
|
@ -86,18 +141,17 @@ static void OfflineBacktraceFunctionCall(const std::function<int(void (*)(void*)
|
||||||
ASSERT_EQ(0, pthread_attr_setstack(&attr, reinterpret_cast<void*>(stack), stack_size));
|
ASSERT_EQ(0, pthread_attr_setstack(&attr, reinterpret_cast<void*>(stack), stack_size));
|
||||||
pthread_t thread;
|
pthread_t thread;
|
||||||
OfflineThreadArg arg;
|
OfflineThreadArg arg;
|
||||||
arg.function = function;
|
arg.exit_flag = 0;
|
||||||
ASSERT_EQ(0, pthread_create(&thread, &attr, OfflineThreadFunc, &arg));
|
ASSERT_EQ(0, pthread_create(&thread, &attr, OfflineThreadFunc, &arg));
|
||||||
// Wait for the offline thread to generate the stack and unw_context information.
|
// Wait for the offline thread to generate the stack and unw_context information.
|
||||||
sleep(1);
|
sleep(1);
|
||||||
// Copy the stack information.
|
// Copy the stack information.
|
||||||
std::vector<uint8_t> stack_data(reinterpret_cast<uint8_t*>(stack),
|
std::vector<uint8_t> stack_data(reinterpret_cast<uint8_t*>(stack),
|
||||||
reinterpret_cast<uint8_t*>(stack) + stack_size);
|
reinterpret_cast<uint8_t*>(stack) + stack_size);
|
||||||
g_exit_flag = true;
|
arg.exit_flag = 1;
|
||||||
ASSERT_EQ(0, pthread_join(thread, nullptr));
|
ASSERT_EQ(0, pthread_join(thread, nullptr));
|
||||||
ASSERT_EQ(0, munmap(stack, stack_size));
|
ASSERT_EQ(0, munmap(stack, stack_size));
|
||||||
|
|
||||||
// Do offline backtrace.
|
|
||||||
std::unique_ptr<BacktraceMap> map(BacktraceMap::Create(getpid()));
|
std::unique_ptr<BacktraceMap> map(BacktraceMap::Create(getpid()));
|
||||||
ASSERT_TRUE(map != nullptr);
|
ASSERT_TRUE(map != nullptr);
|
||||||
|
|
||||||
|
|
@ -106,47 +160,44 @@ static void OfflineBacktraceFunctionCall(const std::function<int(void (*)(void*)
|
||||||
stack_info.end = stack_addr + stack_size;
|
stack_info.end = stack_addr + stack_size;
|
||||||
stack_info.data = stack_data.data();
|
stack_info.data = stack_data.data();
|
||||||
|
|
||||||
std::unique_ptr<Backtrace> backtrace(
|
// Generate offline testdata.
|
||||||
Backtrace::CreateOffline(getpid(), arg.tid, map.get(), stack_info));
|
std::string testdata;
|
||||||
ASSERT_TRUE(backtrace != nullptr);
|
// 1. Dump pid, tid
|
||||||
|
testdata += android::base::StringPrintf("pid: %d tid: %d\n", getpid(), arg.tid);
|
||||||
ucontext_t ucontext = GetUContextFromUnwContext(arg.unw_context);
|
// 2. Dump maps
|
||||||
ASSERT_TRUE(backtrace->Unwind(0, &ucontext));
|
for (auto it = map->begin(); it != map->end(); ++it) {
|
||||||
|
testdata += android::base::StringPrintf(
|
||||||
// Collect pc values of the call stack frames.
|
"map: start: %" PRIxPTR " end: %" PRIxPTR " offset: %" PRIxPTR
|
||||||
for (size_t i = 0; i < backtrace->NumFrames(); ++i) {
|
" load_base: %" PRIxPTR " flags: %d name: %s\n",
|
||||||
pc_values->push_back(backtrace->GetFrame(i)->pc);
|
it->start, it->end, it->offset, it->load_base, it->flags, it->name.c_str());
|
||||||
}
|
}
|
||||||
|
// 3. Dump registers
|
||||||
|
testdata += android::base::StringPrintf("registers: %zu ", sizeof(arg.unw_context));
|
||||||
|
testdata += RawDataToHexString(&arg.unw_context, sizeof(arg.unw_context));
|
||||||
|
testdata.push_back('\n');
|
||||||
|
|
||||||
|
// 4. Dump stack
|
||||||
|
testdata += android::base::StringPrintf(
|
||||||
|
"stack: start: %" PRIx64 " end: %" PRIx64 " size: %zu ",
|
||||||
|
stack_info.start, stack_info.end, stack_data.size());
|
||||||
|
testdata += RawDataToHexString(stack_data.data(), stack_data.size());
|
||||||
|
testdata.push_back('\n');
|
||||||
|
|
||||||
|
// 5. Dump function symbols
|
||||||
|
std::vector<FunctionSymbol> function_symbols = GetFunctionSymbols();
|
||||||
|
for (const auto& symbol : function_symbols) {
|
||||||
|
testdata += android::base::StringPrintf(
|
||||||
|
"function: start: %" PRIxPTR " end: %" PRIxPTR" name: %s\n",
|
||||||
|
symbol.start, symbol.end, symbol.name.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
ASSERT_TRUE(android::base::WriteStringToFile(testdata, "offline_testdata"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the name of the function which matches the address. Although we don't know the
|
// Return the name of the function which matches the address. Although we don't know the
|
||||||
// exact end of each function, it is accurate enough for the tests.
|
// exact end of each function, it is accurate enough for the tests.
|
||||||
static std::string FunctionNameForAddress(uintptr_t addr) {
|
static std::string FunctionNameForAddress(uintptr_t addr,
|
||||||
struct FunctionSymbol {
|
const std::vector<FunctionSymbol>& symbols) {
|
||||||
std::string name;
|
|
||||||
uintptr_t start;
|
|
||||||
uintptr_t end;
|
|
||||||
};
|
|
||||||
|
|
||||||
static std::vector<FunctionSymbol> symbols;
|
|
||||||
if (symbols.empty()) {
|
|
||||||
symbols = std::vector<FunctionSymbol>{
|
|
||||||
{"unknown_start", 0, 0},
|
|
||||||
{"test_level_one", reinterpret_cast<uintptr_t>(&test_level_one), 0},
|
|
||||||
{"test_level_two", reinterpret_cast<uintptr_t>(&test_level_two), 0},
|
|
||||||
{"test_level_three", reinterpret_cast<uintptr_t>(&test_level_three), 0},
|
|
||||||
{"test_level_four", reinterpret_cast<uintptr_t>(&test_level_four), 0},
|
|
||||||
{"test_recursive_call", reinterpret_cast<uintptr_t>(&test_recursive_call), 0},
|
|
||||||
{"GetContextAndExit", reinterpret_cast<uintptr_t>(&GetContextAndExit), 0},
|
|
||||||
{"unknown_end", static_cast<uintptr_t>(-1), static_cast<uintptr_t>(-1)},
|
|
||||||
};
|
|
||||||
std::sort(
|
|
||||||
symbols.begin(), symbols.end(),
|
|
||||||
[](const FunctionSymbol& s1, const FunctionSymbol& s2) { return s1.start < s2.start; });
|
|
||||||
for (size_t i = 0; i + 1 < symbols.size(); ++i) {
|
|
||||||
symbols[i].end = symbols[i + 1].start;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (auto& symbol : symbols) {
|
for (auto& symbol : symbols) {
|
||||||
if (addr >= symbol.start && addr < symbol.end) {
|
if (addr >= symbol.start && addr < symbol.end) {
|
||||||
return symbol.name;
|
return symbol.name;
|
||||||
|
|
@ -155,35 +206,136 @@ static std::string FunctionNameForAddress(uintptr_t addr) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(libbacktrace, offline) {
|
static std::string GetArch() {
|
||||||
std::function<int(void (*)(void*), void*)> function =
|
#if defined(__arm__)
|
||||||
std::bind(test_level_one, 1, 2, 3, 4, std::placeholders::_1, std::placeholders::_2);
|
return "arm";
|
||||||
|
#elif defined(__aarch64__)
|
||||||
|
return "aarch64";
|
||||||
|
#elif defined(__i386__)
|
||||||
|
return "x86";
|
||||||
|
#elif defined(__x86_64__)
|
||||||
|
return "x86_64";
|
||||||
|
#else
|
||||||
|
return "";
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static void BacktraceOfflineTest(const std::string& testlib_name) {
|
||||||
|
const std::string arch = GetArch();
|
||||||
|
if (arch.empty()) {
|
||||||
|
GTEST_LOG_(INFO) << "This test does nothing on current arch.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const std::string offline_testdata_path = "testdata/" + arch + "/offline_testdata";
|
||||||
|
std::string testdata;
|
||||||
|
ASSERT_TRUE(android::base::ReadFileToString(offline_testdata_path, &testdata));
|
||||||
|
|
||||||
|
const std::string testlib_path = "testdata/" + arch + "/" + testlib_name;
|
||||||
|
struct stat st;
|
||||||
|
if (stat(testlib_path.c_str(), &st) == -1) {
|
||||||
|
GTEST_LOG_(INFO) << "This test is skipped as " << testlib_path << " doesn't exist.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse offline_testdata.
|
||||||
|
std::vector<std::string> lines = android::base::Split(testdata, "\n");
|
||||||
|
int pid;
|
||||||
|
int tid;
|
||||||
|
std::vector<backtrace_map_t> maps;
|
||||||
|
unw_context_t unw_context;
|
||||||
|
backtrace_stackinfo_t stack_info;
|
||||||
|
std::vector<uint8_t> stack;
|
||||||
|
std::vector<FunctionSymbol> symbols;
|
||||||
|
for (const auto& line : lines) {
|
||||||
|
if (android::base::StartsWith(line, "pid:")) {
|
||||||
|
sscanf(line.c_str(), "pid: %d tid: %d", &pid, &tid);
|
||||||
|
} else if (android::base::StartsWith(line, "map:")) {
|
||||||
|
maps.resize(maps.size() + 1);
|
||||||
|
int pos;
|
||||||
|
sscanf(line.c_str(),
|
||||||
|
"map: start: %" SCNxPTR " end: %" SCNxPTR " offset: %" SCNxPTR
|
||||||
|
" load_base: %" SCNxPTR " flags: %d name: %n",
|
||||||
|
&maps.back().start, &maps.back().end, &maps.back().offset,
|
||||||
|
&maps.back().load_base, &maps.back().flags, &pos);
|
||||||
|
maps.back().name = android::base::Trim(line.substr(pos));
|
||||||
|
} else if (android::base::StartsWith(line, "registers:")) {
|
||||||
|
size_t size;
|
||||||
|
int pos;
|
||||||
|
sscanf(line.c_str(), "registers: %zu %n", &size, &pos);
|
||||||
|
ASSERT_EQ(sizeof(unw_context), size);
|
||||||
|
HexStringToRawData(&line[pos], &unw_context, size);
|
||||||
|
} else if (android::base::StartsWith(line, "stack:")) {
|
||||||
|
size_t size;
|
||||||
|
int pos;
|
||||||
|
sscanf(line.c_str(),
|
||||||
|
"stack: start: %" SCNx64 " end: %" SCNx64 " size: %zu %n",
|
||||||
|
&stack_info.start, &stack_info.end, &size, &pos);
|
||||||
|
stack.resize(size);
|
||||||
|
HexStringToRawData(&line[pos], &stack[0], size);
|
||||||
|
stack_info.data = stack.data();
|
||||||
|
} else if (android::base::StartsWith(line, "function:")) {
|
||||||
|
symbols.resize(symbols.size() + 1);
|
||||||
|
int pos;
|
||||||
|
sscanf(line.c_str(),
|
||||||
|
"function: start: %" SCNxPTR " end: %" SCNxPTR " name: %n",
|
||||||
|
&symbols.back().start, &symbols.back().end,
|
||||||
|
&pos);
|
||||||
|
symbols.back().name = line.substr(pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fix path of libbacktrace_testlib.so.
|
||||||
|
for (auto& map : maps) {
|
||||||
|
if (map.name.find("libbacktrace_test.so") != std::string::npos) {
|
||||||
|
map.name = testlib_path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do offline backtrace.
|
||||||
|
std::unique_ptr<BacktraceMap> map(BacktraceMap::Create(pid, maps));
|
||||||
|
ASSERT_TRUE(map != nullptr);
|
||||||
|
|
||||||
|
std::unique_ptr<Backtrace> backtrace(
|
||||||
|
Backtrace::CreateOffline(pid, tid, map.get(), stack_info));
|
||||||
|
ASSERT_TRUE(backtrace != nullptr);
|
||||||
|
|
||||||
|
ucontext_t ucontext = GetUContextFromUnwContext(unw_context);
|
||||||
|
ASSERT_TRUE(backtrace->Unwind(0, &ucontext));
|
||||||
|
|
||||||
|
|
||||||
|
// Collect pc values of the call stack frames.
|
||||||
std::vector<uintptr_t> pc_values;
|
std::vector<uintptr_t> pc_values;
|
||||||
OfflineBacktraceFunctionCall(function, &pc_values);
|
for (size_t i = 0; i < backtrace->NumFrames(); ++i) {
|
||||||
ASSERT_FALSE(pc_values.empty());
|
pc_values.push_back(backtrace->GetFrame(i)->pc);
|
||||||
ASSERT_LE(pc_values.size(), static_cast<size_t>(MAX_BACKTRACE_FRAMES));
|
}
|
||||||
|
|
||||||
size_t test_one_index = 0;
|
size_t test_one_index = 0;
|
||||||
for (size_t i = 0; i < pc_values.size(); ++i) {
|
for (size_t i = 0; i < pc_values.size(); ++i) {
|
||||||
if (FunctionNameForAddress(pc_values[i]) == "test_level_one") {
|
if (FunctionNameForAddress(pc_values[i], symbols) == "test_level_one") {
|
||||||
test_one_index = i;
|
test_one_index = i;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ASSERT_GE(test_one_index, 3u);
|
ASSERT_GE(test_one_index, 3u);
|
||||||
ASSERT_EQ("test_level_one", FunctionNameForAddress(pc_values[test_one_index]));
|
ASSERT_EQ("test_level_one", FunctionNameForAddress(pc_values[test_one_index], symbols));
|
||||||
ASSERT_EQ("test_level_two", FunctionNameForAddress(pc_values[test_one_index - 1]));
|
ASSERT_EQ("test_level_two", FunctionNameForAddress(pc_values[test_one_index - 1], symbols));
|
||||||
ASSERT_EQ("test_level_three", FunctionNameForAddress(pc_values[test_one_index - 2]));
|
ASSERT_EQ("test_level_three", FunctionNameForAddress(pc_values[test_one_index - 2], symbols));
|
||||||
ASSERT_EQ("test_level_four", FunctionNameForAddress(pc_values[test_one_index - 3]));
|
ASSERT_EQ("test_level_four", FunctionNameForAddress(pc_values[test_one_index - 3], symbols));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(libbacktrace, offline_max_trace) {
|
TEST(libbacktrace, offline_eh_frame) {
|
||||||
std::function<int(void (*)(void*), void*)> function = std::bind(
|
BacktraceOfflineTest("libbacktrace_test_eh_frame.so");
|
||||||
test_recursive_call, MAX_BACKTRACE_FRAMES + 10, std::placeholders::_1, std::placeholders::_2);
|
}
|
||||||
std::vector<uintptr_t> pc_values;
|
|
||||||
OfflineBacktraceFunctionCall(function, &pc_values);
|
TEST(libbacktrace, offline_debug_frame) {
|
||||||
ASSERT_FALSE(pc_values.empty());
|
BacktraceOfflineTest("libbacktrace_test_debug_frame.so");
|
||||||
ASSERT_EQ(static_cast<size_t>(MAX_BACKTRACE_FRAMES), pc_values.size());
|
}
|
||||||
ASSERT_EQ("test_recursive_call", FunctionNameForAddress(pc_values.back()));
|
|
||||||
|
TEST(libbacktrace, offline_gnu_debugdata) {
|
||||||
|
BacktraceOfflineTest("libbacktrace_test_gnu_debugdata.so");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(libbacktrace, offline_arm_exidx) {
|
||||||
|
BacktraceOfflineTest("libbacktrace_test_arm_exidx.so");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <libunwind.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
int test_level_four(int one, int two, int three, int four,
|
int test_level_four(int one, int two, int three, int four,
|
||||||
|
|
@ -53,3 +54,23 @@ int test_recursive_call(int level, void (*callback_func)(void*), void* data) {
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
unw_context_t* unw_context;
|
||||||
|
volatile int* exit_flag;
|
||||||
|
} GetContextArg;
|
||||||
|
|
||||||
|
static void GetContextAndExit(void* data) {
|
||||||
|
GetContextArg* arg = (GetContextArg*)data;
|
||||||
|
unw_getcontext(arg->unw_context);
|
||||||
|
// Don't touch the stack anymore.
|
||||||
|
while (*arg->exit_flag == 0) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_get_context_and_wait(unw_context_t* unw_context, volatile int* exit_flag) {
|
||||||
|
GetContextArg arg;
|
||||||
|
arg.unw_context = unw_context;
|
||||||
|
arg.exit_flag = exit_flag;
|
||||||
|
test_level_one(1, 2, 3, 4, GetContextAndExit, &arg);
|
||||||
|
}
|
||||||
|
|
|
||||||
BIN
libbacktrace/testdata/aarch64/libbacktrace_test_eh_frame.so
vendored
Executable file
BIN
libbacktrace/testdata/aarch64/libbacktrace_test_eh_frame.so
vendored
Executable file
Binary file not shown.
107
libbacktrace/testdata/aarch64/offline_testdata
vendored
Normal file
107
libbacktrace/testdata/aarch64/offline_testdata
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
libbacktrace/testdata/arm/libbacktrace_test_arm_exidx.so
vendored
Executable file
BIN
libbacktrace/testdata/arm/libbacktrace_test_arm_exidx.so
vendored
Executable file
Binary file not shown.
BIN
libbacktrace/testdata/arm/libbacktrace_test_debug_frame.so
vendored
Executable file
BIN
libbacktrace/testdata/arm/libbacktrace_test_debug_frame.so
vendored
Executable file
Binary file not shown.
BIN
libbacktrace/testdata/arm/libbacktrace_test_gnu_debugdata.so
vendored
Executable file
BIN
libbacktrace/testdata/arm/libbacktrace_test_gnu_debugdata.so
vendored
Executable file
Binary file not shown.
105
libbacktrace/testdata/arm/offline_testdata
vendored
Normal file
105
libbacktrace/testdata/arm/offline_testdata
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
libbacktrace/testdata/x86/libbacktrace_test_debug_frame.so
vendored
Executable file
BIN
libbacktrace/testdata/x86/libbacktrace_test_debug_frame.so
vendored
Executable file
Binary file not shown.
BIN
libbacktrace/testdata/x86/libbacktrace_test_gnu_debugdata.so
vendored
Executable file
BIN
libbacktrace/testdata/x86/libbacktrace_test_gnu_debugdata.so
vendored
Executable file
Binary file not shown.
82
libbacktrace/testdata/x86/offline_testdata
vendored
Normal file
82
libbacktrace/testdata/x86/offline_testdata
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
libbacktrace/testdata/x86_64/libbacktrace_test_eh_frame.so
vendored
Executable file
BIN
libbacktrace/testdata/x86_64/libbacktrace_test_eh_frame.so
vendored
Executable file
Binary file not shown.
93
libbacktrace/testdata/x86_64/offline_testdata
vendored
Normal file
93
libbacktrace/testdata/x86_64/offline_testdata
vendored
Normal file
File diff suppressed because one or more lines are too long
Loading…
Add table
Reference in a new issue