Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5d991bc3 authored by Yabin Cui's avatar Yabin Cui
Browse files

libbacktrace_offline: support .ARM.exidx.

1. Read .ARM.exidx and .ARM.extab, provides these data to libunwind
through AccessMem.
2. In FindProcInfo, search .ARM.exidx for the idx entry of function
containing ip.
3. Add structures to store .ARM.exidx info in DebugFrameInfo, and
clean up the structure.
4. Add tests to test each unwind information (.eh_frame, .debug_frame,
.gnu_debugdata, .ARM.exidx) separately.

Bug: http://b/30974693
Test: run backtrace_test.
Test: run simpleperf runtest.py.
Change-Id: I9377a54379158bd35860efcf0e011423be7350d4
parent 6e5f7302
Loading
Loading
Loading
Loading
+14 −1
Original line number Diff line number Diff line
@@ -117,4 +117,17 @@ cc_library_shared {
    },
    cflags: ["-O0"],
    srcs: ["backtrace_testlib.c"],

    target: {
        linux: {
            shared_libs: [
                "libunwind",
            ],
        },
        android: {
            shared_libs: [
                "libunwind",
            ],
        },
    }
}
 No newline at end of file
+0 −2
Original line number Diff line number Diff line
@@ -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
+214 −113
Original line number Diff line number Diff line
@@ -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,21 +249,16 @@ size_t BacktraceOffline::Read(uintptr_t addr, uint8_t* buffer, size_t bytes) {
  if (read_size != 0) {
    return read_size;
  }
  read_size = stack_space_.Read(addr, buffer, bytes);
  read_size = arm_exidx_space_.Read(addr, buffer, bytes);
  if (read_size != 0) {
    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;
    }
  read_size = arm_extab_space_.Read(addr, buffer, bytes);
  if (read_size != 0) {
    return read_size;
  }
  return false;
  read_size = stack_space_.Read(addr, buffer, bytes);
  return read_size;
}

bool BacktraceOffline::FindProcInfo(unw_addr_space_t addr_space, uint64_t ip,
@@ -236,22 +273,59 @@ 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;

  eh_frame_hdr_space_.Clear();
  eh_frame_space_.Clear();
  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;
      }
    }
  }
    // 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;

  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.eh_frame_hdr_data.size();
    eh_frame_hdr_space_.data = debug_frame->eh_frame.eh_frame_hdr_data.data();
          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.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();
      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));
@@ -261,20 +335,28 @@ bool BacktraceOffline::FindProcInfo(unw_addr_space_t addr_space, uint64_t ip,
      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;
          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);
    return ret == 0;
      if (ret == 0) {
        return true;
      }
    }
  }

  eh_frame_hdr_space_.Clear();
  eh_frame_space_.Clear();
  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);
    return ret == 0;
      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);
  }
  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 ((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;
      }
    }
  }
  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) {
+5 −27
Original line number Diff line number Diff line
@@ -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
+235 −83

File changed.

Preview size limit exceeded, changes collapsed.

Loading