mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2024-11-22 04:05:39 +00:00
GP-0: Fix frame selection in gdb-13.1. CI issues after upgrade.
This commit is contained in:
parent
d8f163b542
commit
dab3e1aa57
@ -113,6 +113,34 @@ public class GdbCommandDoneEvent extends AbstractGdbCompletedCommandEvent {
|
||||
return tids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a new thread is specified
|
||||
*
|
||||
* @return the new thread id, or null if unspecified
|
||||
*/
|
||||
public Integer checkNewThreadId() {
|
||||
String newTid = getInfo().getString("new-thread-id");
|
||||
if (newTid == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return Integer.parseInt(newTid);
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
Msg.error(this, "Unexpected thread id in: " + newTid);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a new frame is specified
|
||||
*
|
||||
* @return the new frame, or null if unspecified
|
||||
*/
|
||||
public GdbMiFieldList checkFrame() {
|
||||
return getInfo().getFieldList("frame");
|
||||
}
|
||||
|
||||
/**
|
||||
* Assume a value is specified, and get it as a string
|
||||
*
|
||||
|
@ -38,8 +38,10 @@ import agent.gdb.manager.evt.*;
|
||||
import agent.gdb.manager.impl.cmd.*;
|
||||
import agent.gdb.manager.impl.cmd.GdbConsoleExecCommand.CompletesWithRunning;
|
||||
import agent.gdb.manager.parsing.GdbMiParser;
|
||||
import agent.gdb.manager.parsing.GdbMiParser.GdbMiFieldList;
|
||||
import agent.gdb.manager.parsing.GdbParsingUtils.GdbParseError;
|
||||
import agent.gdb.pty.*;
|
||||
import agent.gdb.pty.PtyChild.Echo;
|
||||
import agent.gdb.pty.windows.AnsiBufferedInputStream;
|
||||
import ghidra.GhidraApplicationLayout;
|
||||
import ghidra.async.*;
|
||||
@ -652,7 +654,9 @@ public class GdbManagerImpl implements GdbManager {
|
||||
if (gdbCmd != null) {
|
||||
iniThread = new PtyThread(ptyFactory.openpty(), Channel.STDOUT, null);
|
||||
|
||||
gdb = iniThread.pty.getChild().session(fullargs.toArray(new String[] {}), null);
|
||||
Msg.info(this, "Starting gdb with: " + fullargs);
|
||||
gdb =
|
||||
iniThread.pty.getChild().session(fullargs.toArray(new String[] {}), null, Echo.OFF);
|
||||
gdbWaiter = new Thread(this::waitGdbExit, "GDB WaitExit");
|
||||
gdbWaiter.start();
|
||||
|
||||
@ -675,7 +679,7 @@ public class GdbManagerImpl implements GdbManager {
|
||||
cliThread.writer.print("set pagination off" + newLine);
|
||||
String ptyName;
|
||||
try {
|
||||
ptyName = Objects.requireNonNull(mi2Pty.getChild().nullSession());
|
||||
ptyName = Objects.requireNonNull(mi2Pty.getChild().nullSession(Echo.OFF));
|
||||
}
|
||||
catch (UnsupportedOperationException e) {
|
||||
throw new IOException(
|
||||
@ -705,7 +709,7 @@ public class GdbManagerImpl implements GdbManager {
|
||||
}
|
||||
else {
|
||||
Pty mi2Pty = ptyFactory.openpty();
|
||||
String mi2PtyName = mi2Pty.getChild().nullSession();
|
||||
String mi2PtyName = mi2Pty.getChild().nullSession(Echo.OFF);
|
||||
Msg.info(this, "Agent is waiting for GDB/MI v2 interpreter at " + mi2PtyName);
|
||||
mi2Thread = new PtyThread(mi2Pty, Channel.STDOUT, Interpreter.MI2);
|
||||
mi2Thread.setName("GDB Read MI2");
|
||||
@ -1445,6 +1449,21 @@ public class GdbManagerImpl implements GdbManager {
|
||||
}
|
||||
}
|
||||
|
||||
private void emitNewThreadFrameIfSpecified(GdbCommandDoneEvent evt) {
|
||||
Integer newTid = evt.checkNewThreadId();
|
||||
if (newTid == null) {
|
||||
return;
|
||||
}
|
||||
GdbThreadImpl thread = threads.get(newTid);
|
||||
if (thread == null) {
|
||||
return;
|
||||
}
|
||||
GdbMiFieldList newFrame = evt.checkFrame();
|
||||
GdbStackFrameImpl frame =
|
||||
newFrame == null ? null : GdbStackFrameImpl.fromFieldList(thread, newFrame);
|
||||
event(() -> listenersEvent.fire.threadSelected(thread, frame, evt), "command-done");
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for "^done"
|
||||
*
|
||||
@ -1452,6 +1471,7 @@ public class GdbManagerImpl implements GdbManager {
|
||||
* @param v nothing
|
||||
*/
|
||||
protected void processCommandDone(GdbCommandDoneEvent evt, Void v) {
|
||||
emitNewThreadFrameIfSpecified(evt);
|
||||
checkClaimed(evt);
|
||||
}
|
||||
|
||||
|
@ -106,8 +106,41 @@ public class GdbStackFrameImpl implements GdbStackFrame {
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> setActive(boolean internal) {
|
||||
return manager
|
||||
.execute(new GdbSetActiveThreadCommand(manager, thread.getId(), level, internal));
|
||||
/**
|
||||
* Since gdb-13.1, it seems there is no longer a way to select the thread and frame in a
|
||||
* single command. I think it's a bug, but it's hard to tell what their intended behavior
|
||||
* is. Take the following example MI command:
|
||||
*
|
||||
* <pre>
|
||||
* -interpreter-exec --thread 1 console "frame 2"
|
||||
* </pre>
|
||||
*
|
||||
* This will produce console output and an MI event indicating a frame context change:
|
||||
*
|
||||
* <pre>
|
||||
* ~"#2 0x... in ... ()\n"
|
||||
* =thread-selected,id="1",frame={level="2",...}
|
||||
* </pre>
|
||||
*
|
||||
* However, the console has not actually changed context. If we then issue the {@code frame}
|
||||
* command, we get:
|
||||
*
|
||||
* <pre>
|
||||
* &"frame\n" ~"#0 0x... in read () from /.../libc.so.6\n"
|
||||
* ^done
|
||||
* (gdb)
|
||||
* </pre>
|
||||
*
|
||||
* I'd expect either the context change to persist, or there not to be an event reported.
|
||||
* Until or unless this is fixed, we will have to select the thread using
|
||||
* {@code -thread-select}, then the frame using {@code -interpreter exec console "frame"}.
|
||||
* Even if it is fixed, because we code to the least common denominator, we'll probably
|
||||
* leave the two-command approach in place.
|
||||
*/
|
||||
return thread.setActive(true).thenCompose(__ -> {
|
||||
return manager.execute(
|
||||
new GdbSetActiveFrameCommand(manager, null, level, internal));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -135,7 +135,7 @@ public class GdbThreadImpl implements GdbThread {
|
||||
@Override
|
||||
public CompletableFuture<Void> setActive(boolean internal) {
|
||||
// Bypass the select-me-first logic
|
||||
return manager.execute(new GdbSetActiveThreadCommand(manager, id, null, internal));
|
||||
return manager.execute(new GdbSetActiveThreadCommand(manager, id, internal));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -105,6 +105,8 @@ public abstract class AbstractGdbCommand<T> implements GdbCommand<T> {
|
||||
* will likely type "start" into the existing CLI. Thus, we have to be careful not to let
|
||||
* spurious {@code ^running} command-completion events actually complete any command, except
|
||||
* ones where we expect that result. This seems a bug in GDB to me.
|
||||
*
|
||||
* UPDATE: It looks like this will be fixed in gdb-14.
|
||||
*/
|
||||
if (evt instanceof GdbCommandRunningEvent) {
|
||||
return false;
|
||||
|
@ -0,0 +1,67 @@
|
||||
/* ###
|
||||
* 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.evt.GdbCommandDoneEvent;
|
||||
import agent.gdb.manager.evt.GdbThreadSelectedEvent;
|
||||
import agent.gdb.manager.impl.*;
|
||||
|
||||
public class GdbSetActiveFrameCommand extends AbstractGdbCommandWithThreadId<Void> {
|
||||
private final Integer frameId;
|
||||
private final boolean internal;
|
||||
|
||||
/**
|
||||
* Select the given thread and frame
|
||||
*
|
||||
* @param manager the manager to execute the command
|
||||
* @param threadId the desired thread Id
|
||||
* @param frameId the desired frame level
|
||||
* @param internal true to prevent announcement of the change
|
||||
*/
|
||||
public GdbSetActiveFrameCommand(GdbManagerImpl manager, Integer threadId, int frameId,
|
||||
boolean internal) {
|
||||
super(manager, threadId);
|
||||
this.frameId = frameId;
|
||||
this.internal = internal;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String encode(String threadPart) {
|
||||
return "-interpreter-exec" + threadPart + " console \"frame " + frameId + "\"";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(GdbEvent<?> evt, GdbPendingCommand<?> pending) {
|
||||
if (super.handle(evt, pending)) {
|
||||
return true;
|
||||
}
|
||||
else if (evt instanceof GdbThreadSelectedEvent) {
|
||||
pending.claim(evt);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void complete(GdbPendingCommand<?> pending) {
|
||||
pending.checkCompletion(GdbCommandDoneEvent.class);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFocusInternallyDriven() {
|
||||
return internal;
|
||||
}
|
||||
}
|
@ -15,38 +15,31 @@
|
||||
*/
|
||||
package agent.gdb.manager.impl.cmd;
|
||||
|
||||
import agent.gdb.manager.evt.*;
|
||||
import agent.gdb.manager.evt.GdbCommandDoneEvent;
|
||||
import agent.gdb.manager.evt.GdbThreadSelectedEvent;
|
||||
import agent.gdb.manager.impl.*;
|
||||
import agent.gdb.manager.parsing.GdbMiParser.GdbMiFieldList;
|
||||
|
||||
public class GdbSetActiveThreadCommand extends AbstractGdbCommandWithThreadAndFrameId<Void> {
|
||||
public class GdbSetActiveThreadCommand extends AbstractGdbCommand<Void> {
|
||||
private final int threadId;
|
||||
private final boolean internal;
|
||||
|
||||
/**
|
||||
* Select the given thread and frame level
|
||||
*
|
||||
* <p>
|
||||
* To simply select a thread, you should use frame 0 as the default.
|
||||
* Select the given thread
|
||||
*
|
||||
* @param manager the manager to execute the command
|
||||
* @param threadId the desired thread Id
|
||||
* @param frameId the desired frame level
|
||||
* @param internal true to prevent announcement of the change
|
||||
*/
|
||||
public GdbSetActiveThreadCommand(GdbManagerImpl manager, int threadId, Integer frameId,
|
||||
boolean internal) {
|
||||
super(manager, threadId, frameId);
|
||||
public GdbSetActiveThreadCommand(GdbManagerImpl manager, int threadId, boolean internal) {
|
||||
super(manager);
|
||||
this.threadId = threadId;
|
||||
this.internal = internal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String encode(String threadPart, String framePart) {
|
||||
/**
|
||||
* Yes, it's a bit redundant to use {@code --thread} here, but this allows frame selection
|
||||
* via {@code --frame} as well. Granted {@code -stack-select-frame} may be available, it
|
||||
* doesn't appear to produce notifications, and so I've opted not to use it.
|
||||
*/
|
||||
return "-thread-select" + threadPart + framePart + " " + threadId;
|
||||
public String encode() {
|
||||
return "-thread-select " + threadId;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -62,19 +55,7 @@ public class GdbSetActiveThreadCommand extends AbstractGdbCommandWithThreadAndFr
|
||||
|
||||
@Override
|
||||
public Void complete(GdbPendingCommand<?> pending) {
|
||||
GdbCommandDoneEvent done = pending.checkCompletion(GdbCommandDoneEvent.class);
|
||||
GdbThreadSelectedEvent already = pending.getFirstOf(GdbThreadSelectedEvent.class);
|
||||
if (already != null) {
|
||||
return null;
|
||||
}
|
||||
// Otherwise, we just changed frames within a thread. Fire the event ourselves.
|
||||
GdbThreadImpl thread = manager.getThread(threadId);
|
||||
GdbMiFieldList fields = done.getInfo().getFieldList("frame");
|
||||
if (fields == null) { // Uhhh... I guess we'll have to do without
|
||||
return null;
|
||||
}
|
||||
GdbStackFrameImpl frame = GdbStackFrameImpl.fromFieldList(thread, fields);
|
||||
manager.doThreadSelected(thread, frame, done.getCause());
|
||||
pending.checkCompletion(GdbCommandDoneEvent.class);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -189,7 +189,10 @@ public class GdbModelTargetSession extends DefaultTargetModelRoot
|
||||
GdbModelTargetInferior inf = inferiors.getTargetInferior(thread.getInferior());
|
||||
GdbModelTargetThread t = inf.threads.getTargetThread(thread);
|
||||
if (frame == null) {
|
||||
setFocus(t);
|
||||
GdbModelSelectableObject curFocus = getFocus();
|
||||
if (curFocus != null && !PathUtils.isAncestor(t.getPath(), curFocus.getPath())) {
|
||||
setFocus(t);
|
||||
}
|
||||
return;
|
||||
}
|
||||
GdbModelTargetStackFrame f = t.stack.getTargetFrame(frame);
|
||||
|
@ -16,41 +16,73 @@
|
||||
package agent.gdb.pty;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* The child (UNIX "slave") end of a pseudo-terminal
|
||||
*/
|
||||
public interface PtyChild extends PtyEndpoint {
|
||||
|
||||
/**
|
||||
* A terminal mode flag
|
||||
*/
|
||||
interface TermMode {
|
||||
}
|
||||
|
||||
/**
|
||||
* Mode flag for local echo
|
||||
*/
|
||||
enum Echo implements TermMode {
|
||||
/**
|
||||
* Input is echoed to output by the terminal itself.
|
||||
*/
|
||||
ON,
|
||||
/**
|
||||
* No local echo.
|
||||
*/
|
||||
OFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Spawn a subprocess in a new session whose controlling tty is this pseudo-terminal
|
||||
*
|
||||
* <p>
|
||||
* This method or {@link #nullSession()} can only be invoked once per pty.
|
||||
* This method or {@link #nullSession(Collection)} can only be invoked once per pty.
|
||||
*
|
||||
* @param args the image path and arguments
|
||||
* @param env the environment
|
||||
* @param mode the terminal mode. If a mode is not implemented, it may be silently ignored.
|
||||
* @return a handle to the subprocess
|
||||
* @throws IOException if the session could not be started
|
||||
*/
|
||||
PtySession session(String[] args, Map<String, String> env) throws IOException;
|
||||
PtySession session(String[] args, Map<String, String> env, Collection<TermMode> mode)
|
||||
throws IOException;
|
||||
|
||||
default PtySession session(String[] args, Map<String, String> env, TermMode... mode)
|
||||
throws IOException {
|
||||
return session(args, env, List.of(mode));
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a session without a real leader, instead obtaining the pty's name
|
||||
*
|
||||
* <p>
|
||||
* This method or {@link #session(String[], Map)} can only be invoked once per pty. It must be
|
||||
* called before anyone reads the parent's output stream, since obtaining the filename may be
|
||||
* implemented by the parent sending commands to its child.
|
||||
* This method or {@link #session(String[], Map, Collection)} can only be invoked once per pty.
|
||||
* It must be called before anyone reads the parent's output stream, since obtaining the
|
||||
* filename may be implemented by the parent sending commands to its child.
|
||||
*
|
||||
* <p>
|
||||
* If the child end of the pty is on a remote system, this should be the file (or other
|
||||
* resource) name as it would be accessed on that remote system.
|
||||
*
|
||||
* @param mode the terminal mode. If a mode is not implemented, it may be silently ignored.
|
||||
* @return the file name
|
||||
* @throws IOException if the session could not be started or the pty name could not be
|
||||
* determined
|
||||
*/
|
||||
String nullSession() throws IOException;
|
||||
String nullSession(Collection<TermMode> mode) throws IOException;
|
||||
|
||||
default String nullSession(TermMode... mode) throws IOException {
|
||||
return nullSession(List.of(mode));
|
||||
}
|
||||
}
|
||||
|
@ -65,6 +65,9 @@ public class FdInputStream extends InputStream {
|
||||
if (closed) {
|
||||
throw new IOException("Stream closed");
|
||||
}
|
||||
if (len == 0) {
|
||||
return 0;
|
||||
}
|
||||
Memory buf = new Memory(len);
|
||||
int ret = LIB_POSIX.read(fd, buf, len);
|
||||
buf.read(0, b, off, ret);
|
||||
|
@ -23,10 +23,13 @@ import java.util.*;
|
||||
|
||||
import agent.gdb.pty.PtyChild;
|
||||
import agent.gdb.pty.PtySession;
|
||||
import agent.gdb.pty.linux.PosixC.Termios;
|
||||
import agent.gdb.pty.local.LocalProcessPtySession;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
public class LinuxPtyChild extends LinuxPtyEndpoint implements PtyChild {
|
||||
static final PosixC LIB_POSIX = PosixC.INSTANCE;
|
||||
|
||||
private final String name;
|
||||
|
||||
LinuxPtyChild(int fd, String name) {
|
||||
@ -35,7 +38,8 @@ public class LinuxPtyChild extends LinuxPtyEndpoint implements PtyChild {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String nullSession() {
|
||||
public String nullSession(Collection<TermMode> mode) {
|
||||
applyMode(mode);
|
||||
return name;
|
||||
}
|
||||
|
||||
@ -53,19 +57,15 @@ public class LinuxPtyChild extends LinuxPtyEndpoint implements PtyChild {
|
||||
* received by the leader by mistake if sent immediately upon spawning a new session.
|
||||
* Users should send a simple command, e.g., "echo", to confirm that the requested
|
||||
* program is active before sending special characters.
|
||||
*
|
||||
* @param args the image path and arguments
|
||||
* @param env the environment
|
||||
* @return a handle to the subprocess
|
||||
* @throws IOException
|
||||
*/
|
||||
@Override
|
||||
public PtySession session(String[] args, Map<String, String> env) throws IOException {
|
||||
return sessionUsingJavaLeader(args, env);
|
||||
public PtySession session(String[] args, Map<String, String> env, Collection<TermMode> mode)
|
||||
throws IOException {
|
||||
return sessionUsingJavaLeader(args, env, mode);
|
||||
}
|
||||
|
||||
protected PtySession sessionUsingJavaLeader(String[] args, Map<String, String> env)
|
||||
throws IOException {
|
||||
protected PtySession sessionUsingJavaLeader(String[] args, Map<String, String> env,
|
||||
Collection<TermMode> mode) throws IOException {
|
||||
final List<String> argsList = new ArrayList<>();
|
||||
String javaCommand =
|
||||
System.getProperty("java.home") + File.separator + "bin" + File.separator + "java";
|
||||
@ -82,6 +82,8 @@ public class LinuxPtyChild extends LinuxPtyEndpoint implements PtyChild {
|
||||
}
|
||||
builder.inheritIO();
|
||||
|
||||
applyMode(mode);
|
||||
|
||||
try {
|
||||
return new LocalProcessPtySession(builder.start());
|
||||
}
|
||||
@ -148,4 +150,17 @@ public class LinuxPtyChild extends LinuxPtyEndpoint implements PtyChild {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void applyMode(Collection<TermMode> mode) {
|
||||
if (mode.contains(Echo.OFF)) {
|
||||
disableEcho();
|
||||
}
|
||||
}
|
||||
|
||||
private void disableEcho() {
|
||||
Termios.ByReference tmios = new Termios.ByReference();
|
||||
LIB_POSIX.tcgetattr(fd, tmios);
|
||||
tmios.c_lflag &= ~Termios.ECHO;
|
||||
LIB_POSIX.tcsetattr(fd, Termios.TCSANOW, tmios);
|
||||
}
|
||||
}
|
||||
|
@ -21,12 +21,12 @@ import java.io.OutputStream;
|
||||
import agent.gdb.pty.PtyEndpoint;
|
||||
|
||||
public class LinuxPtyEndpoint implements PtyEndpoint {
|
||||
//private final int fd;
|
||||
protected final int fd;
|
||||
private final FdOutputStream outputStream;
|
||||
private final FdInputStream inputStream;
|
||||
|
||||
LinuxPtyEndpoint(int fd) {
|
||||
//this.fd = fd;
|
||||
this.fd = fd;
|
||||
this.outputStream = new FdOutputStream(fd);
|
||||
this.inputStream = new FdInputStream(fd);
|
||||
}
|
||||
|
@ -16,6 +16,7 @@
|
||||
package agent.gdb.pty.linux;
|
||||
|
||||
import com.sun.jna.*;
|
||||
import com.sun.jna.Structure.FieldOrder;
|
||||
|
||||
/**
|
||||
* Interface for POSIX functions in libc
|
||||
@ -24,6 +25,29 @@ import com.sun.jna.*;
|
||||
* The functions are not documented here. Instead see the POSIX manual pages.
|
||||
*/
|
||||
public interface PosixC extends Library {
|
||||
|
||||
@FieldOrder({ "c_iflag", "c_oflag", "c_cflag", "c_lflag", "c_line", "c_cc", "c_ispeed",
|
||||
"c_ospeed" })
|
||||
class Termios extends Structure {
|
||||
public static final int TCSANOW = 0;
|
||||
|
||||
public static final int ECHO = 0000010; // Octal
|
||||
|
||||
public int c_iflag;
|
||||
public int c_oflag;
|
||||
public int c_cflag;
|
||||
public int c_lflag;
|
||||
|
||||
public byte c_line;
|
||||
public byte[] c_cc = new byte[32];
|
||||
|
||||
public int c_ispeed;
|
||||
public int c_ospeed;
|
||||
|
||||
public static class ByReference extends Termios implements Structure.ByReference {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The bare library without error handling
|
||||
*
|
||||
@ -71,6 +95,16 @@ public interface PosixC extends Library {
|
||||
public int execv(String path, String[] argv) {
|
||||
return Err.checkLt0(BARE.execv(path, argv));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int tcgetattr(int fd, Termios.ByReference termios_p) {
|
||||
return Err.checkLt0(BARE.tcgetattr(fd, termios_p));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int tcsetattr(int fd, int optional_actions, Termios.ByReference termios_p) {
|
||||
return Err.checkLt0(BARE.tcsetattr(fd, optional_actions, termios_p));
|
||||
}
|
||||
};
|
||||
|
||||
String strerror(int errnum);
|
||||
@ -88,4 +122,8 @@ public interface PosixC extends Library {
|
||||
int dup2(int oldfd, int newfd);
|
||||
|
||||
int execv(String path, String[] argv);
|
||||
|
||||
int tcgetattr(int fd, Termios.ByReference termios_p);
|
||||
|
||||
int tcsetattr(int fd, int optional_actions, Termios.ByReference termios_p);
|
||||
}
|
||||
|
@ -16,13 +16,11 @@
|
||||
package agent.gdb.pty.ssh;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.help.UnsupportedOperationException;
|
||||
|
||||
import com.jcraft.jsch.*;
|
||||
import com.jcraft.jsch.ChannelExec;
|
||||
import com.jcraft.jsch.JSchException;
|
||||
|
||||
import agent.gdb.pty.PtyChild;
|
||||
import ghidra.dbg.util.ShellUtils;
|
||||
@ -38,8 +36,23 @@ public class SshPtyChild extends SshPtyEndpoint implements PtyChild {
|
||||
this.channel = channel;
|
||||
}
|
||||
|
||||
private String sttyString(Collection<TermMode> mode) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (mode.contains(Echo.OFF)) {
|
||||
sb.append("-echo ");
|
||||
}
|
||||
else if (mode.contains(Echo.ON)) {
|
||||
sb.append("echo ");
|
||||
}
|
||||
if (sb.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
return "stty " + sb + "&& ";
|
||||
}
|
||||
|
||||
@Override
|
||||
public SshPtySession session(String[] args, Map<String, String> env) throws IOException {
|
||||
public SshPtySession session(String[] args, Map<String, String> env, Collection<TermMode> mode)
|
||||
throws IOException {
|
||||
/**
|
||||
* TODO: This syntax assumes a UNIX-style shell, and even among them, this may not be
|
||||
* universal. This certainly works for my version of bash :)
|
||||
@ -52,7 +65,7 @@ public class SshPtyChild extends SshPtyEndpoint implements PtyChild {
|
||||
.collect(Collectors.joining(" ")) +
|
||||
" ";
|
||||
String cmdStr = ShellUtils.generateLine(Arrays.asList(args));
|
||||
channel.setCommand(envStr + cmdStr);
|
||||
channel.setCommand(sttyString(mode) + envStr + cmdStr);
|
||||
try {
|
||||
channel.connect();
|
||||
}
|
||||
@ -62,11 +75,11 @@ public class SshPtyChild extends SshPtyEndpoint implements PtyChild {
|
||||
return new SshPtySession(channel);
|
||||
}
|
||||
|
||||
private String getTtyNameAndStartNullSession() throws IOException {
|
||||
private String getTtyNameAndStartNullSession(Collection<TermMode> mode) throws IOException {
|
||||
// NB. UNIX sleep is only required to support integer durations
|
||||
channel.setCommand(
|
||||
("sh -c 'tty && ctrlc() { echo; } && trap ctrlc INT && while true; do sleep " +
|
||||
Integer.MAX_VALUE + "; done'"));
|
||||
channel.setCommand(sttyString(mode) +
|
||||
"sh -c 'tty && ctrlc() { echo; } && trap ctrlc INT && while true; do sleep " +
|
||||
Integer.MAX_VALUE + "; done'");
|
||||
try {
|
||||
channel.connect();
|
||||
}
|
||||
@ -85,9 +98,9 @@ public class SshPtyChild extends SshPtyEndpoint implements PtyChild {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String nullSession() throws IOException {
|
||||
public String nullSession(Collection<TermMode> mode) throws IOException {
|
||||
if (name == null) {
|
||||
this.name = getTtyNameAndStartNullSession();
|
||||
this.name = getTtyNameAndStartNullSession(mode);
|
||||
if ("".equals(name)) {
|
||||
throw new IOException("Could not determine child remote tty name");
|
||||
}
|
||||
|
@ -16,8 +16,7 @@
|
||||
package agent.gdb.pty.windows;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
|
||||
import com.sun.jna.*;
|
||||
import com.sun.jna.platform.win32.Kernel32;
|
||||
@ -76,10 +75,12 @@ public class ConPtyChild extends ConPtyEndpoint implements PtyChild {
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalWindowsNativeProcessPtySession session(String[] args, Map<String, String> env)
|
||||
throws IOException {
|
||||
public LocalWindowsNativeProcessPtySession session(String[] args, Map<String, String> env,
|
||||
Collection<TermMode> mode) throws IOException {
|
||||
/**
|
||||
* TODO: How to incorporate environment into CreateProcess?
|
||||
*
|
||||
* TODO: How to control local echo?
|
||||
*/
|
||||
|
||||
STARTUPINFOEX si = prepareStartupInfo();
|
||||
@ -105,7 +106,7 @@ public class ConPtyChild extends ConPtyEndpoint implements PtyChild {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String nullSession() throws IOException {
|
||||
public String nullSession(Collection<TermMode> mode) throws IOException {
|
||||
throw new UnsupportedOperationException("ConPTY does not have a name");
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ package agent.gdb.model.gadp;
|
||||
|
||||
import agent.gdb.model.AbstractModelForGdbSessionAttacherTest;
|
||||
|
||||
public class GadpModelForGdbSessiopnAttacherTest extends AbstractModelForGdbSessionAttacherTest {
|
||||
public class GadpModelForGdbSessionAttacherTest extends AbstractModelForGdbSessionAttacherTest {
|
||||
@Override
|
||||
public ModelHost modelHost() throws Throwable {
|
||||
return new GadpGdbModelHost();
|
@ -17,7 +17,7 @@ package agent.gdb.model.invm;
|
||||
|
||||
import agent.gdb.model.AbstractModelForGdbSessionAttacherTest;
|
||||
|
||||
public class InVmModelForGdbSessiopnAttacherTest extends AbstractModelForGdbSessionAttacherTest {
|
||||
public class InVmModelForGdbSessionAttacherTest extends AbstractModelForGdbSessionAttacherTest {
|
||||
@Override
|
||||
public ModelHost modelHost() throws Throwable {
|
||||
return new InVmGdbModelHost();
|
@ -15,8 +15,7 @@
|
||||
*/
|
||||
package agent.gdb.pty.linux;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assume.assumeTrue;
|
||||
|
||||
import java.io.*;
|
||||
@ -26,6 +25,7 @@ import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import agent.gdb.pty.AbstractPtyTest;
|
||||
import agent.gdb.pty.PtyChild.Echo;
|
||||
import agent.gdb.pty.PtySession;
|
||||
import ghidra.dbg.testutil.DummyProc;
|
||||
import ghidra.framework.OperatingSystem;
|
||||
@ -177,4 +177,38 @@ public class LinuxPtyTest extends AbstractPtyTest {
|
||||
assertEquals(3, bash.waitExited());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLocalEchoOn() throws IOException {
|
||||
try (LinuxPty pty = LinuxPty.openpty()) {
|
||||
pty.getChild().nullSession();
|
||||
|
||||
PrintWriter writer = new PrintWriter(pty.getParent().getOutputStream());
|
||||
BufferedReader reader =
|
||||
new BufferedReader(new InputStreamReader(pty.getParent().getInputStream()));
|
||||
|
||||
writer.println("Hello, World!");
|
||||
writer.flush();
|
||||
assertEquals("Hello, World!", reader.readLine());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLocalEchoOff() throws IOException {
|
||||
try (LinuxPty pty = LinuxPty.openpty()) {
|
||||
pty.getChild().nullSession(Echo.OFF);
|
||||
|
||||
PrintWriter writerP = new PrintWriter(pty.getParent().getOutputStream());
|
||||
PrintWriter writerC = new PrintWriter(pty.getChild().getOutputStream());
|
||||
BufferedReader reader =
|
||||
new BufferedReader(new InputStreamReader(pty.getParent().getInputStream()));
|
||||
|
||||
writerP.println("Hello, World!");
|
||||
writerP.flush();
|
||||
writerC.println("Good bye!");
|
||||
writerC.flush();
|
||||
|
||||
assertEquals("Good bye!", reader.readLine());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ import java.io.*;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import agent.gdb.pty.PtyChild.Echo;
|
||||
import agent.gdb.pty.PtySession;
|
||||
import ghidra.app.script.AskDialog;
|
||||
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
||||
@ -85,4 +86,17 @@ public class SshPtyTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
assertEquals(0, bash.waitExited());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDisableEcho() throws IOException, InterruptedException {
|
||||
try (SshPty pty = factory.openpty()) {
|
||||
PtySession bash =
|
||||
pty.getChild().session(new String[] { "bash" }, null, Echo.OFF);
|
||||
OutputStream out = pty.getParent().getOutputStream();
|
||||
out.write("exit\n".getBytes("UTF-8"));
|
||||
out.flush();
|
||||
new StreamPumper(pty.getParent().getInputStream(), System.out).start();
|
||||
assertEquals(0, bash.waitExited());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user