Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Get debug attach to work for 3.12 #1683

Merged
merged 3 commits into from
Sep 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading