mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 19:42:36 +02:00
Merge remote-tracking branch 'origin/GP-869_d-millar_ConPTY--REBASED-1'
(Closes #2908)
This commit is contained in:
commit
7ab2f5d38f
33 changed files with 1922 additions and 208 deletions
|
@ -28,6 +28,8 @@ dependencies {
|
|||
api project(':Debugger-gadp')
|
||||
api project(':Python')
|
||||
api 'com.jcraft:jsch:0.1.55'
|
||||
api "net.java.dev.jna:jna:5.4.0"
|
||||
api "net.java.dev.jna:jna-platform:5.4.0"
|
||||
|
||||
testImplementation project(path: ':Framework-AsyncComm', configuration: 'testArtifacts')
|
||||
testImplementation project(path: ':Framework-Debugging', configuration: 'testArtifacts')
|
||||
|
|
|
@ -20,7 +20,7 @@ import java.util.concurrent.CompletableFuture;
|
|||
|
||||
import agent.gdb.manager.GdbManager;
|
||||
import agent.gdb.model.impl.GdbModelImpl;
|
||||
import agent.gdb.pty.linux.LinuxPtyFactory;
|
||||
import agent.gdb.pty.PtyFactory;
|
||||
import ghidra.dbg.DebuggerModelFactory;
|
||||
import ghidra.dbg.DebuggerObjectModel;
|
||||
import ghidra.dbg.util.ConfigurableFactory.FactoryDescription;
|
||||
|
@ -50,9 +50,8 @@ public class GdbInJvmDebuggerModelFactory implements DebuggerModelFactory {
|
|||
|
||||
@Override
|
||||
public CompletableFuture<? extends DebuggerObjectModel> build() {
|
||||
// TODO: Choose Linux or Windows pty based on host OS
|
||||
List<String> gdbCmdLine = ShellUtils.parseArgs(gdbCmd);
|
||||
GdbModelImpl model = new GdbModelImpl(new LinuxPtyFactory());
|
||||
GdbModelImpl model = new GdbModelImpl(PtyFactory.local());
|
||||
return model
|
||||
.startGDB(existing ? null : gdbCmdLine.get(0),
|
||||
gdbCmdLine.subList(1, gdbCmdLine.size()).toArray(String[]::new))
|
||||
|
|
|
@ -21,7 +21,7 @@ import java.util.concurrent.CompletableFuture;
|
|||
|
||||
import agent.gdb.gadp.GdbGadpServer;
|
||||
import agent.gdb.model.impl.GdbModelImpl;
|
||||
import agent.gdb.pty.linux.LinuxPtyFactory;
|
||||
import agent.gdb.pty.PtyFactory;
|
||||
import ghidra.dbg.gadp.server.AbstractGadpServer;
|
||||
|
||||
public class GdbGadpServerImpl implements GdbGadpServer {
|
||||
|
@ -36,8 +36,7 @@ public class GdbGadpServerImpl implements GdbGadpServer {
|
|||
|
||||
public GdbGadpServerImpl(SocketAddress addr) throws IOException {
|
||||
super();
|
||||
// TODO: Select Linux or Windows factory based on host OS
|
||||
this.model = new GdbModelImpl(new LinuxPtyFactory());
|
||||
this.model = new GdbModelImpl(PtyFactory.local());
|
||||
this.server = new GadpSide(model, addr);
|
||||
}
|
||||
|
||||
|
|
|
@ -26,7 +26,6 @@ import agent.gdb.manager.breakpoint.GdbBreakpointInsertions;
|
|||
import agent.gdb.manager.impl.GdbManagerImpl;
|
||||
import agent.gdb.pty.PtyFactory;
|
||||
import agent.gdb.pty.linux.LinuxPty;
|
||||
import agent.gdb.pty.linux.LinuxPtyFactory;
|
||||
|
||||
/**
|
||||
* The controlling side of a GDB session, using GDB/MI, usually via a pseudo-terminal
|
||||
|
@ -86,8 +85,7 @@ public interface GdbManager extends AutoCloseable, GdbConsoleOperations, GdbBrea
|
|||
*/
|
||||
public static void main(String[] args)
|
||||
throws InterruptedException, ExecutionException, IOException {
|
||||
// TODO: Choose factory by host OS
|
||||
try (GdbManager mgr = newInstance(new LinuxPtyFactory())) {
|
||||
try (GdbManager mgr = newInstance(PtyFactory.local())) {
|
||||
mgr.start(DEFAULT_GDB_CMD, args);
|
||||
mgr.runRC().get();
|
||||
mgr.consoleLoop();
|
||||
|
@ -434,23 +432,6 @@ public interface GdbManager extends AutoCloseable, GdbConsoleOperations, GdbBrea
|
|||
*/
|
||||
CompletableFuture<Void> removeInferior(GdbInferior inferior);
|
||||
|
||||
/**
|
||||
* Interrupt the GDB session
|
||||
*
|
||||
* <p>
|
||||
* The manager may employ a variety of mechanisms depending on the current configuration. If
|
||||
* multiple interpreters are available, it will issue an "interrupt" command on whichever
|
||||
* interpreter it believes is responsive -- usually the opposite of the one issuing the last
|
||||
* run, continue, step, etc. command. Otherwise, it sends Ctrl-C to GDB's TTY, which
|
||||
* unfortunately is notoriously unreliable. The manager will send Ctrl-C to the TTY up to three
|
||||
* times, waiting about 10ms between each, until GDB issues a stopped event and presents a new
|
||||
* prompt. If that fails, it is up to the user to find an alternative means to interrupt the
|
||||
* target, e.g., issuing {@code kill [pid]} from the a terminal on the target's host.
|
||||
*
|
||||
* @return a future that completes when GDB has entered the stopped state
|
||||
*/
|
||||
CompletableFuture<Void> interrupt();
|
||||
|
||||
/**
|
||||
* List GDB's inferiors
|
||||
*
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
package agent.gdb.manager.evt;
|
||||
|
||||
import agent.gdb.manager.GdbCause;
|
||||
import agent.gdb.manager.GdbCause.Causes;
|
||||
import agent.gdb.manager.GdbState;
|
||||
import agent.gdb.manager.impl.GdbEvent;
|
||||
import agent.gdb.manager.impl.GdbPendingCommand;
|
||||
|
|
|
@ -20,6 +20,7 @@ import agent.gdb.manager.parsing.GdbParsingUtils.GdbParseError;
|
|||
/**
|
||||
* An "event" corresponding with GDB/MI commands
|
||||
*
|
||||
* <p>
|
||||
* If using a PTY configured with local echo, the manager needs to recognize and ignore the commands
|
||||
* it issued. GDB/MI makes them easy to distinguish, because they start with "-".
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
/* ###
|
||||
* 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 agent.gdb.manager.evt;
|
||||
|
||||
import agent.gdb.manager.parsing.GdbParsingUtils.GdbParseError;
|
||||
|
||||
/**
|
||||
* An "event" corresponding with the {@code -exec-interrupt} GDB/MI command
|
||||
*
|
||||
* <p>
|
||||
* If issued, the {@code -exec-interrupt} command is always issued "out of band". It skips the queue
|
||||
* and is printed straight to GDB's pty, usually preceded by a Ctrl-C (char 3). As a result, GDB is
|
||||
* going to print {@code ^done}, which will get mistaken for the completion of a command in the
|
||||
* queue. By recognizing the command being echoed back, we can identify the done event that does
|
||||
* with it, and ignore it.
|
||||
*/
|
||||
public class GdbCommandEchoInterruptEvent extends AbstractGdbEvent<String> {
|
||||
|
||||
/**
|
||||
* Construct a new "event", passing the tail through as information
|
||||
*
|
||||
* @param tail the text following the event type in the GDB/MI event record
|
||||
* @throws GdbParseError if the tail cannot be parsed
|
||||
*/
|
||||
public GdbCommandEchoInterruptEvent(CharSequence tail) throws GdbParseError {
|
||||
super(tail);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String parseInfo(CharSequence tail) throws GdbParseError {
|
||||
return tail.toString();
|
||||
}
|
||||
}
|
|
@ -15,12 +15,11 @@
|
|||
*/
|
||||
package agent.gdb.manager.impl;
|
||||
|
||||
import static ghidra.async.AsyncUtils.loop;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import javax.swing.JDialog;
|
||||
import javax.swing.JOptionPane;
|
||||
|
@ -40,12 +39,14 @@ import agent.gdb.manager.impl.cmd.GdbConsoleExecCommand.CompletesWithRunning;
|
|||
import agent.gdb.manager.parsing.GdbMiParser;
|
||||
import agent.gdb.manager.parsing.GdbParsingUtils.GdbParseError;
|
||||
import agent.gdb.pty.*;
|
||||
import agent.gdb.pty.windows.AnsiBufferedInputStream;
|
||||
import ghidra.GhidraApplicationLayout;
|
||||
import ghidra.async.*;
|
||||
import ghidra.async.AsyncLock.Hold;
|
||||
import ghidra.dbg.error.DebuggerModelTerminatingException;
|
||||
import ghidra.dbg.util.HandlerMap;
|
||||
import ghidra.dbg.util.PrefixMap;
|
||||
import ghidra.framework.OperatingSystem;
|
||||
import ghidra.lifecycle.Internal;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.SystemUtilities;
|
||||
|
@ -107,8 +108,13 @@ public class GdbManagerImpl implements GdbManager {
|
|||
PtyThread(Pty pty, Channel channel, Interpreter interpreter) {
|
||||
this.pty = pty;
|
||||
this.channel = channel;
|
||||
this.reader =
|
||||
new BufferedReader(new InputStreamReader(pty.getParent().getInputStream()));
|
||||
InputStream inputStream = pty.getParent().getInputStream();
|
||||
// TODO: This should really only be applied to the MI2 console
|
||||
// But, we don't know what we have until we read it....
|
||||
if (OperatingSystem.CURRENT_OPERATING_SYSTEM == OperatingSystem.WINDOWS) {
|
||||
inputStream = new AnsiBufferedInputStream(inputStream);
|
||||
}
|
||||
this.reader = new BufferedReader(new InputStreamReader(inputStream));
|
||||
this.interpreter = interpreter;
|
||||
hasWriter = new CompletableFuture<>();
|
||||
}
|
||||
|
@ -202,9 +208,9 @@ public class GdbManagerImpl implements GdbManager {
|
|||
private final AsyncLock cmdLock = new AsyncLock();
|
||||
private final AtomicReference<AsyncLock.Hold> cmdLockHold = new AtomicReference<>(null);
|
||||
private ExecutorService executor;
|
||||
private final AsyncTimer timer = AsyncTimer.DEFAULT_TIMER;
|
||||
|
||||
private GdbPendingCommand<?> curCmd = null;
|
||||
private int interruptCount = 0;
|
||||
|
||||
private final Map<Integer, GdbInferiorImpl> inferiors = new LinkedHashMap<>();
|
||||
private GdbInferiorImpl curInferior = null;
|
||||
|
@ -248,6 +254,7 @@ public class GdbManagerImpl implements GdbManager {
|
|||
File userSettings = layout.getUserSettingsDir();
|
||||
File logFile = new File(userSettings, "GDB.log");
|
||||
try {
|
||||
logFile.getParentFile().mkdirs();
|
||||
logFile.createNewFile();
|
||||
}
|
||||
catch (Exception e) {
|
||||
|
@ -287,6 +294,7 @@ public class GdbManagerImpl implements GdbManager {
|
|||
}
|
||||
|
||||
private void defaultPrefixes() {
|
||||
mi2PrefixMap.put("-exec-interrupt", GdbCommandEchoInterruptEvent::new);
|
||||
mi2PrefixMap.put("-", GdbCommandEchoEvent::new);
|
||||
mi2PrefixMap.put("~", GdbConsoleOutputEvent::fromMi2);
|
||||
mi2PrefixMap.put("@", GdbTargetOutputEvent::new);
|
||||
|
@ -320,6 +328,7 @@ public class GdbManagerImpl implements GdbManager {
|
|||
}
|
||||
|
||||
private void defaultHandlers() {
|
||||
handlerMap.putVoid(GdbCommandEchoInterruptEvent.class, this::pushCmdInterrupt);
|
||||
handlerMap.putVoid(GdbCommandEchoEvent.class, this::ignoreCmdEcho);
|
||||
handlerMap.putVoid(GdbConsoleOutputEvent.class, this::processStdOut);
|
||||
handlerMap.putVoid(GdbTargetOutputEvent.class, this::processTargetOut);
|
||||
|
@ -637,7 +646,8 @@ public class GdbManagerImpl implements GdbManager {
|
|||
.get(10, TimeUnit.SECONDS);
|
||||
}
|
||||
catch (InterruptedException | ExecutionException | TimeoutException e) {
|
||||
throw new IOException("Could not detect GDB's interpreter mode");
|
||||
throw new IOException(
|
||||
"Could not detect GDB's interpreter mode. Try " + gdbCmd + " -i mi2");
|
||||
}
|
||||
if (state.get() == GdbState.EXIT) {
|
||||
throw new IOException("GDB terminated before first prompt");
|
||||
|
@ -651,15 +661,24 @@ public class GdbManagerImpl implements GdbManager {
|
|||
// Looks terrible, but we're already in this world
|
||||
cliThread.writer.print("set confirm off" + newLine);
|
||||
cliThread.writer.print("set pagination off" + newLine);
|
||||
cliThread.writer
|
||||
.print("new-ui mi2 " + mi2Pty.getChild().nullSession() + newLine);
|
||||
String ptyName;
|
||||
try {
|
||||
ptyName = Objects.requireNonNull(mi2Pty.getChild().nullSession());
|
||||
}
|
||||
catch (UnsupportedOperationException e) {
|
||||
throw new IOException(
|
||||
"Pty implementation does not support null sessions. Try " + gdbCmd +
|
||||
" i mi2",
|
||||
e);
|
||||
}
|
||||
cliThread.writer.print("new-ui mi2 " + ptyName + newLine);
|
||||
cliThread.writer.flush();
|
||||
|
||||
mi2Thread = new PtyThread(mi2Pty, Channel.STDOUT, Interpreter.MI2);
|
||||
mi2Thread.setName("GDB Read MI2");
|
||||
mi2Thread.start();
|
||||
try {
|
||||
mi2Thread.hasWriter.get(2, TimeUnit.SECONDS);
|
||||
mi2Thread.hasWriter.get(10, TimeUnit.SECONDS);
|
||||
}
|
||||
catch (InterruptedException | ExecutionException | TimeoutException e) {
|
||||
throw new IOException(
|
||||
|
@ -720,10 +739,13 @@ public class GdbManagerImpl implements GdbManager {
|
|||
// NB. confirm and pagination are already disabled here
|
||||
return AsyncUtils.NIL;
|
||||
}
|
||||
else {
|
||||
// NB. Don't disable pagination here. MI2 is not paginated.
|
||||
return console("set confirm off", CompletesWithRunning.CANNOT);
|
||||
}
|
||||
// NB. Don't disable pagination here. MI2 is not paginated.
|
||||
return CompletableFuture.allOf(
|
||||
console("set confirm off", CompletesWithRunning.CANNOT),
|
||||
console("set new-console on", CompletesWithRunning.CANNOT).exceptionally(e -> {
|
||||
// not Windows. So what?
|
||||
return null;
|
||||
}));
|
||||
}
|
||||
|
||||
protected void resync() {
|
||||
|
@ -918,6 +940,12 @@ public class GdbManagerImpl implements GdbManager {
|
|||
}
|
||||
|
||||
protected synchronized void processEvent(GdbEvent<?> evt) {
|
||||
if (evt instanceof AbstractGdbCompletedCommandEvent && interruptCount > 0) {
|
||||
interruptCount--;
|
||||
Msg.debug(this, "Ignoring " + evt +
|
||||
" from -exec-interrupt. new count = " + interruptCount);
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* NOTE: I've forgotten why, but the the state update needs to happen between handle and
|
||||
* finish.
|
||||
|
@ -1006,6 +1034,10 @@ public class GdbManagerImpl implements GdbManager {
|
|||
Msg.info(this, "GDB exited with code " + exitcode);
|
||||
}
|
||||
|
||||
protected void pushCmdInterrupt(GdbCommandEchoInterruptEvent evt, Void v) {
|
||||
interruptCount++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called for lines starting with "-", which are just commands echoed back by the PTY
|
||||
*
|
||||
|
@ -1376,6 +1408,7 @@ public class GdbManagerImpl implements GdbManager {
|
|||
/**
|
||||
* Check that a command completion event was claimed
|
||||
*
|
||||
* <p>
|
||||
* Except under certain error conditions, GDB should never issue a command completed event that
|
||||
* is not associated with a command. A command implementation in the manager must claim the
|
||||
* completion event. This is an assertion to ensure no implementation forgets to do that.
|
||||
|
@ -1738,33 +1771,6 @@ public class GdbManagerImpl implements GdbManager {
|
|||
GdbConsoleExecCommand.Output.CAPTURE, cwr));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> interrupt() {
|
||||
AtomicInteger retryCount = new AtomicInteger();
|
||||
return loop(TypeSpec.VOID, loop -> {
|
||||
GdbCommand<Void> interrupt = new GdbInterruptCommand(this);
|
||||
execute(interrupt).thenApply(e -> (Throwable) null)
|
||||
.exceptionally(e -> e)
|
||||
.handle(loop::consume);
|
||||
}, TypeSpec.cls(Throwable.class), (exc, loop) -> {
|
||||
Msg.debug(this, "Executed an interrupt");
|
||||
if (exc == null) {
|
||||
loop.exit();
|
||||
}
|
||||
else if (state.get() == GdbState.STOPPED) {
|
||||
// Not the cleanest, but as long as we're stopped, why not call it good?
|
||||
loop.exit();
|
||||
}
|
||||
else if (retryCount.getAndAdd(1) >= INTERRUPT_MAX_RETRIES) {
|
||||
loop.exit(exc);
|
||||
}
|
||||
else {
|
||||
Msg.error(this, "Error executing interrupt: " + exc);
|
||||
timer.mark().after(INTERRUPT_RETRY_PERIOD_MILLIS).handle(loop::repeat);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Map<Integer, GdbInferior>> listInferiors() {
|
||||
return execute(new GdbListInferiorsCommand(this));
|
||||
|
|
|
@ -1,84 +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 agent.gdb.manager.impl.cmd;
|
||||
|
||||
import agent.gdb.manager.GdbManager;
|
||||
import agent.gdb.manager.GdbState;
|
||||
import agent.gdb.manager.evt.GdbStoppedEvent;
|
||||
import agent.gdb.manager.impl.*;
|
||||
import agent.gdb.manager.impl.GdbManagerImpl.Interpreter;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
/**
|
||||
* Implementation of {@link GdbManager#interrupt()} when we start GDB
|
||||
*/
|
||||
public class GdbInterruptCommand extends AbstractGdbCommand<Void> {
|
||||
public GdbInterruptCommand(GdbManagerImpl manager) {
|
||||
super(manager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validInState(GdbState state) {
|
||||
//return state == GdbState.RUNNING;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String encode() {
|
||||
Interpreter i = getInterpreter();
|
||||
if (i == manager.getRunningInterpreter()) {
|
||||
Msg.debug(this, "Using ^C to interrupt via " + i);
|
||||
return "\u0003";
|
||||
}
|
||||
switch (i) {
|
||||
case CLI:
|
||||
Msg.debug(this, "Interrupting via CLI");
|
||||
return "interrupt";
|
||||
case MI2:
|
||||
Msg.debug(this, "Interrupting via MI2");
|
||||
return "-exec-interrupt";
|
||||
default:
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(GdbEvent<?> evt, GdbPendingCommand<?> pending) {
|
||||
if (super.handle(evt, pending)) {
|
||||
return true;
|
||||
}
|
||||
else if (evt instanceof GdbStoppedEvent) {
|
||||
pending.claim(evt);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void complete(GdbPendingCommand<?> pending) {
|
||||
// When using -exec-interrupt, ^done will come before *stopped
|
||||
//pending.findSingleOf(GdbStoppedEvent.class);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Interpreter getInterpreter() {
|
||||
if (manager.hasCli() && manager.getRunningInterpreter() == Interpreter.MI2) {
|
||||
return Interpreter.CLI;
|
||||
}
|
||||
return Interpreter.MI2;
|
||||
}
|
||||
}
|
|
@ -144,7 +144,8 @@ public class GdbModelImpl extends AbstractDebuggerObjectModel {
|
|||
gdb.start(gdbCmd, args);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new DebuggerModelTerminatingException("Error while starting GDB", e);
|
||||
throw new DebuggerModelTerminatingException(
|
||||
"Error while starting GDB: " + e.getMessage(), e);
|
||||
}
|
||||
}).thenCompose(__ -> {
|
||||
return gdb.runRC();
|
||||
|
|
|
@ -17,11 +17,31 @@ package agent.gdb.pty;
|
|||
|
||||
import java.io.IOException;
|
||||
|
||||
import agent.gdb.pty.linux.LinuxPtyFactory;
|
||||
import agent.gdb.pty.windows.ConPtyFactory;
|
||||
import ghidra.framework.OperatingSystem;
|
||||
|
||||
/**
|
||||
* A mechanism for opening pseudo-terminals
|
||||
*/
|
||||
public interface PtyFactory {
|
||||
|
||||
/**
|
||||
* Choose a factory of local pty's for the host operating system
|
||||
*
|
||||
* @return the factory
|
||||
*/
|
||||
static PtyFactory local() {
|
||||
switch (OperatingSystem.CURRENT_OPERATING_SYSTEM) {
|
||||
case LINUX:
|
||||
return new LinuxPtyFactory();
|
||||
case WINDOWS:
|
||||
return new ConPtyFactory();
|
||||
default:
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a new pseudo-terminal
|
||||
*
|
||||
|
|
|
@ -28,6 +28,6 @@ public class LinuxPtyFactory implements PtyFactory {
|
|||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "local";
|
||||
return "local (Linux)";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 agent.gdb.pty.local;
|
||||
|
||||
import com.sun.jna.LastErrorException;
|
||||
import com.sun.jna.platform.win32.Kernel32;
|
||||
import com.sun.jna.platform.win32.WinBase;
|
||||
import com.sun.jna.ptr.IntByReference;
|
||||
|
||||
import agent.gdb.pty.PtySession;
|
||||
import agent.gdb.pty.windows.Handle;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
public class LocalWindowsNativeProcessPtySession implements PtySession {
|
||||
//private final int pid;
|
||||
//private final int tid;
|
||||
private final Handle processHandle;
|
||||
//private final Handle threadHandle;
|
||||
|
||||
public LocalWindowsNativeProcessPtySession(int pid, int tid, Handle processHandle,
|
||||
Handle threadHandle) {
|
||||
//this.pid = pid;
|
||||
//this.tid = tid;
|
||||
this.processHandle = processHandle;
|
||||
//this.threadHandle = threadHandle;
|
||||
|
||||
Msg.info(this, "local Windows Pty session. PID = " + pid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int waitExited() throws InterruptedException {
|
||||
while (true) {
|
||||
switch (Kernel32.INSTANCE.WaitForSingleObject(processHandle.getNative(), -1)) {
|
||||
case Kernel32.WAIT_OBJECT_0:
|
||||
case Kernel32.WAIT_ABANDONED:
|
||||
IntByReference lpExitCode = new IntByReference();
|
||||
Kernel32.INSTANCE.GetExitCodeProcess(processHandle.getNative(), lpExitCode);
|
||||
if (lpExitCode.getValue() != WinBase.STILL_ACTIVE) {
|
||||
return lpExitCode.getValue();
|
||||
}
|
||||
case Kernel32.WAIT_TIMEOUT:
|
||||
throw new AssertionError();
|
||||
case Kernel32.WAIT_FAILED:
|
||||
throw new LastErrorException(Kernel32.INSTANCE.GetLastError());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroyForcibly() {
|
||||
if (!Kernel32.INSTANCE.TerminateProcess(processHandle.getNative(), 1)) {
|
||||
int error = Kernel32.INSTANCE.GetLastError();
|
||||
switch (error) {
|
||||
case Kernel32.ERROR_ACCESS_DENIED:
|
||||
/**
|
||||
* This indicates the process has already terminated. It's unclear to me whether
|
||||
* or not that is the only possible cause of this error.
|
||||
*/
|
||||
return;
|
||||
}
|
||||
throw new LastErrorException(error);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,473 @@
|
|||
/* ###
|
||||
* 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 agent.gdb.pty.windows;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Arrays;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
// TODO: I shouldn't have to do any of this.
|
||||
public class AnsiBufferedInputStream extends InputStream {
|
||||
private static final Charset WINDOWS_1252 = Charset.forName("windows-1252");
|
||||
|
||||
private enum Mode {
|
||||
CHARS,
|
||||
ESC,
|
||||
CSI,
|
||||
CSI_p,
|
||||
CSI_Q,
|
||||
OSC,
|
||||
WINDOW_TITLE,
|
||||
WINDOW_TITLE_ESC;
|
||||
}
|
||||
|
||||
private final InputStream in;
|
||||
|
||||
private int countIn = 0;
|
||||
|
||||
private ByteBuffer lineBaked = ByteBuffer.allocate(Short.MAX_VALUE);
|
||||
private ByteBuffer lineBuf = ByteBuffer.allocate(Short.MAX_VALUE);
|
||||
private ByteBuffer escBuf = ByteBuffer.allocate(1024);
|
||||
private ByteBuffer titleBuf = ByteBuffer.allocate(255);
|
||||
|
||||
private Mode mode = Mode.CHARS;
|
||||
|
||||
public AnsiBufferedInputStream(InputStream in) {
|
||||
if (in instanceof HandleInputStream) {
|
||||
// Spare myself the 1-by-1 native calls
|
||||
in = new BufferedInputStream(in);
|
||||
}
|
||||
this.in = in;
|
||||
|
||||
lineBuf.limit(0);
|
||||
lineBaked.limit(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
if (lineBaked.hasRemaining()) {
|
||||
return lineBaked.get();
|
||||
}
|
||||
if (readUntilBaked() < 0) {
|
||||
return -1;
|
||||
}
|
||||
if (lineBaked.hasRemaining()) {
|
||||
return lineBaked.get();
|
||||
}
|
||||
return -1; // EOF
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
if (!lineBaked.hasRemaining()) {
|
||||
if (readUntilBaked() < 0) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
int read = Math.min(lineBaked.remaining(), len);
|
||||
lineBaked.get(b, off, read);
|
||||
return read;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
in.close();
|
||||
super.close();
|
||||
}
|
||||
|
||||
protected int readUntilBaked() throws IOException {
|
||||
while (!lineBaked.hasRemaining()) {
|
||||
if (processNext() < 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!lineBaked.hasRemaining()) {
|
||||
return -1;
|
||||
}
|
||||
return lineBaked.remaining();
|
||||
}
|
||||
|
||||
protected void printDebugChar(byte c) {
|
||||
if (0x20 <= c && c <= 0x7f) {
|
||||
System.err.print(new String(new byte[] { c }));
|
||||
}
|
||||
else {
|
||||
System.err.print(String.format("<%02x>", c & 0xff));
|
||||
}
|
||||
}
|
||||
|
||||
protected int processNext() throws IOException {
|
||||
int ci = in.read();
|
||||
if (ci == -1) {
|
||||
return -1;
|
||||
}
|
||||
byte c = (byte) ci;
|
||||
// printDebugChar(c);
|
||||
switch (mode) {
|
||||
case CHARS:
|
||||
processChars(c);
|
||||
break;
|
||||
case ESC:
|
||||
processEsc(c);
|
||||
break;
|
||||
case CSI:
|
||||
processCsi(c);
|
||||
break;
|
||||
case CSI_p:
|
||||
processCsiParamOrCommand(c);
|
||||
break;
|
||||
case CSI_Q:
|
||||
processCsiQ(c);
|
||||
break;
|
||||
case OSC:
|
||||
processOsc(c);
|
||||
break;
|
||||
case WINDOW_TITLE:
|
||||
processWindowTitle(c);
|
||||
break;
|
||||
case WINDOW_TITLE_ESC:
|
||||
processWindowTitleEsc(c);
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError();
|
||||
}
|
||||
countIn++;
|
||||
return c;
|
||||
}
|
||||
|
||||
/**
|
||||
* There's not really a good way to know if any trailing space was intentional. For GDB/MI, that
|
||||
* doesn't really matter.
|
||||
*/
|
||||
protected int guessEnd() {
|
||||
for (int i = lineBuf.limit() - 1; i >= 0; i--) {
|
||||
byte c = lineBuf.get(i);
|
||||
if (c != 0x20 && c != 0) {
|
||||
return i + 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected void bakeLine() {
|
||||
lineBuf.position(0);
|
||||
lineBuf.limit(guessEnd() + 1);
|
||||
lineBuf.put(lineBuf.limit() - 1, (byte) '\n');
|
||||
ByteBuffer temp = lineBaked;
|
||||
lineBaked = lineBuf;
|
||||
lineBuf = temp;
|
||||
lineBuf.clear();
|
||||
Arrays.fill(lineBuf.array(), (byte) 0);
|
||||
lineBuf.limit(0);
|
||||
}
|
||||
|
||||
protected void appendChar(byte c) {
|
||||
int limit = lineBuf.limit();
|
||||
if (lineBuf.position() == limit) {
|
||||
lineBuf.limit(limit + 1);
|
||||
}
|
||||
lineBuf.put(c);
|
||||
}
|
||||
|
||||
protected void processChars(byte c) {
|
||||
switch (c) {
|
||||
case 0x08:
|
||||
if (lineBuf.get(lineBuf.position() - 1) == ' ') {
|
||||
lineBuf.position(lineBuf.position() - 1);
|
||||
}
|
||||
break;
|
||||
case '\n':
|
||||
//appendChar(c);
|
||||
bakeLine();
|
||||
break;
|
||||
case 0x1b:
|
||||
mode = Mode.ESC;
|
||||
break;
|
||||
default:
|
||||
appendChar(c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected void processEsc(byte c) {
|
||||
switch (c) {
|
||||
case '[':
|
||||
mode = Mode.CSI;
|
||||
break;
|
||||
case ']':
|
||||
mode = Mode.OSC;
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError("Saw 'ESC " + c + "' at " + countIn);
|
||||
}
|
||||
}
|
||||
|
||||
protected void processCsi(byte c) {
|
||||
switch (c) {
|
||||
default:
|
||||
processCsiParamOrCommand(c);
|
||||
break;
|
||||
case '?':
|
||||
mode = Mode.CSI_Q;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected void processCsiParamOrCommand(byte c) {
|
||||
switch (c) {
|
||||
default:
|
||||
escBuf.put(c);
|
||||
break;
|
||||
case 'A':
|
||||
execCursorUp();
|
||||
mode = Mode.CHARS;
|
||||
break;
|
||||
case 'B':
|
||||
execCursorDown();
|
||||
mode = Mode.CHARS;
|
||||
break;
|
||||
case 'C':
|
||||
execCursorForward();
|
||||
mode = Mode.CHARS;
|
||||
break;
|
||||
case 'D':
|
||||
execCursorBackward();
|
||||
mode = Mode.CHARS;
|
||||
break;
|
||||
case 'H':
|
||||
execCursorPosition();
|
||||
mode = Mode.CHARS;
|
||||
break;
|
||||
case 'J':
|
||||
execEraseInDisplay();
|
||||
mode = Mode.CHARS;
|
||||
break;
|
||||
case 'K':
|
||||
execEraseInLine();
|
||||
mode = Mode.CHARS;
|
||||
break;
|
||||
case 'X':
|
||||
execEraseCharacter();
|
||||
mode = Mode.CHARS;
|
||||
break;
|
||||
case 'm':
|
||||
execSetGraphicsRendition();
|
||||
mode = Mode.CHARS;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected void processCsiQ(byte c) {
|
||||
String buf;
|
||||
switch (c) {
|
||||
default:
|
||||
escBuf.put(c);
|
||||
break;
|
||||
case 'h':
|
||||
buf = readAndClearEscBuf();
|
||||
if ("12".equals(buf)) {
|
||||
execTextCursorEnableBlinking();
|
||||
escBuf.clear();
|
||||
mode = Mode.CHARS;
|
||||
}
|
||||
else if ("25".equals(buf)) {
|
||||
execTextCursorEnableModeShow();
|
||||
escBuf.clear();
|
||||
mode = Mode.CHARS;
|
||||
}
|
||||
else {
|
||||
throw new AssertionError();
|
||||
}
|
||||
break;
|
||||
case 'l':
|
||||
buf = readAndClearEscBuf();
|
||||
if ("12".equals(buf)) {
|
||||
execTextCursorDisableBlinking();
|
||||
escBuf.clear();
|
||||
mode = Mode.CHARS;
|
||||
}
|
||||
else if ("25".equals(buf)) {
|
||||
execTextCursorDisableModeShow();
|
||||
escBuf.clear();
|
||||
mode = Mode.CHARS;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected void processOsc(byte c) {
|
||||
switch (c) {
|
||||
default:
|
||||
escBuf.put(c);
|
||||
break;
|
||||
case ';':
|
||||
if (Set.of("0", "2").contains(readAndClearEscBuf())) {
|
||||
mode = Mode.WINDOW_TITLE;
|
||||
escBuf.clear();
|
||||
break;
|
||||
}
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
protected void processWindowTitle(byte c) {
|
||||
switch (c) {
|
||||
default:
|
||||
titleBuf.put(c);
|
||||
break;
|
||||
case 0x07: // bell, even though MSDN says longer form preferred
|
||||
execSetWindowTitle();
|
||||
mode = Mode.CHARS;
|
||||
break;
|
||||
case 0x1b:
|
||||
mode = Mode.WINDOW_TITLE_ESC;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected void processWindowTitleEsc(byte c) {
|
||||
switch (c) {
|
||||
case '\\':
|
||||
execSetWindowTitle();
|
||||
mode = Mode.CHARS;
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError("Saw <ST> ... ESC " + c + " at " + countIn);
|
||||
}
|
||||
}
|
||||
|
||||
protected String readAndClear(ByteBuffer buf) {
|
||||
buf.flip();
|
||||
String result = new String(buf.array(), buf.position(), buf.remaining(), WINDOWS_1252);
|
||||
buf.clear();
|
||||
return result;
|
||||
}
|
||||
|
||||
protected String readAndClearEscBuf() {
|
||||
return readAndClear(escBuf);
|
||||
}
|
||||
|
||||
protected int parseNumericBuffer() {
|
||||
String numeric = readAndClearEscBuf();
|
||||
int result = Integer.parseInt(numeric);
|
||||
return result;
|
||||
}
|
||||
|
||||
protected int[] parseNumericListBuffer() {
|
||||
String numericList = readAndClearEscBuf();
|
||||
if (numericList.isEmpty()) {
|
||||
return new int[] {};
|
||||
}
|
||||
return Stream.of(numericList.split(";"))
|
||||
.mapToInt(Integer::parseInt)
|
||||
.toArray();
|
||||
}
|
||||
|
||||
protected void execCursorUp() {
|
||||
throw new UnsupportedOperationException("Cursor Up");
|
||||
}
|
||||
|
||||
protected void execCursorDown() {
|
||||
throw new UnsupportedOperationException("Cursor Down");
|
||||
}
|
||||
|
||||
protected void setPosition(int newPosition) {
|
||||
if (lineBuf.limit() < newPosition) {
|
||||
lineBuf.limit(newPosition);
|
||||
}
|
||||
lineBuf.position(newPosition);
|
||||
}
|
||||
|
||||
protected void execCursorForward() {
|
||||
int delta = parseNumericBuffer();
|
||||
setPosition(lineBuf.position() + delta);
|
||||
}
|
||||
|
||||
protected void execCursorBackward() {
|
||||
int delta = parseNumericBuffer();
|
||||
lineBuf.position(lineBuf.position() - delta);
|
||||
}
|
||||
|
||||
protected void execCursorPosition() {
|
||||
int[] yx = parseNumericListBuffer();
|
||||
if (yx.length == 0) {
|
||||
lineBuf.position(0);
|
||||
return;
|
||||
}
|
||||
if (yx.length != 2) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
if (yx[0] != 1) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
lineBuf.position(yx[1] - 1);
|
||||
}
|
||||
|
||||
protected void execTextCursorEnableBlinking() {
|
||||
// Don't care
|
||||
}
|
||||
|
||||
protected void execTextCursorDisableBlinking() {
|
||||
// Don't care
|
||||
}
|
||||
|
||||
protected void execTextCursorEnableModeShow() {
|
||||
// Don't care
|
||||
}
|
||||
|
||||
protected void execTextCursorDisableModeShow() {
|
||||
// Don't care
|
||||
}
|
||||
|
||||
protected void execEraseInDisplay() {
|
||||
// Because I have only one line, right?
|
||||
execEraseInLine();
|
||||
}
|
||||
|
||||
protected void execEraseInLine() {
|
||||
switch (parseNumericBuffer()) {
|
||||
case 0:
|
||||
Arrays.fill(lineBuf.array(), lineBuf.position(), lineBuf.capacity(), (byte) 0);
|
||||
break;
|
||||
case 1:
|
||||
Arrays.fill(lineBuf.array(), 0, lineBuf.position() + 1, (byte) 0);
|
||||
break;
|
||||
case 2:
|
||||
Arrays.fill(lineBuf.array(), (byte) 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected void execEraseCharacter() {
|
||||
int count = parseNumericBuffer();
|
||||
Arrays.fill(lineBuf.array(), lineBuf.position(), lineBuf.position() + count, (byte) ' ');
|
||||
}
|
||||
|
||||
protected void execSetGraphicsRendition() {
|
||||
// TODO: Maybe echo these or provide callbacks
|
||||
// Otherwise, don't care
|
||||
escBuf.clear();
|
||||
}
|
||||
|
||||
protected void execSetWindowTitle() {
|
||||
// Msg.info(this, "Title: " + readAndClear(titleBuf));
|
||||
// TODO: Maybe a callback. Otherwise, don't care
|
||||
titleBuf.clear();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
/* ###
|
||||
* 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 agent.gdb.pty.windows;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import com.sun.jna.platform.win32.Kernel32;
|
||||
import com.sun.jna.platform.win32.WinDef.DWORD;
|
||||
import com.sun.jna.platform.win32.WinNT.HANDLEByReference;
|
||||
import com.sun.jna.platform.win32.COM.COMUtils;
|
||||
|
||||
import agent.gdb.pty.*;
|
||||
import agent.gdb.pty.windows.jna.ConsoleApiNative;
|
||||
import agent.gdb.pty.windows.jna.ConsoleApiNative.COORD;
|
||||
|
||||
public class ConPty implements Pty {
|
||||
static final DWORD DW_ZERO = new DWORD(0);
|
||||
static final DWORD DW_ONE = new DWORD(1);
|
||||
static final DWORD PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE = new DWORD(0x20016);
|
||||
static final DWORD EXTENDED_STARTUPINFO_PRESENT =
|
||||
new DWORD(Kernel32.EXTENDED_STARTUPINFO_PRESENT);
|
||||
private static final COORD SIZE = new COORD();
|
||||
static {
|
||||
SIZE.X = Short.MAX_VALUE;
|
||||
SIZE.Y = 1;
|
||||
}
|
||||
|
||||
private final Pipe pipeToChild;
|
||||
private final Pipe pipeFromChild;
|
||||
private final PseudoConsoleHandle pseudoConsoleHandle;
|
||||
private boolean closed = false;
|
||||
|
||||
private final ConPtyParent parent;
|
||||
private final ConPtyChild child;
|
||||
|
||||
public static ConPty openpty() {
|
||||
// Create communication channels
|
||||
|
||||
Pipe pipeToChild = Pipe.createPipe();
|
||||
Pipe pipeFromChild = Pipe.createPipe();
|
||||
|
||||
// Close the child-connected ends after creating the pseudoconsole
|
||||
// Keep the parent-connected ends, because we're the parent
|
||||
|
||||
HANDLEByReference lphPC = new HANDLEByReference();
|
||||
|
||||
COMUtils.checkRC(ConsoleApiNative.INSTANCE.CreatePseudoConsole(
|
||||
SIZE,
|
||||
pipeToChild.getReadHandle().getNative(),
|
||||
pipeFromChild.getWriteHandle().getNative(),
|
||||
DW_ZERO,
|
||||
lphPC));
|
||||
|
||||
return new ConPty(pipeToChild, pipeFromChild, new PseudoConsoleHandle(lphPC.getValue()));
|
||||
}
|
||||
|
||||
public ConPty(Pipe pipeToChild, Pipe pipeFromChild, PseudoConsoleHandle pseudoConsoleHandle) {
|
||||
this.pipeToChild = pipeToChild;
|
||||
this.pipeFromChild = pipeFromChild;
|
||||
this.pseudoConsoleHandle = pseudoConsoleHandle;
|
||||
|
||||
// TODO: See if this can all be combined with named pipes.
|
||||
// Would be nice if that's sufficient to support new-ui
|
||||
|
||||
this.parent = new ConPtyParent(pipeToChild.getWriteHandle(), pipeFromChild.getReadHandle());
|
||||
this.child = new ConPtyChild(pipeFromChild.getWriteHandle(), pipeToChild.getReadHandle(),
|
||||
pseudoConsoleHandle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PtyParent getParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PtyChild getChild() {
|
||||
return child;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void close() throws IOException {
|
||||
if (closed) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
pseudoConsoleHandle.close();
|
||||
pipeToChild.close();
|
||||
pipeFromChild.close();
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw e;
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
closed = true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
/* ###
|
||||
* 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 agent.gdb.pty.windows;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
import com.sun.jna.*;
|
||||
import com.sun.jna.platform.win32.Kernel32;
|
||||
import com.sun.jna.platform.win32.WinBase;
|
||||
import com.sun.jna.platform.win32.WinBase.PROCESS_INFORMATION;
|
||||
import com.sun.jna.platform.win32.WinDef.*;
|
||||
import com.sun.jna.platform.win32.WinNT.HANDLE;
|
||||
|
||||
import agent.gdb.pty.PtyChild;
|
||||
import agent.gdb.pty.local.LocalWindowsNativeProcessPtySession;
|
||||
import agent.gdb.pty.windows.jna.ConsoleApiNative;
|
||||
import agent.gdb.pty.windows.jna.ConsoleApiNative.STARTUPINFOEX;
|
||||
import ghidra.dbg.util.ShellUtils;
|
||||
|
||||
public class ConPtyChild extends ConPtyEndpoint implements PtyChild {
|
||||
private final Handle pseudoConsoleHandle;
|
||||
|
||||
public ConPtyChild(Handle writeHandle, Handle readHandle, Handle pseudoConsoleHandle) {
|
||||
super(writeHandle, readHandle);
|
||||
this.pseudoConsoleHandle = pseudoConsoleHandle;
|
||||
}
|
||||
|
||||
protected STARTUPINFOEX prepareStartupInfo() {
|
||||
STARTUPINFOEX si = new STARTUPINFOEX();
|
||||
si.StartupInfo.cb = new DWORD(si.size());
|
||||
si.StartupInfo.hStdOutput = new HANDLE();
|
||||
si.StartupInfo.hStdError = new HANDLE();
|
||||
si.StartupInfo.hStdInput = new HANDLE();
|
||||
si.StartupInfo.dwFlags = WinBase.STARTF_USESTDHANDLES;
|
||||
|
||||
// Discover the size required for the thread attrs list and allocate
|
||||
UINTByReference bytesRequired = new UINTByReference();
|
||||
// NB. This will "fail." See Remarks on MSDN.
|
||||
ConsoleApiNative.INSTANCE.InitializeProcThreadAttributeList(
|
||||
null, ConPty.DW_ONE, ConPty.DW_ZERO, bytesRequired);
|
||||
// NB. Memory frees itself in .finalize()
|
||||
si.lpAttributeList = new Memory(bytesRequired.getValue().intValue());
|
||||
// Initialize it
|
||||
if (!ConsoleApiNative.INSTANCE.InitializeProcThreadAttributeList(
|
||||
si.lpAttributeList, ConPty.DW_ONE, ConPty.DW_ZERO, bytesRequired)
|
||||
.booleanValue()) {
|
||||
throw new LastErrorException(Kernel32.INSTANCE.GetLastError());
|
||||
}
|
||||
|
||||
// Set the pseudoconsole information into the list
|
||||
if (!ConsoleApiNative.INSTANCE.UpdateProcThreadAttribute(
|
||||
si.lpAttributeList, ConPty.DW_ZERO,
|
||||
ConPty.PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE,
|
||||
new PVOID(pseudoConsoleHandle.getNative().getPointer()),
|
||||
new DWORD(Native.POINTER_SIZE),
|
||||
null, null).booleanValue()) {
|
||||
throw new LastErrorException(Kernel32.INSTANCE.GetLastError());
|
||||
}
|
||||
|
||||
return si;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalWindowsNativeProcessPtySession session(String[] args, Map<String, String> env)
|
||||
throws IOException {
|
||||
/**
|
||||
* TODO: How to incorporate environment into CreateProcess?
|
||||
*/
|
||||
|
||||
STARTUPINFOEX si = prepareStartupInfo();
|
||||
PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
|
||||
|
||||
if (!ConsoleApiNative.INSTANCE.CreateProcessW(
|
||||
null /*lpApplicationName*/,
|
||||
new WString(ShellUtils.generateLine(Arrays.asList(args))),
|
||||
null /*lpProcessAttributes*/,
|
||||
null /*lpThreadAttributes*/,
|
||||
false /*bInheritHandles*/,
|
||||
ConPty.EXTENDED_STARTUPINFO_PRESENT /*dwCreationFlags*/,
|
||||
null /*lpEnvironment*/,
|
||||
null /*lpCurrentDirectory*/,
|
||||
si /*lpStartupInfo*/,
|
||||
pi /*lpProcessInformation*/).booleanValue()) {
|
||||
throw new LastErrorException(Kernel32.INSTANCE.GetLastError());
|
||||
}
|
||||
|
||||
return new LocalWindowsNativeProcessPtySession(pi.dwProcessId.intValue(),
|
||||
pi.dwThreadId.intValue(),
|
||||
new Handle(pi.hProcess), new Handle(pi.hThread));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String nullSession() throws IOException {
|
||||
throw new UnsupportedOperationException("ConPTY does not have a name");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
/* ###
|
||||
* 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 agent.gdb.pty.windows;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import agent.gdb.pty.PtyEndpoint;
|
||||
|
||||
public class ConPtyEndpoint implements PtyEndpoint {
|
||||
protected InputStream inputStream;
|
||||
protected OutputStream outputStream;
|
||||
|
||||
public ConPtyEndpoint(Handle writeHandle, Handle readHandle) {
|
||||
this.inputStream = new HandleInputStream(readHandle);
|
||||
this.outputStream = new HandleOutputStream(writeHandle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream getOutputStream() {
|
||||
return outputStream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() {
|
||||
return inputStream;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/* ###
|
||||
* 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 agent.gdb.pty.windows;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import agent.gdb.pty.Pty;
|
||||
import agent.gdb.pty.PtyFactory;
|
||||
|
||||
public class ConPtyFactory implements PtyFactory {
|
||||
@Override
|
||||
public Pty openpty() throws IOException {
|
||||
return ConPty.openpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "local (Windows)";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
/* ###
|
||||
* 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 agent.gdb.pty.windows;
|
||||
|
||||
import agent.gdb.pty.PtyParent;
|
||||
|
||||
public class ConPtyParent extends ConPtyEndpoint implements PtyParent {
|
||||
public ConPtyParent(Handle writeHandle, Handle readHandle) {
|
||||
super(writeHandle, readHandle);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
/* ###
|
||||
* 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 agent.gdb.pty.windows;
|
||||
|
||||
import java.lang.ref.Cleaner;
|
||||
|
||||
import com.sun.jna.LastErrorException;
|
||||
import com.sun.jna.platform.win32.Kernel32;
|
||||
import com.sun.jna.platform.win32.WinNT.HANDLE;
|
||||
|
||||
public class Handle implements AutoCloseable {
|
||||
private static final Cleaner CLEANER = Cleaner.create();
|
||||
|
||||
protected static class State implements Runnable {
|
||||
protected final HANDLE handle;
|
||||
|
||||
protected State(HANDLE handle) {
|
||||
this.handle = handle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (!Kernel32.INSTANCE.CloseHandle(handle)) {
|
||||
throw new LastErrorException(Kernel32.INSTANCE.GetLastError());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final State state;
|
||||
private final Cleaner.Cleanable cleanable;
|
||||
|
||||
public Handle(HANDLE handle) {
|
||||
this.state = newState(handle);
|
||||
this.cleanable = CLEANER.register(this, state);
|
||||
}
|
||||
|
||||
protected State newState(HANDLE handle) {
|
||||
return new State(handle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
cleanable.clean();
|
||||
}
|
||||
|
||||
public HANDLE getNative() {
|
||||
HANDLE handle = state.handle;
|
||||
if (handle == null) {
|
||||
throw new IllegalStateException("This handle is no longer valid");
|
||||
}
|
||||
return handle;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
/* ###
|
||||
* 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 agent.gdb.pty.windows;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import com.sun.jna.LastErrorException;
|
||||
import com.sun.jna.platform.win32.Kernel32;
|
||||
import com.sun.jna.ptr.IntByReference;
|
||||
|
||||
public class HandleInputStream extends InputStream {
|
||||
private final Handle handle;
|
||||
private boolean closed = false;
|
||||
|
||||
HandleInputStream(Handle handle) {
|
||||
this.handle = handle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized int read() throws IOException {
|
||||
if (closed) {
|
||||
throw new IOException("Stream closed");
|
||||
}
|
||||
byte[] buf = new byte[1];
|
||||
if (0 == read(buf)) {
|
||||
return -1;
|
||||
}
|
||||
return buf[0] & 0x0FF;
|
||||
}
|
||||
|
||||
protected void waitPipeConnected() {
|
||||
if (Kernel32.INSTANCE.ConnectNamedPipe(handle.getNative(), null)) {
|
||||
return; // We waited, and now we're connected
|
||||
}
|
||||
int error = Kernel32.INSTANCE.GetLastError();
|
||||
if (error == Kernel32.ERROR_PIPE_CONNECTED) {
|
||||
return; // We got the connection before we waited. OK
|
||||
}
|
||||
throw new LastErrorException(error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized int read(byte[] b) throws IOException {
|
||||
if (closed) {
|
||||
throw new IOException("Stream closed");
|
||||
}
|
||||
IntByReference dwRead = new IntByReference();
|
||||
|
||||
while (!Kernel32.INSTANCE.ReadFile(handle.getNative(), b, b.length, dwRead, null)) {
|
||||
int error = Kernel32.INSTANCE.GetLastError();
|
||||
switch (error) {
|
||||
case Kernel32.ERROR_BROKEN_PIPE:
|
||||
return -1;
|
||||
case Kernel32.ERROR_PIPE_LISTENING:
|
||||
/**
|
||||
* Well, we know we're dealing with a listening pipe, now. Wait for a client,
|
||||
* then try reading again.
|
||||
*/
|
||||
waitPipeConnected();
|
||||
continue;
|
||||
}
|
||||
throw new IOException("Could not read",
|
||||
new LastErrorException(error));
|
||||
}
|
||||
return dwRead.getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized int read(byte[] b, int off, int len) throws IOException {
|
||||
byte[] temp = new byte[len];
|
||||
int read = read(temp);
|
||||
System.arraycopy(temp, 0, b, off, read);
|
||||
return read;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void close() throws IOException {
|
||||
closed = true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
/* ###
|
||||
* 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 agent.gdb.pty.windows;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Arrays;
|
||||
|
||||
import com.sun.jna.LastErrorException;
|
||||
import com.sun.jna.platform.win32.Kernel32;
|
||||
import com.sun.jna.ptr.IntByReference;
|
||||
|
||||
public class HandleOutputStream extends OutputStream {
|
||||
private final Handle handle;
|
||||
private boolean closed = false;
|
||||
|
||||
public HandleOutputStream(Handle handle) {
|
||||
this.handle = handle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void write(int b) throws IOException {
|
||||
write(new byte[] { (byte) b });
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void write(byte[] b) throws IOException {
|
||||
if (closed) {
|
||||
throw new IOException("Stream closed");
|
||||
}
|
||||
|
||||
int total = 0;
|
||||
do {
|
||||
IntByReference dwWritten = new IntByReference();
|
||||
if (!Kernel32.INSTANCE.WriteFile(handle.getNative(), b, b.length, dwWritten, null)) {
|
||||
throw new IOException("Could not write",
|
||||
new LastErrorException(Kernel32.INSTANCE.GetLastError()));
|
||||
}
|
||||
total += dwWritten.getValue();
|
||||
}
|
||||
while (total < b.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void write(byte[] b, int off, int len) throws IOException {
|
||||
// Abstraction of Windows API given by JNA doesn't have offset
|
||||
// In C, could add offset to lpBuffer, but not here :(
|
||||
write(Arrays.copyOfRange(b, off, off + len));
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void close() throws IOException {
|
||||
closed = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether this handle has buffered output
|
||||
*
|
||||
* <p>
|
||||
* Windows can get touchy when trying to flush handles that are not actually buffered. If the
|
||||
* wrapped handle is not buffered, then this method must return false, otherwise, any attempt to
|
||||
* flush this stream will result in {@code ERROR_INVALID_HANDLE}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
protected boolean isBuffered() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() throws IOException {
|
||||
if (!isBuffered()) {
|
||||
return;
|
||||
}
|
||||
if (!(Kernel32.INSTANCE.FlushFileBuffers(handle.getNative()))) {
|
||||
throw new LastErrorException(Kernel32.INSTANCE.GetLastError());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 agent.gdb.pty.windows;
|
||||
|
||||
import com.sun.jna.LastErrorException;
|
||||
import com.sun.jna.platform.win32.Kernel32;
|
||||
import com.sun.jna.platform.win32.WinBase.SECURITY_ATTRIBUTES;
|
||||
import com.sun.jna.platform.win32.WinNT.HANDLEByReference;
|
||||
|
||||
public class Pipe {
|
||||
public static Pipe createPipe() {
|
||||
HANDLEByReference pRead = new HANDLEByReference();
|
||||
HANDLEByReference pWrite = new HANDLEByReference();
|
||||
|
||||
if (!Kernel32.INSTANCE.CreatePipe(pRead, pWrite, new SECURITY_ATTRIBUTES(), 0)) {
|
||||
throw new LastErrorException(Kernel32.INSTANCE.GetLastError());
|
||||
}
|
||||
return new Pipe(new Handle(pRead.getValue()), new Handle(pWrite.getValue()));
|
||||
}
|
||||
|
||||
private final Handle readHandle;
|
||||
private final Handle writeHandle;
|
||||
|
||||
private Pipe(Handle read, Handle write) {
|
||||
this.readHandle = read;
|
||||
this.writeHandle = write;
|
||||
}
|
||||
|
||||
public Handle getReadHandle() {
|
||||
return readHandle;
|
||||
}
|
||||
|
||||
public Handle getWriteHandle() {
|
||||
return writeHandle;
|
||||
}
|
||||
|
||||
public void close() throws Exception {
|
||||
writeHandle.close();
|
||||
readHandle.close();
|
||||
}
|
||||
}
|
|
@ -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 agent.gdb.pty.windows;
|
||||
|
||||
import com.sun.jna.platform.win32.WinNT.HANDLE;
|
||||
|
||||
import agent.gdb.pty.windows.jna.ConsoleApiNative;
|
||||
|
||||
public class PseudoConsoleHandle extends Handle {
|
||||
|
||||
protected static class PseudoConsoleState extends State {
|
||||
public PseudoConsoleState(HANDLE handle) {
|
||||
super(handle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
ConsoleApiNative.INSTANCE.ClosePseudoConsole(handle);
|
||||
}
|
||||
}
|
||||
|
||||
public PseudoConsoleHandle(HANDLE handle) {
|
||||
super(handle);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected State newState(HANDLE handle) {
|
||||
return new PseudoConsoleState(handle);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,160 @@
|
|||
/* ###
|
||||
* 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 agent.gdb.pty.windows.jna;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.sun.jna.*;
|
||||
import com.sun.jna.platform.win32.WinBase;
|
||||
import com.sun.jna.platform.win32.WinDef.*;
|
||||
import com.sun.jna.platform.win32.WinNT.*;
|
||||
import com.sun.jna.win32.StdCallLibrary;
|
||||
|
||||
public interface ConsoleApiNative extends StdCallLibrary {
|
||||
ConsoleApiNative INSTANCE = Native.load("Kernel32.dll", ConsoleApiNative.class);
|
||||
BOOL FAIL = new BOOL(false);
|
||||
|
||||
BOOL CreatePipe(HANDLEByReference hReadPipe, HANDLEByReference hWritePipe,
|
||||
SECURITY_ATTRIBUTES.ByReference lpPipeAttributes, DWORD nSize);
|
||||
|
||||
HRESULT CreatePseudoConsole(COORD.ByValue size, HANDLE hInput, HANDLE hOutput,
|
||||
DWORD dwFlags,
|
||||
HANDLEByReference phPC);
|
||||
|
||||
void ClosePseudoConsole(HANDLE hPC);
|
||||
|
||||
BOOL InitializeProcThreadAttributeList(Pointer lpAttributeList,
|
||||
DWORD dwAttributeCount, DWORD dwFlags, UINTByReference lpSize);
|
||||
|
||||
BOOL UpdateProcThreadAttribute(
|
||||
Pointer lpAttributeList,
|
||||
DWORD dwFlags,
|
||||
DWORD Attribute,
|
||||
PVOID lpValue,
|
||||
DWORD cbSize,
|
||||
PVOID lpPreviousValue,
|
||||
ULONGLONGByReference lpReturnSize);
|
||||
|
||||
BOOL CreateProcessW(
|
||||
WString lpApplicationName,
|
||||
WString lpCommandLine,
|
||||
WinBase.SECURITY_ATTRIBUTES lpProcessAttributes,
|
||||
WinBase.SECURITY_ATTRIBUTES lpThreadAttributes,
|
||||
boolean bInheritHandles,
|
||||
DWORD dwCreationFlags,
|
||||
Pointer lpEnvironment,
|
||||
WString lpCurrentDirectory,
|
||||
STARTUPINFOEX lpStartupInfo,
|
||||
WinBase.PROCESS_INFORMATION lpProcessInformation);
|
||||
|
||||
/*
|
||||
BOOL GetConsoleMode(
|
||||
HANDLE hConsoleMode,
|
||||
DWORDByReference dwMode);
|
||||
|
||||
BOOL CreateProcessWithTokenW(
|
||||
HANDLE hToken,
|
||||
DWORD dwLogonFlags,
|
||||
WString lpApplicationName,
|
||||
WString lpCommandLine,
|
||||
DWORD dwCreationFlags,
|
||||
Pointer lpEnvironment,
|
||||
WString lpCurrentDirectory,
|
||||
STARTUPINFOEX lpStartupInfo,
|
||||
WinBase.PROCESS_INFORMATION lpProcessInformation);
|
||||
|
||||
BOOL LogonUserW(
|
||||
WString lpUsername,
|
||||
WString lpDomain,
|
||||
WString lpPassword,
|
||||
DWORD dwLogonType,
|
||||
DWORD dwLogonProvider,
|
||||
HANDLEByReference phToken);
|
||||
*/
|
||||
|
||||
public static class COORD extends Structure implements Structure.ByValue {
|
||||
public static class ByReference extends COORD
|
||||
implements Structure.ByReference {
|
||||
}
|
||||
|
||||
public static final List<String> FIELDS = createFieldsOrder("X", "Y");
|
||||
|
||||
public short X;
|
||||
public short Y;
|
||||
|
||||
@Override
|
||||
protected List<String> getFieldOrder() {
|
||||
return FIELDS;
|
||||
}
|
||||
}
|
||||
|
||||
public static class SECURITY_ATTRIBUTES extends Structure {
|
||||
public static class ByReference extends SECURITY_ATTRIBUTES
|
||||
implements Structure.ByReference {
|
||||
}
|
||||
|
||||
public static final List<String> FIELDS = createFieldsOrder(
|
||||
"nLength", "lpSecurityDescriptor", "bInheritedHandle");
|
||||
|
||||
public DWORD nLength;
|
||||
public ULONGLONG lpSecurityDescriptor;
|
||||
public BOOL bInheritedHandle;
|
||||
|
||||
@Override
|
||||
protected List<String> getFieldOrder() {
|
||||
return FIELDS;
|
||||
}
|
||||
}
|
||||
|
||||
public static class PROC_THREAD_ATTRIBUTE_LIST extends Structure {
|
||||
public static class ByReference extends PROC_THREAD_ATTRIBUTE_LIST
|
||||
implements Structure.ByReference {
|
||||
}
|
||||
|
||||
public static final List<String> FIELDS = createFieldsOrder(
|
||||
"dwFlags", "Size", "Count", "Reserved", "Unknown");
|
||||
|
||||
public DWORD dwFlags;
|
||||
public ULONG Size;
|
||||
public ULONG Count;
|
||||
public ULONG Reserved;
|
||||
public ULONGLONG Unknown;
|
||||
//public PROC_THREAD_ATTRIBUTE_ENTRY Entries[0];
|
||||
|
||||
@Override
|
||||
protected List<String> getFieldOrder() {
|
||||
return FIELDS;
|
||||
}
|
||||
}
|
||||
|
||||
public static class STARTUPINFOEX extends Structure {
|
||||
public static class ByReference extends STARTUPINFOEX
|
||||
implements Structure.ByReference {
|
||||
}
|
||||
|
||||
public static final List<String> FIELDS = createFieldsOrder(
|
||||
"StartupInfo", "lpAttributeList");
|
||||
|
||||
public WinBase.STARTUPINFO StartupInfo;
|
||||
public Pointer lpAttributeList;
|
||||
|
||||
@Override
|
||||
protected List<String> getFieldOrder() {
|
||||
return FIELDS;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -264,7 +264,7 @@ public abstract class AbstractGdbManagerTest extends AbstractGhidraHeadlessInteg
|
|||
waitOn(libcLoaded);
|
||||
Thread.sleep(100); // TODO: Why?
|
||||
Msg.debug(this, "Interrupting");
|
||||
waitOn(mgr.interrupt());
|
||||
mgr.sendInterruptNow();
|
||||
waitOn(mgr.waitForState(GdbState.STOPPED));
|
||||
assertResponsive(mgr);
|
||||
}
|
||||
|
@ -285,7 +285,7 @@ public abstract class AbstractGdbManagerTest extends AbstractGhidraHeadlessInteg
|
|||
waitOn(libcLoaded);
|
||||
Thread.sleep(100); // TODO: Why?
|
||||
Msg.debug(this, "Interrupting");
|
||||
waitOn(mgr.interrupt());
|
||||
mgr.sendInterruptNow();
|
||||
Msg.debug(this, "Verifying at syscall");
|
||||
String out = waitOn(mgr.consoleCapture("x/1i $pc-2"));
|
||||
// TODO: This is x86-specific
|
||||
|
@ -296,7 +296,7 @@ public abstract class AbstractGdbManagerTest extends AbstractGhidraHeadlessInteg
|
|||
CompletableFuture<Void> stopped = mgr.waitForState(GdbState.STOPPED);
|
||||
Thread.sleep(100); // NB: Not exactly reliable, but verify we're waiting
|
||||
assertFalse(stopped.isDone());
|
||||
waitOn(mgr.interrupt());
|
||||
mgr.sendInterruptNow();
|
||||
waitOn(stopped);
|
||||
assertResponsive(mgr);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
/* ###
|
||||
* 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 agent.gdb.manager.impl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.junit.Ignore;
|
||||
|
||||
import agent.gdb.manager.GdbManager;
|
||||
import agent.gdb.pty.PtyFactory;
|
||||
import agent.gdb.pty.windows.ConPtyFactory;
|
||||
|
||||
@Ignore("Need compatible version on CI")
|
||||
public class SpawnedWindowsMi2GdbManagerTest extends AbstractGdbManagerTest {
|
||||
@Override
|
||||
protected CompletableFuture<Void> startManager(GdbManager manager) {
|
||||
try {
|
||||
manager.start("C:\\msys64\\mingw64\\bin\\gdb.exe", "-i", "mi2");
|
||||
return manager.runRC();
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PtyFactory getPtyFactory() {
|
||||
// TODO: Choose by host OS
|
||||
return new ConPtyFactory();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
/* ###
|
||||
* 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 agent.gdb.pty;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
public class AbstractPtyTest {
|
||||
public Thread pump(InputStream is, OutputStream os) {
|
||||
Thread t = new Thread(() -> {
|
||||
byte[] buf = new byte[1];
|
||||
while (true) {
|
||||
int len;
|
||||
try {
|
||||
len = is.read(buf);
|
||||
if (len == -1) {
|
||||
return;
|
||||
}
|
||||
os.write(buf, 0, len);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
t.setDaemon(true);
|
||||
t.start();
|
||||
return t;
|
||||
}
|
||||
|
||||
public BufferedReader loggingReader(InputStream is) {
|
||||
return new BufferedReader(new InputStreamReader(is)) {
|
||||
@Override
|
||||
public String readLine() throws IOException {
|
||||
String line = super.readLine();
|
||||
System.out.println("log: " + line);
|
||||
return line;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public Thread runExitCheck(int expected, PtySession session) {
|
||||
Thread exitCheck = new Thread(() -> {
|
||||
while (true) {
|
||||
try {
|
||||
assertEquals("Early exit with wrong code", expected,
|
||||
session.waitExited());
|
||||
return;
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
System.err.println("Exit check interrupted");
|
||||
}
|
||||
}
|
||||
});
|
||||
exitCheck.setDaemon(true);
|
||||
exitCheck.start();
|
||||
return exitCheck;
|
||||
}
|
||||
}
|
|
@ -17,16 +17,25 @@ package agent.gdb.pty.linux;
|
|||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assume.assumeTrue;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import agent.gdb.pty.AbstractPtyTest;
|
||||
import agent.gdb.pty.PtySession;
|
||||
import ghidra.dbg.testutil.DummyProc;
|
||||
import ghidra.framework.OperatingSystem;
|
||||
|
||||
public class LinuxPtyTest extends AbstractPtyTest {
|
||||
@Before
|
||||
public void checkLinux() {
|
||||
assumeTrue(OperatingSystem.LINUX == OperatingSystem.CURRENT_OPERATING_SYSTEM);
|
||||
}
|
||||
|
||||
public class LinuxPtyTest {
|
||||
@Test
|
||||
public void testOpenClosePty() throws IOException {
|
||||
LinuxPty pty = LinuxPty.openpty();
|
||||
|
@ -82,54 +91,6 @@ public class LinuxPtyTest {
|
|||
}
|
||||
}
|
||||
|
||||
public Thread pump(InputStream is, OutputStream os) {
|
||||
Thread t = new Thread(() -> {
|
||||
byte[] buf = new byte[1024];
|
||||
while (true) {
|
||||
int len;
|
||||
try {
|
||||
len = is.read(buf);
|
||||
os.write(buf, 0, len);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
t.setDaemon(true);
|
||||
t.start();
|
||||
return t;
|
||||
}
|
||||
|
||||
public BufferedReader loggingReader(InputStream is) {
|
||||
return new BufferedReader(new InputStreamReader(is)) {
|
||||
@Override
|
||||
public String readLine() throws IOException {
|
||||
String line = super.readLine();
|
||||
System.out.println("log: " + line);
|
||||
return line;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public Thread runExitCheck(int expected, PtySession session) {
|
||||
Thread exitCheck = new Thread(() -> {
|
||||
while (true) {
|
||||
try {
|
||||
assertEquals("Early exit with wrong code", expected,
|
||||
session.waitExited());
|
||||
return;
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
System.err.println("Exit check interrupted");
|
||||
}
|
||||
}
|
||||
});
|
||||
exitCheck.setDaemon(true);
|
||||
exitCheck.start();
|
||||
return exitCheck;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSessionBashEchoTest() throws IOException, InterruptedException {
|
||||
Map<String, String> env = new HashMap<>();
|
||||
|
|
|
@ -0,0 +1,194 @@
|
|||
/* ###
|
||||
* 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 agent.gdb.pty.windows;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.junit.Assume.assumeTrue;
|
||||
|
||||
import java.io.*;
|
||||
import java.lang.ProcessBuilder.Redirect;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.sun.jna.LastErrorException;
|
||||
|
||||
import agent.gdb.pty.*;
|
||||
import ghidra.dbg.testutil.DummyProc;
|
||||
import ghidra.framework.OperatingSystem;
|
||||
|
||||
public class ConPtyTest extends AbstractPtyTest {
|
||||
|
||||
@Before
|
||||
public void checkWindows() {
|
||||
assumeTrue(OperatingSystem.WINDOWS == OperatingSystem.CURRENT_OPERATING_SYSTEM);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSessionCmd() throws IOException, InterruptedException {
|
||||
try (Pty pty = ConPty.openpty()) {
|
||||
PtySession cmd = pty.getChild().session(new String[] { DummyProc.which("cmd") }, null);
|
||||
pty.getParent().getOutputStream().write("exit\r\n".getBytes());
|
||||
assertEquals(0, cmd.waitExited());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSessionNonExistent() throws IOException, InterruptedException {
|
||||
try (Pty pty = ConPty.openpty()) {
|
||||
pty.getChild().session(new String[] { "thisHadBetterNoExist" }, null);
|
||||
fail();
|
||||
}
|
||||
catch (LastErrorException e) {
|
||||
assertEquals(2, e.getErrorCode());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSessionCmdEchoTest() throws IOException, InterruptedException {
|
||||
try (Pty pty = ConPty.openpty()) {
|
||||
PtyParent parent = pty.getParent();
|
||||
PrintWriter writer = new PrintWriter(parent.getOutputStream());
|
||||
BufferedReader reader = loggingReader(parent.getInputStream());
|
||||
PtySession cmd = pty.getChild().session(new String[] { DummyProc.which("cmd") }, null);
|
||||
runExitCheck(3, cmd);
|
||||
|
||||
writer.println("echo test");
|
||||
writer.flush();
|
||||
String line;
|
||||
do {
|
||||
line = reader.readLine();
|
||||
}
|
||||
while (!"test".equals(line));
|
||||
|
||||
writer.println("exit 3");
|
||||
writer.flush();
|
||||
|
||||
assertEquals(3, cmd.waitExited());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSessionGdbLineLength() throws IOException, InterruptedException {
|
||||
try (Pty pty = ConPty.openpty()) {
|
||||
PtyParent parent = pty.getParent();
|
||||
PrintWriter writer = new PrintWriter(parent.getOutputStream());
|
||||
BufferedReader reader = loggingReader(parent.getInputStream());
|
||||
PtySession gdb =
|
||||
pty.getChild().session(new String[] { "C:\\msys64\\mingw64\\bin\\gdb.exe" }, null);
|
||||
|
||||
writer.println(
|
||||
"echo This line is cleary much, much, much, much, much, much, much, much, much " +
|
||||
" longer than 80 characters");
|
||||
writer.flush();
|
||||
String line;
|
||||
do {
|
||||
line = reader.readLine();
|
||||
}
|
||||
while (!"test".equals(line));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGdbInterruptPlain() throws Exception {
|
||||
ProcessBuilder builder = new ProcessBuilder("C:\\msys64\\mingw64\\bin\\gdb.exe");
|
||||
builder.redirectOutput(Redirect.PIPE);
|
||||
builder.redirectInput(Redirect.PIPE);
|
||||
builder.redirectErrorStream(true);
|
||||
|
||||
Process gdb = builder.start();
|
||||
|
||||
PrintWriter writer = new PrintWriter(gdb.getOutputStream());
|
||||
pump(gdb.getInputStream(), System.err);
|
||||
|
||||
System.out.println("Testing");
|
||||
writer.println("echo test");
|
||||
writer.println("set new-console on");
|
||||
System.out.println("Launching notepad");
|
||||
writer.println("file C:\\\\Windows\\\\notepad.exe");
|
||||
writer.println("run");
|
||||
writer.flush();
|
||||
System.out.println("Waiting");
|
||||
Thread.sleep(3000);
|
||||
System.out.println("Interrupting");
|
||||
writer.write(3);
|
||||
writer.println();
|
||||
writer.flush();
|
||||
System.out.println("Killing");
|
||||
writer.println("kill");
|
||||
writer.flush();
|
||||
writer.println("y");
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGdbInterruptConPty() throws Exception {
|
||||
try (Pty pty = ConPty.openpty()) {
|
||||
PtyParent parent = pty.getParent();
|
||||
PrintWriter writer = new PrintWriter(parent.getOutputStream());
|
||||
//BufferedReader reader = loggingReader(parent.getInputStream());
|
||||
PtySession gdb =
|
||||
pty.getChild().session(new String[] { "C:\\msys64\\mingw64\\bin\\gdb.exe" }, null);
|
||||
|
||||
pump(parent.getInputStream(), System.err);
|
||||
|
||||
System.out.println("Testing");
|
||||
writer.println("echo test");
|
||||
writer.println("set new-console on");
|
||||
System.out.println("Launching notepad");
|
||||
writer.println("file C:\\\\Windows\\\\notepad.exe");
|
||||
writer.println("run");
|
||||
writer.flush();
|
||||
System.out.println("Waiting");
|
||||
Thread.sleep(3000);
|
||||
System.out.println("Interrupting");
|
||||
writer.write(3);
|
||||
writer.println();
|
||||
writer.flush();
|
||||
System.out.println("Killing");
|
||||
writer.println("kill");
|
||||
writer.flush();
|
||||
writer.println("y");
|
||||
writer.flush();
|
||||
|
||||
Thread.sleep(100000);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGdbMiConPty() throws Exception {
|
||||
try (Pty pty = ConPty.openpty()) {
|
||||
PtyParent parent = pty.getParent();
|
||||
PrintWriter writer = new PrintWriter(parent.getOutputStream());
|
||||
//BufferedReader reader = loggingReader(parent.getInputStream());
|
||||
PtySession gdb = pty.getChild()
|
||||
.session(new String[] { "C:\\msys64\\mingw64\\bin\\gdb.exe", "-i", "mi2" },
|
||||
null);
|
||||
|
||||
InputStream inputStream = parent.getInputStream();
|
||||
inputStream = new AnsiBufferedInputStream(inputStream);
|
||||
pump(inputStream, System.out);
|
||||
|
||||
writer.println("-interpreter-exec console \"echo test\"");
|
||||
writer.println("-interpreter-exec console \"quit\"");
|
||||
writer.flush();
|
||||
|
||||
gdb.waitExited();
|
||||
//System.out.println("Exited");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
/* ###
|
||||
* 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 agent.gdb.pty.windows;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
import com.sun.jna.LastErrorException;
|
||||
import com.sun.jna.platform.win32.Kernel32;
|
||||
import com.sun.jna.platform.win32.WinNT.HANDLE;
|
||||
|
||||
public class NamedPipeTest {
|
||||
|
||||
protected Handle checkHandle(HANDLE handle) {
|
||||
if (Kernel32.INVALID_HANDLE_VALUE.equals(handle)) {
|
||||
throw new LastErrorException(Kernel32.INSTANCE.GetLastError());
|
||||
}
|
||||
return new Handle(handle);
|
||||
}
|
||||
|
||||
// @Test
|
||||
/**
|
||||
* Experiment with MinGW GDB, named pipes, and {@code new-ui}.
|
||||
*
|
||||
* <p>
|
||||
* Run this test, start GDB in a command shell, then issue
|
||||
* "{@code new-ui mi2 \\\\.\\pipe\\GhidraGDB}". With GDB 11.1, GDB will print "New UI allocated"
|
||||
* to the console, and I'll receive {@code =thread-group-added,id="i1"} on the pipe. However,
|
||||
* GDB never seems to process my {@code -add-inferior} command. Furthermore, GDB freezes and no
|
||||
* longer accepts input on the console, either.
|
||||
*/
|
||||
public void testExpNamedPipes() throws Exception {
|
||||
Handle hPipe = checkHandle(Kernel32.INSTANCE.CreateNamedPipe(
|
||||
"\\\\.\\pipe\\GhidraGDB" /*lpName*/,
|
||||
Kernel32.PIPE_ACCESS_DUPLEX /*dwOpenMode*/,
|
||||
Kernel32.PIPE_TYPE_BYTE | Kernel32.PIPE_WAIT /*dwPipeMode*/,
|
||||
Kernel32.PIPE_UNLIMITED_INSTANCES /*nMaxInstances*/,
|
||||
1024 /*nOutBufferSize*/,
|
||||
1024 /*nInBufferSize*/,
|
||||
0 /*nDefaultTimeOut*/,
|
||||
null /*lpSecurityAttributes*/));
|
||||
|
||||
try (PrintWriter writer = new PrintWriter(new HandleOutputStream(hPipe));
|
||||
BufferedReader reader =
|
||||
new BufferedReader(new InputStreamReader(new HandleInputStream(hPipe)))) {
|
||||
|
||||
writer.println("-add-inferior");
|
||||
writer.flush();
|
||||
|
||||
String line;
|
||||
while (null != (line = reader.readLine())) {
|
||||
System.out.println(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -73,6 +73,27 @@ public class GdbDebuggerProgramLaunchOpinion implements DebuggerProgramLaunchOpi
|
|||
}
|
||||
}
|
||||
|
||||
protected class InVmGdbConPtyDebuggerProgramLaunchOffer
|
||||
extends AbstractGdbDebuggerProgramLaunchOffer {
|
||||
private static final String FACTORY_CLS_NAME =
|
||||
"agent.gdb.GdbInJvmConPtyDebuggerModelFactory";
|
||||
|
||||
public InVmGdbConPtyDebuggerProgramLaunchOffer(Program program, PluginTool tool,
|
||||
DebuggerModelFactory factory) {
|
||||
super(program, tool, factory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getConfigName() {
|
||||
return "IN-VM GDB (Windows)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMenuTitle() {
|
||||
return "in GDB locally IN-VM (Windows)";
|
||||
}
|
||||
}
|
||||
|
||||
protected class GadpGdbDebuggerProgramLaunchOffer
|
||||
extends AbstractGdbDebuggerProgramLaunchOffer {
|
||||
private static final String FACTORY_CLS_NAME =
|
||||
|
@ -143,6 +164,9 @@ public class GdbDebuggerProgramLaunchOpinion implements DebuggerProgramLaunchOpi
|
|||
else if (clsName.equals(SshGdbDebuggerProgramLaunchOffer.FACTORY_CLS_NAME)) {
|
||||
offers.add(new SshGdbDebuggerProgramLaunchOffer(program, tool, factory));
|
||||
}
|
||||
else if (clsName.equals(InVmGdbConPtyDebuggerProgramLaunchOffer.FACTORY_CLS_NAME)) {
|
||||
offers.add(new InVmGdbConPtyDebuggerProgramLaunchOffer(program, tool, factory));
|
||||
}
|
||||
}
|
||||
return offers;
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ public class GdbX86DebuggerMappingOpinion implements DebuggerMappingOpinion {
|
|||
protected static final LanguageID LANG_ID_X86 = new LanguageID("x86:LE:32:default");
|
||||
protected static final LanguageID LANG_ID_X86_64 = new LanguageID("x86:LE:64:default");
|
||||
protected static final CompilerSpecID COMP_ID_GCC = new CompilerSpecID("gcc");
|
||||
protected static final CompilerSpecID COMP_ID_VS = new CompilerSpecID("Visual Studio");
|
||||
protected static final CompilerSpecID COMP_ID_VS = new CompilerSpecID("windows");
|
||||
|
||||
protected static class GdbI386X86_64RegisterMapper extends DefaultDebuggerRegisterMapper {
|
||||
public GdbI386X86_64RegisterMapper(CompilerSpec cSpec,
|
||||
|
@ -120,7 +120,7 @@ public class GdbX86DebuggerMappingOpinion implements DebuggerMappingOpinion {
|
|||
return Set.of(new GdbI386LinuxOffer(process));
|
||||
}
|
||||
}
|
||||
else if (os.contains("Cygwin")) {
|
||||
else if (os.contains("Cygwin") || os.contains("Windows")) {
|
||||
if (is64Bit) {
|
||||
return Set.of(new GdbI386X86_64WindowsOffer(process));
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue