Debugging in the browser WIP

This commit is contained in:
Jindra Petřík
2026-02-14 11:57:04 +01:00
parent 3e7279b848
commit 8032b2e39b
11 changed files with 292 additions and 40 deletions
@@ -495,7 +495,7 @@ public class DebugPanel extends JPanel {
SelectedTab oldSel = selectedTab;
localsTable = null;
SWF swf = Main.getMainFrame().getPanel().getCurrentSwf();
SWF swf = Main.getDebuggedSWF();
if (swf == null) {
return;
}
@@ -31,7 +31,9 @@ import com.jpexs.debugger.flash.messages.in.InCallFunction;
import com.jpexs.debugger.flash.messages.in.InConstantPool;
import com.jpexs.debugger.flash.messages.in.InContinue;
import com.jpexs.debugger.flash.messages.in.InErrorException;
import com.jpexs.debugger.flash.messages.in.InExit;
import com.jpexs.debugger.flash.messages.in.InFrame;
import com.jpexs.debugger.flash.messages.in.InGetSwf;
import com.jpexs.debugger.flash.messages.in.InGetVariable;
import com.jpexs.debugger.flash.messages.in.InNumScript;
import com.jpexs.debugger.flash.messages.in.InPlaceObject;
@@ -43,6 +45,7 @@ import com.jpexs.debugger.flash.messages.in.InTrace;
import com.jpexs.debugger.flash.messages.in.InVersion;
import com.jpexs.debugger.flash.messages.out.OutAddWatch2;
import com.jpexs.debugger.flash.messages.out.OutGetBreakReason;
import com.jpexs.debugger.flash.messages.out.OutGetSwf;
import com.jpexs.debugger.flash.messages.out.OutPlay;
import com.jpexs.debugger.flash.messages.out.OutProcessedTag;
import com.jpexs.debugger.flash.messages.out.OutRewind;
@@ -51,7 +54,11 @@ import com.jpexs.debugger.flash.messages.out.OutSwfInfo;
import com.jpexs.decompiler.flash.SWF;
import com.jpexs.decompiler.flash.configuration.Configuration;
import com.jpexs.decompiler.graph.DottedChain;
import com.jpexs.helpers.Helper;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
@@ -72,7 +79,7 @@ import java.util.regex.Pattern;
* @author JPEXS
*/
public class DebuggerHandler implements DebugConnectionListener {
private boolean connected = false;
private DebuggerCommands commands = null;
@@ -586,15 +593,13 @@ public class DebuggerHandler implements DebugConnectionListener {
}
}
public void disconnect() {
private void disconnected() {
frame = null;
pool = null;
breakInfo = null;
breakReason = null;
connected = false;
if (commands != null) {
commands.disconnect();
}
commands = null;
synchronized (this) {
for (SWF debuggedSwf : debuggedSwfs) {
@@ -630,6 +635,18 @@ public class DebuggerHandler implements DebugConnectionListener {
}
debuggedSwfs.clear();
}
public void disconnect() {
frame = null;
pool = null;
breakInfo = null;
breakReason = null;
connected = false;
if (commands != null) {
commands.disconnect();
}
disconnected();
}
public synchronized boolean isConnected() {
return connected;
@@ -659,10 +676,6 @@ public class DebuggerHandler implements DebugConnectionListener {
@Override
public void connected(DebuggerConnection con) {
/*for (SWF debuggedSwf : debuggedSwfs) {
makeBreakPointsUnconfirmed(debuggedSwf);
}*/
Main.startWork(AppStrings.translate("work.debugging"), null);
synchronized (this) {
@@ -701,7 +714,7 @@ public class DebuggerHandler implements DebugConnectionListener {
final Pattern patAS3 = Pattern.compile("^(.*);(.*);(.*)\\.as$");
final Pattern patAS3PCode = Pattern.compile("^(?<hash>[0-9a-z_]+):#PCODE abc:(?<abc>[0-9]+),script:(?<script>[0-9]+),class:(?<class>-?[0-9]+),trait:(?<trait>-?[0-9]+),method:(?<method>[0-9]+),body:(?<body>[0-9]+);(.*)$");
boolean isAS3 = Main.getRunningSWF().isAS3();
//boolean isAS3 = Main.getRunningSWF().isAS3();
try {
con.addMessageListener(new DebugMessageListener<InErrorException>() {
@@ -811,16 +824,26 @@ public class DebuggerHandler implements DebugConnectionListener {
commands.setGetterTimeout(1500);
commands.setSetterTimeout(5000);
con.isAS3 = isAS3;
//Widelines - only AS3, it hangs in AS1/2 and SWD does not support UI32 lines
if (isAS3) {
con.wideLines = commands.getOption("wide_line_player", "false").equals("true");
if (con.wideLines) {
commands.setOption("wide_line_debugger", "on");
}
}
/*if (isAS3) {
}*/
commands.squelch(true);
con.addMessageListener(new DebugMessageListener<InExit>() {
@Override
public void message(InExit t) {
Logger.getLogger(DebuggerHandler.class.getName()).fine("Handling inExit");
//con.dropMessage(t);
synchronized (DebuggerHandler.this) {
paused = false;
connected = false;
}
Logger.getLogger(DebuggerHandler.class.getName()).fine("Calling disconnected");
disconnected();
Logger.getLogger(DebuggerHandler.class.getName()).fine("Disconnected called");
}
});
con.writeMessage(new OutSwfInfo(con, 0));
con.addMessageListener(new DebugMessageListener<InSwfInfo>() {
@@ -840,6 +863,57 @@ public class DebuggerHandler implements DebugConnectionListener {
});
}*/
con.dropMessage(t);
View.execInEventDispatch(new Runnable() {
@Override
public void run() {
for (InSwfInfo.SwfInfo s : t.swfInfos) {
try {
/*try {
con.sendMessageWithTimeout(new OutGetSwf(con, (int) s.index), InGetSwf.class);
con.sendMessageWithTimeout(new OutGetSwd(con, (int) s.index), InGetSwd.class);
} catch (IOException ex) {
//ignore
}*/
InGetSwf inGetSwf = con.sendMessageWithTimeout(new OutGetSwf(con, (int) s.index), InGetSwf.class);
System.err.println("Received SWF");
String sha256 = Helper.byteArrayToHex(MessageDigest.getInstance("SHA-256").digest(inGetSwf.swfData));
Main.findDebuggedSwf(sha256);
SWF debuggedSwf = Main.getDebuggedSWF();
if (debuggedSwf == null) {
con.disconnect();
synchronized (DebuggerHandler.this) {
paused = false;
connected = false;
}
disconnected();
} else {
con.isAS3 = debuggedSwf.isAS3();
if (debuggedSwf.isAS3()) {
con.wideLines = commands.getOption("wide_line_player", "false").equals("true");
if (con.wideLines) {
commands.setOption("wide_line_debugger", "on");
}
} else {
Logger.getLogger(DebuggerHandler.class.getName()).log(Level.FINER, "End of connect - sending continue");
con.writeMessage(new OutRewind(con));
con.writeMessage(new OutPlay(con));
commands.sendContinue();
}
}
} catch (IOException | NoSuchAlgorithmException ex) {
//ignore
con.disconnect();
synchronized (DebuggerHandler.this) {
paused = false;
connected = false;
}
disconnected();
}
}
}
});
}
});
@@ -998,17 +1072,16 @@ public class DebuggerHandler implements DebugConnectionListener {
}
});
if (!isAS3) {
Logger.getLogger(DebuggerHandler.class.getName()).log(Level.FINER, "End of connect - sending continue");
con.writeMessage(new OutRewind(con));
con.writeMessage(new OutPlay(con));
commands.sendContinue();
}
/* if (!isAS3) {
}*/
} catch (IOException ex) {
synchronized (this) {
paused = false;
connected = false;
}
disconnected();
}
}
@@ -1026,7 +1099,7 @@ public class DebuggerHandler implements DebugConnectionListener {
SWF debuggedSwf = debuggedSwfs.get(swfIndex);
String hash = Main.getSwfHash(debuggedSwf);
Logger.getLogger(DebuggerHandler.class.getName()).log(Level.FINEST, "sending breakpoints of ", hash);
Logger.getLogger(DebuggerHandler.class.getName()).log(Level.FINEST, "sending breakpoints of {0}", hash);
if (toRemoveBPointMap.containsKey(debuggedSwf)) {
for (String scriptName : toRemoveBPointMap.get(debuggedSwf).keySet()) {
+91 -8
View File
@@ -120,6 +120,8 @@ import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
@@ -139,6 +141,7 @@ import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.logging.ConsoleHandler;
import java.util.logging.FileHandler;
import java.util.logging.Formatter;
@@ -228,13 +231,49 @@ public class Main {
public static String currentDebuggerPackage = null;
private static SWF runningSWF = null;
private static SWF debuggedSWF = null;
private static String preparedHashSha256 = null;
private static SwfPreparation runningPreparation = null;
private static boolean loggingFileLoaded = false;
private static boolean debugListening = false;
public static SWF getDebuggedSWF() {
return debuggedSWF;
}
public static void findDebuggedSwf(String hashSha256) {
System.err.println("Searching for hash " + hashSha256 + "...");
for (SWF swf : mainFrame.getPanel().getAllSwfs()) {
System.err.println("Checking " + swf +" with hash " + swf.getHashSha256());
if (Objects.equals(hashSha256, swf.getHashSha256())) {
Main.debuggedSWF = swf;
System.err.println("RECEIVED SWF FOUND");
return;
}
}
if (preparedHashSha256 != null && hashSha256.equals(preparedHashSha256)) {
System.err.println("RECEIVED SWF IS ONE THAT WAS PREPARED FOR RUN...");
Main.debuggedSWF = runningSWF;
return;
}
System.err.println("RECEIVED SWF IS NOT ANY OF OURS");
Main.debuggedSWF = null;
}
public static SWF getRunningSWF() {
return runningSWF;
//System.err.println("debuggedSWF: " + (debuggedSWF == null ? "NOT SET" : debuggedSWF));
//return runningSWF;
return debuggedSWF;
}
public static WatchService getWatcher() {
@@ -276,6 +315,7 @@ public class Main {
if (runTempFile != null) {
deleteCookiesAfterRun(runTempFile);
runTempFile.delete();
System.err.println("deleted " + runTempFile);
runTempFile = null;
}
for (File f : runTempFiles) {
@@ -296,11 +336,11 @@ public class Main {
}
public static synchronized boolean isDebugPaused() {
return runProcess != null && runProcessDebug && getDebugHandler().isPaused();
return (runProcess != null && runProcessDebug || !flashDebugger.isStopped()) && getDebugHandler().isPaused();
}
public static synchronized boolean isDebugRunning() {
return runProcess != null && runProcessDebug;
return (runProcess != null && runProcessDebug) || !flashDebugger.isStopped();
}
public static synchronized boolean isDebugPCode() {
@@ -401,7 +441,7 @@ public class Main {
} catch (InterruptedException ex) {
if (proc != null) {
try {
proc.destroy();
proc.destroyForcibly();
} catch (Exception ex2) {
//ignore
}
@@ -425,6 +465,9 @@ public class Main {
if (runProcess != null) {
try {
runProcess.destroy();
if (!runProcess.waitFor(5, TimeUnit.SECONDS)) {
runProcess.destroyForcibly();
}
} catch (Exception ex) {
//ignored
}
@@ -445,10 +488,18 @@ public class Main {
synchronized (Main.class) {
if (runProcess != null) {
runProcess.destroy();
try {
if (!runProcess.waitFor(5, TimeUnit.SECONDS)) {
runProcess.destroyForcibly();
}
} catch (InterruptedException ex) {
//ignore
}
}
}
freeRun();
stopDebugger();
stopWork();
mainFrame.getMenu().updateComponents();
}
@@ -596,9 +647,23 @@ public class Main {
try (FileOutputStream fos = new FileOutputStream(toPrepareFile)) {
instrSWF.saveTo(fos);
}
}
if ("main".equals(swfHash)) {
preparedHashSha256 = instrSWF.getHashSha256();
}
}
}
public static void prepareForDebug(SWF swf, File fTempFile, File tempRunDir, List<File> tempFiles, boolean doPCode) throws IOException, InterruptedException {
SWF swfToSave = swf;
if (swf.gfx) {
swfToSave = new GfxConvertor().convertSwf(swf);
}
try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(fTempFile))) {
swfToSave.saveTo(fos, false, swf.gfx);
}
prepareSwf("main", new SwfDebugPrepare(doPCode), fTempFile, swf.getFile() == null ? null : new File(swf.getFile()), tempRunDir, tempFiles);
}
private static File createTempFileInDir(File tempFilesDir, String prefix, String suffix) throws IOException {
if (tempFilesDir == null || !tempFilesDir.isDirectory()) {
return File.createTempFile(prefix, suffix);
@@ -776,7 +841,16 @@ public class Main {
SharedObjectsStorage.addChangedListener(tempFile, runCookieListener);
}
public static void runDebug(SWF swf, final boolean doPCode) {
public static void startDebugListening() {
debugListening = true;
Main.stopWork();
Main.startWork(AppStrings.translate("work.debugging.start") + "...", null, true);
Main.startDebugger();
Main.startWork(AppStrings.translate("work.debugging.listening"), null);
mainFrame.getMenu().hilightPath("/debugging");
}
public static void runDebug(SWF swf, final boolean doPCode) {
String flashVars = ""; //key=val&key2=val2
String playerLocation = Configuration.playerDebugLocation.get();
if (playerLocation.isEmpty() || (!new File(playerLocation).exists())) {
@@ -2932,9 +3006,15 @@ public class Main {
@Override
public void disconnected() {
if (Main.mainFrame != null) {
Main.mainFrame.getMenu().updateComponents();
}
if (Main.mainFrame != null && Main.mainFrame.getPanel() != null) {
Main.mainFrame.getPanel().refreshBreakPoints();
}
if (debugListening) {
Main.startWork(AppStrings.translate("work.debugging.listening"), null, true);
}
}
});
flashDebugger.addConnectionListener(debugHandler);
@@ -2948,7 +3028,7 @@ public class Main {
}
public static void startDebugger() {
flashDebugger.startDebugger();
flashDebugger.startDebugger();
}
public static void stopDebugger() {
@@ -3759,6 +3839,9 @@ public class Main {
public static void showBreakpointsList() {
SWF swf = getMainFrame().getPanel().getCurrentSwf();
if (swf == null) {
return;
}
getMainFrame().getPanel().showBreakpointlistDialog(swf);
}
@@ -1175,6 +1175,7 @@ public abstract class MainFrameMenu implements MenuBuilder {
setMenuEnabled("/file/start/run", swfSelected && !isRunningOrDebugging);
setMenuEnabled("/file/start/debug", swfSelected && !isRunningOrDebugging);
setMenuEnabled("/file/start/debugpcode", swfSelected && !isRunningOrDebugging);
setMenuEnabled("/file/start/debuglisten", !isDebugRunning);
setMenuEnabled("/file/start/stop", isRunningOrDebugging);
setMenuEnabled("/debugging/debug/stop", isRunningOrDebugging); //same as previous
@@ -1298,6 +1299,7 @@ public abstract class MainFrameMenu implements MenuBuilder {
addMenuItem("/file/start/stop", translate("menu.file.start.stop"), "stop32", this::stopActionPerformed, PRIORITY_TOP, null, true, null, false);
addMenuItem("/file/start/debug", translate("menu.file.start.debug"), "debug32", this::debugActionPerformed, PRIORITY_MEDIUM, null, true, new HotKey("CTRL+F5"), false);
addMenuItem("/file/start/debugpcode", translate("menu.file.start.debugpcode"), "debug32", this::debugPCodeActionPerformed, PRIORITY_MEDIUM, null, true, null, false);
addMenuItem("/file/start/debuglisten", translate("menu.file.start.debuglisten"), "listen32", this::debugListenActionPerformed, PRIORITY_MEDIUM, null, true, null, false);
finishMenu("/file/start");
addMenuItem("/file/view", translate("menu.view"), null, null, 0, null, false, null, false);
@@ -1736,6 +1738,11 @@ public abstract class MainFrameMenu implements MenuBuilder {
Main.runDebug((SWF) openable, false);
return true;
}
public boolean debugListenActionPerformed(ActionEvent evt) {
Main.startDebugListening();
return true;
}
public boolean debugPCodeActionPerformed(ActionEvent evt) {
Main.runDebug((SWF) openable, true);
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

@@ -1105,4 +1105,10 @@ contextmenu.normalizeFonts = Normalize fonts
#after 24.1.1
contextmenu.saveSwc = Save as Swc...
work.generating.swc = Generating Swc...
work.generating.swc = Generating Swc...
#after 25.0.0
menu.file.start.debuglisten = Debug listen...
work.debugging.listening = Listening for incoming connections...
contextmenu.prepareDebug.injectDebug = Prepare file for debugging (+ inject debug info)
contextmenu.prepareDebug.generateSwd = Prepare file for debugging (+ generate SWD)
@@ -381,7 +381,11 @@ public class TagTreeContextMenu extends JPopupMenu {
private JMenuItem convertPlaceObjectTypeMenuItem;
private JMenuItem normalizeFontsMenuItem;
private JMenuItem prepareDebugInject;
private JMenuItem prepareDebugSwd;
private List<TreeItem> items = new ArrayList<>();
private static final int KIND_TAG_MOVETO = 0;
@@ -653,6 +657,17 @@ public class TagTreeContextMenu extends JPopupMenu {
addSeparator();
prepareDebugInject = new JMenuItem(mainPanel.translate("contextmenu.prepareDebug.injectDebug"));
prepareDebugInject.addActionListener(this::prepareDebugActionPerformed);
prepareDebugInject.setIcon(View.getIcon("debug16"));
add(prepareDebugInject);
prepareDebugSwd = new JMenuItem(mainPanel.translate("contextmenu.prepareDebug.generateSwd"));
prepareDebugSwd.addActionListener(this::prepareDebugActionPerformed);
prepareDebugSwd.setIcon(View.getIcon("debug16"));
add(prepareDebugSwd);
gotoDocumentClassMenuItem = new JMenuItem(mainPanel.translate("menu.tools.gotoDocumentClass"));
gotoDocumentClassMenuItem.addActionListener(this::gotoDocumentClassActionPerformed);
gotoDocumentClassMenuItem.setIcon(View.getIcon("gotomainclass16"));
@@ -1354,6 +1369,8 @@ public class TagTreeContextMenu extends JPopupMenu {
boolean hasExportableNodes = tree.hasExportableNodes();
prepareDebugInject.setVisible(false);
prepareDebugSwd.setVisible(false);
gotoDocumentClassMenuItem.setVisible(false);
setAsLinkageMenuItem.setVisible(false);
setAs3ClassLinkageMenuItem.setVisible(false);
@@ -1549,7 +1566,7 @@ public class TagTreeContextMenu extends JPopupMenu {
if ("__Packages".equals(firstPkg)) {
addAs12ClassMenuItem.setVisible(true);
}
}
}
if (firstItem instanceof ClassesListTreeModel) {
addAs3ClassMenuItem.setVisible(true);
if (firstItem.getOpenable() instanceof SWF) {
@@ -1586,6 +1603,9 @@ public class TagTreeContextMenu extends JPopupMenu {
if (firstItem instanceof SWF) {
if (((SWF) firstItem).isAS3()) {
gotoDocumentClassMenuItem.setVisible(true);
prepareDebugInject.setVisible(true);
} else {
prepareDebugSwd.setVisible(true);
}
}
@@ -3055,6 +3075,45 @@ public class TagTreeContextMenu extends JPopupMenu {
mainPanel.showGenericTag((Tag) itemr);
}
private void prepareDebugActionPerformed(ActionEvent evt) {
TreeItem item = getCurrentItem();
SWF swf = (SWF) item.getOpenable();
File dir = null;
String newName = "debug.swf";
if (swf.getFile() != null) {
dir = new File(swf.getFile()).getParentFile();
String name = new File(swf.getFile()).getName();
newName = name.substring(0, name.lastIndexOf(".")) + "_debug" + name.substring(name.lastIndexOf("."));
}
JFileChooser chooser = View.getFileChooserWithIcon("debug");
chooser.setCurrentDirectory(dir);
chooser.setSelectedFile(new File(dir, newName));
chooser.setFileFilter(new FileFilter() {
@Override
public boolean accept(File f) {
return f.getAbsolutePath().toLowerCase().endsWith(".swf") || f.isDirectory();
}
@Override
public String getDescription() {
return AppStrings.translate("filter.swf");
}
});
if (chooser.showSaveDialog(this) != JFileChooser.APPROVE_OPTION) {
return;
}
File selFile = Helper.fixDialogFile(chooser.getSelectedFile());
boolean doPCode = false;
List<File> tempFiles = new ArrayList<>();
try {
Main.prepareForDebug(swf, selFile, selFile.getParentFile(), tempFiles, doPCode);
} catch (IOException | InterruptedException ex) {
ViewMessages.showMessageDialog(mainPanel, ex.toString(), AppStrings.translate("error"), JOptionPane.ERROR_MESSAGE);
}
Main.stopWork();
}
private void gotoDocumentClassActionPerformed(ActionEvent evt) {
TreeItem item = getCurrentItem();
item.getOpenable();