diff --git a/python3/vimspector/code.py b/python3/vimspector/code.py index 37fe23b..635a12f 100644 --- a/python3/vimspector/code.py +++ b/python3/vimspector/code.py @@ -296,11 +296,18 @@ class CodeView( object ): def ShowMemory( self, memoryReference, 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 ): - utils.SetBufferContents( - buf, - msg.get( 'body', {} ).get( 'data', 'Cannot read memory' ) ) + # 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, utils.Base64ToHexDump( data ) ) + + utils.JumpToWindow( self._window ) + utils.OpenFileInCurrentWindow( buf_name ) diff --git a/python3/vimspector/debug_session.py b/python3/vimspector/debug_session.py index 3fc983a..bd93967 100644 --- a/python3/vimspector/debug_session.py +++ b/python3/vimspector/debug_session.py @@ -550,13 +550,13 @@ class DebugSession( object ): return def handler( msg ): - self._codeView.ShowMemory( msg ) + self._codeView.ShowMemory( memoryReference, msg ) self._connection.DoRequest( handler, { 'command': 'readMemory', 'arguments': { 'memoryReference': memoryReference, - 'count': 10 # TODO: What + 'count': 1024 # TODO: What } } ) 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/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