diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/terminal/TerminalLayoutModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/terminal/TerminalLayoutModel.java index d1d50edfd6..cc5ff995ee 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/terminal/TerminalLayoutModel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/terminal/TerminalLayoutModel.java @@ -94,6 +94,7 @@ public class TerminalLayoutModel implements LayoutModel, VtHandler { // Flags for what's been enabled protected boolean showCursor; protected boolean bracketedPaste; + protected boolean win32InputMode; // not implemented protected boolean reportMousePress; protected boolean reportMouseRelease; protected boolean reportFocus; @@ -139,6 +140,7 @@ public class TerminalLayoutModel implements LayoutModel, VtHandler { buffer = bufPrimary; bracketedPaste = false; + win32InputMode = false; reportMousePress = false; reportMouseRelease = false; reportFocus = false; @@ -497,6 +499,11 @@ public class TerminalLayoutModel implements LayoutModel, VtHandler { public void handleBracketedPasteMode(boolean en) { this.bracketedPaste = en; } + + @Override + public void handleWin32InputMode(boolean en) { + this.win32InputMode = en; + } @Override public void handleSaveCursorPos() { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/terminal/vt/VtHandler.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/terminal/vt/VtHandler.java index d59a02af91..cf39d2eecf 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/terminal/vt/VtHandler.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/terminal/vt/VtHandler.java @@ -28,10 +28,8 @@ import ghidra.util.Msg; /** * The handler of parsed ANSI VT control sequences - * *

* Here are some of the resources where I found useful documentation: - * *

- * *

* They were incredibly useful, even when experimentation was required to fill in details, because * they at least described the sort of behavior I should be looking for. Throughout the referenced * documents and within this documentation, the following abbreviations are used for escape * sequences: - * * * * @@ -78,13 +74,11 @@ import ghidra.util.Msg; * * *
Abbreviation{@code "\007"}
- * *

* The separation between the parser and the handler deals in state management. The parser manages * state only of the control sequence parser itself, i.e., the current node in the token parsing * state machine. The state of the terminal, e.g., the current attributes, cursor position, etc., * are managed by the handler and its delegates. - * *

* For example, the Cursor Position sequence is documented as: *

@@ -97,7 +91,6 @@ import ghidra.util.Msg; * It will thus invoke the abstract {@link #handleMoveCursor(int, int)} method passing 12 and 39. * Note that 1 is subtracted from both parameters, because ANSI specifies 1-up indexing while Java * lends itself to 0-up indexing. - * *

* The XTerm documentation, which is arguably the most thorough, presents the CSI commands * alphabetically by the final byte, in ASCII order. For sanity and consistency, we adopt the same @@ -134,10 +127,10 @@ public interface VtHandler { public static final byte[] Q1048 = ascii("?1048"); public static final byte[] Q1049 = ascii("?1049"); public static final byte[] Q2004 = ascii("?2004"); + public static final byte[] Q9001 = ascii("?9001"); /** * An ANSI color specification - * *

* We avoid going straight to AWT colors, 1) Because it provides better separation between the * terminal logic and the rendering framework, and 2) Because some specifications, e.g., default @@ -149,7 +142,6 @@ public interface VtHandler { /** * A singleton representing the default color - * *

* The actual color selected will depend on context and use. Most notably, the default color * used for foreground should greatly contrast the default color used for the background. @@ -160,7 +152,6 @@ public interface VtHandler { /** * One of the eight standard ANSI colors - * *

* The actual color may be modified by other SGR attributes, notably {@link Intensity}. For * colors that are described by hue, some thought should be given to how the standard and @@ -219,7 +210,6 @@ public interface VtHandler { /** * Get the standard color for the given numerical code - * *

* For example, the sequence {@code CSI [ 34 m} would use code 4 (blue). * @@ -233,7 +223,6 @@ public interface VtHandler { /** * One of the eight ANSI intense colors - * *

* Note that intense colors may also be specified using the standard color with the * {@link Intensity#BOLD} attribute, depending on the command sequence. @@ -279,7 +268,6 @@ public interface VtHandler { /** * Get the intense color for the given numerical code - * *

* For example, the sequence {@code CSI [ 94 m} would use code 4 (blue). * @@ -335,7 +323,6 @@ public interface VtHandler { /** * Get the dim color for the given numerical code - * *

* For example, the sequence {@code CSI [ 34 m} would use code 4 (blue). * @@ -349,7 +336,6 @@ public interface VtHandler { /** * For 8-bit colors, one of the 216 colors from the RGB cube - * *

* The r, g, and b fields give the "step" number from 0 to 5, dimmest to brightest. */ @@ -357,7 +343,6 @@ public interface VtHandler { /** * For 8-bit colors, one of the 24 grays - * *

* The v field is a value from 0 to 23, 0 being the dimmest, but not true black, and 23 being * the brightest, but not true white. @@ -366,7 +351,6 @@ public interface VtHandler { /** * A 24-bit color - * *

* The r, g, and b fields are values from 0 to 255 dimmest to brightest. */ @@ -374,7 +358,6 @@ public interface VtHandler { /** * Modifies the intensity of the character either by color or by font weight. - * *

* The renderer may choose a combination of strategies. For example, {@link #NORMAL} may be * rendered using the standard color and bold type. Then {@link #BOLD} would use the intense @@ -434,7 +417,6 @@ public interface VtHandler { /** * Causes text to blink - * *

* If implemented, renderers should take care not to irritate the user. One option is to make * {@link #FAST} actually slow, and {@link #SLOW} even slower. Another option is to only blink @@ -567,7 +549,6 @@ public interface VtHandler { /** * For cursor and keypad, specifies normal or application mode - * *

* This affects the codes sent by the terminal. */ @@ -628,7 +609,6 @@ public interface VtHandler { /** * Handle normal character output, i.e., place the character on the display - * *

* This excludes control sequences and control characters, e.g., tab, line feed. While we've not * tested, in theory, this can instead buffer the byte for decoding from UTF-8. Still, the @@ -641,7 +621,6 @@ public interface VtHandler { /** * Handle a character not part of an escape sequence. - * *

* This may include control characters, which are displatched appropriately by this method. * Additionally, this handles any exception thrown by {@link #handleChar(byte)}. @@ -683,7 +662,6 @@ public interface VtHandler { /** * Parse a sequence of integers in the form {@code n ; m ;} .... - * *

* This is designed to replace the {@link String#split(String)} and * {@link Integer#parseInt(String)} pattern, which should avoid some unnecessary object @@ -799,6 +777,9 @@ public interface VtHandler { else if (bufEq(csiParam, Q2004)) { handleBracketedPasteMode(en); } + else if (bufEq(csiParam, Q9001)) { + handleWin32InputMode(en); + } else { throw new UnknownCsiException(); } @@ -1189,7 +1170,6 @@ public interface VtHandler { /** * Decode the 8-bit ANSI color. - * *

* Colors 0-15 are the standard + high-intensity. Colors 16-231 come from a 6x6x6 RGB cube. * Finally, colors 232-255 are 24 steps of gray scale. @@ -1413,7 +1393,6 @@ public interface VtHandler { /** * Handle toggling of reverse video - * *

* This can be a bit confusing with default colors. In general, this means swapping the * foreground and background color specifications (not inverting the colors or mirroring or some @@ -1449,7 +1428,6 @@ public interface VtHandler { /** * Handle toggling insert mode - * *

* In insert mode, characters at and to the right of the cursor are shifted right to make room * for each new character. In replace mode (default), the character at the cursor is replaced @@ -1482,7 +1460,6 @@ public interface VtHandler { /** * Toggle blinking of the cursor - * *

* Renderers should take care not to irritate the user. Some possibilities are to blink slowly, * blink only for a short period of time after it moves, and/or blink only when the terminal has @@ -1523,7 +1500,6 @@ public interface VtHandler { /** * Switch to and from the alternate screen buffer, optionally clearing it - * *

* This will never clear the normal buffer. If the buffer does not change as a result of this * call, then the alternate buffer is not cleared, even if clearAlt is specified. @@ -1545,6 +1521,20 @@ public interface VtHandler { */ void handleBracketedPasteMode(boolean en); + /** + * Toggle Win32 input mode + * + *

+ * See the Windows Terminal specification: + * https://github.com/microsoft/terminal/blob/main/doc/specs/%234999%20-%20Improved%20keyboard%20handling%20in%20Conpty.md. + * It should be safe to ignore this, but could provide us options if we'd like to forward more + * detailed keyboard events to a Windows Console application than is permitted with standard VT + * sequences. + * + * @param en true to enable Win32 input mode + */ + void handleWin32InputMode(boolean en); + /** * Handle a request to save the cursor position */ @@ -1573,7 +1563,6 @@ public interface VtHandler { /** * Handle an absolute cursor row movement command - * *

* The column should remain the same, i.e., do not reset the column to 0. * @@ -1595,7 +1584,6 @@ public interface VtHandler { /** * Handle a request to save the terminal window's icon title - * *

* "Icon titles" are a concept from the X Windows system. Do the closest equivalent, if anything * applies at all. The current title is pushed to a stack of limited size. @@ -1604,7 +1592,6 @@ public interface VtHandler { /** * Handle a request to save the terminal window's title - * *

* Window titles are fairly applicable to all desktop windowing systems. The current title is * pushed to a stack of limited size. @@ -1613,7 +1600,6 @@ public interface VtHandler { /** * Handle a request to restore the terminal window's icon title - * *

* The title is set to the one popped from the stack of saved window icon titles. * @@ -1623,7 +1609,6 @@ public interface VtHandler { /** * Handle a request to restore the terminal window's title - * *

* The title is set to the one popped from the stack of saved window titles. * @@ -1647,7 +1632,6 @@ public interface VtHandler { /** * Insert n lines at and below the cursor - * *

* Lines within the viewport are shifted down or deleted to make room for the new lines. * @@ -1657,7 +1641,6 @@ public interface VtHandler { /** * Delete n lines at and below the cursor - * *

* Lines within the viewport are shifted up, and new lines inserted at the bottom. * @@ -1702,7 +1685,6 @@ public interface VtHandler { /** * Set the range of rows (viewport) involved in scrolling. - * *

* This applies not only to {@link #handleScrollUp()} and {@link #handleScrollDown()}, but also * to when the cursor moves far enough down that the display must scroll. Normally, start is 0 @@ -1721,7 +1703,6 @@ public interface VtHandler { /** * Scroll the display n lines down, considering only those lines in the scrolling range. - * *

* To be unambiguous, this of movement of the viewport. The viewport scrolls down, so the lines * themselves scroll up. The default range is the whole display. The cursor is not moved. @@ -1743,7 +1724,6 @@ public interface VtHandler { /** * Scroll the lines n slots down, considering only those lines in the scrolling range. - * *

* This is equivalent to scrolling the viewport n lines up. This method exists * in attempt to reflect "up" and "down" correctly in the documentation. Unfortunately, the @@ -1759,7 +1739,6 @@ public interface VtHandler { /** * Scroll the lines n slots up, considering only those lines in the scrolling range. - * *

* The is equivalent to scrolling the viewport n lines down. This method * exists in attempt to reflect "up" and "down" correctly in the documentation. Unfortunately, @@ -1784,7 +1763,6 @@ public interface VtHandler { /** * Handle a request to fully reset the terminal - * *

* All buffers should be cleared and all state variables, positions, attributes, etc., should be * reset to their defaults. diff --git a/Ghidra/Framework/Pty/src/main/java/ghidra/pty/windows/AnsiBufferedInputStream.java b/Ghidra/Framework/Pty/src/main/java/ghidra/pty/windows/AnsiBufferedInputStream.java index 4b78da1205..ec6c8246d2 100644 --- a/Ghidra/Framework/Pty/src/main/java/ghidra/pty/windows/AnsiBufferedInputStream.java +++ b/Ghidra/Framework/Pty/src/main/java/ghidra/pty/windows/AnsiBufferedInputStream.java @@ -19,7 +19,6 @@ 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. @@ -118,34 +117,17 @@ public class AnsiBufferedInputStream extends InputStream { return -1; } byte c = (byte) ci; - //printDebugChar(c); +// 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(); + case CHARS -> processChars(c); + case ESC -> processEsc(c); + case CSI -> processCsi(c); + case CSI_p -> processCsiParamOrCommand(c); + case CSI_Q -> processCsiQ(c); + case OSC -> processOsc(c); + case WINDOW_TITLE -> processWindowTitle(c); + case WINDOW_TITLE_ESC -> processWindowTitleEsc(c); + default -> throw new AssertionError(); } countIn++; return c; @@ -187,180 +169,151 @@ public class AnsiBufferedInputStream extends InputStream { protected void processChars(byte c) { switch (c) { - case 0x08: + default -> appendChar(c); + case '\b' -> { 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; + } + case '\n' -> bakeLine(); + case '\r' -> lineBuf.position(0); + case 0x1b -> mode = Mode.ESC; } } 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); + case '[' -> mode = Mode.CSI; + case ']' -> mode = Mode.OSC; + 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; + default -> processCsiParamOrCommand(c); + case '?' -> mode = Mode.CSI_Q; } } protected void processCsiParamOrCommand(byte c) { switch (c) { - default: - escBuf.put(c); - break; - case 'A': + default -> escBuf.put(c); + case 'A' -> { execCursorUp(); mode = Mode.CHARS; - break; - case 'B': + } + case 'B' -> { execCursorDown(); mode = Mode.CHARS; - break; - case 'C': + } + case 'C' -> { execCursorForward(); mode = Mode.CHARS; - break; - case 'D': + } + case 'D' -> { execCursorBackward(); mode = Mode.CHARS; - break; - case 'G': + } + case 'G' -> { execCursorCharAbsolute(); mode = Mode.CHARS; - break; - case 'H': + } + case 'H' -> { execCursorPosition(); mode = Mode.CHARS; - break; - case 'J': + } + case 'J' -> { execEraseInDisplay(); mode = Mode.CHARS; - break; - case 'K': + } + case 'K' -> { execEraseInLine(); mode = Mode.CHARS; - break; - case 'X': + } + case 'X' -> { execEraseCharacter(); mode = Mode.CHARS; - break; - case 'm': + } + case 'm' -> { execSetGraphicsRendition(); mode = Mode.CHARS; - break; - case 'h': + } + case 'h' -> { execPrivateSequence(true); mode = Mode.CHARS; - break; - case 'l': + } + case 'l' -> { execPrivateSequence(false); mode = Mode.CHARS; - break; + } } } + public static final String PRIV_12 = "12"; + public static final String PRIV_25 = "25"; + public static final String PRIV_1004 = "1004"; + public static final String PRIV_2004 = "2004"; + public static final String PRIV_9001 = "9001"; + 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; + default -> escBuf.put(c); + case 'h' -> { + switch (readAndClearEscBuf()) { + case PRIV_12 -> execTextCursorEnableBlinking(); + case PRIV_25 -> execTextCursorEnableModeShow(); + case PRIV_1004 -> execEnableFocusReport(); + case PRIV_2004 -> execEnableBracketedPasteMode(); + case PRIV_9001 -> execEnableWin32InputMode(); + case String buf -> throw new AssertionError("Got CsiQ(h): %s".formatted(buf)); } - else if ("25".equals(buf)) { - execTextCursorEnableModeShow(); - escBuf.clear(); - mode = Mode.CHARS; + mode = Mode.CHARS; + } + case 'l' -> { + switch (readAndClearEscBuf()) { + case PRIV_12 -> execTextCursorDisableBlinking(); + case PRIV_25 -> execTextCursorDisableModeShow(); + case PRIV_1004 -> execDisableFocusReport(); + case PRIV_2004 -> execDisableBracketedPasteMode(); + case PRIV_9001 -> execDisableWin32InputMode(); + case String buf -> throw new AssertionError("Got CsiQ(l): %s".formatted(buf)); } - 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; + mode = Mode.CHARS; + } } } 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; + default -> escBuf.put(c); + case ';' -> { + switch (readAndClearEscBuf()) { + case "0", "2" -> mode = Mode.WINDOW_TITLE; + default -> throw new AssertionError(); } - 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 + default -> titleBuf.put(c); + case 0x07 -> { // bell, even though MSDN says longer form preferred execSetWindowTitle(); mode = Mode.CHARS; - break; - case 0x1b: - mode = Mode.WINDOW_TITLE_ESC; - break; + } + case 0x1b -> mode = Mode.WINDOW_TITLE_ESC; } } protected void processWindowTitleEsc(byte c) { switch (c) { - case '\\': + case '\\' -> { execSetWindowTitle(); mode = Mode.CHARS; - break; - default: - throw new AssertionError("Saw ... ESC " + c + " at " + countIn); + } + default -> throw new AssertionError("Saw ... ESC " + c + " at " + countIn); } } @@ -455,6 +408,30 @@ public class AnsiBufferedInputStream extends InputStream { // Don't care } + protected void execEnableFocusReport() { + // Don't care + } + + protected void execDisableFocusReport() { + // Don't care + } + + protected void execEnableBracketedPasteMode() { + // Don't care + } + + protected void execDisableBracketedPasteMode() { + // Don't care + } + + protected void execEnableWin32InputMode() { + // Don't care + } + + protected void execDisableWin32InputMode() { + // Don't care + } + protected void execEraseInDisplay() { // Because I have only one line, right? execEraseInLine(); @@ -462,15 +439,10 @@ public class AnsiBufferedInputStream extends InputStream { 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; + case 0 -> Arrays.fill(lineBuf.array(), lineBuf.position(), lineBuf.capacity(), + (byte) 0); + case 1 -> Arrays.fill(lineBuf.array(), 0, lineBuf.position() + 1, (byte) 0); + case 2 -> Arrays.fill(lineBuf.array(), (byte) 0); } } diff --git a/Ghidra/Test/DebuggerIntegrationTest/src/test.slow/java/agent/TraceRmiPythonClientTest.java b/Ghidra/Test/DebuggerIntegrationTest/src/test.slow/java/agent/TraceRmiPythonClientTest.java index 90c86fc9c7..1423f377ec 100644 --- a/Ghidra/Test/DebuggerIntegrationTest/src/test.slow/java/agent/TraceRmiPythonClientTest.java +++ b/Ghidra/Test/DebuggerIntegrationTest/src/test.slow/java/agent/TraceRmiPythonClientTest.java @@ -15,6 +15,7 @@ */ package agent; +import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.*; import java.io.*; @@ -47,6 +48,7 @@ import ghidra.framework.plugintool.util.*; import ghidra.pty.*; import ghidra.pty.PtyChild.Echo; import ghidra.pty.testutil.DummyProc; +import ghidra.pty.windows.AnsiBufferedInputStream; import ghidra.trace.model.Trace; import ghidra.trace.model.target.schema.PrimitiveTraceObjectSchema.MinimalSchemaContext; import ghidra.trace.model.target.schema.TraceObjectSchema.SchemaName; @@ -107,11 +109,14 @@ public class TraceRmiPythonClientTest extends AbstractGhidraHeadedDebuggerTest { protected Path getPathToPython() { try { - return Paths.get(DummyProc.which("python3")); + String py3path = DummyProc.which("python3"); + if (py3path != null && !py3path.contains("msys")) { + return Paths.get(py3path); + } } catch (RuntimeException e) { - return Paths.get(DummyProc.which("python")); } + return Paths.get(DummyProc.which("python")); } @Before @@ -119,6 +124,7 @@ public class TraceRmiPythonClientTest extends AbstractGhidraHeadedDebuggerTest { traceRmi = addPlugin(tool, TraceRmiPlugin.class); pathToPython = getPathToPython(); + Msg.info(this, "Using python: %s".formatted(pathToPython)); } protected void addAllDebuggerPlugins() throws PluginException { @@ -180,6 +186,14 @@ public class TraceRmiPythonClientTest extends AbstractGhidraHeadedDebuggerTest { protected ExecInPy execInPy(String script) throws IOException { Map env = new HashMap<>(System.getenv()); setPythonPath(env); + /** + * A new REPL was instroduced in Python 3.13. Unfortunately, the REPL is in play when we use + * a PTY, because it assumes that is a human. It will automatically insert indentation after + * pressing ENTER, which really goofs up our input. (It's worth noting, this happens even + * when copy-pasting a code block from notepad, which seems like a bug on their part.) This + * environment variable (at least for the moment) disables that new REPL. + */ + env.put("PYTHON_BASIC_REPL", "1"); Pty pty = PtyFactory.local().openpty(); PtySession session = @@ -187,7 +201,7 @@ public class TraceRmiPythonClientTest extends AbstractGhidraHeadedDebuggerTest { ByteArrayOutputStream out = new ByteArrayOutputStream(); new Thread(() -> { - InputStream is = pty.getParent().getInputStream(); + InputStream is = new AnsiBufferedInputStream(pty.getParent().getInputStream()); byte[] buf = new byte[1024]; while (true) { try { @@ -203,7 +217,12 @@ public class TraceRmiPythonClientTest extends AbstractGhidraHeadedDebuggerTest { }).start(); PrintWriter stdin = new PrintWriter(pty.getParent().getOutputStream()); - script.lines().forEach(stdin::println); // to transform newlines. + /** + * Because we're using a pty, we need to use CR instead of LF, i.e., to simulate the user + * pressing ENTER. + */ + script = script.replace("\n", "\r"); + stdin.write(script); stdin.flush(); return new ExecInPy(session, stdin, CompletableFuture.supplyAsync(() -> { try {