Skip to content

Commit de86e82

Browse files
thexaiclaude
andcommitted
gh-152433: Make ctypes callbacks work on Windows UWP builds
UWP does not allow memory to be writable and executable at the same time. Allocate each closure in its own PAGE_READWRITE page, then switch it to PAGE_EXECUTE_READ with VirtualProtectFromApp() and flush the instruction cache once ffi_prep_closure_loc() has written it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent b8dd06f commit de86e82

4 files changed

Lines changed: 61 additions & 15 deletions

File tree

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
:mod:`ctypes` callbacks now work on Windows UWP builds. Closure memory is
2+
allocated writable and switched to executable once the closure is prepared,
3+
since UWP does not allow memory that is writable and executable at once.

‎Modules/_ctypes/callbacks.c‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -545,6 +545,13 @@ CThunkObject *_ctypes_alloc_callback(ctypes_state *st,
545545
goto error;
546546
}
547547

548+
#if defined(MS_WIN32) && !defined(MS_WINDOWS_DESKTOP)
549+
if (Py_ffi_closure_make_executable(p->pcl_write,
550+
sizeof(ffi_closure)) < 0) {
551+
goto error;
552+
}
553+
#endif
554+
548555
p->converters = Py_NewRef(converters);
549556
p->callable = Py_NewRef(callable);
550557
return p;

‎Modules/_ctypes/ctypes.h‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -553,6 +553,11 @@ extern void _ctypes_init_fielddesc(void);
553553
#ifdef USING_MALLOC_CLOSURE_DOT_C
554554
void Py_ffi_closure_free(void *p);
555555
void *Py_ffi_closure_alloc(size_t size, void** codeloc);
556+
#if defined(MS_WIN32) && !defined(MS_WINDOWS_DESKTOP)
557+
/* Make a closure written by ffi_prep_closure_loc() executable (UWP only).
558+
Return 0 on success, or set an exception and return -1 on error. */
559+
int Py_ffi_closure_make_executable(void* p, size_t size);
560+
#endif
556561
#else
557562
#define Py_ffi_closure_free ffi_closure_free
558563
#define Py_ffi_closure_alloc ffi_closure_alloc

‎Modules/_ctypes/malloc_closure.c‎

Lines changed: 46 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,50 @@
2828

2929
/******************************************************************/
3030

31+
#if defined(MS_WIN32) && !defined(MS_WINDOWS_DESKTOP)
32+
/* UWP does not allow memory to be writable and executable at the same time:
33+
VirtualAllocFromApp() rejects PAGE_EXECUTE_READWRITE. Instead, each
34+
closure gets its own page, which is allocated as PAGE_READWRITE so that
35+
ffi_prep_closure_loc() can write the trampoline, and is then switched to
36+
PAGE_EXECUTE_READ by Py_ffi_closure_make_executable(). Using one page
37+
per closure ensures that changing the protection never affects another
38+
closure which may be executing concurrently. This requires the
39+
"codeGeneration" capability in the application manifest. */
40+
41+
void Py_ffi_closure_free(void *p)
42+
{
43+
VirtualFree(p, 0, MEM_RELEASE);
44+
}
45+
46+
void *Py_ffi_closure_alloc(size_t size, void** codeloc)
47+
{
48+
void *p = VirtualAllocFromApp(NULL, size, MEM_COMMIT | MEM_RESERVE,
49+
PAGE_READWRITE);
50+
if (p == NULL) {
51+
return NULL;
52+
}
53+
#ifdef _M_ARM
54+
// set Thumb bit so that blx is called correctly
55+
*codeloc = (void *)((uintptr_t)p | 1);
56+
#else
57+
*codeloc = p;
58+
#endif
59+
return p;
60+
}
61+
62+
int Py_ffi_closure_make_executable(void *p, size_t size)
63+
{
64+
ULONG old_protect;
65+
if (!VirtualProtectFromApp(p, size, PAGE_EXECUTE_READ, &old_protect)
66+
|| !FlushInstructionCache(GetCurrentProcess(), p, size))
67+
{
68+
PyErr_SetFromWindowsErr(0);
69+
return -1;
70+
}
71+
return 0;
72+
}
73+
74+
#else /* MS_WINDOWS_DESKTOP or not MS_WIN32 */
3175

3276
#ifdef Py_GIL_DISABLED
3377
static PyMutex malloc_closure_lock;
@@ -68,25 +112,10 @@ static void more_core(void)
68112

69113
/* allocate a memory block */
70114
#ifdef MS_WIN32
71-
#ifdef MS_WINDOWS_DESKTOP
72115
item = (ITEM *)VirtualAlloc(NULL,
73116
count * sizeof(ITEM),
74117
MEM_COMMIT,
75118
PAGE_EXECUTE_READWRITE);
76-
#else // UWP
77-
/* Due security restrictions, UWP not allows request Read-Write-Execute permissions at once.
78-
The correct flow in UWP for execute dynamic code in memmory is:
79-
1. Alloate as Read-Write (PAGE_READWRITE) and write dynamic code to memmory.
80-
2. Change to Executable (PAGE_EXECUTE_READ) with 'VirtualProtectFromApp'.
81-
3. Flush cache with 'FlushInstructionCache' to ensure CPU instruction cache coherency
82-
before executing the generated code.
83-
TODO: Implement 2 and 3 in the appropriate places. For now, this defers
84-
the error from import time to time of use (or never, if an app avoids it). */
85-
item = (ITEM*)VirtualAllocFromApp(NULL,
86-
count * sizeof(ITEM),
87-
MEM_COMMIT | MEM_RESERVE,
88-
PAGE_READWRITE);
89-
#endif // !MS_WINDOWS_DESKTOP
90119
if (item == NULL)
91120
return;
92121
#else
@@ -176,3 +205,5 @@ void *Py_ffi_closure_alloc(size_t size, void** codeloc)
176205
FT_MUTEX_UNLOCK(&malloc_closure_lock);
177206
return (void *)item;
178207
}
208+
209+
#endif /* MS_WINDOWS_DESKTOP or not MS_WIN32 */

0 commit comments

Comments
 (0)