PythonProcess.cs

Full Listing

View diff from d71588c3c91a

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
/* ****************************************************************************
 *
 * Copyright (c) Microsoft Corporation. 
 *
 * This source code is subject to terms and conditions of the Apache License, Version 2.0. A 
 * copy of the license can be found in the License.html file at the root of this distribution. If 
 * you cannot locate the Apache License, Version 2.0, please send an email to 
 * vspython@microsoft.com. By using this source code in any fashion, you are agreeing to be bound 
 * by the terms of the Apache License, Version 2.0.
 *
 * You must not remove this notice, or any other, from this software.
 *
 * ***************************************************************************/
 
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net.Sockets;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using Microsoft.PythonTools.Analysis.Interpreter;
using Microsoft.PythonTools.Parsing;
using Microsoft.PythonTools.Parsing.Ast;
using Microsoft.Win32;
using Microsoft.Win32.SafeHandles;
 
namespace Microsoft.PythonTools.Debugger {
    /// <summary>
    /// Handles all interactions with a Python process which is being debugged.
    /// </summary>
    class PythonProcess {
        private readonly Process _process;
        private readonly Dictionary<int, PythonThread> _threads = new Dictionary<int, PythonThread>();
        private readonly Dictionary<int, PythonBreakpoint> _breakpoints = new Dictionary<int, PythonBreakpoint>();
        private readonly IdDispenser _ids = new IdDispenser();
        private readonly AutoResetEvent _lineEvent = new AutoResetEvent(false);         // set when result of setting current line returns
        private readonly Dictionary<int, CompletionInfo> _pendingExecutes = new Dictionary<int, CompletionInfo>();
        private readonly Dictionary<int, ChildrenInfo> _pendingChildEnums = new Dictionary<int, ChildrenInfo>();
        private readonly PythonLanguageVersion _langVersion;
        private readonly Guid _processGuid = Guid.NewGuid();
        private readonly List<string[]> _dirMapping;
 
        private bool _sentExited;
        private Socket _socket;
        private int _breakpointCounter;
        private bool _setLineResult;                    // contains result of attempting to set the current line of a frame
        private bool _createdFirstThread;
 
        private static Random _portGenerator = new Random();
 
        private PythonProcess(PythonLanguageVersion languageVersion) {
            _langVersion = languageVersion;
 
            ListenForConnection();
        }
 
        private PythonProcess(int pid) {
            _process = Process.GetProcessById(pid);
            _process.Exited += new EventHandler(_process_Exited);
 
            ListenForConnection();
 
            using (var result = DebugAttach.Attach(pid, DebugConnectionListener.ListenerPort, _processGuid)) {
                if (result.Error != ConnErrorMessages.None) {
                    throw new AttachException(result.Error);
                }
 
                _langVersion = (PythonLanguageVersion)result.LanguageVersion;
                if (!result.AttachDone.WaitOne(5000)) {
                    throw new AttachException(ConnErrorMessages.TimeOut);
                }
            }
        }
 
        public PythonProcess(PythonLanguageVersion languageVersion, string exe, string args, string dir, string env, string interpreterOptions, PythonDebugOptions options = PythonDebugOptions.None, List<string[]> dirMapping = null)
            : this(languageVersion) {
            if (dir.EndsWith("\\")) {
                dir = dir.Substring(0, dir.Length - 1);
            }
            _dirMapping = dirMapping;
            var processInfo = new ProcessStartInfo(exe);
 
            processInfo.CreateNoWindow = false;
            processInfo.UseShellExecute = false;
            processInfo.RedirectStandardOutput = false;
 
            processInfo.Arguments = 
                (String.IsNullOrWhiteSpace(interpreterOptions) ? "" : (interpreterOptions + " ")) +
                "\"" + Path.Combine(GetPythonToolsInstallPath(), "visualstudio_py_launcher.py") + "\" " +
                "\"" + dir + "\" " +
                " " + DebugConnectionListener.ListenerPort + " " +
                " " + _processGuid + " " +
                (((options & PythonDebugOptions.WaitOnAbnormalExit) != 0) ? " --wait-on-exception " : "") +
                (((options & PythonDebugOptions.WaitOnNormalExit) != 0) ? " --wait-on-exit " : "") +
                (((options & PythonDebugOptions.RedirectOutput) != 0) ? " --redirect-output " : "") +
                args;
 
            if (env != null) {
                string[] envValues = env.Split('\0');
                foreach (var curValue in envValues) {
                    string[] nameValue = curValue.Split(new[] { '=' }, 2);
                    if (nameValue.Length == 2 && !String.IsNullOrWhiteSpace(nameValue[0])) {
                        processInfo.EnvironmentVariables[nameValue[0]] = nameValue[1];
                    }
                }
            }
 
            Debug.WriteLine(String.Format("Launching: {0} {1}", processInfo.FileName, processInfo.Arguments));
            _process = new Process();
            _process.StartInfo = processInfo;
            _process.Exited += new EventHandler(_process_Exited);
        }
 
