Fix axdebug build and step-over on Python 3.11+#2723
Merged
mhammond merged 2 commits intomhammond:mainfrom Mar 23, 2026
Merged
Conversation
8936867 to
3d6a091
Compare
Python 3.11 made PyFrameObject opaque, breaking axdebug's SetThreadStateTrace which directly accessed frame struct fields. Changes: - AXDebug.cpp: Use PyThreadState_GetFrame, PyFrame_GetCode, PyFrame_GetBack, PyObject_SetAttrString for Python >= 3.11. All new getter APIs return strong references and are properly Py_DECREF'd. Pre-3.11 code path preserved with #if guards. - setup.py: Remove the 3.11+ build skip for axdebug. - adb.py: Fix step-over (F10) not stopping after function returns. When stopframe matches the returning frame, promote it to the caller so dispatch_line stops at the next line in the caller. Tested on Python 3.14.2 with VS 2022 Professional.
3d6a091 to
4a0286a
Compare
Contributor
Author
|
Note: @Avasam
PR is +33/-13 lines across 3 files. It recovers active Python debugging that was dead since Python 3.11. Tested on Python 3.14.2 with a custom Python ActiveX Script Host exercising the full debug path. Also built a DAP (Debug Adapter Protocol) bridge that enables VS Code to debug ActiveX-hosted Python scripts. Breakpoints, step-into, step-over, step-out, variable inspection, and watch expressions all work correctly through the fixed axdebug module. |
Owner
|
That looks fantastic, thanks! Can you please add a short (ie, non AI generated 😉) changelog entry? |
Contributor
Author
Done. Thank you. Fully hand-crafted change log added. |
wxinix-2022
added a commit
to wxinix/pywin32
that referenced
this pull request
Mar 24, 2026
mhammond
pushed a commit
that referenced
this pull request
Mar 25, 2026
* Fix axdebug 64-bit overflow in sourceContext and stack addresses
On 64-bit Windows, several axdebug COM wrappers use 32-bit types
for values that are 64-bit (DWORDLONG), causing silent overflow:
1. PyIActiveScriptSiteDebug::GetDocumentContextFromPosition
- dwSourceContext is DWORDLONG on 64-bit but parsed as DWORD ("i")
- Both client-side and gateway-side fixed to use "K" format
2. PyIActiveScriptDebug::EnumCodeContextsOfPosition
- Same dwSourceContext overflow, same fix
3. PyIDebugDocumentHelper::DefineScriptBlock
- Return value pdwSourceContext is DWORDLONG but built as "i"
- Gateway side also parses as "i"
4. PyIDebugDocumentHelper::GetScriptBlockInfo
- dwSourceContext parameter, same pattern
5. stackframe.py EnumDebugStackFrames._wrap()
- GetStackAddress() returns 64-bit pointers on x64
- PyGEnumDebugStackFrames::Next uses PyArg_ParseTuple "i" for
dwMin/dwLim which overflows; clamped to 31-bit as workaround
All 32-bit code paths preserved with #ifdef _WIN64 guards.
* Fix clang-format violations in axdebug 64-bit changes
* Address PR review: fix indentation, use proper 64-bit stack frame enum
- Fix clang-format comment indentation in PyIActiveScriptDebug.cpp,
PyIActiveScriptSiteDebug.cpp, and PyIDebugDocumentHelper.cpp
- Use 'k' (unsigned long) format for dwMin/dwLim in
PyIEnumDebugStackFrames Next() instead of 'i' (signed int)
- Remove 31-bit address clamping hack in stackframe.py _wrap()
- Implement IEnumDebugStackFrames64 gateway on 64-bit with Next64()
using 'K' format for full DWORDLONG stack addresses
- Fix step-out in adb.py: skip break-flag checks when returnframe
is set so bdb's set_return() logic controls stopping
* Fix clang-format: accept indentation for comments after braceless if
* Add changelog entry for axdebug fixes (PRs #2723, #2724, #2725)
* Fix clang-format: use braces for if-blocks before @pyparm comments
Add braces to single-statement if-blocks that precede @pyparm doc
comments, so clang-format no longer forces misleading 8-space indent
on the comments. Suggested by @Avasam in PR #2725 review.
* Fix step-out break reason for VS 2022 script debugger
user_line() always reported BREAKREASON_STEP when stop_here triggered
(stopframe match). VS's script debugger treats BREAKREASON_STEP as an
informational step event and auto-resumes during step-out, causing
step-out to skip the caller line and land at the next breakpoint.
Use BREAKREASON_BREAKPOINT when frame is stopframe (definitive stop)
so VS always stops. Keep BREAKREASON_STEP for break_here triggers
(e.g. intermediate steps inside a called function during step-over)
so VS can correctly auto-resume those.
Verified with VS 2022 Professional + real Windows PDM: breakpoints,
F5, F10, F11, and Shift+F11 all work correctly.
---------
Co-authored-by: wxinix-caliper <wuping@caliper.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
PyThreadState_GetFrame,PyFrame_GetCode,PyFrame_GetBack,PyObject_SetAttrString) for Python >= 3.11. Pre-3.11 code pathpreserved with
#ifguards. All new getter APIs return strong references and areproperly
Py_DECREF'd._why_cant_build_extension().stopframematches the returning frame in
dispatch_return, promote it to the caller frameso
dispatch_linestops at the next line.Background
Python 3.11 (PEP 523) made
PyFrameObjectopaque,breaking
axdebug'sSetThreadStateTrace()which directly accessedstate->frame,frame->f_code,frame->f_back, andframe->f_trace. The build was skipped on 3.11+as a workaround.
The replacement getter APIs (
PyFrame_GetCode,PyFrame_GetBack, etc.) are stableacross Python 3.11–3.14.
Changes
AXDebug.cpp —
SetThreadStateTrace()state->framePyThreadState_GetFrame(state)— strong ref, mustPy_DECREFframe->f_codePyFrame_GetCode(frame)— strong ref, mustPy_DECREFframe->f_code->co_filenameviaPyBytes_AsStringPyObject_GetAttrString(code, "co_filename")viaPyUnicode_AsUTF8frame->f_trace = funcPyObject_SetAttrString(frame, "f_trace", func)frame->f_backPyFrame_GetBack(frame)— strong ref, mustPy_DECREFadb.py —
dispatch_return()The original
stop_here()only checkedframe is self.stopframe(identity). Whenstepping over a function call (F10), bdb sets
stopframeto the current frame. Afterthe function returns, the frame object is gone, so stepping never stopped in the caller.
Fix: in
dispatch_return, when the returning frame IS thestopframe, promotestopframetoframe.f_back(the caller). This waydispatch_linein the callermatches and execution stops at the next line.
Tests Performed
axdebug.pydimports successfullyIActiveScriptSiteDebugsupport