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