Skip to content

Commit 1e727f5

Browse files
gh-152966: Add a disassembly browser to IDLE
Add a Disassembly Browser on the Tools menu, showing the disassembled bytecode of the editor content, the Shell input, or the selection as collapsible code objects. Selecting an instruction highlights the matching source, and selecting source selects the instructions. While the debugger is stopped, the browser instead shows the code object that is executing and marks the current instruction. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent d842aca commit 1e727f5

13 files changed

Lines changed: 1158 additions & 6 deletions

Doc/library/idle.rst

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,22 @@ AST Browser
321321
Double-click a node, or press :kbd:`Escape`,
322322
to hide the browser and return to the editor at the node.
323323

324+
Disassembly Browser
325+
Open a window showing the disassembled bytecode of the editor content
326+
(or, in the Shell, the current input), or of the selection if there is one.
327+
Each code object is a collapsible row of its instructions,
328+
colored by the kind of operand they use;
329+
the one holding the cursor is opened and its instructions selected.
330+
Selecting an instruction highlights the matching region in the editor
331+
and moves the cursor there;
332+
selecting text or moving the cursor in the editor
333+
selects the instructions built from it.
334+
Double-click a row, or press :kbd:`Escape`,
335+
to hide the browser and return to the editor at the instruction.
336+
While the debugger is stopped, the browser follows it instead of the
337+
editor, showing the code object that is executing
338+
and marking the current instruction.
339+
324340
Options menu (Shell and Editor)
325341
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
326342

Lib/idlelib/News3.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,15 @@ Released on 2026-10-01
44
=========================
55

66

7+
gh-152966: Add a Disassembly Browser to IDLE, opened from the Tools
8+
menu. It shows the disassembled bytecode of the editor content, the
9+
Shell input, or the selection, as collapsible code objects colored by
10+
operand kind. Selecting an instruction highlights the matching source,
11+
and selecting source selects the instructions. While the debugger is
12+
stopped, the browser instead shows the code object that is executing and
13+
marks the current instruction. Patch by Serhiy Storchaka and Claude
14+
Code.
15+
716
gh-152942: Add an AST Browser to IDLE, opened from the Tools menu. It
817
shows the abstract syntax tree of the editor content, the Shell input,
918
or the selection. Selecting a node highlights the matching region in

Lib/idlelib/config-extensions.def

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,12 @@ enable= 1
6161
[ASTBrowser_cfgBindings]
6262
ast-browser=
6363

64+
# A browser for the disassembled bytecode of the editor, from the Tools menu.
65+
[DisBrowser]
66+
enable= 1
67+
[DisBrowser_cfgBindings]
68+
disassembly-browser=
69+
6470
# A fake extension for testing and example purposes. When enabled and
6571
# invoked, inserts or deletes z-text at beginning of every line.
6672
[ZzDummy]

Lib/idlelib/debugger.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,11 @@ def close(self, event=None):
170170
# (Causes a harmless extra cycle through close_debugger() if user
171171
# toggled debugger from pyshell Debug menu)
172172
self.pyshell.close_debugger()
173+
# Tell observers (e.g. the disassembly browser) debugging has ended.
174+
try:
175+
self.pyshell.text.event_generate("<<debugger-off>>", when="tail")
176+
except Exception:
177+
pass
173178
# Now close the debugger control window....
174179
self.top.destroy()
175180

@@ -282,6 +287,9 @@ def interaction(self, message, frame, info=None):
282287
if self.vsource.get():
283288
self.sync_source_line()
284289

290+
# Let observers (e.g. the disassembly browser) react to the stop.
291+
self.pyshell.text.event_generate("<<debugger-stopped>>", when="tail")
292+
285293
for b in self.buttons:
286294
b.configure(state="normal")
287295

@@ -313,6 +321,20 @@ def __frame2fileline(self, frame):
313321
lineno = frame.f_lineno
314322
return filename, lineno
315323

324+
def current_frame_code(self):
325+
"""Return (code object, last-instruction offset) for the current frame.
326+
327+
The code object is the one actually executing: obtained via marshal
328+
from a remote FrameProxy, or directly from a local frame. Return None
329+
when the program is not stopped.
330+
"""
331+
frame = self.frame
332+
if frame is None:
333+
return None
334+
get_code = getattr(frame, "code_object", None) # A remote FrameProxy.
335+
code = get_code() if get_code else frame.f_code # Else a local frame.
336+
return code, frame.f_lasti
337+
316338
def cont(self):
317339
self.idb.set_continue()
318340
self.abort_loop()

Lib/idlelib/debugger_r.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
barrier, in particular frame and traceback objects.
2020
2121
"""
22+
import marshal
2223
import reprlib
2324
import types
2425
from idlelib import debugger
@@ -144,6 +145,12 @@ def frame_code(self, fid):
144145
codetable[cid] = code
145146
return cid
146147

148+
def frame_code_marshal(self, fid):
149+
# A code object cannot be pickled through the RPC, but it can be
150+
# marshalled; the two interpreters are the same build, so the bytes
151+
# load back into an equivalent code object in the IDLE process.
152+
return marshal.dumps(frametable[fid].f_code)
153+
147154
#----------called by a CodeProxy----------
148155

149156
def code_name(self, cid):
@@ -219,6 +226,12 @@ def _get_f_code(self):
219226
cid = self._conn.remotecall(self._oid, "frame_code", (self._fid,), {})
220227
return CodeProxy(self._conn, self._oid, cid)
221228

229+
def code_object(self):
230+
"Return the frame's real code object, transported via marshal."
231+
blob = self._conn.remotecall(self._oid, "frame_code_marshal",
232+
(self._fid,), {})
233+
return marshal.loads(blob)
234+
222235
def _get_f_globals(self):
223236
did = self._conn.remotecall(self._oid, "frame_globals",
224237
(self._fid,), {})

0 commit comments

Comments
 (0)