Sprinter: добавить отладку C-исходников и интеграцию VS Code
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
#!/usr/bin/env python3
|
||||
"""stdio DAP-адаптер Sprinter MAME; stdout содержит только DAP frames."""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
import select
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import threading
|
||||
import uuid
|
||||
|
||||
from sdbg.dap import DapEngine
|
||||
from sdbg.session import SessionError
|
||||
|
||||
|
||||
class Protocol:
|
||||
def __init__(self):
|
||||
self.engine = DapEngine()
|
||||
self.sequence = 1
|
||||
self.write_lock = threading.Lock()
|
||||
self.stopping = threading.Event()
|
||||
self.poller = None
|
||||
self.launcher = None
|
||||
self.launcher_log = None
|
||||
|
||||
def send(self, message):
|
||||
with self.write_lock:
|
||||
message['seq'] = self.sequence
|
||||
self.sequence += 1
|
||||
data = json.dumps(message, ensure_ascii=False, separators=(',', ':')).encode()
|
||||
sys.stdout.buffer.write(f'Content-Length: {len(data)}\r\n\r\n'.encode() + data)
|
||||
sys.stdout.buffer.flush()
|
||||
|
||||
def event(self, name, body):
|
||||
self.send({'type': 'event', 'event': name, 'body': body})
|
||||
|
||||
def _poll(self):
|
||||
while not self.stopping.is_set():
|
||||
try:
|
||||
events, closed = self.engine.poll_events(1)
|
||||
for name, body in events:
|
||||
self.event(name, body)
|
||||
if closed:
|
||||
return
|
||||
except (SessionError, OSError, ValueError) as error:
|
||||
self.event('output', {'category': 'stderr', 'output': 'sdbg: ' + str(error) + '\n'})
|
||||
self.event('terminated', {'restart': False})
|
||||
return
|
||||
|
||||
def request(self, request):
|
||||
response = {'type': 'response', 'request_seq': request.get('seq', 0),
|
||||
'command': request.get('command', ''), 'success': True}
|
||||
events = []
|
||||
try:
|
||||
if request['command'] == 'launch':
|
||||
body, events = self.launch(request.get('arguments') or {})
|
||||
else:
|
||||
body, events = self.engine.handle(request['command'], request.get('arguments') or {})
|
||||
response['body'] = body
|
||||
except (SessionError, OSError, ValueError, KeyError, TypeError) as error:
|
||||
response.update(success=False, message=str(error))
|
||||
self.send(response)
|
||||
for name, body in events:
|
||||
self.event(name, body)
|
||||
if request.get('command') in ('attach','launch') and response['success'] and self.poller is None:
|
||||
self.poller = threading.Thread(target=self._poll, name='dap-events', daemon=True)
|
||||
self.poller.start()
|
||||
if request.get('command') == 'disconnect':
|
||||
self.stopping.set()
|
||||
self.stop_launcher()
|
||||
|
||||
def stop_launcher(self):
|
||||
if self.launcher is not None and self.launcher.poll() is None:
|
||||
self.launcher.terminate()
|
||||
if self.launcher_log is not None:
|
||||
self.launcher_log.close()
|
||||
self.launcher_log = None
|
||||
|
||||
def launcher_diagnostics(self):
|
||||
if self.launcher_log is None:
|
||||
return ''
|
||||
self.launcher_log.flush()
|
||||
self.launcher_log.seek(0)
|
||||
data = self.launcher_log.read()[-4000:].strip()
|
||||
return '\n' + data if data else ''
|
||||
|
||||
def launch(self, arguments):
|
||||
build = arguments.get('build')
|
||||
if not isinstance(build, str) or not build:
|
||||
raise SessionError('В launch требуется build с debug-пакетом')
|
||||
socket_path = arguments.get('socket') or '/tmp/sprinter-sdbg-' + uuid.uuid4().hex + '.sock'
|
||||
command = [sys.executable, str(Path(__file__).with_name('sdbg_launcher.py')),
|
||||
'--build', build, '--socket', socket_path]
|
||||
if arguments.get('mame'):
|
||||
command.extend(['--mame', arguments['mame']])
|
||||
if arguments.get('debugger'):
|
||||
command.extend(['--debugger', arguments['debugger']])
|
||||
if arguments.get('launchAt') is not None:
|
||||
command.extend(['--launch-at', str(arguments['launchAt'])])
|
||||
if arguments.get('dssTimeout') is not None:
|
||||
command.extend(['--dss-timeout', str(arguments['dssTimeout'])])
|
||||
for filename in arguments.get('data', []):
|
||||
command.extend(['--data', filename])
|
||||
self.launcher_log = tempfile.TemporaryFile(mode='w+t', encoding='utf-8')
|
||||
try:
|
||||
self.launcher = subprocess.Popen(command, stdout=subprocess.PIPE,
|
||||
stderr=self.launcher_log, text=True)
|
||||
except OSError:
|
||||
self.launcher_log.close()
|
||||
self.launcher_log = None
|
||||
raise
|
||||
try:
|
||||
readable, _, _ = select.select([self.launcher.stdout], [], [], 75)
|
||||
if not readable:
|
||||
raise SessionError('Launcher не дошёл до main за 75 секунд' +
|
||||
self.launcher_diagnostics())
|
||||
line = self.launcher.stdout.readline()
|
||||
if not line:
|
||||
code = self.launcher.wait(timeout=5)
|
||||
raise SessionError(f'Launcher завершился до готовности, rc={code}' +
|
||||
self.launcher_diagnostics())
|
||||
ready = json.loads(line)
|
||||
if not ready.get('ready'):
|
||||
raise SessionError('Launcher вернул неверный ready')
|
||||
body, events = self.engine.handle('attach', {'socket': ready['socket']})
|
||||
body['entry'] = ready['entry']
|
||||
body['mamePid'] = ready['pid']
|
||||
body['socket'] = ready['socket']
|
||||
return body, events
|
||||
except BaseException:
|
||||
self.stop_launcher()
|
||||
raise
|
||||
|
||||
def run(self):
|
||||
stream = sys.stdin.buffer
|
||||
while not self.stopping.is_set():
|
||||
headers = {}
|
||||
while True:
|
||||
line = stream.readline()
|
||||
if not line:
|
||||
self.stopping.set()
|
||||
return
|
||||
if line in (b'\r\n', b'\n'):
|
||||
break
|
||||
name, value = line.decode('ascii').split(':', 1)
|
||||
headers[name.lower()] = value.strip()
|
||||
length = int(headers['content-length'])
|
||||
if length > 1_048_576:
|
||||
raise ValueError('DAP frame слишком велик')
|
||||
request = json.loads(stream.read(length))
|
||||
if request.get('type') == 'request':
|
||||
self.request(request)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
Protocol().run()
|
||||
except (ValueError, OSError, KeyError) as error:
|
||||
print('sdbg-dap: ' + str(error), file=sys.stderr)
|
||||
sys.exit(1)
|
||||
Reference in New Issue
Block a user