        public static ConnErrorMessages TryAttach(int pid, out PythonProcess process) {
            try {
                process = new PythonProcess(pid);
                return ConnErrorMessages.None;
            } catch (AttachException ex) {
                process = null;
                return ex.Error;
            }
        }
 
        class AttachException : Exception {
            private readonly ConnErrorMessages _error;
 
            public AttachException(ConnErrorMessages error) {
                _error = error;
            }
 
            public ConnErrorMessages Error {
                get {
                    return _error;
                }
            }
        }
 
        #region Public Process API
 
        public int Id {
            get {
                return _process.Id;
            }
        }
 
        public void Start() {
            _process.Start();
        }
 
        private void ListenForConnection() {
            DebugConnectionListener.RegisterProcess(_processGuid, this);
        }
 
        ~PythonProcess() {
            DebugConnectionListener.UnregisterProcess(_processGuid);
        }
 
        void _process_Exited(object sender, EventArgs e) {
            if (!_sentExited) {
                _sentExited = true;
                var exited = ProcessExited;
                if (exited != null) {
                    int exitCode;
                    try {
                        exitCode = _process.HasExited ? _process.ExitCode : -1;
                    } catch (InvalidOperationException) {
                        // debug attach, we didn't start the process...
                        exitCode = -1;
                    }
                    exited(this, new ProcessExitedEventArgs(exitCode));
                }
            }
        }
 
        public void WaitForExit() {
            _process.WaitForExit();
        }
 
        public void Terminate() {
            if (!_process.HasExited) {
                _socket = null;
                _process.Kill();
            }
        }
 
        public bool HasExited {
            get {
                return _process.HasExited;
            }
        }
 
        /// <summary>
        /// Breaks into the process.
        /// </summary>
        public void Break() {
            DebugWriteCommand("BreakAll");
            _socket.Send(BreakAllCommandBytes);
        }
 
        [Conditional("DEBUG")]
        private void DebugWriteCommand(string commandName) {
            Debug.WriteLine("PythonDebugger " + _processGuid + " Sending Command " + commandName);
        }
 
        public void Resume() {
            DebugWriteCommand("ResumeAll");
            _socket.Send(ResumeAllCommandBytes);
        }
 
        public void Continue() {
            Resume();
        }
 
        public PythonBreakpoint AddBreakPoint(string filename, int lineNo, string condition = "", bool breakWhenChanged = false) {
            int id = _breakpointCounter++;
            var res = new PythonBreakpoint(this, filename, lineNo, condition, breakWhenChanged, id);
            _breakpoints[id] = res;
            return res;
        }
 
        public PythonLanguageVersion LanguageVersion {
            get {
                return _langVersion;
            }
        }
 
        public void SetExceptionInfo(int defaultBreakOnMode, ICollection<KeyValuePair<string, int>> breakOn) {
            _socket.Send(SetExceptionInfoCommandBytes);
            _socket.Send(BitConverter.GetBytes(defaultBreakOnMode));
            _socket.Send(BitConverter.GetBytes(breakOn.Count));
            foreach (var item in breakOn) {
                _socket.Send(BitConverter.GetBytes(item.Value));
                SendString(_socket, item.Key);
            }
        }
 
        #endregion
 
        #region Debuggee Communcation
 
        internal void Connected(Socket socket) {
            Debug.WriteLine("Process Connected: " + _processGuid);
 
            _socket = socket;
            var debuggerThread = new Thread(DebugEventThread);
            debuggerThread.Name = "Python Debugger Thread " + _processGuid;
            debuggerThread.Start();
 
            DebugConnectionListener.UnregisterProcess(_processGuid);
            GC.SuppressFinalize(this);
        }
 
        private void DebugEventThread() {
            Debug.WriteLine("DebugEvent Thread Started " + _processGuid);
 
 
            byte[] cmd_buffer = new byte[4];
            try {
                Socket socket;
                while ((socket = _socket) != null && socket.Receive(cmd_buffer) == 4) {
                    Debug.WriteLine(String.Format("Received Debugger command: {0} ({1})", CommandtoString(cmd_buffer), _processGuid));
 
                    switch (CommandtoString(cmd_buffer)) {
                        case "EXCP": HandleException(socket); break;
                        case "BRKH": HandleBreakPointHit(socket); break;
                        case "NEWT": HandleThreadCreate(socket); break;
                        case "EXTT": HandleThreadExit(socket); break;
                        case "MODL": HandleModuleLoad(socket); break;
                        case "STPD": HandleStepDone(socket); break;
                        case "EXIT": HandleProcessExit(socket); return;
                        case "BRKS": HandleBreakPointSet(socket); break;
                        case "BRKF": HandleBreakPointFailed(socket); break;
                        case "LOAD": HandleProcessLoad(socket); break;
                        case "THRF": HandleThreadFrameList(socket); break;
                        case "EXCR": HandleExecutionResult(socket); break;
                        case "EXCE": HandleExecutionException(socket); break;
                        case "ASBR": HandleAsyncBreak(socket); break;
                        case "SETL": HandleSetLineResult(socket); break;
                        case "CHLD": HandleEnumChildren(socket); break;
                        case "OUTP": HandleDebuggerOutput(socket); break;
                        case "REQH": HandleRequestHandlers(socket); break;
                        case "DETC": _process_Exited(this, EventArgs.Empty); break; // detach, report process exit
                    }
                }
            } catch (SocketException) {
                _process_Exited(this, EventArgs.Empty);
            }
        }
 
