Skip to content

Commit

Permalink
[GR-12007] Added --inspect.Attach option.
Browse files Browse the repository at this point in the history
  • Loading branch information
entlicher committed Oct 11, 2018
1 parent a0c5ba9 commit 5cc66be
Show file tree
Hide file tree
Showing 8 changed files with 307 additions and 39 deletions.
13 changes: 13 additions & 0 deletions tools/mx.tools/suite.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"NanoHTTPD",
"NanoHTTPD-WebSocket",
"TruffleJSON",
"Java-WebSocket",
],
"exports" : [
"<package-info>", # exports all packages containing package-info.java
Expand Down Expand Up @@ -126,6 +127,18 @@
],
"sha1" : "8819cea8bfe22c9c63f55465e296b3855ea41786",
},
"Java-WebSocket" : {
"path" : "lib/Java-WebSocket-1.3.9.jar",
"urls" : [
"https://search.maven.org/remotecontent?filepath=org/java-websocket/Java-WebSocket/1.3.9/Java-WebSocket-1.3.9.jar",
],
"sha1" : "e6e60889b7211a80b21052a249bd7e0f88f79fee",
"maven" : {
"groupId" : "org.java-websocket",
"artifactId" : "Java-WebSocket",
"version" : "1.3.9",
}
},
"VISUALVM_COMMON" : {
"urls" : ["https://lafo.ssw.uni-linz.ac.at/pub/graal-external-deps/visualvm-606.tar.gz"],
"sha1" : "4cb0f27e677582e760fdce1d71900048cb30c3e8",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,12 @@ public void doRunIfWaitingForDebugger() {
}
}

public boolean canRun() {
synchronized (runPermission) {
return runPermission[0];
}
}

