@Override
	public LaunchResult launchProgram(TaskMonitor monitor, LaunchConfigurator configurator) {
		if (requiresImage() && program == null) {
			throw new IllegalStateException("Offer requires image, but no program given.");
		}
		InternalTraceRmiService service = tool.getService(InternalTraceRmiService.class);
		DebuggerTraceManagerService traceManager =
			tool.getService(DebuggerTraceManagerService.class);
		final PromptMode mode = configurator.getPromptMode();
		boolean prompt = mode == PromptMode.ALWAYS;

		DefaultTraceRmiAcceptor acceptor = null;
		Map<String, TerminalSession> sessions = new LinkedHashMap<>();
		TraceRmiHandler connection = null;
		Trace trace = null;
		Throwable lastExc = null;

		updateMonitorMax(monitor, null);
		while (true) {
			try {
				monitor.setMessage("Gathering arguments");
				Map<String, ValStr<?>> args = getLauncherArgs(prompt, configurator, lastExc);
				if (args == null) {
					if (lastExc == null) {
						lastExc = new CancelledException();
					}
					return new LaunchResult(program, sessions, acceptor, connection, trace,
						lastExc);
				}
				updateMonitorMax(monitor, args);
				monitor.increment();

				acceptor = null;
				sessions.clear();
				connection = null;
				trace = null;
				lastExc = null;

				monitor.setMessage("Listening for connection");
				acceptor = service.acceptOne(new InetSocketAddress("127.0.0.1", 0));
				monitor.increment();

				monitor.setMessage("Launching back-end");
				launchBackEnd(monitor, sessions, args, acceptor.getAddress());
				monitor.increment();

				monitor.setMessage("Waiting for connection");
				acceptor.setTimeout(getConnectionTimeoutMillis());
				connection = acceptor.accept();
				connection.registerTerminals(sessions.values());
				monitor.increment();

				monitor.setMessage("Waiting for trace");
				trace = connection.waitForTrace(getTimeoutMillis());
				traceManager.openTrace(trace);
				traceManager.activate(traceManager.resolveTrace(trace),
					ActivationCause.TARGET_UPDATED);
				monitor.increment();

				waitForModuleMapping(monitor, connection, trace);
			}
			catch (CancelledException e) {
				lastExc = e;
				LaunchResult result =
					new LaunchResult(program, sessions, acceptor, connection, trace, lastExc);
				try {
					result.close();
				}
				catch (Exception e1) {
					Msg.error(this, "Could not close", e1);
				}
				return new LaunchResult(program, Map.of(), null, null, null, lastExc);
			}
			catch (NoStaticMappingException e) {
				DebuggerConsoleService consoleService =
					tool.getService(DebuggerConsoleService.class);
				if (consoleService == null) {
					Msg.error(this, e.getMessage());
				}
				else {
					consoleService.log(DebuggerResources.ICON_MODULES,
						"<html>The trace <b>%s</b> has no mapping to its program <b>%s</b></html>"
								.formatted(
									HTMLUtilities.escapeHTML(trace.getDomainFile().getName()),
									HTMLUtilities.escapeHTML(program.getDomainFile().getName())),
						new DebuggerMissingProgramActionContext(trace, program));
				}
				return new LaunchResult(program, sessions, acceptor, connection, trace, e);
			}
			catch (Exception e) {
				DebuggerConsoleService consoleService =
					tool.getService(DebuggerConsoleService.class);
				if (consoleService != null) {
					consoleService.log(DebuggerResources.ICON_LOG_ERROR,
						"Launch %s Failed".formatted(getTitle()), e);
				}
				lastExc = e;
				prompt = mode != PromptMode.NEVER;
				LaunchResult result =
					new LaunchResult(program, sessions, acceptor, connection, trace, lastExc);
				if (prompt) {
					switch (promptError(result)) {
						case KEEP:
							result.showTerminals();
							return result;
						case RETRY:
							try {
								result.close();
							}
							catch (Exception e1) {
								Msg.error(this, "Could not close", e1);
							}
							continue;
						case TERMINATE:
							try {
								result.close();
							}
							catch (Exception e1) {
								Msg.error(this, "Could not close", e1);
							}
							return new LaunchResult(program, Map.of(), null, null, null, lastExc);
					}
					continue;
				}
				return result;
			}
			return new LaunchResult(program, sessions, acceptor, connection, trace, null);
		}
	}
Obtain the launcher arguments

<p>
This should either call {@link #promptLauncherArgs(LaunchConfigurator, Throwable)} or
{@link #loadLastLauncherArgs(boolean)}. Note if choosing the latter, the user will not be
prompted to confirm.

@param prompt true to prompt the user, false to use saved arguments
@param configurator the rules for configuring the launcher
@param lastExc if retrying, the last exception to display as an error message
@return the chosen arguments, or null if the user cancels at the prompt
Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/AbstractTraceRmiLaunchOffer.java