        private static string ToDottedNameString(Expression expr, PythonAst ast) {
            NameExpression name;
            MemberExpression member;
            if ((name = expr as NameExpression) != null) {
                return name.Name;
            } else if ((member = expr as MemberExpression) != null) {
                while (member.Target is MemberExpression) {
                    member = (MemberExpression)member.Target;
                }
                if (member.Target is NameExpression) {
                    return expr.ToCodeString(ast);
                }
            }
            return null;
        }
 
        internal IList<Tuple<int, int, IList<string>>> GetHandledExceptionRanges(string filename) {
            PythonAst ast;
            TryHandlerWalker walker = new TryHandlerWalker();
            var statements = new List<Tuple<int, int, IList<string>>>();
 
            try {
                using (var source = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read)) {
                    ast = Parser.CreateParser(source, LanguageVersion).ParseFile();
                    ast.Walk(walker);
                }
            } catch (Exception ex) {
                Debug.WriteLine("Exception in GetHandledExceptionRanges:");
                Debug.WriteLine(string.Format("Filename: {0}", filename));
                Debug.WriteLine(ex);
                return statements;
            }
 
            foreach (var statement in walker.Statements) {
                int start = statement.GetStart(ast).Line;
                int end = statement.Body.GetEnd(ast).Line + 1;
                var expressions = new List<string>();
 
                if (statement.Handlers == null) {
                    expressions.Add("*");
                } else {
                    foreach (var handler in statement.Handlers) {
                        Expression expr = handler.Test;
                        TupleExpression tuple;
                        if (expr == null) {
                            expressions.Clear();
                            expressions.Add("*");
                            break;
                        } else if ((tuple = handler.Test as TupleExpression) != null) {
                            foreach (var e in tuple.Items) {
                                var text = ToDottedNameString(e, ast);
                                if (text != null) {
                                    expressions.Add(text);
                                }
                            }
                        } else {
                            var text = ToDottedNameString(expr, ast);
                            if (text != null) {
                                expressions.Add(text);
                            }
                        }
                    }
                }
 
                if (expressions.Count > 0) {
                    statements.Add(new Tuple<int, int, IList<string>>(start, end, expressions));
                }
            }
 
 
            return statements;
        }
 
        private void HandleRequestHandlers(Socket socket) {
            string filename = socket.ReadString();
 
            Debug.WriteLine("Exception handlers requested for: " + filename);
            var statements = GetHandledExceptionRanges(filename);
 
            _socket.Send(SetExceptionHandlerInfoCommandBytes);
            SendString(_socket, filename);
 
            _socket.Send(BitConverter.GetBytes(statements.Count));
 
            foreach (var t in statements) {
                _socket.Send(BitConverter.GetBytes(t.Item1));
                _socket.Send(BitConverter.GetBytes(t.Item2));
 
                foreach (var expr in t.Item3) {
                    SendString(_socket, expr);
                }
                SendString(_socket, "-");
            }
        }
 
        private void HandleDebuggerOutput(Socket socket) {
            int tid = socket.ReadInt();
            string output = socket.ReadString();
 
            PythonThread thread;
            if (_threads.TryGetValue(tid, out thread)) {
                var outputEvent = DebuggerOutput;
                if (outputEvent != null) {
                    outputEvent(this, new OutputEventArgs(thread, output));
                }
            }
        }
 
        private void HandleSetLineResult(Socket socket) {
            int res = socket.ReadInt();
            int tid = socket.ReadInt();
            int newLine = socket.ReadInt();
            _setLineResult = res != 0;
            if (_setLineResult) {
                _threads[tid].Frames[0].LineNo = newLine;
            }
            _lineEvent.Set();
        }
 
        private void HandleAsyncBreak(Socket socket) {
            int tid = socket.ReadInt();
            var thread = _threads[tid];
            var asyncBreak = AsyncBreakComplete;
            Debug.WriteLine("Received async break command from thread {0}", tid);
            if (asyncBreak != null) {
                asyncBreak(this, new ThreadEventArgs(thread));
            }
        }
 
        private void HandleExecutionException(Socket socket) {
            int execId = socket.ReadInt();
            CompletionInfo completion;
 
            lock (_pendingExecutes) {
                completion = _pendingExecutes[execId];
                _pendingExecutes.Remove(execId);
            }
 
            string exceptionText = socket.ReadString();
            completion.Completion(new PythonEvaluationResult(this, exceptionText, completion.Text, completion.Frame));
        }
 
