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