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