Patches from Robert Kraske (robekras):
[phpeclipse.git] / net.sourceforge.phpeclipse.debug.core / src / net / sourceforge / phpdt / internal / debug / core / PHPDBGProxy.java
1 /***********************************************************************************************************************************
2  * Copyright (c) 2000, 2002 IBM Corp. and others. All rights reserved. This program and the accompanying materials are made
3  * available under the terms of the Common Public License v1.0 which accompanies this distribution, and is available at
4  * http://www.eclipse.org/legal/cpl-v10.html
5  *
6  * Contributors: IBM Corporation - Initial implementation Vicente Fernando - www.alfersoft.com.ar Christian Perkonig - remote debug
7  **********************************************************************************************************************************/
8 package net.sourceforge.phpdt.internal.debug.core;
9
10 import java.io.BufferedReader;
11 import java.io.IOException;
12 import java.io.InputStreamReader;
13 import java.io.OutputStream;
14 import java.net.ServerSocket;
15 import java.net.Socket;
16 import java.net.SocketTimeoutException;
17 import java.util.Map;
18
19 import net.sourceforge.phpdt.internal.debug.core.breakpoints.PHPLineBreakpoint;
20 import net.sourceforge.phpdt.internal.debug.core.model.PHPDebugTarget;
21 import net.sourceforge.phpdt.internal.debug.core.model.PHPStackFrame;
22 import net.sourceforge.phpdt.internal.debug.core.model.PHPThread;
23 import net.sourceforge.phpdt.internal.debug.core.model.PHPVariable;
24 import net.sourceforge.phpeclipse.PHPeclipsePlugin;
25
26 import org.eclipse.core.runtime.CoreException;
27 import org.eclipse.core.runtime.IPath;
28 import org.eclipse.core.runtime.Path;
29 import org.eclipse.debug.core.DebugException;
30 import org.eclipse.debug.core.DebugPlugin;
31 import org.eclipse.debug.core.model.IBreakpoint;
32
33 public class PHPDBGProxy {
34
35         private ServerSocket            server          = null;
36         private BufferedReader          reader          = null;
37         private PHPDBGInterface         DBGInt          = null;         // The DBG interface which is linked with the proxy
38         private PHPDebugTarget          debugTarget = null;
39         private PHPDBGProxy             thisProxy       = null;
40         private PHPLoop                         phpLoop;
41         private PHPThread                       PHPMainThread;
42         private Socket                          socket;
43         private int                             port;
44         private boolean                         remote;
45         private boolean                         pathtranslation;
46         private Map                             pathmap;
47         private IPath                           remoteSourcePath;
48
49         /**
50          */
51         public PHPDBGProxy () {
52           thisProxy = this;
53         }
54
55         /**
56          * @param remote
57          * @param remoteSourcePath
58          * @param pathTranslate
59          * @param paths
60          */
61         public PHPDBGProxy (boolean remote, String remoteSourcePath, boolean pathTranslate, Map paths) {
62                 thisProxy             = this;
63                 this.remote               = remote;
64                 this.remoteSourcePath = new Path (remoteSourcePath);
65                 this.pathmap          = paths;
66                 this.pathtranslation  = pathTranslate;
67         }
68
69         /**
70          *
71          */
72         public void start () {
73                 createServerSocket ();                                      // Create a server socket for communicatio with DBG
74
75                 this.startPHPLoop ();                                                                           //
76         }
77
78         /**
79          *
80          */
81         public void stop () {
82                 phpLoop.setShouldStop ();                                   // Notify the thread's 'run loop' to stop
83
84                 if (DBGInt != null) {                                       // If we have a DBG interface linked with this proxy
85                 DBGInt.setShouldStop ();                                //  Notify the DBG interface to stop the waiting for response
86                 }
87
88                 if (!remote) {                                              // If it's not a remote proxy session
89                 try {
90                         getDebugTarget ().getProcess ().terminate ();       //
91                 } catch (DebugException e) {
92                         e.printStackTrace ();
93                 }
94                 }
95
96                 phpLoop.notifyWait ();
97         }
98
99         /**
100          * TODO Is this method called from anywhere?
101          *
102          * Returns a already created server socket, or
103          * creates a server socket if none exists, and
104          * returns the newly created one.
105          *
106          * @return A server socket
107          */
108         protected ServerSocket getServerSocket () throws IOException {
109                 if (server == null) {                                                                           // Do we have already a server socket
110                 createServerSocket ();                                                                  //  No, then create one
111                 }
112
113                 return server;                                                                                          // Return the server socket
114         }
115
116         /**
117          *
118          * TODO The example for setting up DBG within PHP.ini shows ports from 10000 to 10016 ???
119          * if my interpretation is correct.
120          * How can we find the correct DBG port?
121          */
122         protected void createServerSocket () {
123                 port = SocketUtil.findUnusedLocalPort ("localhost", 10001, 10101);      // Get the first free port in the range from 10001 to 10101
124
125 //          port = 10001;
126                 if (port == -1) {                                                   // Did we get a free port?
127                 PHPDebugCorePlugin.log (5, "Cannot find free port!!!!");        //  No, output a error message
128
129                 return;                                                         //  And return
130                 }
131                 try {
132                 if (server == null) {                                           // If there is no server socket yet
133                         server = new ServerSocket (port);                           //  create a server socket for the free port
134                         //System.out.println("ServerSocket on port: " + port);
135                 }
136                 } catch (IOException e) {
137                 // IO Error
138                 PHPDebugCorePlugin.log (e);
139                 stop ();
140                 }
141         }
142
143         /**
144          *
145          */
146         public Socket getSocket () throws IOException {
147                 return socket;                                                          // Return the socket
148         }
149
150         /**
151          * Set the DBG interface which is linked to this proxy
152          *
153          * @paran DBGInt The DGB interface which is linked with this proxy
154          */
155         protected void setDBGInterface (PHPDBGInterface DBGInt) {
156                 this.DBGInt = DBGInt;
157         }
158
159         /**
160          * Give back a buffered input stream for the socket which is
161          * linked with this proxy
162          */
163         public BufferedReader getReader () throws IOException {
164                 if (reader == null) {                                               // Do we already have a buffered input stream
165                 reader = new BufferedReader (new InputStreamReader (this.getSocket ().getInputStream (),
166                                                                         "ISO8859_1"));
167                 }
168
169           return reader;                                                      // Return the buffered input stream
170         }
171
172         /**
173          *
174          */
175         public BufferedReader getReader (Socket socket) throws IOException {
176                 if (socket != null) {                                                                                           // Is a socket provided
177                 return new BufferedReader (new InputStreamReader (socket.getInputStream (),
178                                                                                                   "ISO8859_1"));  // Then create a buffered input stream
179                 }
180                 else {
181                 return null;                                                      // Without a socket we can't create a input stream
182                 }
183         }
184
185         /**
186          *
187          * @return The output stream for this proxy's socket
188          */
189         public OutputStream getOutputStream () throws IOException {
190                 return this.getSocket ().getOutputStream ();
191         }
192
193         /**
194          *
195          */
196         protected void setBreakPoints () throws IOException, CoreException {
197                 IBreakpoint[] breakpoints = DebugPlugin.getDefault ().getBreakpointManager ().getBreakpoints ();
198
199                 for (int i = 0; i < breakpoints.length; i++) {
200                         if (breakpoints[i].isEnabled ()) {
201                                 addBreakpoint (breakpoints[i]);
202                         }
203                 }
204         }
205
206         /**
207          *
208          */
209         private String MapPath (PHPLineBreakpoint phpLBP) {
210                 IPath filename;
211
212                 if (remote) {
213                         filename = phpLBP.getMarker().getResource().getProjectRelativePath();
214                         filename = remoteSourcePath.append (filename);
215                 } else {
216                         filename = phpLBP.getMarker().getResource().getLocation();
217                 }
218
219                 String path = filename.toOSString();
220
221                 if ((pathmap != null) && remote) {
222                         java.util.Iterator i = pathmap.keySet().iterator();
223
224                         while (i.hasNext()) {
225                                 String k = (String) i.next();
226                                 if (path.startsWith(k)) {
227                                         path = pathmap.get(k) + path.substring(k.length());
228                                         break;
229                                 }
230                         }
231                 }
232
233                 if (pathtranslation && remote) {
234                         if (remoteSourcePath.toString ().substring (0, 1).equals ("/")) {
235                                 path = path.replace ('\\', '/');
236                         }
237                         else {
238                                 path = path.replace ('/', '\\');
239                         }
240                 }
241
242                 return path;
243         }
244
245         /**
246          *
247          */
248         public void addBreakpoint (IBreakpoint breakpoint) {
249                 if (DBGInt == null) {
250                 return;
251                 }
252
253                 int bpNo = 0;
254
255                 try {
256                 PHPLineBreakpoint phpLBP;
257
258                 if (breakpoint.getModelIdentifier() == PHPDebugCorePlugin.getUniqueIdentifier()) {
259                         phpLBP = (PHPLineBreakpoint) breakpoint;
260
261                         //      bpNo= DBGInt.addBreakpoint(phpLBP.getMarker().getResource().getLocation().toOSString(), phpLBP.getLineNumber());
262
263                         bpNo = DBGInt.addBreakpoint(MapPath(phpLBP), phpLBP.getLineNumber());
264                         phpLBP.setDBGBpNo(bpNo);
265                 }
266                 } catch (IOException e) {
267                     PHPDebugCorePlugin.log(e);
268                     stop();
269                 } catch (CoreException e) {
270                 PHPDebugCorePlugin.log(e);
271                 stop();
272                 }
273         }
274
275         /**
276          *
277          */
278         public void removeBreakpoint (IBreakpoint breakpoint) {
279                 if (DBGInt == null) {
280                 return;
281                 }
282
283                 try {
284                 PHPLineBreakpoint phpLBP;
285
286                 if (breakpoint.getModelIdentifier() == PHPDebugCorePlugin.getUniqueIdentifier ()) {
287                         phpLBP = (PHPLineBreakpoint) breakpoint;
288
289                         //      bpNo= DBGInt.addBreakpoint(filename.toOSString(), phpLBP.getLineNumber());
290
291                         DBGInt.removeBreakpoint(MapPath(phpLBP), phpLBP.getLineNumber(), phpLBP.getDBGBpNo());
292                 }
293                 } catch (IOException e) {
294                 PHPDebugCorePlugin.log (e);
295                 stop ();
296                 } catch (CoreException e) {
297                 PHPDebugCorePlugin.log (e);
298                 stop ();
299                 }
300         }
301
302         /**
303          *
304          */
305         public void phpLoopNotify () {
306                 phpLoop.notifyWait ();
307         }
308
309         /**
310          *
311          */
312         public void startPHPLoop () {
313                 phpLoop = new PHPLoop ();                                                                       // Create a DBG communication loop object
314
315                 phpLoop.start ();                                                                                       // And start the communication loop
316         }
317
318         /**
319          *
320          */
321         public void resume () {
322                 try {
323                 DBGInt.continueExecution();
324                 phpLoop.notifyWait();
325                 } catch (IOException e) {
326                 PHPeclipsePlugin.log("Debugging session ended.", e);
327                 stop();
328                 }
329         }
330
331         /**
332          *
333          */
334         public void pause () {
335                 try {
336                 if (null != DBGInt) {
337                                 DBGInt.pauseExecution();
338                         }
339                 else {
340                         // TODO Make sure the Suspend action is grayed out
341                         // when DBGInt is null
342                 }
343                 } catch (IOException e) {
344                 PHPDebugCorePlugin.log (e);
345                 stop ();
346                 }
347         }
348
349         /**
350          *
351          */
352         protected PHPDebugTarget getDebugTarget() {
353                 return debugTarget;
354         }
355
356         /**
357          *
358          * @param debugTarget
359          */
360         public void setDebugTarget (PHPDebugTarget debugTarget) {
361                 this.debugTarget = debugTarget;
362                 debugTarget.setPHPDBGProxy(this);
363         }
364
365         /**
366          * This method is called by a stackframe.
367          * It reads the variables from PHP via DBG
368          *
369          * @param frame The stackframe which wants the variables.
370          * @return      The array of variables for this stackframe.
371          */
372         public PHPVariable[] readVariables (PHPStackFrame frame) {
373                 try {
374                 return DBGInt.getVariables (frame);                                             // Get the variables from DBG interface
375                 } catch (IOException ioex) {
376                 ioex.printStackTrace ();
377                 throw new RuntimeException (ioex.getMessage ());
378                 } catch (DebugException ex) {
379                     ex.printStackTrace ();
380                     throw new RuntimeException (ex.getMessage ());
381                 }
382         }
383
384         /**
385          *
386          * @param frame
387          * @param evalString
388          * @return
389          */
390         public PHPVariable[] eval (PHPStackFrame frame, String evalString) {
391                 try {
392                 return DBGInt.evalBlock (frame, evalString);
393                 //return DBGInt.getVariables(frame);
394                 } catch (IOException ioex) {
395                 ioex.printStackTrace();
396                 throw new RuntimeException(ioex.getMessage());
397                 } catch (DebugException ex) {
398                 ex.printStackTrace();
399                 throw new RuntimeException(ex.getMessage());
400                 }
401         }
402
403         public void readStepOverEnd (PHPStackFrame stackFrame) {
404                 try {
405                 DBGInt.stepOver();
406                 phpLoop.notifyWait();
407                 } catch (Exception e) {
408                 PHPDebugCorePlugin.log(e);
409                 }
410         }
411
412         public void readStepReturnEnd (PHPStackFrame stackFrame) {
413                 try {
414                 DBGInt.stepOut();
415                     phpLoop.notifyWait();
416                 } catch (Exception e) {
417                 PHPDebugCorePlugin.log(e);
418                 }
419         }
420
421         public void readStepIntoEnd (PHPStackFrame stackFrame) {
422                 try {
423                 DBGInt.stepInto();
424                 phpLoop.notifyWait();
425                 } catch (Exception e) {
426                 PHPDebugCorePlugin.log(e);
427                 }
428         }
429
430         /*
431          * public PHPStackFrame[] readFrames(PHPThread thread) { //try { //this.println("th " + thread.getId() + " ; f "); //return new
432          * FramesReader(getMultiReaderStrategy()).readFrames(thread); return null; //} catch (IOException e) { //
433          * PHPDebugCorePlugin.log(e); // return null; //}
434          *  }
435          */
436
437         public void closeSocket() throws IOException {
438                 if (socket != null) {
439                 socket.close();
440                 }
441         }
442
443         public void closeServerSocket() throws IOException {
444                 if (server != null) {
445                 server.close();
446                 }
447         }
448
449         public int getPort() {
450                 return port;
451         }
452
453         /**
454          *
455          *
456          */
457         class PHPLoop extends Thread {
458                 private boolean shouldStop;
459
460                 public PHPLoop () {
461                 shouldStop = false;
462                 this.setName ("PHPDebuggerLoop");
463                 }
464
465                 /**
466                  *
467                  */
468                 public synchronized void setShouldStop () {
469                         shouldStop = true;                                                                                      // The run loop should stop
470
471                         try {
472                                 // If the loop thread is blocked on the server socket,
473                                 // forcibly unblock it to avoid leaking the thread,
474                                 // the socket and the port
475                                 closeServerSocket ();
476                         } catch (IOException x) {
477                                 // Log this as a warning?
478                                 PHPDebugCorePlugin.log (x);
479                         }
480                 }
481
482                 /**
483                  *
484                  */
485                 public synchronized void notifyWait () {
486                 notify ();
487                 }
488
489                 /**
490                  *
491                  *
492                  */
493                 public void run () {
494                 try {
495                         int                     i;
496                                 int                     timeout;
497                         long                    interval        = 200;                                  // Wait 200 ms maximum for a DBG response
498                         boolean                 newconnect      = false;                                //
499                         Socket                  newSocket       = null;
500                         PHPStackFrame[] StackList;
501                         PHPDBGInterface newDBGInt;
502
503                         //                              synchronized (this) {
504                         //                                      wait();
505                         //                              }
506
507                         PHPMainThread = new PHPThread (getDebugTarget (), getPort ());
508                         PHPMainThread.setName ("Thread [main]");
509                         timeout       = 0;
510
511                         //                              while ((getDebugTarget() == null) && (timeout < 100)) {
512                         //                                      sleep(100);
513                         //                                      timeout++;
514                         //                              }
515                         // Be sure debug target is set
516                         //                              PHPMainThread.setDebugTarget(getDebugTarget());
517
518                                 getDebugTarget ().addThread (PHPMainThread);
519
520                         //System.out.println("Waiting for breakpoints.");
521
522                         while (!shouldStop) {                                                           // As long as nobody will stop us
523                                 newconnect = true;                              // The first time
524
525                                 try {
526                                         newSocket = server.accept();                            // Waits until DBG want to connect
527                                         //System.out.println("Accepted! : " + socket.toString());
528                                 } catch (SocketTimeoutException e) {
529                                         newconnect = false;                                                     // No one wants to connect (connection already done)
530                                 } catch (IOException e) {
531                                         PHPDebugCorePlugin.log(e);
532                                         return;
533                                 }
534
535                                 if (newconnect) {                                                               // Is it just after a new connection
536                                         if (DBGInt == null) {                                           // Do we have a DBG interface?
537                                         server.setSoTimeout(1);                                 // ???
538                                                 }
539
540                                         newDBGInt = new PHPDBGInterface (getReader (newSocket),                 // Create a new interface
541                                                                                                                  newSocket.getOutputStream (),
542                                                                                                                  thisProxy);
543                                         newDBGInt.waitResponse (1000);                          // Wait for the initial DBG response
544                                         newDBGInt.flushAllPackets ();               // Read and process the DBG response
545
546                                         // Check version and session ID
547                                         if ((DBGInt == null) ||                                 // If we have no interface
548                                                 (DBGInt.getSID () == newDBGInt.getSID ())) {// or the new session ID is different to the old one
549                                         DBGInt = newDBGInt;                                                     // Set the new interface as current one
550
551                                         try {
552                                                 closeSocket ();
553                                         }
554                                                         catch (IOException e) {
555                                                 PHPDebugCorePlugin.log (e);
556                                                 shouldStop = true;
557                                         }
558
559                                         socket = newSocket;
560                                         setBreakPoints ();
561                                         DBGInt.continueExecution ();                    // Notify DBG that PHP should continue
562                                         }
563                                                 else {
564                                         newDBGInt.continueExecution ();                         // Notify DBG that PHP should continue
565                                         newSocket.close ();
566                                         }
567                                 }
568
569                                 if (DBGInt.waitResponse (interval)) {                                           // Wait for a DBG response (200 ms)
570                                         DBGInt.flushAllPackets ();                                                              // If we got something, read and process it
571
572                                         if (DBGInt.BPUnderHit != 0) {                                                   // ???
573                                         StackList = DBGInt.getStackList ();                 // Get the stack list from DBGInterface
574
575                                             if (StackList.length > 0) {                         // If there is something in stack list
576                                                 for (i = 0; i < StackList.length; i++) {        // For all stack list
577                                                         StackList[i].setThread (PHPMainThread);     // Set the PHPTread for all PHPStackFrames
578
579                                                         if (DBGInt.getModByNo (StackList[i].getModNo ()).equals ("")) {
580                                                                 DBGInt.getSourceTree ();
581                                                         }
582
583                                                         StackList[i].setFile (DBGInt.getModByNo (StackList[i].getModNo ()));
584                                                 }
585
586                                                 PHPMainThread.setStackFrames (StackList);
587                                         }
588
589                                         PHPMainThread.suspend ();                             // Fire debug event
590
591                                             synchronized (this) {
592                                                 wait ();
593                                         }
594                                         }
595                                 }
596
597                                 if (remote) {
598                                         if (PHPMainThread.isTerminated ()) {
599                                         shouldStop = true;
600
601                                             break;                                                // Go for terminating the thread
602                                         }
603                                 } else {
604                                         if (PHPMainThread.isTerminated () ||
605                                                 getDebugTarget ().getProcess ().isTerminated ()) {
606                                         shouldStop = true;
607
608                                         break;                                                // Go for terminating the thread
609                                         }
610                                 }
611                         }
612                 } catch (Exception ex) {
613                         PHPDebugCorePlugin.log (ex);
614                         System.out.println (ex);
615                 } finally {
616                         try {
617                                 getDebugTarget ().terminate ();
618                                 closeSocket();
619                                 closeServerSocket ();
620                         } catch (IOException e) {
621                                 PHPDebugCorePlugin.log (e);
622
623                                 return;
624                         }
625
626                         //System.out.println("Socket loop finished.");
627                 }
628                 }
629         }
630 }