Skip to content

Commit 92a7b29

Browse files
authored
Merge pull request #47 from zinja-coder/debug_tools_jadx_ai_mcp_4
- Added debug get threads mcp tool - Added debug get variables mcp tool
2 parents d5f9436 + 40b7d27 commit 92a7b29

File tree

2 files changed

+154
-1
lines changed

2 files changed

+154
-1
lines changed

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
<groupId>com.zin.jadxaimcp</groupId>
77
<artifactId>jadx-ai-mcp</artifactId>
8-
<version>3.3.6</version>
8+
<version>4.0.0</version>
99

1010
<properties>
1111
<maven.compiler.source>11</maven.compiler.source>

src/main/java/com/zin/jadxaimcp/JadxAIMCP.java

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@
4444
import org.w3c.dom.Document;
4545

4646
import javax.swing.*;
47+
import javax.swing.tree.DefaultMutableTreeNode;
48+
import javax.swing.tree.TreeNode;
4749
import java.awt.*;
4850
import java.awt.event.ActionEvent;
4951
import java.awt.event.ActionListener;
@@ -251,6 +253,8 @@ public void start() {
251253
app.get("/health", this::handleHealth);
252254

253255
app.get("/debug/stack-frames", this::handleGetStackFrames);
256+
app.get("/debug/variables", this::handleGetVariables);
257+
app.get("/debug/threads", this::handleGetThreads);
254258

255259
logger.info(JadxAIMCPBanner.banner);
256260
logger.info(
@@ -1562,6 +1566,122 @@ private void handleGetStackFrames(Context ctx) {
15621566
}
15631567
}
15641568

1569+
1570+
/**
1571+
* Get threads from JComboBox UI component
1572+
* Uses DefaultComboBoxModel API
1573+
*/
1574+
private void handleGetThreads(Context ctx) {
1575+
try {
1576+
JDebuggerPanel debuggerPanel = mainWindow.getDebuggerPanel();
1577+
if (debuggerPanel == null) {
1578+
ctx.status(400).json(Map.of("error", "Debugger panel not initialized"));
1579+
return;
1580+
}
1581+
1582+
IDebugController controller = debuggerPanel.getDbgController();
1583+
if (controller == null || !controller.isDebugging()) {
1584+
ctx.status(400).json(Map.of("error", "Debugger not attached"));
1585+
return;
1586+
}
1587+
1588+
try {
1589+
// Access threadBox through reflection
1590+
java.lang.reflect.Field threadField = JDebuggerPanel.class.getDeclaredField("threadBox");
1591+
threadField.setAccessible(true);
1592+
@SuppressWarnings("unchecked")
1593+
JComboBox<JDebuggerPanel.IListElement> threadBox =
1594+
(JComboBox<JDebuggerPanel.IListElement>) threadField.get(debuggerPanel);
1595+
1596+
// Get the combo box model
1597+
DefaultComboBoxModel<JDebuggerPanel.IListElement> model =
1598+
(DefaultComboBoxModel<JDebuggerPanel.IListElement>) threadBox.getModel();
1599+
1600+
List<String> threads = new ArrayList<>();
1601+
String selectedThread = null;
1602+
1603+
// Iterate through all elements in the combo box
1604+
for (int i = 0; i < model.getSize(); i++) {
1605+
JDebuggerPanel.IListElement element = model.getElementAt(i);
1606+
threads.add(element.toString());
1607+
}
1608+
1609+
// Get selected thread
1610+
Object selected = model.getSelectedItem();
1611+
if (selected != null) {
1612+
selectedThread = selected.toString();
1613+
}
1614+
1615+
Map<String, Object> result = new HashMap<>();
1616+
result.put("threads", threads);
1617+
result.put("selectedThread", selectedThread);
1618+
result.put("count", threads.size());
1619+
1620+
ctx.json(result);
1621+
} catch (NoSuchFieldException | IllegalAccessException e) {
1622+
ctx.status(500).json(Map.of("error", "Failed to access thread box: " + e.getMessage()));
1623+
}
1624+
} catch (Exception e) {
1625+
logger.error("JADX AI MCP Debug Error: " + e.getMessage(), e);
1626+
ctx.status(500).json(Map.of("error", "Failed to get threads: " + e.getMessage()));
1627+
}
1628+
}
1629+
1630+
/**
1631+
* Get all variables (registers and 'this' object fields)
1632+
* Extracts from JTree UI components: regTreeNode and thisTreeNode
1633+
*/
1634+
private void handleGetVariables(Context ctx) {
1635+
try {
1636+
JDebuggerPanel debuggerPanel = mainWindow.getDebuggerPanel();
1637+
if (debuggerPanel == null) {
1638+
ctx.status(400).json(Map.of("error", "Debugger panel not initialized"));
1639+
return;
1640+
}
1641+
1642+
IDebugController controller = debuggerPanel.getDbgController();
1643+
if (controller == null || !controller.isDebugging()) {
1644+
ctx.status(400).json(Map.of("error", "Debugger not attached"));
1645+
return;
1646+
}
1647+
1648+
if (!controller.isSuspended()) {
1649+
ctx.status(400).json(Map.of("error", "Process not suspended. Variables only available when paused."));
1650+
return;
1651+
}
1652+
1653+
Map<String, Object> variables = new HashMap<>();
1654+
1655+
// Access the variable tree through reflection since fields are private
1656+
try {
1657+
// Get regTreeNode (registers/local variables)
1658+
java.lang.reflect.Field regField = JDebuggerPanel.class.getDeclaredField("regTreeNode");
1659+
regField.setAccessible(true);
1660+
DefaultMutableTreeNode regTreeNode = (DefaultMutableTreeNode) regField.get(debuggerPanel);
1661+
1662+
// Get thisTreeNode (object fields)
1663+
java.lang.reflect.Field thisField = JDebuggerPanel.class.getDeclaredField("thisTreeNode");
1664+
thisField.setAccessible(true);
1665+
DefaultMutableTreeNode thisTreeNode = (DefaultMutableTreeNode) thisField.get(debuggerPanel);
1666+
1667+
// Extract register variables
1668+
List<Map<String, Object>> registers = extractTreeNodeData(regTreeNode);
1669+
variables.put("registers", registers);
1670+
1671+
// Extract 'this' object fields
1672+
List<Map<String, Object>> thisFields = extractTreeNodeData(thisTreeNode);
1673+
variables.put("thisObject", thisFields);
1674+
1675+
ctx.json(variables);
1676+
} catch (NoSuchFieldException | IllegalAccessException e) {
1677+
ctx.status(500).json(Map.of("error", "Failed to access tree nodes: " + e.getMessage()));
1678+
}
1679+
} catch (Exception e) {
1680+
logger.error("JADX AI MCP Debug Error: " + e.getMessage(), e);
1681+
ctx.status(500).json(Map.of("error", "Failed to get variables: " + e.getMessage()));
1682+
}
1683+
}
1684+
15651685
// -------------------------- helper methods to assist the request handler methods -------------------------- //
15661686
private String getSelectedTabTitle() {
15671687
JTabbedPane tabs = mainWindow.getTabbedPane();
@@ -1588,6 +1708,39 @@ private JTextArea findTextArea(Component component) {
15881708
return null;
15891709
}
15901710

1711+
/**
1712+
* Helper method to extract data from JTree nodes
1713+
* Uses standard Swing TreeNode API
1714+
*/
1715+
private List<Map<String, Object>> extractTreeNodeData(DefaultMutableTreeNode node) {
1716+
List<Map<String, Object>> result = new ArrayList<>();
1717+
1718+
// Iterate through all children of the node
1719+
for (int i = 0; i < node.getChildCount(); i++) {
1720+
TreeNode childNode = node.getChildAt(i);
1721+
1722+
if (childNode instanceof JDebuggerPanel.ValueTreeNode) {
1723+
JDebuggerPanel.ValueTreeNode valueNode = (JDebuggerPanel.ValueTreeNode) childNode;
1724+
1725+
Map<String, Object> varInfo = new HashMap<>();
1726+
varInfo.put("name", valueNode.getName());
1727+
varInfo.put("value", valueNode.getValue());
1728+
varInfo.put("type", valueNode.getType());
1729+
varInfo.put("typeId", valueNode.getTypeID());
1730+
varInfo.put("updated", valueNode.isUpdated());
1731+
1732+
// Recursively extract children if any
1733+
if (valueNode.getChildCount() > 0) {
1734+
varInfo.put("children", extractTreeNodeData(valueNode));
1735+
}
1736+
1737+
result.add(varInfo);
1738+
}
1739+
}
1740+
1741+
return result;
1742+
}
1743+
15911744
// reusing jadx's secure xml parsing logic for parsing manifest xml file
15921745
// this code is taken from jadx -
15931746
// https://github.com/skylot/jadx/blob/47647bbb9a9a3cd3150705e09cc1f84a5e9f0be6/jadx-core/src/main/java/jadx/core/utils/android/AndroidManifestParser.java#L214

0 commit comments

Comments
 (0)