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