Skip to content

Allow stepping through disassembly#831

Open
danthe1st wants to merge 1 commit intoeclipse-jdt:masterfrom
danthe1st:debug-disassembly
Open

Allow stepping through disassembly#831
danthe1st wants to merge 1 commit intoeclipse-jdt:masterfrom
danthe1st:debug-disassembly

Conversation

@danthe1st
Copy link
Copy Markdown

@danthe1st danthe1st commented Dec 22, 2025

What it does

This PR allows stepping through the bytecode of disassembled classes in the class file editor.

This is just an unfinished prototype but I wanted to create a (draft) PR early to be able to get feedback and show the basic approach.

This depends on another PR in jdt.ui (eclipse-jdt/eclipse.jdt.ui#2708)

image image

@SougandhS Since you have worked on similar things recently, I think you might be interested in that.

How to test

  • Start Eclipse with this change as well as this patch
  • Step through some code that steps into libraries without a source attachment
  • It automatically jumps to the relevant bytecode instruction(s) corresponding to the stack frame
  • Stepping through normal (non-library) classes and library classes with source attachments should still work

Author checklist

@SougandhS
Copy link
Copy Markdown
Member

Thanks for the feature @danthe1st, looks great on feature wise

Would it be possible to change highlight color for dark theme ? its bit hard to read

image

@SougandhS SougandhS added the enhancement New feature or request label Jan 1, 2026
@eclipse-jdt-bot
Copy link
Copy Markdown
Contributor

This pull request changes some projects for the first time in this development cycle.
Therefore the following files need a version increment:

org.eclipse.jdt.debug/META-INF/MANIFEST.MF

An additional commit containing all the necessary changes was pushed to the top of this PR's branch. To obtain these changes (for example if you want to push more changes) either fetch from your fork or apply the git patch.

Git patch
From 0fc0c7499e1927da534bae46cee3001370717b67 Mon Sep 17 00:00:00 2001
From: Eclipse JDT Bot <jdt-bot@eclipse.org>
Date: Thu, 1 Jan 2026 05:20:08 +0000
Subject: [PATCH] Version bump(s) for 4.39 stream


diff --git a/org.eclipse.jdt.debug/META-INF/MANIFEST.MF b/org.eclipse.jdt.debug/META-INF/MANIFEST.MF
index 9afbed70f..bef1c1bbb 100644
--- a/org.eclipse.jdt.debug/META-INF/MANIFEST.MF
+++ b/org.eclipse.jdt.debug/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@ Manifest-Version: 1.0
 Bundle-ManifestVersion: 2
 Bundle-Name: %pluginName
 Bundle-SymbolicName: org.eclipse.jdt.debug; singleton:=true
-Bundle-Version: 3.25.0.qualifier
+Bundle-Version: 3.25.100.qualifier
 Bundle-ClassPath: jdimodel.jar
 Bundle-Activator: org.eclipse.jdt.internal.debug.core.JDIDebugPlugin
 Bundle-Vendor: %providerName
-- 
2.52.0

Further information are available in Common Build Issues - Missing version increments.

@danthe1st danthe1st force-pushed the debug-disassembly branch 4 times, most recently from a53115b to 191da85 Compare January 5, 2026 10:57
@danthe1st
Copy link
Copy Markdown
Author

@SougandhS I updated this to use the current instruction pointer background color (see the updated screenshots).

Do you know whether there is a way to detect when the stack current stack frame is no longer there/when the highlighting should be removed (e.g. when resuming or stepping into a different file)?

@SougandhS
Copy link
Copy Markdown
Member

@SougandhS I updated this to use the current instruction pointer background color (see the updated screenshots).

Thanks, much better now

Do you know whether there is a way to detect when the stack current stack frame is no longer there/when the highlighting should be removed (e.g. when resuming or stepping into a different file)?

You can achieve this by registering an IDebugContextListener and check that stackframe's status accordingly

@danthe1st danthe1st force-pushed the debug-disassembly branch 4 times, most recently from 86a12ff to 86d25a3 Compare January 10, 2026 15:22
@danthe1st danthe1st marked this pull request as ready for review January 10, 2026 15:31
@danthe1st
Copy link
Copy Markdown
Author

@SougandhS Hi. Now that I was able to write a test for this as well, I think eclipse-jdt/eclipse.jdt.ui#2708 and this are ready for review.

Note that this won't be able to build successfully without eclipse-jdt/eclipse.jdt.ui#2708.
Also, as you probably see, I am not that experienced with the tests here ;).

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds prototype support for stepping through and highlighting bytecode instructions in the Class File Editor when debugging into libraries without source attachments (disassembly view).

