diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index f186f5d..14f5979 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -13,7 +13,7 @@ defaults: jobs: PythonLint: - runs-on: ubuntu-18.04 + runs-on: ubuntu-16.04 container: 'puremourning/vimspector:test' steps: - uses: actions/checkout@v2 @@ -22,7 +22,7 @@ jobs: - name: 'Run flake8' run: '$HOME/.local/bin/flake8 python3/ *.py' VimscriptLint: - runs-on: 'ubuntu-18.04' + runs-on: 'ubuntu-16.04' container: 'puremourning/vimspector:test' steps: - uses: actions/checkout@v2 @@ -32,7 +32,7 @@ jobs: run: $HOME/.local/bin/vint autoload/ compiler/ plugin/ tests/ syntax/ Linux: - runs-on: 'ubuntu-18.04' + runs-on: 'ubuntu-16.04' container: image: 'puremourning/vimspector:test' options: --cap-add=SYS_PTRACE --security-opt seccomp=unconfined @@ -156,7 +156,7 @@ jobs: # SSH_PASS: ${{ secrets.SSH_PASS }} # [V]imspector PublishRelease: - runs-on: 'ubuntu-18.04' + runs-on: 'ubuntu-16.04' needs: - Linux - MacOS diff --git a/README.md b/README.md index d198292..f8db3aa 100644 --- a/README.md +++ b/README.md @@ -61,10 +61,9 @@ For detailed explanation of the `.vimspector.json` format, see the * [Closing debugger](#closing-debugger) * [Terminate debuggee](#terminate-debuggee) * [Debug profile configuration](#debug-profile-configuration) - * [C, C++, Rust, etc.](#c-c-rust-etc) - * [Data visualization / pretty printing](#data-visualization--pretty-printing) - * [C++ Remote debugging](#c-remote-debugging) - * [C++ Remote launch and attach](#c-remote-launch-and-attach) + * [C, C , Rust, etc.](#c-c-rust-etc) + * [C Remote debugging](#c-remote-debugging) + * [C Remote launch and attach](#c-remote-launch-and-attach) * [Rust](#rust) * [Python](#python) * [Python Remote Debugging](#python-remote-debugging) @@ -291,7 +290,7 @@ If you just want to try out vimspector without changing your vim config, there are example projects for a number of languages in `support/test`, including: * Python (`support/test/python/simple_python`) -* Go (`support/test/go/hello_world` and `support/test/go/name-starts-with-vowel`) +* Go (`support/test/go/hello_world`) * Nodejs (`support/test/node/simple`) * Chrome (`support/test/chrome/`) * etc. @@ -1177,38 +1176,6 @@ licensing. } ``` -### Data visualization / pretty printing - -Depending on the backend you need to enable pretty printing of complex types manually. - -* LLDB: Pretty printing is enabled by default - -* GDB: To enable gdb pretty printers, consider the snippet below. - It is not enough to have `set print pretty on` in your .gdbinit! - -``` -{ - "configurations": { - "Launch": { - "adapter": "vscode-cpptools", - "configuration": { - "request": "launch", - "program": "", - ... - "MIMode": "gdb" - "setupCommands": [ - { - "description": "Enable pretty-printing for gdb", - "text": "-enable-pretty-printing", - "ignoreFailures": true - } - ], - } - } - } -} -``` - ### C++ Remote debugging The cpptools documentation describes how to attach cpptools to gdbserver using @@ -1401,8 +1368,6 @@ Requires: * [Delve][delve-install] installed, e.g. `go get -u github.com/go-delve/delve/cmd/dlv` * Delve to be in your PATH, or specify the `dlvToolPath` launch option -NOTE: Vimspector uses the ["legacy" vscode-go debug adapter](https://github.com/golang/vscode-go/blob/master/docs/debugging-legacy.md) rather than the "built-in" DAP support in Delve. You can track https://github.com/puremourning/vimspector/issues/186 for that. - ```json { "configurations": { @@ -1420,7 +1385,7 @@ NOTE: Vimspector uses the ["legacy" vscode-go debug adapter](https://github.com/ ``` See the vscode-go docs for -[troubleshooting information](https://github.com/golang/vscode-go/blob/master/docs/debugging-legacy.md#troubleshooting) +[troubleshooting information](https://github.com/golang/vscode-go/blob/master/docs/debugging.md#troubleshooting) ## PHP diff --git a/autoload/vimspector.vim b/autoload/vimspector.vim index 78c7c1b..0eb394f 100644 --- a/autoload/vimspector.vim +++ b/autoload/vimspector.vim @@ -223,6 +223,13 @@ function! vimspector#SetVariableValue( ... ) abort endif endfunction +function! vimspector#ReadMemory() abort + if !s:Enabled() + return + endif + py3 _vimspector_session.ReadMemory() +endfunction + function! vimspector#DeleteWatch() abort if !s:Enabled() return diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock index acf20f2..0fa7776 100644 --- a/docs/Gemfile.lock +++ b/docs/Gemfile.lock @@ -7,7 +7,7 @@ GEM minitest (~> 5.1) tzinfo (~> 1.1) zeitwerk (~> 2.2, >= 2.2.2) - addressable (2.8.0) + addressable (2.7.0) public_suffix (>= 2.0.2, < 5.0) coffee-script (2.4.1) coffee-script-source diff --git a/docs/configuration.md b/docs/configuration.md index 3d524bf..a2864b1 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -722,7 +722,7 @@ Vimspector then orchestrates the various tools to set you up. "variables": { // Just an example of how to specify a variable manually rather than // vimspector asking for input from the user - "ServiceName": "${fileBasenameNoExtension}" + "ServiceName": "${fileBasenameNoExtention}" }, "adapter": "python-remote", diff --git a/python3/vimspector/code.py b/python3/vimspector/code.py index 98aeca5..5f54e1c 100644 --- a/python3/vimspector/code.py +++ b/python3/vimspector/code.py @@ -16,6 +16,7 @@ import vim import logging import json +import os from collections import defaultdict from vimspector import utils, terminal, signs @@ -40,6 +41,7 @@ class CodeView( object ): 'breakpoints': [] } self._current_frame = None + self._scratch_buffers = [] with utils.LetCurrentWindow( self._window ): if utils.UseWinBar(): @@ -173,6 +175,10 @@ class CodeView( object ): self.ClearBreakpoints() self.Clear() + for b in self._scratch_buffers: + utils.CleanUpHiddenBuffer( b ) + self._scratch_buffers = [] + def AddBreakpoints( self, source, breakpoints ): for breakpoint in breakpoints: source = breakpoint.get( 'source' ) or source @@ -287,3 +293,32 @@ class CodeView( object ): # FIXME: Change this tor return the PID rather than having debug_session # work that out return self._terminal.buffer_number + + + def ShowMemory( self, memoryReference, length, offset, msg ): + if not self._window.valid: + return False + + buf_name = os.path.join( '_vimspector_mem', memoryReference ) + buf = utils.BufferForFile( buf_name ) + self._scratch_buffers.append( buf ) + utils.SetUpHiddenBuffer( buf, buf_name ) + with utils.ModifiableScratchBuffer( buf ): + # TODO: The data is encoded in base64, so we need to convert that to the + # equivalent output of say xxd + data = msg.get( 'body', {} ).get( 'data', '' ) + utils.SetBufferContents( buf, [ + f'Memory Dump for Reference {memoryReference} Length: {length} bytes' + f' Offset: {offset}', + '-' * 80, + 'Offset Bytes Text', + '-' * 80, + ] ) + utils.AppendToBuffer( buf, utils.Base64ToHexDump( data ) ) + + utils.SetSyntax( '', 'xxd', buf ) + utils.JumpToWindow( self._window ) + utils.OpenFileInCurrentWindow( buf_name ) + + # TODO: Need to set up some mappings here that allow the user to browse + # around by setting the offset diff --git a/python3/vimspector/debug_session.py b/python3/vimspector/debug_session.py index 36ad62b..c8c6ce8 100644 --- a/python3/vimspector/debug_session.py +++ b/python3/vimspector/debug_session.py @@ -535,6 +535,45 @@ class DebugSession( object ): def SetVariableValue( self, new_value = None, buf = None, line_num = None ): self._variablesView.SetVariableValue( new_value, buf, line_num ) + @IfConnected() + def ReadMemory( self, offset = None, buf = None, line_num = None ): + if not self._server_capabilities.get( 'supportsReadMemoryRequest' ): + utils.UserMessage( "Server does not support memory request", + error = True ) + return + + memoryReference = self._variablesView.GetMemoryReference( buf, line_num ) + if memoryReference is None: + utils.UserMessage( "Cannot find memory reference for that", + error = True ) + return + + length = utils.AskForInput( 'How much data to display? ', + default_value = '1024' ) + + if length is None: + return + + offset = utils.AskForInput( 'Location offset? ', + default_value = '0' ) + + if offset is None: + return + + + def handler( msg ): + self._codeView.ShowMemory( memoryReference, length, offset, msg ) + + self._connection.DoRequest( handler, { + 'command': 'readMemory', + 'arguments': { + 'memoryReference': memoryReference, + 'count': int( length ), + 'offset': int( offset ) + } + } ) + + @IfConnected() def AddWatch( self, expression ): self._variablesView.AddWatch( self._stackTraceView.GetCurrentFrame(), @@ -1163,7 +1202,8 @@ class DebugSession( object ): 'pathFormat': 'path', 'supportsVariableType': True, 'supportsVariablePaging': False, - 'supportsRunInTerminalRequest': True + 'supportsRunInTerminalRequest': True, + 'supportsMemoryReferences': True }, } ) diff --git a/python3/vimspector/gadgets.py b/python3/vimspector/gadgets.py index 02eb0e7..140c9f4 100644 --- a/python3/vimspector/gadgets.py +++ b/python3/vimspector/gadgets.py @@ -30,12 +30,12 @@ GADGETS = { root, gadget ), 'all': { - 'version': '1.6.0', + 'version': '0.27.0', "adapters": { "vscode-cpptools": { "name": "cppdbg", "command": [ - "${gadgetDir}/vscode-cpptools/debugAdapters/bin/OpenDebugAD7" + "${gadgetDir}/vscode-cpptools/debugAdapters/OpenDebugAD7" ], "attach": { "pidProperty": "processId", @@ -53,17 +53,17 @@ GADGETS = { 'linux': { 'file_name': 'cpptools-linux.vsix', 'checksum': - 'c25299bcfb46b22d41aa3f125df7184e6282a35ff9fb69c47def744cb4778f55', + '3695202e1e75a03de18049323b66d868165123f26151f8c974a480eaf0205435', }, 'macos': { - 'file_name': 'cpptools-osx-arm64.vsix', + 'file_name': 'cpptools-osx.vsix', 'checksum': - 'ceb3e8cdaa2b5bb45af50913ddd8402089969748af8d70f5d46480408287ba6f', + 'cb061e3acd7559a539e5586f8d3f535101c4ec4e8a48195856d1d39380b5cf3c', }, 'windows': { 'file_name': 'cpptools-win32.vsix', 'checksum': - 'ef7ac5831874a3c7dbf0feb826bfda2be579aff9b6d990622fff1d0d4ede00d1', + 'aa294368ed16d48c59e49c8000e146eae5a19ad07b654efed5db8ec93b24229e', "adapters": { "vscode-cpptools": { "name": "cppdbg", @@ -323,10 +323,10 @@ GADGETS = { '${version}/${file_name}', }, 'all': { - 'version': 'v1.17.0', - 'file_name': 'php-debug-1.17.0.vsix', + 'version': 'v1.16.0', + 'file_name': 'php-debug-1.16.0.vsix', 'checksum': - 'd0fff272503414b6696cc737bc2e18e060fdd5e5dc4bcaf38ae7373afd8d8bc9', + '62d210f7b87b21315c37ea10a1a5dbae376ff9f963b8f8cf33361e01413731be', }, 'adapters': { 'vscode-php-debug': { @@ -394,12 +394,12 @@ GADGETS = { '${version}/${file_name}', }, 'all': { - 'version': 'v1.6.6', + 'version': 'v1.6.4', }, 'macos': { - 'file_name': 'codelldb-aarch64-darwin.vsix', + 'file_name': 'codelldb-x86_64-darwin.vsix', 'checksum': - '5adc3b9139eabdafd825bd5efc55df4424a203fb2b6087b425cd434956e7ec58', + 'aa920b7b7d2ad4e9d70086355841b0b4844fb5f62cdea1296904100a1b660776', 'make_executable': [ 'adapter/codelldb', 'lldb/bin/debugserver', @@ -410,7 +410,7 @@ GADGETS = { 'linux': { 'file_name': 'codelldb-x86_64-linux.vsix', 'checksum': - 'eda2cd9b3089dcc0524c273e91ffb5875fe08c930bf643739a2cd1846e1f98d6', + '', 'make_executable': [ 'adapter/codelldb', 'lldb/bin/lldb', @@ -421,7 +421,7 @@ GADGETS = { 'windows': { 'file_name': 'codelldb-x86_64-windows.vsix', 'checksum': - '8ddebe8381a3d22dc3d95139c3797fda06b5cc34aadf300e13b1c516b9da95fe', + '', 'make_executable': [] }, 'adapters': { diff --git a/python3/vimspector/installer.py b/python3/vimspector/installer.py index a81db8f..f0f85a4 100644 --- a/python3/vimspector/installer.py +++ b/python3/vimspector/installer.py @@ -358,8 +358,7 @@ def InstallCppTools( name, root, gadget ): # It's hilarious, but the execute bits aren't set in the vsix. So they # actually have javascript code which does this. It's just a horrible horrible # hack that really is not funny. - MakeExecutable( - os.path.join( extension, 'debugAdapters', 'bin', 'OpenDebugAD7' ) ) + MakeExecutable( os.path.join( extension, 'debugAdapters', 'OpenDebugAD7' ) ) with open( os.path.join( extension, 'package.json' ) ) as f: package = json.load( f ) runtime_dependencies = package[ 'runtimeDependencies' ] diff --git a/python3/vimspector/output.py b/python3/vimspector/output.py index 3f0da1e..8c94b44 100644 --- a/python3/vimspector/output.py +++ b/python3/vimspector/output.py @@ -32,7 +32,6 @@ class TabBuffer( object ): BUFFER_MAP = { 'console': 'Console', 'stdout': 'Console', - 'output': 'Console', 'stderr': 'stderr', 'telemetry': None, } diff --git a/python3/vimspector/settings.py b/python3/vimspector/settings.py index 89378af..60e8341 100644 --- a/python3/vimspector/settings.py +++ b/python3/vimspector/settings.py @@ -54,7 +54,8 @@ DEFAULTS = { 'variables': { 'expand_collapse': [ '', '<2-LeftMouse>' ], 'delete': [ '' ], - 'set_value': [ '', '' ] + 'set_value': [ '', '' ], + 'read_memory': [ 'm' ], }, 'stack_trace': { 'expand_or_jump': [ '', '<2-LeftMouse>' ], diff --git a/python3/vimspector/utils.py b/python3/vimspector/utils.py index 5f836fc..150ab95 100644 --- a/python3/vimspector/utils.py +++ b/python3/vimspector/utils.py @@ -25,7 +25,9 @@ import shlex import collections import re import typing +import base64 +from vimspector.vendor.hexdump import hexdump LOG_FILE = os.path.expanduser( os.path.join( '~', '.vimspector.log' ) ) @@ -864,3 +866,8 @@ def UseWinBar(): # Buggy neovim doesn't render correctly when the WinBar is defined: # https://github.com/neovim/neovim/issues/12689 return not int( Call( 'has', 'nvim' ) ) + + +def Base64ToHexDump( data ): + data = base64.b64decode( data ) + return list( hexdump( data, 'generator' ) ) diff --git a/python3/vimspector/variables.py b/python3/vimspector/variables.py index 8dcb493..8524d67 100644 --- a/python3/vimspector/variables.py +++ b/python3/vimspector/variables.py @@ -56,6 +56,11 @@ class Expandable: def VariablesReference( self ): assert False + @abc.abstractmethod + def MemoryReference( self ): + assert None + + class Scope( Expandable ): """Holds an expandable scope (a DAP scope dict), with expand/collapse state""" @@ -66,6 +71,9 @@ class Scope( Expandable ): def VariablesReference( self ): return self.scope.get( 'variablesReference', 0 ) + def MemoryReference( self ): + return None + def Update( self, scope ): self.scope = scope @@ -81,6 +89,9 @@ class WatchResult( Expandable ): def VariablesReference( self ): return self.result.get( 'variablesReference', 0 ) + def MemoryReference( self ): + return self.result.get( 'memoryReference' ) + def Update( self, result ): self.changed = False if self.result[ 'result' ] != result[ 'result' ]: @@ -105,6 +116,9 @@ class Variable( Expandable ): def VariablesReference( self ): return self.variable.get( 'variablesReference', 0 ) + def MemoryReference( self ): + return self.variable.get( 'memoryReference' ) + def Update( self, variable ): self.changed = False if self.variable[ 'value' ] != variable[ 'value' ]: @@ -163,6 +177,10 @@ def AddExpandMappings( mappings = None ): for mapping in utils.GetVimList( mappings, 'set_value' ): vim.command( f'nnoremap { mapping } ' ':call vimspector#SetVariableValue()' ) + for mapping in utils.GetVimList( mappings, 'read_memory' ): + vim.command( f'nnoremap { mapping } ' + ':call vimspector#ReadMemory()' ) + class VariablesView( object ): @@ -187,6 +205,8 @@ class VariablesView( object ): if utils.UseWinBar(): vim.command( 'nnoremenu 1.1 WinBar.Set ' ':call vimspector#SetVariableValue()' ) + vim.command( 'nnoremenu 1.2 WinBar.Memory ' + ':call vimspector#ReadMemory()' ) AddExpandMappings( mappings ) # Set up the "Watches" buffer in the watches_win (and create a WinBar in @@ -211,8 +231,10 @@ class VariablesView( object ): ':call vimspector#ExpandVariable()' ) vim.command( 'nnoremenu 1.3 WinBar.Delete ' ':call vimspector#DeleteWatch()' ) - vim.command( 'nnoremenu 1.1 WinBar.Set ' + vim.command( 'nnoremenu 1.4 WinBar.Set ' ':call vimspector#SetVariableValue()' ) + vim.command( 'nnoremenu 1.5 WinBar.Memory ' + ':call vimspector#ReadMemory()' ) # Set the (global!) balloon expr if supported has_balloon = int( vim.eval( "has( 'balloon_eval' )" ) ) @@ -580,6 +602,14 @@ class VariablesView( object ): }, failure_handler = failure_handler ) + def GetMemoryReference( self, buf = None, line_num = None ): + # Get a memoryReference for use in a ReadMemory request + variable, _ = self._GetVariable( buf, line_num ) + if variable is None: + return None + + return variable.MemoryReference() + def _DrawVariables( self, view, variables, indent, is_short = False ): assert indent > 0 @@ -595,10 +625,12 @@ class VariablesView( object ): value = variable.variable.get( 'value', '' ) ) else: + marker = 'm' if variable.MemoryReference() is not None else ' ' + marker += '*' if variable.changed else ' ' text = '{indent}{marker}{icon} {name} ({type_}): {value}'.format( # We borrow 1 space of indent to draw the change marker indent = ' ' * ( indent - 1 ), - marker = '*' if variable.changed else ' ', + marker = marker, icon = '+' if ( variable.IsExpandable() and not variable.IsExpanded() ) else '-', name = variable.variable.get( 'name', '' ), diff --git a/python3/vimspector/vendor/hexdump.py b/python3/vimspector/vendor/hexdump.py new file mode 100755 index 0000000..33b9b6b --- /dev/null +++ b/python3/vimspector/vendor/hexdump.py @@ -0,0 +1,466 @@ +#!/usr/bin/env python +# -*- coding: latin-1 -*- + +# <-- removing this magic comment breaks Python 3.4 on Windows +""" +1. Dump binary data to the following text format: + +00000000: 00 00 00 5B 68 65 78 64 75 6D 70 5D 00 00 00 00 ...[hexdump].... +00000010: 00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF .."3DUfw........ + +It is similar to the one used by: +Scapy +00 00 00 5B 68 65 78 64 75 6D 70 5D 00 00 00 00 ...[hexdump].... +00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF .."3DUfw........ + +Far Manager +000000000: 00 00 00 5B 68 65 78 64 ¦ 75 6D 70 5D 00 00 00 00 [hexdump] +000000010: 00 11 22 33 44 55 66 77 ¦ 88 99 AA BB CC DD EE FF ?"3DUfwˆ™ª»ÌÝîÿ + + +2. Restore binary data from the formats above as well + as from less exotic strings of raw hex + +""" + +__version__ = '3.3' +__author__ = 'anatoly techtonik ' +__license__ = 'Public Domain' + +__history__ = \ +""" +3.3 (2015-01-22) + * accept input from sys.stdin if "-" is specified + for both dump and restore (issue #1) + * new normalize_py() helper to set sys.stdout to + binary mode on Windows + +3.2 (2015-07-02) + * hexdump is now packaged as .zip on all platforms + (on Linux created archive was tar.gz) + * .zip is executable! try `python hexdump-3.2.zip` + * dump() now accepts configurable separator, patch + by Ian Land (PR #3) + +3.1 (2014-10-20) + * implemented workaround against mysterious coding + issue with Python 3 (see revision 51302cf) + * fix Python 3 installs for systems where UTF-8 is + not default (Windows), thanks to George Schizas + (the problem was caused by reading of README.txt) + +3.0 (2014-09-07) + * remove unused int2byte() helper + * add dehex(text) helper to convert hex string + to binary data + * add 'size' argument to dump() helper to specify + length of chunks + +2.0 (2014-02-02) + * add --restore option to command line mode to get + binary data back from hex dump + * support saving test output with `--test logfile` + * restore() from hex strings without spaces + * restore() now raises TypeError if input data is + not string + * hexdump() and dumpgen() now don't return unicode + strings in Python 2.x when generator is requested + +1.0 (2013-12-30) + * length of address is reduced from 10 to 8 + * hexdump() got new 'result' keyword argument, it + can be either 'print', 'generator' or 'return' + * actual dumping logic is now in new dumpgen() + generator function + * new dump(binary) function that takes binary data + and returns string like "66 6F 72 6D 61 74" + * new genchunks(mixed, size) function that chunks + both sequences and file like objects + +0.5 (2013-06-10) + * hexdump is now also a command line utility (no + restore yet) + +0.4 (2013-06-09) + * fix installation with Python 3 for non English + versions of Windows, thanks to George Schizas + +0.3 (2013-04-29) + * fully Python 3 compatible + +0.2 (2013-04-28) + * restore() to recover binary data from a hex dump in + native, Far Manager and Scapy text formats (others + might work as well) + * restore() is Python 3 compatible + +0.1 (2013-04-28) + * working hexdump() function for Python 2 +""" + +import binascii # binascii is required for Python 3 +import sys + +# --- constants +PY3K = sys.version_info >= (3, 0) + +# --- workaround against Python consistency issues +def normalize_py(): + ''' Problem 001 - sys.stdout in Python is by default opened in + text mode, and writes to this stdout produce corrupted binary + data on Windows + + python -c "import sys; sys.stdout.write('_\n_')" > file + python -c "print(repr(open('file', 'rb').read()))" + ''' + if sys.platform == "win32": + # set sys.stdout to binary mode on Windows + import os, msvcrt + msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) + +# --- - chunking helpers +def chunks(seq, size): + '''Generator that cuts sequence (bytes, memoryview, etc.) + into chunks of given size. If `seq` length is not multiply + of `size`, the lengh of the last chunk returned will be + less than requested. + + >>> list( chunks([1,2,3,4,5,6,7], 3) ) + [[1, 2, 3], [4, 5, 6], [7]] + ''' + d, m = divmod(len(seq), size) + for i in range(d): + yield seq[i*size:(i+1)*size] + if m: + yield seq[d*size:] + +def chunkread(f, size): + '''Generator that reads from file like object. May return less + data than requested on the last read.''' + c = f.read(size) + while len(c): + yield c + c = f.read(size) + +def genchunks(mixed, size): + '''Generator to chunk binary sequences or file like objects. + The size of the last chunk returned may be less than + requested.''' + if hasattr(mixed, 'read'): + return chunkread(mixed, size) + else: + return chunks(mixed, size) +# --- - /chunking helpers + + +def dehex(hextext): + """ + Convert from hex string to binary data stripping + whitespaces from `hextext` if necessary. + """ + if PY3K: + return bytes.fromhex(hextext) + else: + hextext = "".join(hextext.split()) + return hextext.decode('hex') + +def dump(binary, size=2, sep=' '): + ''' + Convert binary data (bytes in Python 3 and str in + Python 2) to hex string like '00 DE AD BE EF'. + `size` argument specifies length of text chunks + and `sep` sets chunk separator. + ''' + hexstr = binascii.hexlify(binary) + if PY3K: + hexstr = hexstr.decode('ascii') + return sep.join(chunks(hexstr.upper(), size)) + +def dumpgen(data): + ''' + Generator that produces strings: + + '00000000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................' + ''' + generator = genchunks(data, 16) + for addr, d in enumerate(generator): + # 00000000: + line = '%08X: ' % (addr*16) + # 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + dumpstr = dump(d) + line += dumpstr[:8*3] + if len(d) > 8: # insert separator if needed + line += ' ' + dumpstr[8*3:] + # ................ + # calculate indentation, which may be different for the last line + pad = 2 + if len(d) < 16: + pad += 3*(16 - len(d)) + if len(d) <= 8: + pad += 1 + line += ' '*pad + + for byte in d: + # printable ASCII range 0x20 to 0x7E + if not PY3K: + byte = ord(byte) + if 0x20 <= byte <= 0x7E: + line += chr(byte) + else: + line += '.' + yield line + +def hexdump(data, result='print'): + ''' + Transform binary data to the hex dump text format: + + 00000000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ + + [x] data argument as a binary string + [x] data argument as a file like object + + Returns result depending on the `result` argument: + 'print' - prints line by line + 'return' - returns single string + 'generator' - returns generator that produces lines + ''' + if PY3K and type(data) == str: + raise TypeError('Abstract unicode data (expected bytes sequence)') + + gen = dumpgen(data) + if result == 'generator': + return gen + elif result == 'return': + return '\n'.join(gen) + elif result == 'print': + for line in gen: + print(line) + else: + raise ValueError('Unknown value of `result` argument') + +def restore(dump): + ''' + Restore binary data from a hex dump. + [x] dump argument as a string + [ ] dump argument as a line iterator + + Supported formats: + [x] hexdump.hexdump + [x] Scapy + [x] Far Manager + ''' + minhexwidth = 2*16 # minimal width of the hex part - 00000... style + bytehexwidth = 3*16-1 # min width for a bytewise dump - 00 00 ... style + + result = bytes() if PY3K else '' + if type(dump) != str: + raise TypeError('Invalid data for restore') + + text = dump.strip() # ignore surrounding empty lines + for line in text.split('\n'): + # strip address part + addrend = line.find(':') + if 0 < addrend < minhexwidth: # : is not in ascii part + line = line[addrend+1:] + line = line.lstrip() + # check dump type + if line[2] == ' ': # 00 00 00 ... type of dump + # check separator + sepstart = (2+1)*7+2 # ('00'+' ')*7+'00' + sep = line[sepstart:sepstart+3] + if sep[:2] == ' ' and sep[2:] != ' ': # ...00 00 00 00... + hexdata = line[:bytehexwidth+1] + elif sep[2:] == ' ': # ...00 00 | 00 00... - Far Manager + hexdata = line[:sepstart] + line[sepstart+3:bytehexwidth+2] + else: # ...00 00 00 00... - Scapy, no separator + hexdata = line[:bytehexwidth] + line = hexdata + result += dehex(line) + return result + + +def runtest(logfile=None): + '''Run hexdump tests. Requires hexfile.bin to be in the same + directory as hexdump.py itself''' + + class TeeOutput(object): + def __init__(self, stream1, stream2): + self.outputs = [stream1, stream2] + + # -- methods from sys.stdout / sys.stderr + def write(self, data): + for stream in self.outputs: + if PY3K: + if 'b' in stream.mode: + data = data.encode('utf-8') + stream.write(data) + stream.flush() + + def tell(self): + raise IOError + + def flush(self): + for stream in self.outputs: + stream.flush() + # --/ sys.stdout + + if logfile: + openlog = open(logfile, 'wb') + # copy stdout and stderr streams to log file + savedstd = sys.stderr, sys.stdout + sys.stderr = TeeOutput(sys.stderr, openlog) + sys.stdout = TeeOutput(sys.stdout, openlog) + + + def echo(msg, linefeed=True): + sys.stdout.write(msg) + if linefeed: + sys.stdout.write('\n') + + expected = '''\ +00000000: 00 00 00 5B 68 65 78 64 75 6D 70 5D 00 00 00 00 ...[hexdump].... +00000010: 00 11 22 33 44 55 66 77 88 99 0A BB CC DD EE FF .."3DUfw........\ +''' + + # get path to hexfile.bin + # this doesn't work from .zip + # import os.path as osp + # hexfile = osp.dirname(osp.abspath(__file__)) + '/hexfile.bin' + # this doesn't work either + # hexfile = osp.dirname(sys.modules[__name__].__file__) + '/hexfile.bin' + # this works + import pkgutil + bin = pkgutil.get_data('hexdump', 'data/hexfile.bin') + + # varios length of input data + hexdump(b'zzzz'*12) + hexdump(b'o'*17) + hexdump(b'p'*24) + hexdump(b'q'*26) + # allowable character set filter + hexdump(b'line\nfeed\r\ntest') + hexdump(b'\x00\x00\x00\x5B\x68\x65\x78\x64\x75\x6D\x70\x5D\x00\x00\x00\x00' + b'\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\x0A\xBB\xCC\xDD\xEE\xFF') + print('---') + # dumping file-like binary object to screen (default behavior) + hexdump(bin) + print('return output') + hexout = hexdump(bin, result='return') + assert hexout == expected, 'returned hex didn\'t match' + print('return generator') + hexgen = hexdump(bin, result='generator') + assert next(hexgen) == expected.split('\n')[0], 'hex generator 1 didn\'t match' + assert next(hexgen) == expected.split('\n')[1], 'hex generator 2 didn\'t match' + + # binary restore test + bindata = restore( +''' +00000000: 00 00 00 5B 68 65 78 64 75 6D 70 5D 00 00 00 00 ...[hexdump].... +00000010: 00 11 22 33 44 55 66 77 88 99 0A BB CC DD EE FF .."3DUfw........ +''') + echo('restore check ', linefeed=False) + assert bin == bindata, 'restore check failed' + echo('passed') + + far = \ +''' +000000000: 00 00 00 5B 68 65 78 64 ¦ 75 6D 70 5D 00 00 00 00 [hexdump] +000000010: 00 11 22 33 44 55 66 77 ¦ 88 99 0A BB CC DD EE FF ?"3DUfwˆ™ª»ÌÝîÿ +''' + echo('restore far format ', linefeed=False) + assert bin == restore(far), 'far format check failed' + echo('passed') + + scapy = '''\ +00 00 00 5B 68 65 78 64 75 6D 70 5D 00 00 00 00 ...[hexdump].... +00 11 22 33 44 55 66 77 88 99 0A BB CC DD EE FF .."3DUfw........ +''' + echo('restore scapy format ', linefeed=False) + assert bin == restore(scapy), 'scapy format check failed' + echo('passed') + + if not PY3K: + assert restore('5B68657864756D705D') == '[hexdump]', 'no space check failed' + assert dump('\\\xa1\xab\x1e', sep='').lower() == '5ca1ab1e' + else: + assert restore('5B68657864756D705D') == b'[hexdump]', 'no space check failed' + assert dump(b'\\\xa1\xab\x1e', sep='').lower() == '5ca1ab1e' + + print('---[test file hexdumping]---') + + import os + import tempfile + hexfile = tempfile.NamedTemporaryFile(delete=False) + try: + hexfile.write(bin) + hexfile.close() + hexdump(open(hexfile.name, 'rb')) + finally: + os.remove(hexfile.name) + if logfile: + sys.stderr, sys.stdout = savedstd + openlog.close() + + +def main(): + from optparse import OptionParser + parser = OptionParser(usage=''' + %prog [binfile|-] + %prog -r hexfile + %prog --test [logfile]''', version=__version__) + parser.add_option('-r', '--restore', action='store_true', + help='restore binary from hex dump') + parser.add_option('--test', action='store_true', help='run hexdump sanity checks') + + options, args = parser.parse_args() + + if options.test: + if args: + runtest(logfile=args[0]) + else: + runtest() + elif not args or len(args) > 1: + parser.print_help() + sys.exit(-1) + else: + ## dump file + if not options.restore: + # [x] memory effective dump + if args[0] == '-': + if not PY3K: + hexdump(sys.stdin) + else: + hexdump(sys.stdin.buffer) + else: + hexdump(open(args[0], 'rb')) + + ## restore file + else: + # prepare input stream + if args[0] == '-': + instream = sys.stdin + else: + if PY3K: + instream = open(args[0]) + else: + instream = open(args[0], 'rb') + + # output stream + # [ ] memory efficient restore + if PY3K: + sys.stdout.buffer.write(restore(instream.read())) + else: + # Windows - binary mode for sys.stdout to prevent data corruption + normalize_py() + sys.stdout.write(restore(instream.read())) + +if __name__ == '__main__': + main() + +# [x] file restore from command line utility +# [ ] write dump with LF on Windows for consistency +# [ ] encoding param for hexdump()ing Python 3 str if anybody requests that + +# [ ] document chunking API +# [ ] document hexdump API +# [ ] blog about sys.stdout text mode problem on Windows diff --git a/run_tests b/run_tests index 201ec1b..441acb0 100755 --- a/run_tests +++ b/run_tests @@ -21,7 +21,7 @@ out_fd=1 while [ -n "$1" ]; do case "$1" in - "--basedir"|"--base-dir"|"--test-base") + "--basedir") shift SetBaseDir $1 shift @@ -36,7 +36,7 @@ while [ -n "$1" ]; do INSTALL=$1 shift ;; - "--update"|"--upgrade") + "--update") UPDATE=1 shift ;; diff --git a/support/custom_ui_vimrc b/support/custom_ui_vimrc index e76c6ee..a8812cb 100644 --- a/support/custom_ui_vimrc +++ b/support/custom_ui_vimrc @@ -54,7 +54,7 @@ function s:SetUpTerminal() let padding = 4 let cols = max( [ min( [ &columns - left_bar - code - padding, 80 ] ), 10 ] ) call win_gotoid( terminal_win ) - execute string(cols) . 'wincmd |' + execute cols . 'wincmd |' endfunction function! s:CustomiseWinBar() diff --git a/support/gadget_upgrade/README.md b/support/gadget_upgrade/README.md deleted file mode 100644 index 9ae3d7f..0000000 --- a/support/gadget_upgrade/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# Manually updating shipped gadgets - -Download the gadget files manuall from their official source into this dir. -Run `./checksum.py ` to get the checksums. - -Update ../../python3/vimspector/gadgets.py with the new version and the -checksums. - diff --git a/support/gadget_upgrade/checksum.py b/support/gadget_upgrade/checksum.py deleted file mode 100755 index d0c1404..0000000 --- a/support/gadget_upgrade/checksum.py +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env python3 - -import hashlib -import sys - - -def GetChecksumSHA254( file_path ): - with open( file_path, 'rb' ) as existing_file: - return hashlib.sha256( existing_file.read() ).hexdigest() - - -for arg in sys.argv[ 1: ]: - print( f"{ arg } = { GetChecksumSHA254( arg ) }" ) diff --git a/support/test/bash/.vimspector.json b/support/test/bash/.vimspector.json deleted file mode 100644 index a1be1b9..0000000 --- a/support/test/bash/.vimspector.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "$schema": "https://puremourning.github.io/vimspector/schema/vimspector.schema.json", - "configurations": { - "Run Current Script": { - "adapter": "vscode-bash", - "autoselect": false, - "configuration": { - "request": "launch", - "program": "${file}", - "cwd": "${fileDirname}", - "args": [ "*${args}" ] - } - } - } -} diff --git a/support/test/go/name-starts-with-vowel/.vimspector.json b/support/test/go/name-starts-with-vowel/.vimspector.json deleted file mode 100644 index ffcfc93..0000000 --- a/support/test/go/name-starts-with-vowel/.vimspector.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "configurations": { - "run-cmd": { - "adapter": "vscode-go", - "configuration": { - "request": "launch", - "program": "${workspaceRoot}/cmd/namestartswithvowel/main.go", - "mode": "debug", - "dlvToolPath": "$HOME/go/bin/dlv", - "dlvLoadConfig": { - "maxArrayValues": 1000, - "maxStringLen": 1000 - } - } - }, - "test-current-file": { - "adapter": "vscode-go", - "configuration": { - "request": "launch", - "mode": "test", - "program": "${fileDirname}", - "cwd": "${fileDirname}", - "dlvToolPath": "$GOPATH/bin/dlv", - "env": {}, - "args": [] - } - } - } -} diff --git a/support/test/go/name-starts-with-vowel/README.md b/support/test/go/name-starts-with-vowel/README.md deleted file mode 100644 index fec967e..0000000 --- a/support/test/go/name-starts-with-vowel/README.md +++ /dev/null @@ -1,33 +0,0 @@ -# Purpose - -This example comes with two example vimspector configs for the Go programming language. - -1) `run-cmd` will launch the main programme under `cmd/namestartswithvowel`. -1) `test-current-file` will run the tests in the current file in debug mode. - -## Example use-cases - -### run-cmd - -* Open `cmd/namestartswithvowel/main.go` -* Add a breakpoint somewhere within the programme -* Start the debugger (`:call vimspector#Continue()` or your relevant keymapping) -* Select the first launch configuration (`1: run-cmd`) - -### test-current-file - -* Open `internal/vowels/vowels_test.go` -* Add a breakpoint somewhere within the test -* Start the debugger (`:call vimspector#Continue()` or your relevant keymapping) -* Select the second launch configuration (`2: test-current-file`) - -## Additional Configuration - -There are two additional configuration options specified under `run-cmd`; these parameters configure the maximum string/array size to be shown while debugging. - -``` -"dlvLoadConfig": { - "maxArrayValues": 1000, - "maxStringLen": 1000 -} -``` diff --git a/support/test/go/name-starts-with-vowel/cmd/namestartswithvowel/main.go b/support/test/go/name-starts-with-vowel/cmd/namestartswithvowel/main.go deleted file mode 100644 index c160aea..0000000 --- a/support/test/go/name-starts-with-vowel/cmd/namestartswithvowel/main.go +++ /dev/null @@ -1,20 +0,0 @@ -package main - -import ( - "fmt" - - "example.com/internal/vowels" -) - -func main() { - names := []string{"Simon", "Bob", "Jennifer", "Amy", "Duke", "Elizabeth"} - - for _, n := range names { - if vowels.NameStartsWithVowel(n) { - fmt.Printf("%s starts with a vowel!\n", n) - continue - } - - fmt.Printf("%s does not start with a vowel!\n", n) - } -} diff --git a/support/test/go/name-starts-with-vowel/go.mod b/support/test/go/name-starts-with-vowel/go.mod deleted file mode 100644 index 3070734..0000000 --- a/support/test/go/name-starts-with-vowel/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module example.com - -go 1.16 diff --git a/support/test/go/name-starts-with-vowel/internal/vowels/vowels.go b/support/test/go/name-starts-with-vowel/internal/vowels/vowels.go deleted file mode 100644 index 4e76480..0000000 --- a/support/test/go/name-starts-with-vowel/internal/vowels/vowels.go +++ /dev/null @@ -1,9 +0,0 @@ -package vowels - -import "strings" - -func NameStartsWithVowel(name string) bool { - s := strings.Split(strings.ToLower(name), "") - - return s[0] == "a" || s[0] == "e" || s[0] == "i" || s[0] == "o" || s[0] == "u" -} diff --git a/support/test/go/name-starts-with-vowel/internal/vowels/vowels_test.go b/support/test/go/name-starts-with-vowel/internal/vowels/vowels_test.go deleted file mode 100644 index e0d5773..0000000 --- a/support/test/go/name-starts-with-vowel/internal/vowels/vowels_test.go +++ /dev/null @@ -1,30 +0,0 @@ -package vowels - -import ( - "fmt" - "testing" -) - -func TestNameStartsWithVowel(t *testing.T) { - testCases := []struct { - input string - expectedOutput bool - }{ - { - input: "Simon", - expectedOutput: false, - }, - { - input: "Andy", - expectedOutput: true, - }, - } - for _, tt := range testCases { - t.Run(fmt.Sprintf("%s should product %t", tt.input, tt.expectedOutput), func(t *testing.T) { - out := NameStartsWithVowel(tt.input) - if out != tt.expectedOutput { - t.Errorf("%s produced %t, when %t was expected", tt.input, out, tt.expectedOutput) - } - }) - } -} diff --git a/tests/testdata/cpp/simple/.vimspector.json b/tests/testdata/cpp/simple/.vimspector.json index 48ce801..0dca061 100644 --- a/tests/testdata/cpp/simple/.vimspector.json +++ b/tests/testdata/cpp/simple/.vimspector.json @@ -12,7 +12,7 @@ "externalConsole": false, "stopAtEntry": true, "stopOnEntry": true, - "MIMode": "${VIMSPECTOR_MIMODE}" + "MImode": "${VIMSPECTOR_MIMODE}" }, "breakpoints": { "exception": { @@ -33,7 +33,7 @@ "externalConsole": false, "stopAtEntry": false, "stopOnEntry": false, - "MIMode": "${VIMSPECTOR_MIMODE}" + "MImode": "${VIMSPECTOR_MIMODE}" }, "breakpoints": { "exception": { @@ -55,7 +55,7 @@ "externalConsole": false, "stopAtEntry": false, "stopOnEntry": false, - "MIMode": "${VIMSPECTOR_MIMODE}" + "MImode": "${VIMSPECTOR_MIMODE}" }, "breakpoints": { "exception": { @@ -82,7 +82,7 @@ "configuration": { "request": "launch", "program": "${workspaceRoot}/${fileBasenameNoExtension}", - "MIMode": "${VIMSPECTOR_MIMODE}", + "MImode": "${VIMSPECTOR_MIMODE}", "externalConsole": false, "args": [ "CALCULATED_LIST", "${CALCULATED_LIST}", diff --git a/tests/variables.test.vim b/tests/variables.test.vim index c00fb7f..59ca2c0 100644 --- a/tests/variables.test.vim +++ b/tests/variables.test.vim @@ -194,7 +194,6 @@ function! Test_ExpandVariables() \ [ \ '- Scope: Locals', \ ' *+ t (Test): {...}', - \ '+ Scope: Registers', \ ], \ getbufline( winbufnr( g:vimspector_session_windows.variables ), \ 1, @@ -220,7 +219,6 @@ function! Test_ExpandVariables() \ ' \*- c (char): 0 ''\\0\{1,3}''', \ ' \*- fffff (float): 0', \ ' \*+ another_test (AnotherTest):\( {...}\)\?', - \ '+ Scope: Registers', \ ], \ getbufline( winbufnr( g:vimspector_session_windows.variables ), \ 1, @@ -239,7 +237,6 @@ function! Test_ExpandVariables() \ ' - c (char): 0 ''\\0\{1,3}''', \ ' - fffff (float): 0', \ ' + another_test (AnotherTest):\( {...}\)\?', - \ '+ Scope: Registers', \ ], \ getbufline( winbufnr( g:vimspector_session_windows.variables ), \ 1, @@ -256,7 +253,6 @@ function! Test_ExpandVariables() \ [ \ '- Scope: Locals', \ ' + t (Test): {...}', - \ '+ Scope: Registers', \ ], \ getbufline( winbufnr( g:vimspector_session_windows.variables ), \ 1, @@ -271,7 +267,6 @@ function! Test_ExpandVariables() \ [ \ '- Scope: Locals', \ ' + t (Test): {...}', - \ '+ Scope: Registers', \ ], \ getbufline( winbufnr( g:vimspector_session_windows.variables ), \ 1, @@ -291,7 +286,6 @@ function! Test_ExpandVariables() \ ' \*- c (char): 99 ''c''', \ ' \*- fffff (float): 0', \ ' \*+ another_test (AnotherTest):\( {...}\)\?', - \ '+ Scope: Registers', \ ], \ getbufline( winbufnr( g:vimspector_session_windows.variables ), \ 1, @@ -308,7 +302,6 @@ function! Test_ExpandVariables() \ assert_equal( \ [ \ '+ Scope: Locals', - \ '+ Scope: Registers', \ ], \ getbufline( winbufnr( g:vimspector_session_windows.variables ), \ 1, @@ -323,7 +316,6 @@ function! Test_ExpandVariables() \ assert_equal( \ [ \ '+ Scope: Locals', - \ '+ Scope: Registers', \ ], \ getbufline( winbufnr( g:vimspector_session_windows.variables ), \ 1, @@ -339,7 +331,6 @@ function! Test_ExpandVariables() \ assert_equal( \ [ \ '+ Scope: Locals', - \ '+ Scope: Registers', \ ], \ getbufline( winbufnr( g:vimspector_session_windows.variables ), \ 1, @@ -855,7 +846,6 @@ function! Test_SetVariableValue_Local() \ [ \ '- Scope: Locals', \ ' *+ t (Test): {...}', - \ '+ Scope: Registers', \ ], \ getbufline( winbufnr( g:vimspector_session_windows.variables ), \ 1, @@ -881,7 +871,6 @@ function! Test_SetVariableValue_Local() \ ' \*- c (char): 0 ''\\0\{1,3}''', \ ' \*- fffff (float): 0', \ ' \*+ another_test (AnotherTest):\( {...}\)\?', - \ '+ Scope: Registers', \ ], \ getbufline( winbufnr( g:vimspector_session_windows.variables ), \ 1, @@ -908,7 +897,6 @@ EOF \ ' \*- c (char): 0 ''\\0\{1,3}''', \ ' \*- fffff (float): 0', \ ' \*+ another_test (AnotherTest):\( {...}\)\?', - \ '+ Scope: Registers', \ ], \ getbufline( winbufnr( g:vimspector_session_windows.variables ), \ 1, @@ -928,7 +916,6 @@ EOF \ ' \*- c (char): 0 ''\\0\{1,3}''', \ ' \*- fffff (float): 0', \ ' \*+ another_test (AnotherTest):\( {...}\)\?', - \ '+ Scope: Registers', \ ], \ getbufline( winbufnr( g:vimspector_session_windows.variables ), \ 1, @@ -948,7 +935,6 @@ EOF \ ' \*- c (char): 0 ''\\0\{1,3}''', \ ' \*- fffff (float): 0', \ ' \*+ another_test (AnotherTest):\( {...}\)\?', - \ '+ Scope: Registers', \ ], \ getbufline( winbufnr( g:vimspector_session_windows.variables ), \ 1,