4444import org .w3c .dom .Document ;
4545
4646import javax .swing .*;
47+ import javax .swing .tree .DefaultMutableTreeNode ;
48+ import javax .swing .tree .TreeNode ;
4749import java .awt .*;
4850import java .awt .event .ActionEvent ;
4951import 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