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