Get debug attach to work for 3.12 (#1683)

* Get debug attach to work for 3.12

* Skip flakey test and update binaries

* Fix the skip if to work correctly
This commit is contained in:
Rich Chiodo 2024-09-24 09:55:36 -07:00 committed by GitHub
parent ae6812bdac
commit 25955a05d8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 30 additions and 13 deletions

View file

@ -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")

View file

@ -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)();

View file

@ -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,
};
@ -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...

View file

@ -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
@ -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
};
@ -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;
}
@ -311,6 +310,11 @@ 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.
@ -318,6 +322,7 @@ extern "C"
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
@ -368,6 +373,8 @@ 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);
@ -375,15 +382,16 @@ extern "C"
::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;

View file

@ -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

View file

@ -12,7 +12,7 @@ from tests.patterns import some
@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():