mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-04 10:19:23 +02:00
GP-3836: Add Trace RMI 'Connections' pane.
This commit is contained in:
parent
5fd01c739d
commit
bf8f7c8f78
82 changed files with 3836 additions and 270 deletions
|
@ -17,6 +17,8 @@ package ghidra.app.plugin.core.debug.gui.tracermi.connection;
|
|||
|
||||
import ghidra.app.plugin.PluginCategoryNames;
|
||||
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
||||
import ghidra.app.plugin.core.debug.event.TraceActivatedPluginEvent;
|
||||
import ghidra.app.plugin.core.debug.event.TraceInactiveCoordinatesPluginEvent;
|
||||
import ghidra.app.services.TraceRmiService;
|
||||
import ghidra.framework.plugintool.*;
|
||||
import ghidra.framework.plugintool.util.PluginStatus;
|
||||
|
@ -29,14 +31,30 @@ import ghidra.framework.plugintool.util.PluginStatus;
|
|||
""",
|
||||
category = PluginCategoryNames.DEBUGGER,
|
||||
packageName = DebuggerPluginPackage.NAME,
|
||||
status = PluginStatus.RELEASED,
|
||||
status = PluginStatus.STABLE,
|
||||
eventsConsumed = {
|
||||
TraceActivatedPluginEvent.class,
|
||||
TraceInactiveCoordinatesPluginEvent.class,
|
||||
},
|
||||
servicesRequired = {
|
||||
TraceRmiService.class,
|
||||
})
|
||||
public class TraceRmiConnectionManagerPlugin extends Plugin {
|
||||
private final TraceRmiConnectionManagerProvider provider;
|
||||
|
||||
public TraceRmiConnectionManagerPlugin(PluginTool tool) {
|
||||
super(tool);
|
||||
this.provider = new TraceRmiConnectionManagerProvider(this);
|
||||
}
|
||||
|
||||
// TODO: Add the actual provider. This will probably replace DebuggerTargetsPlugin.
|
||||
@Override
|
||||
public void processEvent(PluginEvent event) {
|
||||
super.processEvent(event);
|
||||
if (event instanceof TraceActivatedPluginEvent evt) {
|
||||
provider.coordinates(evt.getActiveCoordinates());
|
||||
}
|
||||
if (event instanceof TraceInactiveCoordinatesPluginEvent evt) {
|
||||
provider.coordinates(evt.getCoordinates());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,532 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.gui.tracermi.connection;
|
||||
|
||||
import java.awt.AWTEvent;
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.event.*;
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.tree.TreeSelectionModel;
|
||||
|
||||
import docking.ActionContext;
|
||||
import docking.WindowPosition;
|
||||
import docking.action.DockingAction;
|
||||
import docking.action.builder.ActionBuilder;
|
||||
import docking.widgets.tree.*;
|
||||
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||
import ghidra.app.plugin.core.debug.gui.objects.components.DebuggerMethodInvocationDialog;
|
||||
import ghidra.app.plugin.core.debug.gui.tracermi.connection.tree.*;
|
||||
import ghidra.app.services.*;
|
||||
import ghidra.dbg.target.TargetMethod.ParameterDescription;
|
||||
import ghidra.dbg.target.TargetMethod.TargetParameterMap;
|
||||
import ghidra.debug.api.control.ControlMode;
|
||||
import ghidra.debug.api.target.Target;
|
||||
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
|
||||
import ghidra.debug.api.tracermi.TraceRmiAcceptor;
|
||||
import ghidra.debug.api.tracermi.TraceRmiConnection;
|
||||
import ghidra.framework.plugintool.*;
|
||||
import ghidra.framework.plugintool.AutoService.Wiring;
|
||||
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
||||
import ghidra.framework.plugintool.util.PluginUtils;
|
||||
import ghidra.util.HelpLocation;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
|
||||
public class TraceRmiConnectionManagerProvider extends ComponentProviderAdapter {
|
||||
public static final String TITLE = "Connections";
|
||||
public static final HelpLocation HELP =
|
||||
new HelpLocation(PluginUtils.getPluginNameFromClass(TraceRmiConnectionManagerPlugin.class),
|
||||
DebuggerResources.HELP_ANCHOR_PLUGIN);
|
||||
|
||||
private static final String GROUP_SERVER = "2. Server";
|
||||
private static final String GROUP_CONNECT = "1. Connect";
|
||||
private static final String GROUP_MAINTENANCE = "3. Maintenance";
|
||||
|
||||
private static final ParameterDescription<String> PARAM_ADDRESS =
|
||||
ParameterDescription.create(String.class, "address", true, "localhost",
|
||||
"Host/Address", "Address or hostname for interface(s) to listen on");
|
||||
private static final ParameterDescription<Integer> PARAM_PORT =
|
||||
ParameterDescription.create(Integer.class, "port", true, 0,
|
||||
"Port", "TCP port number, 0 for ephemeral");
|
||||
private static final TargetParameterMap PARAMETERS = TargetParameterMap.ofEntries(
|
||||
Map.entry(PARAM_ADDRESS.name, PARAM_ADDRESS),
|
||||
Map.entry(PARAM_PORT.name, PARAM_PORT));
|
||||
|
||||
interface StartServerAction {
|
||||
String NAME = "Start Server";
|
||||
String DESCRIPTION = "Start a TCP server for incoming connections (indefinitely)";
|
||||
String GROUP = GROUP_SERVER;
|
||||
String HELP_ANCHOR = "start_server";
|
||||
|
||||
static ActionBuilder builder(Plugin owner) {
|
||||
String ownerName = owner.getName();
|
||||
return new ActionBuilder(NAME, ownerName)
|
||||
.description(DESCRIPTION)
|
||||
.menuPath(NAME)
|
||||
.menuGroup(GROUP)
|
||||
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
|
||||
}
|
||||
}
|
||||
|
||||
interface StopServerAction {
|
||||
String NAME = "Stop Server";
|
||||
String DESCRIPTION = "Close the TCP server";
|
||||
String GROUP = GROUP_SERVER;
|
||||
String HELP_ANCHOR = "stop_server";
|
||||
|
||||
static ActionBuilder builder(Plugin owner) {
|
||||
String ownerName = owner.getName();
|
||||
return new ActionBuilder(NAME, ownerName)
|
||||
.description(DESCRIPTION)
|
||||
.menuPath(NAME)
|
||||
.menuGroup(GROUP)
|
||||
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
|
||||
}
|
||||
}
|
||||
|
||||
interface ConnectAcceptAction {
|
||||
String NAME = "Connect by Accept";
|
||||
String DESCRIPTION = "Accept a single inbound TCP connection";
|
||||
String GROUP = GROUP_CONNECT;
|
||||
Icon ICON = DebuggerResources.ICON_CONNECT_ACCEPT;
|
||||
String HELP_ANCHOR = "connect_accept";
|
||||
|
||||
static ActionBuilder builder(Plugin owner) {
|
||||
String ownerName = owner.getName();
|
||||
return new ActionBuilder(NAME, ownerName)
|
||||
.description(DESCRIPTION)
|
||||
.toolBarIcon(ICON)
|
||||
.toolBarGroup(GROUP)
|
||||
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
|
||||
}
|
||||
}
|
||||
|
||||
interface ConnectOutboundAction {
|
||||
String NAME = "Connect Outbound";
|
||||
String DESCRIPTION = "Connect to a listening agent/plugin by TCP";
|
||||
String GROUP = GROUP_CONNECT;
|
||||
Icon ICON = DebuggerResources.ICON_CONNECT_OUTBOUND;
|
||||
String HELP_ANCHOR = "connect_outbound";
|
||||
|
||||
static ActionBuilder builder(Plugin owner) {
|
||||
String ownerName = owner.getName();
|
||||
return new ActionBuilder(NAME, ownerName)
|
||||
.description(DESCRIPTION)
|
||||
.toolBarIcon(ICON)
|
||||
.toolBarGroup(GROUP)
|
||||
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
|
||||
}
|
||||
}
|
||||
|
||||
interface CloseConnectionAction {
|
||||
String NAME = "Close";
|
||||
String DESCRIPTION = "Close a connection or server";
|
||||
String GROUP = GROUP_MAINTENANCE;
|
||||
String HELP_ANCHOR = "close";
|
||||
|
||||
static ActionBuilder builder(Plugin owner) {
|
||||
String ownerName = owner.getName();
|
||||
return new ActionBuilder(NAME, ownerName)
|
||||
.description(DESCRIPTION)
|
||||
.menuPath(NAME)
|
||||
.popupMenuPath(NAME)
|
||||
.menuGroup(GROUP)
|
||||
.popupMenuGroup(GROUP)
|
||||
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
|
||||
}
|
||||
}
|
||||
|
||||
interface CloseAllAction {
|
||||
String NAME = "Close All";
|
||||
String DESCRIPTION = "Close all connections and the server";
|
||||
String GROUP = GROUP_MAINTENANCE;
|
||||
String HELP_ANCHOR = "close_all";
|
||||
|
||||
static ActionBuilder builder(Plugin owner) {
|
||||
String ownerName = owner.getName();
|
||||
return new ActionBuilder(NAME, ownerName)
|
||||
.description(DESCRIPTION)
|
||||
.menuPath(NAME)
|
||||
.menuGroup(GROUP)
|
||||
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
|
||||
}
|
||||
}
|
||||
|
||||
class InjectableGTree extends GTree {
|
||||
public InjectableGTree(GTreeNode root) {
|
||||
super(root);
|
||||
}
|
||||
|
||||
/**
|
||||
* This allows the test framework to use reflection to access this method.
|
||||
*/
|
||||
@Override
|
||||
protected void processEvent(AWTEvent e) {
|
||||
super.processEvent(e);
|
||||
}
|
||||
}
|
||||
|
||||
private final TraceRmiConnectionManagerPlugin plugin;
|
||||
|
||||
// @AutoServiceConsumed via method
|
||||
TraceRmiService traceRmiService;
|
||||
// @AutoServiceConsumed via method
|
||||
DebuggerTargetService targetService;
|
||||
@AutoServiceConsumed
|
||||
DebuggerConsoleService consoleService;
|
||||
@AutoServiceConsumed
|
||||
DebuggerTraceManagerService traceManagerService;
|
||||
@AutoServiceConsumed
|
||||
DebuggerControlService controlService;
|
||||
@SuppressWarnings("unused")
|
||||
private final Wiring autoServiceWiring;
|
||||
|
||||
private JPanel mainPanel;
|
||||
protected GTree tree;
|
||||
protected TraceRmiServiceNode rootNode = new TraceRmiServiceNode(this);
|
||||
|
||||
DockingAction actionStartServer;
|
||||
DockingAction actionStopServer;
|
||||
DockingAction actionConnectAccept;
|
||||
DockingAction actionConnectOutbound;
|
||||
DockingAction actionCloseConnection;
|
||||
DockingAction actionCloseAll;
|
||||
|
||||
TraceRmiManagerActionContext myActionContext;
|
||||
|
||||
public TraceRmiConnectionManagerProvider(TraceRmiConnectionManagerPlugin plugin) {
|
||||
super(plugin.getTool(), TITLE, plugin.getName());
|
||||
this.plugin = plugin;
|
||||
|
||||
this.autoServiceWiring = AutoService.wireServicesConsumed(plugin, this);
|
||||
setTitle(TITLE);
|
||||
setIcon(DebuggerResources.ICON_PROVIDER_TARGETS);
|
||||
setHelpLocation(HELP);
|
||||
setWindowMenuGroup(DebuggerPluginPackage.NAME);
|
||||
|
||||
buildMainPanel();
|
||||
|
||||
setDefaultWindowPosition(WindowPosition.LEFT);
|
||||
setVisible(true);
|
||||
createActions();
|
||||
}
|
||||
|
||||
private void buildMainPanel() {
|
||||
mainPanel = new JPanel(new BorderLayout());
|
||||
|
||||
tree = new InjectableGTree(rootNode);
|
||||
tree.setRootVisible(false);
|
||||
tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
|
||||
mainPanel.add(tree);
|
||||
|
||||
tree.getGTSelectionModel().addGTreeSelectionListener(evt -> {
|
||||
setContext();
|
||||
});
|
||||
tree.addGTModelListener((AnyChangeTreeModelListener) e -> {
|
||||
setContext();
|
||||
});
|
||||
// TODO: Double-click or ENTER (activate) should open and/or activate trace/snap
|
||||
tree.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
if (e.getClickCount() == 2 && e.getButton() == MouseEvent.BUTTON1) {
|
||||
activateSelectedNode();
|
||||
e.consume();
|
||||
}
|
||||
}
|
||||
});
|
||||
tree.addKeyListener(new KeyAdapter() {
|
||||
@Override
|
||||
public void keyPressed(KeyEvent e) {
|
||||
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
|
||||
activateSelectedNode();
|
||||
e.consume();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void activateSelectedNode() {
|
||||
List<GTreeNode> selList = tree.getSelectedNodes();
|
||||
if (selList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
assert selList.size() == 1;
|
||||
GTreeNode sel = selList.get(0);
|
||||
nodeActivated((TraceRmiManagerNode) sel);
|
||||
}
|
||||
|
||||
private void nodeActivated(TraceRmiManagerNode node) {
|
||||
if (node instanceof TraceRmiTargetNode tNode) {
|
||||
if (traceManagerService == null) {
|
||||
return;
|
||||
}
|
||||
Target target = tNode.getTarget();
|
||||
traceManagerService.activateTarget(target);
|
||||
if (controlService == null) {
|
||||
return;
|
||||
}
|
||||
if (!controlService.getCurrentMode(target.getTrace()).isTarget()) {
|
||||
controlService.setCurrentMode(target.getTrace(), ControlMode.RO_TARGET);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void createActions() {
|
||||
actionStartServer = StartServerAction.builder(plugin)
|
||||
.enabledWhen(this::isActionStartServerEnabled)
|
||||
.onAction(this::doActionStartServerActivated)
|
||||
.buildAndInstallLocal(this);
|
||||
actionStopServer = StopServerAction.builder(plugin)
|
||||
.enabledWhen(this::isActionStopServerEnabled)
|
||||
.onAction(this::doActionStopServerActivated)
|
||||
.buildAndInstallLocal(this);
|
||||
|
||||
actionConnectAccept = ConnectAcceptAction.builder(plugin)
|
||||
.enabledWhen(this::isActionConnectAcceptEnabled)
|
||||
.onAction(this::doActionConnectAcceptActivated)
|
||||
.buildAndInstallLocal(this);
|
||||
actionConnectOutbound = ConnectOutboundAction.builder(plugin)
|
||||
.enabledWhen(this::isActionConnectOutboundEnabled)
|
||||
.onAction(this::doActionConnectOutboundActivated)
|
||||
.buildAndInstallLocal(this);
|
||||
|
||||
actionCloseConnection = CloseConnectionAction.builder(plugin)
|
||||
.withContext(TraceRmiManagerActionContext.class)
|
||||
.enabledWhen(this::isActionCloseConnectionEnabled)
|
||||
.onAction(this::doActionCloseConnectionActivated)
|
||||
.buildAndInstallLocal(this);
|
||||
actionCloseAll = CloseAllAction.builder(plugin)
|
||||
.enabledWhen(this::isActionCloseAllEnabled)
|
||||
.onAction(this::doActionCloseAllActivated)
|
||||
.buildAndInstallLocal(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActionContext getActionContext(MouseEvent event) {
|
||||
if (myActionContext == null) {
|
||||
return super.getActionContext(event);
|
||||
}
|
||||
return myActionContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JComponent getComponent() {
|
||||
return mainPanel;
|
||||
}
|
||||
|
||||
private void setContext() {
|
||||
myActionContext = new TraceRmiManagerActionContext(this, tree.getSelectionPath(), tree);
|
||||
contextChanged();
|
||||
}
|
||||
|
||||
private boolean isActionStartServerEnabled(ActionContext __) {
|
||||
return traceRmiService != null && !traceRmiService.isServerStarted();
|
||||
}
|
||||
|
||||
private InetSocketAddress promptSocketAddress(String title, String okText) {
|
||||
DebuggerMethodInvocationDialog dialog = new DebuggerMethodInvocationDialog(tool,
|
||||
title, okText, DebuggerResources.ICON_CONNECTION);
|
||||
Map<String, ?> arguments;
|
||||
do {
|
||||
dialog.forgetMemorizedArguments();
|
||||
arguments = dialog.promptArguments(PARAMETERS);
|
||||
}
|
||||
while (dialog.isResetRequested());
|
||||
if (arguments == null) {
|
||||
return null;
|
||||
}
|
||||
String address = PARAM_ADDRESS.get(arguments);
|
||||
int port = PARAM_PORT.get(arguments);
|
||||
return new InetSocketAddress(address, port);
|
||||
}
|
||||
|
||||
private void doActionStartServerActivated(ActionContext __) {
|
||||
InetSocketAddress sockaddr = promptSocketAddress("Start Trace RMI Server", "Start");
|
||||
if (sockaddr == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
traceRmiService.setServerAddress(sockaddr);
|
||||
traceRmiService.startServer();
|
||||
if (consoleService != null) {
|
||||
consoleService.log(DebuggerResources.ICON_CONNECTION,
|
||||
"TraceRmi Server listening at " + traceRmiService.getServerAddress());
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
Msg.error(this, "Could not start TraceRmi server: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isActionStopServerEnabled(ActionContext __) {
|
||||
return traceRmiService != null && traceRmiService.isServerStarted();
|
||||
}
|
||||
|
||||
private void doActionStopServerActivated(ActionContext __) {
|
||||
traceRmiService.stopServer();
|
||||
if (consoleService != null) {
|
||||
consoleService.log(DebuggerResources.ICON_DISCONNECT, "TraceRmi Server stopped");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isActionConnectAcceptEnabled(ActionContext __) {
|
||||
return traceRmiService != null;
|
||||
}
|
||||
|
||||
private void doActionConnectAcceptActivated(ActionContext __) {
|
||||
InetSocketAddress sockaddr = promptSocketAddress("Accept Trace RMI Connection", "Listen");
|
||||
if (sockaddr == null) {
|
||||
return;
|
||||
}
|
||||
CompletableFuture.runAsync(() -> {
|
||||
// TODO: Progress entry
|
||||
try {
|
||||
TraceRmiAcceptor acceptor = traceRmiService.acceptOne(sockaddr);
|
||||
acceptor.accept();
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
// Nothing. User should already know.
|
||||
}
|
||||
catch (Throwable e) {
|
||||
Msg.showError(this, null, "Accept",
|
||||
"Could not accept Trace RMI Connection on " + sockaddr + ": " + e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private boolean isActionConnectOutboundEnabled(ActionContext __) {
|
||||
return traceRmiService != null;
|
||||
}
|
||||
|
||||
private void doActionConnectOutboundActivated(ActionContext __) {
|
||||
InetSocketAddress sockaddr = promptSocketAddress("Connect to Trace RMI", "Connect");
|
||||
if (sockaddr == null) {
|
||||
return;
|
||||
}
|
||||
CompletableFuture.runAsync(() -> {
|
||||
// TODO: Progress entry?
|
||||
try {
|
||||
traceRmiService.connect(sockaddr);
|
||||
}
|
||||
catch (Throwable e) {
|
||||
Msg.showError(this, null, "Connect",
|
||||
"Could connect to Trace RMI at " + sockaddr + ": " + e.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private boolean isActionCloseConnectionEnabled(TraceRmiManagerActionContext context) {
|
||||
TraceRmiManagerNode node = context.getSelectedNode();
|
||||
if (node instanceof TraceRmiConnectionNode) {
|
||||
return true;
|
||||
}
|
||||
if (node instanceof TraceRmiAcceptorNode) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void doActionCloseConnectionActivated(TraceRmiManagerActionContext context) {
|
||||
TraceRmiManagerNode node = context.getSelectedNode();
|
||||
if (node instanceof TraceRmiConnectionNode cxNode) {
|
||||
try {
|
||||
cxNode.getConnection().close();
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.showError(this, null, "Close Connection",
|
||||
"Could not close Trace RMI connection: " + e);
|
||||
}
|
||||
}
|
||||
else if (node instanceof TraceRmiAcceptorNode acNode) {
|
||||
acNode.getAcceptor().cancel();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isActionCloseAllEnabled(ActionContext __) {
|
||||
return traceRmiService != null;
|
||||
}
|
||||
|
||||
private void doActionCloseAllActivated(ActionContext __) {
|
||||
try {
|
||||
doActionStopServerActivated(__);
|
||||
}
|
||||
catch (Throwable e) {
|
||||
Msg.error(this, "Could not close server: " + e);
|
||||
}
|
||||
for (TraceRmiConnection connection : traceRmiService.getAllConnections()) {
|
||||
try {
|
||||
connection.close();
|
||||
}
|
||||
catch (Throwable e) {
|
||||
Msg.error(this, "Could not close " + connection + ": " + e);
|
||||
}
|
||||
}
|
||||
for (TraceRmiAcceptor acceptor : traceRmiService.getAllAcceptors()) {
|
||||
try {
|
||||
acceptor.cancel();
|
||||
}
|
||||
catch (Throwable e) {
|
||||
Msg.error(this, "Could not cancel " + acceptor + ": " + e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AutoServiceConsumed
|
||||
private void setTraceRmiService(TraceRmiService traceRmiService) {
|
||||
if (this.traceRmiService != null) {
|
||||
this.traceRmiService.removeTraceServiceListener(rootNode);
|
||||
}
|
||||
this.traceRmiService = traceRmiService;
|
||||
if (this.traceRmiService != null) {
|
||||
this.traceRmiService.addTraceServiceListener(rootNode);
|
||||
}
|
||||
}
|
||||
|
||||
@AutoServiceConsumed
|
||||
private void setTargetService(DebuggerTargetService targetService) {
|
||||
if (this.targetService != null) {
|
||||
this.targetService.removeTargetPublicationListener(rootNode);
|
||||
}
|
||||
this.targetService = targetService;
|
||||
if (this.targetService != null) {
|
||||
this.targetService.addTargetPublicationListener(rootNode);
|
||||
}
|
||||
}
|
||||
|
||||
public TraceRmiService getTraceRmiService() {
|
||||
return traceRmiService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Coordinates, whether active or inactive, for a trace changed
|
||||
*
|
||||
* @param coordinates the coordinates
|
||||
*/
|
||||
public void coordinates(DebuggerCoordinates coordinates) {
|
||||
if (rootNode == null) {
|
||||
return;
|
||||
}
|
||||
rootNode.coordinates(coordinates);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.gui.tracermi.connection;
|
||||
|
||||
import javax.swing.tree.TreePath;
|
||||
|
||||
import docking.DefaultActionContext;
|
||||
import docking.widgets.tree.GTree;
|
||||
import ghidra.app.plugin.core.debug.gui.tracermi.connection.tree.TraceRmiManagerNode;
|
||||
|
||||
public class TraceRmiManagerActionContext extends DefaultActionContext {
|
||||
private final TreePath path;
|
||||
|
||||
public TraceRmiManagerActionContext(TraceRmiConnectionManagerProvider provider,
|
||||
TreePath path, GTree tree) {
|
||||
super(provider, path, tree);
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public TraceRmiManagerNode getSelectedNode() {
|
||||
if (path == null) {
|
||||
return null;
|
||||
}
|
||||
return (TraceRmiManagerNode) path.getLastPathComponent();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.gui.tracermi.connection.tree;
|
||||
|
||||
import docking.widgets.tree.GTreeNode;
|
||||
import ghidra.app.plugin.core.debug.gui.tracermi.connection.TraceRmiConnectionManagerProvider;
|
||||
|
||||
public abstract class AbstractTraceRmiManagerNode extends GTreeNode implements TraceRmiManagerNode {
|
||||
protected final TraceRmiConnectionManagerProvider provider;
|
||||
protected final String name;
|
||||
|
||||
public AbstractTraceRmiManagerNode(TraceRmiConnectionManagerProvider provider, String name) {
|
||||
this.provider = provider;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.gui.tracermi.connection.tree;
|
||||
|
||||
import javax.swing.Icon;
|
||||
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||
import ghidra.app.plugin.core.debug.gui.tracermi.connection.TraceRmiConnectionManagerProvider;
|
||||
import ghidra.debug.api.tracermi.TraceRmiAcceptor;
|
||||
|
||||
public class TraceRmiAcceptorNode extends AbstractTraceRmiManagerNode {
|
||||
|
||||
private static final Icon ICON = DebuggerResources.ICON_CONNECT_ACCEPT;
|
||||
|
||||
private final TraceRmiAcceptor acceptor;
|
||||
|
||||
public TraceRmiAcceptorNode(TraceRmiConnectionManagerProvider provider,
|
||||
TraceRmiAcceptor acceptor) {
|
||||
super(provider, "ACCEPTING: " + acceptor.getAddress());
|
||||
this.acceptor = acceptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Icon getIcon(boolean expanded) {
|
||||
return ICON;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getToolTip() {
|
||||
return "Trace RMI Acceptor listening at " + acceptor.getAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLeaf() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public TraceRmiAcceptor getAcceptor() {
|
||||
return acceptor;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.gui.tracermi.connection.tree;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.swing.Icon;
|
||||
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||
import ghidra.app.plugin.core.debug.gui.tracermi.connection.TraceRmiConnectionManagerProvider;
|
||||
import ghidra.debug.api.target.Target;
|
||||
import ghidra.debug.api.tracermi.TraceRmiConnection;
|
||||
|
||||
public class TraceRmiConnectionNode extends AbstractTraceRmiManagerNode {
|
||||
private static final Icon ICON = DebuggerResources.ICON_CONNECTION;
|
||||
|
||||
private final TraceRmiConnection connection;
|
||||
private final Map<Target, TraceRmiTargetNode> targetNodes = new HashMap<>();
|
||||
|
||||
public TraceRmiConnectionNode(TraceRmiConnectionManagerProvider provider,
|
||||
TraceRmiConnection connection) {
|
||||
// TODO: Can the connector identify/describe itself for this display?
|
||||
super(provider, "Connected: " + connection.getRemoteAddress());
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayText() {
|
||||
return connection.getDescription() + " at " + connection.getRemoteAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Icon getIcon(boolean expanded) {
|
||||
return ICON;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getToolTip() {
|
||||
return "Trace RMI Connection to " + connection.getDescription() + " at " +
|
||||
connection.getRemoteAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLeaf() {
|
||||
return false;
|
||||
}
|
||||
|
||||
private TraceRmiTargetNode newTargetNode(Target target) {
|
||||
return new TraceRmiTargetNode(provider, this, target);
|
||||
}
|
||||
|
||||
private TraceRmiTargetNode addTargetNode(Target target) {
|
||||
TraceRmiTargetNode node;
|
||||
synchronized (targetNodes) {
|
||||
node = targetNodes.computeIfAbsent(target, this::newTargetNode);
|
||||
}
|
||||
addNode(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
private void removeTargetNode(Target target) {
|
||||
TraceRmiTargetNode node;
|
||||
synchronized (targetNodes) {
|
||||
node = targetNodes.remove(target);
|
||||
}
|
||||
if (node == null) {
|
||||
return;
|
||||
}
|
||||
removeNode(node);
|
||||
}
|
||||
|
||||
public TraceRmiTargetNode targetPublished(Target target) {
|
||||
return addTargetNode(target);
|
||||
}
|
||||
|
||||
public void targetWithdrawn(Target target) {
|
||||
removeTargetNode(target);
|
||||
}
|
||||
|
||||
public TraceRmiConnection getConnection() {
|
||||
return connection;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.gui.tracermi.connection.tree;
|
||||
|
||||
public interface TraceRmiManagerNode {
|
||||
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.gui.tracermi.connection.tree;
|
||||
|
||||
import javax.swing.Icon;
|
||||
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||
import ghidra.app.plugin.core.debug.gui.tracermi.connection.TraceRmiConnectionManagerProvider;
|
||||
import ghidra.app.services.TraceRmiService;
|
||||
|
||||
public class TraceRmiServerNode extends AbstractTraceRmiManagerNode {
|
||||
private static final Icon ICON = DebuggerResources.ICON_THREAD; // TODO: Different name?
|
||||
|
||||
public TraceRmiServerNode(TraceRmiConnectionManagerProvider provider) {
|
||||
super(provider, "Server");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Icon getIcon(boolean expanded) {
|
||||
return ICON;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayText() {
|
||||
TraceRmiService service = provider.getTraceRmiService();
|
||||
if (service == null) {
|
||||
return "<SERVICE MISSING>";
|
||||
}
|
||||
if (!service.isServerStarted()) {
|
||||
return "Server: CLOSED";
|
||||
}
|
||||
return "Server: LISTENING " + service.getServerAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getToolTip() {
|
||||
return getDisplayText();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLeaf() {
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,205 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.gui.tracermi.connection.tree;
|
||||
|
||||
import java.net.SocketAddress;
|
||||
import java.util.*;
|
||||
|
||||
import javax.swing.Icon;
|
||||
|
||||
import ghidra.app.plugin.core.debug.gui.tracermi.connection.TraceRmiConnectionManagerProvider;
|
||||
import ghidra.debug.api.target.Target;
|
||||
import ghidra.debug.api.target.TargetPublicationListener;
|
||||
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
|
||||
import ghidra.debug.api.tracermi.*;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
public class TraceRmiServiceNode extends AbstractTraceRmiManagerNode
|
||||
implements TraceRmiServiceListener, TargetPublicationListener {
|
||||
private static final String DESCRIPTION = "The TraceRmi service";
|
||||
|
||||
final TraceRmiServerNode serverNode;
|
||||
final Map<TraceRmiConnection, TraceRmiConnectionNode> connectionNodes = new HashMap<>();
|
||||
final Map<TraceRmiAcceptor, TraceRmiAcceptorNode> acceptorNodes = new HashMap<>();
|
||||
// weak because each connection node keeps the strong map
|
||||
final Map<Target, TraceRmiTargetNode> targetNodes = new WeakHashMap<>();
|
||||
|
||||
public TraceRmiServiceNode(TraceRmiConnectionManagerProvider provider) {
|
||||
super(provider, "<root>");
|
||||
this.serverNode = new TraceRmiServerNode(provider);
|
||||
|
||||
addNode(serverNode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Icon getIcon(boolean expanded) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getToolTip() {
|
||||
return DESCRIPTION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLeaf() {
|
||||
return false;
|
||||
}
|
||||
|
||||
private TraceRmiConnectionNode newConnectionNode(TraceRmiConnection connection) {
|
||||
return new TraceRmiConnectionNode(provider, connection);
|
||||
}
|
||||
|
||||
private void addConnectionNode(TraceRmiConnection connection) {
|
||||
TraceRmiConnectionNode node;
|
||||
synchronized (connectionNodes) {
|
||||
node = connectionNodes.computeIfAbsent(connection, this::newConnectionNode);
|
||||
}
|
||||
addNode(node);
|
||||
}
|
||||
|
||||
private void removeConnectionNode(TraceRmiConnection connection) {
|
||||
TraceRmiConnectionNode node;
|
||||
synchronized (connectionNodes) {
|
||||
node = connectionNodes.remove(connection);
|
||||
}
|
||||
if (node == null) {
|
||||
return;
|
||||
}
|
||||
removeNode(node);
|
||||
}
|
||||
|
||||
private TraceRmiAcceptorNode newAcceptorNode(TraceRmiAcceptor acceptor) {
|
||||
return new TraceRmiAcceptorNode(provider, acceptor);
|
||||
}
|
||||
|
||||
private void addAcceptorNode(TraceRmiAcceptor acceptor) {
|
||||
TraceRmiAcceptorNode node;
|
||||
synchronized (acceptorNodes) {
|
||||
node = acceptorNodes.computeIfAbsent(acceptor, this::newAcceptorNode);
|
||||
}
|
||||
addNode(node);
|
||||
}
|
||||
|
||||
private void removeAcceptorNode(TraceRmiAcceptor acceptor) {
|
||||
TraceRmiAcceptorNode node;
|
||||
synchronized (acceptorNodes) {
|
||||
node = acceptorNodes.remove(acceptor);
|
||||
}
|
||||
if (node == null) {
|
||||
return;
|
||||
}
|
||||
removeNode(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serverStarted(SocketAddress address) {
|
||||
serverNode.fireNodeChanged();
|
||||
provider.contextChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serverStopped() {
|
||||
serverNode.fireNodeChanged();
|
||||
provider.contextChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connected(TraceRmiConnection connection, ConnectMode mode,
|
||||
TraceRmiAcceptor acceptor) {
|
||||
addConnectionNode(connection);
|
||||
removeAcceptorNode(acceptor);
|
||||
provider.contextChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnected(TraceRmiConnection connection) {
|
||||
removeConnectionNode(connection);
|
||||
provider.contextChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void waitingAccept(TraceRmiAcceptor acceptor) {
|
||||
addAcceptorNode(acceptor);
|
||||
provider.contextChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void acceptCancelled(TraceRmiAcceptor acceptor) {
|
||||
removeAcceptorNode(acceptor);
|
||||
provider.contextChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void acceptFailed(TraceRmiAcceptor acceptor, Exception e) {
|
||||
removeAcceptorNode(acceptor);
|
||||
provider.contextChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void targetPublished(TraceRmiConnection connection, Target target) {
|
||||
TraceRmiConnectionNode cxNode;
|
||||
synchronized (connectionNodes) {
|
||||
cxNode = connectionNodes.get(connection);
|
||||
}
|
||||
if (cxNode == null) {
|
||||
Msg.warn(this,
|
||||
"Target published on a connection I don't have! " + connection + " " + target);
|
||||
return;
|
||||
}
|
||||
TraceRmiTargetNode tNode = cxNode.targetPublished(target);
|
||||
if (tNode == null) {
|
||||
return;
|
||||
}
|
||||
synchronized (targetNodes) {
|
||||
targetNodes.put(target, tNode);
|
||||
}
|
||||
provider.contextChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void targetPublished(Target target) {
|
||||
// Dont care. Using targetPublished(connection, target) instead
|
||||
}
|
||||
|
||||
@Override
|
||||
public void targetWithdrawn(Target target) {
|
||||
TraceRmiTargetNode node;
|
||||
synchronized (targetNodes) {
|
||||
node = targetNodes.remove(target);
|
||||
}
|
||||
if (node == null) {
|
||||
return;
|
||||
}
|
||||
node.getConnectionNode().targetWithdrawn(target);
|
||||
provider.contextChanged();
|
||||
}
|
||||
|
||||
public void coordinates(DebuggerCoordinates coordinates) {
|
||||
Target target = coordinates.getTarget();
|
||||
if (target == null) {
|
||||
return;
|
||||
}
|
||||
TraceRmiTargetNode node;
|
||||
synchronized (targetNodes) {
|
||||
node = targetNodes.get(target);
|
||||
}
|
||||
if (node == null) {
|
||||
return;
|
||||
}
|
||||
node.fireNodeChanged();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.gui.tracermi.connection.tree;
|
||||
|
||||
import javax.swing.Icon;
|
||||
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||
import ghidra.app.plugin.core.debug.gui.tracermi.connection.TraceRmiConnectionManagerProvider;
|
||||
import ghidra.debug.api.target.Target;
|
||||
|
||||
public class TraceRmiTargetNode extends AbstractTraceRmiManagerNode {
|
||||
private static final Icon ICON = DebuggerResources.ICON_RECORD;
|
||||
|
||||
private final TraceRmiConnectionNode connectionNode;
|
||||
private final Target target;
|
||||
|
||||
public TraceRmiTargetNode(TraceRmiConnectionManagerProvider provider,
|
||||
TraceRmiConnectionNode connectionNode, Target target) {
|
||||
super(provider, target.getTrace().getName());
|
||||
this.connectionNode = connectionNode;
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Icon getIcon(boolean expanded) {
|
||||
return ICON;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayText() {
|
||||
return target.getTrace().getName() + " (snap=" + target.getSnap() + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getToolTip() {
|
||||
return "Target: " + target.getTrace().getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLeaf() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public TraceRmiConnectionNode getConnectionNode() {
|
||||
return connectionNode;
|
||||
}
|
||||
|
||||
public Target getTarget() {
|
||||
return target;
|
||||
}
|
||||
}
|
|
@ -33,8 +33,8 @@ import db.Transaction;
|
|||
import docking.widgets.OptionDialog;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||
import ghidra.app.plugin.core.debug.gui.objects.components.DebuggerMethodInvocationDialog;
|
||||
import ghidra.app.plugin.core.debug.service.rmi.trace.DefaultTraceRmiAcceptor;
|
||||
import ghidra.app.plugin.core.debug.service.rmi.trace.TraceRmiHandler;
|
||||
import ghidra.app.plugin.core.debug.service.tracermi.DefaultTraceRmiAcceptor;
|
||||
import ghidra.app.plugin.core.debug.service.tracermi.TraceRmiHandler;
|
||||
import ghidra.app.plugin.core.terminal.TerminalListener;
|
||||
import ghidra.app.services.*;
|
||||
import ghidra.app.services.DebuggerTraceManagerService.ActivationCause;
|
||||
|
|
|
@ -1,47 +0,0 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.service.rmi.trace;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.SocketAddress;
|
||||
|
||||
import ghidra.debug.api.tracermi.TraceRmiAcceptor;
|
||||
|
||||
public class DefaultTraceRmiAcceptor extends TraceRmiServer implements TraceRmiAcceptor {
|
||||
|
||||
public DefaultTraceRmiAcceptor(TraceRmiPlugin plugin, SocketAddress address) {
|
||||
super(plugin, address);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() throws IOException {
|
||||
socket = new ServerSocket();
|
||||
bind();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void bind() throws IOException {
|
||||
socket.bind(address, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceRmiHandler accept() throws IOException {
|
||||
TraceRmiHandler handler = super.accept();
|
||||
close();
|
||||
return handler;
|
||||
}
|
||||
}
|
|
@ -13,38 +13,43 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.service.rmi.trace;
|
||||
package ghidra.app.plugin.core.debug.service.tracermi;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.*;
|
||||
|
||||
import ghidra.debug.api.tracermi.TraceRmiAcceptor;
|
||||
import ghidra.debug.api.tracermi.TraceRmiServiceListener.ConnectMode;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
|
||||
public class TraceRmiServer {
|
||||
public abstract class AbstractTraceRmiListener {
|
||||
protected final TraceRmiPlugin plugin;
|
||||
protected final SocketAddress address;
|
||||
|
||||
protected ServerSocket socket;
|
||||
|
||||
public TraceRmiServer(TraceRmiPlugin plugin, SocketAddress address) {
|
||||
public AbstractTraceRmiListener(TraceRmiPlugin plugin, SocketAddress address) {
|
||||
this.plugin = plugin;
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
protected void bind() throws IOException {
|
||||
socket.bind(address);
|
||||
}
|
||||
protected abstract void bind() throws IOException;
|
||||
|
||||
public void start() throws IOException {
|
||||
socket = new ServerSocket();
|
||||
bind();
|
||||
new Thread(this::serviceLoop, "trace-rmi server " + socket.getLocalSocketAddress()).start();
|
||||
startServiceLoop();
|
||||
}
|
||||
|
||||
protected abstract void startServiceLoop();
|
||||
|
||||
public void setTimeout(int millis) throws SocketException {
|
||||
socket.setSoTimeout(millis);
|
||||
}
|
||||
|
||||
protected abstract ConnectMode getConnectMode();
|
||||
|
||||
/**
|
||||
* Accept a connection and handle its requests.
|
||||
*
|
||||
|
@ -54,36 +59,17 @@ public class TraceRmiServer {
|
|||
*
|
||||
* @return the handler
|
||||
* @throws IOException on error
|
||||
* @throws CancelledException if the accept is cancelled
|
||||
*/
|
||||
@SuppressWarnings("resource")
|
||||
protected TraceRmiHandler accept() throws IOException {
|
||||
protected TraceRmiHandler doAccept(TraceRmiAcceptor acceptor) throws IOException {
|
||||
Socket client = socket.accept();
|
||||
TraceRmiHandler handler = new TraceRmiHandler(plugin, client);
|
||||
handler.start();
|
||||
plugin.listeners.invoke().connected(handler, getConnectMode(), acceptor);
|
||||
return handler;
|
||||
}
|
||||
|
||||
protected void serviceLoop() {
|
||||
try {
|
||||
accept();
|
||||
}
|
||||
catch (IOException e) {
|
||||
if (socket.isClosed()) {
|
||||
return;
|
||||
}
|
||||
Msg.error("Error accepting TraceRmi client", e);
|
||||
return;
|
||||
}
|
||||
finally {
|
||||
try {
|
||||
socket.close();
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.error("Error closing TraceRmi service", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void close() {
|
||||
try {
|
||||
socket.close();
|
|
@ -13,7 +13,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.service.rmi.trace;
|
||||
package ghidra.app.plugin.core.debug.service.tracermi;
|
||||
|
||||
import java.util.concurrent.*;
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.service.rmi.trace;
|
||||
package ghidra.app.plugin.core.debug.service.tracermi;
|
||||
|
||||
import java.util.*;
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.service.tracermi;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.SocketAddress;
|
||||
|
||||
import ghidra.debug.api.tracermi.TraceRmiAcceptor;
|
||||
import ghidra.debug.api.tracermi.TraceRmiServiceListener.ConnectMode;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
|
||||
public class DefaultTraceRmiAcceptor extends AbstractTraceRmiListener implements TraceRmiAcceptor {
|
||||
private boolean cancelled = false;
|
||||
|
||||
public DefaultTraceRmiAcceptor(TraceRmiPlugin plugin, SocketAddress address) {
|
||||
super(plugin, address);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startServiceLoop() {
|
||||
// Don't. Instead, client calls accept()
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void bind() throws IOException {
|
||||
socket.bind(address, 1);
|
||||
plugin.addAcceptor(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ConnectMode getConnectMode() {
|
||||
return ConnectMode.ACCEPT_ONE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceRmiHandler accept() throws IOException, CancelledException {
|
||||
try {
|
||||
TraceRmiHandler handler = doAccept(this);
|
||||
close();
|
||||
return handler;
|
||||
}
|
||||
catch (Exception e) {
|
||||
close();
|
||||
if (cancelled) {
|
||||
throw new CancelledException();
|
||||
}
|
||||
plugin.listeners.invoke().acceptFailed(this, e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
plugin.removeAcceptor(this);
|
||||
super.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
cancelled = true;
|
||||
close();
|
||||
plugin.listeners.invoke().acceptCancelled(this);
|
||||
}
|
||||
}
|
|
@ -13,9 +13,9 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.service.rmi.trace;
|
||||
package ghidra.app.plugin.core.debug.service.tracermi;
|
||||
|
||||
import ghidra.app.plugin.core.debug.service.rmi.trace.TraceRmiHandler.*;
|
||||
import ghidra.app.plugin.core.debug.service.tracermi.TraceRmiHandler.*;
|
||||
import ghidra.debug.api.tracermi.TraceRmiError;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.lang.Register;
|
|
@ -13,7 +13,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.service.rmi.trace;
|
||||
package ghidra.app.plugin.core.debug.service.tracermi;
|
||||
|
||||
import java.util.Map;
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.service.rmi.trace;
|
||||
package ghidra.app.plugin.core.debug.service.tracermi;
|
||||
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
|
||||
import ghidra.debug.api.tracermi.RemoteParameter;
|
|
@ -13,7 +13,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.service.rmi.trace;
|
||||
package ghidra.app.plugin.core.debug.service.tracermi;
|
||||
|
||||
import java.io.*;
|
||||
import java.math.BigInteger;
|
||||
|
@ -41,9 +41,12 @@ import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
|
|||
import ghidra.dbg.target.schema.XmlSchemaContext;
|
||||
import ghidra.dbg.util.PathPattern;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.debug.api.progress.CloseableTaskMonitor;
|
||||
import ghidra.debug.api.target.ActionName;
|
||||
import ghidra.debug.api.target.Target;
|
||||
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
|
||||
import ghidra.debug.api.tracermi.*;
|
||||
import ghidra.framework.Application;
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.framework.plugintool.AutoService;
|
||||
import ghidra.framework.plugintool.AutoService.Wiring;
|
||||
|
@ -65,7 +68,6 @@ import ghidra.trace.model.time.TraceSnapshot;
|
|||
import ghidra.util.*;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.exception.DuplicateFileException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class TraceRmiHandler implements TraceRmiConnection {
|
||||
public static final String VERSION = "10.4";
|
||||
|
@ -180,7 +182,14 @@ public class TraceRmiHandler implements TraceRmiConnection {
|
|||
byTrace.put(openTrace.trace, openTrace);
|
||||
first.complete(openTrace);
|
||||
|
||||
plugin.publishTarget(openTrace.target);
|
||||
plugin.publishTarget(TraceRmiHandler.this, openTrace.target);
|
||||
}
|
||||
|
||||
public synchronized List<Target> getTargets() {
|
||||
return byId.values()
|
||||
.stream()
|
||||
.map(ot -> ot.target)
|
||||
.collect(Collectors.toUnmodifiableList());
|
||||
}
|
||||
|
||||
public CompletableFuture<OpenTrace> getFirstAsync() {
|
||||
|
@ -192,7 +201,7 @@ public class TraceRmiHandler implements TraceRmiConnection {
|
|||
private final Socket socket;
|
||||
private final InputStream in;
|
||||
private final OutputStream out;
|
||||
private final CompletableFuture<Void> negotiate = new CompletableFuture<>();
|
||||
private final CompletableFuture<String> negotiate = new CompletableFuture<>();
|
||||
private final CompletableFuture<Void> closed = new CompletableFuture<>();
|
||||
private final Set<TerminalSession> terminals = new LinkedHashSet<>();
|
||||
|
||||
|
@ -276,8 +285,8 @@ public class TraceRmiHandler implements TraceRmiConnection {
|
|||
DoId nextKey = openTraces.idSet().iterator().next();
|
||||
OpenTrace open = openTraces.removeById(nextKey);
|
||||
if (traceManager == null || traceManager.isSaveTracesByDefault()) {
|
||||
try {
|
||||
open.trace.save("Save on Disconnect", plugin.getTaskMonitor());
|
||||
try (CloseableTaskMonitor monitor = plugin.createMonitor()) {
|
||||
open.trace.save("Save on Disconnect", monitor);
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.error(this, "Could not save " + open.trace);
|
||||
|
@ -289,6 +298,7 @@ public class TraceRmiHandler implements TraceRmiConnection {
|
|||
open.trace.release(this);
|
||||
}
|
||||
closed.complete(null);
|
||||
plugin.listeners.invoke().disconnected(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -344,18 +354,19 @@ public class TraceRmiHandler implements TraceRmiConnection {
|
|||
protected DomainFile createDeconflictedFile(DomainFolder parent, DomainObject object)
|
||||
throws InvalidNameException, CancelledException, IOException {
|
||||
String name = object.getName();
|
||||
TaskMonitor monitor = plugin.getTaskMonitor();
|
||||
for (int nextId = 1; nextId < 100; nextId++) {
|
||||
try {
|
||||
return parent.createFile(name, object, monitor);
|
||||
}
|
||||
catch (DuplicateFileException e) {
|
||||
name = object.getName() + "." + nextId;
|
||||
try (CloseableTaskMonitor monitor = plugin.createMonitor()) {
|
||||
for (int nextId = 1; nextId < 100; nextId++) {
|
||||
try {
|
||||
return parent.createFile(name, object, monitor);
|
||||
}
|
||||
catch (DuplicateFileException e) {
|
||||
name = object.getName() + "." + nextId;
|
||||
}
|
||||
}
|
||||
name = object.getName() + "." + System.currentTimeMillis();
|
||||
// Don't catch it this last time
|
||||
return parent.createFile(name, object, monitor);
|
||||
}
|
||||
name = object.getName() + "." + System.currentTimeMillis();
|
||||
// Don't catch it this last time
|
||||
return parent.createFile(name, object, monitor);
|
||||
}
|
||||
|
||||
public void start() {
|
||||
|
@ -911,16 +922,7 @@ public class TraceRmiHandler implements TraceRmiConnection {
|
|||
OpenTrace open = requireOpenTrace(req.getOid());
|
||||
long snap = req.getSnap().getSnap();
|
||||
|
||||
/**
|
||||
* TODO: Is this composition of laziness upon laziness efficient enough?
|
||||
*
|
||||
* <p>
|
||||
* Can experiment with ordering of address-set-view "expression" to optimize early
|
||||
* termination.
|
||||
*
|
||||
* <p>
|
||||
* Want addresses satisfying {@code known | (readOnly & everKnown)}
|
||||
*/
|
||||
// Want addresses satisfying {@code known | (readOnly & everKnown)}
|
||||
TraceMemoryManager memoryManager = open.trace.getMemoryManager();
|
||||
AddressSetView readOnly =
|
||||
memoryManager.getRegionsAddressSetWith(snap, r -> !r.isWrite());
|
||||
|
@ -939,8 +941,9 @@ public class TraceRmiHandler implements TraceRmiConnection {
|
|||
dis.setInitialContext(DebuggerDisassemblerPlugin.deriveAlternativeDefaultContext(
|
||||
host.getLanguage(), host.getLanguage().getLanguageID(), start));
|
||||
|
||||
TaskMonitor monitor = plugin.getTaskMonitor();
|
||||
dis.applyToTyped(open.trace.getFixedProgramView(snap), monitor);
|
||||
try (CloseableTaskMonitor monitor = plugin.createMonitor()) {
|
||||
dis.applyToTyped(open.trace.getFixedProgramView(snap), monitor);
|
||||
}
|
||||
|
||||
return ReplyDisassemble.newBuilder()
|
||||
.setLength(dis.getDisassembledAddressSet().getNumAddresses())
|
||||
|
@ -1027,8 +1030,10 @@ public class TraceRmiHandler implements TraceRmiConnection {
|
|||
new SchemaName(m.getReturnType().getName()));
|
||||
methodRegistry.add(rm);
|
||||
}
|
||||
negotiate.complete(null);
|
||||
return ReplyNegotiate.getDefaultInstance();
|
||||
negotiate.complete(req.getDescription());
|
||||
return ReplyNegotiate.newBuilder()
|
||||
.setDescription(Application.getName() + " " + Application.getApplicationVersion())
|
||||
.build();
|
||||
}
|
||||
|
||||
protected ReplyPutBytes handlePutBytes(RequestPutBytes req) {
|
||||
|
@ -1110,7 +1115,9 @@ public class TraceRmiHandler implements TraceRmiConnection {
|
|||
protected ReplySaveTrace handleSaveTrace(RequestSaveTrace req)
|
||||
throws CancelledException, IOException {
|
||||
OpenTrace open = requireOpenTrace(req.getOid());
|
||||
open.trace.save("TraceRMI", plugin.getTaskMonitor());
|
||||
try (CloseableTaskMonitor monitor = plugin.createMonitor()) {
|
||||
open.trace.save("TraceRMI", monitor);
|
||||
}
|
||||
return ReplySaveTrace.getDefaultInstance();
|
||||
}
|
||||
|
||||
|
@ -1271,9 +1278,25 @@ public class TraceRmiHandler implements TraceRmiConnection {
|
|||
return openTraces.getByTrace(trace) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Target> getTargets() {
|
||||
return openTraces.getTargets();
|
||||
}
|
||||
|
||||
public void registerTerminals(Collection<TerminalSession> terminals) {
|
||||
synchronized (this.terminals) {
|
||||
this.terminals.addAll(terminals);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
// NOTE: Negotiation happens during construction, so unless this is called internally,
|
||||
// or there's some error, we should always have a read description.
|
||||
String description = negotiate.getNow("(Negotiating...)");
|
||||
if (description.isBlank()) {
|
||||
return "Trace RMI";
|
||||
}
|
||||
return description;
|
||||
}
|
||||
}
|
|
@ -13,7 +13,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.service.rmi.trace;
|
||||
package ghidra.app.plugin.core.debug.service.tracermi;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.*;
|
||||
|
@ -24,14 +24,16 @@ import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
|||
import ghidra.app.plugin.core.debug.event.TraceActivatedPluginEvent;
|
||||
import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent;
|
||||
import ghidra.app.services.*;
|
||||
import ghidra.debug.api.tracermi.TraceRmiConnection;
|
||||
import ghidra.debug.api.progress.CloseableTaskMonitor;
|
||||
import ghidra.debug.api.tracermi.*;
|
||||
import ghidra.debug.api.tracermi.TraceRmiServiceListener.ConnectMode;
|
||||
import ghidra.framework.plugintool.*;
|
||||
import ghidra.framework.plugintool.AutoService.Wiring;
|
||||
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
||||
import ghidra.framework.plugintool.util.PluginStatus;
|
||||
import ghidra.util.Swing;
|
||||
import ghidra.util.datastruct.ListenerSet;
|
||||
import ghidra.util.task.ConsoleTaskMonitor;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
@PluginInfo(
|
||||
shortDescription = "Connect to back-end debuggers via Trace RMI",
|
||||
|
@ -56,26 +58,41 @@ import ghidra.util.task.TaskMonitor;
|
|||
public class TraceRmiPlugin extends Plugin implements InternalTraceRmiService {
|
||||
private static final int DEFAULT_PORT = 15432;
|
||||
|
||||
static class FallbackTaskMonitor extends ConsoleTaskMonitor implements CloseableTaskMonitor {
|
||||
@Override
|
||||
public void close() {
|
||||
// Nothing
|
||||
}
|
||||
}
|
||||
|
||||
@AutoServiceConsumed
|
||||
private DebuggerTargetService targetService;
|
||||
@AutoServiceConsumed
|
||||
private ProgressService progressService;
|
||||
@SuppressWarnings("unused")
|
||||
private final Wiring autoServiceWiring;
|
||||
|
||||
private final TaskMonitor monitor = new ConsoleTaskMonitor();
|
||||
|
||||
private SocketAddress serverAddress = new InetSocketAddress("0.0.0.0", DEFAULT_PORT);
|
||||
private TraceRmiServer server;
|
||||
|
||||
private final Set<TraceRmiHandler> handlers = new LinkedHashSet<>();
|
||||
private final Set<DefaultTraceRmiAcceptor> acceptors = new LinkedHashSet<>();
|
||||
|
||||
final ListenerSet<TraceRmiServiceListener> listeners =
|
||||
new ListenerSet<>(TraceRmiServiceListener.class, true);
|
||||
|
||||
private final CloseableTaskMonitor fallbackMonitor = new FallbackTaskMonitor();
|
||||
|
||||
public TraceRmiPlugin(PluginTool tool) {
|
||||
super(tool);
|
||||
autoServiceWiring = AutoService.wireServicesProvidedAndConsumed(this);
|
||||
}
|
||||
|
||||
public TaskMonitor getTaskMonitor() {
|
||||
// TODO: Create one in the Debug Console?
|
||||
return monitor;
|
||||
protected CloseableTaskMonitor createMonitor() {
|
||||
if (progressService == null) {
|
||||
return fallbackMonitor;
|
||||
}
|
||||
return progressService.publishTask();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -102,14 +119,16 @@ public class TraceRmiPlugin extends Plugin implements InternalTraceRmiService {
|
|||
}
|
||||
server = new TraceRmiServer(this, serverAddress);
|
||||
server.start();
|
||||
listeners.invoke().serverStarted(server.getAddress());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopServer() {
|
||||
if (server != null) {
|
||||
server.close();
|
||||
server = null;
|
||||
listeners.invoke().serverStopped();
|
||||
}
|
||||
server = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -124,6 +143,7 @@ public class TraceRmiPlugin extends Plugin implements InternalTraceRmiService {
|
|||
socket.connect(address);
|
||||
TraceRmiHandler handler = new TraceRmiHandler(this, socket);
|
||||
handler.start();
|
||||
listeners.invoke().connected(handler, ConnectMode.CONNECT, null);
|
||||
return handler;
|
||||
}
|
||||
|
||||
|
@ -131,25 +151,52 @@ public class TraceRmiPlugin extends Plugin implements InternalTraceRmiService {
|
|||
public DefaultTraceRmiAcceptor acceptOne(SocketAddress address) throws IOException {
|
||||
DefaultTraceRmiAcceptor acceptor = new DefaultTraceRmiAcceptor(this, address);
|
||||
acceptor.start();
|
||||
listeners.invoke().waitingAccept(acceptor);
|
||||
return acceptor;
|
||||
}
|
||||
|
||||
void addHandler(TraceRmiHandler handler) {
|
||||
handlers.add(handler);
|
||||
synchronized (handlers) {
|
||||
handlers.add(handler);
|
||||
}
|
||||
}
|
||||
|
||||
void removeHandler(TraceRmiHandler handler) {
|
||||
handlers.remove(handler);
|
||||
synchronized (handlers) {
|
||||
handlers.remove(handler);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<TraceRmiConnection> getAllConnections() {
|
||||
return List.copyOf(handlers);
|
||||
synchronized (handlers) {
|
||||
return List.copyOf(handlers);
|
||||
}
|
||||
}
|
||||
|
||||
void publishTarget(TraceRmiTarget target) {
|
||||
void addAcceptor(DefaultTraceRmiAcceptor acceptor) {
|
||||
synchronized (acceptors) {
|
||||
acceptors.add(acceptor);
|
||||
}
|
||||
}
|
||||
|
||||
void removeAcceptor(DefaultTraceRmiAcceptor acceptor) {
|
||||
synchronized (acceptors) {
|
||||
acceptors.remove(acceptor);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<TraceRmiAcceptor> getAllAcceptors() {
|
||||
synchronized (acceptors) {
|
||||
return List.copyOf(acceptors);
|
||||
}
|
||||
}
|
||||
|
||||
void publishTarget(TraceRmiHandler handler, TraceRmiTarget target) {
|
||||
Swing.runIfSwingOrRunLater(() -> {
|
||||
targetService.publishTarget(target);
|
||||
listeners.invoke().targetPublished(handler, target);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -158,4 +205,14 @@ public class TraceRmiPlugin extends Plugin implements InternalTraceRmiService {
|
|||
targetService.withdrawTarget(target);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTraceServiceListener(TraceRmiServiceListener listener) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeTraceServiceListener(TraceRmiServiceListener listener) {
|
||||
listeners.remove(listener);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.service.tracermi;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.SocketAddress;
|
||||
|
||||
import ghidra.debug.api.tracermi.TraceRmiServiceListener.ConnectMode;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
public class TraceRmiServer extends AbstractTraceRmiListener {
|
||||
public TraceRmiServer(TraceRmiPlugin plugin, SocketAddress address) {
|
||||
super(plugin, address);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void bind() throws IOException {
|
||||
socket.bind(address);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startServiceLoop() {
|
||||
new Thread(this::serviceLoop, "trace-rmi server " + socket.getLocalSocketAddress()).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ConnectMode getConnectMode() {
|
||||
return ConnectMode.SERVER;
|
||||
}
|
||||
|
||||
@SuppressWarnings("resource")
|
||||
protected void serviceLoop() {
|
||||
try {
|
||||
doAccept(null);
|
||||
}
|
||||
catch (IOException e) {
|
||||
if (socket.isClosed()) {
|
||||
return;
|
||||
}
|
||||
Msg.error("Error accepting TraceRmi client", e);
|
||||
return;
|
||||
}
|
||||
finally {
|
||||
try {
|
||||
socket.close();
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.error("Error closing TraceRmi service", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,7 +13,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.service.rmi.trace;
|
||||
package ghidra.app.plugin.core.debug.service.tracermi;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
|
@ -13,7 +13,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.service.rmi.trace;
|
||||
package ghidra.app.plugin.core.debug.service.tracermi;
|
||||
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.service.rmi.trace;
|
||||
package ghidra.app.plugin.core.debug.service.tracermi;
|
||||
|
||||
import ghidra.program.model.address.AddressOverflowException;
|
||||
|
|
@ -18,8 +18,8 @@ package ghidra.app.services;
|
|||
import java.io.IOException;
|
||||
import java.net.SocketAddress;
|
||||
|
||||
import ghidra.app.plugin.core.debug.service.rmi.trace.DefaultTraceRmiAcceptor;
|
||||
import ghidra.app.plugin.core.debug.service.rmi.trace.TraceRmiHandler;
|
||||
import ghidra.app.plugin.core.debug.service.tracermi.DefaultTraceRmiAcceptor;
|
||||
import ghidra.app.plugin.core.debug.service.tracermi.TraceRmiHandler;
|
||||
import ghidra.debug.spi.tracermi.TraceRmiLaunchOpinion;
|
||||
|
||||
/**
|
||||
|
|
|
@ -18,7 +18,7 @@ package ghidra.debug.spi.tracermi;
|
|||
import java.util.Collection;
|
||||
|
||||
import ghidra.app.plugin.core.debug.gui.tracermi.launcher.TraceRmiLauncherServicePlugin;
|
||||
import ghidra.app.plugin.core.debug.service.rmi.trace.TraceRmiHandler;
|
||||
import ghidra.app.plugin.core.debug.service.tracermi.TraceRmiHandler;
|
||||
import ghidra.app.services.InternalTraceRmiService;
|
||||
import ghidra.debug.api.tracermi.TraceRmiLaunchOffer;
|
||||
import ghidra.framework.options.Options;
|
||||
|
|
|
@ -427,9 +427,11 @@ message Method {
|
|||
message RequestNegotiate {
|
||||
string version = 1;
|
||||
repeated Method methods = 2;
|
||||
string description = 3;
|
||||
}
|
||||
|
||||
message ReplyNegotiate {
|
||||
string description = 1;
|
||||
}
|
||||
|
||||
message XRequestInvokeMethod {
|
||||
|
|
|
@ -720,7 +720,7 @@ class Client(object):
|
|||
return Client._read_obj_desc(msg.child_desc), sch.OBJECT
|
||||
raise ValueError("Could not read value: {}".format(msg))
|
||||
|
||||
def __init__(self, s, method_registry: MethodRegistry):
|
||||
def __init__(self, s, description: str, method_registry: MethodRegistry):
|
||||
self._traces = {}
|
||||
self._next_trace_id = 1
|
||||
self.tlock = Lock()
|
||||
|
@ -732,7 +732,7 @@ class Client(object):
|
|||
self.slock = Lock()
|
||||
self.receiver.start()
|
||||
self._method_registry = method_registry
|
||||
self._negotiate()
|
||||
self.description = self._negotiate(description)
|
||||
|
||||
def close(self):
|
||||
self.s.close()
|
||||
|
@ -1083,15 +1083,16 @@ class Client(object):
|
|||
return reply.length
|
||||
return self._batch_or_now(root, 'reply_disassemble', _handle)
|
||||
|
||||
def _negotiate(self):
|
||||
def _negotiate(self, description: str):
|
||||
root = bufs.RootMessage()
|
||||
root.request_negotiate.version = VERSION
|
||||
root.request_negotiate.description = description
|
||||
self._write_methods(root.request_negotiate.methods,
|
||||
self._method_registry._methods.values())
|
||||
|
||||
def _handle(reply):
|
||||
pass
|
||||
self._now(root, 'reply_negotiate', _handle)
|
||||
return reply.description
|
||||
return self._now(root, 'reply_negotiate', _handle)
|
||||
|
||||
def _handle_invoke_method(self, request):
|
||||
if request.HasField('oid'):
|
||||
|
|
|
@ -0,0 +1,416 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.gui.tracermi.connection;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.channels.ServerSocketChannel;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import generic.Unique;
|
||||
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
|
||||
import ghidra.app.plugin.core.debug.gui.objects.components.InvocationDialogHelper;
|
||||
import ghidra.app.plugin.core.debug.gui.tracermi.connection.tree.*;
|
||||
import ghidra.app.plugin.core.debug.service.control.DebuggerControlServicePlugin;
|
||||
import ghidra.app.plugin.core.debug.service.tracermi.TestTraceRmiClient;
|
||||
import ghidra.app.plugin.core.debug.service.tracermi.TestTraceRmiClient.Tx;
|
||||
import ghidra.app.plugin.core.debug.service.tracermi.TraceRmiPlugin;
|
||||
import ghidra.app.services.DebuggerControlService;
|
||||
import ghidra.app.services.TraceRmiService;
|
||||
import ghidra.dbg.target.schema.SchemaContext;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
|
||||
import ghidra.dbg.target.schema.XmlSchemaContext;
|
||||
import ghidra.debug.api.control.ControlMode;
|
||||
import ghidra.debug.api.target.Target;
|
||||
import ghidra.debug.api.tracermi.TraceRmiAcceptor;
|
||||
import ghidra.debug.api.tracermi.TraceRmiConnection;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
|
||||
public class TraceRmiConnectionManagerProviderTest extends AbstractGhidraHeadedDebuggerTest {
|
||||
TraceRmiConnectionManagerProvider provider;
|
||||
TraceRmiService traceRmiService;
|
||||
DebuggerControlService controlService;
|
||||
|
||||
@Before
|
||||
public void setUpConnectionManager() throws Exception {
|
||||
controlService = addPlugin(tool, DebuggerControlServicePlugin.class);
|
||||
traceRmiService = addPlugin(tool, TraceRmiPlugin.class);
|
||||
addPlugin(tool, TraceRmiConnectionManagerPlugin.class);
|
||||
provider = waitForComponentProvider(TraceRmiConnectionManagerProvider.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActionAccept() throws Exception {
|
||||
performEnabledAction(provider, provider.actionConnectAccept, false);
|
||||
InvocationDialogHelper helper = InvocationDialogHelper.waitFor();
|
||||
helper.dismissWithArguments(Map.ofEntries(
|
||||
Map.entry("address", "localhost"),
|
||||
Map.entry("port", 0)));
|
||||
waitForPass(() -> Unique.assertOne(traceRmiService.getAllAcceptors()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActionConnect() throws Exception {
|
||||
try (ServerSocketChannel server = ServerSocketChannel.open()) {
|
||||
server.bind(new InetSocketAddress("localhost", 0), 1);
|
||||
if (!(server.getLocalAddress() instanceof InetSocketAddress sockaddr)) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
performEnabledAction(provider, provider.actionConnectOutbound, false);
|
||||
InvocationDialogHelper helper = InvocationDialogHelper.waitFor();
|
||||
helper.dismissWithArguments(Map.ofEntries(
|
||||
Map.entry("address", sockaddr.getHostString()),
|
||||
Map.entry("port", sockaddr.getPort())));
|
||||
try (SocketChannel channel = server.accept()) {
|
||||
TestTraceRmiClient client = new TestTraceRmiClient(channel);
|
||||
client.sendNegotiate("Test client");
|
||||
client.recvNegotiate();
|
||||
waitForPass(() -> Unique.assertOne(traceRmiService.getAllConnections()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActionStartServer() throws Exception {
|
||||
performEnabledAction(provider, provider.actionStartServer, false);
|
||||
InvocationDialogHelper helper = InvocationDialogHelper.waitFor();
|
||||
helper.dismissWithArguments(Map.ofEntries(
|
||||
Map.entry("address", "localhost"),
|
||||
Map.entry("port", 0)));
|
||||
waitForPass(() -> assertTrue(traceRmiService.isServerStarted()));
|
||||
waitForPass(() -> assertFalse(provider.actionStartServer.isEnabled()));
|
||||
|
||||
traceRmiService.stopServer();
|
||||
waitForPass(() -> assertTrue(provider.actionStartServer.isEnabled()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActionStopServer() throws Exception {
|
||||
waitForPass(() -> assertFalse(provider.actionStopServer.isEnabled()));
|
||||
traceRmiService.startServer();
|
||||
waitForSwing();
|
||||
performEnabledAction(provider, provider.actionStopServer, true);
|
||||
assertFalse(traceRmiService.isServerStarted());
|
||||
|
||||
waitForPass(() -> assertFalse(provider.actionStopServer.isEnabled()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActionCloseOnAcceptor() throws Exception {
|
||||
TraceRmiAcceptor acceptor =
|
||||
traceRmiService.acceptOne(new InetSocketAddress("localhost", 0));
|
||||
TraceRmiAcceptorNode node =
|
||||
TraceRmiConnectionTreeHelper.getAcceptorNodeMap(provider.rootNode).get(acceptor);
|
||||
assertNotNull(node);
|
||||
provider.tree.setSelectedNode(node);
|
||||
// Tree uses a task queue for selection requests
|
||||
waitForPass(() -> assertEquals(node, Unique.assertOne(provider.tree.getSelectedNodes())));
|
||||
|
||||
performEnabledAction(provider, provider.actionCloseConnection, true);
|
||||
try {
|
||||
acceptor.accept();
|
||||
fail();
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
// pass
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActionCloseOnConnection() throws Exception {
|
||||
try (Cx cx = Cx.connect(traceRmiService, "Test client")) {
|
||||
TraceRmiConnectionNode node =
|
||||
TraceRmiConnectionTreeHelper.getConnectionNodeMap(provider.rootNode)
|
||||
.get(cx.connection);
|
||||
assertNotNull(node);
|
||||
provider.tree.setSelectedNode(node);
|
||||
// Tree uses a task queue for selection requests
|
||||
waitForPass(
|
||||
() -> assertEquals(node, Unique.assertOne(provider.tree.getSelectedNodes())));
|
||||
|
||||
performEnabledAction(provider, provider.actionCloseConnection, true);
|
||||
waitForPass(() -> assertTrue(cx.connection.isClosed()));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActionCloseAll() throws Exception {
|
||||
traceRmiService.startServer();
|
||||
TraceRmiAcceptor acceptor =
|
||||
traceRmiService.acceptOne(new InetSocketAddress("localhost", 0));
|
||||
try (Cx cx = Cx.connect(traceRmiService, "Test client")) {
|
||||
performEnabledAction(provider, provider.actionCloseAll, true);
|
||||
|
||||
waitForPass(() -> assertFalse(traceRmiService.isServerStarted()));
|
||||
waitForPass(() -> assertTrue(cx.connection.isClosed()));
|
||||
try {
|
||||
acceptor.accept();
|
||||
fail();
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
// pass
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testServerNode() throws Exception {
|
||||
TraceRmiServerNode node = TraceRmiConnectionTreeHelper.getServerNode(provider.rootNode);
|
||||
assertEquals("Server: CLOSED", node.getDisplayText());
|
||||
traceRmiService.startServer();
|
||||
waitForPass(() -> assertEquals("Server: LISTENING " + traceRmiService.getServerAddress(),
|
||||
node.getDisplayText()));
|
||||
traceRmiService.stopServer();
|
||||
waitForPass(() -> assertEquals("Server: CLOSED", node.getDisplayText()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAcceptHasNode() throws Exception {
|
||||
TraceRmiAcceptor acceptor =
|
||||
traceRmiService.acceptOne(new InetSocketAddress("localhost", 0));
|
||||
TraceRmiAcceptorNode node =
|
||||
TraceRmiConnectionTreeHelper.getAcceptorNodeMap(provider.rootNode).get(acceptor);
|
||||
assertNotNull(node);
|
||||
assertEquals("ACCEPTING: " + acceptor.getAddress(), node.getDisplayText());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAcceptThenCancelNoNode() throws Exception {
|
||||
TraceRmiAcceptor acceptor =
|
||||
traceRmiService.acceptOne(new InetSocketAddress("localhost", 0));
|
||||
assertNotNull(
|
||||
TraceRmiConnectionTreeHelper.getAcceptorNodeMap(provider.rootNode).get(acceptor));
|
||||
|
||||
acceptor.cancel();
|
||||
waitForPass(() -> traceRmiService.getAllAcceptors().isEmpty());
|
||||
assertNull(
|
||||
TraceRmiConnectionTreeHelper.getAcceptorNodeMap(provider.rootNode).get(acceptor));
|
||||
}
|
||||
|
||||
record Cx(SocketChannel channel, TestTraceRmiClient client,
|
||||
TraceRmiConnection connection)
|
||||
implements AutoCloseable {
|
||||
public static Cx complete(TraceRmiAcceptor acceptor, String description)
|
||||
throws IOException, CancelledException {
|
||||
SocketChannel channel = null;
|
||||
TraceRmiConnection connection = null;
|
||||
try {
|
||||
channel = SocketChannel.open(acceptor.getAddress());
|
||||
TestTraceRmiClient client = new TestTraceRmiClient(channel);
|
||||
client.sendNegotiate(description);
|
||||
connection = acceptor.accept();
|
||||
client.recvNegotiate();
|
||||
return new Cx(channel, client, connection);
|
||||
}
|
||||
catch (Throwable t) {
|
||||
if (channel != null) {
|
||||
channel.close();
|
||||
}
|
||||
if (connection != null) {
|
||||
connection.close();
|
||||
}
|
||||
throw t;
|
||||
}
|
||||
}
|
||||
|
||||
public static Cx toServer(TraceRmiService service, String description) throws IOException {
|
||||
SocketChannel channel = null;
|
||||
try {
|
||||
channel = SocketChannel.open(service.getServerAddress());
|
||||
TestTraceRmiClient client = new TestTraceRmiClient(channel);
|
||||
client.sendNegotiate(description);
|
||||
client.recvNegotiate();
|
||||
return new Cx(channel, client,
|
||||
waitForPass(() -> Unique.assertOne(service.getAllConnections())));
|
||||
}
|
||||
catch (Throwable t) {
|
||||
if (channel != null) {
|
||||
channel.close();
|
||||
}
|
||||
throw t;
|
||||
}
|
||||
}
|
||||
|
||||
public static Cx connect(TraceRmiService service, String description)
|
||||
throws IOException, InterruptedException, ExecutionException, TimeoutException {
|
||||
SocketChannel channel = null;
|
||||
CompletableFuture<TraceRmiConnection> future = null;
|
||||
try (ServerSocketChannel server = ServerSocketChannel.open()) {
|
||||
server.bind(new InetSocketAddress("localhost", 0), 1);
|
||||
future = CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
return service.connect(server.getLocalAddress());
|
||||
}
|
||||
catch (IOException e) {
|
||||
return ExceptionUtils.rethrow(e);
|
||||
}
|
||||
});
|
||||
channel = server.accept();
|
||||
TestTraceRmiClient client = new TestTraceRmiClient(channel);
|
||||
client.sendNegotiate(description);
|
||||
client.recvNegotiate();
|
||||
return new Cx(channel, client, future.get(1, TimeUnit.SECONDS));
|
||||
}
|
||||
catch (Throwable t) {
|
||||
if (channel != null) {
|
||||
channel.close();
|
||||
}
|
||||
throw t;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
connection.close();
|
||||
channel.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAcceptThenSuccessNodes() throws Exception {
|
||||
TraceRmiAcceptor acceptor =
|
||||
traceRmiService.acceptOne(new InetSocketAddress("localhost", 0));
|
||||
assertNotNull(
|
||||
TraceRmiConnectionTreeHelper.getAcceptorNodeMap(provider.rootNode).get(acceptor));
|
||||
|
||||
try (Cx cx = Cx.complete(acceptor, "Test client")) {
|
||||
waitForPass(() -> traceRmiService.getAllAcceptors().isEmpty());
|
||||
waitForPass(() -> assertNull(
|
||||
TraceRmiConnectionTreeHelper.getAcceptorNodeMap(provider.rootNode)
|
||||
.get(acceptor)));
|
||||
waitForPass(() -> assertEquals(cx.connection,
|
||||
Unique.assertOne(traceRmiService.getAllConnections())));
|
||||
|
||||
TraceRmiConnectionNode node =
|
||||
TraceRmiConnectionTreeHelper.getConnectionNodeMap(provider.rootNode)
|
||||
.get(cx.connection);
|
||||
assertNotNull(node);
|
||||
assertEquals("Test client at " + cx.connection.getRemoteAddress(),
|
||||
node.getDisplayText());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testServerConnectNode() throws Exception {
|
||||
traceRmiService.startServer();
|
||||
try (Cx cx = Cx.toServer(traceRmiService, "Test client")) {
|
||||
waitForPass(() -> traceRmiService.getAllAcceptors().isEmpty());
|
||||
|
||||
TraceRmiConnectionNode node = waitForValue(
|
||||
() -> TraceRmiConnectionTreeHelper.getConnectionNodeMap(provider.rootNode)
|
||||
.get(cx.connection));
|
||||
assertEquals("Test client at " + cx.connection.getRemoteAddress(),
|
||||
node.getDisplayText());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConnectThenSuccessNodes() throws Exception {
|
||||
try (Cx cx = Cx.connect(traceRmiService, "Test client")) {
|
||||
waitForPass(() -> assertEquals(cx.connection,
|
||||
Unique.assertOne(traceRmiService.getAllConnections())));
|
||||
|
||||
TraceRmiConnectionNode node =
|
||||
TraceRmiConnectionTreeHelper.getConnectionNodeMap(provider.rootNode)
|
||||
.get(cx.connection);
|
||||
assertNotNull(node);
|
||||
assertEquals("Test client at " + cx.connection.getRemoteAddress(),
|
||||
node.getDisplayText());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFrontEndCloseNoNodes() throws Exception {
|
||||
TraceRmiAcceptor acceptor =
|
||||
traceRmiService.acceptOne(new InetSocketAddress("localhost", 0));
|
||||
try (Cx cx = Cx.complete(acceptor, "Test client")) {
|
||||
assertNotNull(TraceRmiConnectionTreeHelper.getConnectionNodeMap(provider.rootNode)
|
||||
.get(cx.connection));
|
||||
|
||||
cx.connection.close();
|
||||
waitForPass(() -> assertTrue(traceRmiService.getAllConnections().isEmpty()));
|
||||
waitForPass(() -> assertNull(
|
||||
TraceRmiConnectionTreeHelper.getConnectionNodeMap(provider.rootNode)
|
||||
.get(cx.connection)));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBackEndCloseNoNodes() throws Exception {
|
||||
TraceRmiAcceptor acceptor =
|
||||
traceRmiService.acceptOne(new InetSocketAddress("localhost", 0));
|
||||
try (Cx cx = Cx.complete(acceptor, "Test client")) {
|
||||
assertNotNull(TraceRmiConnectionTreeHelper.getConnectionNodeMap(provider.rootNode)
|
||||
.get(cx.connection));
|
||||
|
||||
cx.channel.close();
|
||||
waitForPass(() -> assertTrue(traceRmiService.getAllConnections().isEmpty()));
|
||||
waitForPass(() -> assertNull(
|
||||
TraceRmiConnectionTreeHelper.getConnectionNodeMap(provider.rootNode)
|
||||
.get(cx.connection)));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActivateTargetNode() throws Exception {
|
||||
SchemaContext ctx = XmlSchemaContext.deserialize("""
|
||||
<context>
|
||||
<schema name="Root" elementResync="NEVER" attributeResync="NEVER" />
|
||||
</context>
|
||||
""");
|
||||
try (Cx cx = Cx.connect(traceRmiService, "Test client")) {
|
||||
cx.client.createTrace(1, "bash");
|
||||
try (Tx tx = cx.client.new Tx(1, 1, "Create snapshots")) {
|
||||
cx.client.snapshot(1, 0, "First snapshot");
|
||||
cx.client.createRootObject(1, ctx.getSchema(new SchemaName("Root")));
|
||||
cx.client.snapshot(1, 1, "Stepped");
|
||||
}
|
||||
cx.client.activate(1, "");
|
||||
Target target = waitForValue(() -> traceManager.getCurrent().getTarget());
|
||||
|
||||
TraceRmiTargetNode node =
|
||||
TraceRmiConnectionTreeHelper.getTargetNodeMap(provider.rootNode).get(target);
|
||||
assertEquals("bash (snap=1)", node.getDisplayText());
|
||||
|
||||
provider.tree.setSelectedNode(node);
|
||||
// Tree uses a task queue for selection requests
|
||||
waitForPass(
|
||||
() -> assertEquals(node, Unique.assertOne(provider.tree.getSelectedNodes())));
|
||||
|
||||
traceManager.activateSnap(0);
|
||||
waitForPass(() -> {
|
||||
assertEquals(0, traceManager.getCurrentSnap());
|
||||
assertEquals(ControlMode.RO_TRACE,
|
||||
controlService.getCurrentMode(target.getTrace()));
|
||||
});
|
||||
|
||||
triggerEnter(provider.tree);
|
||||
waitForPass(() -> {
|
||||
assertEquals(1, traceManager.getCurrentSnap());
|
||||
assertEquals(ControlMode.RO_TARGET,
|
||||
controlService.getCurrentMode(target.getTrace()));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.gui.tracermi.connection.tree;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import ghidra.debug.api.target.Target;
|
||||
import ghidra.debug.api.tracermi.TraceRmiAcceptor;
|
||||
import ghidra.debug.api.tracermi.TraceRmiConnection;
|
||||
|
||||
public class TraceRmiConnectionTreeHelper {
|
||||
public static Map<TraceRmiAcceptor, TraceRmiAcceptorNode> getAcceptorNodeMap(
|
||||
TraceRmiServiceNode serviceNode) {
|
||||
return serviceNode.acceptorNodes;
|
||||
}
|
||||
|
||||
public static Map<TraceRmiConnection, TraceRmiConnectionNode> getConnectionNodeMap(
|
||||
TraceRmiServiceNode serviceNode) {
|
||||
return serviceNode.connectionNodes;
|
||||
}
|
||||
|
||||
public static Map<Target, TraceRmiTargetNode> getTargetNodeMap(
|
||||
TraceRmiServiceNode serviceNode) {
|
||||
return serviceNode.targetNodes;
|
||||
}
|
||||
|
||||
public static TraceRmiServerNode getServerNode(TraceRmiServiceNode serviceNode) {
|
||||
return serviceNode.serverNode;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.service.tracermi;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SocketChannel;
|
||||
|
||||
import com.google.protobuf.AbstractMessage;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
|
||||
public class ProtobufSocket<T extends AbstractMessage> {
|
||||
public interface Decoder<T> {
|
||||
T decode(ByteBuffer buf) throws InvalidProtocolBufferException;
|
||||
}
|
||||
|
||||
private final ByteBuffer lenSend = ByteBuffer.allocate(4);
|
||||
private final ByteBuffer lenRecv = ByteBuffer.allocate(4);
|
||||
private final SocketChannel channel;
|
||||
private final Decoder<T> decoder;
|
||||
|
||||
public ProtobufSocket(SocketChannel channel, Decoder<T> decoder) {
|
||||
this.channel = channel;
|
||||
this.decoder = decoder;
|
||||
}
|
||||
|
||||
public void send(T msg) throws IOException {
|
||||
synchronized (lenSend) {
|
||||
lenSend.clear();
|
||||
lenSend.putInt(msg.getSerializedSize());
|
||||
lenSend.flip();
|
||||
channel.write(lenSend);
|
||||
for (ByteBuffer buf : msg.toByteString().asReadOnlyByteBufferList()) {
|
||||
channel.write(buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public T recv() throws IOException {
|
||||
synchronized (lenRecv) {
|
||||
lenRecv.clear();
|
||||
while (lenRecv.hasRemaining()) {
|
||||
channel.read(lenRecv);
|
||||
}
|
||||
lenRecv.flip();
|
||||
int len = lenRecv.getInt();
|
||||
// This is just for testing, so littering on the heap is okay.
|
||||
ByteBuffer buf = ByteBuffer.allocate(len);
|
||||
while (buf.hasRemaining()) {
|
||||
channel.read(buf);
|
||||
}
|
||||
buf.flip();
|
||||
return decoder.decode(buf);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,162 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.service.tracermi;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.SocketChannel;
|
||||
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema;
|
||||
import ghidra.dbg.target.schema.XmlSchemaContext;
|
||||
import ghidra.framework.Application;
|
||||
import ghidra.rmi.trace.TraceRmi.*;
|
||||
import ghidra.rmi.trace.TraceRmi.Compiler;
|
||||
|
||||
public class TestTraceRmiClient {
|
||||
final ProtobufSocket<RootMessage> socket;
|
||||
|
||||
public TestTraceRmiClient(SocketChannel channel) {
|
||||
this.socket = new ProtobufSocket<>(channel, RootMessage::parseFrom);
|
||||
}
|
||||
|
||||
public void sendNegotiate(String description) throws IOException {
|
||||
socket.send(RootMessage.newBuilder()
|
||||
.setRequestNegotiate(RequestNegotiate.newBuilder()
|
||||
.setVersion(TraceRmiHandler.VERSION)
|
||||
.setDescription(description))
|
||||
.build());
|
||||
}
|
||||
|
||||
public void recvNegotiate() throws IOException {
|
||||
assertEquals(RootMessage.newBuilder()
|
||||
.setReplyNegotiate(ReplyNegotiate.newBuilder()
|
||||
.setDescription(
|
||||
Application.getName() + " " +
|
||||
Application.getApplicationVersion()))
|
||||
.build(),
|
||||
socket.recv());
|
||||
}
|
||||
|
||||
public void createTrace(int id, String name) throws IOException {
|
||||
socket.send(RootMessage.newBuilder()
|
||||
.setRequestCreateTrace(RequestCreateTrace.newBuilder()
|
||||
.setOid(DomObjId.newBuilder()
|
||||
.setId(id))
|
||||
.setLanguage(Language.newBuilder()
|
||||
.setId("Toy:BE:64:default"))
|
||||
.setCompiler(Compiler.newBuilder()
|
||||
.setId("default"))
|
||||
.setPath(FilePath.newBuilder()
|
||||
.setPath("test/" + name)))
|
||||
.build());
|
||||
assertEquals(RootMessage.newBuilder()
|
||||
.setReplyCreateTrace(ReplyCreateTrace.newBuilder())
|
||||
.build(),
|
||||
socket.recv());
|
||||
}
|
||||
|
||||
public void startTx(int traceId, int txId, String description) throws IOException {
|
||||
socket.send(RootMessage.newBuilder()
|
||||
.setRequestStartTx(RequestStartTx.newBuilder()
|
||||
.setOid(DomObjId.newBuilder()
|
||||
.setId(traceId))
|
||||
.setTxid(TxId.newBuilder().setId(txId))
|
||||
.setDescription(description))
|
||||
.build());
|
||||
assertEquals(RootMessage.newBuilder()
|
||||
.setReplyStartTx(ReplyStartTx.newBuilder())
|
||||
.build(),
|
||||
socket.recv());
|
||||
}
|
||||
|
||||
public void endTx(int traceId, int txId) throws IOException {
|
||||
socket.send(RootMessage.newBuilder()
|
||||
.setRequestEndTx(RequestEndTx.newBuilder()
|
||||
.setOid(DomObjId.newBuilder()
|
||||
.setId(traceId))
|
||||
.setTxid(TxId.newBuilder().setId(txId))
|
||||
.setAbort(false))
|
||||
.build());
|
||||
assertEquals(RootMessage.newBuilder()
|
||||
.setReplyEndTx(ReplyEndTx.newBuilder())
|
||||
.build(),
|
||||
socket.recv());
|
||||
}
|
||||
|
||||
public class Tx implements AutoCloseable {
|
||||
private final int traceId;
|
||||
private final int txId;
|
||||
|
||||
public Tx(int traceId, int txId, String description) throws IOException {
|
||||
this.traceId = traceId;
|
||||
this.txId = txId;
|
||||
startTx(traceId, txId, description);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
endTx(traceId, txId);
|
||||
}
|
||||
}
|
||||
|
||||
public void snapshot(int traceId, long snap, String description) throws IOException {
|
||||
socket.send(RootMessage.newBuilder()
|
||||
.setRequestSnapshot(RequestSnapshot.newBuilder()
|
||||
.setOid(DomObjId.newBuilder()
|
||||
.setId(traceId))
|
||||
.setSnap(Snap.newBuilder()
|
||||
.setSnap(snap))
|
||||
.setDescription(description))
|
||||
.build());
|
||||
assertEquals(RootMessage.newBuilder()
|
||||
.setReplySnapshot(ReplySnapshot.newBuilder())
|
||||
.build(),
|
||||
socket.recv());
|
||||
}
|
||||
|
||||
public void createRootObject(int traceId, TargetObjectSchema schema) throws IOException {
|
||||
String xmlCtx = XmlSchemaContext.serialize(schema.getContext());
|
||||
socket.send(RootMessage.newBuilder()
|
||||
.setRequestCreateRootObject(RequestCreateRootObject.newBuilder()
|
||||
.setOid(DomObjId.newBuilder()
|
||||
.setId(traceId))
|
||||
.setSchemaContext(xmlCtx)
|
||||
.setRootSchema(schema.getName().toString()))
|
||||
.build());
|
||||
assertEquals(RootMessage.newBuilder()
|
||||
.setReplyCreateObject(ReplyCreateObject.newBuilder()
|
||||
.setObject(ObjSpec.newBuilder()
|
||||
.setId(0)))
|
||||
.build(),
|
||||
socket.recv());
|
||||
}
|
||||
|
||||
public void activate(int traceId, String path) throws IOException {
|
||||
socket.send(RootMessage.newBuilder()
|
||||
.setRequestActivate(RequestActivate.newBuilder()
|
||||
.setOid(DomObjId.newBuilder()
|
||||
.setId(traceId))
|
||||
.setObject(ObjSpec.newBuilder()
|
||||
.setPath(ObjPath.newBuilder()
|
||||
.setPath(path))))
|
||||
.build());
|
||||
assertEquals(RootMessage.newBuilder()
|
||||
.setReplyActivate(ReplyActivate.newBuilder())
|
||||
.build(),
|
||||
socket.recv());
|
||||
}
|
||||
}
|
|
@ -13,7 +13,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.service.rmi.trace;
|
||||
package ghidra.app.plugin.core.debug.service.tracermi;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
|
@ -28,6 +28,7 @@ import ghidra.async.AsyncPairingQueue;
|
|||
import ghidra.async.AsyncUtils;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
|
||||
import ghidra.debug.api.target.ActionName;
|
||||
import ghidra.debug.api.target.Target;
|
||||
import ghidra.debug.api.tracermi.*;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.trace.model.Trace;
|
||||
|
@ -89,6 +90,11 @@ public class TestTraceRmiConnection implements TraceRmiConnection {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Test Trace RMI connnection";
|
||||
}
|
||||
|
||||
@Override
|
||||
public SocketAddress getRemoteAddress() {
|
||||
return new InetSocketAddress("localhost", 0);
|
||||
|
@ -185,4 +191,9 @@ public class TestTraceRmiConnection implements TraceRmiConnection {
|
|||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Target> getTargets() {
|
||||
return List.copyOf(targets.values());
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue