Initial ptvsd commit

This commit is contained in:
Pavel Minaev 2017-12-12 12:32:17 -08:00
parent 9c7077b6fe
commit 3543b25329
12 changed files with 1062 additions and 1 deletions

View file

@ -1,5 +1,6 @@
# Python Tools for Visual Studio debug server
# Contributing
## Contributing
This project welcomes contributions and suggestions. Most contributions require you to agree to a
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us

5
ptvsd/__init__.py Normal file
View file

@ -0,0 +1,5 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See LICENSE in the project root for license information.
__author__ = "Microsoft Corporation <ptvshelp@microsoft.com>"
__version__ = "4.0.0a1"

10
ptvsd/__main__.py Normal file
View file

@ -0,0 +1,10 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See LICENSE in the project root for license information.
__author__ = "Microsoft Corporation <ptvshelp@microsoft.com>"
__version__ = "4.0.0a1"
if __name__ == '__main__':
import ptvsd.wrapper
import pydevd
pydevd.main()

14
ptvsd/debugger.py Normal file
View file

@ -0,0 +1,14 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See LICENSE in the project root for license information.
__author__ = "Microsoft Corporation <ptvshelp@microsoft.com>"
__version__ = "4.0.0a1"
DONT_DEBUG = []
def debug(filename, port_num, debug_id, debug_options, run_as):
import sys
import ptvsd.wrapper
import pydevd
sys.argv[1:0] = ['--port', str(port_num), '--client', '127.0.0.1', '--file', filename]
pydevd.main()

136
ptvsd/futures.py Normal file
View file

@ -0,0 +1,136 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See LICENSE in the project root for license information.
from __future__ import print_function, with_statement, absolute_import
__author__ = "Microsoft Corporation <ptvshelp@microsoft.com>"
__version__ = "4.0.0a1"
import sys
import threading
from ptvsd.reraise import reraise
class Future(object):
def __init__(self, loop):
self._lock = threading.Lock()
self._loop = loop
self._done = False
self._observed = False
self._done_callbacks = []
def __del__(self):
with self._lock:
if self._done and self._exc_info and not self._observed:
print('Unobserved exception in a Future:', file=sys.__stderr__)
traceback.print_exception(self._exc_info[0], self._exc_info[1], self._exc_info[2], file=sys.__stderr__)
def result(self):
with self._lock:
self._observed = True
if self._exc_info:
reraise(self._exc_info)
return self._result
def exc_info(self):
with self._lock:
self._observed = True
return self._exc_info
def set_result(self, result):
with self._lock:
self._result = result
self._exc_info = None
self._done = True
callbacks = list(self._done_callbacks)
def invoke_callbacks():
for cb in callbacks:
cb(self)
self._loop.call_soon(invoke_callbacks)
def set_exc_info(self, exc_info):
with self._lock:
self._exc_info = exc_info
self._done = True
callbacks = list(self._done_callbacks)
def invoke_callbacks():
for cb in callbacks:
cb(self)
self._loop.call_soon(invoke_callbacks)
def add_done_callback(self, callback):
with self._lock:
done = self._done
self._done_callbacks.append(callback)
if done:
callback(self)
def remove_done_callback(self, callback):
with self._lock:
self._done_callbacks.remove(callback)
class EventLoop(object):
def __init__(self):
self._queue = []
self._lock = threading.Lock()
self._event = threading.Event()
self._event.set()
def create_future(self):
return Future(self)
def run_forever(self):
while True:
self._event.wait()
with self._lock:
queue = self._queue
self._queue = []
self._event.clear()
for (f, args) in queue:
f(*args)
def call_soon(self, f, *args):
with self._lock:
self._queue.append((f, args))
self._event.set()
def call_soon_threadsafe(self, f, *args):
return self.call_soon(f, *args)
class Result(object):
__slots__ = ['value']
def __init__(self, value):
self.value = value
def async(f):
def g(self, loop, *args, **kwargs):
it = f(self, *args, **kwargs)
result = Future(loop)
if it is None:
result.set_result(None)
return result
def callback(fut):
try:
if fut is None:
x = next(it)
else:
exc_info = fut.exc_info()
if exc_info:
x = it.throw(exc_info[0], exc_info[1], exc_info[2])
else:
x = it.send(fut.result())
except StopIteration:
result.set_result(None)
except BaseException as ex:
result.set_exc_info(sys.exc_info())
else:
if isinstance(x, Result):
result.set_result(x.value)
else:
x.add_done_callback(callback)
callback(None)
return result
return g