        private void HandleExecutionResult(Socket socket) {
            int execId = socket.ReadInt();
            CompletionInfo completion;
            lock (_pendingExecutes) {
                completion = _pendingExecutes[execId];
 
                _pendingExecutes.Remove(execId);
                _ids.Free(execId);
            }
            completion.Completion(ReadPythonObject(socket, completion.Text, "", false, false, completion.Frame));
        }
 
        private void HandleEnumChildren(Socket socket) {
            int execId = socket.ReadInt();
            ChildrenInfo completion;
 
            lock (_pendingChildEnums) {
                completion = _pendingChildEnums[execId];
                _pendingChildEnums.Remove(execId);
            }
 
            int childCount = socket.ReadInt();
            bool childIsIndex = socket.ReadInt() == 1;
            bool childIsEnumerate = socket.ReadInt() == 1;
            PythonEvaluationResult[] res = new PythonEvaluationResult[childCount];
            for (int i = 0; i < res.Length; i++) {
                string expr = socket.ReadString();
                res[i] = ReadPythonObject(socket, completion.Text, expr, childIsIndex, childIsEnumerate, completion.Frame);
            }
            completion.Completion(res);
        }
 
        private PythonEvaluationResult ReadPythonObject(Socket socket, string text, string childText, bool childIsIndex, bool childIsEnumerate, PythonStackFrame frame) {
            string objRepr = socket.ReadString();
            string hexRepr = socket.ReadString();
            string typeName = socket.ReadString();
            bool isExpandable = socket.ReadInt() == 1;
 
            if ((typeName == "unicode" && LanguageVersion.Is2x()) ||
                (typeName == "str" && LanguageVersion.Is3x())) {
                objRepr = objRepr.FixupEscapedUnicodeChars();
            }
            return new PythonEvaluationResult(this, objRepr, hexRepr, typeName, text, childText, childIsIndex, childIsEnumerate, frame, isExpandable);
        }
 
        private void HandleThreadFrameList(Socket socket) {
            // list of thread frames
            var frames = new List<PythonStackFrame>();
            int tid = socket.ReadInt();
            var thread = _threads[tid];
 
            int frameCount = socket.ReadInt();
            for (int i = 0; i < frameCount; i++) {
                int startLine = socket.ReadInt();
                int endLine = socket.ReadInt();
                int lineNo = socket.ReadInt();
                string frameName = socket.ReadString();
                string filename = socket.ReadString();
                int argCount = socket.ReadInt();
                int varCount = socket.ReadInt();
                PythonEvaluationResult[] variables = new PythonEvaluationResult[varCount];
                var frame = new PythonStackFrame(thread, frameName, filename, startLine, endLine, lineNo, argCount, i);
                for (int j = 0; j < variables.Length; j++) {
                    string name = socket.ReadString();
                    variables[j] = ReadPythonObject(socket, name, "", false, false, frame);
                }
                frame.SetVariables(variables);
                frames.Add(frame);
            }
 
            Debug.WriteLine("Received frames for thread {0}", tid);
            thread.Frames = frames;
        }
 
        private void HandleProcessLoad(Socket socket) {
            // process is loaded, no user code has run
            int threadId = socket.ReadInt();
            var thread = _threads[threadId];
 
            var loaded = ProcessLoaded;
            if (loaded != null) {
                loaded(this, new ThreadEventArgs(thread));
            }
        }
 
        private void HandleBreakPointFailed(Socket socket) {
            // break point failed to set
            int id = socket.ReadInt();
            var brkEvent = BreakpointBindFailed;
            if (brkEvent != null) {
                brkEvent(this, new BreakpointEventArgs(_breakpoints[id]));
            }
        }
 
        private void HandleBreakPointSet(Socket socket) {
            // break point successfully set
            int id = socket.ReadInt();
            var unbound = _breakpoints[id];
 
            var brkEvent = BreakpointBindSucceeded;
            if (brkEvent != null) {
                brkEvent(this, new BreakpointEventArgs(unbound));
            }
        }
 
        private void HandleProcessExit(Socket socket) {
            // process exit
            int exitCode = socket.ReadInt();
            var processExited = ProcessExited;
            if (processExited != null) {
                _sentExited = true;
                processExited(this, new ProcessExitedEventArgs(exitCode));
            }
            _socket.Send(ExitCommandBytes);
            _socket.Close();
            _socket = null;
        }
 
        private void HandleStepDone(Socket socket) {
            // stepping done
            int threadId = socket.ReadInt();
            var stepComp = StepComplete;
            if (stepComp != null) {
                stepComp(this, new ThreadEventArgs(_threads[threadId]));
            }
        }
 
        private void HandleModuleLoad(Socket socket) {
            // module load
            int moduleId = socket.ReadInt();
            string filename = socket.ReadString();
            if (filename != null) {
                Debug.WriteLine(String.Format("Module Loaded ({0}): {1}", moduleId, filename));
                var module = new PythonModule(moduleId, filename);
 
                var loaded = ModuleLoaded;
                if (loaded != null) {
                    loaded(this, new ModuleLoadedEventArgs(module));
                }
            }
        }
 
