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