Merge remote-tracking branch 'origin/GP-6505_Dan_fixTraceRmiPythonClientTestsWindows--RB20260325--SQUASHED'

This commit is contained in:
Ryan Kurtz
2026-03-26 04:45:33 -04:00
4 changed files with 162 additions and 186 deletions
@@ -94,6 +94,7 @@ public class TerminalLayoutModel implements LayoutModel, VtHandler {
// Flags for what's been enabled // Flags for what's been enabled
protected boolean showCursor; protected boolean showCursor;
protected boolean bracketedPaste; protected boolean bracketedPaste;
protected boolean win32InputMode; // not implemented
protected boolean reportMousePress; protected boolean reportMousePress;
protected boolean reportMouseRelease; protected boolean reportMouseRelease;
protected boolean reportFocus; protected boolean reportFocus;
@@ -139,6 +140,7 @@ public class TerminalLayoutModel implements LayoutModel, VtHandler {
buffer = bufPrimary; buffer = bufPrimary;
bracketedPaste = false; bracketedPaste = false;
win32InputMode = false;
reportMousePress = false; reportMousePress = false;
reportMouseRelease = false; reportMouseRelease = false;
reportFocus = false; reportFocus = false;
@@ -498,6 +500,11 @@ public class TerminalLayoutModel implements LayoutModel, VtHandler {
this.bracketedPaste = en; this.bracketedPaste = en;
} }
@Override
public void handleWin32InputMode(boolean en) {
this.win32InputMode = en;
}
@Override @Override
public void handleSaveCursorPos() { public void handleSaveCursorPos() {
buffer.saveCursorPos(); buffer.saveCursorPos();
@@ -28,10 +28,8 @@ import ghidra.util.Msg;
/** /**
* The handler of parsed ANSI VT control sequences * The handler of parsed ANSI VT control sequences
*
* <p> * <p>
* Here are some of the resources where I found useful documentation: * Here are some of the resources where I found useful documentation:
*
* <ul> * <ul>
* <li><a href="https://invisible-island.net/xterm/ctlseqs/ctlseqs.html">XTerm Control * <li><a href="https://invisible-island.net/xterm/ctlseqs/ctlseqs.html">XTerm Control
* Sequences</a></li> * Sequences</a></li>
@@ -39,13 +37,11 @@ import ghidra.util.Msg;
* Terminal Control Escape Sequences</a></li> * Terminal Control Escape Sequences</a></li>
* <li><a href="https://en.wikipedia.org/wiki/ANSI_escape_code">Wikipedia: ANSI escape code</a></li> * <li><a href="https://en.wikipedia.org/wiki/ANSI_escape_code">Wikipedia: ANSI escape code</a></li>
* </ul> * </ul>
*
* <p> * <p>
* They were incredibly useful, even when experimentation was required to fill in details, because * 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 * 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 * documents and within this documentation, the following abbreviations are used for escape
* sequences: * sequences:
*
* <table> * <table>
* <tr> * <tr>
* <th>Abbreviation</th> * <th>Abbreviation</th>
@@ -78,13 +74,11 @@ import ghidra.util.Msg;
* <td>{@code "\007"}</td> * <td>{@code "\007"}</td>
* </tr> * </tr>
* </table> * </table>
*
* <p> * <p>
* The separation between the parser and the handler deals in state management. The parser manages * 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 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., * state machine. The state of the terminal, e.g., the current attributes, cursor position, etc.,
* are managed by the handler and its delegates. * are managed by the handler and its delegates.
*
* <p> * <p>
* For example, the Cursor Position sequence is documented as: * For example, the Cursor Position sequence is documented as:
* <p> * <p>
@@ -97,7 +91,6 @@ import ghidra.util.Msg;
* It will thus invoke the abstract {@link #handleMoveCursor(int, int)} method passing 12 and 39. * 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 * Note that 1 is subtracted from both parameters, because ANSI specifies 1-up indexing while Java
* lends itself to 0-up indexing. * lends itself to 0-up indexing.
*
* <p> * <p>
* The XTerm documentation, which is arguably the most thorough, presents the CSI commands * 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 * 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[] Q1048 = ascii("?1048");
public static final byte[] Q1049 = ascii("?1049"); public static final byte[] Q1049 = ascii("?1049");
public static final byte[] Q2004 = ascii("?2004"); public static final byte[] Q2004 = ascii("?2004");
public static final byte[] Q9001 = ascii("?9001");
/** /**
* An ANSI color specification * An ANSI color specification
*
* <p> * <p>
* We avoid going straight to AWT colors, 1) Because it provides better separation between the * 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 * 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 * A singleton representing the default color
*
* <p> * <p>
* The actual color selected will depend on context and use. Most notably, 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. * 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 * One of the eight standard ANSI colors
*
* <p> * <p>
* The actual color may be modified by other SGR attributes, notably {@link Intensity}. For * 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 * 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 * Get the standard color for the given numerical code
*
* <p> * <p>
* For example, the sequence {@code CSI [ 34 m} would use code 4 (blue). * 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 * One of the eight ANSI intense colors
*
* <p> * <p>
* Note that intense colors may also be specified using the standard color with the * Note that intense colors may also be specified using the standard color with the
* {@link Intensity#BOLD} attribute, depending on the command sequence. * {@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 * Get the intense color for the given numerical code
*
* <p> * <p>
* For example, the sequence {@code CSI [ 94 m} would use code 4 (blue). * 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 * Get the dim color for the given numerical code
*
* <p> * <p>
* For example, the sequence {@code CSI [ 34 m} would use code 4 (blue). * 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 * For 8-bit colors, one of the 216 colors from the RGB cube
*
* <p> * <p>
* The r, g, and b fields give the "step" number from 0 to 5, dimmest to brightest. * 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 * For 8-bit colors, one of the 24 grays
*
* <p> * <p>
* The v field is a value from 0 to 23, 0 being the dimmest, but not true black, and 23 being * 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. * the brightest, but not true white.
@@ -366,7 +351,6 @@ public interface VtHandler {
/** /**
* A 24-bit color * A 24-bit color
*
* <p> * <p>
* The r, g, and b fields are values from 0 to 255 dimmest to brightest. * 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. * Modifies the intensity of the character either by color or by font weight.
*
* <p> * <p>
* The renderer may choose a combination of strategies. For example, {@link #NORMAL} may be * 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 * 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 * Causes text to blink
*
* <p> * <p>
* If implemented, renderers should take care not to irritate the user. One option is to make * 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 * {@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 * For cursor and keypad, specifies normal or application mode
*
* <p> * <p>
* This affects the codes sent by the terminal. * 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 * Handle normal character output, i.e., place the character on the display
*
* <p> * <p>
* This excludes control sequences and control characters, e.g., tab, line feed. While we've not * 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 * 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. * Handle a character not part of an escape sequence.
*
* <p> * <p>
* This may include control characters, which are displatched appropriately by this method. * This may include control characters, which are displatched appropriately by this method.
* Additionally, this handles any exception thrown by {@link #handleChar(byte)}. * 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 <em>n</em> ; <em>m</em> ;} .... * Parse a sequence of integers in the form {@code <em>n</em> ; <em>m</em> ;} ....
*
* <p> * <p>
* This is designed to replace the {@link String#split(String)} and * This is designed to replace the {@link String#split(String)} and
* {@link Integer#parseInt(String)} pattern, which should avoid some unnecessary object * {@link Integer#parseInt(String)} pattern, which should avoid some unnecessary object
@@ -799,6 +777,9 @@ public interface VtHandler {
else if (bufEq(csiParam, Q2004)) { else if (bufEq(csiParam, Q2004)) {
handleBracketedPasteMode(en); handleBracketedPasteMode(en);
} }
else if (bufEq(csiParam, Q9001)) {
handleWin32InputMode(en);
}
else { else {
throw new UnknownCsiException(); throw new UnknownCsiException();
} }
@@ -1189,7 +1170,6 @@ public interface VtHandler {
/** /**
* Decode the 8-bit ANSI color. * Decode the 8-bit ANSI color.
*
* <p> * <p>
* Colors 0-15 are the standard + high-intensity. Colors 16-231 come from a 6x6x6 RGB cube. * 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. * Finally, colors 232-255 are 24 steps of gray scale.
@@ -1413,7 +1393,6 @@ public interface VtHandler {
/** /**
* Handle toggling of reverse video * Handle toggling of reverse video
*
* <p> * <p>
* This can be a bit confusing with default colors. In general, this means swapping the * 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 * foreground and background color specifications (not inverting the colors or mirroring or some
@@ -1449,7 +1428,6 @@ public interface VtHandler {
/** /**
* Handle toggling insert mode * Handle toggling insert mode
*
* <p> * <p>
* In insert mode, characters at and to the right of the cursor are shifted right to make room * 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 * 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 * Toggle blinking of the cursor
*
* <p> * <p>
* Renderers should take care not to irritate the user. Some possibilities are to blink slowly, * 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 * 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 * Switch to and from the alternate screen buffer, optionally clearing it
*
* <p> * <p>
* This will never clear the normal buffer. If the buffer does not change as a result of this * 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. * call, then the alternate buffer is not cleared, even if clearAlt is specified.
@@ -1545,6 +1521,20 @@ public interface VtHandler {
*/ */
void handleBracketedPasteMode(boolean en); void handleBracketedPasteMode(boolean en);
/**
* Toggle Win32 input mode
*
* <p>
* 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 * Handle a request to save the cursor position
*/ */
@@ -1573,7 +1563,6 @@ public interface VtHandler {
/** /**
* Handle an absolute cursor row movement command * Handle an absolute cursor row movement command
*
* <p> * <p>
* The column should remain the same, i.e., do <em>not</em> reset the column to 0. * The column should remain the same, i.e., do <em>not</em> reset the column to 0.
* *
@@ -1595,7 +1584,6 @@ public interface VtHandler {
/** /**
* Handle a request to save the terminal window's icon title * Handle a request to save the terminal window's icon title
*
* <p> * <p>
* "Icon titles" are a concept from the X Windows system. Do the closest equivalent, if anything * "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. * 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 * Handle a request to save the terminal window's title
*
* <p> * <p>
* Window titles are fairly applicable to all desktop windowing systems. The current title is * Window titles are fairly applicable to all desktop windowing systems. The current title is
* pushed to a stack of limited size. * 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 * Handle a request to restore the terminal window's icon title
*
* <p> * <p>
* The title is set to the one popped from the stack of saved window icon titles. * 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 * Handle a request to restore the terminal window's title
*
* <p> * <p>
* The title is set to the one popped from the stack of saved window titles. * 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 * Insert n lines at and below the cursor
*
* <p> * <p>
* Lines within the viewport are shifted down or deleted to make room for the new lines. * 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 * Delete n lines at and below the cursor
*
* <p> * <p>
* Lines within the viewport are shifted up, and new lines inserted at the bottom. * 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. * Set the range of rows (viewport) involved in scrolling.
*
* <p> * <p>
* This applies not only to {@link #handleScrollUp()} and {@link #handleScrollDown()}, but also * 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 * 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. * Scroll the display n lines down, considering only those lines in the scrolling range.
*
* <p> * <p>
* To be unambiguous, this of movement of the viewport. The viewport scrolls down, so the lines * 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. * 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. * Scroll the lines n slots down, considering only those lines in the scrolling range.
*
* <p> * <p>
* This is equivalent to scrolling the <em>viewport</em> n lines <em>up</em>. This method exists * This is equivalent to scrolling the <em>viewport</em> n lines <em>up</em>. This method exists
* in attempt to reflect "up" and "down" correctly in the documentation. Unfortunately, the * 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. * Scroll the lines n slots up, considering only those lines in the scrolling range.
*
* <p> * <p>
* The is equivalent to scrolling the <em>viewport</em> n lines <em>down</em>. This method * The is equivalent to scrolling the <em>viewport</em> n lines <em>down</em>. This method
* exists in attempt to reflect "up" and "down" correctly in the documentation. Unfortunately, * 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 * Handle a request to fully reset the terminal
*
* <p> * <p>
* All buffers should be cleared and all state variables, positions, attributes, etc., should be * All buffers should be cleared and all state variables, positions, attributes, etc., should be
* reset to their defaults. * reset to their defaults.
@@ -21,7 +21,6 @@ import java.io.InputStream;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.Arrays; import java.util.Arrays;
import java.util.Set;
import java.util.stream.Stream; import java.util.stream.Stream;
import ghidra.util.Msg; import ghidra.util.Msg;
@@ -122,34 +121,17 @@ public class AnsiBufferedInputStream extends InputStream {
return -1; return -1;
} }
byte c = (byte) ci; byte c = (byte) ci;
//printDebugChar(c); // printDebugChar(c);
switch (mode) { switch (mode) {
case CHARS: case CHARS -> processChars(c);
processChars(c); case ESC -> processEsc(c);
break; case CSI -> processCsi(c);
case ESC: case CSI_p -> processCsiParamOrCommand(c);
processEsc(c); case CSI_Q -> processCsiQ(c);
break; case OSC -> processOsc(c);
case CSI: case WINDOW_TITLE -> processWindowTitle(c);
processCsi(c); case WINDOW_TITLE_ESC -> processWindowTitleEsc(c);
break; default -> throw new AssertionError();
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++; countIn++;
return c; return c;
@@ -191,180 +173,151 @@ public class AnsiBufferedInputStream extends InputStream {
protected void processChars(byte c) { protected void processChars(byte c) {
switch (c) { switch (c) {
case 0x08: default -> appendChar(c);
case '\b' -> {
if (lineBuf.get(lineBuf.position() - 1) == ' ') { if (lineBuf.get(lineBuf.position() - 1) == ' ') {
lineBuf.position(lineBuf.position() - 1); lineBuf.position(lineBuf.position() - 1);
} }
break; }
case '\n': case '\n' -> bakeLine();
//appendChar(c); case '\r' -> lineBuf.position(0);
bakeLine(); case 0x1b -> mode = Mode.ESC;
break;
case 0x1b:
mode = Mode.ESC;
break;
default:
appendChar(c);
break;
} }
} }
protected void processEsc(byte c) { protected void processEsc(byte c) {
switch (c) { switch (c) {
case '[': case '[' -> mode = Mode.CSI;
mode = Mode.CSI; case ']' -> mode = Mode.OSC;
break; default -> throw new AssertionError("Saw 'ESC " + c + "' at " + countIn);
case ']':
mode = Mode.OSC;
break;
default:
throw new AssertionError("Saw 'ESC " + c + "' at " + countIn);
} }
} }
protected void processCsi(byte c) { protected void processCsi(byte c) {
switch (c) { switch (c) {
default: default -> processCsiParamOrCommand(c);
processCsiParamOrCommand(c); case '?' -> mode = Mode.CSI_Q;
break;
case '?':
mode = Mode.CSI_Q;
break;
} }
} }
protected void processCsiParamOrCommand(byte c) { protected void processCsiParamOrCommand(byte c) {
switch (c) { switch (c) {
default: default -> escBuf.put(c);
escBuf.put(c); case 'A' -> {
break;
case 'A':
execCursorUp(); execCursorUp();
mode = Mode.CHARS; mode = Mode.CHARS;
break; }
case 'B': case 'B' -> {
execCursorDown(); execCursorDown();
mode = Mode.CHARS; mode = Mode.CHARS;
break; }
case 'C': case 'C' -> {
execCursorForward(); execCursorForward();
mode = Mode.CHARS; mode = Mode.CHARS;
break; }
case 'D': case 'D' -> {
execCursorBackward(); execCursorBackward();
mode = Mode.CHARS; mode = Mode.CHARS;
break; }
case 'G': case 'G' -> {
execCursorCharAbsolute(); execCursorCharAbsolute();
mode = Mode.CHARS; mode = Mode.CHARS;
break; }
case 'H': case 'H' -> {
execCursorPosition(); execCursorPosition();
mode = Mode.CHARS; mode = Mode.CHARS;
break; }
case 'J': case 'J' -> {
execEraseInDisplay(); execEraseInDisplay();
mode = Mode.CHARS; mode = Mode.CHARS;
break; }
case 'K': case 'K' -> {
execEraseInLine(); execEraseInLine();
mode = Mode.CHARS; mode = Mode.CHARS;
break; }
case 'X': case 'X' -> {
execEraseCharacter(); execEraseCharacter();
mode = Mode.CHARS; mode = Mode.CHARS;
break; }
case 'm': case 'm' -> {
execSetGraphicsRendition(); execSetGraphicsRendition();
mode = Mode.CHARS; mode = Mode.CHARS;
break; }
case 'h': case 'h' -> {
execPrivateSequence(true); execPrivateSequence(true);
mode = Mode.CHARS; mode = Mode.CHARS;
break; }
case 'l': case 'l' -> {
execPrivateSequence(false); execPrivateSequence(false);
mode = Mode.CHARS; 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) { protected void processCsiQ(byte c) {
String buf;
switch (c) { switch (c) {
default: default -> escBuf.put(c);
escBuf.put(c); case 'h' -> {
break; switch (readAndClearEscBuf()) {
case 'h': case PRIV_12 -> execTextCursorEnableBlinking();
buf = readAndClearEscBuf(); case PRIV_25 -> execTextCursorEnableModeShow();
if ("12".equals(buf)) { case PRIV_1004 -> execEnableFocusReport();
execTextCursorEnableBlinking(); case PRIV_2004 -> execEnableBracketedPasteMode();
escBuf.clear(); case PRIV_9001 -> execEnableWin32InputMode();
mode = Mode.CHARS; case String buf -> throw new AssertionError("Got CsiQ(h): %s".formatted(buf));
} }
else if ("25".equals(buf)) { mode = Mode.CHARS;
execTextCursorEnableModeShow(); }
escBuf.clear(); case 'l' -> {
mode = Mode.CHARS; 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 { mode = Mode.CHARS;
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) { protected void processOsc(byte c) {
switch (c) { switch (c) {
default: default -> escBuf.put(c);
escBuf.put(c); case ';' -> {
break; switch (readAndClearEscBuf()) {
case ';': case "0", "2" -> mode = Mode.WINDOW_TITLE;
if (Set.of("0", "2").contains(readAndClearEscBuf())) { default -> throw new AssertionError();
mode = Mode.WINDOW_TITLE;
escBuf.clear();
break;
} }
throw new AssertionError(); }
} }
} }
protected void processWindowTitle(byte c) { protected void processWindowTitle(byte c) {
switch (c) { switch (c) {
default: default -> titleBuf.put(c);
titleBuf.put(c); case 0x07 -> { // bell, even though MSDN says longer form preferred
break;
case 0x07: // bell, even though MSDN says longer form preferred
execSetWindowTitle(); execSetWindowTitle();
mode = Mode.CHARS; mode = Mode.CHARS;
break; }
case 0x1b: case 0x1b -> mode = Mode.WINDOW_TITLE_ESC;
mode = Mode.WINDOW_TITLE_ESC;
break;
} }
} }
protected void processWindowTitleEsc(byte c) { protected void processWindowTitleEsc(byte c) {
switch (c) { switch (c) {
case '\\': case '\\' -> {
execSetWindowTitle(); execSetWindowTitle();
mode = Mode.CHARS; mode = Mode.CHARS;
break; }
default: default -> throw new AssertionError("Saw <ST> ... ESC " + c + " at " + countIn);
throw new AssertionError("Saw <ST> ... ESC " + c + " at " + countIn);
} }
} }
@@ -459,6 +412,30 @@ public class AnsiBufferedInputStream extends InputStream {
// Don't care // 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() { protected void execEraseInDisplay() {
// Because I have only one line, right? // Because I have only one line, right?
execEraseInLine(); execEraseInLine();
@@ -466,15 +443,10 @@ public class AnsiBufferedInputStream extends InputStream {
protected void execEraseInLine() { protected void execEraseInLine() {
switch (parseNumericBuffer()) { switch (parseNumericBuffer()) {
case 0: case 0 -> Arrays.fill(lineBuf.array(), lineBuf.position(), lineBuf.capacity(),
Arrays.fill(lineBuf.array(), lineBuf.position(), lineBuf.capacity(), (byte) 0); (byte) 0);
break; case 1 -> Arrays.fill(lineBuf.array(), 0, lineBuf.position() + 1, (byte) 0);
case 1: case 2 -> Arrays.fill(lineBuf.array(), (byte) 0);
Arrays.fill(lineBuf.array(), 0, lineBuf.position() + 1, (byte) 0);
break;
case 2:
Arrays.fill(lineBuf.array(), (byte) 0);
break;
} }
} }
@@ -15,6 +15,7 @@
*/ */
package agent; package agent;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import java.io.*; import java.io.*;
@@ -47,6 +48,7 @@ import ghidra.framework.plugintool.util.*;
import ghidra.pty.*; import ghidra.pty.*;
import ghidra.pty.PtyChild.Echo; import ghidra.pty.PtyChild.Echo;
import ghidra.pty.testutil.DummyProc; import ghidra.pty.testutil.DummyProc;
import ghidra.pty.windows.AnsiBufferedInputStream;
import ghidra.trace.model.Trace; import ghidra.trace.model.Trace;
import ghidra.trace.model.target.schema.PrimitiveTraceObjectSchema.MinimalSchemaContext; import ghidra.trace.model.target.schema.PrimitiveTraceObjectSchema.MinimalSchemaContext;
import ghidra.trace.model.target.schema.TraceObjectSchema.SchemaName; import ghidra.trace.model.target.schema.TraceObjectSchema.SchemaName;
@@ -107,11 +109,14 @@ public class TraceRmiPythonClientTest extends AbstractGhidraHeadedDebuggerTest {
protected Path getPathToPython() { protected Path getPathToPython() {
try { 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) { catch (RuntimeException e) {
return Paths.get(DummyProc.which("python"));
} }
return Paths.get(DummyProc.which("python"));
} }
@Before @Before
@@ -119,6 +124,7 @@ public class TraceRmiPythonClientTest extends AbstractGhidraHeadedDebuggerTest {
traceRmi = addPlugin(tool, TraceRmiPlugin.class); traceRmi = addPlugin(tool, TraceRmiPlugin.class);
pathToPython = getPathToPython(); pathToPython = getPathToPython();
Msg.info(this, "Using python: %s".formatted(pathToPython));
} }
protected void addAllDebuggerPlugins() throws PluginException { protected void addAllDebuggerPlugins() throws PluginException {
@@ -180,6 +186,14 @@ public class TraceRmiPythonClientTest extends AbstractGhidraHeadedDebuggerTest {
protected ExecInPy execInPy(String script) throws IOException { protected ExecInPy execInPy(String script) throws IOException {
Map<String, String> env = new HashMap<>(System.getenv()); Map<String, String> env = new HashMap<>(System.getenv());
setPythonPath(env); 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(); Pty pty = PtyFactory.local().openpty();
PtySession session = PtySession session =
@@ -187,7 +201,7 @@ public class TraceRmiPythonClientTest extends AbstractGhidraHeadedDebuggerTest {
ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();
new Thread(() -> { new Thread(() -> {
InputStream is = pty.getParent().getInputStream(); InputStream is = new AnsiBufferedInputStream(pty.getParent().getInputStream());
byte[] buf = new byte[1024]; byte[] buf = new byte[1024];
while (true) { while (true) {
try { try {
@@ -203,7 +217,12 @@ public class TraceRmiPythonClientTest extends AbstractGhidraHeadedDebuggerTest {
}).start(); }).start();
PrintWriter stdin = new PrintWriter(pty.getParent().getOutputStream()); 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(); stdin.flush();
return new ExecInPy(session, stdin, CompletableFuture.supplyAsync(() -> { return new ExecInPy(session, stdin, CompletableFuture.supplyAsync(() -> {
try { try {