xref: /llvm-project/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py (revision 11a4d43f4a8ed02694de4d39b5591ff2b1c31803)
1#!/usr/bin/env python
2
3import binascii
4import json
5import optparse
6import os
7import pprint
8import socket
9import string
10import subprocess
11import sys
12import threading
13import time
14
15
16def dump_memory(base_addr, data, num_per_line, outfile):
17    data_len = len(data)
18    hex_string = binascii.hexlify(data)
19    addr = base_addr
20    ascii_str = ""
21    i = 0
22    while i < data_len:
23        outfile.write("0x%8.8x: " % (addr + i))
24        bytes_left = data_len - i
25        if bytes_left >= num_per_line:
26            curr_data_len = num_per_line
27        else:
28            curr_data_len = bytes_left
29        hex_start_idx = i * 2
30        hex_end_idx = hex_start_idx + curr_data_len * 2
31        curr_hex_str = hex_string[hex_start_idx:hex_end_idx]
32        # 'curr_hex_str' now contains the hex byte string for the
33        # current line with no spaces between bytes
34        t = iter(curr_hex_str)
35        # Print hex bytes separated by space
36        outfile.write(" ".join(a + b for a, b in zip(t, t)))
37        # Print two spaces
38        outfile.write("  ")
39        # Calculate ASCII string for bytes into 'ascii_str'
40        ascii_str = ""
41        for j in range(i, i + curr_data_len):
42            ch = data[j]
43            if ch in string.printable and ch not in string.whitespace:
44                ascii_str += "%c" % (ch)
45            else:
46                ascii_str += "."
47        # Print ASCII representation and newline
48        outfile.write(ascii_str)
49        i = i + curr_data_len
50        outfile.write("\n")
51
52
53def read_packet(f, verbose=False, trace_file=None):
54    """Decode a JSON packet that starts with the content length and is
55    followed by the JSON bytes from a file 'f'. Returns None on EOF.
56    """
57    line = f.readline().decode("utf-8")
58    if len(line) == 0:
59        return None  # EOF.
60
61    # Watch for line that starts with the prefix
62    prefix = "Content-Length: "
63    if line.startswith(prefix):
64        # Decode length of JSON bytes
65        if verbose:
66            print('content: "%s"' % (line))
67        length = int(line[len(prefix) :])
68        if verbose:
69            print('length: "%u"' % (length))
70        # Skip empty line
71        line = f.readline()
72        if verbose:
73            print('empty: "%s"' % (line))
74        # Read JSON bytes
75        json_str = f.read(length)
76        if verbose:
77            print('json: "%s"' % (json_str))
78        if trace_file:
79            trace_file.write("from adaptor:\n%s\n" % (json_str))
80        # Decode the JSON bytes into a python dictionary
81        return json.loads(json_str)
82
83    raise Exception("unexpected malformed message from lldb-dap: " + line)
84
85
86def packet_type_is(packet, packet_type):
87    return "type" in packet and packet["type"] == packet_type
88
89
90def dump_dap_log(log_file):
91    print("========= DEBUG ADAPTER PROTOCOL LOGS =========")
92    if log_file is None:
93        print("no log file available")
94    else:
95        with open(log_file, "r") as file:
96            print(file.read())
97    print("========= END =========")
98
99
100def read_packet_thread(vs_comm, log_file):
101    done = False
102    try:
103        while not done:
104            packet = read_packet(vs_comm.recv, trace_file=vs_comm.trace_file)
105            # `packet` will be `None` on EOF. We want to pass it down to
106            # handle_recv_packet anyway so the main thread can handle unexpected
107            # termination of lldb-dap and stop waiting for new packets.
108            done = not vs_comm.handle_recv_packet(packet)
109    finally:
110        dump_dap_log(log_file)
111
112
113class DebugCommunication(object):
114    def __init__(self, recv, send, init_commands, log_file=None):
115        self.trace_file = None
116        self.send = send
117        self.recv = recv
118        self.recv_packets = []
119        self.recv_condition = threading.Condition()
120        self.recv_thread = threading.Thread(
121            target=read_packet_thread, args=(self, log_file)
122        )
123        self.process_event_body = None
124        self.exit_status = None
125        self.initialize_body = None
126        self.thread_stop_reasons = {}
127        self.breakpoint_events = []
128        self.progress_events = []
129        self.reverse_requests = []
130        self.sequence = 1
131        self.threads = None
132        self.recv_thread.start()
133        self.output_condition = threading.Condition()
134        self.output = {}
135        self.configuration_done_sent = False
136        self.frame_scopes = {}
137        self.init_commands = init_commands
138        self.disassembled_instructions = {}
139
140    @classmethod
141    def encode_content(cls, s):
142        return ("Content-Length: %u\r\n\r\n%s" % (len(s), s)).encode("utf-8")
143
144    @classmethod
145    def validate_response(cls, command, response):
146        if command["command"] != response["command"]:
147            raise ValueError("command mismatch in response")
148        if command["seq"] != response["request_seq"]:
149            raise ValueError("seq mismatch in response")
150
151    def get_modules(self):
152        module_list = self.request_modules()["body"]["modules"]
153        modules = {}
154        for module in module_list:
155            modules[module["name"]] = module
156        return modules
157
158    def get_output(self, category, timeout=0.0, clear=True):
159        self.output_condition.acquire()
160        output = None
161        if category in self.output:
162            output = self.output[category]
163            if clear:
164                del self.output[category]
165        elif timeout != 0.0:
166            self.output_condition.wait(timeout)
167            if category in self.output:
168                output = self.output[category]
169                if clear:
170                    del self.output[category]
171        self.output_condition.release()
172        return output
173
174    def collect_output(self, category, timeout_secs, pattern, clear=True):
175        end_time = time.time() + timeout_secs
176        collected_output = ""
177        while end_time > time.time():
178            output = self.get_output(category, timeout=0.25, clear=clear)
179            if output:
180                collected_output += output
181                if pattern is not None and pattern in output:
182                    break
183        return collected_output if collected_output else None
184
185    def enqueue_recv_packet(self, packet):
186        self.recv_condition.acquire()
187        self.recv_packets.append(packet)
188        self.recv_condition.notify()
189        self.recv_condition.release()
190
191    def handle_recv_packet(self, packet):
192        """Called by the read thread that is waiting for all incoming packets
193        to store the incoming packet in "self.recv_packets" in a thread safe
194        way. This function will then signal the "self.recv_condition" to
195        indicate a new packet is available. Returns True if the caller
196        should keep calling this function for more packets.
197        """
198        # If EOF, notify the read thread by enqueuing a None.
199        if not packet:
200            self.enqueue_recv_packet(None)
201            return False
202
203        # Check the packet to see if is an event packet
204        keepGoing = True
205        packet_type = packet["type"]
206        if packet_type == "event":
207            event = packet["event"]
208            body = None
209            if "body" in packet:
210                body = packet["body"]
211            # Handle the event packet and cache information from these packets
212            # as they come in
213            if event == "output":
214                # Store any output we receive so clients can retrieve it later.
215                category = body["category"]
216                output = body["output"]
217                self.output_condition.acquire()
218                if category in self.output:
219                    self.output[category] += output
220                else:
221                    self.output[category] = output
222                self.output_condition.notify()
223                self.output_condition.release()
224                # no need to add 'output' event packets to our packets list
225                return keepGoing
226            elif event == "process":
227                # When a new process is attached or launched, remember the
228                # details that are available in the body of the event
229                self.process_event_body = body
230            elif event == "stopped":
231                # Each thread that stops with a reason will send a
232                # 'stopped' event. We need to remember the thread stop
233                # reasons since the 'threads' command doesn't return
234                # that information.
235                self._process_stopped()
236                tid = body["threadId"]
237                self.thread_stop_reasons[tid] = body
238            elif event == "breakpoint":
239                # Breakpoint events come in when a breakpoint has locations
240                # added or removed. Keep track of them so we can look for them
241                # in tests.
242                self.breakpoint_events.append(packet)
243                # no need to add 'breakpoint' event packets to our packets list
244                return keepGoing
245            elif event.startswith("progress"):
246                # Progress events come in as 'progressStart', 'progressUpdate',
247                # and 'progressEnd' events. Keep these around in case test
248                # cases want to verify them.
249                self.progress_events.append(packet)
250                # No need to add 'progress' event packets to our packets list.
251                return keepGoing
252
253        elif packet_type == "response":
254            if packet["command"] == "disconnect":
255                keepGoing = False
256        self.enqueue_recv_packet(packet)
257        return keepGoing
258
259    def send_packet(self, command_dict, set_sequence=True):
260        """Take the "command_dict" python dictionary and encode it as a JSON
261        string and send the contents as a packet to the VSCode debug
262        adaptor"""
263        # Set the sequence ID for this command automatically
264        if set_sequence:
265            command_dict["seq"] = self.sequence
266            self.sequence += 1
267        # Encode our command dictionary as a JSON string
268        json_str = json.dumps(command_dict, separators=(",", ":"))
269        if self.trace_file:
270            self.trace_file.write("to adaptor:\n%s\n" % (json_str))
271        length = len(json_str)
272        if length > 0:
273            # Send the encoded JSON packet and flush the 'send' file
274            self.send.write(self.encode_content(json_str))
275            self.send.flush()
276
277    def recv_packet(self, filter_type=None, filter_event=None, timeout=None):
278        """Get a JSON packet from the VSCode debug adaptor. This function
279        assumes a thread that reads packets is running and will deliver
280        any received packets by calling handle_recv_packet(...). This
281        function will wait for the packet to arrive and return it when
282        it does."""
283        while True:
284            try:
285                self.recv_condition.acquire()
286                packet = None
287                while True:
288                    for i, curr_packet in enumerate(self.recv_packets):
289                        if not curr_packet:
290                            raise EOFError
291                        packet_type = curr_packet["type"]
292                        if filter_type is None or packet_type in filter_type:
293                            if filter_event is None or (
294                                packet_type == "event"
295                                and curr_packet["event"] in filter_event
296                            ):
297                                packet = self.recv_packets.pop(i)
298                                break
299                    if packet:
300                        break
301                    # Sleep until packet is received
302                    len_before = len(self.recv_packets)
303                    self.recv_condition.wait(timeout)
304                    len_after = len(self.recv_packets)
305                    if len_before == len_after:
306                        return None  # Timed out
307                return packet
308            except EOFError:
309                return None
310            finally:
311                self.recv_condition.release()
312
313        return None
314
315    def send_recv(self, command):
316        """Send a command python dictionary as JSON and receive the JSON
317        response. Validates that the response is the correct sequence and
318        command in the reply. Any events that are received are added to the
319        events list in this object"""
320        self.send_packet(command)
321        done = False
322        while not done:
323            response_or_request = self.recv_packet(filter_type=["response", "request"])
324            if response_or_request is None:
325                desc = 'no response for "%s"' % (command["command"])
326                raise ValueError(desc)
327            if response_or_request["type"] == "response":
328                self.validate_response(command, response_or_request)
329                return response_or_request
330            else:
331                self.reverse_requests.append(response_or_request)
332                if response_or_request["command"] == "runInTerminal":
333                    subprocess.Popen(
334                        response_or_request["arguments"]["args"],
335                        env=response_or_request["arguments"]["env"],
336                    )
337                    self.send_packet(
338                        {
339                            "type": "response",
340                            "seq": -1,
341                            "request_seq": response_or_request["seq"],
342                            "success": True,
343                            "command": "runInTerminal",
344                            "body": {},
345                        },
346                        set_sequence=False,
347                    )
348                elif response_or_request["command"] == "startDebugging":
349                    self.send_packet(
350                        {
351                            "type": "response",
352                            "seq": -1,
353                            "request_seq": response_or_request["seq"],
354                            "success": True,
355                            "command": "startDebugging",
356                            "body": {},
357                        },
358                        set_sequence=False,
359                    )
360                else:
361                    desc = 'unknown reverse request "%s"' % (
362                        response_or_request["command"]
363                    )
364                    raise ValueError(desc)
365
366        return None
367
368    def wait_for_event(self, filter=None, timeout=None):
369        while True:
370            return self.recv_packet(
371                filter_type="event", filter_event=filter, timeout=timeout
372            )
373        return None
374
375    def wait_for_stopped(self, timeout=None):
376        stopped_events = []
377        stopped_event = self.wait_for_event(
378            filter=["stopped", "exited"], timeout=timeout
379        )
380        exited = False
381        while stopped_event:
382            stopped_events.append(stopped_event)
383            # If we exited, then we are done
384            if stopped_event["event"] == "exited":
385                self.exit_status = stopped_event["body"]["exitCode"]
386                exited = True
387                break
388            # Otherwise we stopped and there might be one or more 'stopped'
389            # events for each thread that stopped with a reason, so keep
390            # checking for more 'stopped' events and return all of them
391            stopped_event = self.wait_for_event(filter="stopped", timeout=0.25)
392        if exited:
393            self.threads = []
394        return stopped_events
395
396    def wait_for_exited(self):
397        event_dict = self.wait_for_event("exited")
398        if event_dict is None:
399            raise ValueError("didn't get exited event")
400        return event_dict
401
402    def wait_for_terminated(self):
403        event_dict = self.wait_for_event("terminated")
404        if event_dict is None:
405            raise ValueError("didn't get terminated event")
406        return event_dict
407
408    def get_initialize_value(self, key):
409        """Get a value for the given key if it there is a key/value pair in
410        the "initialize" request response body.
411        """
412        if self.initialize_body and key in self.initialize_body:
413            return self.initialize_body[key]
414        return None
415
416    def get_threads(self):
417        if self.threads is None:
418            self.request_threads()
419        return self.threads
420
421    def get_thread_id(self, threadIndex=0):
422        """Utility function to get the first thread ID in the thread list.
423        If the thread list is empty, then fetch the threads.
424        """
425        if self.threads is None:
426            self.request_threads()
427        if self.threads and threadIndex < len(self.threads):
428            return self.threads[threadIndex]["id"]
429        return None
430
431    def get_stackFrame(self, frameIndex=0, threadId=None):
432        """Get a single "StackFrame" object from a "stackTrace" request and
433        return the "StackFrame" as a python dictionary, or None on failure
434        """
435        if threadId is None:
436            threadId = self.get_thread_id()
437        if threadId is None:
438            print("invalid threadId")
439            return None
440        response = self.request_stackTrace(threadId, startFrame=frameIndex, levels=1)
441        if response:
442            return response["body"]["stackFrames"][0]
443        print("invalid response")
444        return None
445
446    def get_completions(self, text, frameId=None):
447        if frameId is None:
448            stackFrame = self.get_stackFrame()
449            frameId = stackFrame["id"]
450        response = self.request_completions(text, frameId)
451        return response["body"]["targets"]
452
453    def get_scope_variables(self, scope_name, frameIndex=0, threadId=None, is_hex=None):
454        stackFrame = self.get_stackFrame(frameIndex=frameIndex, threadId=threadId)
455        if stackFrame is None:
456            return []
457        frameId = stackFrame["id"]
458        if frameId in self.frame_scopes:
459            frame_scopes = self.frame_scopes[frameId]
460        else:
461            scopes_response = self.request_scopes(frameId)
462            frame_scopes = scopes_response["body"]["scopes"]
463            self.frame_scopes[frameId] = frame_scopes
464        for scope in frame_scopes:
465            if scope["name"] == scope_name:
466                varRef = scope["variablesReference"]
467                variables_response = self.request_variables(varRef, is_hex=is_hex)
468                if variables_response:
469                    if "body" in variables_response:
470                        body = variables_response["body"]
471                        if "variables" in body:
472                            vars = body["variables"]
473                            return vars
474        return []
475
476    def get_global_variables(self, frameIndex=0, threadId=None):
477        return self.get_scope_variables(
478            "Globals", frameIndex=frameIndex, threadId=threadId
479        )
480
481    def get_local_variables(self, frameIndex=0, threadId=None, is_hex=None):
482        return self.get_scope_variables(
483            "Locals", frameIndex=frameIndex, threadId=threadId, is_hex=is_hex
484        )
485
486    def get_registers(self, frameIndex=0, threadId=None):
487        return self.get_scope_variables(
488            "Registers", frameIndex=frameIndex, threadId=threadId
489        )
490
491    def get_local_variable(self, name, frameIndex=0, threadId=None, is_hex=None):
492        locals = self.get_local_variables(
493            frameIndex=frameIndex, threadId=threadId, is_hex=is_hex
494        )
495        for local in locals:
496            if "name" in local and local["name"] == name:
497                return local
498        return None
499
500    def get_local_variable_value(self, name, frameIndex=0, threadId=None, is_hex=None):
501        variable = self.get_local_variable(
502            name, frameIndex=frameIndex, threadId=threadId, is_hex=is_hex
503        )
504        if variable and "value" in variable:
505            return variable["value"]
506        return None
507
508    def get_local_variable_child(
509        self, name, child_name, frameIndex=0, threadId=None, is_hex=None
510    ):
511        local = self.get_local_variable(name, frameIndex, threadId)
512        if local["variablesReference"] == 0:
513            return None
514        children = self.request_variables(local["variablesReference"], is_hex=is_hex)[
515            "body"
516        ]["variables"]
517        for child in children:
518            if child["name"] == child_name:
519                return child
520        return None
521
522    def replay_packets(self, replay_file_path):
523        f = open(replay_file_path, "r")
524        mode = "invalid"
525        set_sequence = False
526        command_dict = None
527        while mode != "eof":
528            if mode == "invalid":
529                line = f.readline()
530                if line.startswith("to adapter:"):
531                    mode = "send"
532                elif line.startswith("from adapter:"):
533                    mode = "recv"
534            elif mode == "send":
535                command_dict = read_packet(f)
536                # Skip the end of line that follows the JSON
537                f.readline()
538                if command_dict is None:
539                    raise ValueError("decode packet failed from replay file")
540                print("Sending:")
541                pprint.PrettyPrinter(indent=2).pprint(command_dict)
542                # raw_input('Press ENTER to send:')
543                self.send_packet(command_dict, set_sequence)
544                mode = "invalid"
545            elif mode == "recv":
546                print("Replay response:")
547                replay_response = read_packet(f)
548                # Skip the end of line that follows the JSON
549                f.readline()
550                pprint.PrettyPrinter(indent=2).pprint(replay_response)
551                actual_response = self.recv_packet()
552                if actual_response:
553                    type = actual_response["type"]
554                    print("Actual response:")
555                    if type == "response":
556                        self.validate_response(command_dict, actual_response)
557                    pprint.PrettyPrinter(indent=2).pprint(actual_response)
558                else:
559                    print("error: didn't get a valid response")
560                mode = "invalid"
561
562    def request_attach(
563        self,
564        program=None,
565        pid=None,
566        waitFor=None,
567        trace=None,
568        initCommands=None,
569        preRunCommands=None,
570        stopCommands=None,
571        exitCommands=None,
572        attachCommands=None,
573        terminateCommands=None,
574        coreFile=None,
575        postRunCommands=None,
576        sourceMap=None,
577    ):
578        args_dict = {}
579        if pid is not None:
580            args_dict["pid"] = pid
581        if program is not None:
582            args_dict["program"] = program
583        if waitFor is not None:
584            args_dict["waitFor"] = waitFor
585        if trace:
586            args_dict["trace"] = trace
587        args_dict["initCommands"] = self.init_commands
588        if initCommands:
589            args_dict["initCommands"].extend(initCommands)
590        if preRunCommands:
591            args_dict["preRunCommands"] = preRunCommands
592        if stopCommands:
593            args_dict["stopCommands"] = stopCommands
594        if exitCommands:
595            args_dict["exitCommands"] = exitCommands
596        if terminateCommands:
597            args_dict["terminateCommands"] = terminateCommands
598        if attachCommands:
599            args_dict["attachCommands"] = attachCommands
600        if coreFile:
601            args_dict["coreFile"] = coreFile
602        if postRunCommands:
603            args_dict["postRunCommands"] = postRunCommands
604        if sourceMap:
605            args_dict["sourceMap"] = sourceMap
606        command_dict = {"command": "attach", "type": "request", "arguments": args_dict}
607        return self.send_recv(command_dict)
608
609    def request_configurationDone(self):
610        command_dict = {
611            "command": "configurationDone",
612            "type": "request",
613            "arguments": {},
614        }
615        response = self.send_recv(command_dict)
616        if response:
617            self.configuration_done_sent = True
618        return response
619
620    def _process_stopped(self):
621        self.threads = None
622        self.frame_scopes = {}
623
624    def request_continue(self, threadId=None):
625        if self.exit_status is not None:
626            raise ValueError("request_continue called after process exited")
627        # If we have launched or attached, then the first continue is done by
628        # sending the 'configurationDone' request
629        if not self.configuration_done_sent:
630            return self.request_configurationDone()
631        args_dict = {}
632        if threadId is None:
633            threadId = self.get_thread_id()
634        args_dict["threadId"] = threadId
635        command_dict = {
636            "command": "continue",
637            "type": "request",
638            "arguments": args_dict,
639        }
640        response = self.send_recv(command_dict)
641        # Caller must still call wait_for_stopped.
642        return response
643
644    def request_restart(self, restartArguments=None):
645        command_dict = {
646            "command": "restart",
647            "type": "request",
648        }
649        if restartArguments:
650            command_dict["arguments"] = restartArguments
651
652        response = self.send_recv(command_dict)
653        # Caller must still call wait_for_stopped.
654        return response
655
656    def request_disconnect(self, terminateDebuggee=None):
657        args_dict = {}
658        if terminateDebuggee is not None:
659            if terminateDebuggee:
660                args_dict["terminateDebuggee"] = True
661            else:
662                args_dict["terminateDebuggee"] = False
663        command_dict = {
664            "command": "disconnect",
665            "type": "request",
666            "arguments": args_dict,
667        }
668        return self.send_recv(command_dict)
669
670    def request_disassemble(
671        self, memoryReference, offset=-50, instructionCount=200, resolveSymbols=True
672    ):
673        args_dict = {
674            "memoryReference": memoryReference,
675            "offset": offset,
676            "instructionCount": instructionCount,
677            "resolveSymbols": resolveSymbols,
678        }
679        command_dict = {
680            "command": "disassemble",
681            "type": "request",
682            "arguments": args_dict,
683        }
684        instructions = self.send_recv(command_dict)["body"]["instructions"]
685        for inst in instructions:
686            self.disassembled_instructions[inst["address"]] = inst
687
688    def request_evaluate(self, expression, frameIndex=0, threadId=None, context=None):
689        stackFrame = self.get_stackFrame(frameIndex=frameIndex, threadId=threadId)
690        if stackFrame is None:
691            return []
692        args_dict = {
693            "expression": expression,
694            "context": context,
695            "frameId": stackFrame["id"],
696        }
697        command_dict = {
698            "command": "evaluate",
699            "type": "request",
700            "arguments": args_dict,
701        }
702        return self.send_recv(command_dict)
703
704    def request_initialize(self, sourceInitFile):
705        command_dict = {
706            "command": "initialize",
707            "type": "request",
708            "arguments": {
709                "adapterID": "lldb-native",
710                "clientID": "vscode",
711                "columnsStartAt1": True,
712                "linesStartAt1": True,
713                "locale": "en-us",
714                "pathFormat": "path",
715                "supportsRunInTerminalRequest": True,
716                "supportsVariablePaging": True,
717                "supportsVariableType": True,
718                "supportsStartDebuggingRequest": True,
719                "sourceInitFile": sourceInitFile,
720            },
721        }
722        response = self.send_recv(command_dict)
723        if response:
724            if "body" in response:
725                self.initialize_body = response["body"]
726        return response
727
728    def request_launch(
729        self,
730        program,
731        args=None,
732        cwd=None,
733        env=None,
734        stopOnEntry=False,
735        disableASLR=True,
736        disableSTDIO=False,
737        shellExpandArguments=False,
738        trace=False,
739        initCommands=None,
740        preRunCommands=None,
741        stopCommands=None,
742        exitCommands=None,
743        terminateCommands=None,
744        sourcePath=None,
745        debuggerRoot=None,
746        launchCommands=None,
747        sourceMap=None,
748        runInTerminal=False,
749        postRunCommands=None,
750        enableAutoVariableSummaries=False,
751        enableSyntheticChildDebugging=False,
752        commandEscapePrefix=None,
753        customFrameFormat=None,
754        customThreadFormat=None,
755    ):
756        args_dict = {"program": program}
757        if args:
758            args_dict["args"] = args
759        if cwd:
760            args_dict["cwd"] = cwd
761        if env:
762            args_dict["env"] = env
763        if stopOnEntry:
764            args_dict["stopOnEntry"] = stopOnEntry
765        if disableASLR:
766            args_dict["disableASLR"] = disableASLR
767        if disableSTDIO:
768            args_dict["disableSTDIO"] = disableSTDIO
769        if shellExpandArguments:
770            args_dict["shellExpandArguments"] = shellExpandArguments
771        if trace:
772            args_dict["trace"] = trace
773        args_dict["initCommands"] = self.init_commands
774        if initCommands:
775            args_dict["initCommands"].extend(initCommands)
776        if preRunCommands:
777            args_dict["preRunCommands"] = preRunCommands
778        if stopCommands:
779            args_dict["stopCommands"] = stopCommands
780        if exitCommands:
781            args_dict["exitCommands"] = exitCommands
782        if terminateCommands:
783            args_dict["terminateCommands"] = terminateCommands
784        if sourcePath:
785            args_dict["sourcePath"] = sourcePath
786        if debuggerRoot:
787            args_dict["debuggerRoot"] = debuggerRoot
788        if launchCommands:
789            args_dict["launchCommands"] = launchCommands
790        if sourceMap:
791            args_dict["sourceMap"] = sourceMap
792        if runInTerminal:
793            args_dict["runInTerminal"] = runInTerminal
794        if postRunCommands:
795            args_dict["postRunCommands"] = postRunCommands
796        if customFrameFormat:
797            args_dict["customFrameFormat"] = customFrameFormat
798        if customThreadFormat:
799            args_dict["customThreadFormat"] = customThreadFormat
800
801        args_dict["enableAutoVariableSummaries"] = enableAutoVariableSummaries
802        args_dict["enableSyntheticChildDebugging"] = enableSyntheticChildDebugging
803        args_dict["commandEscapePrefix"] = commandEscapePrefix
804        command_dict = {"command": "launch", "type": "request", "arguments": args_dict}
805        response = self.send_recv(command_dict)
806
807        if response["success"]:
808            # Wait for a 'process' and 'initialized' event in any order
809            self.wait_for_event(filter=["process", "initialized"])
810            self.wait_for_event(filter=["process", "initialized"])
811        return response
812
813    def request_next(self, threadId):
814        if self.exit_status is not None:
815            raise ValueError("request_continue called after process exited")
816        args_dict = {"threadId": threadId}
817        command_dict = {"command": "next", "type": "request", "arguments": args_dict}
818        return self.send_recv(command_dict)
819
820    def request_stepIn(self, threadId, targetId):
821        if self.exit_status is not None:
822            raise ValueError("request_stepIn called after process exited")
823        args_dict = {"threadId": threadId, "targetId": targetId}
824        command_dict = {"command": "stepIn", "type": "request", "arguments": args_dict}
825        return self.send_recv(command_dict)
826
827    def request_stepInTargets(self, frameId):
828        if self.exit_status is not None:
829            raise ValueError("request_stepInTargets called after process exited")
830        args_dict = {"frameId": frameId}
831        command_dict = {
832            "command": "stepInTargets",
833            "type": "request",
834            "arguments": args_dict,
835        }
836        return self.send_recv(command_dict)
837
838    def request_stepOut(self, threadId):
839        if self.exit_status is not None:
840            raise ValueError("request_stepOut called after process exited")
841        args_dict = {"threadId": threadId}
842        command_dict = {"command": "stepOut", "type": "request", "arguments": args_dict}
843        return self.send_recv(command_dict)
844
845    def request_pause(self, threadId=None):
846        if self.exit_status is not None:
847            raise ValueError("request_pause called after process exited")
848        if threadId is None:
849            threadId = self.get_thread_id()
850        args_dict = {"threadId": threadId}
851        command_dict = {"command": "pause", "type": "request", "arguments": args_dict}
852        return self.send_recv(command_dict)
853
854    def request_scopes(self, frameId):
855        args_dict = {"frameId": frameId}
856        command_dict = {"command": "scopes", "type": "request", "arguments": args_dict}
857        return self.send_recv(command_dict)
858
859    def request_setBreakpoints(self, file_path, line_array, data=None):
860        """data is array of parameters for breakpoints in line_array.
861        Each parameter object is 1:1 mapping with entries in line_entry.
862        It contains optional location/hitCondition/logMessage parameters.
863        """
864        (dir, base) = os.path.split(file_path)
865        source_dict = {"name": base, "path": file_path}
866        args_dict = {
867            "source": source_dict,
868            "sourceModified": False,
869        }
870        if line_array is not None:
871            args_dict["lines"] = "%s" % line_array
872            breakpoints = []
873            for i, line in enumerate(line_array):
874                breakpoint_data = None
875                if data is not None and i < len(data):
876                    breakpoint_data = data[i]
877                bp = {"line": line}
878                if breakpoint_data is not None:
879                    if "condition" in breakpoint_data and breakpoint_data["condition"]:
880                        bp["condition"] = breakpoint_data["condition"]
881                    if (
882                        "hitCondition" in breakpoint_data
883                        and breakpoint_data["hitCondition"]
884                    ):
885                        bp["hitCondition"] = breakpoint_data["hitCondition"]
886                    if (
887                        "logMessage" in breakpoint_data
888                        and breakpoint_data["logMessage"]
889                    ):
890                        bp["logMessage"] = breakpoint_data["logMessage"]
891                breakpoints.append(bp)
892            args_dict["breakpoints"] = breakpoints
893
894        command_dict = {
895            "command": "setBreakpoints",
896            "type": "request",
897            "arguments": args_dict,
898        }
899        return self.send_recv(command_dict)
900
901    def request_setExceptionBreakpoints(self, filters):
902        args_dict = {"filters": filters}
903        command_dict = {
904            "command": "setExceptionBreakpoints",
905            "type": "request",
906            "arguments": args_dict,
907        }
908        return self.send_recv(command_dict)
909
910    def request_setFunctionBreakpoints(self, names, condition=None, hitCondition=None):
911        breakpoints = []
912        for name in names:
913            bp = {"name": name}
914            if condition is not None:
915                bp["condition"] = condition
916            if hitCondition is not None:
917                bp["hitCondition"] = hitCondition
918            breakpoints.append(bp)
919        args_dict = {"breakpoints": breakpoints}
920        command_dict = {
921            "command": "setFunctionBreakpoints",
922            "type": "request",
923            "arguments": args_dict,
924        }
925        return self.send_recv(command_dict)
926
927    def request_dataBreakpointInfo(
928        self, variablesReference, name, frameIndex=0, threadId=None
929    ):
930        stackFrame = self.get_stackFrame(frameIndex=frameIndex, threadId=threadId)
931        if stackFrame is None:
932            return []
933        args_dict = {
934            "variablesReference": variablesReference,
935            "name": name,
936            "frameId": stackFrame["id"],
937        }
938        command_dict = {
939            "command": "dataBreakpointInfo",
940            "type": "request",
941            "arguments": args_dict,
942        }
943        return self.send_recv(command_dict)
944
945    def request_setDataBreakpoint(self, dataBreakpoints):
946        """dataBreakpoints is a list of dictionary with following fields:
947        {
948            dataId: (address in hex)/(size in bytes)
949            accessType: read/write/readWrite
950            [condition]: string
951            [hitCondition]: string
952        }
953        """
954        args_dict = {"breakpoints": dataBreakpoints}
955        command_dict = {
956            "command": "setDataBreakpoints",
957            "type": "request",
958            "arguments": args_dict,
959        }
960        return self.send_recv(command_dict)
961
962    def request_compileUnits(self, moduleId):
963        args_dict = {"moduleId": moduleId}
964        command_dict = {
965            "command": "compileUnits",
966            "type": "request",
967            "arguments": args_dict,
968        }
969        response = self.send_recv(command_dict)
970        return response
971
972    def request_completions(self, text, frameId=None):
973        args_dict = {"text": text, "column": len(text)}
974        if frameId:
975            args_dict["frameId"] = frameId
976        command_dict = {
977            "command": "completions",
978            "type": "request",
979            "arguments": args_dict,
980        }
981        return self.send_recv(command_dict)
982
983    def request_modules(self):
984        return self.send_recv({"command": "modules", "type": "request"})
985
986    def request_stackTrace(
987        self, threadId=None, startFrame=None, levels=None, dump=False
988    ):
989        if threadId is None:
990            threadId = self.get_thread_id()
991        args_dict = {"threadId": threadId}
992        if startFrame is not None:
993            args_dict["startFrame"] = startFrame
994        if levels is not None:
995            args_dict["levels"] = levels
996        command_dict = {
997            "command": "stackTrace",
998            "type": "request",
999            "arguments": args_dict,
1000        }
1001        response = self.send_recv(command_dict)
1002        if dump:
1003            for idx, frame in enumerate(response["body"]["stackFrames"]):
1004                name = frame["name"]
1005                if "line" in frame and "source" in frame:
1006                    source = frame["source"]
1007                    if "sourceReference" not in source:
1008                        if "name" in source:
1009                            source_name = source["name"]
1010                            line = frame["line"]
1011                            print("[%3u] %s @ %s:%u" % (idx, name, source_name, line))
1012                            continue
1013                print("[%3u] %s" % (idx, name))
1014        return response
1015
1016    def request_threads(self):
1017        """Request a list of all threads and combine any information from any
1018        "stopped" events since those contain more information about why a
1019        thread actually stopped. Returns an array of thread dictionaries
1020        with information about all threads"""
1021        command_dict = {"command": "threads", "type": "request", "arguments": {}}
1022        response = self.send_recv(command_dict)
1023        body = response["body"]
1024        # Fill in "self.threads" correctly so that clients that call
1025        # self.get_threads() or self.get_thread_id(...) can get information
1026        # on threads when the process is stopped.
1027        if "threads" in body:
1028            self.threads = body["threads"]
1029            for thread in self.threads:
1030                # Copy the thread dictionary so we can add key/value pairs to
1031                # it without affecting the original info from the "threads"
1032                # command.
1033                tid = thread["id"]
1034                if tid in self.thread_stop_reasons:
1035                    thread_stop_info = self.thread_stop_reasons[tid]
1036                    copy_keys = ["reason", "description", "text"]
1037                    for key in copy_keys:
1038                        if key in thread_stop_info:
1039                            thread[key] = thread_stop_info[key]
1040        else:
1041            self.threads = None
1042        return response
1043
1044    def request_variables(
1045        self, variablesReference, start=None, count=None, is_hex=None
1046    ):
1047        args_dict = {"variablesReference": variablesReference}
1048        if start is not None:
1049            args_dict["start"] = start
1050        if count is not None:
1051            args_dict["count"] = count
1052        if is_hex is not None:
1053            args_dict["format"] = {"hex": is_hex}
1054        command_dict = {
1055            "command": "variables",
1056            "type": "request",
1057            "arguments": args_dict,
1058        }
1059        return self.send_recv(command_dict)
1060
1061    def request_setVariable(self, containingVarRef, name, value, id=None):
1062        args_dict = {
1063            "variablesReference": containingVarRef,
1064            "name": name,
1065            "value": str(value),
1066        }
1067        if id is not None:
1068            args_dict["id"] = id
1069        command_dict = {
1070            "command": "setVariable",
1071            "type": "request",
1072            "arguments": args_dict,
1073        }
1074        return self.send_recv(command_dict)
1075
1076    def request_testGetTargetBreakpoints(self):
1077        """A request packet used in the LLDB test suite to get all currently
1078        set breakpoint infos for all breakpoints currently set in the
1079        target.
1080        """
1081        command_dict = {
1082            "command": "_testGetTargetBreakpoints",
1083            "type": "request",
1084            "arguments": {},
1085        }
1086        return self.send_recv(command_dict)
1087
1088    def terminate(self):
1089        self.send.close()
1090        # self.recv.close()
1091
1092
1093class DebugAdaptorServer(DebugCommunication):
1094    def __init__(
1095        self,
1096        executable=None,
1097        port=None,
1098        init_commands=[],
1099        log_file=None,
1100        env=None,
1101    ):
1102        self.process = None
1103        if executable is not None:
1104            adaptor_env = os.environ.copy()
1105            if env is not None:
1106                adaptor_env.update(env)
1107
1108            if log_file:
1109                adaptor_env["LLDBDAP_LOG"] = log_file
1110            self.process = subprocess.Popen(
1111                [executable],
1112                stdin=subprocess.PIPE,
1113                stdout=subprocess.PIPE,
1114                stderr=subprocess.PIPE,
1115                env=adaptor_env,
1116            )
1117            DebugCommunication.__init__(
1118                self, self.process.stdout, self.process.stdin, init_commands, log_file
1119            )
1120        elif port is not None:
1121            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1122            s.connect(("127.0.0.1", port))
1123            DebugCommunication.__init__(
1124                self, s.makefile("r"), s.makefile("w"), init_commands
1125            )
1126
1127    def get_pid(self):
1128        if self.process:
1129            return self.process.pid
1130        return -1
1131
1132    def terminate(self):
1133        super(DebugAdaptorServer, self).terminate()
1134        if self.process is not None:
1135            self.process.terminate()
1136            self.process.wait()
1137            self.process = None
1138
1139
1140def attach_options_specified(options):
1141    if options.pid is not None:
1142        return True
1143    if options.waitFor:
1144        return True
1145    if options.attach:
1146        return True
1147    if options.attachCmds:
1148        return True
1149    return False
1150
1151
1152def run_vscode(dbg, args, options):
1153    dbg.request_initialize(options.sourceInitFile)
1154    if attach_options_specified(options):
1155        response = dbg.request_attach(
1156            program=options.program,
1157            pid=options.pid,
1158            waitFor=options.waitFor,
1159            attachCommands=options.attachCmds,
1160            initCommands=options.initCmds,
1161            preRunCommands=options.preRunCmds,
1162            stopCommands=options.stopCmds,
1163            exitCommands=options.exitCmds,
1164            terminateCommands=options.terminateCmds,
1165        )
1166    else:
1167        response = dbg.request_launch(
1168            options.program,
1169            args=args,
1170            env=options.envs,
1171            cwd=options.workingDir,
1172            debuggerRoot=options.debuggerRoot,
1173            sourcePath=options.sourcePath,
1174            initCommands=options.initCmds,
1175            preRunCommands=options.preRunCmds,
1176            stopCommands=options.stopCmds,
1177            exitCommands=options.exitCmds,
1178            terminateCommands=options.terminateCmds,
1179        )
1180
1181    if response["success"]:
1182        if options.sourceBreakpoints:
1183            source_to_lines = {}
1184            for file_line in options.sourceBreakpoints:
1185                (path, line) = file_line.split(":")
1186                if len(path) == 0 or len(line) == 0:
1187                    print('error: invalid source with line "%s"' % (file_line))
1188
1189                else:
1190                    if path in source_to_lines:
1191                        source_to_lines[path].append(int(line))
1192                    else:
1193                        source_to_lines[path] = [int(line)]
1194            for source in source_to_lines:
1195                dbg.request_setBreakpoints(source, source_to_lines[source])
1196        if options.funcBreakpoints:
1197            dbg.request_setFunctionBreakpoints(options.funcBreakpoints)
1198        dbg.request_configurationDone()
1199        dbg.wait_for_stopped()
1200    else:
1201        if "message" in response:
1202            print(response["message"])
1203    dbg.request_disconnect(terminateDebuggee=True)
1204
1205
1206def main():
1207    parser = optparse.OptionParser(
1208        description=(
1209            "A testing framework for the Visual Studio Code Debug " "Adaptor protocol"
1210        )
1211    )
1212
1213    parser.add_option(
1214        "--vscode",
1215        type="string",
1216        dest="vscode_path",
1217        help=(
1218            "The path to the command line program that implements the "
1219            "Visual Studio Code Debug Adaptor protocol."
1220        ),
1221        default=None,
1222    )
1223
1224    parser.add_option(
1225        "--program",
1226        type="string",
1227        dest="program",
1228        help="The path to the program to debug.",
1229        default=None,
1230    )
1231
1232    parser.add_option(
1233        "--workingDir",
1234        type="string",
1235        dest="workingDir",
1236        default=None,
1237        help="Set the working directory for the process we launch.",
1238    )
1239
1240    parser.add_option(
1241        "--sourcePath",
1242        type="string",
1243        dest="sourcePath",
1244        default=None,
1245        help=(
1246            "Set the relative source root for any debug info that has "
1247            "relative paths in it."
1248        ),
1249    )
1250
1251    parser.add_option(
1252        "--debuggerRoot",
1253        type="string",
1254        dest="debuggerRoot",
1255        default=None,
1256        help=(
1257            "Set the working directory for lldb-dap for any object files "
1258            "with relative paths in the Mach-o debug map."
1259        ),
1260    )
1261
1262    parser.add_option(
1263        "-r",
1264        "--replay",
1265        type="string",
1266        dest="replay",
1267        help=(
1268            "Specify a file containing a packet log to replay with the "
1269            "current Visual Studio Code Debug Adaptor executable."
1270        ),
1271        default=None,
1272    )
1273
1274    parser.add_option(
1275        "-g",
1276        "--debug",
1277        action="store_true",
1278        dest="debug",
1279        default=False,
1280        help="Pause waiting for a debugger to attach to the debug adaptor",
1281    )
1282
1283    parser.add_option(
1284        "--sourceInitFile",
1285        action="store_true",
1286        dest="sourceInitFile",
1287        default=False,
1288        help="Whether lldb-dap should source .lldbinit file or not",
1289    )
1290
1291    parser.add_option(
1292        "--port",
1293        type="int",
1294        dest="port",
1295        help="Attach a socket to a port instead of using STDIN for VSCode",
1296        default=None,
1297    )
1298
1299    parser.add_option(
1300        "--pid",
1301        type="int",
1302        dest="pid",
1303        help="The process ID to attach to",
1304        default=None,
1305    )
1306
1307    parser.add_option(
1308        "--attach",
1309        action="store_true",
1310        dest="attach",
1311        default=False,
1312        help=(
1313            "Specify this option to attach to a process by name. The "
1314            "process name is the basename of the executable specified with "
1315            "the --program option."
1316        ),
1317    )
1318
1319    parser.add_option(
1320        "-f",
1321        "--function-bp",
1322        type="string",
1323        action="append",
1324        dest="funcBreakpoints",
1325        help=(
1326            "Specify the name of a function to break at. "
1327            "Can be specified more than once."
1328        ),
1329        default=[],
1330    )
1331
1332    parser.add_option(
1333        "-s",
1334        "--source-bp",
1335        type="string",
1336        action="append",
1337        dest="sourceBreakpoints",
1338        default=[],
1339        help=(
1340            "Specify source breakpoints to set in the format of "
1341            "<source>:<line>. "
1342            "Can be specified more than once."
1343        ),
1344    )
1345
1346    parser.add_option(
1347        "--attachCommand",
1348        type="string",
1349        action="append",
1350        dest="attachCmds",
1351        default=[],
1352        help=(
1353            "Specify a LLDB command that will attach to a process. "
1354            "Can be specified more than once."
1355        ),
1356    )
1357
1358    parser.add_option(
1359        "--initCommand",
1360        type="string",
1361        action="append",
1362        dest="initCmds",
1363        default=[],
1364        help=(
1365            "Specify a LLDB command that will be executed before the target "
1366            "is created. Can be specified more than once."
1367        ),
1368    )
1369
1370    parser.add_option(
1371        "--preRunCommand",
1372        type="string",
1373        action="append",
1374        dest="preRunCmds",
1375        default=[],
1376        help=(
1377            "Specify a LLDB command that will be executed after the target "
1378            "has been created. Can be specified more than once."
1379        ),
1380    )
1381
1382    parser.add_option(
1383        "--stopCommand",
1384        type="string",
1385        action="append",
1386        dest="stopCmds",
1387        default=[],
1388        help=(
1389            "Specify a LLDB command that will be executed each time the"
1390            "process stops. Can be specified more than once."
1391        ),
1392    )
1393
1394    parser.add_option(
1395        "--exitCommand",
1396        type="string",
1397        action="append",
1398        dest="exitCmds",
1399        default=[],
1400        help=(
1401            "Specify a LLDB command that will be executed when the process "
1402            "exits. Can be specified more than once."
1403        ),
1404    )
1405
1406    parser.add_option(
1407        "--terminateCommand",
1408        type="string",
1409        action="append",
1410        dest="terminateCmds",
1411        default=[],
1412        help=(
1413            "Specify a LLDB command that will be executed when the debugging "
1414            "session is terminated. Can be specified more than once."
1415        ),
1416    )
1417
1418    parser.add_option(
1419        "--env",
1420        type="string",
1421        action="append",
1422        dest="envs",
1423        default=[],
1424        help=("Specify environment variables to pass to the launched " "process."),
1425    )
1426
1427    parser.add_option(
1428        "--waitFor",
1429        action="store_true",
1430        dest="waitFor",
1431        default=False,
1432        help=(
1433            "Wait for the next process to be launched whose name matches "
1434            "the basename of the program specified with the --program "
1435            "option"
1436        ),
1437    )
1438
1439    (options, args) = parser.parse_args(sys.argv[1:])
1440
1441    if options.vscode_path is None and options.port is None:
1442        print(
1443            "error: must either specify a path to a Visual Studio Code "
1444            "Debug Adaptor vscode executable path using the --vscode "
1445            "option, or a port to attach to for an existing lldb-dap "
1446            "using the --port option"
1447        )
1448        return
1449    dbg = DebugAdaptorServer(executable=options.vscode_path, port=options.port)
1450    if options.debug:
1451        raw_input('Waiting for debugger to attach pid "%i"' % (dbg.get_pid()))
1452    if options.replay:
1453        dbg.replay_packets(options.replay)
1454    else:
1455        run_vscode(dbg, args, options)
1456    dbg.terminate()
1457
1458
1459if __name__ == "__main__":
1460    main()
1461