[Dexter] Add --source-dir-root flag

Summary:
This allows to run dexter tests with separately compiled
binaries that are specified via --binary if the source file
location changed between compilation and dexter test run.

Reviewers: TWeaver, jmorse, probinson, #debug-info

Reviewed By: jmorse

Subscribers: #debug-info, cmtice, llvm-commits

Tags: #llvm, #debug-info

Differential Revision: https://reviews.llvm.org/D81319
This commit is contained in:
Tobias Bosch 2020-06-05 16:19:23 -07:00
parent ecdf48f15b
commit 53d6bfef32
6 changed files with 145 additions and 17 deletions

View File

@ -7,10 +7,13 @@
"""Base class for all debugger interface implementations."""
import abc
import os
import sys
import traceback
import unittest
from dex.dextIR import DebuggerIR, ValueIR
from types import SimpleNamespace
from dex.dextIR import DebuggerIR, FrameIR, LocIR, StepIR, ValueIR
from dex.utils.Exceptions import DebuggerException
from dex.utils.Exceptions import NotYetLoadedDebuggerException
from dex.utils.ReturnCode import ReturnCode
@ -19,6 +22,11 @@ from dex.utils.ReturnCode import ReturnCode
class DebuggerBase(object, metaclass=abc.ABCMeta):
def __init__(self, context):
self.context = context
# Note: We can't already read values from options
# as DebuggerBase is created before we initialize options
# to read potential_debuggers.
self.options = self.context.options
self._interface = None
self.has_loaded = False
self._loading_error = NotYetLoadedDebuggerException()
@ -116,16 +124,27 @@ class DebuggerBase(object, metaclass=abc.ABCMeta):
def clear_breakpoints(self):
pass
@abc.abstractmethod
def add_breakpoint(self, file_, line):
pass
return self._add_breakpoint(self._external_to_debug_path(file_), line)
@abc.abstractmethod
def _add_breakpoint(self, file_, line):
pass
def add_conditional_breakpoint(self, file_, line, condition):
pass
return self._add_conditional_breakpoint(
self._external_to_debug_path(file_), line, condition)
@abc.abstractmethod
def _add_conditional_breakpoint(self, file_, line, condition):
pass
def delete_conditional_breakpoint(self, file_, line, condition):
return self._delete_conditional_breakpoint(
self._external_to_debug_path(file_), line, condition)
@abc.abstractmethod
def _delete_conditional_breakpoint(self, file_, line, condition):
pass
@abc.abstractmethod
@ -140,8 +159,14 @@ class DebuggerBase(object, metaclass=abc.ABCMeta):
def go(self) -> ReturnCode:
pass
@abc.abstractmethod
def get_step_info(self, watches, step_index):
step_info = self._get_step_info(watches, step_index)
for frame in step_info.frames:
frame.loc.path = self._debug_to_external_path(frame.loc.path)
return step_info
@abc.abstractmethod
def _get_step_info(self, watches, step_index):
pass
@abc.abstractproperty
@ -159,3 +184,86 @@ class DebuggerBase(object, metaclass=abc.ABCMeta):
@abc.abstractmethod
def evaluate_expression(self, expression, frame_idx=0) -> ValueIR:
pass
def _external_to_debug_path(self, path):
root_dir = self.options.source_root_dir
if not root_dir or not path:
return path
assert path.startswith(root_dir)
return path[len(root_dir):].lstrip(os.path.sep)
def _debug_to_external_path(self, path):
if not path or not self.options.source_root_dir:
return path
for file in self.options.source_files:
if path.endswith(self._external_to_debug_path(file)):
return file
return path
class TestDebuggerBase(unittest.TestCase):
class MockDebugger(DebuggerBase):
def __init__(self, context, *args):
super().__init__(context, *args)
self.step_info = None
self.breakpoint_file = None
def _add_breakpoint(self, file, line):
self.breakpoint_file = file
def _get_step_info(self, watches, step_index):
return self.step_info
def __init__(self, *args):
super().__init__(*args)
TestDebuggerBase.MockDebugger.__abstractmethods__ = set()
self.options = SimpleNamespace(source_root_dir = '', source_files = [])
context = SimpleNamespace(options = self.options)
self.dbg = TestDebuggerBase.MockDebugger(context)
def _new_step(self, paths):
frames = [
FrameIR(
function=None,
is_inlined=False,
loc=LocIR(path=path, lineno=0, column=0)) for path in paths
]
return StepIR(step_index=0, stop_reason=None, frames=frames)
def _step_paths(self, step):
return [frame.loc.path for frame in step.frames]
def test_add_breakpoint_no_source_root_dir(self):
self.options.source_root_dir = ''
self.dbg.add_breakpoint('/root/some_file', 12)
self.assertEqual('/root/some_file', self.dbg.breakpoint_file)
def test_add_breakpoint_with_source_root_dir(self):
self.options.source_root_dir = '/my_root'
self.dbg.add_breakpoint('/my_root/some_file', 12)
self.assertEqual('some_file', self.dbg.breakpoint_file)
def test_add_breakpoint_with_source_root_dir_slash_suffix(self):
self.options.source_root_dir = '/my_root/'
self.dbg.add_breakpoint('/my_root/some_file', 12)
self.assertEqual('some_file', self.dbg.breakpoint_file)
def test_get_step_info_no_source_root_dir(self):
self.dbg.step_info = self._new_step(['/root/some_file'])
self.assertEqual(['/root/some_file'],
self._step_paths(self.dbg.get_step_info([], 0)))
def test_get_step_info_no_frames(self):
self.options.source_root_dir = '/my_root'
self.dbg.step_info = self._new_step([])
self.assertEqual([],
self._step_paths(self.dbg.get_step_info([], 0)))
def test_get_step_info(self):
self.options.source_root_dir = '/my_root'
self.options.source_files = ['/my_root/some_file']
self.dbg.step_info = self._new_step(
[None, '/other/file', '/dbg/some_file'])
self.assertEqual([None, '/other/file', '/my_root/some_file'],
self._step_paths(self.dbg.get_step_info([], 0)))

