Commit 96f3ea0d authored by Jonas Devlieghere's avatar Jonas Devlieghere
Browse files

[lldb/debugserver] Implement hardware breakpoints for x86_64 and i386

This implements hardware breakpoints for x86_64 and i386 in debugserver.
It's based on Pedro's patch sent to lldb-commits [1] although most of it
is the same as the existing hardware watchpoint implementation.

[1] http://lists.llvm.org/pipermail/lldb-commits/Week-of-Mon-20200113/060327.html

Differential revision: https://reviews.llvm.org/D72985
parent 9902c8e3
Loading
Loading
Loading
Loading
+32 −12
Original line number Diff line number Diff line
@@ -9,26 +9,47 @@ from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbutil

# Hardware breakpoints are supported only by platforms mentioned in oslist.
@skipUnlessPlatform(oslist=['linux'])
class HardwareBreakpointMultiThreadTestCase(TestBase):
    NO_DEBUG_INFO_TESTCASE = True

    mydir = TestBase.compute_mydir(__file__)

    # LLDB supports hardware breakpoints for arm and aarch64 architectures.
    # LLDB on linux supports hardware breakpoints for arm and aarch64
    # architectures.
    @skipUnlessPlatform(oslist=['linux'])
    @skipIf(archs=no_match(['arm', 'aarch64']))
    def test_hw_break_set_delete_multi_thread(self):
    def test_hw_break_set_delete_multi_thread_linux(self):
        self.build()
        self.setTearDownCleanup()
        self.break_multi_thread('delete')
        self.break_multi_thread('delete', 'breakpoint')

    # LLDB supports hardware breakpoints for arm and aarch64 architectures.
    # LLDB on linux supports hardware breakpoints for arm and aarch64
    # architectures.
    @skipUnlessPlatform(oslist=['linux'])
    @skipIf(archs=no_match(['arm', 'aarch64']))
    def test_hw_break_set_disable_multi_thread(self):
    def test_hw_break_set_disable_multi_thread_linux(self):
        self.build()
        self.setTearDownCleanup()
        self.break_multi_thread('disable', 'breakpoint')

    # LLDB on darwin supports hardware breakpoints for arm, aarch64, x86_64 and
    # i386 architectures.
    @skipUnlessDarwin
    @skipIfOutOfTreeDebugserver
    def test_hw_break_set_delete_multi_thread_macos(self):
        self.build()
        self.setTearDownCleanup()
        self.break_multi_thread('disable')
        self.break_multi_thread('delete', 'EXC_BREAKPOINT')

    # LLDB on darwin supports hardware breakpoints for arm, aarch64, x86_64 and
    # i386 architectures.
    @skipUnlessDarwin
    @skipIfOutOfTreeDebugserver
    def test_hw_break_set_disable_multi_thread_macos(self):
        self.build()
        self.setTearDownCleanup()
        self.break_multi_thread('disable', 'EXC_BREAKPOINT')


    def setUp(self):
        # Call super's setUp().
@@ -39,7 +60,7 @@ class HardwareBreakpointMultiThreadTestCase(TestBase):
        self.first_stop = line_number(
            self.source, 'Starting thread creation with hardware breakpoint set')

    def break_multi_thread(self, removal_type):
    def break_multi_thread(self, removal_type, stop_reason):
        """Test that lldb hardware breakpoints work for multiple threads."""
        self.runCmd("file " + self.getBuildArtifact("a.out"),
                    CURRENT_EXECUTABLE_SET)
