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