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