In order to run a {break,watch}point command, lldb can resolve to the
script interpreter to run an arbitrary piece of code or call into a
user-provided function. To do so, we will generate a wrapping function,
where we first copy lldb's internal dictionary keys into the
interpreter's global dictionary, copied inline the user code before
resetting the global dictionary to its previous state.
However, {break,watch}point commands can optionally return a value that
would tell lldb whether we should stop or not. This feature was
only implemented for breakpoint commands and since we inlined the user
code directly into the wrapping function, introducing an early return,
that caused lldb to let the interpreter global dictionary tinted with the
internal dictionary keys.
This patch fixes that issue while also adding the stopping behaviour to
watchpoint commands.
To do so, this patch refactors the {break,watch}point command creation
method, to let the lldb wrapper function generator know if the user code is
a function call or a arbitrary expression.
Then the wrapper generator, if the user input was a function call, the
wrapper function will call the user function and save the return value into
a variable. If the user input was an arbitrary expression, the wrapper will
inline it into a nested function, call the nested function and save the
return value into the same variable. After resetting the interpreter global
dictionary to its previous state, the generated wrapper function will return
the varible containing the return value.
rdar://105461140
Differential Revision: https://reviews.llvm.org/D144688
Signed-off-by: Med Ismail Bennani <medismail.bennani@gmail.com>
179 lines
6.7 KiB
Python
179 lines
6.7 KiB
Python
"""
|
|
Test 'watchpoint command'.
|
|
"""
|
|
|
|
|
|
|
|
import os
|
|
import lldb
|
|
from lldbsuite.test.decorators import *
|
|
from lldbsuite.test.lldbtest import *
|
|
from lldbsuite.test import lldbutil
|
|
|
|
|
|
class WatchpointPythonCommandTestCase(TestBase):
|
|
NO_DEBUG_INFO_TESTCASE = True
|
|
|
|
def setUp(self):
|
|
# Call super's setUp().
|
|
TestBase.setUp(self)
|
|
# Our simple source filename.
|
|
self.source = 'main.cpp'
|
|
# Find the line number to break inside main().
|
|
self.line = line_number(
|
|
self.source, '// Set break point at this line.')
|
|
self.second_line = line_number(
|
|
self.source, '// Set another breakpoint here.')
|
|
# And the watchpoint variable declaration line number.
|
|
self.decl = line_number(self.source,
|
|
'// Watchpoint variable declaration.')
|
|
# Build dictionary to have unique executable names for each test
|
|
# method.
|
|
self.exe_name = self.testMethodName
|
|
self.d = {'CXX_SOURCES': self.source, 'EXE': self.exe_name}
|
|
|
|
def test_watchpoint_command(self):
|
|
"""Test 'watchpoint command'."""
|
|
self.build(dictionary=self.d)
|
|
self.setTearDownCleanup(dictionary=self.d)
|
|
|
|
exe = self.getBuildArtifact(self.exe_name)
|
|
self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET)
|
|
|
|
# Add a breakpoint to set a watchpoint when stopped on the breakpoint.
|
|
lldbutil.run_break_set_by_file_and_line(
|
|
self, None, self.line, num_expected_locations=1)
|
|
|
|
# Run the program.
|
|
self.runCmd("run", RUN_SUCCEEDED)
|
|
|
|
# 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'])
|
|
|
|
# Now let's set a write-type watchpoint for 'global'.
|
|
self.expect(
|
|
"watchpoint set variable -w write global",
|
|
WATCHPOINT_CREATED,
|
|
substrs=[
|
|
'Watchpoint created',
|
|
'size = 4',
|
|
'type = w',
|
|
'%s:%d' %
|
|
(self.source,
|
|
self.decl)])
|
|
|
|
self.runCmd(
|
|
'watchpoint command add -s python 1 -o \'frame.EvaluateExpression("cookie = 777")\'')
|
|
|
|
# List the watchpoint command we just added.
|
|
self.expect("watchpoint command list 1",
|
|
substrs=['frame.EvaluateExpression', 'cookie = 777'])
|
|
|
|
# Use the '-v' option to do verbose listing of the watchpoint.
|
|
# The hit count should be 0 initially.
|
|
self.expect("watchpoint list -v",
|
|
substrs=['hit_count = 0'])
|
|
|
|
self.runCmd("process continue")
|
|
|
|
# We should be stopped again due to the watchpoint (write type).
|
|
# The stop reason of the thread should be watchpoint.
|
|
self.expect("thread backtrace", STOPPED_DUE_TO_WATCHPOINT,
|
|
substrs=['stop reason = watchpoint'])
|
|
|
|
# Check that the watchpoint snapshoting mechanism is working.
|
|
self.expect("watchpoint list -v",
|
|
substrs=['old value: 0',
|
|
'new value: 1'])
|
|
|
|
# The watchpoint command "forced" our global variable 'cookie' to
|
|
# become 777.
|
|
self.expect("frame variable --show-globals cookie",
|
|
substrs=['(int32_t)', 'cookie = 777'])
|
|
|
|
def test_continue_in_watchpoint_command(self):
|
|
"""Test continue in a watchpoint command."""
|
|
self.build(dictionary=self.d)
|
|
self.setTearDownCleanup(dictionary=self.d)
|
|
|
|
exe = self.getBuildArtifact(self.exe_name)
|
|
self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET)
|
|
|
|
# Add a breakpoint to set a watchpoint when stopped on the breakpoint.
|
|
lldbutil.run_break_set_by_file_and_line(
|
|
self, None, self.line, num_expected_locations=1)
|
|
|
|
# Run the program.
|
|
self.runCmd("run", RUN_SUCCEEDED)
|
|
|
|
# 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'])
|
|
|
|
# Now let's set a write-type watchpoint for 'global'.
|
|
self.expect(
|
|
"watchpoint set variable -w write global",
|
|
WATCHPOINT_CREATED,
|
|
substrs=[
|
|
'Watchpoint created',
|
|
'size = 4',
|
|
'type = w',
|
|
'%s:%d' %
|
|
(self.source,
|
|
self.decl)])
|
|
|
|
cmd_script_file = os.path.join(self.getSourceDir(),
|
|
"watchpoint_command.py")
|
|
self.runCmd("command script import '%s'" % (cmd_script_file))
|
|
|
|
self.runCmd(
|
|
'watchpoint command add -F watchpoint_command.watchpoint_command')
|
|
|
|
# List the watchpoint command we just added.
|
|
self.expect("watchpoint command list 1",
|
|
substrs=['watchpoint_command.watchpoint_command'])
|
|
|
|
self.runCmd("process continue")
|
|
|
|
# We should be stopped again due to the watchpoint (write type).
|
|
# The stop reason of the thread should be watchpoint.
|
|
self.expect("thread backtrace", STOPPED_DUE_TO_WATCHPOINT,
|
|
substrs=['stop reason = watchpoint'])
|
|
|
|
# We should have hit the watchpoint once, set cookie to 888, since the
|
|
# user callback returned True.
|
|
self.expect("frame variable --show-globals cookie",
|
|
substrs=['(int32_t)', 'cookie = 888'])
|
|
|
|
self.runCmd("process continue")
|
|
|
|
# We should be stopped again due to the watchpoint (write type).
|
|
# The stop reason of the thread should be watchpoint.
|
|
self.expect("thread backtrace", STOPPED_DUE_TO_WATCHPOINT,
|
|
substrs=['stop reason = watchpoint'])
|
|
|
|
# We should have hit the watchpoint a second time, set cookie to 666,
|
|
# even if the user callback didn't return anything and then continue.
|
|
self.expect("frame variable --show-globals cookie",
|
|
substrs=['(int32_t)', 'cookie = 666'])
|
|
|
|
# Add a breakpoint to set a watchpoint when stopped on the breakpoint.
|
|
lldbutil.run_break_set_by_file_and_line(
|
|
self, None, self.second_line, num_expected_locations=1)
|
|
|
|
self.runCmd("process continue")
|
|
|
|
self.expect("thread backtrace", STOPPED_DUE_TO_BREAKPOINT,
|
|
substrs=['stop reason = breakpoint'])
|
|
|
|
# We should have hit the watchpoint once, set cookie to 888, then continued to the
|
|
# second hit and set it to 999
|
|
self.expect("frame variable --show-globals cookie",
|
|
substrs=['(int32_t)', 'cookie = 999'])
|
|
|