        private void HandleThreadExit(Socket socket) {
            // thread exit
            int threadId = socket.ReadInt();
            var thread = _threads[threadId];
 
            var exited = ThreadExited;
            if (exited != null) {
                exited(this, new ThreadEventArgs(thread));
            }
 
            _threads.Remove(threadId);
            Debug.WriteLine("Thread exited, {0} active threads", _threads.Count);
 
        }
 
        private void HandleThreadCreate(Socket socket) {
            // new thread
            int threadId = socket.ReadInt();
            var thread = _threads[threadId] = new PythonThread(this, threadId, _createdFirstThread);
            _createdFirstThread = true;
 
            var created = ThreadCreated;
            if (created != null) {
                created(this, new ThreadEventArgs(thread));
            }
        }
 
        private void HandleBreakPointHit(Socket socket) {
            int breakId = socket.ReadInt();
            int threadId = socket.ReadInt();
            var brkEvent = BreakpointHit;
            PythonBreakpoint unboundBreakpoint;
            if (brkEvent != null) {
                if (_breakpoints.TryGetValue(breakId, out unboundBreakpoint)) {
                    brkEvent(this, new BreakpointHitEventArgs(unboundBreakpoint, _threads[threadId]));
                } else {
                    SendResumeThread(threadId);
                }
            }
        }
 
        private void HandleException(Socket socket) {
            string typeName = socket.ReadString();
            int tid = socket.ReadInt();
            string desc = socket.ReadString();
            if (typeName != null && desc != null) {
                Debug.WriteLine("Exception: " + desc);
                var excepRaised = ExceptionRaised;
                if (excepRaised != null) {
                    excepRaised(this, new ExceptionRaisedEventArgs(_threads[tid], new PythonException(typeName, desc)));
                }
            }
        }
 
        private static string CommandtoString(byte[] cmd_buffer) {
            return new string(new char[] { (char)cmd_buffer[0], (char)cmd_buffer[1], (char)cmd_buffer[2], (char)cmd_buffer[3] });
        }
 
        internal void SendStepOut(int identity) {
            DebugWriteCommand("StepOut");
            _socket.Send(StepOutCommandBytes);
            _socket.Send(BitConverter.GetBytes(identity));
        }
 
        internal void SendStepOver(int identity) {
            DebugWriteCommand("StepOver");
            _socket.Send(StepOverCommandBytes);
            _socket.Send(BitConverter.GetBytes(identity));
        }
 
        internal void SendStepInto(int identity) {
            DebugWriteCommand("StepInto");
            _socket.Send(StepIntoCommandBytes);
            _socket.Send(BitConverter.GetBytes(identity));
        }
 
        public void SendResumeThread(int threadId) {
            DebugWriteCommand("ResumeThread");
            // race w/ removing the breakpoint, let the thread continue
            _socket.Send(ResumeThreadCommandBytes);
            _socket.Send(BitConverter.GetBytes(threadId));
        }
 
        public void SendClearStepping(int threadId) {
            DebugWriteCommand("ClearStepping");
            // race w/ removing the breakpoint, let the thread continue
            _socket.Send(ClearSteppingCommandBytes);
            _socket.Send(BitConverter.GetBytes(threadId));
        }
 
        public void Detach() {
            DebugWriteCommand("Detach");
            _socket.Send(DetachCommandBytes);
        }
 
        [DllImport("kernel32", SetLastError = true, ExactSpelling = true)]
        public static extern Int32 WaitForSingleObject(SafeWaitHandle handle, Int32 milliseconds);
 
        internal void BindBreakpoint(PythonBreakpoint breakpoint) {
            DebugWriteCommand("Bind Breakpoint");
            _socket.Send(SetBreakPointCommandBytes);
            _socket.Send(BitConverter.GetBytes(breakpoint.Id));
            _socket.Send(BitConverter.GetBytes(breakpoint.LineNo));
            SendString(_socket, MapFile(breakpoint.Filename));
            SendCondition(breakpoint);
        }
 
        /// <summary>
        /// Maps a filename from the debugger machine to the debugge machine to vice versa.
        /// 
        /// The file mapping information is provided by our options when the debugger is started.  
        /// 
        /// This is used so that we can use the files local on the developers machine which have
        /// for setting breakpoints and viewing source code even though the files have been
        /// deployed to a remote machine.  For example the user may have:
        /// 
        /// C:\Users\Me\Documents\MyProject\Foo.py
        /// 
        /// which is deployed to
        /// 
        /// \\mycluster\deploydir\MyProject\Foo.py
        /// 
        /// We want the user to be working w/ the local project files during development but
        /// want to set break points in the cluster deployment share.
        /// </summary>
        internal string MapFile(string file, bool toDebuggee = true) {
            if (_dirMapping != null) {
                foreach (var mappingInfo in _dirMapping) {
                    string mapFrom = mappingInfo[toDebuggee ? 0 : 1];
                    string mapTo = mappingInfo[toDebuggee ? 1 : 0];
 
                    if (file.StartsWith(mapFrom, StringComparison.OrdinalIgnoreCase)) {
                        if (file.StartsWith(mapFrom, StringComparison.OrdinalIgnoreCase)) {
                            int len = mapFrom.Length;
                            if (!mappingInfo[0].EndsWith("\\")) {
                                len++;
                            }
 
                            string newFile = Path.Combine(mapTo, file.Substring(len));
                            Debug.WriteLine(String.Format("Filename mapped from {0} to {1}", file, newFile));
                            return newFile;
                        }
                    }
                }
            }
            return file;
        }
 
