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

Commit f6f691b6 authored by Christopher Ferris's avatar Christopher Ferris
Browse files

Update the Unwinder object and add tests.

Changes:
- Remove unused GetReturnAddressFromDefault function and tests.
- Modify the unwinder to stop when a pc/sp in a device map.
- Modify the unwinder to skip initial frames based on map names.
- Unit tests that exercise all of the paths in the unwinder code.
- Move the test Elf/ElfInterface objects into their own file.
- Update RegsFake to handle extra cases.
- Modify libbacktrace code to use this unwinder.

The new unwinder does not implement the ignore frame functionality since
this is not used very often and is better implemented using a skip frames
in named libraries functionality.

Test: Ran new unit tests, ran backtrace tests.
Change-Id: Ifd65e9acd66ac5e2d0e04bd32a9ad870b54610ff
parent dea5e081
Loading
Loading
Loading
Loading
+30 −120
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@
#include <ucontext.h>

#include <memory>
#include <set>
#include <string>

#if !defined(__ANDROID__)
@@ -37,6 +38,8 @@
#include <unwindstack/Regs.h>
#include <unwindstack/RegsGetLocal.h>

#include <unwindstack/Unwinder.h>

#include "BacktraceLog.h"
#include "UnwindStack.h"
#include "UnwindStackMap.h"
@@ -63,135 +66,42 @@ static std::string GetFunctionName(BacktraceMap* back_map, uintptr_t pc, uintptr
  return name;
}

static bool IsUnwindLibrary(const std::string& map_name) {
  const std::string library(basename(map_name.c_str()));
  return library == "libunwindstack.so" || library == "libbacktrace.so";
}

static void SetFrameInfo(unwindstack::Regs* regs, unwindstack::MapInfo* map_info,
                         uint64_t adjusted_rel_pc, backtrace_frame_data_t* frame) {
  // This will point to the adjusted absolute pc. regs->pc() is
  // unaltered.
  frame->pc = map_info->start + adjusted_rel_pc;
  frame->sp = regs->sp();
  frame->rel_pc = adjusted_rel_pc;
  frame->stack_size = 0;

  frame->map.start = map_info->start;
  frame->map.end = map_info->end;
  frame->map.offset = map_info->offset;
  frame->map.flags = map_info->flags;
  frame->map.name = map_info->name;

  unwindstack::Elf* elf = map_info->elf;
  frame->map.load_bias = elf->GetLoadBias();
  uint64_t func_offset = 0;
  if (elf->GetFunctionName(adjusted_rel_pc, &frame->func_name, &func_offset)) {
    frame->func_name = demangle(frame->func_name.c_str());
  } else {
    frame->func_name = "";
  }
  frame->func_offset = func_offset;
}

