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