Changes:

  • Add JDIStackFrame.getCodeIndex() to expose the current bytecode index for the active stack frame.
  • Introduce a new JavaStackFrameSourceDisplayAdapter to highlight the corresponding disassembly instruction in ClassFileEditor (and absorb prior lambda-selection behavior).
  • Add a new UI test to verify disassembly highlighting updates when stepping, and bump bundle/plugin versions.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/model/JDIStackFrame.java Exposes bytecode codeIndex() via new accessor for UI highlighting.
org.eclipse.jdt.debug/META-INF/MANIFEST.MF Bumps core debug bundle version.
org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/sourcelookup/LambdaStackFrameSourceDisplayAdapter.java Removes the old lambda-only source display adapter (logic moved/merged).
org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/sourcelookup/JavaStackFrameSourceDisplayAdapter.java New unified source display adapter: highlights disassembly instructions and handles lambda selection.
org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/sourcelookup/JavaDebugShowInAdapterFactory.java Routes stack frames to the new adapter (and reuses a single instance).
org.eclipse.jdt.debug.ui/pom.xml Bumps UI plugin Maven version.
org.eclipse.jdt.debug.ui/META-INF/MANIFEST.MF Bumps UI bundle version.
org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/sourcelookup/ClassFileEditorHighlightingTest.java Adds regression test covering disassembly instruction highlighting while stepping.
org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AutomatedSuite.java Registers the new test in the automated suite.
Comments suppressed due to low confidence (5)

org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/model/JDIStackFrame.java:1762

  • getCodeIndex() reads fLocation without the same synchronized (fThread) guard used by other fLocation accessors (e.g., getLineNumber). To avoid races with bind(...) updates, please synchronize similarly and consider returning a sentinel (e.g., -1) when the frame is invalid/not suspended rather than always dereferencing fLocation.
	public long getCodeIndex() {
		return fLocation.codeIndex();
	}