        private void SendCondition(PythonBreakpoint breakpoint) {
            DebugWriteCommand("Send BP Condition");
            SendString(_socket, breakpoint.Condition ?? "");
            _socket.Send(BitConverter.GetBytes(breakpoint.BreakWhenChanged ? 1 : 0));
        }
 
        internal void SetBreakPointCondition(PythonBreakpoint breakpoint) {
            DebugWriteCommand("Set BP Condition");
            _socket.Send(SetBreakPointConditionCommandBytes);
            _socket.Send(BitConverter.GetBytes(breakpoint.Id));
            SendCondition(breakpoint);
        }
 
        internal void ExecuteText(string text, PythonStackFrame pythonStackFrame, Action<PythonEvaluationResult> completion) {
            DebugWriteCommand("ExecuteText");
            _socket.Send(ExecuteTextCommandBytes);
            SendString(_socket, text);
            int executeId = _ids.Allocate();
            lock (_pendingExecutes) {
                _socket.Send(BitConverter.GetBytes(pythonStackFrame.Thread.Id));
                _socket.Send(BitConverter.GetBytes(pythonStackFrame.FrameId));
                _socket.Send(BitConverter.GetBytes(executeId));
                _pendingExecutes[executeId] = new CompletionInfo(completion, text, pythonStackFrame);
            }
        }
 
        internal void EnumChildren(string text, PythonStackFrame pythonStackFrame, bool childIsEnumerate, Action<PythonEvaluationResult[]> completion) {
            DebugWriteCommand("Enum Children");
            _socket.Send(GetChildrenCommandBytes);
            SendString(_socket, text);
            int executeId = _ids.Allocate();
            lock (_pendingChildEnums) {
                _socket.Send(BitConverter.GetBytes(pythonStackFrame.Thread.Id));
                _socket.Send(BitConverter.GetBytes(pythonStackFrame.FrameId));
                _socket.Send(BitConverter.GetBytes(executeId));
                _socket.Send(BitConverter.GetBytes(childIsEnumerate ? 1 : 0));
                _pendingChildEnums[executeId] = new ChildrenInfo(completion, text, pythonStackFrame);
            }
        }
 
        internal void RemoveBreakPoint(PythonBreakpoint unboundBreakpoint) {
            DebugWriteCommand("Remove Breakpoint");
            _breakpoints.Remove(unboundBreakpoint.Id);
 
            DisableBreakPoint(unboundBreakpoint);
        }
 
        internal void DisableBreakPoint(PythonBreakpoint unboundBreakpoint) {
            if (_socket != null && _socket.Connected) {
                DebugWriteCommand("Disable Breakpoint");
                _socket.Send(RemoveBreakPointCommandBytes);
                _socket.Send(BitConverter.GetBytes(unboundBreakpoint.LineNo));
                _socket.Send(BitConverter.GetBytes(unboundBreakpoint.Id));
            }
        }
 
        internal bool SetLineNumber(PythonStackFrame pythonStackFrame, int lineNo) {
            DebugWriteCommand("Set Line Number");
            _setLineResult = false;
            _socket.Send(SetLineNumberCommand);
            _socket.Send(BitConverter.GetBytes(pythonStackFrame.Thread.Id));
            _socket.Send(BitConverter.GetBytes(pythonStackFrame.FrameId));
            _socket.Send(BitConverter.GetBytes(lineNo));
 
            // wait up to 2 seconds for line event...
            for (int i = 0; i < 20 &&
                _socket.Connected &&
                WaitForSingleObject(_lineEvent.SafeWaitHandle, 100) != 0; i++) {
            }
 
            return _setLineResult;
        }
 
        private void SendString(Socket socket, string str) {
            byte[] bytes = Encoding.UTF8.GetBytes(str);
            _socket.Send(BitConverter.GetBytes(bytes.Length));
            _socket.Send(bytes);
        }
 
        private int? ReadInt(Socket socket) {
            byte[] cmd_buffer = new byte[4];
            if (socket.Receive(cmd_buffer) == 4) {
                return BitConverter.ToInt32(cmd_buffer, 0);
            }
            return null;
        }
 
        private static byte[] ExitCommandBytes = MakeCommand("exit");
        private static byte[] StepIntoCommandBytes = MakeCommand("stpi");
        private static byte[] StepOutCommandBytes = MakeCommand("stpo");
        private static byte[] StepOverCommandBytes = MakeCommand("stpv");
        private static byte[] BreakAllCommandBytes = MakeCommand("brka");
        private static byte[] SetBreakPointCommandBytes = MakeCommand("brkp");
        private static byte[] SetBreakPointConditionCommandBytes = MakeCommand("brkc");
        private static byte[] RemoveBreakPointCommandBytes = MakeCommand("brkr");
        private static byte[] ResumeAllCommandBytes = MakeCommand("resa");
        private static byte[] GetThreadFramesCommandBytes = MakeCommand("thrf");
        private static byte[] ExecuteTextCommandBytes = MakeCommand("exec");
        private static byte[] ResumeThreadCommandBytes = MakeCommand("rest");
        private static byte[] ClearSteppingCommandBytes = MakeCommand("clst");
        private static byte[] SetLineNumberCommand = MakeCommand("setl");
        private static byte[] GetChildrenCommandBytes = MakeCommand("chld");
        private static byte[] DetachCommandBytes = MakeCommand("detc");
        private static byte[] SetExceptionInfoCommandBytes = MakeCommand("sexi");
        private static byte[] SetExceptionHandlerInfoCommandBytes = MakeCommand("sehi");
 
        private static byte[] MakeCommand(string command) {
            return new byte[] { (byte)command[0], (byte)command[1], (byte)command[2], (byte)command[3] };
        }
 
        #endregion
 
        #region Debugging Events
 
        /// <summary>
        /// Fired when the process has started and is broken into the debugger, but before any user code is run.
        /// </summary>
        public event EventHandler<ThreadEventArgs> ProcessLoaded;
        public event EventHandler<ThreadEventArgs> ThreadCreated;
        public event EventHandler<ThreadEventArgs> ThreadExited;
        public event EventHandler<ThreadEventArgs> StepComplete;
        public event EventHandler<ThreadEventArgs> AsyncBreakComplete;
        public event EventHandler<ProcessExitedEventArgs> ProcessExited;
        public event EventHandler<ModuleLoadedEventArgs> ModuleLoaded;
        public event EventHandler<ExceptionRaisedEventArgs> ExceptionRaised;
        public event EventHandler<BreakpointHitEventArgs> BreakpointHit;
        public event EventHandler<BreakpointEventArgs> BreakpointBindSucceeded;
        public event EventHandler<BreakpointEventArgs> BreakpointBindFailed;
        public event EventHandler<OutputEventArgs> DebuggerOutput;
 
        #endregion
 
        class CompletionInfo {
            public readonly Action<PythonEvaluationResult> Completion;
            public readonly string Text;
            public readonly PythonStackFrame Frame;
 
            public CompletionInfo(Action<PythonEvaluationResult> completion, string text, PythonStackFrame frame) {
                Completion = completion;
                Text = text;
                Frame = frame;
            }
        }
 
        class ChildrenInfo {
            public readonly Action<PythonEvaluationResult[]> Completion;
            public readonly string Text;
            public readonly PythonStackFrame Frame;
 
            public ChildrenInfo(Action<PythonEvaluationResult[]> completion, string text, PythonStackFrame frame) {
                Completion = completion;
                Text = text;
                Frame = frame;
            }
        }
 
        internal void Close() {
        }
 
        // This is duplicated throughout different assemblies in PythonTools, so search for it if you update it.
        internal static string GetPythonToolsInstallPath() {
            string path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
            if (File.Exists(Path.Combine(path, "PyDebugAttach.dll"))) {
                return path;
            }
 
            // running from the GAC in remote attach scenario.  Look to the VS install dir.
            using (var configKey = OpenVisualStudioKey()) {
                var installDir = configKey.GetValue("InstallDir") as string;
                if (installDir != null) {
                    var toolsPath = Path.Combine(installDir, "Extensions\\Microsoft\\Python Tools for Visual Studio\\1.0");
                    if (File.Exists(Path.Combine(toolsPath, "PyDebugAttach.dll"))) {
                        return toolsPath;
                    }
                }
            }
 
            return null;
        }
 
        private static Win32.RegistryKey OpenVisualStudioKey() {
            if (Environment.Is64BitOperatingSystem) {
                return RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32).OpenSubKey("Software\\Microsoft\\VisualStudio\\10.0");
            } else {
                return Microsoft.Win32.Registry.LocalMachine.OpenSubKey("Software\\Microsoft\\VisualStudio\\10.0");
            }
        }
    }
}

Diff from d71588c3c91a

View full listing

@@ -21,7 +21,9 @@
 using System.Runtime.InteropServices;
 using System.Text;
 using System.Threading;
+using Microsoft.PythonTools.Analysis.Interpreter;
 using Microsoft.PythonTools.Parsing;
+using Microsoft.PythonTools.Parsing.Ast;
 using Microsoft.Win32;
 using Microsoft.Win32.SafeHandles;
 
@@ -225,12 +227,13 @@
             }
         }
 