294
ptvsd/ipcjson.py Normal file
View file

@ -0,0 +1,294 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See LICENSE in the project root for license information.
from __future__ import print_function, with_statement, absolute_import
__author__ = "Microsoft Corporation <ptvshelp@microsoft.com>"
__version__ = "4.0.0a1"
# This module MUST NOT import threading in global scope. This is because in a direct (non-ptvsd)
# attach scenario, it is loaded on the injected debugger attach thread, and if threading module
# hasn't been loaded already, it will assume that the thread on which it is being loaded is the
# main thread. This will cause issues when the thread goes away after attach completes.
import json
import os.path
import itertools
import socket
import sys
import traceback
_TRACE = None
if sys.version_info[0] >= 3:
from encodings import ascii
def to_bytes(cmd_str):
return ascii.Codec.encode(cmd_str)[0]
else:
def to_bytes(cmd_str):
return cmd_str
def _str_or_call(m):
try:
callable = m.__call__
except AttributeError:
return str(m)
else:
return str(callable())
def _trace(*msg):
if _TRACE:
_TRACE(''.join(_str_or_call(m) for m in msg) + '\n')
SKIP_TB_PREFIXES = [
os.path.normcase(os.path.dirname(os.path.abspath(__file__)))
]
class InvalidHeaderError(Exception): pass
class InvalidContentError(Exception): pass
class SocketIO(object):
def __init__(self, *args, **kwargs):
super(SocketIO, self).__init__(*args, **kwargs)
self.__buffer = to_bytes('')
self.__port = kwargs.get('port')
self.__socket = kwargs.get('socket')
self.__own_socket = kwargs.get('own_socket', True)
self.__logfile = kwargs.get('logfile')
if self.__socket is None and self.__port is None:
raise ValueError("A 'port' or a 'socket' must be passed to SocketIO initializer as a keyword argument.")
if self.__socket is None:
self.__socket = socket.create_connection(('127.0.0.1', self.__port))
def _send(self, **payload):
content = json.dumps(payload).encode('utf-8')
headers = ('Content-Length: %d\r\n\r\n' % (len(content), )).encode('ascii')
if self.__logfile is not None:
self.__logfile.write(content)
self.__logfile.write('\n'.encode('utf-8'))
self.__logfile.flush()
self.__socket.send(headers)
self.__socket.send(content)
def _buffered_read_line_as_ascii(self):
'''
Reads bytes until it encounters newline chars, and returns the bytes
ascii decoded, newline chars are excluded from the return value.
Blocks until: newline chars are read OR socket is closed.
'''
newline = '\r\n'.encode('ascii')
while newline not in self.__buffer:
temp = self.__socket.recv(1024)
if not temp:
break
self.__buffer += temp
if not self.__buffer:
return None
try:
index = self.__buffer.index(newline)
except ValueError:
raise InvalidHeaderError('Header line not terminated')
line = self.__buffer[:index]
self.__buffer = self.__buffer[index+len(newline):]
return line.decode('ascii', 'replace')
def _buffered_read_as_utf8(self, length):
while len(self.__buffer) < length:
temp = self.__socket.recv(1024)
if not temp:
break
self.__buffer += temp
if len(self.__buffer) < length:
raise InvalidContentError('Expected to read {0} bytes of content, but only read {1} bytes.'.format(length, len(self.__buffer)))
content = self.__buffer[:length]
self.__buffer = self.__buffer[length:]
return content.decode('utf-8', 'replace')
def _wait_for_message(self):
# base protocol defined at https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md#base-protocol
# read all headers, ascii encoded separated by '\r\n'
# end of headers is indicated by an empty line
headers = {}
line = self._buffered_read_line_as_ascii()
while line:
parts = line.split(':')
if len(parts) == 2:
headers[parts[0]] = parts[1]
else:
raise InvalidHeaderError("Malformed header, expected 'name: value'\n{0}".format(line))
line = self._buffered_read_line_as_ascii()
# end of stream
if not line and not headers:
return
# validate headers
try:
length_text = headers['Content-Length']
try:
length = int(length_text)
except ValueError:
raise InvalidHeaderError("Invalid Content-Length: {0}".format(length_text))
except NameError:
raise InvalidHeaderError('Content-Length not specified in headers')
except KeyError:
raise InvalidHeaderError('Content-Length not specified in headers')
if length < 0 or length > 2147483647:
raise InvalidHeaderError("Invalid Content-Length: {0}".format(length))
# read content, utf-8 encoded
content = self._buffered_read_as_utf8(length)
try:
msg = json.loads(content)
self._receive_message(msg)
except ValueError:
raise InvalidContentError('Error deserializing message content.')
except json.decoder.JSONDecodeError:
raise InvalidContentError('Error deserializing message content.')
def _close(self):
if self.__own_socket:
self.__socket.close()
'''
class StandardIO(object):
def __init__(self, stdin, stdout, *args, **kwargs):
super(StandardIO, self).__init__(*args, **kwargs)
try:
self.__stdin = stdin.buffer
self.__stdout = stdout.buffer
except AttributeError:
self.__stdin = stdin
self.__stdout = stdout
def _send(self, **payload):
data = json.dumps(payload).encode('utf-8') + NEWLINE_BYTES
self.__stdout.write(data)
self.__stdout.flush()
def _wait_for_message(self):
msg = json.loads(self.__stdin.readline().decode('utf-8', 'replace').rstrip())
self._receive_message(msg)
def _close(self):
pass
'''
class IpcChannel(object):
def __init__(self, *args, **kwargs):
# This class is meant to be last in the list of base classes
# Don't call super because object's __init__ doesn't take arguments
try:
import thread
except:
import _thread as thread
self.__seq = itertools.count()
self.__exit = False
self.__lock = thread.allocate_lock()
self.__message = []
self.__exit_on_unknown_command = True
def close(self):
self._close()
def send_event(self, _name, **kwargs):
with self.__lock:
self._send(
type='event',
seq=next(self.__seq),
event=_name,
body=kwargs,
)
def send_response(self, request, success=True, message=None, **kwargs):
with self.__lock:
self._send(
type='response',
seq=next(self.__seq),
request_seq=int(request.get('seq', 0)),
success=success,
command=request.get('command', ''),
message=message or '',
body=kwargs,
)
def set_exit(self):
self.__exit = True
def process_messages(self):
while True:
if self.process_one_message():
return
def process_one_message(self):
try:
msg = self.__message.pop(0)
except IndexError:
self._wait_for_message()
try:
msg = self.__message.pop(0)
except IndexError:
return self.__exit
_trace('Received ', msg)
try:
if msg['type'] == 'request':
self.on_request(msg)
elif msg['type'] == 'response':
self.on_response(msg)
elif msg['type'] == 'event':
self.on_event(msg)
else:
self.on_invalid_request(msg, {})
except AssertionError:
raise
except Exception:
_trace('Error ', traceback.format_exc)
traceback.print_exc()
_trace('self.__exit is ', self.__exit)
return self.__exit
def on_request(self, request):
assert request.get('type', '') == 'request', "Only handle 'request' messages in on_request"
cmd = request.get('command', '')
args = request.get('arguments', {})
target = getattr(self, 'on_' + cmd, self.on_invalid_request)
try:
_trace('Calling ', repr(target))
target(request, args)
except AssertionError:
raise
except Exception:
self.send_response(
request,
success=False,
message=traceback.format_exc(),
)
def on_response(self, msg):
# this class is only used for server side only for now
raise NotImplementedError
def on_event(self, msg):
# this class is only used for server side only for now
raise NotImplementedError
def on_invalid_request(self, request, args):
self.send_response(request, success=False, message='Unknown command')
if self.__exit_on_unknown_command:
self.__exit = True
def _receive_message(self, message):
with self.__lock:
self.__message.append(message)

