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, sourceMap=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 if sourceMap: 609 args_dict['sourceMap'] = sourceMap 610 command_dict = { 611 'command': 'launch', 612 'type': 'request', 613 'arguments': args_dict 614 } 615 response = self.send_recv(command_dict) 616 617 # Wait for a 'process' and 'initialized' event in any order 618 self.wait_for_event(filter=['process', 'initialized']) 619 self.wait_for_event(filter=['process', 'initialized']) 620 return response 621 622 def request_next(self, threadId): 623 if self.exit_status is not None: 624 raise ValueError('request_continue called after process exited') 625 args_dict = {'threadId': threadId} 626 command_dict = { 627 'command': 'next', 628 'type': 'request', 629 'arguments': args_dict 630 } 631 return self.send_recv(command_dict) 632 633 def request_stepIn(self, threadId): 634 if self.exit_status is not None: 635 raise ValueError('request_continue called after process exited') 636 args_dict = {'threadId': threadId} 637 command_dict = { 638 'command': 'stepIn', 639 'type': 'request', 640 'arguments': args_dict 641 } 642 return self.send_recv(command_dict) 643 644 def request_stepOut(self, threadId): 645 if self.exit_status is not None: 646 raise ValueError('request_continue called after process exited') 647 args_dict = {'threadId': threadId} 648 command_dict = { 649 'command': 'stepOut', 650 'type': 'request', 651 'arguments': args_dict 652 } 653 return self.send_recv(command_dict) 654 655 def request_pause(self, threadId=None): 656 if self.exit_status is not None: 657 raise ValueError('request_continue called after process exited') 658 if threadId is None: 659 threadId = self.get_thread_id() 660 args_dict = {'threadId': threadId} 661 command_dict = { 662 'command': 'pause', 663 'type': 'request', 664 'arguments': args_dict 665 } 666 return self.send_recv(command_dict) 667 668 def request_scopes(self, frameId): 669 args_dict = {'frameId': frameId} 670 command_dict = { 671 'command': 'scopes', 672 'type': 'request', 673 'arguments': args_dict 674 } 675 return self.send_recv(command_dict) 676 677 def request_setBreakpoints(self, file_path, line_array, condition=None, 678 hitCondition=None): 679 (dir, base) = os.path.split(file_path) 680 breakpoints = [] 681 for line in line_array: 682 bp = {'line': line} 683 if condition is not None: 684 bp['condition'] = condition 685 if hitCondition is not None: 686 bp['hitCondition'] = hitCondition 687 breakpoints.append(bp) 688 source_dict = { 689 'name': base, 690 'path': file_path 691 } 692 args_dict = { 693 'source': source_dict, 694 'breakpoints': breakpoints, 695 'lines': '%s' % (line_array), 696 'sourceModified': False, 697 } 698 command_dict = { 699 'command': 'setBreakpoints', 700 'type': 'request', 701 'arguments': args_dict 702 } 703 return self.send_recv(command_dict) 704 705 def request_setExceptionBreakpoints(self, filters): 706 args_dict = {'filters': filters} 707 command_dict = { 708 'command': 'setExceptionBreakpoints', 709 'type': 'request', 710 'arguments': args_dict 711 } 712 return self.send_recv(command_dict) 713 714 def request_setFunctionBreakpoints(self, names, condition=None, 715 hitCondition=None): 716 breakpoints = [] 717 for name in names: 718 bp = {'name': name} 719 if condition is not None: 720 bp['condition'] = condition 721 if hitCondition is not None: 722 bp['hitCondition'] = hitCondition 723 breakpoints.append(bp) 724 args_dict = {'breakpoints': breakpoints} 725 command_dict = { 726 'command': 'setFunctionBreakpoints', 727 'type': 'request', 728 'arguments': args_dict 729 } 730 return self.send_recv(command_dict) 731 732 def request_completions(self, text): 733 args_dict = { 734 'text': text, 735 'column': len(text) 736 } 737 command_dict = { 738 'command': 'completions', 739 'type': 'request', 740 'arguments': args_dict 741 } 742 return self.send_recv(command_dict) 743 744 def request_stackTrace(self, threadId=None, startFrame=None, levels=None, 745 dump=False): 746 if threadId is None: 747 threadId = self.get_thread_id() 748 args_dict = {'threadId': threadId} 749 if startFrame is not None: 750 args_dict['startFrame'] = startFrame 751 if levels is not None: 752 args_dict['levels'] = levels 753 command_dict = { 754 'command': 'stackTrace', 755 'type': 'request', 756 'arguments': args_dict 757 } 758 response = self.send_recv(command_dict) 759 if dump: 760 for (idx, frame) in enumerate(response['body']['stackFrames']): 761 name = frame['name'] 762 if 'line' in frame and 'source' in frame: 763 source = frame['source'] 764 if 'sourceReference' not in source: 765 if 'name' in source: 766 source_name = source['name'] 767 line = frame['line'] 768 print("[%3u] %s @ %s:%u" % (idx, name, source_name, 769 line)) 770 continue 771 print("[%3u] %s" % (idx, name)) 772 return response 773 774 def request_threads(self): 775 '''Request a list of all threads and combine any information from any 776 "stopped" events since those contain more information about why a 777 thread actually stopped. Returns an array of thread dictionaries 778 with information about all threads''' 779 command_dict = { 780 'command': 'threads', 781 'type': 'request', 782 'arguments': {} 783 } 784 response = self.send_recv(command_dict) 785 body = response['body'] 786 # Fill in "self.threads" correctly so that clients that call 787 # self.get_threads() or self.get_thread_id(...) can get information 788 # on threads when the process is stopped. 789 if 'threads' in body: 790 self.threads = body['threads'] 791 for thread in self.threads: 792 # Copy the thread dictionary so we can add key/value pairs to 793 # it without affecting the original info from the "threads" 794 # command. 795 tid = thread['id'] 796 if tid in self.thread_stop_reasons: 797 thread_stop_info = self.thread_stop_reasons[tid] 798 copy_keys = ['reason', 'description', 'text'] 799 for key in copy_keys: 800 if key in thread_stop_info: 801 thread[key] = thread_stop_info[key] 802 else: 803 self.threads = None 804 return response 805 806 def request_variables(self, variablesReference, start=None, count=None): 807 args_dict = {'variablesReference': variablesReference} 808 if start is not None: 809 args_dict['start'] = start 810 if count is not None: 811 args_dict['count'] = count 812 command_dict = { 813 'command': 'variables', 814 'type': 'request', 815 'arguments': args_dict 816 } 817 return self.send_recv(command_dict) 818 819 def request_setVariable(self, containingVarRef, name, value, id=None): 820 args_dict = { 821 'variablesReference': containingVarRef, 822 'name': name, 823 'value': str(value) 824 } 825 if id is not None: 826 args_dict['id'] = id 827 command_dict = { 828 'command': 'setVariable', 829 'type': 'request', 830 'arguments': args_dict 831 } 832 return self.send_recv(command_dict) 833 834 def request_testGetTargetBreakpoints(self): 835 '''A request packet used in the LLDB test suite to get all currently 836 set breakpoint infos for all breakpoints currently set in the 837 target. 838 ''' 839 command_dict = { 840 'command': '_testGetTargetBreakpoints', 841 'type': 'request', 842 'arguments': {} 843 } 844 return self.send_recv(command_dict) 845 846 def terminate(self): 847 self.send.close() 848 # self.recv.close() 849 850 851class DebugAdaptor(DebugCommunication): 852 def __init__(self, executable=None, port=None, init_commands=[], log_file=None): 853 self.process = None 854 if executable is not None: 855 adaptor_env = os.environ.copy() 856 if log_file: 857 adaptor_env['LLDBVSCODE_LOG'] = log_file 858 self.process = subprocess.Popen([executable], 859 stdin=subprocess.PIPE, 860 stdout=subprocess.PIPE, 861 stderr=subprocess.PIPE, 862 env=adaptor_env) 863 DebugCommunication.__init__(self, self.process.stdout, 864 self.process.stdin, init_commands) 865 elif port is not None: 866 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 867 s.connect(('127.0.0.1', port)) 868 DebugCommunication.__init__(self, s.makefile('r'), s.makefile('w'), 869 init_commands) 870 871 def get_pid(self): 872 if self.process: 873 return self.process.pid 874 return -1 875 876 def terminate(self): 877 super(DebugAdaptor, self).terminate() 878 if self.process is not None: 879 self.process.terminate() 880 self.process.wait() 881 self.process = None 882 883 884def attach_options_specified(options): 885 if options.pid is not None: 886 return True 887 if options.waitFor: 888 return True 889 if options.attach: 890 return True 891 if options.attachCmds: 892 return True 893 return False 894 895 896def run_vscode(dbg, args, options): 897 dbg.request_initialize() 898 if attach_options_specified(options): 899 response = dbg.request_attach(program=options.program, 900 pid=options.pid, 901 waitFor=options.waitFor, 902 attachCommands=options.attachCmds, 903 initCommands=options.initCmds, 904 preRunCommands=options.preRunCmds, 905 stopCommands=options.stopCmds, 906 exitCommands=options.exitCmds) 907 else: 908 response = dbg.request_launch(options.program, 909 args=args, 910 env=options.envs, 911 cwd=options.workingDir, 912 debuggerRoot=options.debuggerRoot, 913 sourcePath=options.sourcePath, 914 initCommands=options.initCmds, 915 preRunCommands=options.preRunCmds, 916 stopCommands=options.stopCmds, 917 exitCommands=options.exitCmds) 918 919 if response['success']: 920 if options.sourceBreakpoints: 921 source_to_lines = {} 922 for file_line in options.sourceBreakpoints: 923 (path, line) = file_line.split(':') 924 if len(path) == 0 or len(line) == 0: 925 print('error: invalid source with line "%s"' % 926 (file_line)) 927 928 else: 929 if path in source_to_lines: 930 source_to_lines[path].append(int(line)) 931 else: 932 source_to_lines[path] = [int(line)] 933 for source in source_to_lines: 934 dbg.request_setBreakpoints(source, source_to_lines[source]) 935 if options.funcBreakpoints: 936 dbg.request_setFunctionBreakpoints(options.funcBreakpoints) 937 dbg.request_configurationDone() 938 dbg.wait_for_stopped() 939 else: 940 if 'message' in response: 941 print(response['message']) 942 dbg.request_disconnect(terminateDebuggee=True) 943 944 945def main(): 946 parser = optparse.OptionParser( 947 description=('A testing framework for the Visual Studio Code Debug ' 948 'Adaptor protocol')) 949 950 parser.add_option( 951 '--vscode', 952 type='string', 953 dest='vscode_path', 954 help=('The path to the command line program that implements the ' 955 'Visual Studio Code Debug Adaptor protocol.'), 956 default=None) 957 958 parser.add_option( 959 '--program', 960 type='string', 961 dest='program', 962 help='The path to the program to debug.', 963 default=None) 964 965 parser.add_option( 966 '--workingDir', 967 type='string', 968 dest='workingDir', 969 default=None, 970 help='Set the working directory for the process we launch.') 971 972 parser.add_option( 973 '--sourcePath', 974 type='string', 975 dest='sourcePath', 976 default=None, 977 help=('Set the relative source root for any debug info that has ' 978 'relative paths in it.')) 979 980 parser.add_option( 981 '--debuggerRoot', 982 type='string', 983 dest='debuggerRoot', 984 default=None, 985 help=('Set the working directory for lldb-vscode for any object files ' 986 'with relative paths in the Mach-o debug map.')) 987 988 parser.add_option( 989 '-r', '--replay', 990 type='string', 991 dest='replay', 992 help=('Specify a file containing a packet log to replay with the ' 993 'current Visual Studio Code Debug Adaptor executable.'), 994 default=None) 995 996 parser.add_option( 997 '-g', '--debug', 998 action='store_true', 999 dest='debug', 1000 default=False, 1001 help='Pause waiting for a debugger to attach to the debug adaptor') 1002 1003 parser.add_option( 1004 '--port', 1005 type='int', 1006 dest='port', 1007 help="Attach a socket to a port instead of using STDIN for VSCode", 1008 default=None) 1009 1010 parser.add_option( 1011 '--pid', 1012 type='int', 1013 dest='pid', 1014 help="The process ID to attach to", 1015 default=None) 1016 1017 parser.add_option( 1018 '--attach', 1019 action='store_true', 1020 dest='attach', 1021 default=False, 1022 help=('Specify this option to attach to a process by name. The ' 1023 'process name is the basename of the executable specified with ' 1024 'the --program option.')) 1025 1026 parser.add_option( 1027 '-f', '--function-bp', 1028 type='string', 1029 action='append', 1030 dest='funcBreakpoints', 1031 help=('Specify the name of a function to break at. ' 1032 'Can be specified more than once.'), 1033 default=[]) 1034 1035 parser.add_option( 1036 '-s', '--source-bp', 1037 type='string', 1038 action='append', 1039 dest='sourceBreakpoints', 1040 default=[], 1041 help=('Specify source breakpoints to set in the format of ' 1042 '<source>:<line>. ' 1043 'Can be specified more than once.')) 1044 1045 parser.add_option( 1046 '--attachCommand', 1047 type='string', 1048 action='append', 1049 dest='attachCmds', 1050 default=[], 1051 help=('Specify a LLDB command that will attach to a process. ' 1052 'Can be specified more than once.')) 1053 1054 parser.add_option( 1055 '--initCommand', 1056 type='string', 1057 action='append', 1058 dest='initCmds', 1059 default=[], 1060 help=('Specify a LLDB command that will be executed before the target ' 1061 'is created. Can be specified more than once.')) 1062 1063 parser.add_option( 1064 '--preRunCommand', 1065 type='string', 1066 action='append', 1067 dest='preRunCmds', 1068 default=[], 1069 help=('Specify a LLDB command that will be executed after the target ' 1070 'has been created. Can be specified more than once.')) 1071 1072 parser.add_option( 1073 '--stopCommand', 1074 type='string', 1075 action='append', 1076 dest='stopCmds', 1077 default=[], 1078 help=('Specify a LLDB command that will be executed each time the' 1079 'process stops. Can be specified more than once.')) 1080 1081 parser.add_option( 1082 '--exitCommand', 1083 type='string', 1084 action='append', 1085 dest='exitCmds', 1086 default=[], 1087 help=('Specify a LLDB command that will be executed when the process ' 1088 'exits. Can be specified more than once.')) 1089 1090 parser.add_option( 1091 '--env', 1092 type='string', 1093 action='append', 1094 dest='envs', 1095 default=[], 1096 help=('Specify environment variables to pass to the launched ' 1097 'process.')) 1098 1099 parser.add_option( 1100 '--waitFor', 1101 action='store_true', 1102 dest='waitFor', 1103 default=False, 1104 help=('Wait for the next process to be launched whose name matches ' 1105 'the basename of the program specified with the --program ' 1106 'option')) 1107 1108 (options, args) = parser.parse_args(sys.argv[1:]) 1109 1110 if options.vscode_path is None and options.port is None: 1111 print('error: must either specify a path to a Visual Studio Code ' 1112 'Debug Adaptor vscode executable path using the --vscode ' 1113 'option, or a port to attach to for an existing lldb-vscode ' 1114 'using the --port option') 1115 return 1116 dbg = DebugAdaptor(executable=options.vscode_path, port=options.port) 1117 if options.debug: 1118 raw_input('Waiting for debugger to attach pid "%i"' % ( 1119 dbg.get_pid())) 1120 if options.replay: 1121 dbg.replay_packets(options.replay) 1122 else: 1123 run_vscode(dbg, args, options) 1124 dbg.terminate() 1125 1126 1127if __name__ == '__main__': 1128 main() 1129