org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/sourcelookup/ClassFileEditorHighlightingTest.java:40

  • The test method name test123 is not descriptive and doesn’t follow the naming style used in neighboring tests (e.g., testFindDuplicatesBug565462). Please rename it to something that reflects the behavior under test (e.g., highlighting the current bytecode instruction in the class file editor).
	public void test123() throws Exception {
		IJavaProject javaProject = getProjectContext();
		createLineBreakpoint(21, CLASS_NAME);

org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/sourcelookup/JavaStackFrameSourceDisplayAdapter.java:113

  • handleLambda connects the document provider to editorInput but never disconnects it. This can leak provider connections and keep resources pinned; follow the pattern used elsewhere (e.g., connect in a try/finally and always disconnect).
		IDocumentProvider provider = JavaUI.getDocumentProvider();
		IEditorInput editorInput = sourceRes.getEditorInput();
		provider.connect(editorInput);
		IDocument document = provider.getDocument(editorInput);
		IRegion region = document.getLineInformation(jdiFrame.getLineNumber() - 1);

org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/sourcelookup/JavaStackFrameSourceDisplayAdapter.java:94

  • ensureListenerRegistered(page) is invoked both in displaySource (after handleClassFile returns true) and again inside handleClassFile. This duplication makes the control flow harder to follow; please keep the call in only one place (preferably the caller) so listener registration is centralized.
			if (sourceRes.getEditorInput() instanceof IClassFileEditorInput input) {
				if (handleClassFile(page, jdiFrame, input)) {
					ensureListenerRegistered(page);
					return;
				}

org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/sourcelookup/JavaDebugShowInAdapterFactory.java:31

  • The adapter factory now reuses a single JavaStackFrameSourceDisplayAdapter instance across all stack frames/pages. Since the adapter stores mutable state (currentClassFileEditor/currentClassFileFrame and a single classFileUnhighlighterRegistered flag), stepping/highlighting in one workbench window/page can interfere with another, and ensureListenerRegistered will only ever register a listener for the first window/page encountered. Consider scoping state/listeners per workbench window (or not reusing a single adapter instance).
	private JavaStackFrameSourceDisplayAdapter javaStackFrameSourceDisplayAdapter = new JavaStackFrameSourceDisplayAdapter();


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@danthe1st
Copy link
Copy Markdown
Author

danthe1st commented Feb 28, 2026

I implemented some of the suppressed Copilot changes I consider to make sense but didn't implement the change in the review comment as this isn't really code I added.

Regarding the information about this being a prototype: I forgot to remove that from the PR description (done now).

@SougandhS
Copy link
Copy Markdown
Member

SougandhS commented Apr 2, 2026

Hi @danthe1st, Can you test the basic stepping with these 2 options enabled

image and see anything is breaking or not ?

Copy link
Copy Markdown
Member

@SougandhS SougandhS left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image

These load instructions will be skipped (wont be suspended) when IJDIPreferencesConstants.PREF_STATEMENT_LEVEL_STEPPING is enabled. - Can you confirm it ?

@danthe1st
Copy link
Copy Markdown
Author

danthe1st commented Apr 2, 2026

Because those instructions are in the same line in the source file, they are skipped with and without statement stepping.

Screencast_20260402_102531.webm

If I use a custom source file, it can make a difference.

Screencast_20260402_103207.webm

But I noticed some other issues with a custom source file:

Screencast_20260402_104025.webm

I'll have to take a look at that.

@danthe1st
Copy link
Copy Markdown
Author

Nevermind, the issue was because I still had the class file open when I replaced the JAR so the content of the editor didn't match the code executed.

Screencast_20260402_104623.webm

Code:

package dep;

public class Dep {
	public void a() {
		b();
	}
	void b(){
		print(
				c(),
				"",
				d(),
				"---"
		);
	}
	
	String c() {
		return "c";
	}
	String d() {
		return "d";
	}
	void print(Object... args) {
		for(Object o : args) {
			System.out.println(o);
		}
	}
}

}
}

private boolean classFileUnhighlighterRegistered = false;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this is checked in the UI thread, but set from a non-UI thread? I don't think debug events are propagated in the UI thread at least and display source sounds like UI thread code.

Can you debug and check? Best to use AtomicBoolean if that is the case. Or maybe volatile is enough.

I'm also not sure what happens if a debug event unregisters the listener concurrently to the code checking it it should be registered.

I'm also not sure about displaySource just running the UI code, e.g. org.eclipse.debug.internal.ui.elements.adapters.StackFrameSourceDisplayAdapter.displaySource(Object, IWorkbenchPage, boolean) schedules a job to do that...

Copy link
Copy Markdown
Author

@danthe1st danthe1st Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is called in a UI thread, at least in my testing.
image

Copy link
Copy Markdown
Contributor

@trancexpress trancexpress Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I went through StackFrameSourceDisplayAdapter again, it looks like the job is started to not run the source look up in the UI thread.

So likely this is a problem already with: #812

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I switched to using synchronized in b07279b.

The reason for using synchronized over volatile/atomic variables is that there are multiple variables involved and if something tries to call displaySource() concurrently for whatever reason (I don't think this should happen under normal circumstances but I don't know for sure), it should still ensure that these variables are updated together and there's no hard to debug/reproduce in-between state.

ClassFileEditor editor = (ClassFileEditor) EditorUtility.openInEditor(javaProject.getProject().getFile("bin/" + CLASS_NAME + ".class"));
IClassFileEditorInput editorInput = (IClassFileEditorInput) editor.getEditorInput();
IClassFile classFile = editorInput.getClassFile();
thread.getTopStackFrame().getLaunch().setSourceLocator(stackFrame -> classFile);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this necessary?

