Merge remote-tracking branch 'origin/Ghidra_12.1'

This commit is contained in:
Ryan Kurtz
2026-04-19 05:45:33 -04:00
18 changed files with 317 additions and 43 deletions

View File

@@ -185,20 +185,20 @@ def ghidra_trace_connect(address: Optional[str] = None) -> None:
raise RuntimeError("port must be numeric")
def ghidra_trace_listen(address: str = '0.0.0.0:0') -> None:
def ghidra_trace_listen(address: str = '127.0.0.1:0') -> None:
"""Listen for Ghidra to connect for tracing.
Takes an optional address for the host and port on which to listen.
Either the form 'host:port' or just 'port'. If omitted, it will bind
to an ephemeral port on all interfaces. If only the port is given,
it will bind to that port on all interfaces. This command will block
until the connection is established.
to an ephemeral port on localhost. If only the port is given, it will
bind to that port on localhost. This command will block until the
connection is established.
"""
STATE.require_no_client()
parts = address.split(':')
if len(parts) == 1:
host, port = '0.0.0.0', parts[0]
host, port = '127.0.0.1', parts[0]
elif len(parts) == 2:
host, port = parts
else:
@@ -1526,8 +1526,8 @@ def put_exceptions() -> None:
@util.dbg.eng_thread
def put_single_exception(obj: TraceObject, objpath: str,
p: DbgEng._DEBUG_EXCEPTION_FILTER_PARAMETERS,
def put_single_exception(obj: TraceObject, objpath: str,
p: DbgEng._DEBUG_EXCEPTION_FILTER_PARAMETERS,
offset: int, index: int, specific: bool) -> None:
exc_name = "None"
if specific is True:

View File

@@ -170,21 +170,21 @@ def ghidra_trace_connect(address: Optional[str] = None) -> None:
raise RuntimeError("port must be numeric")
def ghidra_trace_listen(address: str = '0.0.0.0:0') -> None:
def ghidra_trace_listen(address: str = '127.0.0.1:0') -> None:
"""
Listen for Ghidra to connect for tracing
Takes an optional address for the host and port on which to listen. Either
the form 'host:port' or just 'port'. If omitted, it will bind to an
ephemeral port on all interfaces. If only the port is given, it will bind to
that port on all interfaces. This command will block until the connection is
established.
Takes an optional address for the host and port on which to listen.
Either the form 'host:port' or just 'port'. If omitted, it will bind
to an ephemeral port on localhost. If only the port is given, it will
bind to that port on localhost. This command will block until the
connection is established.
"""
STATE.require_no_client()
parts = address.split(':')
if len(parts) == 1:
host, port = '0.0.0.0', parts[0]
host, port = '127.0.0.1', parts[0]
elif len(parts) == 2:
host, port = parts
else:
@@ -199,7 +199,7 @@ def ghidra_trace_listen(address: str = '0.0.0.0:0') -> None:
c, (chost, cport) = s.accept()
s.close()
print("Connection from {}:{}".format(chost, cport))
STATE.client = Client(c, "dbgeng.dll", methods.REGISTRY)
STATE.client = Client(c, "drgn", methods.REGISTRY)
except ValueError:
raise RuntimeError("port must be numeric")
@@ -786,7 +786,7 @@ def ghidra_trace_set_value(path: str, key: str, value: Any,
Set a value (attribute or element) in the Ghidra trace's object tree.
A void value implies removal.
NOTE: The type of an expression may be subject to the dbgeng's current
NOTE: The type of an expression may be subject to drgn's current
language. which current defaults to DEBUG_EXPR_CPLUSPLUS (vs DEBUG_EXPR_MASM).
For most non-primitive cases, we are punting to the Python API.
"""

View File

@@ -236,9 +236,9 @@ def ghidra_trace_listen(address: Optional[str] = None, *, is_mi: bool,
Takes an optional address for the host and port on which to listen.
Either the form 'host:port' or just 'port'. If omitted, it will bind
to an ephemeral port on all interfaces. If only the port is given,
it will bind to that port on all interfaces. This command will block
until the connection is established.
to an ephemeral port on localhost. If only the port is given, it will
bind to that port on localhost. This command will block until the
connection is established.
"""
STATE.require_no_client()
@@ -247,13 +247,13 @@ def ghidra_trace_listen(address: Optional[str] = None, *, is_mi: bool,
if address is not None:
parts = address.split(':')
if len(parts) == 1:
host, port = '0.0.0.0', parts[0]
host, port = '127.0.0.1', parts[0]
elif len(parts) == 2:
host, port = parts
else:
raise gdb.GdbError("address must be 'port' or 'host:port'")
else:
host, port = '0.0.0.0', 0
host, port = '127.0.0.1', 0
try:
s = socket.socket()
s.bind((host, int(port)))

View File

@@ -302,23 +302,23 @@ def ghidra_trace_listen(debugger: lldb.SBDebugger, command: str,
Usage: ghidra trace listen [ADDRESS]
ADDRESS must be PORT or HOST:PORT
Takes an optional address for the host and port on which to listen. Either
the form 'host:port' or just 'port'. If omitted, it will bind to an
ephemeral port on all interfaces. If only the port is given, it will bind to
that port on all interfaces. This command will block until the connection is
established.
Takes an optional address for the host and port on which to listen.
Either the form 'host:port' or just 'port'. If omitted, it will bind
to an ephemeral port on localhost. If only the port is given, it will
bind to that port on localhost. This command will block until the
connection is established.
"""
args = shlex.split(command)
host: str
port: Union[str, int]
if len(args) == 0:
host, port = '0.0.0.0', 0
host, port = '127.0.0.1', 0
elif len(args) == 1:
address = args[0]
parts = address.split(':')
if len(parts) == 1:
host, port = '0.0.0.0', parts[0]
host, port = '127.0.0.1', parts[0]
elif len(parts) == 2:
host, port = parts
else:

View File

@@ -182,20 +182,20 @@ def ghidra_trace_connect(address: Optional[str] = None) -> None:
raise RuntimeError("port must be numeric")
def ghidra_trace_listen(address: str = '0.0.0.0:0') -> None:
def ghidra_trace_listen(address: str = '127.0.0.1:0') -> None:
"""Listen for Ghidra to connect for tracing.
Takes an optional address for the host and port on which to listen.
Either the form 'host:port' or just 'port'. If omitted, it will bind
to an ephemeral port on all interfaces. If only the port is given,
it will bind to that port on all interfaces. This command will block
until the connection is established.
to an ephemeral port on localhost. If only the port is given, it will
bind to that port on localhost. This command will block until the
connection is established.
"""
STATE.require_no_client()
parts = address.split(':')
if len(parts) == 1:
host, port = '0.0.0.0', parts[0]
host, port = '127.0.0.1', parts[0]
elif len(parts) == 2:
host, port = parts
else:

View File

@@ -156,7 +156,7 @@ public class JdiCommands {
public void ghidraTraceListen(String address) {
// TODO: UNTESTED
state.requireNoClient();
String host = "0.0.0.0";
String host = "127.0.0.1";
int port = 0;
if (address != null) {
String[] parts = address.split(":");

View File

@@ -70,10 +70,10 @@ import ghidra.util.exception.DuplicateFileException;
public class TraceRmiHandler extends AbstractTraceRmiConnection {
/**
* NOTE: This can't just be Application.getApplicationVersion(), because the Python client only
* specifies up to the minor, not patch, release.
* NOTE: This can't just be {@link Application#getApplicationVersion()}, because the Python
* client only specifies up to the minor, not patch, release.
*/
public static final String VERSION = "12.0";
public static final String VERSION = "12.1";
protected static class VersionMismatchError extends TraceRmiError {
public VersionMismatchError(String remote) {

View File

@@ -79,7 +79,7 @@ public class TraceRmiPlugin extends Plugin implements InternalTraceRmiService {
@SuppressWarnings("unused")
private final Wiring autoServiceWiring;
private SocketAddress serverAddress = new InetSocketAddress("0.0.0.0", DEFAULT_PORT);
private SocketAddress serverAddress = new InetSocketAddress("127.0.0.1", DEFAULT_PORT);
private TraceRmiServer server;
private final Set<TraceRmiHandler> handlers = new LinkedHashSet<>();

View File

@@ -17,7 +17,6 @@ classifiers = [
"Operating System :: OS Independent",
]
dependencies = [
"ghidratrace==12.1",
"protobuf>=6.31.0",
]

View File

@@ -48,7 +48,7 @@ from .util import send_delimited, recv_delimited
# Other places to change:
# * every pyproject.toml file (incl. deps)
# * TraceRmiHandler.VERSION
VERSION = '12.0'
VERSION = '12.1'
E = TypeVar('E')

View File

@@ -37,7 +37,12 @@ import ghidra.trace.model.thread.TraceThread;
public class TraceRmiTargetTest extends AbstractGhidraHeadedDebuggerTest {
class MyTraceRmiConnection extends TestTraceRmiConnection {
@Test
public void testVersionConsistency() {
assertVersionMatchesApplication(TraceRmiHandler.VERSION);
}
static class MyTraceRmiConnection extends TestTraceRmiConnection {
@Override
protected DebuggerTraceManagerService getTraceManager() {

View File

@@ -20,13 +20,15 @@ import static org.junit.Assert.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.*;
import java.nio.file.Files;
import java.util.*;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.*;
import javax.swing.tree.TreePath;
@@ -43,6 +45,8 @@ import docking.action.DockingActionIf;
import docking.widgets.table.DynamicTableColumn;
import docking.widgets.tree.GTree;
import docking.widgets.tree.GTreeNode;
import generic.Unique;
import generic.jar.ResourceFile;
import ghidra.GhidraTestApplicationLayout;
import ghidra.app.nav.Navigatable;
import ghidra.app.plugin.core.debug.gui.action.BasicAutoReadMemorySpec;
@@ -55,6 +59,7 @@ import ghidra.app.util.viewer.listingpanel.ListingPanel;
import ghidra.async.AsyncTestUtils;
import ghidra.debug.api.action.*;
import ghidra.docking.settings.SettingsImpl;
import ghidra.framework.Application;
import ghidra.framework.model.*;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.database.ProgramDB;
@@ -125,6 +130,45 @@ public abstract class AbstractGhidraHeadedDebuggerTest
public static final String LANGID_TOYBE64 = "Toy:BE:64:default";
public static void assertVersionMatchesApplication(String version) {
String applicationVersion = Application.getApplicationVersion();
List<String> partsExp = List.of(applicationVersion.split("\\."));
List<String> partsAct = List.of(version.split("\\."));
assertTrue("Version %s cannot be more specific than application version %s".formatted(
version, applicationVersion), partsExp.size() >= partsAct.size());
assertEquals("Version %s is not consistent with application version %s".formatted(version,
applicationVersion), partsAct, partsExp.subList(0, partsAct.size()));
}
public static List<String> readToml(String module) throws IOException {
ResourceFile toml = Application.getModuleFile(module, "src/main/py/pyproject.toml");
try (BufferedReader reader = new BufferedReader(new FileReader(toml.getFile(false)))) {
return reader.lines().toList();
}
}
private static String group(CharSequence seq, Pattern pat) {
Matcher matcher = pat.matcher(seq);
assertTrue(matcher.matches());
return matcher.group(1);
}
public static String parseVersionFromToml(List<String> toml) throws IOException {
Pattern versionEq = Pattern.compile("version = \"(.*)\"");
return Unique.assertOne(toml.stream()
.filter(versionEq.asMatchPredicate())
.map(l -> group(l, versionEq)));
}
public static String parseGhidraTraceDepFromToml(List<String> toml) throws IOException {
Pattern ghidraTraceDep = Pattern.compile("\\s+\"ghidratrace==(.*)\",?");
return Unique.assertOne(toml.stream()
.dropWhile(l -> !"dependencies = [".equals(l))
.takeWhile(l -> !"]".equals(l))
.filter(ghidraTraceDep.asMatchPredicate())
.map(l -> group(l, ghidraTraceDep)));
}
protected static byte[] arr(String hex) {
return NumericUtilities.convertStringToBytes(hex);
}

View File

@@ -339,6 +339,12 @@ public class TraceRmiPythonClientTest extends AbstractGhidraHeadedDebuggerTest {
waitFor(() -> tb.trace.getCurrentTransactionInfo() == null);
}
@Test
public void testTomlVersionConsistency() throws IOException {
List<String> toml = readToml("Debugger-rmi-trace");
assertVersionMatchesApplication(parseVersionFromToml(toml));
}
@Test
public void testConnect() throws Exception {
runThrowError(addr -> """

View File

@@ -0,0 +1,44 @@
/* ###
* 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.dbgeng.rmi;
import java.io.IOException;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
public class DbgEngVersionTest extends AbstractGhidraHeadedDebuggerTest {
List<String> toml;
@Before
public void read() throws IOException {
toml = readToml("Debugger-agent-dbgeng");
}
@Test
public void testTomlVersionConsistency() throws IOException {
assertVersionMatchesApplication(parseVersionFromToml(toml));
}
@Test
public void testTomlGhidratraceDepVersion() throws IOException {
assertVersionMatchesApplication(parseGhidraTraceDepFromToml(toml));
}
}

View File

@@ -0,0 +1,44 @@
/* ###
* 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.drgn.rmi;
import java.io.IOException;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
public class DrgnVersionTest extends AbstractGhidraHeadedDebuggerTest {
List<String> toml;
@Before
public void read() throws IOException {
toml = readToml("Debugger-agent-drgn");
}
@Test
public void testTomlVersionConsistency() throws IOException {
assertVersionMatchesApplication(parseVersionFromToml(toml));
}
@Test
public void testTomlGhidratraceDepVersion() throws IOException {
assertVersionMatchesApplication(parseGhidraTraceDepFromToml(toml));
}
}

View File

@@ -0,0 +1,44 @@
/* ###
* 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.rmi;
import java.io.IOException;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
public class GdbVersionTest extends AbstractGhidraHeadedDebuggerTest {
List<String> toml;
@Before
public void read() throws IOException {
toml = readToml("Debugger-agent-gdb");
}
@Test
public void testTomlVersionConsistency() throws IOException {
assertVersionMatchesApplication(parseVersionFromToml(toml));
}
@Test
public void testTomlGhidratraceDepVersion() throws IOException {
assertVersionMatchesApplication(parseGhidraTraceDepFromToml(toml));
}
}

View File

@@ -0,0 +1,44 @@
/* ###
* 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.lldb.rmi;
import java.io.IOException;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
public class LldbVersionTest extends AbstractGhidraHeadedDebuggerTest {
List<String> toml;
@Before
public void read() throws IOException {
toml = readToml("Debugger-agent-lldb");
}
@Test
public void testTomlVersionConsistency() throws IOException {
assertVersionMatchesApplication(parseVersionFromToml(toml));
}
@Test
public void testTomlGhidratraceDepVersion() throws IOException {
assertVersionMatchesApplication(parseGhidraTraceDepFromToml(toml));
}
}

View File

@@ -0,0 +1,44 @@
/* ###
* 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.x64dbg.rmi;
import java.io.IOException;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
public class X64dbgVersionTest extends AbstractGhidraHeadedDebuggerTest {
List<String> toml;
@Before
public void read() throws IOException {
toml = readToml("Debugger-agent-x64dbg");
}
@Test
public void testTomlVersionConsistency() throws IOException {
assertVersionMatchesApplication(parseVersionFromToml(toml));
}
@Test
public void testTomlGhidratraceDepVersion() throws IOException {
assertVersionMatchesApplication(parseGhidraTraceDepFromToml(toml));
}
}