Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Data breakpoints #765

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1572,7 +1572,7 @@ Currently tested with the following debug adapters.
## C, C++, Rust, etc.

* [vscode-cpptools](https://github.com/Microsoft/vscode-cpptools)
* On macOS, I *strongly* recommend using [CodeLLDB](#rust) instead for C and C++
* I *strongly* recommend using [CodeLLDB](#rust) over cpptools for almost all
projects. It's really excellent, has fewer dependencies and doesn't open console
apps in another Terminal window.

Expand Down
8 changes: 8 additions & 0 deletions autoload/vimspector.vim
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,14 @@ function! vimspector#ShowDisassembly( ... ) abort
py3 _vimspector_session.ShowDisassembly()
endfunction

function! vimspector#AddDataBreakpoint( ... ) abort
if !s:Enabled()
return
endif
" TODO: how to set options?
py3 _vimspector_session.AddDataBreakpoint( {} )
endfunction

function! vimspector#DeleteWatch() abort
if !s:Enabled()
return
Expand Down
81 changes: 73 additions & 8 deletions python3/vimspector/breakpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ def __init__( self,
self._func_breakpoints = []
self._exception_breakpoints = None
self._configured_breakpoints = {}
self._data_breakponts = []

self._server_capabilities = {}

Expand Down Expand Up @@ -448,6 +449,17 @@ def BreakpointsAsQuickFix( self ):
bp[ 'function' ],
json.dumps( bp[ 'options' ] ) )
} )
for bp in self._data_breakponts:
qf.append( {
'filename': bp[ 'info' ][ 'description' ],
'lnum': 1,
'col': 1,
'type': 'D',
'valid': 0,
'text': f"{ bp['name'] }: Data breakpoint - "
f"{ bp['info' ][ 'description' ] }: " +
json.dumps( bp[ 'options' ] )
} )

return qf

Expand Down Expand Up @@ -523,23 +535,21 @@ def _ClearServerBreakpointData( self, conn: DebugAdapterConnection ):
if not bp[ 'server_bp' ]:
del bp[ 'server_bp' ]


# Clear all instruction breakpoints because they aren't truly portable
# across sessions.
#
# TODO: It might be possible to re-resolve the address stored in the
# breakpoint, though this would only work in a limited way (as load
# addresses will frequently not be the same across runs)


def ShouldKeep( bp ):
def ShouldKeepInsBP( bp ):
if not bp[ 'is_instruction_breakpoint' ]:
return True
if 'address' in bp and bp[ 'session_id' ] != conn.GetSessionId():
return True
return False

breakpoints[ : ] = [ bp for bp in breakpoints if ShouldKeep( bp ) ]
breakpoints[ : ] = [ bp for bp in breakpoints if ShouldKeepInsBP( bp ) ]

# Erase any data breakpoints for this connection too
self._data_breakponts[ : ] = [ bp for bp in self._data_breakponts
if bp[ 'conn' ] != conn.GetSessionId() ]


def _CopyServerLineBreakpointProperties( self,
Expand Down Expand Up @@ -807,7 +817,21 @@ def AddFunctionBreakpoint( self, function, options ):
# 'condition': ...,
# 'hitCondition': ...,
} )
self.UpdateUI()


def AddDataBreakpoint( self,
conn: DebugAdapterConnection,
name,
info,
options ):
self._data_breakponts.append( {
'state': 'ENABLED',
'conn': conn.GetSessionId(),
'name': name,
'info': info,
'options': options
} )
self.UpdateUI()


Expand Down Expand Up @@ -1014,6 +1038,37 @@ def response_handler( conn, msg, bp_idxs = [] ):
failure_handler = response_received
)

if self._data_breakponts and self._server_capabilities[
'supportsDataBreakpoints' ]:
connection: DebugAdapterConnection
for connection in self._connections:
breakpoints = []
for bp in self._data_breakponts:
if bp[ 'state' ] != 'ENABLED':
continue
if bp[ 'conn' ] != connection.GetSessionId():
continue
if not bp[ 'info' ].get( 'dataId' ):
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could 0 be a valid value?

continue

data_bp = {}
data_bp.update( bp[ 'options' ] )
data_bp[ 'dataId' ] = bp[ 'info' ][ 'dataId' ]
breakpoints.append( data_bp )

if breakpoints:
self._awaiting_bp_responses += 1
connection.DoRequest(
lambda msg, conn=connection: response_handler( conn, msg ),
{
'command': 'setDataBreakpoints',
'arguments': {
'breakpoints': breakpoints,
},
},
failure_handler = response_received
)

if self._exception_breakpoints:
for connection in self._connections:
self._awaiting_bp_responses += 1
Expand Down Expand Up @@ -1112,6 +1167,11 @@ def Save( self ):
if bps:
line[ file_name ] = bps

# TODO: Some way to persis data breakpoints? Currently they require
# variablesReference, which is clearly not something that can be persisted
#
# That said, the spec now seems to support data bps on expressions, though i
# can't see any servers which support that.
return {
'line': line,
'function': self._func_breakpoints,
Expand Down Expand Up @@ -1183,6 +1243,11 @@ def _HideBreakpoints( self ):
signs.UnplaceSign( bp[ 'sign_id' ], 'VimspectorBP' )
del bp[ 'sign_id' ]

# TODO could/should we show a sign in the variables view when there's a data
# brakpoint on the variable? Not sure how best to actually do that, but
# maybe the variable view can pass that info when calling AddDataBreakpoint,
# such as the variablesReference/name


def _SignToLine( self, file_name, bp ):
if bp[ 'is_instruction_breakpoint' ]:
Expand Down
32 changes: 31 additions & 1 deletion python3/vimspector/debug_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -1016,7 +1016,37 @@ def OnDisassemblyWindowScrolled( self, win_id ):
self._disassemblyView.OnWindowScrolled( win_id )


@CurrentSession()
@ParentOnly()
def AddDataBreakpoint( self, opts, buf = None, line_num = None ):
# Use the parent session, because the _connection_ comes from the
# variable/watch result that is actually chosen

def add_bp( conn, name, msg ):
breakpoint_info = msg.get( 'body' )
if not breakpoint_info:
utils.UserMessage( "Can't set data breakpoint here" )
return

if breakpoint_info[ 'dataId' ] is None:
utils.UserMessage(
f"Can't set data breakpoint here: {breakpoint_info[ 'description' ]}"
)
return

access_types = breakpoint_info.get( 'accessTypes' )
if access_types and 'accessType' not in opts:
access_type = utils.SelectFromList( 'What type of access?',
access_types )
if access_type is not None:
opts[ 'accessType' ] = access_type

self._breakpoints.AddDataBreakpoint( conn,
name,
breakpoint_info,
opts )

self._variablesView.GetDataBreakpointInfo( add_bp, buf, line_num )

@IfConnected()
def AddWatch( self, expression ):
self._variablesView.AddWatch( self._connection,
Expand Down
1 change: 1 addition & 0 deletions python3/vimspector/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@
'delete': [ '<Del>' ],
'set_value': [ '<C-CR>', '<leader><CR>' ],
'read_memory': [ '<leader>m' ],
'add_data_breakpoint': [ '<F9>' ],
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should probably be based on the mappings mode, but I'm not sure anyone uses anything other than HUMAN mode...

},
'stack_trace': {
'expand_or_jump': [ '<CR>', '<2-LeftMouse>' ],
Expand Down
Loading
Loading