================
@@ -0,0 +1,157 @@
+"""
+Check that when data is written over a software breakpoint site, it does not
+corrupt the breakpoint instruction, and is later written to memory when the
+breakpoint is removed.
+"""
+
+import lldb
+from lldbsuite.test.lldbtest import *
+import lldbsuite.test.lldbutil as lldbutil
+from lldbsuite.test.decorators import *
+
+
+class WriteOverSoftwareBreakpoint(TestBase):
+    NO_DEBUG_INFO_TESTCASE = True
+
+    # Could not find a way to make place_break_here visible to lldb on Windows.
+    @skipIfWindows
+    # debugserver needs fixing, see 
https://github.com/llvm/llvm-project/issues/217359.
+    @llgs_test
+    def test_write_over_breakpoint(self):
+        TestBase.setUp(self)
+        self.line = line_number("main.c", "// break here")
+        self.build()
+        exe = self.getBuildArtifact("a.out")
+        self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET)
+
+        lldbutil.run_break_set_by_file_and_line(
+            self, "main.c", self.line, num_expected_locations=1, loc_exact=True
+        )
+        self.runCmd("run", RUN_SUCCEEDED)
+        self.expect(
+            "thread list",
+            STOPPED_DUE_TO_BREAKPOINT,
+            substrs=["stopped", "stop reason = breakpoint"],
+        )
+
+        target = self.dbg.GetSelectedTarget()
+        process = target.GetProcess()
+
+        loop_start_breakpoint_addr = (
+            target.breakpoints[0].GetLocationAtIndex(0).GetLoadAddress()
+        )
+
+        # Memory operations and breakpoint actions must be sent to the server
+        # right away instead of waiting for the next continue event.
+        self.runCmd("settings set target.process.disable-memory-cache on")
+        self.runCmd("settings set target.process.use-delayed-breakpoints 
false")
+
+        # At this point we are stopped at the start of the for loop.
+        # We will set a further breakpoint in foo, and this is the one we will
+        # test by writing over it then continuing to it.
+        # We could use the same breakpoint, but arranging for dead code
+        # immediately before it is more risky.
+
+        # The breakpoint must be on an exact address, because we do not want
+        # lldb to adjust it based on the debug information for prologues
+        # and epilogues.
+        symbol_contexts = target.FindSymbols("place_break_here")
+        self.assertEqual(1, len(symbol_contexts))
+        label_symbol = symbol_contexts[0].GetSymbol()
+        self.assertTrue(label_symbol.IsValid())
+        bkpt_address = label_symbol.GetStartAddress().GetLoadAddress(target)
+
+        # lldb-server has its algorithm unit tested as part of
+        # NativeProcessProtocol, but debugserver does not use that. So we will
+        # do a few different types of overwrite here so we have coverage for 
both
+        # debug servers.
+        #
+        # We cannot be sure what the software break size will be, so I'm 
assuming
+        # 4 bytes because that's what Arm/AArch64 use. This means we may not be
+        # getting full coverage on Thumb or x86 but the test should still pass
+        # there.
+        #
+        # We assume that the instruction immediately before the breakpoint in
+        # foo is dead code, so we are allowed to corrupt it.
+        writes = [
+            # Up to but not over breakpoint.
+            (-4, 4),
+            # Over start of breakpoint.
+            (-2, 4),
+            # Exactly over breakpoint.
+            (0, 4),
+            # Over end of breakpoint.
+            (2, 4),
+            # Immediately after breakpoint.
+            (4, 4),
+            # From before to after breakpoint.
+            (-4, 12),
+        ]
+
+        for write_offset, write_size in writes:
+            # Place a breakpoint immediately after the dead code in foo.
+            bkpt = target.BreakpointCreateByAddress(bkpt_address)
+            self.assertTrue(bkpt.IsValid())
+            self.assertEqual(bkpt.GetNumLocations(), 1)
+            self.assertFalse(bkpt.IsHardware())
+            self.assertEqual(bkpt.GetLocationAtIndex(0).GetLoadAddress(), 
bkpt_address)
+
+            check_address = bkpt_address + write_offset
+
+            # Read around the breakpoint site. We assume that read subsitution
+            # is working, so the data here is the original contents of memory
+            # without the trap instruction.
+            err = lldb.SBError()
+            original_data = bytearray(
+                process.ReadMemory(check_address, write_size, err)
+            )
+            self.assertSuccess(err)
+            self.assertEqual(len(original_data), write_size)
+
+            # Write around/in/over the breakpoint site.
+            write_data = bytearray(range(write_size))
+            wrote = process.WriteMemory(check_address, write_data, err)
+            self.assertSuccess(err)
+            self.assertEqual(wrote, write_size)
+
+            # The data overlapping the breakpoint site should be in that 
breakpoint's
+            # saved data. So it will appear as if all the data was written to 
memory,
+            # even though it was not yet.
+            after_write = bytearray(process.ReadMemory(check_address, 
write_size, err))
+            self.assertSuccess(err)
+            self.assertEqual(after_write, write_data)
+
+            # The instruction in memory should still be intact so we can 
continue
+            # to the breakpoint.
+            process.Continue()
+
+            thread = process.thread[0]
+            self.assertState(process.GetState(), lldb.eStateStopped)
+            self.assertStopReason(thread.GetStopReason(), 
lldb.eStopReasonBreakpoint)
+            # Should be stopped at the breakpoint we placed in foo. This 
proves that
+            # the breakpoint instruction was intact.
+            self.assertEqual(
+                bkpt_address,
+                thread.selected_frame.GetPC(),
+            )
+
+            # When the breakpoint is removed the saved bytes will be written to
+            # memory.
+            self.assertTrue(target.BreakpointDelete(bkpt.GetID()))
+
+            data = process.ReadMemory(check_address, write_size, err)
+            self.assertSuccess(err)
+            self.assertEqual(bytearray(data), write_data)
+
+            # Restore the original instruction data. The dead code before the
+            # breakpoint is ok but the instructions after it must be put back
+            # so we can continue.
+            wrote = process.WriteMemory(check_address, original_data, err)
+            self.assertSuccess(err)
+            self.assertEqual(wrote, write_size)
+
+            # Continue back to the start of the loop.
+            process.Continue()
+            self.assertState(process.GetState(), lldb.eStateStopped)
+            self.assertStopReason(thread.GetStopReason(), 
lldb.eStopReasonBreakpoint)
+            self.assertEqual(loop_start_breakpoint_addr, 
thread.selected_frame.GetPC())
----------------
felipepiovezan wrote:

I was going to say that `lldbutil.continue_to_breakpoint(process, bkp)` 
combines all four of these lines; but sadly it doesn't assert that there is a 
thread stopped at the breakpoint (confusingly, IMO)

https://github.com/llvm/llvm-project/pull/217348
_______________________________________________
lldb-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to