14
ptvsd/reraise.py Normal file
View file

@ -0,0 +1,14 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See LICENSE in the project root for license information.
from __future__ import print_function, with_statement, absolute_import
__author__ = "Microsoft Corporation <ptvshelp@microsoft.com>"
__version__ = "4.0.0a1"
import sys
if sys.version_info >= (3,):
from ptvsd.reraise3 import reraise
else:
from ptvsd.reraise2 import reraise

10
ptvsd/reraise2.py Normal file
View file

@ -0,0 +1,10 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See LICENSE in the project root for license information.
from __future__ import print_function, with_statement, absolute_import
__author__ = "Microsoft Corporation <ptvshelp@microsoft.com>"
__version__ = "4.0.0a1"
def reraise(exc_info):
raise exc_info[0], exc_info[1], exc_info[2]

10
ptvsd/reraise3.py Normal file
View file

@ -0,0 +1,10 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See LICENSE in the project root for license information.
from __future__ import print_function, with_statement, absolute_import
__author__ = "Microsoft Corporation <ptvshelp@microsoft.com>"
__version__ = "4.0.0a1"
def reraise(exc_info):
raise exc_info[1].with_traceback(exc_info[2])

541
ptvsd/wrapper.py Normal file
View file

@ -0,0 +1,541 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See LICENSE in the project root for license information.
from __future__ import print_function, with_statement, absolute_import
__author__ = "Microsoft Corporation <ptvshelp@microsoft.com>"
__version__ = "4.0.0a1"
import io
import os
import socket
import sys
import time
import threading
import traceback
import untangle
try:
from urllib import quote_plus, unquote, unquote_plus
except:
from urllib.parse import quote_plus, unquote, unquote_plus
import _pydevd_bundle.pydevd_comm as pydevd_comm
from _pydevd_bundle.pydevd_comm import pydevd_log
import ptvsd.ipcjson as ipcjson
import ptvsd.futures as futures
#def ipcjson_trace(s):
# print(s)
#ipcjson._TRACE = ipcjson_trace
# Generates VSCode entity IDs, and maps them to corresponding pydevd entity IDs.
#
# For VSCode, IDs are always integers, and uniquely identify the entity among all other
# entities of the same type - e.g. all frames across all threads have unique IDs.
#
# For pydevd, IDs can be integer or strings, and are usually specific to some scope -
# for example, a frame ID is only unique within a given thread. To produce a truly unique
# ID, the IDs of all the outer scopes have to be combined into a tuple. Thus, for example,
# a pydevd frame ID is (thread_id, frame_id).
#
# Variables (evaluation results) technically don't have IDs in pydevd, as it doesn't have
# evaluation persistence. However, for a given frame, any child can be identified by the
# path one needs to walk from the root of the frame to get to that child - and that path,
# represented as a sequence of its consituent components, is used by pydevd commands to
# identify the variable. So we use the tuple representation of the same as its pydevd ID.
# For example, for something like foo[1].bar, its ID is:
# (thread_id, frame_id, 'FRAME', 'foo', 1, 'bar')
#
# For pydevd breakpoints, the ID has to be specified by the caller when creating, so we
# can just reuse the ID that was generated for VSC. However, when referencing the pydevd
# breakpoint later (e.g. to remove it), its ID must be specified together with path to
# file in which that breakpoint is set - i.e. pydevd treats those IDs as scoped to a file.
# So, even though breakpoint IDs are unique across files, use (path, bp_id) as pydevd ID.
class IDMap(object):
def __init__(self):
self._vscode_to_pydevd = {}
self._pydevd_to_vscode = {}
self._next_id = 1
self._lock = threading.Lock()
def pairs(self):
with self._lock:
return list(self._pydevd_to_vscode.items())
def add(self, pydevd_id):
with self._lock:
vscode_id = self._next_id
if callable(pydevd_id):
pydevd_id = pydevd_id(vscode_id)
self._next_id += 1
self._vscode_to_pydevd[vscode_id] = pydevd_id
self._pydevd_to_vscode[pydevd_id] = vscode_id
return vscode_id
def remove(self, pydevd_id=None, vscode_id=None):
with self._lock:
if pydevd_id is None:
pydevd_id = self._vscode_to_pydevd[vscode_id]
elif vscode_id is None:
vscode_id = self._pydevd_to_vscode[pydevd_id]
del self._vscode_to_pydevd[vscode_id]
del self._pydevd_to_vscode[pydevd_id]
def to_pydevd(self, vscode_id):
return self._vscode_to_pydevd[vscode_id]
def to_vscode(self, pydevd_id, autogen=True):
try:
return self._pydevd_to_vscode[pydevd_id]
except KeyError:
if autogen:
return self.add(pydevd_id)
else:
raise
def pydevd_ids(self):
with self._lock:
ids = list(self._pydevd_to_vscode.keys())
return ids
def vscode_ids(self):
with self._lock:
ids = list(self._vscode_to_pydevd.keys())
return ids
class ExceptionInfo(object):
def __init__(self, name, description):
self.name = name
self.description = description
# A dummy socket-like object that is given to pydevd in lieu of the real thing.
# It parses pydevd messages and redirects them to the provided handler callback.
# It also provides an interface to send notifications and requests to pydevd;
# for requests, the reply can be asynchronously awaited.
class PydevdSocket(object):
def __init__(self, event_handler):
#self.log = open('pydevd.log', 'w')
self.event_handler = event_handler
self.lock = threading.Lock()
self.seq = 1
self.pipe_r, self.pipe_w = os.pipe()
self.requests = {}
def close(self):
pass
def shutdown(self, mode):
pass
def recv(self, count):
data = os.read(self.pipe_r, count)
#self.log.write('>>>[' + data.decode('utf8') + ']\n\n')
#self.log.flush()
return data
def send(self, data):
result = len(data)
data = unquote(data.decode('utf8'))
#self.log.write('<<<[' + data + ']\n\n')
#self.log.flush()
cmd_id, seq, args = data.split('\t', 2)
cmd_id = int(cmd_id)
seq = int(seq)
with self.lock:
loop, fut = self.requests.pop(seq, (None, None))
if fut is None:
self.event_handler(cmd_id, seq, args)
else:
loop.call_soon_threadsafe(fut.set_result, (cmd_id, seq, args))
return result
def make_packet(self, cmd_id, args):
with self.lock:
seq = self.seq
self.seq += 1
s = "%s\t%s\t%s\n" % (cmd_id, seq, args)
return seq, s
def pydevd_notify(self, cmd_id, args):
seq, s = self.make_packet(cmd_id, args)
os.write(self.pipe_w, s.encode('utf8'))
def pydevd_request(self, loop, cmd_id, args):
seq, s = self.make_packet(cmd_id, args)
fut = loop.create_future()
with self.lock:
self.requests[seq] = loop, fut
os.write(self.pipe_w, s.encode('utf8'))
return fut
# IPC JSON message processor for VSC debugger protocol, mapping it to pydevd protocol.
class VSCodeMessageProcessor(ipcjson.SocketIO, ipcjson.IpcChannel):
def __init__(self, socket, pydevd, logfile=None):
super(VSCodeMessageProcessor, self).__init__(socket=socket, own_socket=False, logfile=logfile)
self.socket = socket
self.pydevd = pydevd
self.stack_traces = {}
self.stack_traces_lock = threading.Lock()
self.active_exceptions = {}
self.active_exceptions_lock = threading.Lock()
self.thread_map = IDMap()
self.frame_map = IDMap()
self.var_map = IDMap()
self.bp_map = IDMap()
self.next_var_ref = 0
self.loop = futures.EventLoop()
threading.Thread(target = self.loop.run_forever, name = 'ptvsd.EventLoop').start()
def close(self):
if self.socket:
self.socket.close()
def pydevd_notify(self, cmd_id, args):
try:
return self.pydevd.pydevd_notify(cmd_id, args)
except:
traceback.print_exc()
raise
def pydevd_request(self, cmd_id, args):
return self.pydevd.pydevd_request(self.loop, cmd_id, args)
# Instances of this class provide decorators to mark methods as handlers for various
# pydevd messages - a decorated method is added to the map with the corresponding
# message ID, and is looked up there by pydevd event handler below.
class EventHandlers(dict):
def handler(self, cmd_id):
def decorate(f):
self[cmd_id] = f
return f
return decorate
pydevd_events = EventHandlers()
def on_pydevd_event(self, cmd_id, seq, args):
try:
f = self.pydevd_events[cmd_id]
except KeyError:
raise Exception('Unsupported pydevd command ' + str(cmd_id))
return f(self, seq, args)
def async_handler(m):
m = futures.async(m)
def f(self, *args, **kwargs):
fut = m(self, self.loop, *args, **kwargs)
def done(fut):
try:
fut.result()
except:
traceback.print_exc(file=sys.__stderr__)
fut.add_done_callback(done)
return f
@async_handler
def on_initialize(self, request, args):
yield self.pydevd_request(pydevd_comm.CMD_VERSION, '1.1\tWINDOWS\tID')
self.send_response(request,
supportsExceptionInfoRequest=True,
supportsConfigurationDoneRequest=True,
exceptionBreakpointFilters=[
{'filter': 'raised', 'label': 'Raised Exceptions'},
{'filter': 'uncaught', 'label': 'Uncaught Exceptions'},
]
)
self.send_event('initialized')
def on_configurationDone(self, request, args):
self.send_response(request)
def on_disconnect(self, request, args):
self.send_response(request)
@async_handler
def on_attach(self, request, args):
self.send_response(request)
yield self.pydevd_request(pydevd_comm.CMD_RUN, '')
self.send_process_event('attach')
@async_handler
def on_launch(self, request, args):
self.send_response(request)
yield self.pydevd_request(pydevd_comm.CMD_RUN, '')
self.send_process_event('launch')
def send_process_event(self, start_method):
evt = {
'name': sys.argv[0],
'systemProcessId': os.getpid(),
'isLocalProcess': True,
'startMethod': start_method,
}
self.send_event('process', **evt)
@async_handler
def on_threads(self, request, args):
_, _, args = yield self.pydevd_request(pydevd_comm.CMD_LIST_THREADS, '')
xml = untangle.parse(args).xml
try:
xthreads = xml.thread
except AttributeError:
xthreads = []
threads = []
for xthread in xthreads:
tid = self.thread_map.to_vscode(xthread['id'])
try:
name = unquote(xthread['name'])
except KeyError:
name = None
if not (name and name.startswith('pydevd.')):
threads.append({'id': tid, 'name': name})
self.send_response(request, threads=threads)
@async_handler
def on_stackTrace(self, request, args):
tid = int(args['threadId'])
startFrame = int(args['startFrame'])
levels = int(args['levels'])
tid = self.thread_map.to_pydevd(tid)
with self.stack_traces_lock:
xframes = self.stack_traces[tid]
totalFrames = len(xframes)
if levels == 0:
levels = totalFrames
stackFrames = []
for xframe in xframes:
if startFrame > 0:
startFrame -= 1
continue
if levels <= 0:
break
levels -= 1
fid = self.frame_map.to_vscode((tid, int(xframe['id'])))
name = unquote(xframe['name'])
file = unquote(xframe['file'])
line = int(xframe['line'])
stackFrames.append({'id': fid, 'name': name, 'source': {'path': file}, 'line': line, 'column': 0})
self.send_response(request, stackFrames=stackFrames, totalFrames=totalFrames)
@async_handler
def on_scopes(self, request, args):
vsc_fid = int(args['frameId'])
pyd_tid, pyd_fid = self.frame_map.to_pydevd(vsc_fid)
pyd_var = (pyd_tid, pyd_fid, 'FRAME')
vsc_var = self.var_map.to_vscode(pyd_var)
scope = {'name': 'Locals', 'expensive': False, 'variablesReference': vsc_var}
self.send_response(request, scopes=[scope])
@async_handler
def on_variables(self, request, args):
vsc_var = int(args['variablesReference'])
pyd_var = self.var_map.to_pydevd(vsc_var)
if len(pyd_var) == 3:
cmd = pydevd_comm.CMD_GET_FRAME
else:
cmd = pydevd_comm.CMD_GET_VARIABLE
_, _, args = yield self.pydevd_request(cmd, '\t'.join(str(s) for s in pyd_var))
xml = untangle.parse(args).xml
try:
xvars = xml.var
except AttributeError:
xvars = []
vars = []
for xvar in xvars:
var = {
'name': unquote(xvar['name']),
'type': unquote(xvar['type']),
'value': unquote(xvar['value']),
}
if bool(xvar['isContainer']):
pyd_child = pyd_var + (var['name'],)
var['variablesReference'] = self.var_map.to_vscode(pyd_child)
vars.append(var)
self.send_response(request, variables=vars)
@async_handler
def on_pause(self, request, args):
vsc_tid = int(args['threadId'])
if vsc_tid == 0: # VS does this to mean "stop all threads":
for pyd_tid in self.thread_map.pydevd_ids():
self.pydevd_notify(pydevd_comm.CMD_THREAD_SUSPEND, pyd_tid)
else:
pyd_tid = self.thread_map.to_pydevd(vsc_tid)
self.pydevd_notify(pydevd_comm.CMD_THREAD_SUSPEND, pyd_tid)
self.send_response(request)
@async_handler
def on_continue(self, request, args):
tid = self.thread_map.to_pydevd(int(args['threadId']))
self.pydevd_notify(pydevd_comm.CMD_THREAD_RUN, tid)
self.send_response(request)
@async_handler
def on_next(self, request, args):
tid = self.thread_map.to_pydevd(int(args['threadId']))
self.pydevd_notify(pydevd_comm.CMD_STEP_OVER, tid)
self.send_response(request)
@async_handler
def on_stepIn(self, request, args):
tid = self.thread_map.to_pydevd(int(args['threadId']))
self.pydevd_notify(pydevd_comm.CMD_STEP_INTO, tid)
self.send_response(request)
@async_handler
def on_stepOut(self, request, args):
tid = self.thread_map.to_pydevd(int(args['threadId']))
self.pydevd_notify(pydevd_comm.CMD_STEP_RETURN, tid)
self.send_response(request)
@async_handler
def on_setBreakpoints(self, request, args):
bps = []
path = args['source']['path']
src_bps = args.get('breakpoints', [])
# First, we must delete all existing breakpoints in that source.
for pyd_bpid, vsc_bpid in self.bp_map.pairs():
self.pydevd_notify(pydevd_comm.CMD_REMOVE_BREAK, 'python-line\t%s\t%s' % (path, vsc_bpid))
self.bp_map.remove(pyd_bpid, vsc_bpid)
for src_bp in src_bps:
line = src_bp['line']
vsc_bpid = self.bp_map.add(lambda vsc_bpid: (path, vsc_bpid))
self.pydevd_notify(pydevd_comm.CMD_SET_BREAK, '%s\tpython-line\t%s\t%s\tNone\tNone\tNone' %
(vsc_bpid, path, line))
bps.append({ 'id': vsc_bpid, 'verified': True, 'line': line })
self.send_response(request, breakpoints=bps)
@async_handler
def on_setExceptionBreakpoints(self, request, args):
self.pydevd_notify(pydevd_comm.CMD_REMOVE_EXCEPTION_BREAK, 'python-BaseException')
filters = args['filters']
break_raised = 'raised' in filters
break_uncaught = 'uncaught' in filters
if break_raised or break_uncaught:
self.pydevd_notify(pydevd_comm.CMD_ADD_EXCEPTION_BREAK, 'python-BaseException\t%s\t%s\t%s' % (
2 if break_raised else 0, 1 if break_uncaught else 0, 0))
self.send_response(request)
@async_handler
def on_exceptionInfo(self, request, args):
tid = self.thread_map.to_pydevd(args['threadId'])
with self.active_exceptions_lock:
exc = self.active_exceptions[tid]
self.send_response(request, exceptionId=exc.name, description=exc.description, breakMode='unhandled', details={
'typeName': exc.name,
'message': exc.description,
})
@pydevd_events.handler(pydevd_comm.CMD_THREAD_CREATE)
def on_pydevd_thread_create(self, seq, args):
xml = untangle.parse(args).xml
tid = self.thread_map.to_vscode(xml.thread['id'])
self.send_event('thread', reason='started', threadId=tid)
@pydevd_events.handler(pydevd_comm.CMD_THREAD_KILL)
def on_pydevd_thread_kill(self, seq, args):
try:
tid = self.thread_map.to_vscode(args, autogen=False)
except KeyError:
pass
else:
self.send_event('thread', reason='exited', threadId=tid)
@pydevd_events.handler(pydevd_comm.CMD_THREAD_SUSPEND)
def on_pydevd_thread_suspend(self, seq, args):
xml = untangle.parse(args).xml
tid = xml.thread['id']
reason = int(xml.thread['stop_reason'])
print(reason, file=open('reason.log', 'a'))
if reason in (pydevd_comm.CMD_STEP_INTO, pydevd_comm.CMD_STEP_OVER, pydevd_comm.CMD_STEP_RETURN):
reason = 'step'
elif reason == pydevd_comm.CMD_STEP_CAUGHT_EXCEPTION:
reason = 'exception'
elif reason == pydevd_comm.CMD_SET_BREAK:
reason = 'breakpoint'
else:
reason = 'pause'
with self.stack_traces_lock:
self.stack_traces[tid] = xml.thread.frame
tid = self.thread_map.to_vscode(tid)
self.send_event('stopped', reason=reason, threadId=tid)
@pydevd_events.handler(pydevd_comm.CMD_THREAD_RUN)
def on_pydevd_thread_run(self, seq, args):
pyd_tid, reason = args.split('\t')
vsc_tid = self.thread_map.to_vscode(pyd_tid)
# Stack trace, and all frames and variables for this thread are now invalid; clear their IDs.
with self.stack_traces_lock:
del self.stack_traces[pyd_tid]
for pyd_fid, vsc_fid in self.frame_map.pairs():
if pyd_fid[0] == pyd_tid:
self.frame_map.remove(pyd_fid, vsc_fid)
for pyd_var, vsc_var in self.var_map.pairs():
if pyd_var[0] == pyd_tid:
self.var_map.remove(pyd_var, vsc_var)
self.send_event('continued', threadId=vsc_tid)
@pydevd_events.handler(pydevd_comm.CMD_SEND_CURR_EXCEPTION_TRACE)
def on_pydevd_send_curr_exception_trace(self, seq, args):
_, name, description, xml = args.split('\t')
xml = untangle.parse(xml).xml
pyd_tid = xml.thread['id']
with self.active_exceptions_lock:
self.active_exceptions[pyd_tid] = ExceptionInfo(name, description)
@pydevd_events.handler(pydevd_comm.CMD_SEND_CURR_EXCEPTION_TRACE_PROCEEDED)
def on_pydevd_send_curr_exception_trace_proceeded(self, seq, args):
pass
def start_server(port):
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind(('127.0.0.1', port))
server.listen(1)
client, addr = server.accept()
pydevd = PydevdSocket(lambda *args: proc.on_pydevd_event(*args))
proc = VSCodeMessageProcessor(client, pydevd)
server_thread = threading.Thread(target = proc.process_messages, name = 'ptvsd.Server')
server_thread.start()
return pydevd
def start_client(host, port):
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP)
client.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
client.connect((host, port))
pydevd = PydevdSocket(lambda *args: proc.on_pydevd_event(*args))
proc = VSCodeMessageProcessor(client, pydevd)
server_thread = threading.Thread(target = proc.process_messages, name = 'ptvsd.Client')
server_thread.start()
return pydevd
# These are the functions pydevd invokes to get a socket to the client.
pydevd_comm.start_server = start_server
pydevd_comm.start_client = start_client

2
setup.cfg Normal file
View file

@ -0,0 +1,2 @@
[wheel]
universal = 1

24
setup.py Normal file
View file

@ -0,0 +1,24 @@
#!/usr/bin/env python
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See LICENSE in the project root for license information.
import sys
from setuptools import setup
setup(name='ptvsd',
version='4.0.0a1',
description='Visual Studio remote debugging server for Python',
license='MIT',
author='Microsoft Corporation',
author_email='ptvshelp@microsoft.com',
url='https://aka.ms/ptvs',
classifiers=[
'Development Status :: 3 - Alpha',
'Programming Language :: Python',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 3',
'License :: OSI Approved :: MIT License'],
packages=['ptvsd'],
install_requires=['untangle', 'pydevd>=1.1.1']
)