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