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

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

Add a new unwind method on error.

If a function crashes by jumping into unexecutable code, the old method
could not unwind through that. Add a fallback method to set the pc from
the default return address location.

In addition, add a new finished check for steps. This will provide a method
to indicate that this step is the last step. This prevents cases where
the fallback method might be triggered incorrectly.

Update the libbacktrace code to unwind using the new methodology.

Update the unwind tool to use the new unwind methodology.

Add a new option to crasher that calls through a null function.

Create a new object, Unwinder, that encapsulates the a basic unwind. For now,
libbacktrace will still use the custom code.

Added new unit tests to cover the new cases. Also add a test that
crashes calling a nullptr as a function, and then has call frames in
the signal stack.

Bug: 65842173

Test: Pass all unit tests, verify crasher dumps properly.
Change-Id: Ia18430ab107e9f7bdf0e14a9b74710b1280bd7f4
parent 0bb8dcb2
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -114,6 +114,11 @@ noinline int do_action_on_thread(const char* arg) {
    return reinterpret_cast<uintptr_t>(result);
}

noinline int crash_null() {
  int (*null_func)() = nullptr;
  return null_func();
}

noinline int crash3(int a) {
    *reinterpret_cast<int*>(0xdead) = a;
    return a*4;
@@ -169,6 +174,7 @@ static int usage() {
    fprintf(stderr, "  nostack               crash with a NULL stack pointer\n");
    fprintf(stderr, "\n");
    fprintf(stderr, "  heap-usage            cause a libc abort by abusing a heap function\n");
    fprintf(stderr, "  call-null             cause a crash by calling through a nullptr\n");
    fprintf(stderr, "  leak                  leak memory until we get OOM-killed\n");
    fprintf(stderr, "\n");
    fprintf(stderr, "  abort                 call abort()\n");
@@ -239,6 +245,8 @@ noinline int do_action(const char* arg) {
        crashnostack();
    } else if (!strcasecmp(arg, "exit")) {
        exit(1);
    } else if (!strcasecmp(arg, "call-null")) {
      return crash_null();
    } else if (!strcasecmp(arg, "crash") || !strcmp(arg, "SIGSEGV")) {
        return crash(42);
    } else if (!strcasecmp(arg, "abort")) {
+105 −53
Original line number Diff line number Diff line
@@ -68,6 +68,32 @@ static bool IsUnwindLibrary(const std::string& map_name) {
  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) {
  UnwindStackMap* stack_map = reinterpret_cast<UnwindStackMap*>(back_map);
@@ -75,70 +101,96 @@ static bool Unwind(unwindstack::Regs* regs, BacktraceMap* back_map,
  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) {
    if (regs->pc() == 0) {
      break;
    }
    unwindstack::MapInfo* map_info = maps->Find(regs->pc());
    bool stepped;
    bool in_device_map = false;
    if (map_info == nullptr) {
      break;
      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--;
      }

    unwindstack::Elf* elf = map_info->GetElf(stack_map->process_memory(), true);
    } else {
      unwindstack::Elf* elf = map_info->GetElf(process_memory, true);
      uint64_t rel_pc = elf->GetRelPc(regs->pc(), map_info);

    bool skip_frame = num_frames == 0 && IsUnwindLibrary(map_info->name);
    if (num_ignore_frames == 0 && !skip_frame) {
      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);
          }

          frames->resize(num_frames + 1);
          backtrace_frame_data_t* frame = &frames->at(num_frames);
          frame->num = num_frames;
      // 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;
          SetFrameInfo(regs, map_info, adjusted_rel_pc, frame);

      frame->map.start = map_info->start;
      frame->map.end = map_info->end;
      frame->map.offset = map_info->offset;
      frame->map.load_bias = elf->GetLoadBias();
      frame->map.flags = map_info->flags;
      frame->map.name = map_info->name;

      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;
          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 if (!skip_frame && num_ignore_frames > 0) {
        } else {
          num_ignore_frames--;
        }
    adjust_rel_pc = true;
      }

    // Do not unwind through a device map.
      if (map_info->flags & PROT_DEVICE_MAP) {
      break;
    }
        // 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;

    if (!elf->Step(rel_pc + map_info->elf_offset, regs, stack_map->process_memory().get())) {
    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;
    }
  }

+34 −22
Original line number Diff line number Diff line
@@ -82,6 +82,14 @@ struct dump_thread_t {
  int32_t done;
};

typedef Backtrace* (*create_func_t)(pid_t, pid_t, BacktraceMap*);
typedef BacktraceMap* (*map_create_func_t)(pid_t, bool);

static void VerifyLevelDump(Backtrace* backtrace, create_func_t create_func = nullptr,
                            map_create_func_t map_func = nullptr);
static void VerifyMaxDump(Backtrace* backtrace, create_func_t create_func = nullptr,
                          map_create_func_t map_func = nullptr);

static uint64_t NanoTime() {
  struct timespec t = { 0, 0 };
  clock_gettime(CLOCK_MONOTONIC, &t);
@@ -147,7 +155,7 @@ static bool ReadyLevelBacktrace(Backtrace* backtrace) {
  return found;
}

static void VerifyLevelDump(Backtrace* backtrace) {
static void VerifyLevelDump(Backtrace* backtrace, create_func_t, map_create_func_t) {
  ASSERT_GT(backtrace->NumFrames(), static_cast<size_t>(0))
    << DumpFrames(backtrace);
  ASSERT_LT(backtrace->NumFrames(), static_cast<size_t>(MAX_BACKTRACE_FRAMES))
@@ -189,7 +197,7 @@ static bool ReadyMaxBacktrace(Backtrace* backtrace) {
  return (backtrace->NumFrames() == MAX_BACKTRACE_FRAMES);
}

static void VerifyMaxDump(Backtrace* backtrace) {
static void VerifyMaxDump(Backtrace* backtrace, create_func_t, map_create_func_t) {
  ASSERT_EQ(backtrace->NumFrames(), static_cast<size_t>(MAX_BACKTRACE_FRAMES))
    << DumpFrames(backtrace);
  // Verify that the last frame is our recursive call.
@@ -251,10 +259,14 @@ TEST(libbacktrace, local_trace) {

static void VerifyIgnoreFrames(Backtrace* bt_all, Backtrace* bt_ign1, Backtrace* bt_ign2,
                               const char* cur_proc) {
  EXPECT_EQ(bt_all->NumFrames(), bt_ign1->NumFrames() + 1)
    << "All backtrace:\n" << DumpFrames(bt_all) << "Ignore 1 backtrace:\n" << DumpFrames(bt_ign1);
  EXPECT_EQ(bt_all->NumFrames(), bt_ign2->NumFrames() + 2)
    << "All backtrace:\n" << DumpFrames(bt_all) << "Ignore 2 backtrace:\n" << DumpFrames(bt_ign2);
  ASSERT_EQ(bt_all->NumFrames(), bt_ign1->NumFrames() + 1) << "All backtrace:\n"
                                                           << DumpFrames(bt_all)
                                                           << "Ignore 1 backtrace:\n"
                                                           << DumpFrames(bt_ign1);
  ASSERT_EQ(bt_all->NumFrames(), bt_ign2->NumFrames() + 2) << "All backtrace:\n"
                                                           << DumpFrames(bt_all)
                                                           << "Ignore 2 backtrace:\n"
                                                           << DumpFrames(bt_ign2);

  // Check all of the frames are the same > the current frame.
  bool check = (cur_proc == nullptr);
@@ -305,9 +317,8 @@ TEST(libbacktrace, local_max_trace) {
}

static void VerifyProcTest(pid_t pid, pid_t tid, bool (*ReadyFunc)(Backtrace*),
                           void (*VerifyFunc)(Backtrace*),
                           Backtrace* (*back_func)(pid_t, pid_t, BacktraceMap*),
                           BacktraceMap* (*map_func)(pid_t, bool)) {
                           void (*VerifyFunc)(Backtrace*, create_func_t, map_create_func_t),
                           create_func_t create_func, map_create_func_t map_create_func) {
  pid_t ptrace_tid;
  if (tid < 0) {
    ptrace_tid = pid;
@@ -324,13 +335,13 @@ static void VerifyProcTest(pid_t pid, pid_t tid, bool (*ReadyFunc)(Backtrace*),
      WaitForStop(ptrace_tid);

      std::unique_ptr<BacktraceMap> map;
      map.reset(map_func(pid, false));
      std::unique_ptr<Backtrace> backtrace(back_func(pid, tid, map.get()));
      map.reset(map_create_func(pid, false));
      std::unique_ptr<Backtrace> backtrace(create_func(pid, tid, map.get()));
      ASSERT_TRUE(backtrace.get() != nullptr);
      ASSERT_TRUE(backtrace->Unwind(0));
      ASSERT_EQ(BACKTRACE_UNWIND_NO_ERROR, backtrace->GetError());
      if (ReadyFunc(backtrace.get())) {
        VerifyFunc(backtrace.get());
        VerifyFunc(backtrace.get(), create_func, map_create_func);
        verified = true;
      } else {
        last_dump = DumpFrames(backtrace.get());
@@ -399,13 +410,15 @@ TEST(libbacktrace, ptrace_max_trace_new) {
  ASSERT_EQ(waitpid(pid, &status, 0), pid);
}

static void VerifyProcessIgnoreFrames(Backtrace* bt_all) {
  std::unique_ptr<Backtrace> ign1(Backtrace::Create(bt_all->Pid(), BACKTRACE_CURRENT_THREAD));
static void VerifyProcessIgnoreFrames(Backtrace* bt_all, create_func_t create_func,
                                      map_create_func_t map_create_func) {
  std::unique_ptr<BacktraceMap> map(map_create_func(bt_all->Pid(), false));
  std::unique_ptr<Backtrace> ign1(create_func(bt_all->Pid(), BACKTRACE_CURRENT_THREAD, map.get()));
  ASSERT_TRUE(ign1.get() != nullptr);
  ASSERT_TRUE(ign1->Unwind(1));
  ASSERT_EQ(BACKTRACE_UNWIND_NO_ERROR, ign1->GetError());

  std::unique_ptr<Backtrace> ign2(Backtrace::Create(bt_all->Pid(), BACKTRACE_CURRENT_THREAD));
  std::unique_ptr<Backtrace> ign2(create_func(bt_all->Pid(), BACKTRACE_CURRENT_THREAD, map.get()));
  ASSERT_TRUE(ign2.get() != nullptr);
  ASSERT_TRUE(ign2->Unwind(2));
  ASSERT_EQ(BACKTRACE_UNWIND_NO_ERROR, ign2->GetError());
@@ -1702,9 +1715,8 @@ static void SetValueAndLoop(void* data) {
    ;
}

static void UnwindThroughSignal(bool use_action,
                                Backtrace* (*back_func)(pid_t, pid_t, BacktraceMap*),
                                BacktraceMap* (*map_func)(pid_t, bool)) {
static void UnwindThroughSignal(bool use_action, create_func_t create_func,
                                map_create_func_t map_create_func) {
  volatile int value = 0;
  pid_t pid;
  if ((pid = fork()) == 0) {
@@ -1730,8 +1742,8 @@ static void UnwindThroughSignal(bool use_action,

    WaitForStop(pid);

    std::unique_ptr<BacktraceMap> map(map_func(pid, false));
    std::unique_ptr<Backtrace> backtrace(back_func(pid, pid, map.get()));
    std::unique_ptr<BacktraceMap> map(map_create_func(pid, false));
    std::unique_ptr<Backtrace> backtrace(create_func(pid, pid, map.get()));

    size_t bytes_read = backtrace->Read(reinterpret_cast<uintptr_t>(const_cast<int*>(&value)),
                                        reinterpret_cast<uint8_t*>(&read_value), sizeof(read_value));
@@ -1758,9 +1770,9 @@ static void UnwindThroughSignal(bool use_action,

    WaitForStop(pid);

    map.reset(map_func(pid, false));
    map.reset(map_create_func(pid, false));
    ASSERT_TRUE(map.get() != nullptr);
    backtrace.reset(back_func(pid, pid, map.get()));
    backtrace.reset(create_func(pid, pid, map.get()));
    ASSERT_TRUE(backtrace->Unwind(0));
    bool found = false;
    for (frame_iter = backtrace->begin(); frame_iter != backtrace->end(); ++frame_iter) {
+1 −0
Original line number Diff line number Diff line
@@ -61,6 +61,7 @@ cc_library {
        "Maps.cpp",
        "Memory.cpp",
        "Regs.cpp",
        "Unwinder.cpp",
        "Symbols.cpp",
    ],

+8 −5
Original line number Diff line number Diff line
@@ -47,7 +47,7 @@ const DwarfFde* DwarfSection::GetFdeFromPc(uint64_t pc) {
  return nullptr;
}

bool DwarfSection::Step(uint64_t pc, Regs* regs, Memory* process_memory) {
bool DwarfSection::Step(uint64_t pc, Regs* regs, Memory* process_memory, bool* finished) {
  last_error_ = DWARF_ERROR_NONE;
  const DwarfFde* fde = GetFdeFromPc(pc);
  if (fde == nullptr || fde->cie == nullptr) {
@@ -62,7 +62,7 @@ bool DwarfSection::Step(uint64_t pc, Regs* regs, Memory* process_memory) {
  }

  // Now eval the actual registers.
  return Eval(fde->cie, process_memory, loc_regs, regs);
  return Eval(fde->cie, process_memory, loc_regs, regs, finished);
}

template <typename AddressType>
@@ -92,7 +92,8 @@ bool DwarfSectionImpl<AddressType>::EvalExpression(const DwarfLocation& loc, uin

template <typename AddressType>
bool DwarfSectionImpl<AddressType>::Eval(const DwarfCie* cie, Memory* regular_memory,
                                         const dwarf_loc_regs_t& loc_regs, Regs* regs) {
                                         const dwarf_loc_regs_t& loc_regs, Regs* regs,
                                         bool* finished) {
  RegsImpl<AddressType>* cur_regs = reinterpret_cast<RegsImpl<AddressType>*>(regs);
  if (cie->return_address_register >= cur_regs->total_regs()) {
    last_error_ = DWARF_ERROR_ILLEGAL_VALUE;
@@ -224,12 +225,14 @@ bool DwarfSectionImpl<AddressType>::Eval(const DwarfCie* cie, Memory* regular_me
  // Find the return address location.
  if (return_address_undefined) {
    cur_regs->set_pc(0);
    *finished = true;
  } else {
    cur_regs->set_pc((*cur_regs)[cie->return_address_register]);
    *finished = false;
  }
  cur_regs->set_sp(cfa);
  // Stop if the cfa and pc are the same.
  return prev_cfa != cfa || prev_pc != cur_regs->pc();
  // Return false if the unwind is not finished or the cfa and pc didn't change.
  return *finished || prev_cfa != cfa || prev_pc != cur_regs->pc();
}

template <typename AddressType>
Loading