ClearAllHardwareWatchpoints ();
virtual Error
- IsWatchpointHit (uint8_t wp_index);
+ IsWatchpointHit(uint32_t wp_index, bool &is_hit);
virtual Error
- IsWatchpointVacant (uint32_t wp_index);
+ GetWatchpointHitIndex(uint32_t &wp_index);
+
+ virtual Error
+ IsWatchpointVacant (uint32_t wp_index, bool &is_vacant);
virtual lldb::addr_t
GetWatchpointAddress (uint32_t wp_index);
}
Error
-NativeRegisterContext::IsWatchpointHit (uint8_t wp_index)
+NativeRegisterContext::IsWatchpointHit(uint32_t wp_index, bool &is_hit)
{
+ is_hit = false;
return Error ("not implemented");
}
Error
-NativeRegisterContext::IsWatchpointVacant (uint32_t wp_index)
+NativeRegisterContext::GetWatchpointHitIndex(uint32_t &wp_index)
{
+ wp_index = LLDB_INVALID_INDEX32;
+ return Error ("not implemented");
+}
+
+Error
+NativeRegisterContext::IsWatchpointVacant (uint32_t wp_index, bool &is_vacant)
+{
+ is_vacant = false;
return Error ("not implemented");
}
#include "lldb/Symbol/ObjectFile.h"
#include "lldb/Target/Process.h"
#include "lldb/Target/ProcessLaunchInfo.h"
+#include "lldb/Utility/LLDBAssert.h"
#include "lldb/Utility/PseudoTerminal.h"
#include "Plugins/Process/POSIX/ProcessPOSIXLog.h"
}
case 0:
- case TRAP_TRACE:
- // We receive this on single stepping.
- if (log)
- log->Printf ("NativeProcessLinux::%s() received trace event, pid = %" PRIu64 " (single stepping)", __FUNCTION__, pid);
-
+ case TRAP_TRACE: // We receive this on single stepping.
+ case TRAP_HWBKPT: // We receive this on watchpoint hit
if (thread_sp)
{
- std::static_pointer_cast<NativeThreadLinux> (thread_sp)->SetStoppedByTrace ();
- }
-
- // This thread is currently stopped.
- NotifyThreadStop (pid);
-
- // Here we don't have to request the rest of the threads to stop or request a deferred stop.
- // This would have already happened at the time the Resume() with step operation was signaled.
- // At this point, we just need to say we stopped, and the deferred notifcation will fire off
- // once all running threads have checked in as stopped.
- SetCurrentThreadID (pid);
- // Tell the process we have a stop (from software breakpoint).
- CallAfterRunningThreadsStop (pid,
- [=] (lldb::tid_t signaling_tid)
- {
- SetState (StateType::eStateStopped, true);
- });
- break;
-
- case SI_KERNEL:
- case TRAP_BRKPT:
- if (log)
- log->Printf ("NativeProcessLinux::%s() received breakpoint event, pid = %" PRIu64, __FUNCTION__, pid);
-
- // This thread is currently stopped.
- NotifyThreadStop (pid);
-
- // Mark the thread as stopped at breakpoint.
- if (thread_sp)
- {
- std::static_pointer_cast<NativeThreadLinux> (thread_sp)->SetStoppedByBreakpoint ();
- Error error = FixupBreakpointPCAsNeeded (thread_sp);
- if (error.Fail ())
+ // If a watchpoint was hit, report it
+ uint32_t wp_index;
+ Error error = thread_sp->GetRegisterContext()->GetWatchpointHitIndex(wp_index);
+ if (error.Fail() && log)
+ log->Printf("NativeProcessLinux::%s() "
+ "received error while checking for watchpoint hits, "
+ "pid = %" PRIu64 " error = %s",
+ __FUNCTION__, pid, error.AsCString());
+ if (wp_index != LLDB_INVALID_INDEX32)
{
- if (log)
- log->Printf ("NativeProcessLinux::%s() pid = %" PRIu64 " fixup: %s", __FUNCTION__, pid, error.AsCString ());
+ MonitorWatchpoint(pid, thread_sp, wp_index);
+ break;
}
}
- else
- {
- if (log)
- log->Printf ("NativeProcessLinux::%s() pid = %" PRIu64 ": warning, cannot process software breakpoint since no thread metadata", __FUNCTION__, pid);
- }
-
-
- // We need to tell all other running threads before we notify the delegate about this stop.
- CallAfterRunningThreadsStop (pid,
- [=](lldb::tid_t deferred_notification_tid)
- {
- SetCurrentThreadID (deferred_notification_tid);
- // Tell the process we have a stop (from software breakpoint).
- SetState (StateType::eStateStopped, true);
- });
+ // Otherwise, report step over
+ MonitorTrace(pid, thread_sp);
break;
- case TRAP_HWBKPT:
- if (log)
- log->Printf ("NativeProcessLinux::%s() received watchpoint event, pid = %" PRIu64, __FUNCTION__, pid);
-
- // This thread is currently stopped.
- NotifyThreadStop (pid);
-
- // Mark the thread as stopped at watchpoint.
- // The address is at (lldb::addr_t)info->si_addr if we need it.
- if (thread_sp)
- std::static_pointer_cast<NativeThreadLinux> (thread_sp)->SetStoppedByWatchpoint ();
- else
- {
- if (log)
- log->Printf ("NativeProcessLinux::%s() pid %" PRIu64 " tid %" PRIu64 ": warning, cannot process hardware breakpoint since no thread metadata", __FUNCTION__, GetID (), pid);
- }
-
- // We need to tell all other running threads before we notify the delegate about this stop.
- CallAfterRunningThreadsStop (pid,
- [=](lldb::tid_t deferred_notification_tid)
- {
- SetCurrentThreadID (deferred_notification_tid);
- // Tell the process we have a stop (from hardware breakpoint).
- SetState (StateType::eStateStopped, true);
- });
+ case SI_KERNEL:
+ case TRAP_BRKPT:
+ MonitorBreakpoint(pid, thread_sp);
break;
case SIGTRAP:
}
void
+NativeProcessLinux::MonitorTrace(lldb::pid_t pid, NativeThreadProtocolSP thread_sp)
+{
+ Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS));
+ if (log)
+ log->Printf("NativeProcessLinux::%s() received trace event, pid = %" PRIu64 " (single stepping)",
+ __FUNCTION__, pid);
+
+ if (thread_sp)
+ std::static_pointer_cast<NativeThreadLinux>(thread_sp)->SetStoppedByTrace();
+
+ // This thread is currently stopped.
+ NotifyThreadStop(pid);
+
+ // Here we don't have to request the rest of the threads to stop or request a deferred stop.
+ // This would have already happened at the time the Resume() with step operation was signaled.
+ // At this point, we just need to say we stopped, and the deferred notifcation will fire off
+ // once all running threads have checked in as stopped.
+ SetCurrentThreadID(pid);
+ // Tell the process we have a stop (from software breakpoint).
+ CallAfterRunningThreadsStop(pid,
+ [=](lldb::tid_t signaling_tid)
+ {
+ SetState(StateType::eStateStopped, true);
+ });
+}
+
+void
+NativeProcessLinux::MonitorBreakpoint(lldb::pid_t pid, NativeThreadProtocolSP thread_sp)
+{
+ Log *log(GetLogIfAnyCategoriesSet(LIBLLDB_LOG_PROCESS | LIBLLDB_LOG_BREAKPOINTS));
+ if (log)
+ log->Printf("NativeProcessLinux::%s() received breakpoint event, pid = %" PRIu64,
+ __FUNCTION__, pid);
+
+ // This thread is currently stopped.
+ NotifyThreadStop(pid);
+
+ // Mark the thread as stopped at breakpoint.
+ if (thread_sp)
+ {
+ std::static_pointer_cast<NativeThreadLinux>(thread_sp)->SetStoppedByBreakpoint();
+ Error error = FixupBreakpointPCAsNeeded(thread_sp);
+ if (error.Fail())
+ if (log)
+ log->Printf("NativeProcessLinux::%s() pid = %" PRIu64 " fixup: %s",
+ __FUNCTION__, pid, error.AsCString());
+ }
+ else
+ if (log)
+ log->Printf("NativeProcessLinux::%s() pid = %" PRIu64 ": "
+ "warning, cannot process software breakpoint since no thread metadata",
+ __FUNCTION__, pid);
+
+
+ // We need to tell all other running threads before we notify the delegate about this stop.
+ CallAfterRunningThreadsStop(pid,
+ [=](lldb::tid_t deferred_notification_tid)
+ {
+ SetCurrentThreadID(deferred_notification_tid);
+ // Tell the process we have a stop (from software breakpoint).
+ SetState(StateType::eStateStopped, true);
+ });
+}
+
+void
+NativeProcessLinux::MonitorWatchpoint(lldb::pid_t pid, NativeThreadProtocolSP thread_sp, uint32_t wp_index)
+{
+ Log *log(GetLogIfAnyCategoriesSet(LIBLLDB_LOG_PROCESS | LIBLLDB_LOG_WATCHPOINTS));
+ if (log)
+ log->Printf("NativeProcessLinux::%s() received watchpoint event, "
+ "pid = %" PRIu64 ", wp_index = %" PRIu32,
+ __FUNCTION__, pid, wp_index);
+
+ // This thread is currently stopped.
+ NotifyThreadStop(pid);
+
+ // Mark the thread as stopped at watchpoint.
+ // The address is at (lldb::addr_t)info->si_addr if we need it.
+ lldbassert(thread_sp && "thread_sp cannot be NULL");
+ std::static_pointer_cast<NativeThreadLinux>(thread_sp)->SetStoppedByWatchpoint(wp_index);
+
+ // We need to tell all other running threads before we notify the delegate about this stop.
+ CallAfterRunningThreadsStop(pid,
+ [=](lldb::tid_t deferred_notification_tid)
+ {
+ SetCurrentThreadID(deferred_notification_tid);
+ // Tell the process we have a stop (from watchpoint).
+ SetState(StateType::eStateStopped, true);
+ });
+}
+
+void
NativeProcessLinux::MonitorSignal(const siginfo_t *info, lldb::pid_t pid, bool exited)
{
assert (info && "null info");
MonitorSIGTRAP(const siginfo_t *info, lldb::pid_t pid);
void
+ MonitorTrace(lldb::pid_t pid, NativeThreadProtocolSP thread_sp);
+
+ void
+ MonitorBreakpoint(lldb::pid_t pid, NativeThreadProtocolSP thread_sp);
+
+ void
+ MonitorWatchpoint(lldb::pid_t pid, NativeThreadProtocolSP thread_sp, uint32_t wp_index);
+
+ void
MonitorSignal(const siginfo_t *info, lldb::pid_t pid, bool exited);
#if 0
}
Error
-NativeRegisterContextLinux_mips64::IsWatchpointHit (uint8_t wp_index)
+NativeRegisterContextLinux_mips64::IsWatchpointHit (uint32_t wp_index, bool &is_hit)
{
- Error error;
- error.SetErrorString ("MIPS TODO: NativeRegisterContextLinux_mips64::IsWatchpointHit not implemented");
- return error;
+ is_hit = false;
+ return Error("MIPS TODO: NativeRegisterContextLinux_mips64::IsWatchpointHit not implemented");
}
Error
-NativeRegisterContextLinux_mips64::IsWatchpointVacant (uint32_t wp_index)
+NativeRegisterContextLinux_mips64::IsWatchpointVacant (uint32_t wp_index, bool &is_vacant)
{
- Error error;
- error.SetErrorString ("MIPS TODO: NativeRegisterContextLinux_mips64::IsWatchpointVacant not implemented");
- return error;
+ is_vacant = false;
+ return Error("MIPS TODO: NativeRegisterContextLinux_mips64::IsWatchpointVacant not implemented");
}
bool
WriteAllRegisterValues (const lldb::DataBufferSP &data_sp) override;
Error
- IsWatchpointHit (uint8_t wp_index) override;
+ IsWatchpointHit (uint32_t wp_index, bool &is_hit) override;
Error
- IsWatchpointVacant (uint32_t wp_index) override;
+ IsWatchpointVacant (uint32_t wp_index, bool &is_vacant) override;
bool
ClearHardwareWatchpoint (uint32_t wp_index) override;
#include "NativeRegisterContextLinux_x86_64.h"
+#include "lldb/Core/Log.h"
#include "lldb/lldb-private-forward.h"
#include "lldb/Core/DataBufferHeap.h"
#include "lldb/Core/Error.h"
}
Error
-NativeRegisterContextLinux_x86_64::IsWatchpointHit(uint8_t wp_index)
+NativeRegisterContextLinux_x86_64::IsWatchpointHit(uint32_t wp_index, bool &is_hit)
{
if (wp_index >= NumSupportedHardwareWatchpoints())
- return Error ("Watchpoint index out of range");
+ return Error("Watchpoint index out of range");
RegisterValue reg_value;
Error error = ReadRegisterRaw(m_reg_info.first_dr + 6, reg_value);
- if (error.Fail()) return error;
+ if (error.Fail())
+ {
+ is_hit = false;
+ return error;
+ }
uint64_t status_bits = reg_value.GetAsUInt64();
- bool is_hit = status_bits & (1 << wp_index);
-
- error.SetError (!is_hit, lldb::eErrorTypeInvalid);
+ is_hit = status_bits & (1 << wp_index);
return error;
}
Error
-NativeRegisterContextLinux_x86_64::IsWatchpointVacant(uint32_t wp_index)
+NativeRegisterContextLinux_x86_64::GetWatchpointHitIndex(uint32_t &wp_index) {
+ uint32_t num_hw_wps = NumSupportedHardwareWatchpoints();
+ for (wp_index = 0; wp_index < num_hw_wps; ++wp_index)
+ {
+ bool is_hit;
+ Error error = IsWatchpointHit(wp_index, is_hit);
+ if (error.Fail()) {
+ wp_index = LLDB_INVALID_INDEX32;
+ return error;
+ } else if (is_hit) {
+ return error;
+ }
+ }
+ wp_index = LLDB_INVALID_INDEX32;
+ return Error();
+}
+
+Error
+NativeRegisterContextLinux_x86_64::IsWatchpointVacant(uint32_t wp_index, bool &is_vacant)
{
if (wp_index >= NumSupportedHardwareWatchpoints())
return Error ("Watchpoint index out of range");
RegisterValue reg_value;
Error error = ReadRegisterRaw(m_reg_info.first_dr + 7, reg_value);
- if (error.Fail()) return error;
+ if (error.Fail())
+ {
+ is_vacant = false;
+ return error;
+ }
uint64_t control_bits = reg_value.GetAsUInt64();
- bool is_vacant = !(control_bits & (1 << (2 * wp_index)));
-
- error.SetError (!is_vacant, lldb::eErrorTypeInvalid);
+ is_vacant = !(control_bits & (1 << (2 * wp_index)));
return error;
}
if (size != 1 && size != 2 && size != 4 && size != 8)
return Error ("Invalid size for watchpoint");
- Error error = IsWatchpointVacant (wp_index);
+ bool is_vacant;
+ Error error = IsWatchpointVacant (wp_index, is_vacant);
if (error.Fail()) return error;
+ if (!is_vacant) return Error("Watchpoint index not vacant");
RegisterValue reg_value;
error = ReadRegisterRaw(m_reg_info.first_dr + 7, reg_value);
NativeRegisterContextLinux_x86_64::SetHardwareWatchpoint(
lldb::addr_t addr, size_t size, uint32_t watch_flags)
{
+ Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_WATCHPOINTS));
const uint32_t num_hw_watchpoints = NumSupportedHardwareWatchpoints();
for (uint32_t wp_index = 0; wp_index < num_hw_watchpoints; ++wp_index)
- if (IsWatchpointVacant(wp_index).Success())
+ {
+ bool is_vacant;
+ Error error = IsWatchpointVacant(wp_index, is_vacant);
+ if (is_vacant)
{
- if (SetHardwareWatchpointWithIndex(addr, size, watch_flags, wp_index).Fail())
- continue;
- return wp_index;
+ error = SetHardwareWatchpointWithIndex(addr, size, watch_flags, wp_index);
+ if (error.Success())
+ return wp_index;
}
+ if (error.Fail() && log)
+ {
+ log->Printf("NativeRegisterContextLinux_x86_64::%s Error: %s",
+ __FUNCTION__, error.AsCString());
+ }
+ }
return LLDB_INVALID_INDEX32;
}
WriteAllRegisterValues (const lldb::DataBufferSP &data_sp) override;
Error
- IsWatchpointHit(uint8_t wp_index) override;
+ IsWatchpointHit(uint32_t wp_index, bool &is_hit) override;
Error
- IsWatchpointVacant(uint32_t wp_index) override;
+ GetWatchpointHitIndex(uint32_t &wp_index) override;
+
+ Error
+ IsWatchpointVacant(uint32_t wp_index, bool &is_vacant) override;
bool
ClearHardwareWatchpoint(uint32_t wp_index) override;
#include "lldb/Host/Host.h"
#include "lldb/Host/HostInfo.h"
#include "lldb/Host/HostNativeThread.h"
+#include "lldb/Utility/LLDBAssert.h"
#include "lldb/lldb-enumerations.h"
#include "llvm/ADT/SmallString.h"
}
void
-NativeThreadLinux::SetStoppedByWatchpoint ()
+NativeThreadLinux::SetStoppedByWatchpoint (uint32_t wp_index)
{
- Log *log (GetLogIfAllCategoriesSet (LIBLLDB_LOG_THREAD));
- lldb::pid_t pid = LLDB_INVALID_PROCESS_ID;
- if (log)
- {
- NativeProcessProtocolSP process_sp = m_process_wp.lock ();
- if (process_sp)
- pid = process_sp->GetID ();
- }
-
const StateType new_state = StateType::eStateStopped;
MaybeLogStateChange (new_state);
m_state = new_state;
-
- NativeRegisterContextSP reg_ctx = GetRegisterContext ();
- const uint32_t num_hw_watchpoints = reg_ctx->NumSupportedHardwareWatchpoints ();
-
m_stop_description.clear ();
- for (uint32_t wp_index = 0; wp_index < num_hw_watchpoints; ++wp_index)
- {
- if (reg_ctx->IsWatchpointHit (wp_index).Success())
- {
- if (log)
- log->Printf ("NativeThreadLinux:%s (pid=%" PRIu64 ", tid=%" PRIu64 ") watchpoint found with idx: %u",
- __FUNCTION__, pid, GetID (), wp_index);
-
- std::ostringstream ostr;
- ostr << reg_ctx->GetWatchpointAddress (wp_index) << " " << wp_index;
- m_stop_description = ostr.str();
-
- m_stop_info.reason = StopReason::eStopReasonWatchpoint;
- m_stop_info.details.signal.signo = SIGTRAP;
- return;
- }
- }
- // The process reported a watchpoint was hit, but we haven't found the
- // watchpoint. Assume that a stopped by trace is reported as a hardware
- // watchpoint what happens on some linux kernels (e.g.: android-arm64
- // platfrom-21).
+ lldbassert(wp_index != LLDB_INVALID_INDEX32 &&
+ "wp_index cannot be invalid");
- if (log)
- log->Printf ("NativeThreadLinux:%s (pid=%" PRIu64 ", tid=%" PRIu64 ") none of the watchpoint was hit.",
- __FUNCTION__, pid, GetID ());
+ std::ostringstream ostr;
+ ostr << GetRegisterContext()->GetWatchpointAddress(wp_index) << " ";
+ ostr << wp_index;
+ m_stop_description = ostr.str();
- SetStoppedByTrace ();
+ m_stop_info.reason = StopReason::eStopReasonWatchpoint;
+ m_stop_info.details.signal.signo = SIGTRAP;
}
bool
SetStoppedByBreakpoint ();
void
- SetStoppedByWatchpoint ();
+ SetStoppedByWatchpoint (uint32_t wp_index);
bool
IsStoppedAtBreakpoint ();
--- /dev/null
+LEVEL = ../../../make
+
+C_SOURCES := main.c
+
+include $(LEVEL)/Makefile.rules
--- /dev/null
+"""Test stepping over watchpoints."""
+
+import unittest2
+import lldb
+import lldbutil
+from lldbtest import *
+
+
+class TestStepOverWatchpoint(TestBase):
+
+ mydir = TestBase.compute_mydir(__file__)
+
+ def getCategories(self):
+ return ['basic_process']
+
+ @unittest2.skipUnless(sys.platform.startswith("darwin"), "requires Darwin")
+ @dsym_test
+ def test_with_dsym(self):
+ """Test stepping over watchpoints."""
+ self.buildDsym()
+ self.step_over_watchpoint()
+
+ @dwarf_test
+ def test_with_dwarf(self):
+ """Test stepping over watchpoints."""
+ self.buildDwarf()
+ self.step_over_watchpoint()
+
+ def setUp(self):
+ TestBase.setUp(self)
+
+ def step_inst_for_watchpoint(self, wp_id):
+ watchpoint_hit = False
+ current_line = self.frame().GetLineEntry().GetLine()
+ while self.frame().GetLineEntry().GetLine() == current_line:
+ self.thread().StepInstruction(False) # step_over=False
+ stop_reason = self.thread().GetStopReason()
+ if stop_reason == lldb.eStopReasonWatchpoint:
+ self.assertFalse(watchpoint_hit, "Watchpoint already hit.")
+ expected_stop_desc = "watchpoint %d" % wp_id
+ actual_stop_desc = self.thread().GetStopDescription(20)
+ self.assertTrue(actual_stop_desc == expected_stop_desc,
+ "Watchpoint ID didn't match.")
+ watchpoint_hit = True
+ else:
+ self.assertTrue(stop_reason == lldb.eStopReasonPlanComplete,
+ STOPPED_DUE_TO_STEP_IN)
+ self.assertTrue(watchpoint_hit, "Watchpoint never hit.")
+
+ def step_over_watchpoint(self):
+ """Test stepping over watchpoints."""
+ exe = os.path.join(os.getcwd(), 'a.out')
+
+ target = self.dbg.CreateTarget(exe)
+ self.assertTrue(self.target, VALID_TARGET)
+
+ lldbutil.run_break_set_by_symbol(self, 'main')
+
+ process = target.LaunchSimple(None, None,
+ self.get_process_working_directory())
+ self.assertTrue(process.IsValid(), PROCESS_IS_VALID)
+ self.assertTrue(process.GetState() == lldb.eStateStopped,
+ PROCESS_STOPPED)
+
+ thread = lldbutil.get_stopped_thread(process,
+ lldb.eStopReasonBreakpoint)
+ self.assertTrue(thread.IsValid(), "Failed to get thread.")
+
+ frame = thread.GetFrameAtIndex(0)
+ self.assertTrue(frame.IsValid(), "Failed to get frame.")
+
+ read_value = frame.FindValue('g_watch_me_read',
+ lldb.eValueTypeVariableGlobal)
+ self.assertTrue(read_value.IsValid(), "Failed to find read value.")
+
+ error = lldb.SBError()
+
+ # resolve_location=True, read=True, write=False
+ read_watchpoint = read_value.Watch(True, True, False, error)
+ self.assertTrue(error.Success(),
+ "Error while setting watchpoint: %s" %
+ error.GetCString())
+ self.assertTrue(read_watchpoint, "Failed to set read watchpoint.")
+
+ write_value = frame.FindValue('g_watch_me_write',
+ lldb.eValueTypeVariableGlobal)
+ self.assertTrue(write_value, "Failed to find write value.")
+
+ # resolve_location=True, read=False, write=True
+ write_watchpoint = write_value.Watch(True, False, True, error)
+ self.assertTrue(read_watchpoint, "Failed to set write watchpoint.")
+ self.assertTrue(error.Success(),
+ "Error while setting watchpoint: %s" %
+ error.GetCString())
+
+ thread.StepOver()
+ self.assertTrue(thread.GetStopReason() == lldb.eStopReasonWatchpoint,
+ STOPPED_DUE_TO_WATCHPOINT)
+ self.assertTrue(thread.GetStopDescription(20) == 'watchpoint 1')
+
+ process.Continue()
+ self.assertTrue(process.GetState() == lldb.eStateStopped,
+ PROCESS_STOPPED)
+ self.assertTrue(thread.GetStopDescription(20) == 'step over')
+
+ self.step_inst_for_watchpoint(1)
+
+ thread.StepOver()
+ self.assertTrue(thread.GetStopReason() == lldb.eStopReasonWatchpoint,
+ STOPPED_DUE_TO_WATCHPOINT)
+ self.assertTrue(thread.GetStopDescription(20) == 'watchpoint 2')
+
+ process.Continue()
+ self.assertTrue(process.GetState() == lldb.eStateStopped,
+ PROCESS_STOPPED)
+ self.assertTrue(thread.GetStopDescription(20) == 'step over')
+
+ self.step_inst_for_watchpoint(2)
+
+if __name__ == '__main__':
+ import atexit
+ lldb.SBDebugger.Initialize()
+ atexit.register(lambda: lldb.SBDebugger.Terminate())
+ unittest2.main()
--- /dev/null
+char g_watch_me_read;
+char g_watch_me_write;
+char g_temp;
+
+void watch_read() {
+ g_temp = g_watch_me_read;
+}
+
+void watch_write() {
+ g_watch_me_write = g_temp;
+}
+
+int main() {
+ watch_read();
+ g_temp = g_watch_me_read;
+ watch_write();
+ g_watch_me_write = g_temp;
+ return 0;
+}