-        public void SetExceptionInfo(bool breakAlways, ICollection<string> breakOn) {
+        public void SetExceptionInfo(int defaultBreakOnMode, ICollection<KeyValuePair<string, int>> breakOn) {
             _socket.Send(SetExceptionInfoCommandBytes);
-            _socket.Send(BitConverter.GetBytes(breakAlways ? 1 : 0));
+            _socket.Send(BitConverter.GetBytes(defaultBreakOnMode));
             _socket.Send(BitConverter.GetBytes(breakOn.Count));
-            foreach (var name in breakOn) {
-                SendString(_socket, name);
+            foreach (var item in breakOn) {
+                _socket.Send(BitConverter.GetBytes(item.Value));
+                SendString(_socket, item.Key);
             }
         }
 
@@ -278,6 +281,7 @@
                         case "SETL": HandleSetLineResult(socket); break;
                         case "CHLD": HandleEnumChildren(socket); break;
                         case "OUTP": HandleDebuggerOutput(socket); break;
+                        case "REQH": HandleRequestHandlers(socket); break;
                         case "DETC": _process_Exited(this, EventArgs.Empty); break; // detach, report process exit
                     }
                 }
@@ -286,12 +290,107 @@
             }
         }
 
+        private static string ToDottedNameString(Expression expr, PythonAst ast) {
+            NameExpression name;
+            MemberExpression member;
+            if ((name = expr as NameExpression) != null) {
+                return name.Name;
+            } else if ((member = expr as MemberExpression) != null) {
+                while (member.Target is MemberExpression) {
+                    member = (MemberExpression)member.Target;
+                }
+                if (member.Target is NameExpression) {
+                    return expr.ToCodeString(ast);
+                }
+            }
+            return null;
+        }
+
+        internal IList<Tuple<int, int, IList<string>>> GetHandledExceptionRanges(string filename) {
+            PythonAst ast;
+            TryHandlerWalker walker = new TryHandlerWalker();
+            var statements = new List<Tuple<int, int, IList<string>>>();
+
+            try {
+                using (var source = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read)) {
+                    ast = Parser.CreateParser(source, LanguageVersion).ParseFile();
+                    ast.Walk(walker);
+                }
+            } catch (Exception ex) {
+                Debug.WriteLine("Exception in GetHandledExceptionRanges:");
+                Debug.WriteLine(string.Format("Filename: {0}", filename));
+                Debug.WriteLine(ex);
+                return statements;
+            }
+
+            foreach (var statement in walker.Statements) {
+                int start = statement.GetStart(ast).Line;
+                int end = statement.Body.GetEnd(ast).Line + 1;
+                var expressions = new List<string>();
+
+                if (statement.Handlers == null) {
+                    expressions.Add("*");
+                } else {
+                    foreach (var handler in statement.Handlers) {
+                        Expression expr = handler.Test;
+                        TupleExpression tuple;
+                        if (expr == null) {
+                            expressions.Clear();
+                            expressions.Add("*");
+                            break;
+                        } else if ((tuple = handler.Test as TupleExpression) != null) {
+                            foreach (var e in tuple.Items) {
+                                var text = ToDottedNameString(e, ast);
+                                if (text != null) {
+                                    expressions.Add(text);
+                                }
+                            }
+                        } else {
+                            var text = ToDottedNameString(expr, ast);
+                            if (text != null) {
+                                expressions.Add(text);
+                            }
+                        }
+                    }
+                }
+
+                if (expressions.Count > 0) {
+                    statements.Add(new Tuple<int, int, IList<string>>(start, end, expressions));
+                }
+            }
+
+
+            return statements;
+        }
+
+        private void HandleRequestHandlers(Socket socket) {
+            string filename = socket.ReadString();
+
+            Debug.WriteLine("Exception handlers requested for: " + filename);
+            var statements = GetHandledExceptionRanges(filename);
+
+            _socket.Send(SetExceptionHandlerInfoCommandBytes);
+            SendString(_socket, filename);
+
+            _socket.Send(BitConverter.GetBytes(statements.Count));
+
+            foreach (var t in statements) {
+                _socket.Send(BitConverter.GetBytes(t.Item1));
+                _socket.Send(BitConverter.GetBytes(t.Item2));
+
+                foreach (var expr in t.Item3) {
+                    SendString(_socket, expr);
+                }
+                SendString(_socket, "-");
+            }
+        }
+
         private void HandleDebuggerOutput(Socket socket) {
             int tid = socket.ReadInt();
             string output = socket.ReadString();
@@ -727,6 +826,7 @@
         private static byte[] GetChildrenCommandBytes = MakeCommand("chld");
         private static byte[] DetachCommandBytes = MakeCommand("detc");
         private static byte[] SetExceptionInfoCommandBytes = MakeCommand("sexi");
+        private static byte[] SetExceptionHandlerInfoCommandBytes = MakeCommand("sehi");
 
         private static byte[] MakeCommand(string command) {
             return new byte[] { (byte)command[0], (byte)command[1], (byte)command[2], (byte)command[3] };