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