public ScriptsHandler acquireScriptsHandler() {
if (sch == null) {
InstrumentInfo instrumentInfo = getEnv().getInstruments().get(SourceLoadInstrument.ID);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
/*
* Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.oracle.truffle.tools.chromeinspector.client;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.ByteBuffer;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;

import org.graalvm.polyglot.io.MessageEndpoint;
import com.oracle.truffle.api.TruffleOptions;

import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;

import com.oracle.truffle.tools.chromeinspector.TruffleExecutionContext;
import com.oracle.truffle.tools.chromeinspector.instrument.InspectorWSConnection;
import com.oracle.truffle.tools.chromeinspector.instrument.KeyStoreOptions;
import com.oracle.truffle.tools.chromeinspector.server.ConnectionWatcher;
import com.oracle.truffle.tools.chromeinspector.server.InspectServerSession;

/**
* Web socket client that connects to a listening inspector client.
*/
public class InspectWSClient extends WebSocketClient implements InspectorWSConnection {

private final String host;
private final int port;
private final TruffleExecutionContext executionContext;
private final boolean debugBreak;
private final ConnectionWatcher connectionWatcher;
private final PrintStream traceLog;
private InspectServerSession iss;

private static URI getURI(InetSocketAddress isa, String wsspath) {
try {
return new URI("ws://" + isa.getHostString() + ":" + isa.getPort() + wsspath);
} catch (URISyntaxException ex) {
throw new IllegalStateException(ex);
}
}

public InspectWSClient(InetSocketAddress isa, String wsspath, TruffleExecutionContext executionContext, boolean debugBreak, boolean secure, KeyStoreOptions keyStoreOptions,
ConnectionWatcher connectionWatcher, PrintWriter info) throws IOException {
super(getURI(isa, wsspath));
this.host = isa.getHostString();
this.port = isa.getPort();
this.executionContext = executionContext;
this.debugBreak = debugBreak;
this.connectionWatcher = connectionWatcher;
String traceLogFile = System.getProperty("chromeinspector.traceMessages");
if (traceLogFile != null) {
if (Boolean.parseBoolean(traceLogFile)) {
traceLog = System.err;
} else if (!"false".equalsIgnoreCase(traceLogFile)) {
if ("tmp".equalsIgnoreCase(traceLogFile)) {
traceLog = new PrintStream(new FileOutputStream(File.createTempFile("ChromeInspectorProtocol", ".txt")));
} else {
traceLog = new PrintStream(new FileOutputStream(traceLogFile));
}
} else {
traceLog = null;
}
} else {
traceLog = null;
}
if (secure) {
if (TruffleOptions.AOT) {
throw new IOException("Secure connection is not available in the native-image yet.");
} else {
setSocket(createSecureSocket(keyStoreOptions));
}
}
try {
boolean success = connectBlocking();
if (!success) {
info.println("Could not attach to " + host + ":" + port);
info.flush();
}
} catch (InterruptedException ex) {
throw new IOException("Interrupted " + ex.getLocalizedMessage());
}
}

private static Socket createSecureSocket(KeyStoreOptions keyStoreOptions) throws IOException {
String keyStoreFile = keyStoreOptions.getKeyStore();
if (keyStoreFile != null) {
try {
String filePasswordProperty = keyStoreOptions.getKeyStorePassword();
// obtaining password for unlock keystore
char[] filePassword = filePasswordProperty == null ? "".toCharArray() : filePasswordProperty.toCharArray();
String keystoreType = keyStoreOptions.getKeyStoreType();
if (keystoreType == null) {
keystoreType = KeyStore.getDefaultType();
}
KeyStore keystore = KeyStore.getInstance(keystoreType);
File keyFile = new File(keyStoreFile);
keystore.load(new FileInputStream(keyFile), filePassword);
String keyRecoverPasswordProperty = keyStoreOptions.getKeyPassword();
char[] keyRecoverPassword = keyRecoverPasswordProperty == null ? filePassword : keyRecoverPasswordProperty.toCharArray();
final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keystore, keyRecoverPassword);
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(keystore);

SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
return sslContext.getSocketFactory().createSocket();
} catch (KeyStoreException | KeyManagementException | NoSuchAlgorithmException | CertificateException | UnrecoverableKeyException ex) {
throw new IOException(ex);
}
} else {
throw new IOException("Use options to specify the keystore");
}
}

@Override
public int getPort() {
return port;
}

@Override
public void onOpen(ServerHandshake sh) {
if (traceLog != null) {
traceLog.println("CLIENT ws connection opened at " + getURI());
traceLog.flush();
}
iss = InspectServerSession.create(executionContext, debugBreak, connectionWatcher);
connectionWatcher.notifyOpen();
iss.setMessageListener(new MessageEndpoint() {
@Override
public void sendText(String message) {
if (traceLog != null) {
traceLog.println("SERVER: " + message);
traceLog.flush();
}
send(message);
}

@Override
public void sendBinary(ByteBuffer data) throws IOException {
throw new UnsupportedOperationException("Binary messages are not supported.");
}

@Override
public void sendPing(ByteBuffer data) throws IOException {
}

@Override
public void sendPong(ByteBuffer data) throws IOException {
}

@Override
public void sendClose() throws IOException {
close();
}
});
}

@Override
public void onMessage(String message) {
if (traceLog != null) {
traceLog.println("CLIENT: " + message);
traceLog.flush();
}
iss.sendText(message);
}

@Override
public void onClose(int code, String reason, boolean remote) {
if (traceLog != null) {
traceLog.println("SERVER closed " + reason);
traceLog.flush();
}
connectionWatcher.notifyClosing();
if (!executionContext.canRun()) {
// The connection was not successfull, resume the execution
executionContext.doRunIfWaitingForDebugger();
}
}

@Override
public void onError(Exception excptn) {
if (traceLog != null) {
traceLog.println("SERVER error " + excptn);
traceLog.flush();
}
}

@Override
public void close(String wsspath) {
close();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,9 @@
import com.oracle.truffle.api.nodes.LanguageInfo;

import com.oracle.truffle.tools.chromeinspector.TruffleExecutionContext;
import com.oracle.truffle.tools.chromeinspector.client.InspectWSClient;
import com.oracle.truffle.tools.chromeinspector.server.ConnectionWatcher;
import com.oracle.truffle.tools.chromeinspector.server.InspectServerSession;
import com.oracle.truffle.tools.chromeinspector.server.InspectorServer;
import com.oracle.truffle.tools.chromeinspector.server.WSInterceptorServer;
import com.oracle.truffle.tools.chromeinspector.server.WebSocketServer;

Expand Down Expand Up @@ -88,6 +88,9 @@ public final class InspectorInstrument extends TruffleInstrument {
@com.oracle.truffle.api.Option(name = "", help = "Start the Chrome inspector on [[host:]port]. (default: <loopback address>:" + DEFAULT_PORT + ")", category = OptionCategory.USER) //
static final OptionKey<HostAndPort> Inspect = new OptionKey<>(DEFAULT_ADDRESS, ADDRESS_OR_BOOLEAN);

@com.oracle.truffle.api.Option(help = "Attach to an existing endpoint instead of creating a new one. (default:false)", category = OptionCategory.DEBUG) //
static final OptionKey<Boolean> Attach = new OptionKey<>(false);

@com.oracle.truffle.api.Option(help = "Suspend the execution at first executed source line. (default:true)", category = OptionCategory.USER) //
static final OptionKey<Boolean> Suspend = new OptionKey<>(true);

Expand Down Expand Up @@ -135,8 +138,8 @@ protected void onCreate(Env env) {
connectionWatcher = new ConnectionWatcher();
try {
InetSocketAddress socketAddress = hostAndPort.createSocket(options.get(Remote));
server = new Server(env, "Main Context", socketAddress, options.get(Suspend), options.get(WaitAttached), options.get(HideErrors), options.get(Internal), options.get(Initialization),
options.get(Path), options.get(Secure), new KeyStoreOptions(options), connectionWatcher);
server = new Server(env, "Main Context", socketAddress, options.get(Attach), options.get(Suspend), options.get(WaitAttached), options.get(HideErrors), options.get(Internal),
options.get(Initialization), options.get(Path), options.get(Secure), new KeyStoreOptions(options), connectionWatcher);
} catch (IOException e) {
throw new InspectorIOException(hostAndPort.getHostPort(options.get(Remote)), e);
}
Expand Down Expand Up @@ -232,10 +235,10 @@ InetSocketAddress createSocket(boolean remote) throws UnknownHostException {

private static final class Server {

private InspectorServer wss;
private InspectorWSConnection wss;
private final String wsspath;

Server(final Env env, final String contextName, final InetSocketAddress socketAdress, final boolean debugBreak, final boolean waitAttached, final boolean hideErrors,
Server(final Env env, final String contextName, final InetSocketAddress socketAdress, final boolean attach, final boolean debugBreak, final boolean waitAttached, final boolean hideErrors,
final boolean inspectInternal, final boolean inspectInitialization, final String path, final boolean secure, final KeyStoreOptions keyStoreOptions,
final ConnectionWatcher connectionWatcher) throws IOException {
PrintWriter info = new PrintWriter(env.err());
Expand All @@ -248,31 +251,35 @@ private static final class Server {

PrintWriter err = (hideErrors) ? null : info;
final TruffleExecutionContext executionContext = new TruffleExecutionContext(contextName, inspectInternal, inspectInitialization, env, err);
URI wsuri;
try {
wsuri = new URI(secure ? "wss" : "ws", null, socketAdress.getAddress().getHostAddress(), socketAdress.getPort(), path, null, null);
} catch (URISyntaxException ex) {
throw new IOException(ex);
}
InspectServerSession iss = InspectServerSession.create(executionContext, debugBreak, connectionWatcher);
WSInterceptorServer interceptor = new WSInterceptorServer(wsuri, iss);
MessageEndpoint serverEndpoint;
try {
serverEndpoint = env.startServer(wsuri, iss);
} catch (MessageTransport.VetoException vex) {
throw new IOException(vex.getLocalizedMessage());
}
if (serverEndpoint == null) {
interceptor.close(path);
wss = WebSocketServer.get(socketAdress, wsspath, executionContext, debugBreak, secure, keyStoreOptions, connectionWatcher, iss);
String address = buildAddress(socketAdress.getAddress().getHostAddress(), wss.getListeningPort(), wsspath, secure);
info.println("Debugger listening on port " + wss.getListeningPort() + ".");
info.println("To start debugging, open the following URL in Chrome:");
info.println(" " + address);
info.flush();
if (attach) {
wss = new InspectWSClient(socketAdress, wsspath, executionContext, debugBreak, secure, keyStoreOptions, connectionWatcher, info);
} else {
interceptor.opened(serverEndpoint, connectionWatcher);
wss = interceptor;
URI wsuri;
try {
wsuri = new URI(secure ? "wss" : "ws", null, socketAdress.getAddress().getHostAddress(), socketAdress.getPort(), path, null, null);
} catch (URISyntaxException ex) {
throw new IOException(ex);
}
InspectServerSession iss = InspectServerSession.create(executionContext, debugBreak, connectionWatcher);
WSInterceptorServer interceptor = new WSInterceptorServer(wsuri, iss);
MessageEndpoint serverEndpoint;
try {
serverEndpoint = env.startServer(wsuri, iss);
} catch (MessageTransport.VetoException vex) {
throw new IOException(vex.getLocalizedMessage());
}
if (serverEndpoint == null) {
interceptor.close(path);
wss = WebSocketServer.get(socketAdress, wsspath, executionContext, debugBreak, secure, keyStoreOptions, connectionWatcher, iss);
String address = buildAddress(socketAdress.getAddress().getHostAddress(), wss.getPort(), wsspath, secure);
info.println("Debugger listening on port " + wss.getPort() + ".");
info.println("To start debugging, open the following URL in Chrome:");
info.println(" " + address);
info.flush();
} else {
interceptor.opened(serverEndpoint, connectionWatcher);
wss = interceptor;
}
}
if (debugBreak || waitAttached) {
final AtomicReference<EventBinding<?>> execEnter = new AtomicReference<>();
Expand Down
Loading

0 comments on commit 5cc66be

Please sign in to comment.