View File

@ -100,6 +100,11 @@ def add_debugger_tool_arguments(parser, context, defaults):
default=None,
display_default=defaults.arch,
help='target architecture')
defaults.source_root_dir = ''
parser.add_argument(
'--source-root-dir',
default=None,
help='prefix path to ignore when matching debug info and source files.')
def handle_debugger_tool_base_options(context, defaults): # noqa

View File

@ -76,18 +76,18 @@ class DbgEng(DebuggerBase):
x.RemoveFlags(breakpoint.BreakpointFlags.DEBUG_BREAKPOINT_ENABLED)
self.client.Control.RemoveBreakpoint(x)
def add_breakpoint(self, file_, line):
def _add_breakpoint(self, file_, line):
# Breakpoint setting/deleting is not supported by dbgeng at this moment
# but is something that should be considered in the future.
# TODO: this method is called in the DefaultController but has no effect.
pass
def add_conditional_breakpoint(self, file_, line, condition):
def _add_conditional_breakpoint(self, file_, line, condition):
# breakpoint setting/deleting is not supported by dbgeng at this moment
# but is something that should be considered in the future.
raise NotImplementedError('add_conditional_breakpoint is not yet implemented by dbgeng')
def delete_conditional_breakpoint(self, file_, line, condition):
def _delete_conditional_breakpoint(self, file_, line, condition):
# breakpoint setting/deleting is not supported by dbgeng at this moment
# but is something that should be considered in the future.
raise NotImplementedError('delete_conditional_breakpoint is not yet implemented by dbgeng')
@ -106,7 +106,7 @@ class DbgEng(DebuggerBase):
# We never go -- we always single step.
pass
def get_step_info(self, watches, step_index):
def _get_step_info(self, watches, step_index):
frames = self.step_info
state_frames = []

View File

@ -103,12 +103,12 @@ class LLDB(DebuggerBase):
def clear_breakpoints(self):
self._target.DeleteAllBreakpoints()
def add_breakpoint(self, file_, line):
def _add_breakpoint(self, file_, line):
if not self._target.BreakpointCreateByLocation(file_, line):
raise DebuggerException(
'could not add breakpoint [{}:{}]'.format(file_, line))
def add_conditional_breakpoint(self, file_, line, condition):
def _add_conditional_breakpoint(self, file_, line, condition):
bp = self._target.BreakpointCreateByLocation(file_, line)
if bp:
bp.SetCondition(condition)
@ -116,7 +116,7 @@ class LLDB(DebuggerBase):
raise DebuggerException(
'could not add breakpoint [{}:{}]'.format(file_, line))
def delete_conditional_breakpoint(self, file_, line, condition):
def _delete_conditional_breakpoint(self, file_, line, condition):
bp_count = self._target.GetNumBreakpoints()
bps = [self._target.GetBreakpointAtIndex(ix) for ix in range(0, bp_count)]
@ -163,7 +163,7 @@ class LLDB(DebuggerBase):
self._process.Continue()
return ReturnCode.OK
def get_step_info(self, watches, step_index):
def _get_step_info(self, watches, step_index):
frames = []
state_frames = []

View File

@ -111,14 +111,14 @@ class VisualStudio(DebuggerBase, metaclass=abc.ABCMeta): # pylint: disable=abst
for bp in self._debugger.Breakpoints:
bp.Delete()
def add_breakpoint(self, file_, line):
def _add_breakpoint(self, file_, line):
self._debugger.Breakpoints.Add('', file_, line)
def add_conditional_breakpoint(self, file_, line, condition):
def _add_conditional_breakpoint(self, file_, line, condition):
column = 1
self._debugger.Breakpoints.Add('', file_, line, column, condition)
def delete_conditional_breakpoint(self, file_, line, condition):
def _delete_conditional_breakpoint(self, file_, line, condition):
for bp in self._debugger.Breakpoints:
for bound_bp in bp.Children:
if (bound_bp.File == file_ and bound_bp.FileLine == line and
@ -146,7 +146,7 @@ class VisualStudio(DebuggerBase, metaclass=abc.ABCMeta): # pylint: disable=abst
raise Error('attempted to access stack frame {} out of {}'
.format(idx, len(stack_frames)))
def get_step_info(self, watches, step_index):
def _get_step_info(self, watches, step_index):
thread = self._debugger.CurrentThread
stackframes = thread.StackFrames

View File

@ -0,0 +1,15 @@
// REQUIRES: lldb
// UNSUPPORTED: system-windows
//
// RUN: %dexter --fail-lt 1.0 -w \
// RUN: --builder 'clang' --debugger 'lldb' \
// RUN: --cflags "-O0 -glldb -fdebug-prefix-map=%S=/changed" \
// RUN: --source-root-dir=%S -- %s
#include <stdio.h>
int main() {
int x = 42;
printf("hello world: %d\n", x); // DexLabel('check')
}
// DexExpectWatchValue('x', 42, on_line='check')