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