debugserver should advance pc past builtin_debugtrap insn

On x86_64, when you hit a __builtin_debugtrap instruction, you
can continue past this in the debugger.  This patch has debugserver
recognize the specific instruction used for __builtin_debugtrap
and advance the pc past it, so that the user can continue execution
once they've hit one of these.

In the patch discussion, we were in agreement that it would be better
to have this knowledge up in lldb instead of depending on each
stub rewriting the pc behind the debugger's back, but that's a
larger scale change for another day.

<rdar://problem/65521634>
Differential revision: https://reviews.llvm.org/D91238
This commit is contained in:
Jason Molenda committed 2020-11-12 23:31:14 -08:00
1 parent dabc914d2b
commit 92b036dea2
4 files changed
+106

No files matched your search

@@ -0,0 +1,3 @@
CXX_SOURCES := main.cpp
include Makefile.rules
@@ -0,0 +1,70 @@
"""
Test that lldb can continue past a __builtin_debugtrap, but not a __builtin_trap
"""
import lldb
import lldbsuite.test.lldbutil as lldbutil
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
class BuiltinDebugTrapTestCase(TestBase):
mydir = TestBase.compute_mydir(__file__)
NO_DEBUG_INFO_TESTCASE = True
# Currently this depends on behavior in debugserver to
# advance the pc past __builtin_trap instructions so that
# continue works. Everyone is in agreement that this
# should be moved up into lldb instead of depending on the
# remote stub rewriting the pc values.
@skipUnlessDarwin
def test(self):
self.build()
(target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(
self, "// Set a breakpoint here", lldb.SBFileSpec("main.cpp"))
# Continue to __builtin_debugtrap()
process.Continue()
if self.TraceOn():
self.runCmd("f")
self.runCmd("bt")
self.runCmd("ta v global")
self.assertEqual(process.GetSelectedThread().GetStopReason(),
lldb.eStopReasonException)
list = target.FindGlobalVariables("global", 1, lldb.eMatchTypeNormal)
self.assertEqual(list.GetSize(), 1)
global_value = list.GetValueAtIndex(0)
self.assertEqual(global_value.GetValueAsUnsigned(), 5)
# Continue to the __builtin_trap() -- we should be able to
# continue past __builtin_debugtrap.
process.Continue()
if self.TraceOn():
self.runCmd("f")
self.runCmd("bt")
self.runCmd("ta v global")
self.assertEqual(process.GetSelectedThread().GetStopReason(),
lldb.eStopReasonException)
# "global" is now 10.
self.assertEqual(global_value.GetValueAsUnsigned(), 10)
# We should be at the same point as before -- cannot advance
# past a __builtin_trap().
process.Continue()
if self.TraceOn():
self.runCmd("f")
self.runCmd("bt")
self.runCmd("ta v global")
self.assertEqual(process.GetSelectedThread().GetStopReason(),
lldb.eStopReasonException)
# "global" is still 10.
self.assertEqual(global_value.GetValueAsUnsigned(), 10)
@@ -0,0 +1,11 @@
#include <stdio.h>
int global = 0;
int main()
{
global = 5; // Set a breakpoint here
__builtin_debugtrap();
global = 10;
__builtin_trap();
global = 15;
return global;
}
@@ -524,6 +524,28 @@ bool DNBArchMachARM64::NotifyException(MachException::Data &exc) {
return true;
}
// detect a __builtin_debugtrap instruction pattern ("brk #0xf000")
// and advance the $pc past it, so that the user can continue execution.
// Generally speaking, this knowledge should be centralized in lldb,
// recognizing the builtin_trap instruction and knowing how to advance
// the pc past it, so that continue etc work.
if (exc.exc_data.size() == 2 && exc.exc_data[0] == EXC_ARM_BREAKPOINT) {
nub_addr_t pc = GetPC(INVALID_NUB_ADDRESS);
if (pc != INVALID_NUB_ADDRESS && pc > 0) {
DNBBreakpoint *bp =
m_thread->Process()->Breakpoints().FindByAddress(pc);
if (bp == nullptr) {
uint8_t insnbuf[4];
if (m_thread->Process()->ReadMemory(pc, 4, insnbuf) == 4) {
uint8_t builtin_debugtrap_insn[4] = {0x00, 0x00, 0x3e,
0xd4}; // brk #0xf000
if (memcmp(insnbuf, builtin_debugtrap_insn, 4) == 0) {
SetPC(pc + 4);
}
}
}
}
}
break;
}
return false;