Copy link
Copy Markdown
Author

@danthe1st danthe1st Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes sure it actually uses the ClassFileEditor for displaying the source.
This is necessary because the source file is present in the project and otherwise not show the bytecode.

@trancexpress
Copy link
Copy Markdown
Contributor

trancexpress commented Apr 12, 2026

I think #812 went in the wrong direction and this PR follows with more code.

With the addition of LambdaStackFrameSourceDisplayAdapter, every time I do debug steps inside lambda code, there is a source lookup call in the UI thread: SourceLookupFacility.lookup()

With this PR adding on top, a source lookup is done on every debug step - in the UI thread.

Normally the chain is: SourceLookupFacility.displaySource() -> SourceLookupJob (non-UI) -> SourceDisplayJob (UI) -> SourceLookupFacility.display() (opens editors, selects correct code)

The goal here must have been to not block the UI thread with source lookups - highlighting the current stack frame in the editor is secondary to smooth UI experience. I can imagine the source lookup causing noticeable mini-freezes in big workspaces (the effect would be tickets like eclipse-platform/eclipse.platform#2615).

Since the source lookup and display chain is skipped, custom code is added in LambdaStackFrameSourceDisplayAdapter and was added in this PR. Such code then needs to duplicate preference handling and such (missing in LambdaStackFrameSourceDisplayAdapter but added in this PR).

@SougandhS IMO we need to clean this up, this PR is a good opportunity to do so - since even more intricate code must run once the editor is opened. I need to think on how we want to operate on the editor (given the stack frame), once its opened in SourceLookupFacility.display()... it would be great if you can suggest ideas. Maybe we are in the wrong adapter code, I'm not sure. Maybe we need something new. Maybe this: org.eclipse.debug.ui.IDebugEditorPresentation.addAnnotations(IEditorPart, IStackFrame)

	/**
	 * Positions and adds annotations to the given editor for the specified
	 * stack frame and returns whether any annotations were added. When
	 * <code>true</code> is returned, a call will be made to remove annotations
	 * when the source selection is cleared for the stack frame. When
	 * <code>false</code> is returned, the debugger will position and add
	 * standard annotations to the editor, and a corresponding call to remove
	 * annotations will not be made. This method is called when the debugger is
	 * has opened an editor to display source for the given stack frame.
	 *
	 * @param editorPart the editor the debugger has opened
	 * @param frame the stack frame for which the debugger is displaying
	 *  source
	 * @return <code>true</code> if annotations were added to the given editor part <code>false</code> otherwise
	 */
	boolean addAnnotations(IEditorPart editorPart, IStackFrame frame);

Snippet where source lookups are called on every step inside lambda:

	public static void main(String[] args) {
		int n = 3;
		int m = 20;
		Runnable r = () -> {
		for (int i = 0; i < n; ++i) {
			String l = "0".repeat(m);
			System.out.println(l);
		}
		};
		r.run();
	}

Once within the Runnable.run() body the lambda adapter runs its lookups.

@SougandhS
Copy link
Copy Markdown
Member

I think #812 went in the wrong direction and this PR follows with more code.

@SougandhS IMO we need to clean this up, this PR is a good opportunity to do so - since even more intricate code must run once the editor is opened. I need to think on how we want to operate on the editor (given the stack frame), once its opened in SourceLookupFacility.display()... it would be great if you can suggest ideas. Maybe we are in the wrong adapter code, I'm not sure. Maybe we need something new. Maybe this: org.eclipse.debug.ui.IDebugEditorPresentation.addAnnotations(IEditorPart, IStackFrame)

Handled this in a new PR 👍

@danthe1st
Copy link
Copy Markdown
Author

danthe1st commented Apr 12, 2026

Handled this in a new PR 👍

I guess my PR should wait for that and I can update it once that's merged. Hence, I'm marking this as a draft for now.

@danthe1st danthe1st marked this pull request as draft April 12, 2026 13:09
@trancexpress
Copy link
Copy Markdown
Contributor

@danthe1st please hold off rebasing, I'll try to adjust the test and I'll rebase in the process.

@danthe1st danthe1st force-pushed the debug-disassembly branch 2 times, most recently from 07c57f0 to 952b171 Compare April 12, 2026 20:24
@danthe1st danthe1st marked this pull request as ready for review April 12, 2026 20:24
@danthe1st
Copy link
Copy Markdown
Author

danthe1st commented Apr 12, 2026

please hold off rebasing, I'll try to adjust the test and I'll rebase in the process.

Sorry, I didn't see that until now/I only saw the notification of the other PR being merged

@trancexpress
Copy link
Copy Markdown
Contributor

trancexpress commented Apr 12, 2026

I've adjusted two things in the PR:

  1. The tests no longer open the editor manually, IMO we want to test what the user will be seeing. This slows the tests somewhat, also due to creating the test project for each test. testDisplaySourceWithClassFileEditorHighlightsLine also compiles with debug line information, since otherwise Eclipse doesn't allow to create the line breakpoint.
  2. The editor unhighlight code now runs in a UI job - I'm not sure the code belongs in the debug event notification thread, so I've moved it. Listener registration and deregistration is also done in the UI thread so no need to synchronize it anymore. @danthe1st please test if the unhighlight and listener deregistration works as expected, if this is not covered by the tests.

When I run the tests with coverage, JavaStackFrameEditorPresenter.removeAnnotations(IEditorPart, IThread) is not covered. I'm not sure when this code runs, we'll have to check. I'm also not sure if this code needs to unregister the listener.

I've also adjusted the test to have to run only a small part of the code in the UI thread, so I've removed the UI debug suite.

@danthe1st
Copy link
Copy Markdown
Author

I'm not sure when this code runs, we'll have to check. I'm also not sure if this code needs to unregister the listener.

When I tested it, it didn't seem to be executed at all.

@trancexpress
Copy link
Copy Markdown
Contributor

trancexpress commented Apr 13, 2026

When I tested it, it didn't seem to be executed at all.

I'll try to find where its called.

One more thing to check:

What happens if I close the class editor and another editor is opened when stepping? Will the unhighlighting work? Or if debugging multiple programs. Or the same program with multiple class files.

For the listener probably with different workbench windows we will have a problem too. Likely we need a list of registered listeners / highlighted editors, or some other mechanism that doesn't rely on fields (e.g. a task queue for the clean-up job). For the editors we likely must check if the editor is still opened before removing highlights.

I'm also not 100% sure what the life-cycle of org.eclipse.jdt.internal.debug.ui.JDIModelPresentation is.

Probably this is another case where we should check what platform debug is doing to remove line highlighting when the debug launch is terminated.

And it will be good to have a test for terminating the debug launch and checking that the active class editor has no highlight.

@danthe1st
Copy link
Copy Markdown
Author

What happens if I close the class editor and another editor is opened when stepping? Will the unhighlighting work? Or if debugging multiple programs. Or the same program with multiple class files.

When I tested with what I tried yesterday (a listener that was registered using the IWorkbenchWindow of the editor), unhighlighting worked. I can test it again later.

For the listener probably with different workbench windows we will have a problem too.

I can check that as well.

@trancexpress
Copy link
Copy Markdown
Contributor

@danthe1st if you have time, can you first check where the Java editor line highlight is removed when terminating a Java debug launch? And what the call stacks are to get to there?

@danthe1st
Copy link
Copy Markdown
Author

danthe1st commented Apr 13, 2026

If addAnnotations returns true, removeAnnotations will be called when the instruction pointer doesn't need to be shown any more (this includes the launch being terminated). Both methods are called in a UI thread. I will update my PR accordingly.

This change adjusts JavaStackFrameEditorPresenter for ClassFileEditor,
so that bytecode instructions are highlighted during debug stepping.
@danthe1st
Copy link
Copy Markdown
Author

danthe1st commented Apr 13, 2026

I now used removeAnnotations for the unhighlighting and I added an assertion that ensures this is actually done after termination. However, I think I noticed an NPE in that class so I'll fix that as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants