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