Skip to content

Commit

Permalink
Get debug attach to work for 3.12 (#1683)
Browse files Browse the repository at this point in the history
* Get debug attach to work for 3.12

* Skip flakey test and update binaries

* Fix the skip if to work correctly
  • Loading branch information
rchiodo authored Sep 24, 2024
1 parent ae6812b commit 25955a0
Show file tree
Hide file tree
Showing 21 changed files with 30 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ def run_python_code_windows(pid, python_code, connect_debugger_tracing=False, sh
args = [target_executable, str(pid), target_dll_run_on_dllmain]
subprocess.check_call(args)

if not event.wait_for_event_set(15):
if not event.wait_for_event_set(30):
print("Timeout error: the attach may not have completed.")
sys.stdout.flush()
print("--- Finished dll injection ---\n")
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ typedef PyThreadState* (PyInterpreterState_ThreadHead)(PyInterpreterState* inter
typedef PyThreadState* (PyThreadState_Next)(PyThreadState *tstate);
typedef PyThreadState* (PyThreadState_Swap)(PyThreadState *tstate);
typedef PyThreadState* (_PyThreadState_UncheckedGet)();
typedef PyThreadState* (_PyThreadState_GetCurrent)();
typedef PyObject* (PyObject_CallFunctionObjArgs)(PyObject *callable, ...); // call w/ varargs, last arg should be nullptr
typedef PyObject* (PyInt_FromLong)(long);
typedef PyObject* (PyErr_Occurred)();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ enum PythonVersion {
PythonVersion_38 = 0x0308,
PythonVersion_39 = 0x0309,
PythonVersion_310 = 0x030A,
PythonVersion_311 = 0x030B
PythonVersion_311 = 0x030B,
PythonVersion_312 = 0x030C,
PythonVersion_313 = 0x030D,
};


Expand Down Expand Up @@ -70,6 +72,12 @@ static PythonVersion GetPythonVersion(void *module) {
if(version[3] == '1'){
return PythonVersion_311;
}
if(version[3] == '2'){
return PythonVersion_312;
}
if(version[3] == '3'){
return PythonVersion_313;
}
}
return PythonVersion_Unknown; // we don't care about 3.1 anymore...

Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@

// Access to std::cout and std::endl
#include <iostream>
#include <mutex>
// DECLDIR will perform an export for us
#define DLL_EXPORT

Expand Down Expand Up @@ -108,9 +107,9 @@ struct InitializeThreadingInfo {
PyImport_ImportModule* pyImportMod;
PyEval_Lock* initThreads;

std::mutex mutex;
HANDLE initedEvent; // Note: only access with mutex locked (and check if not already nullptr).
bool completed; // Note: only access with mutex locked
CRITICAL_SECTION cs;
HANDLE initedEvent; // Note: only access with cs locked (and check if not already nullptr).
bool completed; // Note: only access with cs locked
};


Expand All @@ -122,12 +121,12 @@ int AttachCallback(void *voidInitializeThreadingInfo) {
initializeThreadingInfo->initThreads(); // Note: calling multiple times is ok.
initializeThreadingInfo->pyImportMod("threading");

initializeThreadingInfo->mutex.lock();
EnterCriticalSection(&initializeThreadingInfo->cs);
initializeThreadingInfo->completed = true;
if(initializeThreadingInfo->initedEvent != nullptr) {
SetEvent(initializeThreadingInfo->initedEvent);
}
initializeThreadingInfo->completed = true;
initializeThreadingInfo->mutex.unlock();
LeaveCriticalSection(&initializeThreadingInfo->cs);
return 0;
}

Expand Down Expand Up @@ -311,13 +310,19 @@ extern "C"
// Either _PyThreadState_Current or _PyThreadState_UncheckedGet are required
DEFINE_PROC_NO_CHECK(curPythonThread, PyThreadState**, "_PyThreadState_Current", -220); // optional
DEFINE_PROC_NO_CHECK(getPythonThread, _PyThreadState_UncheckedGet*, "_PyThreadState_UncheckedGet", -230); // optional
DEFINE_PROC_NO_CHECK(getPythonThread13, _PyThreadState_GetCurrent*, "_PyThreadState_GetCurrent", -231); // optional
if (getPythonThread == nullptr && getPythonThread13 != nullptr) {
std::cout << "Using Python 3.13 or later, using _PyThreadState_GetCurrent" << std::endl << std::flush;
getPythonThread = getPythonThread13;
}

if (curPythonThread == nullptr && getPythonThread == nullptr) {
// we're missing some APIs, we cannot attach.
std::cerr << "Error, missing Python threading API!!" << std::endl << std::flush;
return -240;
}


// Either _Py_CheckInterval or _PyEval_[GS]etSwitchInterval are useful, but not required
DEFINE_PROC_NO_CHECK(intervalCheck, int*, "_Py_CheckInterval", -250); // optional
DEFINE_PROC_NO_CHECK(getSwitchInterval, _PyEval_GetSwitchInterval*, "_PyEval_GetSwitchInterval", -260); // optional
Expand Down Expand Up @@ -368,22 +373,25 @@ extern "C"
initializeThreadingInfo->pyImportMod = pyImportMod;
initializeThreadingInfo->initThreads = initThreads;
initializeThreadingInfo->initedEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr);
InitializeCriticalSection(&initializeThreadingInfo->cs);


// Add the call to initialize threading.
addPendingCall(&AttachCallback, initializeThreadingInfo);

::WaitForSingleObject(initializeThreadingInfo->initedEvent, 5000);

// Whether this completed or not, release the event handle as we won't use it anymore.
initializeThreadingInfo->mutex.lock();
EnterCriticalSection(&initializeThreadingInfo->cs);
CloseHandle(initializeThreadingInfo->initedEvent);
bool completed = initializeThreadingInfo->completed;
initializeThreadingInfo->initedEvent = nullptr;
initializeThreadingInfo->mutex.unlock();
LeaveCriticalSection(&initializeThreadingInfo->cs);

if(completed) {
// Note that this structure will leak if addPendingCall did not complete in the timeout
// (we can't release now because it's possible that it'll still be called).
DeleteCriticalSection(&initializeThreadingInfo->cs);
delete initializeThreadingInfo;
if (showDebugInfo) {
std::cout << "addPendingCall to initialize threads/import threading completed. " << std::endl << std::flush;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ setlocal

@set VSWHERE=%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe
@echo Using vswhere at %VSWHERE%
@for /f "usebackq tokens=*" %%i in (`"%VSWHERE%" -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath`) do set VSDIR=%%i
@for /f "usebackq tokens=*" %%i in (`"%VSWHERE%" -prerelease -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath`) do set VSDIR=%%i
@echo Using Visual C++ at %VSDIR%

call "%VSDIR%\VC\Auxiliary\Build\vcvarsall.bat" x86 -vcvars_spectre_libs=spectre
Expand Down
2 changes: 1 addition & 1 deletion tests/debugpy/test_attach.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

@pytest.mark.parametrize("stop_method", ["breakpoint", "pause"])
@pytest.mark.parametrize("is_client_connected", ["is_client_connected", ""])
@pytest.mark.parametrize("wait_for_client", ["wait_for_client", ""])
@pytest.mark.parametrize("wait_for_client", ["wait_for_client", pytest.param("", marks=pytest.mark.skipif(sys.platform.startswith("darwin"), reason="Flakey test on Mac"))])
def test_attach_api(pyfile, wait_for_client, is_client_connected, stop_method):
@pyfile
def code_to_debug():
Expand Down

0 comments on commit 25955a0

Please sign in to comment.