support SAPIDAttachingConnector for IDEA-168185

This commit is contained in:
Egor Ushakov
2018-08-17 11:27:51 +03:00
parent 29c92c2728
commit e43fa59477
9 changed files with 271 additions and 74 deletions
@@ -157,7 +157,7 @@ public class ThreadDumpAction extends AnAction implements AnAction.TransparentUp
hasEmptyStack = frames.size() == 0;
final TIntObjectHashMap<List<ObjectReference>> lockedAt = new TIntObjectHashMap<>();
if (vmProxy.canGetMonitorFrameInfo()) {
if (vmProxy.canGetMonitorFrameInfo() && vmProxy.canBeModified()) {
for (MonitorInfo info : threadReference.ownedMonitorsAndFrames()) {
final int stackDepth = info.stackDepth();
List<ObjectReference> monitors;
@@ -73,8 +73,11 @@ public class DebugProcessEvents extends DebugProcessImpl {
super.commitVM(vm);
if(vm != null) {
vmAttached();
myEventThread = new DebuggerEventThread();
ApplicationManager.getApplication().executeOnPooledThread(ConcurrencyUtil.underThreadNameRunnable("DebugProcessEvents", myEventThread));
if (vm.canBeModified()) {
myEventThread = new DebuggerEventThread();
ApplicationManager.getApplication().executeOnPooledThread(
ConcurrencyUtil.underThreadNameRunnable("DebugProcessEvents", myEventThread));
}
}
}
@@ -323,26 +326,28 @@ public class DebugProcessEvents extends DebugProcessImpl {
LOG.assertTrue(!isAttached());
if (myState.compareAndSet(State.INITIAL, State.ATTACHED)) {
final VirtualMachineProxyImpl machineProxy = getVirtualMachineProxy();
final EventRequestManager requestManager = machineProxy.eventRequestManager();
if (machineProxy.canBeModified()) {
final EventRequestManager requestManager = machineProxy.eventRequestManager();
if (machineProxy.canGetMethodReturnValues()) {
myReturnValueWatcher = new MethodReturnValueWatcher(requestManager, this);
if (machineProxy.canGetMethodReturnValues()) {
myReturnValueWatcher = new MethodReturnValueWatcher(requestManager, this);
}
enableNonSuspendingRequest(requestManager.createThreadStartRequest(),
event -> {
ThreadReference thread = ((ThreadStartEvent)event).thread();
getVirtualMachineProxy().threadStarted(thread);
myDebugProcessDispatcher.getMulticaster().threadStarted(this, thread);
});
enableNonSuspendingRequest(requestManager.createThreadDeathRequest(),
event -> {
ThreadReference thread = ((ThreadDeathEvent)event).thread();
getVirtualMachineProxy().threadStopped(thread);
myDebugProcessDispatcher.getMulticaster().threadStopped(this, thread);
});
}
enableNonSuspendingRequest(requestManager.createThreadStartRequest(),
event -> {
ThreadReference thread = ((ThreadStartEvent)event).thread();
getVirtualMachineProxy().threadStarted(thread);
myDebugProcessDispatcher.getMulticaster().threadStarted(this, thread);
});
enableNonSuspendingRequest(requestManager.createThreadDeathRequest(),
event -> {
ThreadReference thread = ((ThreadDeathEvent)event).thread();
getVirtualMachineProxy().threadStopped(thread);
myDebugProcessDispatcher.getMulticaster().threadStopped(this, thread);
});
// fill position managers
((DebuggerManagerImpl)DebuggerManager.getInstance(getProject())).getCustomPositionManagerFactories()
.map(factory -> factory.fun(this))
@@ -374,6 +379,10 @@ public class DebugProcessEvents extends DebugProcessImpl {
final String transportName = DebuggerBundle.getTransportName(getConnection());
showStatusText(DebuggerBundle.message("status.connected", addressDisplayName, transportName));
LOG.debug("leave: processVMStartEvent()");
if (!machineProxy.canBeModified()) {
myDebugProcessDispatcher.getMulticaster().paused(getSuspendManager().pushSuspendContext(EventRequest.SUSPEND_ALL, 0));
}
}
}
@@ -95,6 +95,7 @@ public abstract class DebugProcessImpl extends UserDataHolderBase implements Deb
@NonNls private static final String SHMEM_ATTACHING_CONNECTOR_NAME = "com.sun.jdi.SharedMemoryAttach";
@NonNls private static final String SOCKET_LISTENING_CONNECTOR_NAME = "com.sun.jdi.SocketListen";
@NonNls private static final String SHMEM_LISTENING_CONNECTOR_NAME = "com.sun.jdi.SharedMemoryListen";
@NonNls static final String SAPID_ATTACHING_CONNECTOR_NAME = "sun.jvm.hotspot.jdi.SAPIDAttachingConnector";
private final Project myProject;
private final RequestManagerImpl myRequestManager;
@@ -440,7 +441,27 @@ public abstract class DebugProcessImpl extends UserDataHolderBase implements Deb
}
final String address = myConnection.getAddress();
if (myConnection.isServerMode()) {
if (myConnection instanceof PidRemoteConnection) {
String pid = ((PidRemoteConnection)myConnection).getPid();
if (StringUtil.isEmpty(pid)) {
throw new CantRunException(DebuggerBundle.message("error.no.pid"));
}
AttachingConnector connector = (AttachingConnector)findConnector(SAPID_ATTACHING_CONNECTOR_NAME);
myArguments = connector.defaultArguments();
Connector.Argument pidArg = myArguments.get("pid");
if (pidArg != null) {
pidArg.setValue(pid);
}
myDebugProcessDispatcher.getMulticaster().connectorIsReady();
try {
return connector.attach(myArguments);
}
catch (IllegalArgumentException e) {
throw new CantRunException(e.getLocalizedMessage());
}
}
else if (myConnection.isServerMode()) {
ListeningConnector connector = (ListeningConnector)findConnector(myConnection.isUseSockets(), true);
myArguments = connector.defaultArguments();
if (myArguments == null) {
@@ -567,7 +588,7 @@ public abstract class DebugProcessImpl extends UserDataHolderBase implements Deb
}
@NotNull
private static Connector findConnector(String connectorName) throws ExecutionException {
static Connector findConnector(String connectorName) throws ExecutionException {
VirtualMachineManager virtualMachineManager;
try {
virtualMachineManager = Bootstrap.virtualMachineManager();
@@ -1449,7 +1470,13 @@ public abstract class DebugProcessImpl extends UserDataHolderBase implements Deb
@Override
protected void action() {
if (isAttached()) {
final VirtualMachineProxyImpl virtualMachineProxy = getVirtualMachineProxy();
VirtualMachineProxyImpl virtualMachineProxy = getVirtualMachineProxy();
if (!virtualMachineProxy.canBeModified()) {
closeProcess(true);
return;
}
if (myIsTerminateTargetVM) {
virtualMachineProxy.exit(-1);
}
@@ -2,14 +2,15 @@
package com.intellij.debugger.engine;
import com.intellij.debugger.impl.DebuggerManagerImpl;
import com.intellij.execution.ProgramRunnerUtil;
import com.intellij.execution.RunManager;
import com.intellij.execution.RunnerAndConfigurationSettings;
import com.intellij.execution.configurations.ConfigurationTypeUtil;
import com.intellij.execution.configurations.RunConfiguration;
import com.intellij.debugger.impl.GenericDebuggerRunner;
import com.intellij.execution.*;
import com.intellij.execution.configurations.*;
import com.intellij.execution.executors.DefaultDebugExecutor;
import com.intellij.execution.process.ProcessInfo;
import com.intellij.execution.runners.ExecutionEnvironment;
import com.intellij.execution.ui.RunContentDescriptor;
import com.intellij.internal.DebugAttachDetector;
import com.intellij.openapi.options.SettingsEditor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.Pair;
@@ -17,17 +18,19 @@ import com.intellij.openapi.util.UserDataHolder;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.rt.execution.application.AppMainV2;
import com.intellij.util.ArrayUtil;
import com.intellij.util.ReflectionUtil;
import com.intellij.util.execution.ParametersListUtil;
import com.intellij.xdebugger.attach.XDefaultLocalAttachGroup;
import com.intellij.xdebugger.attach.XLocalAttachDebugger;
import com.intellij.xdebugger.attach.XLocalAttachDebuggerProvider;
import com.intellij.xdebugger.attach.XLocalAttachGroup;
import com.sun.jdi.connect.Connector;
import com.sun.tools.attach.AttachNotSupportedException;
import com.sun.tools.attach.VirtualMachine;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.io.IOException;
import java.util.*;
@@ -35,6 +38,18 @@ import java.util.*;
* @author egor
*/
public class JavaAttachDebuggerProvider implements XLocalAttachDebuggerProvider {
private static final boolean SA_PID_ATTACH_AVAILABLE;
static {
Connector sapidAttachConnector = null;
try {
sapidAttachConnector = DebugProcessImpl.findConnector(DebugProcessImpl.SAPID_ATTACHING_CONNECTOR_NAME);
}
catch (ExecutionException ignored) {
}
SA_PID_ATTACH_AVAILABLE = sapidAttachConnector != null;
}
private static class JavaLocalAttachDebugger implements XLocalAttachDebugger {
private final Project myProject;
private final LocalAttachInfo myInfo;
@@ -52,31 +67,10 @@ public class JavaAttachDebuggerProvider implements XLocalAttachDebuggerProvider
@Override
public void attachDebugSession(@NotNull Project project, @NotNull ProcessInfo processInfo) {
// TODO: first need to remove circular dependency with intellij.java.execution.impl
//RunnerAndConfigurationSettings runSettings = RunManager.getInstance(project)
// .createRunConfiguration(StringUtil.notNullize(address.first) + ":" + address.second,
// RemoteConfigurationType.getInstance().getFactory());
//
//RemoteConfiguration configuration = (RemoteConfiguration)runSettings.getConfiguration();
//configuration.HOST = address.first;
//configuration.PORT = String.valueOf(address.second);
//configuration.USE_SOCKET_TRANSPORT = true;
//configuration.SERVER_MODE = false;
RunnerAndConfigurationSettings runSettings = RunManager.getInstance(myProject).createRunConfiguration(
"localhost:" + myInfo.myAddress,
Objects.requireNonNull(ConfigurationTypeUtil.findConfigurationType("Remote")).getConfigurationFactories()[0]);
RunConfiguration remoteConfiguration = runSettings.getConfiguration();
ReflectionUtil.setField(remoteConfiguration.getClass(), remoteConfiguration, String.class, "HOST",
DebuggerManagerImpl.LOCALHOST_ADDRESS_FALLBACK);
ReflectionUtil.setField(remoteConfiguration.getClass(), remoteConfiguration, String.class,
myInfo.myUseSocket ? "PORT" : "SHMEM_ADDRESS", myInfo.myAddress);
ReflectionUtil.setField(remoteConfiguration.getClass(), remoteConfiguration, boolean.class, "USE_SOCKET_TRANSPORT",
myInfo.myUseSocket);
ReflectionUtil.setField(remoteConfiguration.getClass(), remoteConfiguration, boolean.class, "SERVER_MODE", false);
ProgramRunnerUtil.executeConfiguration(runSettings, DefaultDebugExecutor.getDebugExecutorInstance());
RunnerAndConfigurationSettings runSettings =
RunManager.getInstance(myProject).createRunConfiguration(myInfo.getSessionName(), ProcessAttachRunConfigurationType.FACTORY);
((ProcessAttachRunConfiguration)runSettings.getConfiguration()).myAttachInfo = myInfo;
ProgramRunnerUtil.executeConfiguration(runSettings, ProcessAttachDebugExecutor.INSTANCE);
}
}
@@ -99,20 +93,20 @@ public class JavaAttachDebuggerProvider implements XLocalAttachDebuggerProvider
public String getProcessDisplayText(@NotNull Project project, @NotNull ProcessInfo info, @NotNull UserDataHolder dataHolder) {
LocalAttachInfo attachInfo = getAttachInfo(info, dataHolder.getUserData(ADDRESS_MAP_KEY));
assert attachInfo != null;
StringBuilder res = new StringBuilder();
String res = "";
String executable = info.getExecutableDisplayName();
if ("java".equals(executable)) {
if (!StringUtil.isEmpty(attachInfo.myClass)) {
res.append(attachInfo.myClass);
res = attachInfo.myClass;
}
else {
res.append(StringUtil.notNullize(ArrayUtil.getLastElement(info.getCommandLine().split(" ")))); // should be class name
res = StringUtil.notNullize(ArrayUtil.getLastElement(info.getCommandLine().split(" "))); // should be class name
}
}
else {
res.append(executable);
res = executable;
}
return res.append(" (").append(attachInfo.myAddress).append(')').toString();
return attachInfo.getProcessDisplayText(res);
}
};
@@ -147,16 +141,17 @@ public class JavaAttachDebuggerProvider implements XLocalAttachDebuggerProvider
@Nullable
private static LocalAttachInfo getAttachInfo(ProcessInfo processInfo, @Nullable Map<String, LocalAttachInfo> addressMap) {
LocalAttachInfo res;
String pidString = String.valueOf(processInfo.getPid());
if (addressMap != null) {
res = addressMap.get(String.valueOf(processInfo.getPid()));
res = addressMap.get(pidString);
}
else {
res = getProcessAttachInfo(String.valueOf(processInfo.getPid()));
res = getProcessAttachInfo(pidString);
}
if (res == null) {
Pair<String, Integer> address = DebugAttachDetector.getAttachAddress(ParametersListUtil.parse(processInfo.getCommandLine()));
if (address != null) {
res = new LocalAttachInfo(true, String.valueOf(address.getSecond()), null);
res = new DebuggerLocalAttachInfo(true, String.valueOf(address.getSecond()), null, pidString);
}
}
return res;
@@ -168,16 +163,20 @@ public class JavaAttachDebuggerProvider implements XLocalAttachDebuggerProvider
try {
vm = VirtualMachine.attach(pid);
Properties agentProperties = vm.getAgentProperties();
String command = agentProperties.getProperty("sun.java.command");
if (!StringUtil.isEmpty(command)) {
command = StringUtil.replace(command, AppMainV2.class.getName(), "").trim();
command = StringUtil.substringBefore(command, " ");
}
String property = agentProperties.getProperty("sun.jdwp.listenerAddress");
if (property != null && property.indexOf(':') != -1) {
String command = agentProperties.getProperty("sun.java.command");
if (!StringUtil.isEmpty(command)) {
command = StringUtil.replace(command, AppMainV2.class.getName(), "").trim();
command = StringUtil.substringBefore(command, " ");
}
return new LocalAttachInfo(!"dt_shmem".equals(StringUtil.substringBefore(property, ":")),
StringUtil.substringAfter(property, ":"),
command);
return new DebuggerLocalAttachInfo(!"dt_shmem".equals(StringUtil.substringBefore(property, ":")),
StringUtil.substringAfter(property, ":"),
command,
pid);
}
if (SA_PID_ATTACH_AVAILABLE) {
return new LocalAttachInfo(command, pid);
}
}
catch (AttachNotSupportedException | IOException ignored) {
@@ -194,15 +193,143 @@ public class JavaAttachDebuggerProvider implements XLocalAttachDebuggerProvider
return null;
}
private static class LocalAttachInfo {
private static class DebuggerLocalAttachInfo extends LocalAttachInfo {
final boolean myUseSocket;
final String myAddress;
final String myClass;
private LocalAttachInfo(boolean socket, @NotNull String address, String aClass) {
public DebuggerLocalAttachInfo(boolean socket, @NotNull String address, String aClass, String pid) {
super(aClass, pid);
myUseSocket = socket;
myAddress = address;
}
@Override
RemoteConnection createConnection() {
return new RemoteConnection(myUseSocket, DebuggerManagerImpl.LOCALHOST_ADDRESS_FALLBACK, myAddress, false);
}
@Override
String getSessionName() {
return "localhost:" + myAddress;
}
@Override
String getProcessDisplayText(String text) {
return text + " (" + myAddress + ")";
}
}
private static class LocalAttachInfo {
final String myClass;
final String myPid;
private LocalAttachInfo(String aClass, String pid) {
myClass = aClass;
myPid = pid;
}
RemoteConnection createConnection() {
return new PidRemoteConnection(myPid);
}
String getSessionName() {
return "pid " + myPid;
}
String getProcessDisplayText(String text) {
return text;
}
}
private static class ProcessAttachDebugExecutor extends DefaultDebugExecutor {
static ProcessAttachDebugExecutor INSTANCE = new ProcessAttachDebugExecutor();
private ProcessAttachDebugExecutor() {
}
@NotNull
@Override
public String getId() {
return "ProcessAttachDebug";
}
}
public static class ProcessAttachDebuggerRunner extends GenericDebuggerRunner {
@NotNull
@Override
public String getRunnerId() {
return "ProcessAttachDebuggerRunner";
}
@Nullable
@Override
protected RunContentDescriptor createContentDescriptor(@NotNull RunProfileState state, @NotNull ExecutionEnvironment environment)
throws ExecutionException {
return attachVirtualMachine(state, environment, ((RemoteState)state).getRemoteConnection(), false);
}
@Override
public boolean canRun(@NotNull String executorId, @NotNull RunProfile profile) {
return executorId.equals(ProcessAttachDebugExecutor.INSTANCE.getId());
}
}
private static class ProcessAttachRunConfiguration extends RunConfigurationBase {
private LocalAttachInfo myAttachInfo;
protected ProcessAttachRunConfiguration(@NotNull Project project) {
super(project, ProcessAttachRunConfigurationType.FACTORY, "ProcessAttachRunConfiguration");
}
@NotNull
@Override
public SettingsEditor<? extends RunConfiguration> getConfigurationEditor() {
throw new IllegalStateException("Editing is not supported");
}
@Nullable
@Override
public RunProfileState getState(@NotNull Executor executor, @NotNull ExecutionEnvironment environment) throws ExecutionException {
return new RemoteStateState(getProject(), myAttachInfo.createConnection());
}
}
private static class ProcessAttachRunConfigurationType implements ConfigurationType {
static final ProcessAttachRunConfigurationType INSTANCE = new ProcessAttachRunConfigurationType();
static final ConfigurationFactory FACTORY = new ConfigurationFactory(INSTANCE) {
@NotNull
@Override
public RunConfiguration createTemplateConfiguration(@NotNull Project project) {
return new ProcessAttachRunConfiguration(project);
}
};
@Nls
@Override
public String getDisplayName() {
return getId();
}
@Nls
@Override
public String getConfigurationTypeDescription() {
return getId();
}
@Override
public Icon getIcon() {
return null;
}
@NotNull
@Override
public String getId() {
return "ProcessAttachRunConfigurationType";
}
@Override
public ConfigurationFactory[] getConfigurationFactories() {
return new ConfigurationFactory[]{FACTORY};
}
}
}
@@ -0,0 +1,20 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.debugger.engine;
import com.intellij.execution.configurations.RemoteConnection;
/**
* @author egor
*/
class PidRemoteConnection extends RemoteConnection {
private final String myPid;
public PidRemoteConnection(String pid) {
super(false, null, null, false);
myPid = pid;
}
public String getPid() {
return myPid;
}
}
@@ -382,7 +382,9 @@ public class RequestManagerImpl extends DebugProcessAdapterImpl implements Reque
@Override
public void processAttached(DebugProcessImpl process) {
myEventRequestManager = myDebugProcess.getVirtualMachineProxy().eventRequestManager();
if (myDebugProcess.getVirtualMachineProxy().canBeModified()) {
myEventRequestManager = myDebugProcess.getVirtualMachineProxy().eventRequestManager();
}
}
public void processClassPrepared(final ClassPrepareEvent event) {
@@ -221,6 +221,9 @@ public class VirtualMachineProxyImpl implements JdiTimer, VirtualMachineProxy {
}
public void suspend() {
if (!canBeModified()) {
return;
}
DebuggerManagerThreadImpl.assertIsManagerThread();
myPausePressedCount++;
myVirtualMachine.suspend();
@@ -228,6 +231,9 @@ public class VirtualMachineProxyImpl implements JdiTimer, VirtualMachineProxy {
}
public void resume() {
if (!canBeModified()) {
return;
}
DebuggerManagerThreadImpl.assertIsManagerThread();
if (myPausePressedCount > 0) {
myPausePressedCount--;
@@ -556,6 +562,10 @@ public class VirtualMachineProxyImpl implements JdiTimer, VirtualMachineProxy {
return myCanGetInstanceInfo.isAvailable();
}
public boolean canBeModified() {
return myVirtualMachine.canBeModified();
}
@Override
public final boolean versionHigher(String version) {
return myVirtualMachine.version().compareTo(version) >= 0;
@@ -75,6 +75,7 @@ error.debug.connector.not.found=Required connector ''{0}'' not found. Check your
error.no.debug.listen.port=The port to listen at unspecified
error.no.debug.attach.port=The port to attach to unspecified
error.no.shmem.address=Shared memory address unspecified
error.no.pid=Pid is not specified
debugger.jdi.bootstrap.error={0}. Check your JDK installation.
warning.jdk140.unstable=The debuggee VM version is "1.4.0".\nJ2SDK 1.4.0 documented bugs may cause unstable debugger behavior.\nWe recommend using J2SDK 1.4.0_01 or higher.
title.jdk140.unstable=VM Version Warning
+1
View File
@@ -321,6 +321,7 @@
<debugger.nodeNameAdjuster implementation="com.intellij.debugger.ui.impl.watch.FieldOuterLocalNameAdjuster"/>
<programRunner id="defaultDebugRunner" implementation="com.intellij.debugger.impl.GenericDebuggerRunner" order="last"/>
<programRunner implementation="com.intellij.debugger.engine.JavaAttachDebuggerProvider$ProcessAttachDebuggerRunner" order="last"/>
<programRunner implementation="com.intellij.execution.jar.JarApplicationDebuggerRunner"/>
<iconLayerProvider implementation="com.intellij.compiler.CompilerIconLayerProvider"/>