static bool Unwind(unwindstack::Regs* regs, BacktraceMap* back_map,
                   std::vector<backtrace_frame_data_t>* frames, size_t num_ignore_frames) {
  static std::set<std::string> skip_names{"libunwindstack.so", "libbacktrace.so"};
  UnwindStackMap* stack_map = reinterpret_cast<UnwindStackMap*>(back_map);
  unwindstack::Maps* maps = stack_map->stack_maps();
  bool adjust_rel_pc = false;
  size_t num_frames = 0;
  frames->clear();
  bool return_address_attempted = false;
  auto process_memory = stack_map->process_memory();
  while (num_frames < MAX_BACKTRACE_FRAMES) {
    unwindstack::MapInfo* map_info = maps->Find(regs->pc());
    bool stepped;
    bool in_device_map = false;
    if (map_info == nullptr) {
      stepped = false;
      if (num_ignore_frames == 0) {
        frames->resize(num_frames + 1);
        backtrace_frame_data_t* frame = &frames->at(num_frames);
        frame->pc = regs->pc();
        frame->sp = regs->sp();
        frame->rel_pc = frame->pc;
        num_frames++;
      } else {
        num_ignore_frames--;
      }
    } else {
      unwindstack::Elf* elf = map_info->GetElf(process_memory, true);
      uint64_t rel_pc = elf->GetRelPc(regs->pc(), map_info);
  unwindstack::Unwinder unwinder(MAX_BACKTRACE_FRAMES + num_ignore_frames, stack_map->stack_maps(),
                                 regs, stack_map->process_memory());
  unwinder.Unwind(&skip_names);

      if (frames->size() != 0 || !IsUnwindLibrary(map_info->name)) {
        if (num_ignore_frames == 0) {
          uint64_t adjusted_rel_pc = rel_pc;
          if (adjust_rel_pc) {
            adjusted_rel_pc = regs->GetAdjustedPc(rel_pc, elf);
  if (num_ignore_frames >= unwinder.NumFrames()) {
    frames->resize(0);
    return true;
  }

          frames->resize(num_frames + 1);
          backtrace_frame_data_t* frame = &frames->at(num_frames);
          frame->num = num_frames;
          SetFrameInfo(regs, map_info, adjusted_rel_pc, frame);
  frames->resize(unwinder.NumFrames() - num_ignore_frames);
  auto unwinder_frames = unwinder.frames();
  size_t cur_frame = 0;
  for (size_t i = num_ignore_frames; i < unwinder.NumFrames(); i++, cur_frame++) {
    auto frame = &unwinder_frames[i];
    backtrace_frame_data_t* back_frame = &frames->at(cur_frame);

          if (num_frames > 0) {
            // Set the stack size for the previous frame.
            backtrace_frame_data_t* prev = &frames->at(num_frames - 1);
            prev->stack_size = frame->sp - prev->sp;
          }
          num_frames++;
        } else {
          num_ignore_frames--;
        }
      }
    back_frame->num = frame->num;

      if (map_info->flags & PROT_DEVICE_MAP) {
        // Do not stop here, fall through in case we are
        // in the speculative unwind path and need to remove
        // some of the speculative frames.
        stepped = false;
        in_device_map = true;
      } else {
        unwindstack::MapInfo* sp_info = maps->Find(regs->sp());
        if (sp_info->flags & PROT_DEVICE_MAP) {
          // Do not stop here, fall through in case we are
          // in the speculative unwind path and need to remove
          // some of the speculative frames.
          stepped = false;
          in_device_map = true;
        } else {
          bool finished;
          stepped = elf->Step(rel_pc + map_info->elf_offset, regs, process_memory.get(), &finished);
          if (stepped && finished) {
            break;
          }
        }
      }
    }
    adjust_rel_pc = true;
    back_frame->rel_pc = frame->rel_pc;
    back_frame->pc = frame->pc;
    back_frame->sp = frame->sp;

    if (!stepped) {
      if (return_address_attempted) {
        // Remove the speculative frame.
        if (frames->size() > 0) {
          frames->pop_back();
        }
        break;
      } else if (in_device_map) {
        // Do not attempt any other unwinding, pc or sp is in a device
        // map.
        break;
      } else {
        // Stepping didn't work, try this secondary method.
        if (!regs->SetPcFromReturnAddress(process_memory.get())) {
          break;
        }
        return_address_attempted = true;
      }
    } else {
      return_address_attempted = false;
    }
    back_frame->func_name = frame->function_name;
    back_frame->func_offset = frame->function_offset;

    back_frame->map.name = frame->map_name;
    back_frame->map.start = frame->map_start;
    back_frame->map.end = frame->map_end;
    back_frame->map.offset = frame->map_offset;
    back_frame->map.load_bias = frame->map_load_bias;
    back_frame->map.flags = frame->map_flags;
  }

  return true;
+2 −0
Original line number Diff line number Diff line
@@ -107,6 +107,7 @@ cc_test {
        "tests/DwarfOpTest.cpp",
        "tests/DwarfSectionTest.cpp",
        "tests/DwarfSectionImplTest.cpp",
        "tests/ElfFake.cpp",
        "tests/ElfInterfaceArmTest.cpp",
        "tests/ElfInterfaceTest.cpp",
        "tests/ElfTest.cpp",
@@ -125,6 +126,7 @@ cc_test {
        "tests/RegsTest.cpp",
        "tests/SymbolsTest.cpp",
        "tests/UnwindTest.cpp",
        "tests/UnwinderTest.cpp",
    ],

    cflags: [
+0 −20
Original line number Diff line number Diff line
@@ -33,26 +33,6 @@

namespace unwindstack {

template <typename AddressType>
bool RegsImpl<AddressType>::GetReturnAddressFromDefault(Memory* memory, uint64_t* value) {
  switch (return_loc_.type) {
  case LOCATION_REGISTER:
    CHECK(return_loc_.value < total_regs_);
    *value = regs_[return_loc_.value];
    return true;
  case LOCATION_SP_OFFSET:
    AddressType return_value;
    if (!memory->Read(sp_ + return_loc_.value, &return_value, sizeof(return_value))) {
      return false;
    }
    *value = return_value;
    return true;
  case LOCATION_UNKNOWN:
  default:
    return false;
  }
}

RegsArm::RegsArm()
    : RegsImpl<uint32_t>(ARM_REG_LAST, ARM_REG_SP, Location(LOCATION_REGISTER, ARM_REG_LR)) {}

+54 −16
Original line number Diff line number Diff line
@@ -14,9 +14,11 @@
 * limitations under the License.
 */

#define _GNU_SOURCE 1
#include <elf.h>
#include <inttypes.h>
#include <stdint.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>

@@ -28,35 +30,33 @@

namespace unwindstack {

void Unwinder::FillInFrame(MapInfo* map_info, uint64_t* rel_pc) {
void Unwinder::FillInFrame(MapInfo* map_info, Elf* elf, uint64_t rel_pc, bool adjust_pc) {
  size_t frame_num = frames_.size();
  frames_.resize(frame_num + 1);
  FrameData* frame = &frames_.at(frame_num);
  frame->num = frame_num;
  frame->pc = regs_->pc();
  frame->sp = regs_->sp();
  frame->rel_pc = frame->pc;
  frame->rel_pc = rel_pc;

  if (map_info == nullptr) {
    return;
  }

  Elf* elf = map_info->GetElf(process_memory_, true);
  *rel_pc = elf->GetRelPc(regs_->pc(), map_info);
  if (frame_num != 0) {
  if (adjust_pc) {
    // Don't adjust the first frame pc.
    frame->rel_pc = regs_->GetAdjustedPc(*rel_pc, elf);
    frame->rel_pc = regs_->GetAdjustedPc(rel_pc, elf);

    // Adjust the original pc.
    frame->pc -= *rel_pc - frame->rel_pc;
  } else {
    frame->rel_pc = *rel_pc;
    frame->pc -= rel_pc - frame->rel_pc;
  }

  frame->map_name = map_info->name;
  frame->map_offset = map_info->elf_offset;
  frame->map_start = map_info->start;
  frame->map_end = map_info->end;
  frame->map_flags = map_info->flags;
  frame->map_load_bias = elf->GetLoadBias();

  if (!elf->GetFunctionName(frame->rel_pc, &frame->function_name, &frame->function_offset)) {
    frame->function_name = "";
@@ -64,32 +64,70 @@ void Unwinder::FillInFrame(MapInfo* map_info, uint64_t* rel_pc) {
  }
}

void Unwinder::Unwind() {
void Unwinder::Unwind(std::set<std::string>* initial_map_names_to_skip) {
  frames_.clear();

  bool return_address_attempt = false;
  bool adjust_pc = false;
  for (; frames_.size() < max_frames_;) {
    MapInfo* map_info = maps_->Find(regs_->pc());

    uint64_t rel_pc;
    FillInFrame(map_info, &rel_pc);
    Elf* elf;
    if (map_info == nullptr) {
      rel_pc = regs_->pc();
    } else {
      elf = map_info->GetElf(process_memory_, true);
      rel_pc = elf->GetRelPc(regs_->pc(), map_info);
    }

    if (map_info == nullptr || initial_map_names_to_skip == nullptr ||
        initial_map_names_to_skip->find(basename(map_info->name.c_str())) ==
            initial_map_names_to_skip->end()) {
      FillInFrame(map_info, elf, rel_pc, adjust_pc);
      // Once a frame is added, stop skipping frames.
      initial_map_names_to_skip = nullptr;
    }
    adjust_pc = true;

    bool stepped;
    bool in_device_map = false;
    if (map_info == nullptr) {
      stepped = false;
    } else {
      if (map_info->flags & MAPS_FLAGS_DEVICE_MAP) {
        // Do not stop here, fall through in case we are
        // in the speculative unwind path and need to remove
        // some of the speculative frames.
        stepped = false;
        in_device_map = true;
      } else {
        MapInfo* sp_info = maps_->Find(regs_->sp());
        if (sp_info != nullptr && sp_info->flags & MAPS_FLAGS_DEVICE_MAP) {
          // Do not stop here, fall through in case we are
          // in the speculative unwind path and need to remove
          // some of the speculative frames.
          stepped = false;
          in_device_map = true;
        } else {
          bool finished;
      stepped = map_info->elf->Step(rel_pc + map_info->elf_offset, regs_, process_memory_.get(),
                                    &finished);
          stepped =
              elf->Step(rel_pc + map_info->elf_offset, regs_, process_memory_.get(), &finished);
          if (stepped && finished) {
            break;
          }
        }
      }
    }
    if (!stepped) {
      if (return_address_attempt) {
        // Remove the speculative frame.
        frames_.pop_back();
        break;
      } else if (in_device_map) {
        // Do not attempt any other unwinding, pc or sp is in a device
        // map.
        break;
      } else {
        // Steping didn't work, try this secondary method.
        if (!regs_->SetPcFromReturnAddress(process_memory_.get())) {
+0 −4
Original line number Diff line number Diff line
@@ -55,8 +55,6 @@ class Regs {
  virtual uint64_t pc() = 0;
  virtual uint64_t sp() = 0;

  virtual bool GetReturnAddressFromDefault(Memory* memory, uint64_t* value) = 0;

  virtual uint64_t GetAdjustedPc(uint64_t rel_pc, Elf* elf) = 0;

  virtual bool StepIfSignalHandler(uint64_t rel_pc, Elf* elf, Memory* process_memory) = 0;
@@ -86,8 +84,6 @@ class RegsImpl : public Regs {
      : Regs(total_regs, sp_reg, return_loc), regs_(total_regs) {}
  virtual ~RegsImpl() = default;

  bool GetReturnAddressFromDefault(Memory* memory, uint64_t* value) override;

  uint64_t pc() override { return pc_; }
  uint64_t sp() override { return sp_; }

Loading