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