Skip to content

Fix axdebug build and step-over on Python 3.11+#2723

Merged
mhammond merged 2 commits intomhammond:mainfrom
wxinix:fix-axdebug-python311
Mar 23, 2026
Merged

Fix axdebug build and step-over on Python 3.11+#2723
mhammond merged 2 commits intomhammond:mainfrom
wxinix:fix-axdebug-python311

Conversation

@wxinix
Copy link
Copy Markdown
Contributor

@wxinix wxinix commented Mar 22, 2026

Summary

  • AXDebug.cpp: Use opaque frame APIs (PyThreadState_GetFrame, PyFrame_GetCode,
    PyFrame_GetBack, PyObject_SetAttrString) for Python >= 3.11. Pre-3.11 code path
    preserved with #if guards. All new getter APIs return strong references and are
    properly Py_DECREF'd.
  • setup.py: Remove the 3.11+ build skip for axdebug in _why_cant_build_extension().
  • adb.py: Fix step-over not stopping after a function returns. When stopframe
    matches the returning frame in dispatch_return, promote it to the caller frame
    so dispatch_line stops at the next line.

Background

Python 3.11 (PEP 523) made PyFrameObject opaque,
breaking axdebug's SetThreadStateTrace() which directly accessed state->frame,
frame->f_code, frame->f_back, and frame->f_trace. The build was skipped on 3.11+
as a workaround.

The replacement getter APIs (PyFrame_GetCode, PyFrame_GetBack, etc.) are stable
across Python 3.11–3.14.

Changes

AXDebug.cpp — SetThreadStateTrace()

Old (pre-3.11) New (3.11+)
state->frame PyThreadState_GetFrame(state) — strong ref, must Py_DECREF
frame->f_code PyFrame_GetCode(frame) — strong ref, must Py_DECREF
frame->f_code->co_filename via PyBytes_AsString PyObject_GetAttrString(code, "co_filename") via PyUnicode_AsUTF8
frame->f_trace = func PyObject_SetAttrString(frame, "f_trace", func)
frame->f_back PyFrame_GetBack(frame) — strong ref, must Py_DECREF

adb.py — dispatch_return()

The original stop_here() only checked frame is self.stopframe (identity). When
stepping over a function call (F10), bdb sets stopframe to the current frame. After
the function returns, the frame object is gone, so stepping never stopped in the caller.

Fix: in dispatch_return, when the returning frame IS the stopframe, promote
stopframe to frame.f_back (the caller). This way dispatch_line in the caller
matches and execution stops at the next line.

Tests Performed

  • Builds on Python 3.14.2 with VS 2022 Professional
  • axdebug.pyd imports successfully
  • ActiveX Script Host loads Python engine with IActiveScriptSiteDebug support
  • Step-into, step-over, step-out, continue all work correctly
  • Step-over across function return boundaries stops at the caller's next line

@wxinix-2022 wxinix-2022 force-pushed the fix-axdebug-python311 branch 4 times, most recently from 8936867 to 3d6a091 Compare March 22, 2026 20:31
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.
@wxinix-2022 wxinix-2022 force-pushed the fix-axdebug-python311 branch from 3d6a091 to 4a0286a Compare March 22, 2026 20:37
@wxinix
Copy link
Copy Markdown
Contributor Author

wxinix commented Mar 22, 2026

Note: @Avasam

  • Removed dead code block in SetThreadStateTrace() that referenced Python 2.3-era APIs (state->c_tracefunc).
  • Added clang-format off/on around the function to avoid triggering pre-existing formatting violations.

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.

@mhammond
Copy link
Copy Markdown
Owner

That looks fantastic, thanks! Can you please add a short (ie, non AI generated 😉) changelog entry?

@wxinix
Copy link
Copy Markdown
Contributor Author

wxinix commented Mar 23, 2026

That looks fantastic, thanks! Can you please add a short (ie, non AI generated 😉) changelog entry?

Done. Thank you. Fully hand-crafted change log added.

Copy link
Copy Markdown
Owner

@mhammond mhammond left a comment

Choose a reason for hiding this comment

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

Thanks!

@mhammond mhammond merged commit ca18d51 into mhammond:main Mar 23, 2026
28 checks passed
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants