[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.""" """Base class for all debugger interface implementations."""
import abc import abc
import os
import sys import sys
import traceback 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 DebuggerException
from dex.utils.Exceptions import NotYetLoadedDebuggerException from dex.utils.Exceptions import NotYetLoadedDebuggerException
from dex.utils.ReturnCode import ReturnCode from dex.utils.ReturnCode import ReturnCode
@ -19,6 +22,11 @@ from dex.utils.ReturnCode import ReturnCode
class DebuggerBase(object, metaclass=abc.ABCMeta): class DebuggerBase(object, metaclass=abc.ABCMeta):
def __init__(self, context): def __init__(self, context):
self.context = 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._interface = None
self.has_loaded = False self.has_loaded = False
self._loading_error = NotYetLoadedDebuggerException() self._loading_error = NotYetLoadedDebuggerException()
@ -116,16 +124,27 @@ class DebuggerBase(object, metaclass=abc.ABCMeta):
def clear_breakpoints(self): def clear_breakpoints(self):
pass pass
@abc.abstractmethod
def add_breakpoint(self, file_, line): def add_breakpoint(self, file_, line):
pass return self._add_breakpoint(self._external_to_debug_path(file_), line)
@abc.abstractmethod @abc.abstractmethod
def _add_breakpoint(self, file_, line):
pass
def add_conditional_breakpoint(self, file_, line, condition): def add_conditional_breakpoint(self, file_, line, condition):
pass return self._add_conditional_breakpoint(
self._external_to_debug_path(file_), line, condition)
@abc.abstractmethod @abc.abstractmethod
def _add_conditional_breakpoint(self, file_, line, condition):
pass
def delete_conditional_breakpoint(self, file_, line, condition): 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 pass
@abc.abstractmethod @abc.abstractmethod
@ -140,8 +159,14 @@ class DebuggerBase(object, metaclass=abc.ABCMeta):
def go(self) -> ReturnCode: def go(self) -> ReturnCode:
pass pass
@abc.abstractmethod
def get_step_info(self, watches, step_index): 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 pass
@abc.abstractproperty @abc.abstractproperty
@ -159,3 +184,86 @@ class DebuggerBase(object, metaclass=abc.ABCMeta):
@abc.abstractmethod @abc.abstractmethod
def evaluate_expression(self, expression, frame_idx=0) -> ValueIR: def evaluate_expression(self, expression, frame_idx=0) -> ValueIR:
pass 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, default=None,
display_default=defaults.arch, display_default=defaults.arch,
help='target architecture') 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 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) x.RemoveFlags(breakpoint.BreakpointFlags.DEBUG_BREAKPOINT_ENABLED)
self.client.Control.RemoveBreakpoint(x) 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 # Breakpoint setting/deleting is not supported by dbgeng at this moment
# but is something that should be considered in the future. # but is something that should be considered in the future.
# TODO: this method is called in the DefaultController but has no effect. # TODO: this method is called in the DefaultController but has no effect.
pass 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 # breakpoint setting/deleting is not supported by dbgeng at this moment
# but is something that should be considered in the future. # but is something that should be considered in the future.
raise NotImplementedError('add_conditional_breakpoint is not yet implemented by dbgeng') 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 # breakpoint setting/deleting is not supported by dbgeng at this moment
# but is something that should be considered in the future. # but is something that should be considered in the future.
raise NotImplementedError('delete_conditional_breakpoint is not yet implemented by dbgeng') 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. # We never go -- we always single step.
pass pass
def get_step_info(self, watches, step_index): def _get_step_info(self, watches, step_index):
frames = self.step_info frames = self.step_info
state_frames = [] state_frames = []

View File

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

View File

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