xref: /llvm-project/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py (revision 19ecdedcd2b7fb44689ce3cd59a6a078539eef3f)
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        displayExtendedBacktrace=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["displayExtendedBacktrace"] = displayExtendedBacktrace
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_locations(self, locationReference):
1113        args_dict = {
1114            "locationReference": locationReference,
1115        }
1116        command_dict = {
1117            "command": "locations",
1118            "type": "request",
1119            "arguments": args_dict,
1120        }
1121        return self.send_recv(command_dict)
1122
1123    def request_testGetTargetBreakpoints(self):
1124        """A request packet used in the LLDB test suite to get all currently
1125        set breakpoint infos for all breakpoints currently set in the
1126        target.
1127        """
1128        command_dict = {
1129            "command": "_testGetTargetBreakpoints",
1130            "type": "request",
1131            "arguments": {},
1132        }
1133        return self.send_recv(command_dict)
1134
1135    def terminate(self):
1136        self.send.close()
1137        # self.recv.close()
1138
1139    def request_setInstructionBreakpoints(self, memory_reference=[]):
1140        breakpoints = []
1141        for i in memory_reference:
1142            args_dict = {
1143                "instructionReference": i,
1144            }
1145            breakpoints.append(args_dict)
1146        args_dict = {"breakpoints": breakpoints}
1147        command_dict = {
1148            "command": "setInstructionBreakpoints",
1149            "type": "request",
1150            "arguments": args_dict,
1151        }
1152        return self.send_recv(command_dict)
1153
1154class DebugAdaptorServer(DebugCommunication):
1155    def __init__(
1156        self,
1157        executable=None,
1158        port=None,
1159        init_commands=[],
1160        log_file=None,
1161        env=None,
1162    ):
1163        self.process = None
1164        if executable is not None:
1165            adaptor_env = os.environ.copy()
1166            if env is not None:
1167                adaptor_env.update(env)
1168
1169            if log_file:
1170                adaptor_env["LLDBDAP_LOG"] = log_file
1171            self.process = subprocess.Popen(
1172                [executable],
1173                stdin=subprocess.PIPE,
1174                stdout=subprocess.PIPE,
1175                stderr=subprocess.PIPE,
1176                env=adaptor_env,
1177            )
1178            DebugCommunication.__init__(
1179                self, self.process.stdout, self.process.stdin, init_commands, log_file
1180            )
1181        elif port is not None:
1182            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1183            s.connect(("127.0.0.1", port))
1184            DebugCommunication.__init__(
1185                self, s.makefile("r"), s.makefile("w"), init_commands
1186            )
1187
1188    def get_pid(self):
1189        if self.process:
1190            return self.process.pid
1191        return -1
1192
1193    def terminate(self):
1194        super(DebugAdaptorServer, self).terminate()
1195        if self.process is not None:
1196            self.process.terminate()
1197            self.process.wait()
1198            self.process = None
1199
1200
1201def attach_options_specified(options):
1202    if options.pid is not None:
1203        return True
1204    if options.waitFor:
1205        return True
1206    if options.attach:
1207        return True
1208    if options.attachCmds:
1209        return True
1210    return False
1211
1212
1213def run_vscode(dbg, args, options):
1214    dbg.request_initialize(options.sourceInitFile)
1215    if attach_options_specified(options):
1216        response = dbg.request_attach(
1217            program=options.program,
1218            pid=options.pid,
1219            waitFor=options.waitFor,
1220            attachCommands=options.attachCmds,
1221            initCommands=options.initCmds,
1222            preRunCommands=options.preRunCmds,
1223            stopCommands=options.stopCmds,
1224            exitCommands=options.exitCmds,
1225            terminateCommands=options.terminateCmds,
1226        )
1227    else:
1228        response = dbg.request_launch(
1229            options.program,
1230            args=args,
1231            env=options.envs,
1232            cwd=options.workingDir,
1233            debuggerRoot=options.debuggerRoot,
1234            sourcePath=options.sourcePath,
1235            initCommands=options.initCmds,
1236            preRunCommands=options.preRunCmds,
1237            stopCommands=options.stopCmds,
1238            exitCommands=options.exitCmds,
1239            terminateCommands=options.terminateCmds,
1240        )
1241
1242    if response["success"]:
1243        if options.sourceBreakpoints:
1244            source_to_lines = {}
1245            for file_line in options.sourceBreakpoints:
1246                (path, line) = file_line.split(":")
1247                if len(path) == 0 or len(line) == 0:
1248                    print('error: invalid source with line "%s"' % (file_line))
1249
1250                else:
1251                    if path in source_to_lines:
1252                        source_to_lines[path].append(int(line))
1253                    else:
1254                        source_to_lines[path] = [int(line)]
1255            for source in source_to_lines:
1256                dbg.request_setBreakpoints(source, source_to_lines[source])
1257        if options.funcBreakpoints:
1258            dbg.request_setFunctionBreakpoints(options.funcBreakpoints)
1259        dbg.request_configurationDone()
1260        dbg.wait_for_stopped()
1261    else:
1262        if "message" in response:
1263            print(response["message"])
1264    dbg.request_disconnect(terminateDebuggee=True)
1265
1266
1267def main():
1268    parser = optparse.OptionParser(
1269        description=(
1270            "A testing framework for the Visual Studio Code Debug " "Adaptor protocol"
1271        )
1272    )
1273
1274    parser.add_option(
1275        "--vscode",
1276        type="string",
1277        dest="vscode_path",
1278        help=(
1279            "The path to the command line program that implements the "
1280            "Visual Studio Code Debug Adaptor protocol."
1281        ),
1282        default=None,
1283    )
1284
1285    parser.add_option(
1286        "--program",
1287        type="string",
1288        dest="program",
1289        help="The path to the program to debug.",
1290        default=None,
1291    )
1292
1293    parser.add_option(
1294        "--workingDir",
1295        type="string",
1296        dest="workingDir",
1297        default=None,
1298        help="Set the working directory for the process we launch.",
1299    )
1300
1301    parser.add_option(
1302        "--sourcePath",
1303        type="string",
1304        dest="sourcePath",
1305        default=None,
1306        help=(
1307            "Set the relative source root for any debug info that has "
1308            "relative paths in it."
1309        ),
1310    )
1311
1312    parser.add_option(
1313        "--debuggerRoot",
1314        type="string",
1315        dest="debuggerRoot",
1316        default=None,
1317        help=(
1318            "Set the working directory for lldb-dap for any object files "
1319            "with relative paths in the Mach-o debug map."
1320        ),
1321    )
1322
1323    parser.add_option(
1324        "-r",
1325        "--replay",
1326        type="string",
1327        dest="replay",
1328        help=(
1329            "Specify a file containing a packet log to replay with the "
1330            "current Visual Studio Code Debug Adaptor executable."
1331        ),
1332        default=None,
1333    )
1334
1335    parser.add_option(
1336        "-g",
1337        "--debug",
1338        action="store_true",
1339        dest="debug",
1340        default=False,
1341        help="Pause waiting for a debugger to attach to the debug adaptor",
1342    )
1343
1344    parser.add_option(
1345        "--sourceInitFile",
1346        action="store_true",
1347        dest="sourceInitFile",
1348        default=False,
1349        help="Whether lldb-dap should source .lldbinit file or not",
1350    )
1351
1352    parser.add_option(
1353        "--port",
1354        type="int",
1355        dest="port",
1356        help="Attach a socket to a port instead of using STDIN for VSCode",
1357        default=None,
1358    )
1359
1360    parser.add_option(
1361        "--pid",
1362        type="int",
1363        dest="pid",
1364        help="The process ID to attach to",
1365        default=None,
1366    )
1367
1368    parser.add_option(
1369        "--attach",
1370        action="store_true",
1371        dest="attach",
1372        default=False,
1373        help=(
1374            "Specify this option to attach to a process by name. The "
1375            "process name is the basename of the executable specified with "
1376            "the --program option."
1377        ),
1378    )
1379
1380    parser.add_option(
1381        "-f",
1382        "--function-bp",
1383        type="string",
1384        action="append",
1385        dest="funcBreakpoints",
1386        help=(
1387            "Specify the name of a function to break at. "
1388            "Can be specified more than once."
1389        ),
1390        default=[],
1391    )
1392
1393    parser.add_option(
1394        "-s",
1395        "--source-bp",
1396        type="string",
1397        action="append",
1398        dest="sourceBreakpoints",
1399        default=[],
1400        help=(
1401            "Specify source breakpoints to set in the format of "
1402            "<source>:<line>. "
1403            "Can be specified more than once."
1404        ),
1405    )
1406
1407    parser.add_option(
1408        "--attachCommand",
1409        type="string",
1410        action="append",
1411        dest="attachCmds",
1412        default=[],
1413        help=(
1414            "Specify a LLDB command that will attach to a process. "
1415            "Can be specified more than once."
1416        ),
1417    )
1418
1419    parser.add_option(
1420        "--initCommand",
1421        type="string",
1422        action="append",
1423        dest="initCmds",
1424        default=[],
1425        help=(
1426            "Specify a LLDB command that will be executed before the target "
1427            "is created. Can be specified more than once."
1428        ),
1429    )
1430
1431    parser.add_option(
1432        "--preRunCommand",
1433        type="string",
1434        action="append",
1435        dest="preRunCmds",
1436        default=[],
1437        help=(
1438            "Specify a LLDB command that will be executed after the target "
1439            "has been created. Can be specified more than once."
1440        ),
1441    )
1442
1443    parser.add_option(
1444        "--stopCommand",
1445        type="string",
1446        action="append",
1447        dest="stopCmds",
1448        default=[],
1449        help=(
1450            "Specify a LLDB command that will be executed each time the"
1451            "process stops. Can be specified more than once."
1452        ),
1453    )
1454
1455    parser.add_option(
1456        "--exitCommand",
1457        type="string",
1458        action="append",
1459        dest="exitCmds",
1460        default=[],
1461        help=(
1462            "Specify a LLDB command that will be executed when the process "
1463            "exits. Can be specified more than once."
1464        ),
1465    )
1466
1467    parser.add_option(
1468        "--terminateCommand",
1469        type="string",
1470        action="append",
1471        dest="terminateCmds",
1472        default=[],
1473        help=(
1474            "Specify a LLDB command that will be executed when the debugging "
1475            "session is terminated. Can be specified more than once."
1476        ),
1477    )
1478
1479    parser.add_option(
1480        "--env",
1481        type="string",
1482        action="append",
1483        dest="envs",
1484        default=[],
1485        help=("Specify environment variables to pass to the launched " "process."),
1486    )
1487
1488    parser.add_option(
1489        "--waitFor",
1490        action="store_true",
1491        dest="waitFor",
1492        default=False,
1493        help=(
1494            "Wait for the next process to be launched whose name matches "
1495            "the basename of the program specified with the --program "
1496            "option"
1497        ),
1498    )
1499
1500    (options, args) = parser.parse_args(sys.argv[1:])
1501
1502    if options.vscode_path is None and options.port is None:
1503        print(
1504            "error: must either specify a path to a Visual Studio Code "
1505            "Debug Adaptor vscode executable path using the --vscode "
1506            "option, or a port to attach to for an existing lldb-dap "
1507            "using the --port option"
1508        )
1509        return
1510    dbg = DebugAdaptorServer(executable=options.vscode_path, port=options.port)
1511    if options.debug:
1512        raw_input('Waiting for debugger to attach pid "%i"' % (dbg.get_pid()))
1513    if options.replay:
1514        dbg.replay_packets(options.replay)
1515    else:
1516        run_vscode(dbg, args, options)
1517    dbg.terminate()
1518
1519
1520if __name__ == "__main__":
1521    main()
1522