[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
This commit is contained in:
Jonas Devlieghere 2020-01-24 15:06:00 -08:00
parent 9902c8e3c6
commit 96f3ea0d21
8 changed files with 364 additions and 22 deletions

View File

@ -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')
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('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.

View File

@ -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();

View File

@ -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,

View File

@ -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) {

View File

@ -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,

View File

@ -679,6 +679,12 @@ uint32_t DNBArchImplX86_64::NumSupportedHardwareWatchpoints() {
return 4;
}
uint32_t DNBArchImplX86_64::NumSupportedHardwareBreakpoints() {
DNBLogThreadedIf(LOG_BREAKPOINTS,
"DNBArchImplX86_64::NumSupportedHardwareBreakpoints");
return 4;
}
static uint32_t size_and_rw_bits(nub_size_t size, bool read, bool write) {
uint32_t rw;
if (read) {
@ -853,6 +859,153 @@ DNBArchImplX86_64::DBG DNBArchImplX86_64::GetDBGCheckpoint() {
return m_2pc_dbg_checkpoint;
}
void DNBArchImplX86_64::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));
switch (hw_index) {
case 0:
debug_state.__dr0 = addr;
break;
case 1:
debug_state.__dr1 = addr;
break;
case 2:
debug_state.__dr2 = addr;
break;
case 3:
debug_state.__dr3 = addr;
break;
default:
assert(0 &&
"invalid hardware register index, must be one of 0, 1, 2, or 3");
}
return;
}
uint32_t DNBArchImplX86_64::EnableHardwareBreakpoint(nub_addr_t addr,
nub_size_t size,
bool also_set_on_task) {
DNBLogThreadedIf(LOG_BREAKPOINTS,
"DNBArchImplX86_64::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,
"DNBArchImplX86_64::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,
"DNBArchImplX86_64::"
"EnableHardwareBreakpoint() "
"SetDBGState() => 0x%8.8x.",
kret);
if (kret == KERN_SUCCESS) {
DNBLogThreadedIf(
LOG_BREAKPOINTS,
"DNBArchImplX86_64::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,
"DNBArchImplX86_64::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 DNBArchImplX86_64::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,
"DNBArchImplX86_64::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;
}
uint32_t DNBArchImplX86_64::EnableHardwareWatchpoint(nub_addr_t addr,
nub_size_t size, bool read,
bool write,

View File

@ -50,7 +50,13 @@ 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_break_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);
@ -213,6 +219,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,

View File

@ -279,12 +279,10 @@ void RNBRemote::CreatePacketTable() {
"x", "Read data from memory"));
t.push_back(Packet(write_data_to_memory, &RNBRemote::HandlePacket_X, NULL,
"X", "Write data to memory"));
// t.push_back (Packet (insert_hardware_bp,
// &RNBRemote::HandlePacket_UNIMPLEMENTED, NULL, "Z1", "Insert hardware
// breakpoint"));
// t.push_back (Packet (remove_hardware_bp,
// &RNBRemote::HandlePacket_UNIMPLEMENTED, NULL, "z1", "Remove hardware
// breakpoint"));
t.push_back(Packet(insert_hardware_bp, &RNBRemote::HandlePacket_z, NULL, "Z1",
"Insert hardware breakpoint"));
t.push_back(Packet(remove_hardware_bp, &RNBRemote::HandlePacket_z, NULL, "z1",
"Remove hardware breakpoint"));
t.push_back(Packet(insert_write_watch_bp, &RNBRemote::HandlePacket_z, NULL,
"Z2", "Insert write watchpoint"));
t.push_back(Packet(remove_write_watch_bp, &RNBRemote::HandlePacket_z, NULL,