xref: /llvm-project/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py (revision 4f48a81a620bc9280be4780f3554cdc9bda55bd3)
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        gdbRemotePort=None,
578        gdbRemoteHostname=None,
579    ):
580        args_dict = {}
581        if pid is not None:
582            args_dict["pid"] = pid
583        if program is not None:
584            args_dict["program"] = program
585        if waitFor is not None:
586            args_dict["waitFor"] = waitFor
587        if trace:
588            args_dict["trace"] = trace
589        args_dict["initCommands"] = self.init_commands
590        if initCommands:
591            args_dict["initCommands"].extend(initCommands)
592        if preRunCommands:
593            args_dict["preRunCommands"] = preRunCommands
594        if stopCommands:
595            args_dict["stopCommands"] = stopCommands
596        if exitCommands:
597            args_dict["exitCommands"] = exitCommands
598        if terminateCommands:
599            args_dict["terminateCommands"] = terminateCommands
600        if attachCommands:
601            args_dict["attachCommands"] = attachCommands
602        if coreFile:
603            args_dict["coreFile"] = coreFile
604        if postRunCommands:
605            args_dict["postRunCommands"] = postRunCommands
606        if sourceMap:
607            args_dict["sourceMap"] = sourceMap
608        if gdbRemotePort is not None:
609            args_dict["gdb-remote-port"] = gdbRemotePort
610        if gdbRemoteHostname is not None:
611            args_dict["gdb-remote-hostname"] = gdbRemoteHostname
612        command_dict = {"command": "attach", "type": "request", "arguments": args_dict}
613        return self.send_recv(command_dict)
614
615    def request_breakpointLocations(
616        self, file_path, line, end_line=None, column=None, end_column=None
617    ):
618        (dir, base) = os.path.split(file_path)
619        source_dict = {"name": base, "path": file_path}
620        args_dict = {}
621        args_dict["source"] = source_dict
622        if line is not None:
623            args_dict["line"] = line
624        if end_line is not None:
625            args_dict["endLine"] = end_line
626        if column is not None:
627            args_dict["column"] = column
628        if end_column is not None:
629            args_dict["endColumn"] = end_column
630        command_dict = {
631            "command": "breakpointLocations",
632            "type": "request",
633            "arguments": args_dict,
634        }
635        return self.send_recv(command_dict)
636
637    def request_configurationDone(self):
638        command_dict = {
639            "command": "configurationDone",
640            "type": "request",
641            "arguments": {},
642        }
643        response = self.send_recv(command_dict)
644        if response:
645            self.configuration_done_sent = True
646        return response
647
648    def _process_stopped(self):
649        self.threads = None
650        self.frame_scopes = {}
651
652    def request_continue(self, threadId=None):
653        if self.exit_status is not None:
654            raise ValueError("request_continue called after process exited")
655        # If we have launched or attached, then the first continue is done by
656        # sending the 'configurationDone' request
657        if not self.configuration_done_sent:
658            return self.request_configurationDone()
659        args_dict = {}
660        if threadId is None:
661            threadId = self.get_thread_id()
662        args_dict["threadId"] = threadId
663        command_dict = {
664            "command": "continue",
665            "type": "request",
666            "arguments": args_dict,
667        }
668        response = self.send_recv(command_dict)
669        # Caller must still call wait_for_stopped.
670        return response
671
672    def request_restart(self, restartArguments=None):
673        command_dict = {
674            "command": "restart",
675            "type": "request",
676        }
677        if restartArguments:
678            command_dict["arguments"] = restartArguments
679
680        response = self.send_recv(command_dict)
681        # Caller must still call wait_for_stopped.
682        return response
683
684    def request_disconnect(self, terminateDebuggee=None):
685        args_dict = {}
686        if terminateDebuggee is not None:
687            if terminateDebuggee:
688                args_dict["terminateDebuggee"] = True
689            else:
690                args_dict["terminateDebuggee"] = False
691        command_dict = {
692            "command": "disconnect",
693            "type": "request",
694            "arguments": args_dict,
695        }
696        return self.send_recv(command_dict)
697
698    def request_disassemble(
699        self, memoryReference, offset=-50, instructionCount=200, resolveSymbols=True
700    ):
701        args_dict = {
702            "memoryReference": memoryReference,
703            "offset": offset,
704            "instructionCount": instructionCount,
705            "resolveSymbols": resolveSymbols,
706        }
707        command_dict = {
708            "command": "disassemble",
709            "type": "request",
710            "arguments": args_dict,
711        }
712        instructions = self.send_recv(command_dict)["body"]["instructions"]
713        for inst in instructions:
714            self.disassembled_instructions[inst["address"]] = inst
715
716    def request_readMemory(self, memoryReference, offset, count):
717        args_dict = {
718            "memoryReference": memoryReference,
719            "offset": offset,
720            "count": count,
721        }
722        command_dict = {
723            "command": "readMemory",
724            "type": "request",
725            "arguments": args_dict,
726        }
727        return self.send_recv(command_dict)
728
729    def request_evaluate(self, expression, frameIndex=0, threadId=None, context=None):
730        stackFrame = self.get_stackFrame(frameIndex=frameIndex, threadId=threadId)
731        if stackFrame is None:
732            return []
733        args_dict = {
734            "expression": expression,
735            "context": context,
736            "frameId": stackFrame["id"],
737        }
738        command_dict = {
739            "command": "evaluate",
740            "type": "request",
741            "arguments": args_dict,
742        }
743        return self.send_recv(command_dict)
744
745    def request_exceptionInfo(self, threadId=None):
746        if threadId is None:
747            threadId = self.get_thread_id()
748        args_dict = {"threadId": threadId}
749        command_dict = {
750            "command": "exceptionInfo",
751            "type": "request",
752            "arguments": args_dict,
753        }
754        return self.send_recv(command_dict)
755
756    def request_initialize(self, sourceInitFile):
757        command_dict = {
758            "command": "initialize",
759            "type": "request",
760            "arguments": {
761                "adapterID": "lldb-native",
762                "clientID": "vscode",
763                "columnsStartAt1": True,
764                "linesStartAt1": True,
765                "locale": "en-us",
766                "pathFormat": "path",
767                "supportsRunInTerminalRequest": True,
768                "supportsVariablePaging": True,
769                "supportsVariableType": True,
770                "supportsStartDebuggingRequest": True,
771                "sourceInitFile": sourceInitFile,
772            },
773        }
774        response = self.send_recv(command_dict)
775        if response:
776            if "body" in response:
777                self.initialize_body = response["body"]
778        return response
779
780    def request_launch(
781        self,
782        program,
783        args=None,
784        cwd=None,
785        env=None,
786        stopOnEntry=False,
787        disableASLR=True,
788        disableSTDIO=False,
789        shellExpandArguments=False,
790        trace=False,
791        initCommands=None,
792        preRunCommands=None,
793        stopCommands=None,
794        exitCommands=None,
795        terminateCommands=None,
796        sourcePath=None,
797        debuggerRoot=None,
798        launchCommands=None,
799        sourceMap=None,
800        runInTerminal=False,
801        postRunCommands=None,
802        enableAutoVariableSummaries=False,
803        displayExtendedBacktrace=False,
804        enableSyntheticChildDebugging=False,
805        commandEscapePrefix=None,
806        customFrameFormat=None,
807        customThreadFormat=None,
808    ):
809        args_dict = {"program": program}
810        if args:
811            args_dict["args"] = args
812        if cwd:
813            args_dict["cwd"] = cwd
814        if env:
815            args_dict["env"] = env
816        if stopOnEntry:
817            args_dict["stopOnEntry"] = stopOnEntry
818        if disableSTDIO:
819            args_dict["disableSTDIO"] = disableSTDIO
820        if shellExpandArguments:
821            args_dict["shellExpandArguments"] = shellExpandArguments
822        if trace:
823            args_dict["trace"] = trace
824        args_dict["initCommands"] = self.init_commands
825        if initCommands:
826            args_dict["initCommands"].extend(initCommands)
827        if preRunCommands:
828            args_dict["preRunCommands"] = preRunCommands
829        if stopCommands:
830            args_dict["stopCommands"] = stopCommands
831        if exitCommands:
832            args_dict["exitCommands"] = exitCommands
833        if terminateCommands:
834            args_dict["terminateCommands"] = terminateCommands
835        if sourcePath:
836            args_dict["sourcePath"] = sourcePath
837        if debuggerRoot:
838            args_dict["debuggerRoot"] = debuggerRoot
839        if launchCommands:
840            args_dict["launchCommands"] = launchCommands
841        if sourceMap:
842            args_dict["sourceMap"] = sourceMap
843        if runInTerminal:
844            args_dict["runInTerminal"] = runInTerminal
845        if postRunCommands:
846            args_dict["postRunCommands"] = postRunCommands
847        if customFrameFormat:
848            args_dict["customFrameFormat"] = customFrameFormat
849        if customThreadFormat:
850            args_dict["customThreadFormat"] = customThreadFormat
851
852        args_dict["disableASLR"] = disableASLR
853        args_dict["enableAutoVariableSummaries"] = enableAutoVariableSummaries
854        args_dict["enableSyntheticChildDebugging"] = enableSyntheticChildDebugging
855        args_dict["displayExtendedBacktrace"] = displayExtendedBacktrace
856        args_dict["commandEscapePrefix"] = commandEscapePrefix
857        command_dict = {"command": "launch", "type": "request", "arguments": args_dict}
858        response = self.send_recv(command_dict)
859
860        if response["success"]:
861            # Wait for a 'process' and 'initialized' event in any order
862            self.wait_for_event(filter=["process", "initialized"])
863            self.wait_for_event(filter=["process", "initialized"])
864        return response
865
866    def request_next(self, threadId, granularity="statement"):
867        if self.exit_status is not None:
868            raise ValueError("request_continue called after process exited")
869        args_dict = {"threadId": threadId, "granularity": granularity}
870        command_dict = {"command": "next", "type": "request", "arguments": args_dict}
871        return self.send_recv(command_dict)
872
873    def request_stepIn(self, threadId, targetId, granularity="statement"):
874        if self.exit_status is not None:
875            raise ValueError("request_stepIn called after process exited")
876        if threadId is None:
877            threadId = self.get_thread_id()
878        args_dict = {
879            "threadId": threadId,
880            "targetId": targetId,
881            "granularity": granularity,
882        }
883        command_dict = {"command": "stepIn", "type": "request", "arguments": args_dict}
884        return self.send_recv(command_dict)
885
886    def request_stepInTargets(self, frameId):
887        if self.exit_status is not None:
888            raise ValueError("request_stepInTargets called after process exited")
889        args_dict = {"frameId": frameId}
890        command_dict = {
891            "command": "stepInTargets",
892            "type": "request",
893            "arguments": args_dict,
894        }
895        return self.send_recv(command_dict)
896
897    def request_stepOut(self, threadId):
898        if self.exit_status is not None:
899            raise ValueError("request_stepOut called after process exited")
900        args_dict = {"threadId": threadId}
901        command_dict = {"command": "stepOut", "type": "request", "arguments": args_dict}
902        return self.send_recv(command_dict)
903
904    def request_pause(self, threadId=None):
905        if self.exit_status is not None:
906            raise ValueError("request_pause called after process exited")
907        if threadId is None:
908            threadId = self.get_thread_id()
909        args_dict = {"threadId": threadId}
910        command_dict = {"command": "pause", "type": "request", "arguments": args_dict}
911        return self.send_recv(command_dict)
912
913    def request_scopes(self, frameId):
914        args_dict = {"frameId": frameId}
915        command_dict = {"command": "scopes", "type": "request", "arguments": args_dict}
916        return self.send_recv(command_dict)
917
918    def request_setBreakpoints(self, file_path, line_array, data=None):
919        """data is array of parameters for breakpoints in line_array.
920        Each parameter object is 1:1 mapping with entries in line_entry.
921        It contains optional location/hitCondition/logMessage parameters.
922        """
923        (dir, base) = os.path.split(file_path)
924        source_dict = {"name": base, "path": file_path}
925        args_dict = {
926            "source": source_dict,
927            "sourceModified": False,
928        }
929        if line_array is not None:
930            args_dict["lines"] = "%s" % line_array
931            breakpoints = []
932            for i, line in enumerate(line_array):
933                breakpoint_data = None
934                if data is not None and i < len(data):
935                    breakpoint_data = data[i]
936                bp = {"line": line}
937                if breakpoint_data is not None:
938                    if breakpoint_data.get("condition"):
939                        bp["condition"] = breakpoint_data["condition"]
940                    if breakpoint_data.get("hitCondition"):
941                        bp["hitCondition"] = breakpoint_data["hitCondition"]
942                    if breakpoint_data.get("logMessage"):
943                        bp["logMessage"] = breakpoint_data["logMessage"]
944                    if breakpoint_data.get("column"):
945                        bp["column"] = breakpoint_data["column"]
946                breakpoints.append(bp)
947            args_dict["breakpoints"] = breakpoints
948
949        command_dict = {
950            "command": "setBreakpoints",
951            "type": "request",
952            "arguments": args_dict,
953        }
954        return self.send_recv(command_dict)
955
956    def request_setExceptionBreakpoints(self, filters):
957        args_dict = {"filters": filters}
958        command_dict = {
959            "command": "setExceptionBreakpoints",
960            "type": "request",
961            "arguments": args_dict,
962        }
963        return self.send_recv(command_dict)
964
965    def request_setFunctionBreakpoints(self, names, condition=None, hitCondition=None):
966        breakpoints = []
967        for name in names:
968            bp = {"name": name}
969            if condition is not None:
970                bp["condition"] = condition
971            if hitCondition is not None:
972                bp["hitCondition"] = hitCondition
973            breakpoints.append(bp)
974        args_dict = {"breakpoints": breakpoints}
975        command_dict = {
976            "command": "setFunctionBreakpoints",
977            "type": "request",
978            "arguments": args_dict,
979        }
980        return self.send_recv(command_dict)
981
982    def request_dataBreakpointInfo(
983        self, variablesReference, name, frameIndex=0, threadId=None
984    ):
985        stackFrame = self.get_stackFrame(frameIndex=frameIndex, threadId=threadId)
986        if stackFrame is None:
987            return []
988        args_dict = {
989            "variablesReference": variablesReference,
990            "name": name,
991            "frameId": stackFrame["id"],
992        }
993        command_dict = {
994            "command": "dataBreakpointInfo",
995            "type": "request",
996            "arguments": args_dict,
997        }
998        return self.send_recv(command_dict)
999
1000    def request_setDataBreakpoint(self, dataBreakpoints):
1001        """dataBreakpoints is a list of dictionary with following fields:
1002        {
1003            dataId: (address in hex)/(size in bytes)
1004            accessType: read/write/readWrite
1005            [condition]: string
1006            [hitCondition]: string
1007        }
1008        """
1009        args_dict = {"breakpoints": dataBreakpoints}
1010        command_dict = {
1011            "command": "setDataBreakpoints",
1012            "type": "request",
1013            "arguments": args_dict,
1014        }
1015        return self.send_recv(command_dict)
1016
1017    def request_compileUnits(self, moduleId):
1018        args_dict = {"moduleId": moduleId}
1019        command_dict = {
1020            "command": "compileUnits",
1021            "type": "request",
1022            "arguments": args_dict,
1023        }
1024        response = self.send_recv(command_dict)
1025        return response
1026
1027    def request_completions(self, text, frameId=None):
1028        args_dict = {"text": text, "column": len(text) + 1}
1029        if frameId:
1030            args_dict["frameId"] = frameId
1031        command_dict = {
1032            "command": "completions",
1033            "type": "request",
1034            "arguments": args_dict,
1035        }
1036        return self.send_recv(command_dict)
1037
1038    def request_modules(self):
1039        return self.send_recv({"command": "modules", "type": "request"})
1040
1041    def request_stackTrace(
1042        self, threadId=None, startFrame=None, levels=None, dump=False
1043    ):
1044        if threadId is None:
1045            threadId = self.get_thread_id()
1046        args_dict = {"threadId": threadId}
1047        if startFrame is not None:
1048            args_dict["startFrame"] = startFrame
1049        if levels is not None:
1050            args_dict["levels"] = levels
1051        command_dict = {
1052            "command": "stackTrace",
1053            "type": "request",
1054            "arguments": args_dict,
1055        }
1056        response = self.send_recv(command_dict)
1057        if dump:
1058            for idx, frame in enumerate(response["body"]["stackFrames"]):
1059                name = frame["name"]
1060                if "line" in frame and "source" in frame:
1061                    source = frame["source"]
1062                    if "sourceReference" not in source:
1063                        if "name" in source:
1064                            source_name = source["name"]
1065                            line = frame["line"]
1066                            print("[%3u] %s @ %s:%u" % (idx, name, source_name, line))
1067                            continue
1068                print("[%3u] %s" % (idx, name))
1069        return response
1070
1071    def request_threads(self):
1072        """Request a list of all threads and combine any information from any
1073        "stopped" events since those contain more information about why a
1074        thread actually stopped. Returns an array of thread dictionaries
1075        with information about all threads"""
1076        command_dict = {"command": "threads", "type": "request", "arguments": {}}
1077        response = self.send_recv(command_dict)
1078        body = response["body"]
1079        # Fill in "self.threads" correctly so that clients that call
1080        # self.get_threads() or self.get_thread_id(...) can get information
1081        # on threads when the process is stopped.
1082        if "threads" in body:
1083            self.threads = body["threads"]
1084            for thread in self.threads:
1085                # Copy the thread dictionary so we can add key/value pairs to
1086                # it without affecting the original info from the "threads"
1087                # command.
1088                tid = thread["id"]
1089                if tid in self.thread_stop_reasons:
1090                    thread_stop_info = self.thread_stop_reasons[tid]
1091                    copy_keys = ["reason", "description", "text"]
1092                    for key in copy_keys:
1093                        if key in thread_stop_info:
1094                            thread[key] = thread_stop_info[key]
1095        else:
1096            self.threads = None
1097        return response
1098
1099    def request_variables(
1100        self, variablesReference, start=None, count=None, is_hex=None
1101    ):
1102        args_dict = {"variablesReference": variablesReference}
1103        if start is not None:
1104            args_dict["start"] = start
1105        if count is not None:
1106            args_dict["count"] = count
1107        if is_hex is not None:
1108            args_dict["format"] = {"hex": is_hex}
1109        command_dict = {
1110            "command": "variables",
1111            "type": "request",
1112            "arguments": args_dict,
1113        }
1114        return self.send_recv(command_dict)
1115
1116    def request_setVariable(self, containingVarRef, name, value, id=None):
1117        args_dict = {
1118            "variablesReference": containingVarRef,
1119            "name": name,
1120            "value": str(value),
1121        }
1122        if id is not None:
1123            args_dict["id"] = id
1124        command_dict = {
1125            "command": "setVariable",
1126            "type": "request",
1127            "arguments": args_dict,
1128        }
1129        return self.send_recv(command_dict)
1130
1131    def request_locations(self, locationReference):
1132        args_dict = {
1133            "locationReference": locationReference,
1134        }
1135        command_dict = {
1136            "command": "locations",
1137            "type": "request",
1138            "arguments": args_dict,
1139        }
1140        return self.send_recv(command_dict)
1141
1142    def request_testGetTargetBreakpoints(self):
1143        """A request packet used in the LLDB test suite to get all currently
1144        set breakpoint infos for all breakpoints currently set in the
1145        target.
1146        """
1147        command_dict = {
1148            "command": "_testGetTargetBreakpoints",
1149            "type": "request",
1150            "arguments": {},
1151        }
1152        return self.send_recv(command_dict)
1153
1154    def terminate(self):
1155        self.send.close()
1156        # self.recv.close()
1157
1158    def request_setInstructionBreakpoints(self, memory_reference=[]):
1159        breakpoints = []
1160        for i in memory_reference:
1161            args_dict = {
1162                "instructionReference": i,
1163            }
1164            breakpoints.append(args_dict)
1165        args_dict = {"breakpoints": breakpoints}
1166        command_dict = {
1167            "command": "setInstructionBreakpoints",
1168            "type": "request",
1169            "arguments": args_dict,
1170        }
1171        return self.send_recv(command_dict)
1172
1173class DebugAdaptorServer(DebugCommunication):
1174    def __init__(
1175        self,
1176        executable=None,
1177        port=None,
1178        init_commands=[],
1179        log_file=None,
1180        env=None,
1181    ):
1182        self.process = None
1183        if executable is not None:
1184            adaptor_env = os.environ.copy()
1185            if env is not None:
1186                adaptor_env.update(env)
1187
1188            if log_file:
1189                adaptor_env["LLDBDAP_LOG"] = log_file
1190            self.process = subprocess.Popen(
1191                [executable],
1192                stdin=subprocess.PIPE,
1193                stdout=subprocess.PIPE,
1194                stderr=subprocess.PIPE,
1195                env=adaptor_env,
1196            )
1197            DebugCommunication.__init__(
1198                self, self.process.stdout, self.process.stdin, init_commands, log_file
1199            )
1200        elif port is not None:
1201            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1202            s.connect(("127.0.0.1", port))
1203            DebugCommunication.__init__(
1204                self, s.makefile("r"), s.makefile("w"), init_commands
1205            )
1206
1207    def get_pid(self):
1208        if self.process:
1209            return self.process.pid
1210        return -1
1211
1212    def terminate(self):
1213        super(DebugAdaptorServer, self).terminate()
1214        if self.process is not None:
1215            self.process.terminate()
1216            self.process.wait()
1217            self.process = None
1218
1219
1220def attach_options_specified(options):
1221    if options.pid is not None:
1222        return True
1223    if options.waitFor:
1224        return True
1225    if options.attach:
1226        return True
1227    if options.attachCmds:
1228        return True
1229    return False
1230
1231
1232def run_vscode(dbg, args, options):
1233    dbg.request_initialize(options.sourceInitFile)
1234    if attach_options_specified(options):
1235        response = dbg.request_attach(
1236            program=options.program,
1237            pid=options.pid,
1238            waitFor=options.waitFor,
1239            attachCommands=options.attachCmds,
1240            initCommands=options.initCmds,
1241            preRunCommands=options.preRunCmds,
1242            stopCommands=options.stopCmds,
1243            exitCommands=options.exitCmds,
1244            terminateCommands=options.terminateCmds,
1245        )
1246    else:
1247        response = dbg.request_launch(
1248            options.program,
1249            args=args,
1250            env=options.envs,
1251            cwd=options.workingDir,
1252            debuggerRoot=options.debuggerRoot,
1253            sourcePath=options.sourcePath,
1254            initCommands=options.initCmds,
1255            preRunCommands=options.preRunCmds,
1256            stopCommands=options.stopCmds,
1257            exitCommands=options.exitCmds,
1258            terminateCommands=options.terminateCmds,
1259        )
1260
1261    if response["success"]:
1262        if options.sourceBreakpoints:
1263            source_to_lines = {}
1264            for file_line in options.sourceBreakpoints:
1265                (path, line) = file_line.split(":")
1266                if len(path) == 0 or len(line) == 0:
1267                    print('error: invalid source with line "%s"' % (file_line))
1268
1269                else:
1270                    if path in source_to_lines:
1271                        source_to_lines[path].append(int(line))
1272                    else:
1273                        source_to_lines[path] = [int(line)]
1274            for source in source_to_lines:
1275                dbg.request_setBreakpoints(source, source_to_lines[source])
1276        if options.funcBreakpoints:
1277            dbg.request_setFunctionBreakpoints(options.funcBreakpoints)
1278        dbg.request_configurationDone()
1279        dbg.wait_for_stopped()
1280    else:
1281        if "message" in response:
1282            print(response["message"])
1283    dbg.request_disconnect(terminateDebuggee=True)
1284
1285
1286def main():
1287    parser = optparse.OptionParser(
1288        description=(
1289            "A testing framework for the Visual Studio Code Debug Adaptor protocol"
1290        )
1291    )
1292
1293    parser.add_option(
1294        "--vscode",
1295        type="string",
1296        dest="vscode_path",
1297        help=(
1298            "The path to the command line program that implements the "
1299            "Visual Studio Code Debug Adaptor protocol."
1300        ),
1301        default=None,
1302    )
1303
1304    parser.add_option(
1305        "--program",
1306        type="string",
1307        dest="program",
1308        help="The path to the program to debug.",
1309        default=None,
1310    )
1311
1312    parser.add_option(
1313        "--workingDir",
1314        type="string",
1315        dest="workingDir",
1316        default=None,
1317        help="Set the working directory for the process we launch.",
1318    )
1319
1320    parser.add_option(
1321        "--sourcePath",
1322        type="string",
1323        dest="sourcePath",
1324        default=None,
1325        help=(
1326            "Set the relative source root for any debug info that has "
1327            "relative paths in it."
1328        ),
1329    )
1330
1331    parser.add_option(
1332        "--debuggerRoot",
1333        type="string",
1334        dest="debuggerRoot",
1335        default=None,
1336        help=(
1337            "Set the working directory for lldb-dap for any object files "
1338            "with relative paths in the Mach-o debug map."
1339        ),
1340    )
1341
1342    parser.add_option(
1343        "-r",
1344        "--replay",
1345        type="string",
1346        dest="replay",
1347        help=(
1348            "Specify a file containing a packet log to replay with the "
1349            "current Visual Studio Code Debug Adaptor executable."
1350        ),
1351        default=None,
1352    )
1353
1354    parser.add_option(
1355        "-g",
1356        "--debug",
1357        action="store_true",
1358        dest="debug",
1359        default=False,
1360        help="Pause waiting for a debugger to attach to the debug adaptor",
1361    )
1362
1363    parser.add_option(
1364        "--sourceInitFile",
1365        action="store_true",
1366        dest="sourceInitFile",
1367        default=False,
1368        help="Whether lldb-dap should source .lldbinit file or not",
1369    )
1370
1371    parser.add_option(
1372        "--port",
1373        type="int",
1374        dest="port",
1375        help="Attach a socket to a port instead of using STDIN for VSCode",
1376        default=None,
1377    )
1378
1379    parser.add_option(
1380        "--pid",
1381        type="int",
1382        dest="pid",
1383        help="The process ID to attach to",
1384        default=None,
1385    )
1386
1387    parser.add_option(
1388        "--attach",
1389        action="store_true",
1390        dest="attach",
1391        default=False,
1392        help=(
1393            "Specify this option to attach to a process by name. The "
1394            "process name is the basename of the executable specified with "
1395            "the --program option."
1396        ),
1397    )
1398
1399    parser.add_option(
1400        "-f",
1401        "--function-bp",
1402        type="string",
1403        action="append",
1404        dest="funcBreakpoints",
1405        help=(
1406            "Specify the name of a function to break at. "
1407            "Can be specified more than once."
1408        ),
1409        default=[],
1410    )
1411
1412    parser.add_option(
1413        "-s",
1414        "--source-bp",
1415        type="string",
1416        action="append",
1417        dest="sourceBreakpoints",
1418        default=[],
1419        help=(
1420            "Specify source breakpoints to set in the format of "
1421            "<source>:<line>. "
1422            "Can be specified more than once."
1423        ),
1424    )
1425
1426    parser.add_option(
1427        "--attachCommand",
1428        type="string",
1429        action="append",
1430        dest="attachCmds",
1431        default=[],
1432        help=(
1433            "Specify a LLDB command that will attach to a process. "
1434            "Can be specified more than once."
1435        ),
1436    )
1437
1438    parser.add_option(
1439        "--initCommand",
1440        type="string",
1441        action="append",
1442        dest="initCmds",
1443        default=[],
1444        help=(
1445            "Specify a LLDB command that will be executed before the target "
1446            "is created. Can be specified more than once."
1447        ),
1448    )
1449
1450    parser.add_option(
1451        "--preRunCommand",
1452        type="string",
1453        action="append",
1454        dest="preRunCmds",
1455        default=[],
1456        help=(
1457            "Specify a LLDB command that will be executed after the target "
1458            "has been created. Can be specified more than once."
1459        ),
1460    )
1461
1462    parser.add_option(
1463        "--stopCommand",
1464        type="string",
1465        action="append",
1466        dest="stopCmds",
1467        default=[],
1468        help=(
1469            "Specify a LLDB command that will be executed each time the"
1470            "process stops. Can be specified more than once."
1471        ),
1472    )
1473
1474    parser.add_option(
1475        "--exitCommand",
1476        type="string",
1477        action="append",
1478        dest="exitCmds",
1479        default=[],
1480        help=(
1481            "Specify a LLDB command that will be executed when the process "
1482            "exits. Can be specified more than once."
1483        ),
1484    )
1485
1486    parser.add_option(
1487        "--terminateCommand",
1488        type="string",
1489        action="append",
1490        dest="terminateCmds",
1491        default=[],
1492        help=(
1493            "Specify a LLDB command that will be executed when the debugging "
1494            "session is terminated. Can be specified more than once."
1495        ),
1496    )
1497
1498    parser.add_option(
1499        "--env",
1500        type="string",
1501        action="append",
1502        dest="envs",
1503        default=[],
1504        help=("Specify environment variables to pass to the launched " "process."),
1505    )
1506
1507    parser.add_option(
1508        "--waitFor",
1509        action="store_true",
1510        dest="waitFor",
1511        default=False,
1512        help=(
1513            "Wait for the next process to be launched whose name matches "
1514            "the basename of the program specified with the --program "
1515            "option"
1516        ),
1517    )
1518
1519    (options, args) = parser.parse_args(sys.argv[1:])
1520
1521    if options.vscode_path is None and options.port is None:
1522        print(
1523            "error: must either specify a path to a Visual Studio Code "
1524            "Debug Adaptor vscode executable path using the --vscode "
1525            "option, or a port to attach to for an existing lldb-dap "
1526            "using the --port option"
1527        )
1528        return
1529    dbg = DebugAdaptorServer(executable=options.vscode_path, port=options.port)
1530    if options.debug:
1531        raw_input('Waiting for debugger to attach pid "%i"' % (dbg.get_pid()))
1532    if options.replay:
1533        dbg.replay_packets(options.replay)
1534    else:
1535        run_vscode(dbg, args, options)
1536    dbg.terminate()
1537
1538
1539if __name__ == "__main__":
1540    main()
1541