Merge "libbacktrace_offline: support .ARM.exidx."
This commit is contained in:
commit
aece425166
17 changed files with 877 additions and 227 deletions
|
|
@ -117,4 +117,17 @@ cc_library_shared {
|
|||
},
|
||||
cflags: ["-O0"],
|
||||
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 += \
|
||||
-ldl \
|
||||
|
||||
backtrace_test_strip_module := false
|
||||
|
||||
module := backtrace_test
|
||||
module_tag := debug
|
||||
build_type := target
|
||||
|
|
|
|||
|
|
@ -50,6 +50,48 @@ extern "C" {
|
|||
|
||||
#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() {
|
||||
start = 0;
|
||||
end = 0;
|
||||
|
|
@ -207,23 +249,18 @@ size_t BacktraceOffline::Read(uintptr_t addr, uint8_t* buffer, size_t bytes) {
|
|||
if (read_size != 0) {
|
||||
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);
|
||||
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,
|
||||
unw_proc_info_t* proc_info, int need_unwind_info) {
|
||||
backtrace_map_t map;
|
||||
|
|
@ -236,45 +273,90 @@ bool BacktraceOffline::FindProcInfo(unw_addr_space_t addr_space, uint64_t ip,
|
|||
if (debug_frame == nullptr) {
|
||||
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_space_.Clear();
|
||||
unw_dyn_info_t di;
|
||||
unw_word_t segbase = map.start - map.offset;
|
||||
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);
|
||||
return ret == 0;
|
||||
arm_exidx_space_.Clear();
|
||||
arm_extab_space_.Clear();
|
||||
|
||||
// vaddr in the elf file.
|
||||
uint64_t ip_vaddr = ip - map.start + debug_frame->min_vaddr;
|
||||
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;
|
||||
}
|
||||
|
|
@ -462,33 +544,18 @@ std::string BacktraceOffline::GetFunctionNameRaw(uintptr_t, uintptr_t* offset) {
|
|||
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);
|
||||
|
||||
DebugFrameInfo* BacktraceOffline::GetDebugFrameInFile(const std::string& filename) {
|
||||
if (cache_file_) {
|
||||
auto it = debug_frames_.find(filename);
|
||||
if (it != debug_frames_.end()) {
|
||||
auto it = g_debug_frames.find(filename);
|
||||
if (it != g_debug_frames.end()) {
|
||||
return it->second.get();
|
||||
}
|
||||
if (debug_frame_missing_files_.find(filename) != debug_frame_missing_files_.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
DebugFrameInfo* debug_frame = ReadDebugFrameFromFile(filename);
|
||||
if (cache_file_) {
|
||||
if (debug_frame != nullptr) {
|
||||
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;
|
||||
g_debug_frames.emplace(filename, std::unique_ptr<DebugFrameInfo>(debug_frame));
|
||||
}
|
||||
return debug_frame;
|
||||
}
|
||||
|
|
@ -556,72 +623,106 @@ static bool GetFdeTableOffsetInEhFrameHdr(const std::vector<uint8_t>& data,
|
|||
return true;
|
||||
}
|
||||
|
||||
using ProgramHeader = DebugFrameInfo::EhFrame::ProgramHeader;
|
||||
|
||||
template <class ELFT>
|
||||
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;
|
||||
uint64_t eh_frame_hdr_vaddr = 0;
|
||||
std::vector<uint8_t> eh_frame_hdr_data;
|
||||
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) {
|
||||
llvm::ErrorOr<llvm::StringRef> name = elf->getSectionName(&*it);
|
||||
if (name) {
|
||||
if (name.get() == ".debug_frame") {
|
||||
DebugFrameInfo* debug_frame = new DebugFrameInfo;
|
||||
debug_frame->is_eh_frame = false;
|
||||
return debug_frame;
|
||||
}
|
||||
if (name.get() == ".eh_frame_hdr") {
|
||||
has_eh_frame_hdr = true;
|
||||
eh_frame_hdr_vaddr = it->sh_addr;
|
||||
std::string s = name.get();
|
||||
if (s == ".debug_frame") {
|
||||
result->has_debug_frame = true;
|
||||
} else if (s == ".gnu_debugdata") {
|
||||
result->has_gnu_debugdata = true;
|
||||
} else if (s == ".eh_frame_hdr") {
|
||||
result->eh_frame.hdr_vaddr = it->sh_addr;
|
||||
llvm::ErrorOr<llvm::ArrayRef<uint8_t>> data = elf->getSectionContents(&*it);
|
||||
if (data) {
|
||||
eh_frame_hdr_data.insert(eh_frame_hdr_data.begin(), data->data(),
|
||||
data->data() + data->size());
|
||||
} else {
|
||||
return nullptr;
|
||||
result->eh_frame.hdr_data.insert(result->eh_frame.hdr_data.end(),
|
||||
data->data(), data->data() + data->size());
|
||||
|
||||
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") {
|
||||
has_eh_frame = true;
|
||||
eh_frame_vaddr = it->sh_addr;
|
||||
} else if (s == ".eh_frame") {
|
||||
result->eh_frame.vaddr = it->sh_addr;
|
||||
llvm::ErrorOr<llvm::ArrayRef<uint8_t>> data = elf->getSectionContents(&*it);
|
||||
if (data) {
|
||||
eh_frame_data.insert(eh_frame_data.begin(), data->data(), data->data() + data->size());
|
||||
} else {
|
||||
return nullptr;
|
||||
result->eh_frame.data.insert(result->eh_frame.data.end(),
|
||||
data->data(), data->data() + data->size());
|
||||
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;
|
||||
}
|
||||
uint64_t fde_table_offset;
|
||||
if (!GetFdeTableOffsetInEhFrameHdr(eh_frame_hdr_data, &fde_table_offset)) {
|
||||
return nullptr;
|
||||
|
||||
if (has_eh_frame_hdr && has_eh_frame) {
|
||||
result->has_eh_frame = true;
|
||||
}
|
||||
|
||||
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) {
|
||||
ProgramHeader header;
|
||||
header.vaddr = it->p_vaddr;
|
||||
header.file_offset = it->p_offset;
|
||||
header.file_size = it->p_filesz;
|
||||
program_headers.push_back(header);
|
||||
if ((it->p_type == llvm::ELF::PT_LOAD) && (it->p_flags & llvm::ELF::PF_X)) {
|
||||
if (it->p_vaddr < result->min_vaddr) {
|
||||
result->min_vaddr = it->p_vaddr;
|
||||
}
|
||||
}
|
||||
}
|
||||
DebugFrameInfo* debug_frame = new DebugFrameInfo;
|
||||
debug_frame->is_eh_frame = true;
|
||||
debug_frame->eh_frame.eh_frame_hdr_vaddr = eh_frame_hdr_vaddr;
|
||||
debug_frame->eh_frame.eh_frame_vaddr = eh_frame_vaddr;
|
||||
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);
|
||||
debug_frame->eh_frame.eh_frame_data = std::move(eh_frame_data);
|
||||
debug_frame->eh_frame.program_headers = program_headers;
|
||||
return debug_frame;
|
||||
if (!result->has_eh_frame && !result->has_arm_exidx && !result->has_debug_frame &&
|
||||
!result->has_gnu_debugdata) {
|
||||
delete result;
|
||||
return nullptr;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
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;
|
||||
};
|
||||
struct DebugFrameInfo;
|
||||
|
||||
class BacktraceOffline : public Backtrace {
|
||||
public:
|
||||
|
|
@ -63,18 +48,13 @@ class BacktraceOffline : public Backtrace {
|
|||
bool cache_file)
|
||||
: Backtrace(pid, tid, map),
|
||||
cache_file_(cache_file),
|
||||
context_(nullptr),
|
||||
last_debug_frame_(nullptr) {
|
||||
context_(nullptr) {
|
||||
stack_space_.start = stack.start;
|
||||
stack_space_.end = stack.end;
|
||||
stack_space_.data = stack.data;
|
||||
}
|
||||
|
||||
virtual ~BacktraceOffline() {
|
||||
if (last_debug_frame_ != nullptr) {
|
||||
delete last_debug_frame_;
|
||||
}
|
||||
}
|
||||
virtual ~BacktraceOffline() = default;
|
||||
|
||||
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;
|
||||
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_;
|
||||
ucontext_t* context_;
|
||||
Space eh_frame_hdr_space_;
|
||||
Space eh_frame_space_;
|
||||
Space arm_extab_space_;
|
||||
Space arm_exidx_space_;
|
||||
Space stack_space_;
|
||||
DebugFrameInfo* last_debug_frame_;
|
||||
};
|
||||
|
||||
#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 <pthread.h>
|
||||
#include <stdint.h>
|
||||
|
|
@ -9,6 +26,9 @@
|
|||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <android-base/file.h>
|
||||
#include <android-base/stringprintf.h>
|
||||
#include <android-base/strings.h>
|
||||
#include <backtrace/Backtrace.h>
|
||||
#include <backtrace/BacktraceMap.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_four(int, int, int, int, void (*)(void*), void*);
|
||||
int test_recursive_call(int, void (*)(void*), void*);
|
||||
}
|
||||
|
||||
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;
|
||||
void test_get_context_and_wait(unw_context_t* unw_context, volatile int* exit_flag);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
static void OfflineBacktraceFunctionCall(const std::function<int(void (*)(void*), void*)>& function,
|
||||
std::vector<uintptr_t>* pc_values) {
|
||||
struct FunctionSymbol {
|
||||
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.
|
||||
g_exit_flag = false;
|
||||
const size_t stack_size = 1024 * 1024;
|
||||
const size_t stack_size = 16 * 1024;
|
||||
void* stack = mmap(NULL, stack_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
ASSERT_NE(MAP_FAILED, 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));
|
||||
pthread_t thread;
|
||||
OfflineThreadArg arg;
|
||||
arg.function = function;
|
||||
arg.exit_flag = 0;
|
||||
ASSERT_EQ(0, pthread_create(&thread, &attr, OfflineThreadFunc, &arg));
|
||||
// Wait for the offline thread to generate the stack and unw_context information.
|
||||
sleep(1);
|
||||
// Copy the stack information.
|
||||
std::vector<uint8_t> stack_data(reinterpret_cast<uint8_t*>(stack),
|
||||
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, munmap(stack, stack_size));
|
||||
|
||||
// Do offline backtrace.
|
||||
std::unique_ptr<BacktraceMap> map(BacktraceMap::Create(getpid()));
|
||||
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.data = stack_data.data();
|
||||
|
||||
std::unique_ptr<Backtrace> backtrace(
|
||||
Backtrace::CreateOffline(getpid(), arg.tid, map.get(), stack_info));
|
||||
ASSERT_TRUE(backtrace != nullptr);
|
||||
|
||||
ucontext_t ucontext = GetUContextFromUnwContext(arg.unw_context);
|
||||
ASSERT_TRUE(backtrace->Unwind(0, &ucontext));
|
||||
|
||||
// Collect pc values of the call stack frames.
|
||||
for (size_t i = 0; i < backtrace->NumFrames(); ++i) {
|
||||
pc_values->push_back(backtrace->GetFrame(i)->pc);
|
||||
// Generate offline testdata.
|
||||
std::string testdata;
|
||||
// 1. Dump pid, tid
|
||||
testdata += android::base::StringPrintf("pid: %d tid: %d\n", getpid(), arg.tid);
|
||||
// 2. Dump maps
|
||||
for (auto it = map->begin(); it != map->end(); ++it) {
|
||||
testdata += android::base::StringPrintf(
|
||||
"map: start: %" PRIxPTR " end: %" PRIxPTR " offset: %" PRIxPTR
|
||||
" load_base: %" PRIxPTR " flags: %d name: %s\n",
|
||||
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
|
||||
// exact end of each function, it is accurate enough for the tests.
|
||||
static std::string FunctionNameForAddress(uintptr_t addr) {
|
||||
struct FunctionSymbol {
|
||||
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;
|
||||
}
|
||||
}
|
||||
static std::string FunctionNameForAddress(uintptr_t addr,
|
||||
const std::vector<FunctionSymbol>& symbols) {
|
||||
for (auto& symbol : symbols) {
|
||||
if (addr >= symbol.start && addr < symbol.end) {
|
||||
return symbol.name;
|
||||
|
|
@ -155,35 +206,136 @@ static std::string FunctionNameForAddress(uintptr_t addr) {
|
|||
return "";
|
||||
}
|
||||
|
||||
TEST(libbacktrace, offline) {
|
||||
std::function<int(void (*)(void*), void*)> function =
|
||||
std::bind(test_level_one, 1, 2, 3, 4, std::placeholders::_1, std::placeholders::_2);
|
||||
static std::string GetArch() {
|
||||
#if defined(__arm__)
|
||||
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;
|
||||
OfflineBacktraceFunctionCall(function, &pc_values);
|
||||
ASSERT_FALSE(pc_values.empty());
|
||||
ASSERT_LE(pc_values.size(), static_cast<size_t>(MAX_BACKTRACE_FRAMES));
|
||||
for (size_t i = 0; i < backtrace->NumFrames(); ++i) {
|
||||
pc_values.push_back(backtrace->GetFrame(i)->pc);
|
||||
}
|
||||
|
||||
size_t test_one_index = 0;
|
||||
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;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT_GE(test_one_index, 3u);
|
||||
ASSERT_EQ("test_level_one", FunctionNameForAddress(pc_values[test_one_index]));
|
||||
ASSERT_EQ("test_level_two", FunctionNameForAddress(pc_values[test_one_index - 1]));
|
||||
ASSERT_EQ("test_level_three", FunctionNameForAddress(pc_values[test_one_index - 2]));
|
||||
ASSERT_EQ("test_level_four", FunctionNameForAddress(pc_values[test_one_index - 3]));
|
||||
ASSERT_EQ("test_level_one", FunctionNameForAddress(pc_values[test_one_index], symbols));
|
||||
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], symbols));
|
||||
ASSERT_EQ("test_level_four", FunctionNameForAddress(pc_values[test_one_index - 3], symbols));
|
||||
}
|
||||
|
||||
TEST(libbacktrace, offline_max_trace) {
|
||||
std::function<int(void (*)(void*), void*)> function = std::bind(
|
||||
test_recursive_call, MAX_BACKTRACE_FRAMES + 10, std::placeholders::_1, std::placeholders::_2);
|
||||
std::vector<uintptr_t> pc_values;
|
||||
OfflineBacktraceFunctionCall(function, &pc_values);
|
||||
ASSERT_FALSE(pc_values.empty());
|
||||
ASSERT_EQ(static_cast<size_t>(MAX_BACKTRACE_FRAMES), pc_values.size());
|
||||
ASSERT_EQ("test_recursive_call", FunctionNameForAddress(pc_values.back()));
|
||||
TEST(libbacktrace, offline_eh_frame) {
|
||||
BacktraceOfflineTest("libbacktrace_test_eh_frame.so");
|
||||
}
|
||||
|
||||
TEST(libbacktrace, offline_debug_frame) {
|
||||
BacktraceOfflineTest("libbacktrace_test_debug_frame.so");
|
||||
}
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
#include <libunwind.h>
|
||||
#include <stdio.h>
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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