Allow stepping through disassembly#831
Conversation
|
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
|
|
This pull request changes some projects for the first time in this development cycle. 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 patchFurther information are available in Common Build Issues - Missing version increments. |
a53115b to
191da85
Compare
|
@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)? |
Thanks, much better now
You can achieve this by registering an |
86a12ff to
86d25a3
Compare
86d25a3 to
5935fe2
Compare
|
@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. |
5935fe2 to
188bb59
Compare
There was a problem hiding this comment.
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
JavaStackFrameSourceDisplayAdapterto highlight the corresponding disassembly instruction inClassFileEditor(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()readsfLocationwithout the samesynchronized (fThread)guard used by otherfLocationaccessors (e.g.,getLineNumber). To avoid races withbind(...)updates, please synchronize similarly and consider returning a sentinel (e.g., -1) when the frame is invalid/not suspended rather than always dereferencingfLocation.
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
test123is 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
handleLambdaconnects the document provider toeditorInputbut 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 indisplaySource(afterhandleClassFilereturns true) and again insidehandleClassFile. 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
JavaStackFrameSourceDisplayAdapterinstance across all stack frames/pages. Since the adapter stores mutable state (currentClassFileEditor/currentClassFileFrameand a singleclassFileUnhighlighterRegisteredflag), stepping/highlighting in one workbench window/page can interfere with another, andensureListenerRegisteredwill 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.
...ui/ui/org/eclipse/jdt/internal/debug/ui/sourcelookup/JavaStackFrameSourceDisplayAdapter.java
Outdated
Show resolved
Hide resolved
cc8ae8a to
bfb5d76
Compare
|
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). |
bfb5d76 to
46323d7
Compare
46323d7 to
5d2b6a8
Compare
|
Hi @danthe1st, Can you test the basic stepping with these 2 options enabled
and see anything is breaking or not ?
|
|
Because those instructions are in the same line in the source file, they are skipped with and without statement stepping. Screencast_20260402_102531.webmIf I use a custom source file, it can make a difference. Screencast_20260402_103207.webmBut I noticed some other issues with a custom source file: Screencast_20260402_104025.webmI'll have to take a look at that. |
|
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.webmCode: 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);
}
}
} |
|
When I compile the file with debugging symbols ( Screencast_20260402_105200.webmOther than that, it can step over individual bytecode instructions if no debugging information (like line numbers) is present: Screencast_20260402_105542.webm(It cannot properly highlight it in If wanted, I can also try to add some tests for things like stepping without debugging information. |
OK for me 👍
Sounds nice
That would be good. please add few more tests if possible |
5d2b6a8 to
d473972
Compare
|
I added another test for the situation with no debugging information in the class files (I compiled it using the JDK APIs there). If more/different tests are desired, please tell me what else you think should have a regression test. |
43213a7 to
41bb3c5
Compare
|
The above pushes update to a change in my JDT.ui PR and make sure that it also works correctly on constructors. |
41bb3c5 to
b1717b8
Compare
|
Since the JDU-UI changes are in, I am now getting actual test failures (it seems like a problem in how I wrote these tests/how the tests/workbench are initialized). I plan to check that tomorrow. |
b1717b8 to
65ad692
Compare
|
The tests failed with Jenkins because |
|
Odd, this seems to want the UI thread: The shared launch also specifies running in the UI thread. What is the error message from the test? |
See https://github.com/eclipse-jdt/eclipse.jdt.debug/runs/70316773248
/**
* Debug test suite framework that runs test in a non UI thread.
*/
public abstract class DebugSuite extends TestSuite {I could also move the test to a different test suite but I don't know which one would be better. |
|
I'm not sure, maybe the non-UI thread choice was made due t o From my POV no need to overcomplicate things for one test case, what is in the PR right now seems OK. If we find that the UI thread test suite is growing, we can have two top level test suites, where the UI one doesn't run tests in a separate thread. |
|
I think there's also |
...ui/ui/org/eclipse/jdt/internal/debug/ui/sourcelookup/JavaStackFrameSourceDisplayAdapter.java
Outdated
Show resolved
Hide resolved
.../ui/org/eclipse/jdt/internal/debug/ui/sourcelookup/LambdaStackFrameSourceDisplayAdapter.java
Show resolved
Hide resolved
| } catch (DebugException e) { | ||
| DebugUIPlugin.log(e); | ||
| if (adaptableObject instanceof JDIStackFrame) { | ||
| return (T) javaStackFrameSourceDisplayAdapter;// ensure the same instance is reused |
There was a problem hiding this comment.
Please add to the comment why it should be the same instance.
There was a problem hiding this comment.
This is the case because T is ISourceDisplay (checked by the if) which is the type of javaStackFrameSourceDisplayAdapter (I now changed that field to use the interface as well). I hope this is clear now.
There was a problem hiding this comment.
Sorry but I still don't understand the comment with the re-use. Is it for efficiency, not creating the object many times? Or there is some state that is important?
There was a problem hiding this comment.
This is because it stores state, specifically information about/for the listener ensuring that the highlighting is cleaned up when the instruction pointer is no longer at the class file.
...ui/ui/org/eclipse/jdt/internal/debug/ui/sourcelookup/JavaStackFrameSourceDisplayAdapter.java
Outdated
Show resolved
Hide resolved
...ui/ui/org/eclipse/jdt/internal/debug/ui/sourcelookup/JavaStackFrameSourceDisplayAdapter.java
Outdated
Show resolved
Hide resolved
...ui/ui/org/eclipse/jdt/internal/debug/ui/sourcelookup/JavaStackFrameSourceDisplayAdapter.java
Outdated
Show resolved
Hide resolved
| } | ||
| } | ||
|
|
||
| private boolean classFileUnhighlighterRegistered = false; |
There was a problem hiding this comment.
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...
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
65ad692 to
4a6b692
Compare
4a6b692 to
28be404
Compare
28be404 to
54fa916
Compare
| 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); |
There was a problem hiding this comment.
Why is this necessary?
There was a problem hiding this comment.
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.
54fa916 to
b07279b
Compare




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)
@SougandhS Since you have worked on similar things recently, I think you might be interested in that.
How to test
Author checklist