xref: /llvm-project/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py (revision 1654d7dc38af3bd10ab55929161966601672c115)
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, duration, clear=True):
175        end_time = time.time() + duration
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        return collected_output if collected_output else None
182
183    def enqueue_recv_packet(self, packet):
184        self.recv_condition.acquire()
185        self.recv_packets.append(packet)
186        self.recv_condition.notify()
187        self.recv_condition.release()
188
189    def handle_recv_packet(self, packet):
190        """Called by the read thread that is waiting for all incoming packets
191        to store the incoming packet in "self.recv_packets" in a thread safe
192        way. This function will then signal the "self.recv_condition" to
193        indicate a new packet is available. Returns True if the caller
194        should keep calling this function for more packets.
195        """
196        # If EOF, notify the read thread by enqueuing a None.
197        if not packet:
198            self.enqueue_recv_packet(None)
199            return False
200
201        # Check the packet to see if is an event packet
202        keepGoing = True
203        packet_type = packet["type"]
204        if packet_type == "event":
205            event = packet["event"]
206            body = None
207            if "body" in packet:
208                body = packet["body"]
209            # Handle the event packet and cache information from these packets
210            # as they come in
211            if event == "output":
212                # Store any output we receive so clients can retrieve it later.
213                category = body["category"]
214                output = body["output"]
215                self.output_condition.acquire()
216                if category in self.output:
217                    self.output[category] += output
218                else:
219                    self.output[category] = output
220                self.output_condition.notify()
221                self.output_condition.release()
222                # no need to add 'output' event packets to our packets list
223                return keepGoing
224            elif event == "process":
225                # When a new process is attached or launched, remember the
226                # details that are available in the body of the event
227                self.process_event_body = body
228            elif event == "stopped":
229                # Each thread that stops with a reason will send a
230                # 'stopped' event. We need to remember the thread stop
231                # reasons since the 'threads' command doesn't return
232                # that information.
233                self._process_stopped()
234                tid = body["threadId"]
235                self.thread_stop_reasons[tid] = body
236            elif event == "breakpoint":
237                # Breakpoint events come in when a breakpoint has locations
238                # added or removed. Keep track of them so we can look for them
239                # in tests.
240                self.breakpoint_events.append(packet)
241                # no need to add 'breakpoint' event packets to our packets list
242                return keepGoing
243            elif event.startswith("progress"):
244                # Progress events come in as 'progressStart', 'progressUpdate',
245                # and 'progressEnd' events. Keep these around in case test
246                # cases want to verify them.
247                self.progress_events.append(packet)
248                # No need to add 'progress' event packets to our packets list.
249                return keepGoing
250
251        elif packet_type == "response":
252            if packet["command"] == "disconnect":
253                keepGoing = False
254        self.enqueue_recv_packet(packet)
255        return keepGoing
256
257    def send_packet(self, command_dict, set_sequence=True):
258        """Take the "command_dict" python dictionary and encode it as a JSON
259        string and send the contents as a packet to the VSCode debug
260        adaptor"""
261        # Set the sequence ID for this command automatically
262        if set_sequence:
263            command_dict["seq"] = self.sequence
264            self.sequence += 1
265        # Encode our command dictionary as a JSON string
266        json_str = json.dumps(command_dict, separators=(",", ":"))
267        if self.trace_file:
268            self.trace_file.write("to adaptor:\n%s\n" % (json_str))
269        length = len(json_str)
270        if length > 0:
271            # Send the encoded JSON packet and flush the 'send' file
272            self.send.write(self.encode_content(json_str))
273            self.send.flush()
274
275    def recv_packet(self, filter_type=None, filter_event=None, timeout=None):
276        """Get a JSON packet from the VSCode debug adaptor. This function
277        assumes a thread that reads packets is running and will deliver
278        any received packets by calling handle_recv_packet(...). This
279        function will wait for the packet to arrive and return it when
280        it does."""
281        while True:
282            try:
283                self.recv_condition.acquire()
284                packet = None
285                while True:
286                    for i, curr_packet in enumerate(self.recv_packets):
287                        if not curr_packet:
288                            raise EOFError
289                        packet_type = curr_packet["type"]
290                        if filter_type is None or packet_type in filter_type:
291                            if filter_event is None or (
292                                packet_type == "event"
293                                and curr_packet["event"] in filter_event
294                            ):
295                                packet = self.recv_packets.pop(i)
296                                break
297                    if packet:
298                        break
299                    # Sleep until packet is received
300                    len_before = len(self.recv_packets)
301                    self.recv_condition.wait(timeout)
302                    len_after = len(self.recv_packets)
303                    if len_before == len_after:
304                        return None  # Timed out
305                return packet
306            except EOFError:
307                return None
308            finally:
309                self.recv_condition.release()
310
311        return None
312
313    def send_recv(self, command):
314        """Send a command python dictionary as JSON and receive the JSON
315        response. Validates that the response is the correct sequence and
316        command in the reply. Any events that are received are added to the
317        events list in this object"""
318        self.send_packet(command)
319        done = False
320        while not done:
321            response_or_request = self.recv_packet(filter_type=["response", "request"])
322            if response_or_request is None:
323                desc = 'no response for "%s"' % (command["command"])
324                raise ValueError(desc)
325            if response_or_request["type"] == "response":
326                self.validate_response(command, response_or_request)
327                return response_or_request
328            else:
329                self.reverse_requests.append(response_or_request)
330                if response_or_request["command"] == "runInTerminal":
331                    subprocess.Popen(
332                        response_or_request["arguments"]["args"],
333                        env=response_or_request["arguments"]["env"],
334                    )
335                    self.send_packet(
336                        {
337                            "type": "response",
338                            "seq": -1,
339                            "request_seq": response_or_request["seq"],
340                            "success": True,
341                            "command": "runInTerminal",
342                            "body": {},
343                        },
344                        set_sequence=False,
345                    )
346                elif response_or_request["command"] == "startDebugging":
347                    self.send_packet(
348                        {
349                            "type": "response",
350                            "seq": -1,
351                            "request_seq": response_or_request["seq"],
352                            "success": True,
353                            "command": "startDebugging",
354                            "body": {},
355                        },
356                        set_sequence=False,
357                    )
358                else:
359                    desc = 'unknown reverse request "%s"' % (
360                        response_or_request["command"]
361                    )
362                    raise ValueError(desc)
363
364        return None
365
366    def wait_for_event(self, filter=None, timeout=None):
367        while True:
368            return self.recv_packet(
369                filter_type="event", filter_event=filter, timeout=timeout
370            )
371        return None
372
373    def wait_for_stopped(self, timeout=None):
374        stopped_events = []
375        stopped_event = self.wait_for_event(
376            filter=["stopped", "exited"], timeout=timeout
377        )
378        exited = False
379        while stopped_event:
380            stopped_events.append(stopped_event)
381            # If we exited, then we are done
382            if stopped_event["event"] == "exited":
383                self.exit_status = stopped_event["body"]["exitCode"]
384                exited = True
385                break
386            # Otherwise we stopped and there might be one or more 'stopped'
387            # events for each thread that stopped with a reason, so keep
388            # checking for more 'stopped' events and return all of them
389            stopped_event = self.wait_for_event(filter="stopped", timeout=0.25)
390        if exited:
391            self.threads = []
392        return stopped_events
393
394    def wait_for_exited(self):
395        event_dict = self.wait_for_event("exited")
396        if event_dict is None:
397            raise ValueError("didn't get exited event")
398        return event_dict
399
400    def wait_for_terminated(self):
401        event_dict = self.wait_for_event("terminated")
402        if event_dict is None:
403            raise ValueError("didn't get terminated event")
404        return event_dict
405
406    def get_initialize_value(self, key):
407        """Get a value for the given key if it there is a key/value pair in
408        the "initialize" request response body.
409        """
410        if self.initialize_body and key in self.initialize_body:
411            return self.initialize_body[key]
412        return None
413
414    def get_threads(self):
415        if self.threads is None:
416            self.request_threads()
417        return self.threads
418
419    def get_thread_id(self, threadIndex=0):
420        """Utility function to get the first thread ID in the thread list.
421        If the thread list is empty, then fetch the threads.
422        """
423        if self.threads is None:
424            self.request_threads()
425        if self.threads and threadIndex < len(self.threads):
426            return self.threads[threadIndex]["id"]
427        return None
428
429    def get_stackFrame(self, frameIndex=0, threadId=None):
430        """Get a single "StackFrame" object from a "stackTrace" request and
431        return the "StackFrame" as a python dictionary, or None on failure
432        """
433        if threadId is None:
434            threadId = self.get_thread_id()
435        if threadId is None:
436            print("invalid threadId")
437            return None
438        response = self.request_stackTrace(threadId, startFrame=frameIndex, levels=1)
439        if response:
440            return response["body"]["stackFrames"][0]
441        print("invalid response")
442        return None
443
444    def get_completions(self, text, frameId=None):
445        if frameId is None:
446            stackFrame = self.get_stackFrame()
447            frameId = stackFrame["id"]
448        response = self.request_completions(text, frameId)
449        return response["body"]["targets"]
450
451    def get_scope_variables(self, scope_name, frameIndex=0, threadId=None):
452        stackFrame = self.get_stackFrame(frameIndex=frameIndex, threadId=threadId)
453        if stackFrame is None:
454            return []
455        frameId = stackFrame["id"]
456        if frameId in self.frame_scopes:
457            frame_scopes = self.frame_scopes[frameId]
458        else:
459            scopes_response = self.request_scopes(frameId)
460            frame_scopes = scopes_response["body"]["scopes"]
461            self.frame_scopes[frameId] = frame_scopes
462        for scope in frame_scopes:
463            if scope["name"] == scope_name:
464                varRef = scope["variablesReference"]
465                variables_response = self.request_variables(varRef)
466                if variables_response:
467                    if "body" in variables_response:
468                        body = variables_response["body"]
469                        if "variables" in body:
470                            vars = body["variables"]
471                            return vars
472        return []
473
474    def get_global_variables(self, frameIndex=0, threadId=None):
475        return self.get_scope_variables(
476            "Globals", frameIndex=frameIndex, threadId=threadId
477        )
478
479    def get_local_variables(self, frameIndex=0, threadId=None):
480        return self.get_scope_variables(
481            "Locals", frameIndex=frameIndex, threadId=threadId
482        )
483
484    def get_registers(self, frameIndex=0, threadId=None):
485        return self.get_scope_variables(
486            "Registers", frameIndex=frameIndex, threadId=threadId
487        )
488
489    def get_local_variable(self, name, frameIndex=0, threadId=None):
490        locals = self.get_local_variables(frameIndex=frameIndex, threadId=threadId)
491        for local in locals:
492            if "name" in local and local["name"] == name:
493                return local
494        return None
495
496    def get_local_variable_value(self, name, frameIndex=0, threadId=None):
497        variable = self.get_local_variable(
498            name, frameIndex=frameIndex, threadId=threadId
499        )
500        if variable and "value" in variable:
501            return variable["value"]
502        return None
503
504    def replay_packets(self, replay_file_path):
505        f = open(replay_file_path, "r")
506        mode = "invalid"
507        set_sequence = False
508        command_dict = None
509        while mode != "eof":
510            if mode == "invalid":
511                line = f.readline()
512                if line.startswith("to adapter:"):
513                    mode = "send"
514                elif line.startswith("from adapter:"):
515                    mode = "recv"
516            elif mode == "send":
517                command_dict = read_packet(f)
518                # Skip the end of line that follows the JSON
519                f.readline()
520                if command_dict is None:
521                    raise ValueError("decode packet failed from replay file")
522                print("Sending:")
523                pprint.PrettyPrinter(indent=2).pprint(command_dict)
524                # raw_input('Press ENTER to send:')
525                self.send_packet(command_dict, set_sequence)
526                mode = "invalid"
527            elif mode == "recv":
528                print("Replay response:")
529                replay_response = read_packet(f)
530                # Skip the end of line that follows the JSON
531                f.readline()
532                pprint.PrettyPrinter(indent=2).pprint(replay_response)
533                actual_response = self.recv_packet()
534                if actual_response:
535                    type = actual_response["type"]
536                    print("Actual response:")
537                    if type == "response":
538                        self.validate_response(command_dict, actual_response)
539                    pprint.PrettyPrinter(indent=2).pprint(actual_response)
540                else:
541                    print("error: didn't get a valid response")
542                mode = "invalid"
543
544    def request_attach(
545        self,
546        program=None,
547        pid=None,
548        waitFor=None,
549        trace=None,
550        initCommands=None,
551        preRunCommands=None,
552        stopCommands=None,
553        exitCommands=None,
554        attachCommands=None,
555        terminateCommands=None,
556        coreFile=None,
557        postRunCommands=None,
558        sourceMap=None,
559    ):
560        args_dict = {}
561        if pid is not None:
562            args_dict["pid"] = pid
563        if program is not None:
564            args_dict["program"] = program
565        if waitFor is not None:
566            args_dict["waitFor"] = waitFor
567        if trace:
568            args_dict["trace"] = trace
569        args_dict["initCommands"] = self.init_commands
570        if initCommands:
571            args_dict["initCommands"].extend(initCommands)
572        if preRunCommands:
573            args_dict["preRunCommands"] = preRunCommands
574        if stopCommands:
575            args_dict["stopCommands"] = stopCommands
576        if exitCommands:
577            args_dict["exitCommands"] = exitCommands
578        if terminateCommands:
579            args_dict["terminateCommands"] = terminateCommands
580        if attachCommands:
581            args_dict["attachCommands"] = attachCommands
582        if coreFile:
583            args_dict["coreFile"] = coreFile
584        if postRunCommands:
585            args_dict["postRunCommands"] = postRunCommands
586        if sourceMap:
587            args_dict["sourceMap"] = sourceMap
588        command_dict = {"command": "attach", "type": "request", "arguments": args_dict}
589        return self.send_recv(command_dict)
590
591    def request_configurationDone(self):
592        command_dict = {
593            "command": "configurationDone",
594            "type": "request",
595            "arguments": {},
596        }
597        response = self.send_recv(command_dict)
598        if response:
599            self.configuration_done_sent = True
600        return response
601
602    def _process_stopped(self):
603        self.threads = None
604        self.frame_scopes = {}
605
606    def request_continue(self, threadId=None):
607        if self.exit_status is not None:
608            raise ValueError("request_continue called after process exited")
609        # If we have launched or attached, then the first continue is done by
610        # sending the 'configurationDone' request
611        if not self.configuration_done_sent:
612            return self.request_configurationDone()
613        args_dict = {}
614        if threadId is None:
615            threadId = self.get_thread_id()
616        args_dict["threadId"] = threadId
617        command_dict = {
618            "command": "continue",
619            "type": "request",
620            "arguments": args_dict,
621        }
622        response = self.send_recv(command_dict)
623        # Caller must still call wait_for_stopped.
624        return response
625
626    def request_restart(self, restartArguments=None):
627        command_dict = {
628            "command": "restart",
629            "type": "request",
630        }
631        if restartArguments:
632            command_dict["arguments"] = restartArguments
633
634        response = self.send_recv(command_dict)
635        # Caller must still call wait_for_stopped.
636        return response
637
638    def request_disconnect(self, terminateDebuggee=None):
639        args_dict = {}
640        if terminateDebuggee is not None:
641            if terminateDebuggee:
642                args_dict["terminateDebuggee"] = True
643            else:
644                args_dict["terminateDebuggee"] = False
645        command_dict = {
646            "command": "disconnect",
647            "type": "request",
648            "arguments": args_dict,
649        }
650        return self.send_recv(command_dict)
651
652    def request_disassemble(
653        self, memoryReference, offset=-50, instructionCount=200, resolveSymbols=True
654    ):
655        args_dict = {
656            "memoryReference": memoryReference,
657            "offset": offset,
658            "instructionCount": instructionCount,
659            "resolveSymbols": resolveSymbols,
660        }
661        command_dict = {
662            "command": "disassemble",
663            "type": "request",
664            "arguments": args_dict,
665        }
666        instructions = self.send_recv(command_dict)["body"]["instructions"]
667        for inst in instructions:
668            self.disassembled_instructions[inst["address"]] = inst
669
670    def request_evaluate(self, expression, frameIndex=0, threadId=None, context=None):
671        stackFrame = self.get_stackFrame(frameIndex=frameIndex, threadId=threadId)
672        if stackFrame is None:
673            return []
674        args_dict = {
675            "expression": expression,
676            "context": context,
677            "frameId": stackFrame["id"],
678        }
679        command_dict = {
680            "command": "evaluate",
681            "type": "request",
682            "arguments": args_dict,
683        }
684        return self.send_recv(command_dict)
685
686    def request_initialize(self, sourceInitFile):
687        command_dict = {
688            "command": "initialize",
689            "type": "request",
690            "arguments": {
691                "adapterID": "lldb-native",
692                "clientID": "vscode",
693                "columnsStartAt1": True,
694                "linesStartAt1": True,
695                "locale": "en-us",
696                "pathFormat": "path",
697                "supportsRunInTerminalRequest": True,
698                "supportsVariablePaging": True,
699                "supportsVariableType": True,
700                "supportsStartDebuggingRequest": True,
701                "sourceInitFile": sourceInitFile,
702            },
703        }
704        response = self.send_recv(command_dict)
705        if response:
706            if "body" in response:
707                self.initialize_body = response["body"]
708        return response
709
710    def request_launch(
711        self,
712        program,
713        args=None,
714        cwd=None,
715        env=None,
716        stopOnEntry=False,
717        disableASLR=True,
718        disableSTDIO=False,
719        shellExpandArguments=False,
720        trace=False,
721        initCommands=None,
722        preRunCommands=None,
723        stopCommands=None,
724        exitCommands=None,
725        terminateCommands=None,
726        sourcePath=None,
727        debuggerRoot=None,
728        launchCommands=None,
729        sourceMap=None,
730        runInTerminal=False,
731        postRunCommands=None,
732        enableAutoVariableSummaries=False,
733        enableSyntheticChildDebugging=False,
734        commandEscapePrefix="`",
735        customFrameFormat=None,
736        customThreadFormat=None,
737    ):
738        args_dict = {"program": program}
739        if args:
740            args_dict["args"] = args
741        if cwd:
742            args_dict["cwd"] = cwd
743        if env:
744            args_dict["env"] = env
745        if stopOnEntry:
746            args_dict["stopOnEntry"] = stopOnEntry
747        if disableASLR:
748            args_dict["disableASLR"] = disableASLR
749        if disableSTDIO:
750            args_dict["disableSTDIO"] = disableSTDIO
751        if shellExpandArguments:
752            args_dict["shellExpandArguments"] = shellExpandArguments
753        if trace:
754            args_dict["trace"] = trace
755        args_dict["initCommands"] = self.init_commands
756        if initCommands:
757            args_dict["initCommands"].extend(initCommands)
758        if preRunCommands:
759            args_dict["preRunCommands"] = preRunCommands
760        if stopCommands:
761            args_dict["stopCommands"] = stopCommands
762        if exitCommands:
763            args_dict["exitCommands"] = exitCommands
764        if terminateCommands:
765            args_dict["terminateCommands"] = terminateCommands
766        if sourcePath:
767            args_dict["sourcePath"] = sourcePath
768        if debuggerRoot:
769            args_dict["debuggerRoot"] = debuggerRoot
770        if launchCommands:
771            args_dict["launchCommands"] = launchCommands
772        if sourceMap:
773            args_dict["sourceMap"] = sourceMap
774        if runInTerminal:
775            args_dict["runInTerminal"] = runInTerminal
776        if postRunCommands:
777            args_dict["postRunCommands"] = postRunCommands
778        if customFrameFormat:
779            args_dict["customFrameFormat"] = customFrameFormat
780        if customThreadFormat:
781            args_dict["customThreadFormat"] = customThreadFormat
782
783        args_dict["enableAutoVariableSummaries"] = enableAutoVariableSummaries
784        args_dict["enableSyntheticChildDebugging"] = enableSyntheticChildDebugging
785        args_dict["commandEscapePrefix"] = commandEscapePrefix
786        command_dict = {"command": "launch", "type": "request", "arguments": args_dict}
787        response = self.send_recv(command_dict)
788
789        if response["success"]:
790            # Wait for a 'process' and 'initialized' event in any order
791            self.wait_for_event(filter=["process", "initialized"])
792            self.wait_for_event(filter=["process", "initialized"])
793        return response
794
795    def request_next(self, threadId):
796        if self.exit_status is not None:
797            raise ValueError("request_continue called after process exited")
798        args_dict = {"threadId": threadId}
799        command_dict = {"command": "next", "type": "request", "arguments": args_dict}
800        return self.send_recv(command_dict)
801
802    def request_stepIn(self, threadId):
803        if self.exit_status is not None:
804            raise ValueError("request_continue called after process exited")
805        args_dict = {"threadId": threadId}
806        command_dict = {"command": "stepIn", "type": "request", "arguments": args_dict}
807        return self.send_recv(command_dict)
808
809    def request_stepOut(self, threadId):
810        if self.exit_status is not None:
811            raise ValueError("request_continue called after process exited")
812        args_dict = {"threadId": threadId}
813        command_dict = {"command": "stepOut", "type": "request", "arguments": args_dict}
814        return self.send_recv(command_dict)
815
816    def request_pause(self, threadId=None):
817        if self.exit_status is not None:
818            raise ValueError("request_continue called after process exited")
819        if threadId is None:
820            threadId = self.get_thread_id()
821        args_dict = {"threadId": threadId}
822        command_dict = {"command": "pause", "type": "request", "arguments": args_dict}
823        return self.send_recv(command_dict)
824
825    def request_scopes(self, frameId):
826        args_dict = {"frameId": frameId}
827        command_dict = {"command": "scopes", "type": "request", "arguments": args_dict}
828        return self.send_recv(command_dict)
829
830    def request_setBreakpoints(self, file_path, line_array, data=None):
831        """data is array of parameters for breakpoints in line_array.
832        Each parameter object is 1:1 mapping with entries in line_entry.
833        It contains optional location/hitCondition/logMessage parameters.
834        """
835        (dir, base) = os.path.split(file_path)
836        source_dict = {"name": base, "path": file_path}
837        args_dict = {
838            "source": source_dict,
839            "sourceModified": False,
840        }
841        if line_array is not None:
842            args_dict["lines"] = "%s" % line_array
843            breakpoints = []
844            for i, line in enumerate(line_array):
845                breakpoint_data = None
846                if data is not None and i < len(data):
847                    breakpoint_data = data[i]
848                bp = {"line": line}
849                if breakpoint_data is not None:
850                    if "condition" in breakpoint_data and breakpoint_data["condition"]:
851                        bp["condition"] = breakpoint_data["condition"]
852                    if (
853                        "hitCondition" in breakpoint_data
854                        and breakpoint_data["hitCondition"]
855                    ):
856                        bp["hitCondition"] = breakpoint_data["hitCondition"]
857                    if (
858                        "logMessage" in breakpoint_data
859                        and breakpoint_data["logMessage"]
860                    ):
861                        bp["logMessage"] = breakpoint_data["logMessage"]
862                breakpoints.append(bp)
863            args_dict["breakpoints"] = breakpoints
864
865        command_dict = {
866            "command": "setBreakpoints",
867            "type": "request",
868            "arguments": args_dict,
869        }
870        return self.send_recv(command_dict)
871
872    def request_setExceptionBreakpoints(self, filters):
873        args_dict = {"filters": filters}
874        command_dict = {
875            "command": "setExceptionBreakpoints",
876            "type": "request",
877            "arguments": args_dict,
878        }
879        return self.send_recv(command_dict)
880
881    def request_setFunctionBreakpoints(self, names, condition=None, hitCondition=None):
882        breakpoints = []
883        for name in names:
884            bp = {"name": name}
885            if condition is not None:
886                bp["condition"] = condition
887            if hitCondition is not None:
888                bp["hitCondition"] = hitCondition
889            breakpoints.append(bp)
890        args_dict = {"breakpoints": breakpoints}
891        command_dict = {
892            "command": "setFunctionBreakpoints",
893            "type": "request",
894            "arguments": args_dict,
895        }
896        return self.send_recv(command_dict)
897
898    def request_compileUnits(self, moduleId):
899        args_dict = {"moduleId": moduleId}
900        command_dict = {
901            "command": "compileUnits",
902            "type": "request",
903            "arguments": args_dict,
904        }
905        response = self.send_recv(command_dict)
906        return response
907
908    def request_completions(self, text, frameId=None):
909        args_dict = {"text": text, "column": len(text)}
910        if frameId:
911            args_dict["frameId"] = frameId
912        command_dict = {
913            "command": "completions",
914            "type": "request",
915            "arguments": args_dict,
916        }
917        return self.send_recv(command_dict)
918
919    def request_modules(self):
920        return self.send_recv({"command": "modules", "type": "request"})
921
922    def request_stackTrace(
923        self, threadId=None, startFrame=None, levels=None, dump=False
924    ):
925        if threadId is None:
926            threadId = self.get_thread_id()
927        args_dict = {"threadId": threadId}
928        if startFrame is not None:
929            args_dict["startFrame"] = startFrame
930        if levels is not None:
931            args_dict["levels"] = levels
932        command_dict = {
933            "command": "stackTrace",
934            "type": "request",
935            "arguments": args_dict,
936        }
937        response = self.send_recv(command_dict)
938        if dump:
939            for idx, frame in enumerate(response["body"]["stackFrames"]):
940                name = frame["name"]
941                if "line" in frame and "source" in frame:
942                    source = frame["source"]
943                    if "sourceReference" not in source:
944                        if "name" in source:
945                            source_name = source["name"]
946                            line = frame["line"]
947                            print("[%3u] %s @ %s:%u" % (idx, name, source_name, line))
948                            continue
949                print("[%3u] %s" % (idx, name))
950        return response
951
952    def request_threads(self):
953        """Request a list of all threads and combine any information from any
954        "stopped" events since those contain more information about why a
955        thread actually stopped. Returns an array of thread dictionaries
956        with information about all threads"""
957        command_dict = {"command": "threads", "type": "request", "arguments": {}}
958        response = self.send_recv(command_dict)
959        body = response["body"]
960        # Fill in "self.threads" correctly so that clients that call
961        # self.get_threads() or self.get_thread_id(...) can get information
962        # on threads when the process is stopped.
963        if "threads" in body:
964            self.threads = body["threads"]
965            for thread in self.threads:
966                # Copy the thread dictionary so we can add key/value pairs to
967                # it without affecting the original info from the "threads"
968                # command.
969                tid = thread["id"]
970                if tid in self.thread_stop_reasons:
971                    thread_stop_info = self.thread_stop_reasons[tid]
972                    copy_keys = ["reason", "description", "text"]
973                    for key in copy_keys:
974                        if key in thread_stop_info:
975                            thread[key] = thread_stop_info[key]
976        else:
977            self.threads = None
978        return response
979
980    def request_variables(self, variablesReference, start=None, count=None):
981        args_dict = {"variablesReference": variablesReference}
982        if start is not None:
983            args_dict["start"] = start
984        if count is not None:
985            args_dict["count"] = count
986        command_dict = {
987            "command": "variables",
988            "type": "request",
989            "arguments": args_dict,
990        }
991        return self.send_recv(command_dict)
992
993    def request_setVariable(self, containingVarRef, name, value, id=None):
994        args_dict = {
995            "variablesReference": containingVarRef,
996            "name": name,
997            "value": str(value),
998        }
999        if id is not None:
1000            args_dict["id"] = id
1001        command_dict = {
1002            "command": "setVariable",
1003            "type": "request",
1004            "arguments": args_dict,
1005        }
1006        return self.send_recv(command_dict)
1007
1008    def request_testGetTargetBreakpoints(self):
1009        """A request packet used in the LLDB test suite to get all currently
1010        set breakpoint infos for all breakpoints currently set in the
1011        target.
1012        """
1013        command_dict = {
1014            "command": "_testGetTargetBreakpoints",
1015            "type": "request",
1016            "arguments": {},
1017        }
1018        return self.send_recv(command_dict)
1019
1020    def terminate(self):
1021        self.send.close()
1022        # self.recv.close()
1023
1024
1025class DebugAdaptorServer(DebugCommunication):
1026    def __init__(
1027        self,
1028        executable=None,
1029        port=None,
1030        init_commands=[],
1031        log_file=None,
1032        env=None,
1033    ):
1034        self.process = None
1035        if executable is not None:
1036            adaptor_env = os.environ.copy()
1037            if env is not None:
1038                adaptor_env.update(env)
1039
1040            if log_file:
1041                adaptor_env["LLDBDAP_LOG"] = log_file
1042            self.process = subprocess.Popen(
1043                [executable],
1044                stdin=subprocess.PIPE,
1045                stdout=subprocess.PIPE,
1046                stderr=subprocess.PIPE,
1047                env=adaptor_env,
1048            )
1049            DebugCommunication.__init__(
1050                self, self.process.stdout, self.process.stdin, init_commands, log_file
1051            )
1052        elif port is not None:
1053            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1054            s.connect(("127.0.0.1", port))
1055            DebugCommunication.__init__(
1056                self, s.makefile("r"), s.makefile("w"), init_commands
1057            )
1058
1059    def get_pid(self):
1060        if self.process:
1061            return self.process.pid
1062        return -1
1063
1064    def terminate(self):
1065        super(DebugAdaptorServer, self).terminate()
1066        if self.process is not None:
1067            self.process.terminate()
1068            self.process.wait()
1069            self.process = None
1070
1071
1072def attach_options_specified(options):
1073    if options.pid is not None:
1074        return True
1075    if options.waitFor:
1076        return True
1077    if options.attach:
1078        return True
1079    if options.attachCmds:
1080        return True
1081    return False
1082
1083
1084def run_vscode(dbg, args, options):
1085    dbg.request_initialize(options.sourceInitFile)
1086    if attach_options_specified(options):
1087        response = dbg.request_attach(
1088            program=options.program,
1089            pid=options.pid,
1090            waitFor=options.waitFor,
1091            attachCommands=options.attachCmds,
1092            initCommands=options.initCmds,
1093            preRunCommands=options.preRunCmds,
1094            stopCommands=options.stopCmds,
1095            exitCommands=options.exitCmds,
1096            terminateCommands=options.terminateCmds,
1097        )
1098    else:
1099        response = dbg.request_launch(
1100            options.program,
1101            args=args,
1102            env=options.envs,
1103            cwd=options.workingDir,
1104            debuggerRoot=options.debuggerRoot,
1105            sourcePath=options.sourcePath,
1106            initCommands=options.initCmds,
1107            preRunCommands=options.preRunCmds,
1108            stopCommands=options.stopCmds,
1109            exitCommands=options.exitCmds,
1110            terminateCommands=options.terminateCmds,
1111        )
1112
1113    if response["success"]:
1114        if options.sourceBreakpoints:
1115            source_to_lines = {}
1116            for file_line in options.sourceBreakpoints:
1117                (path, line) = file_line.split(":")
1118                if len(path) == 0 or len(line) == 0:
1119                    print('error: invalid source with line "%s"' % (file_line))
1120
1121                else:
1122                    if path in source_to_lines:
1123                        source_to_lines[path].append(int(line))
1124                    else:
1125                        source_to_lines[path] = [int(line)]
1126            for source in source_to_lines:
1127                dbg.request_setBreakpoints(source, source_to_lines[source])
1128        if options.funcBreakpoints:
1129            dbg.request_setFunctionBreakpoints(options.funcBreakpoints)
1130        dbg.request_configurationDone()
1131        dbg.wait_for_stopped()
1132    else:
1133        if "message" in response:
1134            print(response["message"])
1135    dbg.request_disconnect(terminateDebuggee=True)
1136
1137
1138def main():
1139    parser = optparse.OptionParser(
1140        description=(
1141            "A testing framework for the Visual Studio Code Debug " "Adaptor protocol"
1142        )
1143    )
1144
1145    parser.add_option(
1146        "--vscode",
1147        type="string",
1148        dest="vscode_path",
1149        help=(
1150            "The path to the command line program that implements the "
1151            "Visual Studio Code Debug Adaptor protocol."
1152        ),
1153        default=None,
1154    )
1155
1156    parser.add_option(
1157        "--program",
1158        type="string",
1159        dest="program",
1160        help="The path to the program to debug.",
1161        default=None,
1162    )
1163
1164    parser.add_option(
1165        "--workingDir",
1166        type="string",
1167        dest="workingDir",
1168        default=None,
1169        help="Set the working directory for the process we launch.",
1170    )
1171
1172    parser.add_option(
1173        "--sourcePath",
1174        type="string",
1175        dest="sourcePath",
1176        default=None,
1177        help=(
1178            "Set the relative source root for any debug info that has "
1179            "relative paths in it."
1180        ),
1181    )
1182
1183    parser.add_option(
1184        "--debuggerRoot",
1185        type="string",
1186        dest="debuggerRoot",
1187        default=None,
1188        help=(
1189            "Set the working directory for lldb-dap for any object files "
1190            "with relative paths in the Mach-o debug map."
1191        ),
1192    )
1193
1194    parser.add_option(
1195        "-r",
1196        "--replay",
1197        type="string",
1198        dest="replay",
1199        help=(
1200            "Specify a file containing a packet log to replay with the "
1201            "current Visual Studio Code Debug Adaptor executable."
1202        ),
1203        default=None,
1204    )
1205
1206    parser.add_option(
1207        "-g",
1208        "--debug",
1209        action="store_true",
1210        dest="debug",
1211        default=False,
1212        help="Pause waiting for a debugger to attach to the debug adaptor",
1213    )
1214
1215    parser.add_option(
1216        "--sourceInitFile",
1217        action="store_true",
1218        dest="sourceInitFile",
1219        default=False,
1220        help="Whether lldb-dap should source .lldbinit file or not",
1221    )
1222
1223    parser.add_option(
1224        "--port",
1225        type="int",
1226        dest="port",
1227        help="Attach a socket to a port instead of using STDIN for VSCode",
1228        default=None,
1229    )
1230
1231    parser.add_option(
1232        "--pid",
1233        type="int",
1234        dest="pid",
1235        help="The process ID to attach to",
1236        default=None,
1237    )
1238
1239    parser.add_option(
1240        "--attach",
1241        action="store_true",
1242        dest="attach",
1243        default=False,
1244        help=(
1245            "Specify this option to attach to a process by name. The "
1246            "process name is the basename of the executable specified with "
1247            "the --program option."
1248        ),
1249    )
1250
1251    parser.add_option(
1252        "-f",
1253        "--function-bp",
1254        type="string",
1255        action="append",
1256        dest="funcBreakpoints",
1257        help=(
1258            "Specify the name of a function to break at. "
1259            "Can be specified more than once."
1260        ),
1261        default=[],
1262    )
1263
1264    parser.add_option(
1265        "-s",
1266        "--source-bp",
1267        type="string",
1268        action="append",
1269        dest="sourceBreakpoints",
1270        default=[],
1271        help=(
1272            "Specify source breakpoints to set in the format of "
1273            "<source>:<line>. "
1274            "Can be specified more than once."
1275        ),
1276    )
1277
1278    parser.add_option(
1279        "--attachCommand",
1280        type="string",
1281        action="append",
1282        dest="attachCmds",
1283        default=[],
1284        help=(
1285            "Specify a LLDB command that will attach to a process. "
1286            "Can be specified more than once."
1287        ),
1288    )
1289
1290    parser.add_option(
1291        "--initCommand",
1292        type="string",
1293        action="append",
1294        dest="initCmds",
1295        default=[],
1296        help=(
1297            "Specify a LLDB command that will be executed before the target "
1298            "is created. Can be specified more than once."
1299        ),
1300    )
1301
1302    parser.add_option(
1303        "--preRunCommand",
1304        type="string",
1305        action="append",
1306        dest="preRunCmds",
1307        default=[],
1308        help=(
1309            "Specify a LLDB command that will be executed after the target "
1310            "has been created. Can be specified more than once."
1311        ),
1312    )
1313
1314    parser.add_option(
1315        "--stopCommand",
1316        type="string",
1317        action="append",
1318        dest="stopCmds",
1319        default=[],
1320        help=(
1321            "Specify a LLDB command that will be executed each time the"
1322            "process stops. Can be specified more than once."
1323        ),
1324    )
1325
1326    parser.add_option(
1327        "--exitCommand",
1328        type="string",
1329        action="append",
1330        dest="exitCmds",
1331        default=[],
1332        help=(
1333            "Specify a LLDB command that will be executed when the process "
1334            "exits. Can be specified more than once."
1335        ),
1336    )
1337
1338    parser.add_option(
1339        "--terminateCommand",
1340        type="string",
1341        action="append",
1342        dest="terminateCmds",
1343        default=[],
1344        help=(
1345            "Specify a LLDB command that will be executed when the debugging "
1346            "session is terminated. Can be specified more than once."
1347        ),
1348    )
1349
1350    parser.add_option(
1351        "--env",
1352        type="string",
1353        action="append",
1354        dest="envs",
1355        default=[],
1356        help=("Specify environment variables to pass to the launched " "process."),
1357    )
1358
1359    parser.add_option(
1360        "--waitFor",
1361        action="store_true",
1362        dest="waitFor",
1363        default=False,
1364        help=(
1365            "Wait for the next process to be launched whose name matches "
1366            "the basename of the program specified with the --program "
1367            "option"
1368        ),
1369    )
1370
1371    (options, args) = parser.parse_args(sys.argv[1:])
1372
1373    if options.vscode_path is None and options.port is None:
1374        print(
1375            "error: must either specify a path to a Visual Studio Code "
1376            "Debug Adaptor vscode executable path using the --vscode "
1377            "option, or a port to attach to for an existing lldb-dap "
1378            "using the --port option"
1379        )
1380        return
1381    dbg = DebugAdaptorServer(executable=options.vscode_path, port=options.port)
1382    if options.debug:
1383        raw_input('Waiting for debugger to attach pid "%i"' % (dbg.get_pid()))
1384    if options.replay:
1385        dbg.replay_packets(options.replay)
1386    else:
1387        run_vscode(dbg, args, options)
1388    dbg.terminate()
1389
1390
1391if __name__ == "__main__":
1392    main()
1393