@@ -54,8 +75,7 @@ class HardwareBreakpointMultiThreadTestCase(TestBase):
        # We should be stopped again due to the breakpoint.
        # The stop reason of the thread should be breakpoint.
        self.expect("thread list", STOPPED_DUE_TO_BREAKPOINT,
                    substrs=['stopped',
                             'stop reason = breakpoint'])
                    substrs=['stopped', 'stop reason = breakpoint'])

        # Now set a hardware breakpoint in thread function.
        self.expect("breakpoint set -b hw_break_function --hardware",
@@ -75,7 +95,7 @@ class HardwareBreakpointMultiThreadTestCase(TestBase):
            # The stop reason of the thread should be breakpoint.
            self.expect("thread list", STOPPED_DUE_TO_BREAKPOINT,
                substrs=[
                    'stop reason = breakpoint',
                    'stop reason = {}'.format(stop_reason),
                    'hw_break_function'])

            # Continue the loop and test that we are stopped 4 times.
+4 −2
Original line number Diff line number Diff line
@@ -980,7 +980,8 @@ uint32_t DNBArchMachARM::NumSupportedHardwareWatchpoints() {
}

uint32_t DNBArchMachARM::EnableHardwareBreakpoint(nub_addr_t addr,
                                                  nub_size_t size) {
                                                  nub_size_t size,
                                                  bool also_set_on_task) {
  // Make sure our address isn't bogus
  if (addr & 1)
    return INVALID_NUB_HW_INDEX;
@@ -1052,7 +1053,8 @@ uint32_t DNBArchMachARM::EnableHardwareBreakpoint(nub_addr_t addr,
  return INVALID_NUB_HW_INDEX;
}

bool DNBArchMachARM::DisableHardwareBreakpoint(uint32_t hw_index) {
bool DNBArchMachARM::DisableHardwareBreakpoint(uint32_t hw_index,
                                               bool also_set_on_task) {
  kern_return_t kret = GetDBGState(false);

  const uint32_t num_hw_points = NumSupportedHardwareBreakpoints();
+4 −2
Original line number Diff line number Diff line
@@ -70,8 +70,10 @@ public:

  virtual uint32_t NumSupportedHardwareBreakpoints();
  virtual uint32_t NumSupportedHardwareWatchpoints();
  virtual uint32_t EnableHardwareBreakpoint(nub_addr_t addr, nub_size_t size);
  virtual bool DisableHardwareBreakpoint(uint32_t hw_break_index);
  virtual uint32_t EnableHardwareBreakpoint(nub_addr_t addr, nub_size_t size,
                                            bool also_set_on_task);
  virtual bool DisableHardwareBreakpoint(uint32_t hw_break_index,
                                         bool also_set_on_task);

  virtual uint32_t EnableHardwareWatchpoint(nub_addr_t addr, nub_size_t size,
                                            bool read, bool write,
+150 −0
Original line number Diff line number Diff line
@@ -718,6 +718,11 @@ bool DNBArchImplI386::NotifyException(MachException::Data &exc) {
  return false;
}

uint32_t DNBArchImplI386::NumSupportedHardwareBreakpoints() {
  // Available debug address registers: dr0, dr1, dr2, dr3.
  return 4;
}

uint32_t DNBArchImplI386::NumSupportedHardwareWatchpoints() {
  // Available debug address registers: dr0, dr1, dr2, dr3.
  return 4;
@@ -797,6 +802,151 @@ void DNBArchImplI386::SetWatchpoint(DBG &debug_state, uint32_t hw_index,
  return;
}

void DNBArchImplI386::SetHardwareBreakpoint(DBG &debug_state, uint32_t hw_index,
                                            nub_addr_t addr, nub_size_t size) {
  // Set both dr7 (debug control register) and dri (debug address register).

  // dr7{7-0} encodes the local/gloabl enable bits:
  //  global enable --. .-- local enable
  //                  | |
  //                  v v
  //      dr0 -> bits{1-0}
  //      dr1 -> bits{3-2}
  //      dr2 -> bits{5-4}
  //      dr3 -> bits{7-6}
  //
  // dr7{31-16} encodes the rw/len bits:
  //  b_x+3, b_x+2, b_x+1, b_x
  //      where bits{x+1, x} => rw
  //            0b00: execute, 0b01: write, 0b11: read-or-write, 0b10: io
  //            read-or-write (unused)
  //      and bits{x+3, x+2} => len
  //            0b00: 1-byte, 0b01: 2-byte, 0b11: 4-byte, 0b10: 8-byte
  //
  //      dr0 -> bits{19-16}
  //      dr1 -> bits{23-20}
  //      dr2 -> bits{27-24}
  //      dr3 -> bits{31-28}
  debug_state.__dr7 |= (1 << (2 * hw_index) | 0 << (16 + 4 * hw_index));
  uint32_t addr_32 = addr & 0xffffffff;
  switch (hw_index) {
  case 0:
    debug_state.__dr0 = addr_32;
    break;
  case 1:
    debug_state.__dr1 = addr_32;
    break;
  case 2:
    debug_state.__dr2 = addr_32;
    break;
  case 3:
    debug_state.__dr3 = addr_32;
    break;
  default:
    assert(0 &&
           "invalid hardware register index, must be one of 0, 1, 2, or 3");
  }
  return;
}

uint32_t DNBArchImplI386::EnableHardwareBreakpoint(nub_addr_t addr,
                                                   nub_size_t size,
                                                   bool also_set_on_task) {
  DNBLogThreadedIf(LOG_BREAKPOINTS,
                   "DNBArchImplI386::EnableHardwareBreakpoint( addr = "
                   "0x%8.8llx, size = %llu )",
                   (uint64_t)addr, (uint64_t)size);

  const uint32_t num_hw_breakpoints = NumSupportedHardwareBreakpoints();
  // Read the debug state
  kern_return_t kret = GetDBGState(false);

  if (kret != KERN_SUCCESS) {
    return INVALID_NUB_HW_INDEX;
  }

  // Check to make sure we have the needed hardware support
  uint32_t i = 0;

  DBG &debug_state = m_state.context.dbg;
  for (i = 0; i < num_hw_breakpoints; ++i) {
    if (IsWatchpointVacant(debug_state, i)) {
      break;
    }
  }

  // See if we found an available hw breakpoint slot above
  if (i < num_hw_breakpoints) {
    DNBLogThreadedIf(
        LOG_BREAKPOINTS,
        "DNBArchImplI386::EnableHardwareBreakpoint( free slot = %u )", i);

    StartTransForHWP();

    // Modify our local copy of the debug state, first.
    SetHardwareBreakpoint(debug_state, i, addr, size);
    // Now set the watch point in the inferior.
    kret = SetDBGState(also_set_on_task);

    DNBLogThreadedIf(LOG_BREAKPOINTS,
                     "DNBArchImplI386::"
                     "EnableHardwareBreakpoint() "
                     "SetDBGState() => 0x%8.8x.",
                     kret);

    if (kret == KERN_SUCCESS) {
      DNBLogThreadedIf(
          LOG_BREAKPOINTS,
          "DNBArchImplI386::EnableHardwareBreakpoint( enabled at slot = %u)",
          i);
      return i;
    }
    // Revert to the previous debug state voluntarily.  The transaction
    // coordinator knows that we have failed.
    else {
      m_state.context.dbg = GetDBGCheckpoint();
    }
  } else {
    DNBLogThreadedIf(LOG_BREAKPOINTS,
                     "DNBArchImplI386::EnableHardwareBreakpoint(addr = "
                     "0x%8.8llx, size = %llu) => all hardware breakpoint "
                     "resources are being used.",
                     (uint64_t)addr, (uint64_t)size);
  }

  return INVALID_NUB_HW_INDEX;
}

bool DNBArchImplI386::DisableHardwareBreakpoint(uint32_t hw_index,
                                                bool also_set_on_task) {
  kern_return_t kret = GetDBGState(false);

  const uint32_t num_hw_points = NumSupportedHardwareBreakpoints();
  if (kret == KERN_SUCCESS) {
    DBG &debug_state = m_state.context.dbg;
    if (hw_index < num_hw_points &&
        !IsWatchpointVacant(debug_state, hw_index)) {

      StartTransForHWP();

      // Modify our local copy of the debug state, first.
      ClearWatchpoint(debug_state, hw_index);
      // Now disable the watch point in the inferior.
      kret = SetDBGState(true);
      DNBLogThreadedIf(LOG_WATCHPOINTS,
                       "DNBArchImplI386::DisableHardwareBreakpoint( %u )",
                       hw_index);

      if (kret == KERN_SUCCESS)
        return true;
      else // Revert to the previous debug state voluntarily.  The transaction
           // coordinator knows that we have failed.
        m_state.context.dbg = GetDBGCheckpoint();
    }
  }
  return false;
}

void DNBArchImplI386::ClearWatchpoint(DBG &debug_state, uint32_t hw_index) {
  debug_state.__dr7 &= ~(3 << (2 * hw_index));
  switch (hw_index) {
+8 −0
Original line number Diff line number Diff line
@@ -51,7 +51,12 @@ public:
  virtual bool ThreadDidStop();
  virtual bool NotifyException(MachException::Data &exc);

  virtual uint32_t NumSupportedHardwareBreakpoints();
  virtual uint32_t NumSupportedHardwareWatchpoints();
  virtual uint32_t EnableHardwareBreakpoint(nub_addr_t addr, nub_size_t size,
                                            bool also_set_on_task);
  virtual bool DisableHardwareBreakpoint(uint32_t hw_index,
                                         bool also_set_on_task);
  virtual uint32_t EnableHardwareWatchpoint(nub_addr_t addr, nub_size_t size,
                                            bool read, bool write,
                                            bool also_set_on_task);
@@ -210,6 +215,9 @@ protected:

  static uint32_t GetRegisterContextSize();

  static void SetHardwareBreakpoint(DBG &debug_state, uint32_t hw_index,
                                    nub_addr_t addr, nub_size_t size);

  // Helper functions for watchpoint manipulations.
  static void SetWatchpoint(DBG &debug_state, uint32_t hw_index,
                            nub_addr_t addr, nub_size_t size, bool read,
Loading