Run DCE after a LoopFlatten test to reduce spurious output [nfc]
[llvm-project.git] / lldb / packages / Python / lldbsuite / test / tools / lldb-dap / dap_server.py
blobd1fb478bc8bb9eed18e7ba8ba896760eaae866d7
1 #!/usr/bin/env python
3 import binascii
4 import json
5 import optparse
6 import os
7 import pprint
8 import socket
9 import string
10 import subprocess
11 import sys
12 import threading
13 import time
16 def 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")
53 def 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.
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)
83 raise Exception("unexpected malformed message from lldb-dap: " + line)
86 def packet_type_is(packet, packet_type):
87 return "type" in packet and packet["type"] == packet_type
90 def 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 =========")
100 def 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)
113 class 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)
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 = {}
140 @classmethod
141 def encode_content(cls, s):
142 return ("Content-Length: %u\r\n\r\n%s" % (len(s), s)).encode("utf-8")
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")
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
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
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
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()
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.
196 # If EOF, notify the read thread by enqueuing a None.
197 if not packet:
198 self.enqueue_recv_packet(None)
199 return False
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
251 elif packet_type == "response":
252 if packet["command"] == "disconnect":
253 keepGoing = False
254 self.enqueue_recv_packet(packet)
255 return keepGoing
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()
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
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()
311 return None
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"],
335 self.send_packet(
337 "type": "response",
338 "seq": -1,
339 "request_seq": response_or_request["seq"],
340 "success": True,
341 "command": "runInTerminal",
342 "body": {},
344 set_sequence=False,
346 elif response_or_request["command"] == "startDebugging":
347 self.send_packet(
349 "type": "response",
350 "seq": -1,
351 "request_seq": response_or_request["seq"],
352 "success": True,
353 "command": "startDebugging",
354 "body": {},
356 set_sequence=False,
358 else:
359 desc = 'unknown reverse request "%s"' % (
360 response_or_request["command"]
362 raise ValueError(desc)
364 return None
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
371 return None
373 def wait_for_stopped(self, timeout=None):
374 stopped_events = []
375 stopped_event = self.wait_for_event(
376 filter=["stopped", "exited"], timeout=timeout
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
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
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
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.
410 if self.initialize_body and key in self.initialize_body:
411 return self.initialize_body[key]
412 return None
414 def get_threads(self):
415 if self.threads is None:
416 self.request_threads()
417 return self.threads
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.
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
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
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
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"]
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 []
474 def get_global_variables(self, frameIndex=0, threadId=None):
475 return self.get_scope_variables(
476 "Globals", frameIndex=frameIndex, threadId=threadId
479 def get_local_variables(self, frameIndex=0, threadId=None):
480 return self.get_scope_variables(
481 "Locals", frameIndex=frameIndex, threadId=threadId
484 def get_registers(self, frameIndex=0, threadId=None):
485 return self.get_scope_variables(
486 "Registers", frameIndex=frameIndex, threadId=threadId
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
496 def get_local_variable_value(self, name, frameIndex=0, threadId=None):
497 variable = self.get_local_variable(
498 name, frameIndex=frameIndex, threadId=threadId
500 if variable and "value" in variable:
501 return variable["value"]
502 return None
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"
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,
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)
591 def request_configurationDone(self):
592 command_dict = {
593 "command": "configurationDone",
594 "type": "request",
595 "arguments": {},
597 response = self.send_recv(command_dict)
598 if response:
599 self.configuration_done_sent = True
600 return response
602 def _process_stopped(self):
603 self.threads = None
604 self.frame_scopes = {}
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,
622 response = self.send_recv(command_dict)
623 # Caller must still call wait_for_stopped.
624 return response
626 def request_restart(self, restartArguments=None):
627 command_dict = {
628 "command": "restart",
629 "type": "request",
631 if restartArguments:
632 command_dict["arguments"] = restartArguments
634 response = self.send_recv(command_dict)
635 # Caller must still call wait_for_stopped.
636 return response
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,
650 return self.send_recv(command_dict)
652 def request_disassemble(
653 self, memoryReference, offset=-50, instructionCount=200, resolveSymbols=True
655 args_dict = {
656 "memoryReference": memoryReference,
657 "offset": offset,
658 "instructionCount": instructionCount,
659 "resolveSymbols": resolveSymbols,
661 command_dict = {
662 "command": "disassemble",
663 "type": "request",
664 "arguments": args_dict,
666 instructions = self.send_recv(command_dict)["body"]["instructions"]
667 for inst in instructions:
668 self.disassembled_instructions[inst["address"]] = inst
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"],
679 command_dict = {
680 "command": "evaluate",
681 "type": "request",
682 "arguments": args_dict,
684 return self.send_recv(command_dict)
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,
704 response = self.send_recv(command_dict)
705 if response:
706 if "body" in response:
707 self.initialize_body = response["body"]
708 return response
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="`",
736 args_dict = {"program": program}
737 if args:
738 args_dict["args"] = args
739 if cwd:
740 args_dict["cwd"] = cwd
741 if env:
742 args_dict["env"] = env
743 if stopOnEntry:
744 args_dict["stopOnEntry"] = stopOnEntry
745 if disableASLR:
746 args_dict["disableASLR"] = disableASLR
747 if disableSTDIO:
748 args_dict["disableSTDIO"] = disableSTDIO
749 if shellExpandArguments:
750 args_dict["shellExpandArguments"] = shellExpandArguments
751 if trace:
752 args_dict["trace"] = trace
753 args_dict["initCommands"] = self.init_commands
754 if initCommands:
755 args_dict["initCommands"].extend(initCommands)
756 if preRunCommands:
757 args_dict["preRunCommands"] = preRunCommands
758 if stopCommands:
759 args_dict["stopCommands"] = stopCommands
760 if exitCommands:
761 args_dict["exitCommands"] = exitCommands
762 if terminateCommands:
763 args_dict["terminateCommands"] = terminateCommands
764 if sourcePath:
765 args_dict["sourcePath"] = sourcePath
766 if debuggerRoot:
767 args_dict["debuggerRoot"] = debuggerRoot
768 if launchCommands:
769 args_dict["launchCommands"] = launchCommands
770 if sourceMap:
771 args_dict["sourceMap"] = sourceMap
772 if runInTerminal:
773 args_dict["runInTerminal"] = runInTerminal
774 if postRunCommands:
775 args_dict["postRunCommands"] = postRunCommands
776 args_dict["enableAutoVariableSummaries"] = enableAutoVariableSummaries
777 args_dict["enableSyntheticChildDebugging"] = enableSyntheticChildDebugging
778 args_dict["commandEscapePrefix"] = commandEscapePrefix
779 command_dict = {"command": "launch", "type": "request", "arguments": args_dict}
780 response = self.send_recv(command_dict)
782 if response["success"]:
783 # Wait for a 'process' and 'initialized' event in any order
784 self.wait_for_event(filter=["process", "initialized"])
785 self.wait_for_event(filter=["process", "initialized"])
786 return response
788 def request_next(self, threadId):
789 if self.exit_status is not None:
790 raise ValueError("request_continue called after process exited")
791 args_dict = {"threadId": threadId}
792 command_dict = {"command": "next", "type": "request", "arguments": args_dict}
793 return self.send_recv(command_dict)
795 def request_stepIn(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": "stepIn", "type": "request", "arguments": args_dict}
800 return self.send_recv(command_dict)
802 def request_stepOut(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": "stepOut", "type": "request", "arguments": args_dict}
807 return self.send_recv(command_dict)
809 def request_pause(self, threadId=None):
810 if self.exit_status is not None:
811 raise ValueError("request_continue called after process exited")
812 if threadId is None:
813 threadId = self.get_thread_id()
814 args_dict = {"threadId": threadId}
815 command_dict = {"command": "pause", "type": "request", "arguments": args_dict}
816 return self.send_recv(command_dict)
818 def request_scopes(self, frameId):
819 args_dict = {"frameId": frameId}
820 command_dict = {"command": "scopes", "type": "request", "arguments": args_dict}
821 return self.send_recv(command_dict)
823 def request_setBreakpoints(self, file_path, line_array, data=None):
824 """data is array of parameters for breakpoints in line_array.
825 Each parameter object is 1:1 mapping with entries in line_entry.
826 It contains optional location/hitCondition/logMessage parameters.
828 (dir, base) = os.path.split(file_path)
829 source_dict = {"name": base, "path": file_path}
830 args_dict = {
831 "source": source_dict,
832 "sourceModified": False,
834 if line_array is not None:
835 args_dict["lines"] = "%s" % line_array
836 breakpoints = []
837 for i, line in enumerate(line_array):
838 breakpoint_data = None
839 if data is not None and i < len(data):
840 breakpoint_data = data[i]
841 bp = {"line": line}
842 if breakpoint_data is not None:
843 if "condition" in breakpoint_data and breakpoint_data["condition"]:
844 bp["condition"] = breakpoint_data["condition"]
845 if (
846 "hitCondition" in breakpoint_data
847 and breakpoint_data["hitCondition"]
849 bp["hitCondition"] = breakpoint_data["hitCondition"]
850 if (
851 "logMessage" in breakpoint_data
852 and breakpoint_data["logMessage"]
854 bp["logMessage"] = breakpoint_data["logMessage"]
855 breakpoints.append(bp)
856 args_dict["breakpoints"] = breakpoints
858 command_dict = {
859 "command": "setBreakpoints",
860 "type": "request",
861 "arguments": args_dict,
863 return self.send_recv(command_dict)
865 def request_setExceptionBreakpoints(self, filters):
866 args_dict = {"filters": filters}
867 command_dict = {
868 "command": "setExceptionBreakpoints",
869 "type": "request",
870 "arguments": args_dict,
872 return self.send_recv(command_dict)
874 def request_setFunctionBreakpoints(self, names, condition=None, hitCondition=None):
875 breakpoints = []
876 for name in names:
877 bp = {"name": name}
878 if condition is not None:
879 bp["condition"] = condition
880 if hitCondition is not None:
881 bp["hitCondition"] = hitCondition
882 breakpoints.append(bp)
883 args_dict = {"breakpoints": breakpoints}
884 command_dict = {
885 "command": "setFunctionBreakpoints",
886 "type": "request",
887 "arguments": args_dict,
889 return self.send_recv(command_dict)
891 def request_compileUnits(self, moduleId):
892 args_dict = {"moduleId": moduleId}
893 command_dict = {
894 "command": "compileUnits",
895 "type": "request",
896 "arguments": args_dict,
898 response = self.send_recv(command_dict)
899 return response
901 def request_completions(self, text, frameId=None):
902 args_dict = {"text": text, "column": len(text)}
903 if frameId:
904 args_dict["frameId"] = frameId
905 command_dict = {
906 "command": "completions",
907 "type": "request",
908 "arguments": args_dict,
910 return self.send_recv(command_dict)
912 def request_modules(self):
913 return self.send_recv({"command": "modules", "type": "request"})
915 def request_stackTrace(
916 self, threadId=None, startFrame=None, levels=None, dump=False
918 if threadId is None:
919 threadId = self.get_thread_id()
920 args_dict = {"threadId": threadId}
921 if startFrame is not None:
922 args_dict["startFrame"] = startFrame
923 if levels is not None:
924 args_dict["levels"] = levels
925 command_dict = {
926 "command": "stackTrace",
927 "type": "request",
928 "arguments": args_dict,
930 response = self.send_recv(command_dict)
931 if dump:
932 for idx, frame in enumerate(response["body"]["stackFrames"]):
933 name = frame["name"]
934 if "line" in frame and "source" in frame:
935 source = frame["source"]
936 if "sourceReference" not in source:
937 if "name" in source:
938 source_name = source["name"]
939 line = frame["line"]
940 print("[%3u] %s @ %s:%u" % (idx, name, source_name, line))
941 continue
942 print("[%3u] %s" % (idx, name))
943 return response
945 def request_threads(self):
946 """Request a list of all threads and combine any information from any
947 "stopped" events since those contain more information about why a
948 thread actually stopped. Returns an array of thread dictionaries
949 with information about all threads"""
950 command_dict = {"command": "threads", "type": "request", "arguments": {}}
951 response = self.send_recv(command_dict)
952 body = response["body"]
953 # Fill in "self.threads" correctly so that clients that call
954 # self.get_threads() or self.get_thread_id(...) can get information
955 # on threads when the process is stopped.
956 if "threads" in body:
957 self.threads = body["threads"]
958 for thread in self.threads:
959 # Copy the thread dictionary so we can add key/value pairs to
960 # it without affecting the original info from the "threads"
961 # command.
962 tid = thread["id"]
963 if tid in self.thread_stop_reasons:
964 thread_stop_info = self.thread_stop_reasons[tid]
965 copy_keys = ["reason", "description", "text"]
966 for key in copy_keys:
967 if key in thread_stop_info:
968 thread[key] = thread_stop_info[key]
969 else:
970 self.threads = None
971 return response
973 def request_variables(self, variablesReference, start=None, count=None):
974 args_dict = {"variablesReference": variablesReference}
975 if start is not None:
976 args_dict["start"] = start
977 if count is not None:
978 args_dict["count"] = count
979 command_dict = {
980 "command": "variables",
981 "type": "request",
982 "arguments": args_dict,
984 return self.send_recv(command_dict)
986 def request_setVariable(self, containingVarRef, name, value, id=None):
987 args_dict = {
988 "variablesReference": containingVarRef,
989 "name": name,
990 "value": str(value),
992 if id is not None:
993 args_dict["id"] = id
994 command_dict = {
995 "command": "setVariable",
996 "type": "request",
997 "arguments": args_dict,
999 return self.send_recv(command_dict)
1001 def request_testGetTargetBreakpoints(self):
1002 """A request packet used in the LLDB test suite to get all currently
1003 set breakpoint infos for all breakpoints currently set in the
1004 target.
1006 command_dict = {
1007 "command": "_testGetTargetBreakpoints",
1008 "type": "request",
1009 "arguments": {},
1011 return self.send_recv(command_dict)
1013 def terminate(self):
1014 self.send.close()
1015 # self.recv.close()
1018 class DebugAdaptorServer(DebugCommunication):
1019 def __init__(
1020 self,
1021 executable=None,
1022 port=None,
1023 init_commands=[],
1024 log_file=None,
1025 env=None,
1027 self.process = None
1028 if executable is not None:
1029 adaptor_env = os.environ.copy()
1030 if env is not None:
1031 adaptor_env.update(env)
1033 if log_file:
1034 adaptor_env["LLDBDAP_LOG"] = log_file
1035 self.process = subprocess.Popen(
1036 [executable],
1037 stdin=subprocess.PIPE,
1038 stdout=subprocess.PIPE,
1039 stderr=subprocess.PIPE,
1040 env=adaptor_env,
1042 DebugCommunication.__init__(
1043 self, self.process.stdout, self.process.stdin, init_commands, log_file
1045 elif port is not None:
1046 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1047 s.connect(("127.0.0.1", port))
1048 DebugCommunication.__init__(
1049 self, s.makefile("r"), s.makefile("w"), init_commands
1052 def get_pid(self):
1053 if self.process:
1054 return self.process.pid
1055 return -1
1057 def terminate(self):
1058 super(DebugAdaptorServer, self).terminate()
1059 if self.process is not None:
1060 self.process.terminate()
1061 self.process.wait()
1062 self.process = None
1065 def attach_options_specified(options):
1066 if options.pid is not None:
1067 return True
1068 if options.waitFor:
1069 return True
1070 if options.attach:
1071 return True
1072 if options.attachCmds:
1073 return True
1074 return False
1077 def run_vscode(dbg, args, options):
1078 dbg.request_initialize(options.sourceInitFile)
1079 if attach_options_specified(options):
1080 response = dbg.request_attach(
1081 program=options.program,
1082 pid=options.pid,
1083 waitFor=options.waitFor,
1084 attachCommands=options.attachCmds,
1085 initCommands=options.initCmds,
1086 preRunCommands=options.preRunCmds,
1087 stopCommands=options.stopCmds,
1088 exitCommands=options.exitCmds,
1089 terminateCommands=options.terminateCmds,
1091 else:
1092 response = dbg.request_launch(
1093 options.program,
1094 args=args,
1095 env=options.envs,
1096 cwd=options.workingDir,
1097 debuggerRoot=options.debuggerRoot,
1098 sourcePath=options.sourcePath,
1099 initCommands=options.initCmds,
1100 preRunCommands=options.preRunCmds,
1101 stopCommands=options.stopCmds,
1102 exitCommands=options.exitCmds,
1103 terminateCommands=options.terminateCmds,
1106 if response["success"]:
1107 if options.sourceBreakpoints:
1108 source_to_lines = {}
1109 for file_line in options.sourceBreakpoints:
1110 (path, line) = file_line.split(":")
1111 if len(path) == 0 or len(line) == 0:
1112 print('error: invalid source with line "%s"' % (file_line))
1114 else:
1115 if path in source_to_lines:
1116 source_to_lines[path].append(int(line))
1117 else:
1118 source_to_lines[path] = [int(line)]
1119 for source in source_to_lines:
1120 dbg.request_setBreakpoints(source, source_to_lines[source])
1121 if options.funcBreakpoints:
1122 dbg.request_setFunctionBreakpoints(options.funcBreakpoints)
1123 dbg.request_configurationDone()
1124 dbg.wait_for_stopped()
1125 else:
1126 if "message" in response:
1127 print(response["message"])
1128 dbg.request_disconnect(terminateDebuggee=True)
1131 def main():
1132 parser = optparse.OptionParser(
1133 description=(
1134 "A testing framework for the Visual Studio Code Debug " "Adaptor protocol"
1138 parser.add_option(
1139 "--vscode",
1140 type="string",
1141 dest="vscode_path",
1142 help=(
1143 "The path to the command line program that implements the "
1144 "Visual Studio Code Debug Adaptor protocol."
1146 default=None,
1149 parser.add_option(
1150 "--program",
1151 type="string",
1152 dest="program",
1153 help="The path to the program to debug.",
1154 default=None,
1157 parser.add_option(
1158 "--workingDir",
1159 type="string",
1160 dest="workingDir",
1161 default=None,
1162 help="Set the working directory for the process we launch.",
1165 parser.add_option(
1166 "--sourcePath",
1167 type="string",
1168 dest="sourcePath",
1169 default=None,
1170 help=(
1171 "Set the relative source root for any debug info that has "
1172 "relative paths in it."
1176 parser.add_option(
1177 "--debuggerRoot",
1178 type="string",
1179 dest="debuggerRoot",
1180 default=None,
1181 help=(
1182 "Set the working directory for lldb-dap for any object files "
1183 "with relative paths in the Mach-o debug map."
1187 parser.add_option(
1188 "-r",
1189 "--replay",
1190 type="string",
1191 dest="replay",
1192 help=(
1193 "Specify a file containing a packet log to replay with the "
1194 "current Visual Studio Code Debug Adaptor executable."
1196 default=None,
1199 parser.add_option(
1200 "-g",
1201 "--debug",
1202 action="store_true",
1203 dest="debug",
1204 default=False,
1205 help="Pause waiting for a debugger to attach to the debug adaptor",
1208 parser.add_option(
1209 "--sourceInitFile",
1210 action="store_true",
1211 dest="sourceInitFile",
1212 default=False,
1213 help="Whether lldb-dap should source .lldbinit file or not",
1216 parser.add_option(
1217 "--port",
1218 type="int",
1219 dest="port",
1220 help="Attach a socket to a port instead of using STDIN for VSCode",
1221 default=None,
1224 parser.add_option(
1225 "--pid",
1226 type="int",
1227 dest="pid",
1228 help="The process ID to attach to",
1229 default=None,
1232 parser.add_option(
1233 "--attach",
1234 action="store_true",
1235 dest="attach",
1236 default=False,
1237 help=(
1238 "Specify this option to attach to a process by name. The "
1239 "process name is the basename of the executable specified with "
1240 "the --program option."
1244 parser.add_option(
1245 "-f",
1246 "--function-bp",
1247 type="string",
1248 action="append",
1249 dest="funcBreakpoints",
1250 help=(
1251 "Specify the name of a function to break at. "
1252 "Can be specified more than once."
1254 default=[],
1257 parser.add_option(
1258 "-s",
1259 "--source-bp",
1260 type="string",
1261 action="append",
1262 dest="sourceBreakpoints",
1263 default=[],
1264 help=(
1265 "Specify source breakpoints to set in the format of "
1266 "<source>:<line>. "
1267 "Can be specified more than once."
1271 parser.add_option(
1272 "--attachCommand",
1273 type="string",
1274 action="append",
1275 dest="attachCmds",
1276 default=[],
1277 help=(
1278 "Specify a LLDB command that will attach to a process. "
1279 "Can be specified more than once."
1283 parser.add_option(
1284 "--initCommand",
1285 type="string",
1286 action="append",
1287 dest="initCmds",
1288 default=[],
1289 help=(
1290 "Specify a LLDB command that will be executed before the target "
1291 "is created. Can be specified more than once."
1295 parser.add_option(
1296 "--preRunCommand",
1297 type="string",
1298 action="append",
1299 dest="preRunCmds",
1300 default=[],
1301 help=(
1302 "Specify a LLDB command that will be executed after the target "
1303 "has been created. Can be specified more than once."
1307 parser.add_option(
1308 "--stopCommand",
1309 type="string",
1310 action="append",
1311 dest="stopCmds",
1312 default=[],
1313 help=(
1314 "Specify a LLDB command that will be executed each time the"
1315 "process stops. Can be specified more than once."
1319 parser.add_option(
1320 "--exitCommand",
1321 type="string",
1322 action="append",
1323 dest="exitCmds",
1324 default=[],
1325 help=(
1326 "Specify a LLDB command that will be executed when the process "
1327 "exits. Can be specified more than once."
1331 parser.add_option(
1332 "--terminateCommand",
1333 type="string",
1334 action="append",
1335 dest="terminateCmds",
1336 default=[],
1337 help=(
1338 "Specify a LLDB command that will be executed when the debugging "
1339 "session is terminated. Can be specified more than once."
1343 parser.add_option(
1344 "--env",
1345 type="string",
1346 action="append",
1347 dest="envs",
1348 default=[],
1349 help=("Specify environment variables to pass to the launched " "process."),
1352 parser.add_option(
1353 "--waitFor",
1354 action="store_true",
1355 dest="waitFor",
1356 default=False,
1357 help=(
1358 "Wait for the next process to be launched whose name matches "
1359 "the basename of the program specified with the --program "
1360 "option"
1364 (options, args) = parser.parse_args(sys.argv[1:])
1366 if options.vscode_path is None and options.port is None:
1367 print(
1368 "error: must either specify a path to a Visual Studio Code "
1369 "Debug Adaptor vscode executable path using the --vscode "
1370 "option, or a port to attach to for an existing lldb-dap "
1371 "using the --port option"
1373 return
1374 dbg = DebugAdaptorServer(executable=options.vscode_path, port=options.port)
1375 if options.debug:
1376 raw_input('Waiting for debugger to attach pid "%i"' % (dbg.get_pid()))
1377 if options.replay:
1378 dbg.replay_packets(options.replay)
1379 else:
1380 run_vscode(dbg, args, options)
1381 dbg.terminate()
1384 if __name__ == "__main__":
1385 main()