diff --git a/debuginfo-tests/dexter/dex/debugger/DebuggerBase.py b/debuginfo-tests/dexter/dex/debugger/DebuggerBase.py --- a/debuginfo-tests/dexter/dex/debugger/DebuggerBase.py +++ b/debuginfo-tests/dexter/dex/debugger/DebuggerBase.py @@ -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 @@ 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 @@ 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_conditional_breakpoint(self, file_, line, condition): + def _add_breakpoint(self, file_, line): pass + def add_conditional_breakpoint(self, file_, line, condition): + 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 @@ 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 @@ @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))) diff --git a/debuginfo-tests/dexter/dex/debugger/Debuggers.py b/debuginfo-tests/dexter/dex/debugger/Debuggers.py --- a/debuginfo-tests/dexter/dex/debugger/Debuggers.py +++ b/debuginfo-tests/dexter/dex/debugger/Debuggers.py @@ -100,6 +100,11 @@ 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 diff --git a/debuginfo-tests/dexter/dex/debugger/dbgeng/dbgeng.py b/debuginfo-tests/dexter/dex/debugger/dbgeng/dbgeng.py --- a/debuginfo-tests/dexter/dex/debugger/dbgeng/dbgeng.py +++ b/debuginfo-tests/dexter/dex/debugger/dbgeng/dbgeng.py @@ -76,18 +76,18 @@ 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 @@ # 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 = [] diff --git a/debuginfo-tests/dexter/dex/debugger/lldb/LLDB.py b/debuginfo-tests/dexter/dex/debugger/lldb/LLDB.py --- a/debuginfo-tests/dexter/dex/debugger/lldb/LLDB.py +++ b/debuginfo-tests/dexter/dex/debugger/lldb/LLDB.py @@ -103,12 +103,12 @@ 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 @@ 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 @@ 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 = [] diff --git a/debuginfo-tests/dexter/dex/debugger/visualstudio/VisualStudio.py b/debuginfo-tests/dexter/dex/debugger/visualstudio/VisualStudio.py --- a/debuginfo-tests/dexter/dex/debugger/visualstudio/VisualStudio.py +++ b/debuginfo-tests/dexter/dex/debugger/visualstudio/VisualStudio.py @@ -111,14 +111,14 @@ 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 @@ 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 diff --git a/debuginfo-tests/dexter/feature_tests/subtools/test/source-root-dir.cpp b/debuginfo-tests/dexter/feature_tests/subtools/test/source-root-dir.cpp new file mode 100644 --- /dev/null +++ b/debuginfo-tests/dexter/feature_tests/subtools/test/source-root-dir.cpp @@ -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 +int main() { + int x = 42; + printf("hello world: %d\n", x); // DexLabel('check') +} + +// DexExpectWatchValue('x', 42, on_line='check')